Skip to main content

Rover2: Serial Motor Controller

My new rover is supposed to be simpler than the first one and something that irked me with the first design was the motor controllers.  They worked, but they are ridiculously complicated.  So I bought a Sparkfun Monster Moto Controller and hooked it up - much simpler.

The only hassles with it is that if you would plug another board on top of it, then it could short to the tops of the capacitors and the VIN connector could short to the Arduino ICS pins underneath it.  I stuck a rectangle of clear plastic cut from some screws packaging between the boards and snipped the ICS pins off - done.

Serial Control

Controlling a DC motor is straight forward, using two pins to switch the H bridge direction (INA1, INB1) and one for speed PWM (PWM1).  There is also a current sense input (CS1) that you can set to turn the motors off if they get stuck and the current increases too much.  You'll have to set the sense level with trial and terror.

Here is an example for a serial motor control interface, using simple two character messages:
  • ms - stop
  • mf - forward
  • mb - backward
  • mr - turn right
  • ml - turn left
  • mu - speed up
  • md - slow down
This is also a good example of how to parse a serial data protocol efficiently with a switch-case statement in C.  A switch statement is easy to read and understand by humans and very efficient on computers.  Each case compiles to a check and a conditional jump.  It doesn't slog through all the code from top to bottom.

// Monster Moto Board
// Herman Oosthuysen, Sep 2016

// Monster motor controller with simple two character ASCII serial interface
// Controls two motors to make a rover run forward reverse and turn
// Turns are executed by speeding up the motor on one side and slowing down (or reversing) on other side
// A command starts with M and ends with Enter (CR or LF)
// MS - Stop, short the motors to ground
// MF - Forward, best to slow down and stop before going backward
// MB - Backward, best to slow down and stop before going forward
// ML - Turn Left, cancel a left turn with a right turn
// MR - Turn Right, cancel a right turn with a left turn
// MU - Speed Up, only three speed steps, stop, slow and fast
// MD - Slow Down, only three speed steps, stop, slow and fast

// Literals
#define BAUD  9600
#define RATE  200
#define CR    0x0D
#define LF    0x0A
#define MAX   2
#define MIN   -2
#define INC   0x20
#define MAXCS 0x80

// Pins
// Motor 1
#define INA1  7
#define INB1  8
#define PWM1  5
#define EN1   A0
#define CS1   A2

// Motor 2
#define INA2  4
#define INB2  9
#define PWM2  6
#define EN2   A1
#define CS2   A3

// Global Variables
int cs1 = 0;  // 0 to FF
int cs2 = 0;  // 0 to FF
int spd1 = 0; // -2 to 2
int spd2 = 0; // -2 to 2
int pwm1 = 0; // 0 to 1023
int pwm2 = 0; // 0 to 1023
char ch = 0;  // ASCII character received
char adr = 0; // ASCII M start of Motor message
char cmd = 0; // ASCII command


void setup()
{
  Serial.begin(BAUD);
  Serial.println("Monster Moto, eh.");
 
  // Stop the motors
  pinMode(INA1, OUTPUT);
  digitalWrite(INA1, LOW);
 
  pinMode(INA2, OUTPUT);
  digitalWrite(INA2, LOW);
 
  pinMode(INB1, OUTPUT);
  digitalWrite(INB1, LOW);
 
  pinMode(INB2, OUTPUT);
  digitalWrite(INB2, LOW);

  // PWM zero speed
  analogWrite(PWM1, 0);
  analogWrite(PWM2, 0);
}


void loop()
{
  if(Serial.available())
  { 
    ch = Serial.read();

    // Message starts with M and ends with Enter
    // eg: ms[enter]
    // Ensure that the serial terminal sends the line ends
    if((ch == CR) | (ch == LF))
    {
      adr = 0;
      cmd = 0;
    }
    else if((ch == 'M') | (ch == 'm'))
    {
      adr = 1;
      cmd = 'M';
    }
    else if(adr)
    {
      cmd = ch;
      Serial.print("cmd = ");

        switch(cmd)
        {
          case 'S':
          case 's':
            spd1 = 0;
            spd2 = 0;
            pwm1 = 0;
            pwm2 = 0;
            Serial.println(cmd);
            break;
   
          case 'F':
          case 'f':
            spd1 = 1;
            spd2 = 1;
            pwm1 = spd1 * INC;
            pwm2 = pwm1;
            Serial.println(cmd);
            break;
   
          case 'B':
          case 'b':
            spd1 = -1;
            spd2 = -1;
            pwm1 = abs(spd1) * INC;
            pwm2 = pwm1;
            Serial.println(cmd);
            break;
   
          case 'L':
          case 'l':
            spd1--;
            if(spd1 < MIN)
              spd1 = MIN;
            spd2++;
            if(spd2 > MAX)
              spd2 = MAX;
            pwm1 = abs(spd1) * INC;   
            pwm2 = abs(spd2) * INC;  
            Serial.println(cmd);
            break;
   
          case 'R':
          case 'r':
            spd1++;
            if(spd1 > MAX)
              spd1 = MAX;
            spd2--;
            if(spd2 < MIN)
              spd2 = MIN;
            pwm1 = abs(spd1) * INC;
            pwm2 = abs(spd2) * INC;        
            Serial.println(cmd);
            break;
   
          case 'U':
          case 'u':
            spd1++;
            if(spd1 > MAX)
              spd1 = MAX;
            spd2++;
            if(spd2 > MAX)
              spd2 = MAX;
            pwm1 = abs(spd1) * INC;
            pwm2 = abs(spd2) * INC;       
            Serial.println(cmd);
            break;
   
          case 'D':
          case 'd':
            spd1--;
            if(spd1 < MIN)
              spd1 = MIN;
            spd2--;
            if(spd2 < MIN)
              spd2 = MIN;
            pwm1 = abs(spd1) * INC; 
            pwm2 = abs(spd2) * INC;       
            Serial.println(cmd);
            break;
   
          default:
            Serial.println();
            Serial.print("Err = ");
            Serial.println(cmd);
            break;
        }
        Serial.print("spd1 = ");
        Serial.println(spd1);
        Serial.print("spd2 = ");
        Serial.println(spd2);
        Serial.print("pwm1 = ");
        Serial.println(pwm1);
        Serial.print("pwm2 = ");
        Serial.println(pwm2);  
    }
  }

  // Periodic Motor Control Update
  sense();
  direction(spd1, spd2);
  speed(pwm1, pwm2);
  delay(RATE);
}


// Left and right hand motors rotate in opposite directions
void direction(int spd1, int spd2)
{
  if(spd1 >= 0)
  {
    digitalWrite(INA1, HIGH);  // CW - Forward
    digitalWrite(INB1, LOW);
  }
  else
  {
    digitalWrite(INA1, LOW);  // CCW - Reverse
    digitalWrite(INB1, HIGH);         
  }
 
  if(spd2 >= 0)
  {
    digitalWrite(INA2, LOW);  // CCW - Forward
    digitalWrite(INB2, HIGH);
  }
  else
  {
    digitalWrite(INA2, HIGH);  // CW - Reverse
    digitalWrite(INB2, LOW);         
  }
}


void speed(int pwm1, int pwm2)
{
  analogWrite(PWM1, pwm1);
  analogWrite(PWM2, pwm2);
}


void sense(void)
{
  cs1 = analogRead(CS1);
  cs2 = analogRead(CS2);

  if((cs1 > MAXCS) | (cs2 > MAXCS))
  {
    spd1 = 0;
    spd2 = 0;
  }
}


PWM motor control is not linear.  The motors need a minimum amount of power to start turning and after that, the speed increases rapidly until a maximum is reached.  So the performance curve is S shaped and some experiments are required with your motors if you want to have meaningful speed steps for crawl, walk and run for example.

Seriously High Power

MOSFETs can be paralleled, so if you need to control a very big motor, then you can wire the two channels together, so this is a really nice controller board that could control a winch, a scooter or a wheel chair for example.  So it would be good if you need to build something to help a disabled friend - Sparkfun to the rescue!

A high powered motor controller should be close to the motor because of the high currents in the wires.  So in a big system, you may need one Arduino per motor controller.  If you use the same serial interface protocol on multiple Arduino powered actuators, then you can multi-drop the serial line from the main control computer to the various actuator computers.  For this example, the messages start with M and for something else, it could start with S or whatever else you like.

For a simple toy, error checks are a waste of time, so I just send a message and hope it works.  I don't bother with CRCs and Retries on toys, but if you want to control a winch or a wheel chair, then you should be more careful!

BTW, if you need to build something serious operating at 12V, then I recommend that you get Anti-Gravity batteries.   These are light weight and will ensure that one can still manhandle the thing - lead-acid batteries make it impossible to lift a wheel-chair into a car.

 
La Voila!

Herman

Comments

Popular posts from this blog

Parasitic Quadrifilar Helical Antenna

This article was reprinted in OSCAR News, March 2018:  http://www.amsat-uk.org If you want to receive Satellite Weather Pictures , then you need a decent antenna, otherwise you will receive more noise than picture. For polar orbit satellites, one needs an antenna with a mushroom shaped radiation pattern .  It needs to have strong gain towards the horizon where the satellites are distant, less gain upwards where they are close and as little as possible downwards, which would be wasted and a source of noise.  Most satellites are spin stabilized and therefore the antenna also needs circular polarization, otherwise the received signal will flutter as the antennas rotate through nulls. The helical antenna, first proposed by Kraus in 1948, is the natural solution to circular polarized satellite communications.  It is a simple twisted wire - there seems to be nothing to it.  Various papers have been published on helix antennas, so the operation is pretty well ...

Unlock CRA PDF Forms

Unlock Canada Revenue Agency PDF Forms It appears that there is a relatively new PDF feature to prevent casual copying and saving of a file and that some programs save PDF files with these foolish features active by default.  Many forms from the Canada Revenue Agency are locked in this way, which makes it difficult to do one's taxes, since one can fill the form, but cannot save it.  One can only print the form.  It should be possible to print to a file or export it to a new PDF file, but it is far better to reset the annoying anti-taxpayer flags, since the 'printed' form cannot be edited easily any more and I always manage to make a mistake or three that need to be corrected after review. If there is a Linux (virtual) machine handy, install qpdf and use it to reset the silly flags: $ su - password # dnf update # dnf install qpdf # exit $ qpdf --decrypt lockedfile.pdf unlockedfile.pdf One doesn't need a password to unlock these flags, so the fix is instant. La voila! He...

To C or not to C, That is the Question

As most would know, the Kernighan and Ritchie C Programming Language is an improved version of B, which is a simplified version of BCPL, which is derived from ALGOL, which is the Ur computer language that started the whole madness, when Adam needed an operating system for his Abacus, to count Eve's apples in the garden of Eden in Iraq.  The result is that C is my favourite, most hated computer language , which I use for everything. At university, I learned FORTRAN with punch cards on a Sperry-Univac, in order to run SPICE, to simulate an operational amplifier.  Computers rapidly lost their glamour after that era! Nobody taught me C.  I bought the book and figured it out myself. Over time, I wrote a couple of assemblers, a linker-locator, various low level debuggers and schedulers and I even fixed a bug in a C compiler - not because I wanted to, but because I had to, to get the job done!   Much of my software work was down in the weeds with DSP and radio modems...