Decoding and mixing RC signals

Hi,
I am trying to control 2 motor drives (without reverse) corresponding to left and right wheels with RC and arduino.

I found the following code here. I am using only the INT0 and INT1 inputs and the D10 and D11 for pwm output and it is working quite nice.

My problem is that I need to be able to spin on spot (almost) by driving one side motor while the other remains still. If I turn the steering stick left and right while the throttle is on neutral, nothing happens.

What I want is, if throttle is neutral and steering goes to min. - activate right motor, and if steering goes to max. - activate the left motor.

I'm assuming it has something to do with gGear in the code but I'm unable to fully understand the code to be able to modify it...

Any help will be really appreciated!

(I had to delete some comments in the code as it was to long to be posted)

//geeky singh 14th dec 2015 for instructables.com

#define SRC_NEUTRAL 1500
#define SRC_MAX 2000
#define SRC_MIN 1000
#define TRC_NEUTRAL 1500
#define TRC_MAX 2000
#define TRC_MIN 1000
#define RC_DEADBAND 50
#define ERROR_center 50
#define pERROR 100  

uint16_t unSteeringMin = SRC_MIN + pERROR;
uint16_t unSteeringMax = SRC_MAX - pERROR;
uint16_t unSteeringCenter = SRC_NEUTRAL;

uint16_t unThrottleMin = TRC_MIN + pERROR;
uint16_t unThrottleMax = TRC_MAX - pERROR;
uint16_t unThrottleCenter = TRC_NEUTRAL;

#define PWM_MIN 0
#define PWM_MAX 255

#define GEAR_NONE 1
#define GEAR_IDLE 1
#define GEAR_FULL 2

#define PWM_SPEED_LEFT 10
#define PWM_SPEED_RIGHT 11
#define LEFT1 5
#define LEFT2 6
#define RIGHT1 7
#define RIGHT2 8

#define PROGRAM_PIN 9

// Assign your channel in pins
#define THROTTLE_IN_PIN 2
#define STEERING_IN_PIN 3

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2

// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediately take local copies so that the ISR can keep ownership of the shared ones.
// To access these in loop we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on as quickly as possible
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;

// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;

uint8_t gThrottle = 0;
uint8_t gGear = GEAR_NONE;
uint8_t gOldGear = GEAR_NONE;

#define DIRECTION_STOP 0
#define DIRECTION_FORWARD 1
#define DIRECTION_REVERSE 2
#define DIRECTION_ROTATE_RIGHT 3
#define DIRECTION_ROTATE_LEFT 4

uint8_t gThrottleDirection = DIRECTION_STOP;
uint8_t gDirection = DIRECTION_STOP;
uint8_t gOldDirection = DIRECTION_STOP;

#define IDLE_MAX 50

#define MODE_RUN 0

uint8_t gMode = MODE_RUN;

unsigned long pulse_time  ;

void setup()
{
Serial.begin(9600);
Serial.println("hello");

attachInterrupt(0 /* INT0 = THROTTLE_IN_PIN */,calcThrottle,CHANGE);
attachInterrupt(1 /* INT1 = STEERING_IN_PIN */,calcSteering,CHANGE);

pinMode(PWM_SPEED_LEFT,OUTPUT);
pinMode(PWM_SPEED_RIGHT,OUTPUT);
pinMode(LEFT1,OUTPUT);
pinMode(LEFT2,OUTPUT);
pinMode(RIGHT1,OUTPUT);
pinMode(RIGHT2,OUTPUT);
pinMode(12,OUTPUT);
pulse_time =millis() ;
pinMode(PROGRAM_PIN,INPUT);
}

void loop()
{
 // create local variables to hold a local copies of the channel inputs
 // these are declared static so that thier values will be retained
 // between calls to loop.
 static uint16_t unThrottleIn;
 static uint16_t unSteeringIn;
 // local copy of update flags
 static uint8_t bUpdateFlags;
// fail_safe();

 if(bUpdateFlagsShared)
 {
  noInterrupts();
   pulse_time =millis() ;

  bUpdateFlags = bUpdateFlagsShared;

  if(bUpdateFlags & THROTTLE_FLAG)
  {
   unThrottleIn = unThrottleInShared;
  }

  if(bUpdateFlags & STEERING_FLAG)
  {
   unSteeringIn = unSteeringInShared;
  }

  bUpdateFlagsShared = 0;

  interrupts();
 }

  // do any processing from here onwards
  // only use the local values unAuxIn, unThrottleIn and unSteeringIn
  
  if(gMode == MODE_RUN)
  {
    // we are checking to see if the channel value has changed, this is indicated 
    // by the flags.
    if(bUpdateFlags & THROTTLE_FLAG)
    {
      unThrottleIn = constrain(unThrottleIn,unThrottleMin,unThrottleMax);
      
      if(unThrottleIn > (unThrottleCenter + ERROR_center))
      {
        gThrottle = map(unThrottleIn,(unThrottleCenter + ERROR_center),unThrottleMax,PWM_MIN,PWM_MAX);
        gThrottleDirection = DIRECTION_FORWARD;
      }
      else if (unThrottleIn < (unThrottleCenter - ERROR_center))
      {
        gThrottle = map(unThrottleIn,unThrottleMin,(unThrottleCenter- ERROR_center),PWM_MAX,PWM_MIN);
        gThrottleDirection = DIRECTION_REVERSE;
      }
      else
      {
      gThrottleDirection =DIRECTION_STOP;
      gThrottle=0;
      }
  
      if(gThrottle < IDLE_MAX)
      {
        gGear = GEAR_IDLE;
      }
      else
      {
        gGear = GEAR_FULL;
      }
    }
    if(bUpdateFlags & STEERING_FLAG)
    {
      uint8_t throttleLeft = gThrottle;
      uint8_t throttleRight = gThrottle;
  
      gDirection = gThrottleDirection;
      
      // see previous comments regarding trapping out of range errors
      // this is left for the user to decide how to handle and flag
      unSteeringIn = constrain(unSteeringIn,unSteeringMin,unSteeringMax);
  
      // if idle spin on spot
      switch(gGear)
      {
      case GEAR_IDLE:
        if(unSteeringIn > (unSteeringCenter + RC_DEADBAND))
        {
          gDirection = DIRECTION_ROTATE_RIGHT;
          // use steering to set throttle
          throttleRight = throttleLeft = map(unSteeringIn,unSteeringCenter,unSteeringMax,PWM_MIN,PWM_MAX);
        }
        else if(unSteeringIn < (unSteeringCenter - RC_DEADBAND))
        {
          gDirection = DIRECTION_ROTATE_LEFT;
          // use steering to set throttle
          throttleRight = throttleLeft = map(unSteeringIn,unSteeringMin,unSteeringCenter,PWM_MAX,PWM_MIN);
        }
        break;
      // if not idle proportionally restrain inside track to turn vehicle around it
      case GEAR_FULL:
        if(unSteeringIn > (unSteeringCenter + RC_DEADBAND))
        {
          throttleLeft = map(unSteeringIn,unSteeringCenter,unSteeringMax,gThrottle,PWM_MIN);
        }
        else if(unSteeringIn < (unSteeringCenter - RC_DEADBAND))
        {
          throttleRight = map(unSteeringIn,unSteeringMin,unSteeringCenter,PWM_MIN,gThrottle);
        }
        break;
      }
      analogWrite(PWM_SPEED_LEFT,throttleLeft);
      analogWrite(PWM_SPEED_RIGHT,throttleRight);
    }
  }
  if((gDirection != gOldDirection) || (gGear != gOldGear))
  {
    gOldDirection = gDirection;
    gOldGear = gGear;

 digitalWrite(LEFT1,LOW);
 digitalWrite(LEFT2,LOW);
 digitalWrite(RIGHT1,LOW);
 digitalWrite(RIGHT2,LOW);

switch(gDirection)
{
case DIRECTION_FORWARD:
 digitalWrite(LEFT1,LOW);
 digitalWrite(LEFT2,HIGH);
 digitalWrite(RIGHT1,LOW);
 digitalWrite(RIGHT2,HIGH);
 break;
case DIRECTION_REVERSE:
 digitalWrite(LEFT1,HIGH);
 digitalWrite(LEFT2,LOW);
 digitalWrite(RIGHT1,HIGH);
 digitalWrite(RIGHT2,LOW);
 break;
case DIRECTION_ROTATE_RIGHT:
 digitalWrite(LEFT1,HIGH);
 digitalWrite(LEFT2,LOW);
 digitalWrite(RIGHT1,LOW);
 digitalWrite(RIGHT2,HIGH);
 break;
case DIRECTION_ROTATE_LEFT:
 digitalWrite(LEFT1,LOW);
 digitalWrite(LEFT2,HIGH);
 digitalWrite(RIGHT1,HIGH);
 digitalWrite(RIGHT2,LOW);
 break;
case DIRECTION_STOP:
 digitalWrite(LEFT1,LOW);
 digitalWrite(LEFT2,LOW);
 digitalWrite(RIGHT1,LOW);
 digitalWrite(RIGHT2,LOW);
 break;
}
  }
  bUpdateFlags = 0;
}

void calcThrottle()
{
  if(digitalRead(THROTTLE_IN_PIN) == HIGH)
  {
    ulThrottleStart = micros();
  }
  else
  {
    unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
    bUpdateFlagsShared |= THROTTLE_FLAG;
  }
}

void calcSteering()
{
  if(digitalRead(STEERING_IN_PIN) == HIGH)
  {
    ulSteeringStart = micros();
  }
  else
  {
    unSteeringInShared = (uint16_t)(micros() - ulSteeringStart);
    bUpdateFlagsShared |= STEERING_FLAG;
  }
}

That's a pretty neat code. It looks simple enough.

It looks at the throttle signal and uses that to decide if it's 'in gear' or not. If it's less than the idle threshold then moving the steering is supposed to drive the motors in opposite directions, to spin on the spot.

If it's not detecting that idle position, then check that the throttle signal is getting into that idle range. Print out the unThrottleIn variable before it gets passed through the constrain() function. Print out the gThrottle result after the processing. You may need to change the value of IDLE_MAX or some of the other parameters to suit the actual values that your RC is producing.

I think that the LEFT1, LEFT2, RIGHT1 and RIGHT2 are for the motors direction change. But as I have only forward direction on my motors, they will spin for both forward and reverse.

But anyway, if the throttle is neutral, moving the steering left or right doesn't do anything. And in this case I want to have the right motor spinning for the left steering input and vice versa.

The code says different. It says that if gGear==GEAR_IDLE then it will drive both motors in opposite directions.

If your motors are somehow unable to go in reverse then you need to change the code so it doesn't attempt to drive both of them for an on-the-spot turn.

paulus_v:
Hi,
I am trying to control 2 motor drives (without reverse) corresponding to left and right wheels with RC and arduino.

I found the following code here. I am using only the INT0 and INT1 inputs and the D10 and D11 for pwm output and it is working quite nice.

My problem is that I need to be able to spin on spot (almost) by driving one side motor while the other remains still. If I turn the steering stick left and right while the throttle is on neutral, nothing happens.

What I want is, if throttle is neutral and steering goes to min. - activate right motor, and if steering goes to max. - activate the left motor.

XY to Tank was a challenge for me too :slight_smile: instead of re-inventing the wheel I have also created a library to do this. My library can control the motors fully forward and reverse with any position of the xy stick. I added a no reverse switch to meet your request.

Note: The Tank library uses what are called function callbacks (attachInterrupts ( ) uses a callback function in the same way) where you create a function to do something that my library can't predict like how to drive your HBridge or other motor control. you then give my library the name of your function MyTank.GoRightDrive ( RightMotorDrive ) ; and my library will calls it when needed at a later time. The following function **MyTank.TankPWM ( ) ; **Triggers these callback function at the appropriate time to allow for proper response from your motor controllers.

Test sample code:

#include "Tank.h"

#define PWM_A     (6)   // 0-255             Arduino Digital pin 6
#define PWM_B     (3)   // 0-255             Arduino Digital pin 3
#define HBgA1HighEn (8) // Enable/Disable    Arduino Digital pin 8
#define HBgA2HighEn (7) // Enable/Disable    Arduino Digital pin 7
#define HBgB1HighEn (4) // Enable/Disable    Arduino Digital pin 4
#define HBgB2HighEn (5) // Enable/Disable    Arduino Digital pin 5

#define StopLevel 255 // set this to zero for full coasting and match the value set to SetInputMax(255); for full breaking 
#define NoReverse 1// prevents motors from driving in reverse
Tank MyTank; // create an instance of Tank class calle MyTank

// this is your callback function you will hand my code 
/* 
int8_t is the same as a char or a number +-128 no to be confused with byte 0-255
int16_t is the same as int or a number +- 32,767
Direction hands the following values 0 = stop, -1 = reverse +1 = forward
Power is a value between 0 and MyTank.SetInputMax(255); defaults to 255 for standard PWM use
your function name can be what ever you please just use the same name when handing to my class:
MyTank.GoRightDrive(RightMotorDrive); // Store the function callback
*/ 
void RightMotorDrive(int8_t Direction, int16_t Power) { //Direction 1 = forward
    if (Direction == 0) { // full breaking STOP
      digitalWrite(HBgA1HighEn, LOW); // H-Bridge on Low side
      digitalWrite(HBgA2HighEn, LOW); // H-Bridge on Low side
      analogWrite(PWM_A, StopLevel); // 255 is full breaking and 0 is full costing 
      return;
    }
    analogWrite(PWM_A, (NoReverse && Direction < 0) ? 0 : Power); // Motor Speed Control
    digitalWrite(HBgA1HighEn, (Direction < 0) ? HIGH : LOW); // Motor Direction Control 
    digitalWrite(HBgA2HighEn, (Direction > 0) ? HIGH : LOW); // Motor Direction Control 

  }
void LeftMotorDrive(int8_t Direction, int16_t Power) {
    if (Direction == 0) {
      digitalWrite(HBgB1HighEn, LOW);
      digitalWrite(HBgB2HighEn, LOW);
      analogWrite(PWM_B, StopLevel);
      return;
    }
    analogWrite(PWM_B, (NoReverse && Direction < 0) ? 0 : Power);
    digitalWrite(HBgB1HighEn, (Direction < 0) ? HIGH : LOW);
    digitalWrite(HBgB2HighEn, (Direction > 0) ? HIGH : LOW);
  }

void setup() {
  //Tank H-Bridge PWM Calls:
  // your input range must be Signed number between -255 and +255 with SetInputMax set at 255 
  MyTank.SetInputMax(255); // Set this to match your input +- Value with zero being stick in center 
  // your output range will be handed to your above function number between 0 and 255 with this set at 255 PWM 
  // default is 255 for standard PWM
  MyTank.SetOutputMax(255); // Set this to match your output (PWM) range

  MyTank.GoRightDrive(RightMotorDrive); // Store the function callback
  MyTank.GoLeftDrive(LeftMotorDrive);   // Store the function callback
}

void loop() {
  int X = 0; // forward and reverse
  int Y = 0; // Left and right
  // GET your reading from you input and set then into X and Y with the range of -255 to +255 (or change the input range above to your liking) with Zero as stop
  MyTank.XYtoTank( X,  Y); // Calculate from XY single Stick to Tank Left motor and Right motor
  MyTank.TankPWM();  // run the motors Calls the above motor drive functions we created above

}

The above code does compile but it has been simplified for posting on this forum and has not been actually tested on my tank
I added no reverse flag which has not been tested either.

The attached libraries have been fully tested!

Tank.cpp (2.03 KB)

Tank.h (1.25 KB)

Thanks MorganS and zhomeslice!

I'm still learning programming so I tried to fully understand the code before trying to modify it. Just learned about interrupts, switch/ case statements, and other things I didn't knew and luckily my first try worked as expected.

When I'll have the time, I'll try the Tank library but still need to learn how to read and use libraries..

One thing that I did not understand is about the steering mixing with throttle.

In case GEAR_FULL:

for right? turning:

throttleLeft = map(unSteeringIn,unSteeringCenter,unSteeringMax,gThrottle,PWM_MIN)

where

gThrottle = map(unThrottleIn,(unThrottleCenter + ERROR_center),unThrottleMax,PWM_MIN,PWM_MAX)

I don't understand how is the steering value added to the throttleLeft through gThrottle?

paulus_v:
Thanks MorganS and zhomeslice!

I'm still learning programming so I tried to fully understand the code before trying to modify it. Just learned about interrupts, switch/ case statements, and other things I didn't knew and luckily my first try worked as expected.

When I'll have the time, I'll try the Tank library but still need to learn how to read and use libraries..

One thing that I did not understand is about the steering mixing with throttle.

In case GEAR_FULL:

for right? turning:

throttleLeft = map(unSteeringIn,unSteeringCenter,unSteeringMax,gThrottle,PWM_MIN)

where

gThrottle = map(unThrottleIn,(unThrottleCenter + ERROR_center),unThrottleMax,PWM_MIN,PWM_MAX)

I don't understand how is the steering value added to the throttleLeft through gThrottle?

Can you drive in reverse? Or do you only want forward?

zhomeslice:
Can you drive in reverse? Or do you only want forward?

Only forward with some cheap chinese drives. Now I'm waiting for a sabertooth dual drive that works with RC directly, but still learning arduino coding in the meantime...

paulus_v:
Only forward with some cheap chinese drives. Now I'm waiting for a sabertooth dual drive that works with RC directly, but still learning arduino coding in the meantime...

What's the part number of the breakout board of the cheap chinese drives?
Z