Saturday, September 24, 2016

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

No comments:

Post a Comment

On topic comments are welcome. Junk will be deleted.