Differential Steering using a joystick / programming question....

Hi Guys,

I am brand new here, so please forgive my "noobie"ness. This should be quite a simple problem, but it is driving me loopy...

Basically, I want to control two motors (forward fast / slow and steering via differential speed).

The inputs are: X-axis potentiometer (turn) is connected to pin A1 and Y-axis (move) pot is connected to A0.
0,-128 is bottom left and 255,128 is top right. There is a "forward only" strip in the centre from -10 to +10, where turning signal is ignored (just to make it not too squirrely).

Outputs are: left motor digital pin 10 and right motor digital pin 9.

I have written a sketch which sort of works, but whenever the "turn" value is greater than the "move" value, it starts behaving erratically.

I have been trying to figure this out all day, and am starting to lose the plot!! Please find the code below... any ideas? This is my first real go at programming, so please be kind.... :slight_smile: Thanks, Tim

/* This script is intended to operate a differential steering system, but is
a little buggy. Whaenever the Y value is greater than the X value, it reverses
it's operation.
*/

const int LMotor=10;
const int RMotor=9;
int val = 0;
int off = 0;

int move;
int turn;

void setup() {

pinMode(A0, INPUT);
pinMode(A1, INPUT);

pinMode(LMotor,OUTPUT);
pinMode(RMotor,OUTPUT);

Serial.begin(9600);
}

void loop() {
// read the X (turning) input on analog pin A1 and Y (ahead) input
//on analog pin A0:

int aheadY = analogRead(A0);
int turnX = analogRead(A1);

move = map(aheadY, 0,1023, 0, 255);
move = constrain(move, 0, 255);

turn = map(turnX, 0,1023,-128,127);
turn = constrain(turn, -128,127);

Serial.print("Move ");
Serial.println(move);

Serial.print(" Turn ");
Serial.println(turn);

if (turn >=-10 && turn <=10) // go forward
{
analogWrite (LMotor,move);
analogWrite (RMotor,move);
}
if (turn > move) // this sequence seems to
{
analogWrite (LMotor,0); // have absolutely no effect..
analogWrite (RMotor,0);
}

if (turn <-10) // forward left
{
analogWrite (LMotor,move + turn);
analogWrite (RMotor,move);
}

if (turn >10) // forward right
{
analogWrite (LMotor,move);
analogWrite (RMotor,move - turn);
}
}

Here is a photo of the offending item....

Your circuit will only turn the motor on or off. there is no way to get reverse with that circuit. a one one the Arduino output will turn the motor one, zero off. you will need an H-bridge to get forward and reverse. there are lots of examples for dc motor control.

Hi GF,

Thanks for the reply.

For this application, I don't need reverse, just to be able to control speed and rate of turn.

If I was really clever, I would even be able to program the radius of turn at a given speed, but I am just not that smart..... :frowning:

:slight_smile:

Tim

I'm going to try to explain something - and once I do, it will either get your brain running again - or it just might be a "doh!" moment...

You have two axes on the joystick - an x-axis and a y-axis, right? So - let's imagine you have the potentiometers of each axis connected to an analog input on the Arduino. So - what happens then if you push the joystick all the way up into the upper-left hand corner?

That's right - your Arduino is going to read "0" for the x-axis, and "0" for the y-axis (note: I am ignoring any possibility that calibration of the joystick will be needed - in reality, this is most likely - I don't know if you are old enough or not to remember old DOS games, and how you had to calibrate the analog joystick before playing a game - the routine was a part of the game itself).

Now - what happens if you then push the joystick to the lower-right corner?

Yes - your Arduino will read "1023" for the x-axis, and "1023" for the y-axis...

So - what does this mean? Well - here are the values (more or less):

0,0     511,0   1023,0
  \       |       /
   \      |      /
    \     |     /
     \    |    /
      \   |   /
       \  |  /
        \ | /
         \|/
0,511-----+-----1023,511
         /|\    
        / | \        
       /  |  \
      /   |   \
     /    |    \
    /     |     \
   /      |      \
  /       |       \
0,1023 511,1023 1023,1023

Now - if you subtract 511 from each axis - that'll "center-up" the values - which means that the upper-left will be "-511,-511", the center will be "0,0", and the lower-right will be "512, 512"...and the other values will change similarly - understand?

Now - given that - what can you do?

Well - what if you said "my x-axis value will drive my left wheel" and "my y-axis value will drive my right wheel" - and then you scaled the values for the PWM, and based on the sign of the value, switched the PWM from one side or the other of the h-bridge...

So - let's suppose positive values mean "forward" and negative values mean "reverse". So now, at rest - the joystick (auto-centered) would read "0,0" - and a PWM values of "0" would be sent to each motor, and they would remain "off".

Then, when you pushed the joystick to the bottom-right corner, you are reading "512,512", then after scaling and sign checking - you apply PWM values of "255,255" to both motors - you're now going forward!

If you pushed the joystick to the upper-left corner, you are reading "-511, -511", then scaled and sign checked to mean "apply PWM values of 255,255 to the other pin of the h-bridge" to drive both motors in the opposite direction - and you're going backward!

So what happens if you push the joystick to the upper-right side? Well, you would then read "512, -511" - and after scaling and sign-checking - hey look! One motor turning one way, and the other the opposite! Turning in place, so to speak. For the "lower-left" side, you would read "-511, 512" - wow! Spinning in the opposite direction.

Take a look at the other ordinal points - and notice what they do: That's right, you can turn one motor off, and have the other spinning - that is, one wheel moving, and the other stationary - so instead of turning on the center-point of the "axle" between the wheels, you are now rotating around the center of one of the wheels.

All fine and well - for the most part - but! Everything is on a diagonal - how can I use my joystick this way (you're asking yourself)? Simple:

Rotate the joystick 135 degrees counter-clockwise - so that the normally "bottom-right" corner of the joystick faces "up"...

Note that this isn't a perfect solution; it has it's bugs - but maybe you (or someone else) can make use of it. I hope it helps, or at least gives you some ideas. It's an example of thinking outside of the box (the main part being to not look at the joystick as needing to be explicitly oriented in an X/Y position as given by the potentiometers).

Good luck with your project!

:smiley:

cr0sh, you are a genius, mate.

I have done what you suggested (turned the stick by 45 degrees) and have a "stick plan" which looks like this:
L R
max 255,255
left wheel /
pot here /
min/ |
L R | L R
255,0 - - + - - 0,255
max |
\ |
rt wheel
pot here \ L R
min 0,0

One potentiometer controls the left wheel, and the other controls the right. There is currently no reverse, although by remapping the pots to say, -10 to 245 and using an if statement that could easily be handled.

The right stick values ("R" in the diagram) are then fed to the left motor, and the left stick values ("L" in the diagram) are then fed to the right motor..... simples!

It works a treat, the only thing it needs now is some way of "proportionaly slowing it down"... in other words, having both motors reading 255 and running at full chat is fine, but having one reading 0 and stopped and the other reading 255 and at running full blat is a bit harsh. I have tried subtracting the 2 values and dividing them, but I'm just not getting it right....

Anyway, here is the code if anyone needs it....

// This script operates a differential steering system. By Tim Coyne

const int LMotor=10;
const int RMotor=9;
int val = 0;

void setup() {

pinMode(A0, INPUT); // connects #1 potentiometer which senses stick inputs on the right of center to Arduino
pinMode(A1, INPUT); // connects #2 potentiometer which senses stick inputs on the left of center

pinMode(10,OUTPUT); // connects left motor to Arduino
pinMode(9,OUTPUT); // connects right motor

Serial.begin(9600); // sends stick left and right values back to computer
}

void loop() {

int leftStick = analogRead(A0); // reads leftStick inputs
int rightStick = analogRead(A1); // reads rightStick inputs

leftStick = map(leftStick, 0,1023, 0, 255); // changes 1023 steps to 256 steps which Arduino can read
leftStick = constrain(leftStick, 0, 255);

rightStick = map(rightStick, 0,1023, 0,255); // changes 1023 steps to 256 steps which Arduino can read
rightStick = constrain(rightStick, 0,255);

Serial.print("Left Stick "); // This makes the left and right values easier to read on the computer screen.
Serial.println(leftStick);
Serial.print(" Right Stick ");
Serial.println(rightStick);

{
analogWrite (LMotor,rightStick); // the left motor is driven by right stick value
analogWrite (RMotor,leftStick); // the right motor is driven by left stick value
}}

// simples. :slight_smile:

Hi all,

I have read the above article, and I have a similar setup:

|-255,+255 | +000,+255 | +255,+255 |
|-255,+000 | +000,+000 | +255,+000 |
|-255, -255 | +000,-255 | +255, -255|

Is there a way to programatically "twist" this by 45deg CCW so that +255,+255 points north so to account for analog inbetween values as well?

BR//D

Thought I'd share my code as I did not find anythig that exactly matched what I wanted.

I'm running two RC servo outputs into my arduino one on pins 12 and 13 and using 3,9, 10,11 as pwm outputs for my two H-bridges. Enable-signal on the bridges are constantly enabled. My code is a mix and match from several sources+some own adaptions. I'm sure some things can be improved, but it works as is.

Maybe I'll rewrite it to use digital control channels for direction and only two PWM outputs (pins 5-6)in the future (to be able to increase the PWM frequency).

const int chA=12;  //Fwd-rev servo input 
const int chB=13;  //Left-right servo input

//RX signal massaging values
const int RXLo=1000;
const int RXHi=2000;
const int RXDeadLo=1490;
const int RXDeadHi=1510;
const int RXCenter=1500;

const byte controllerFA = 11; //PWM FORWARD PIN for OSMC Controller A (left motor)
const byte controllerRA = 10;  //PWM REVERSE PIN for OSMC Controller A (left motor)
const byte controllerFB = 9;  //PWM FORWARD PIN for OSMC Controller B (right motor)
const byte controllerRB = 3;  //PWM REVERSE PIN for OSMC Controller B (right motor)

int analogTmp = 0; //temporary variable to store 
int throttle, direction = 0; //throttle (Y axis) and direction (X axis) 

int leftMotor,leftMotorScaled = 0; //left Motor helper variables
float leftMotorScale = 0;

int rightMotor,rightMotorScaled = 0; //right Motor helper variables
float rightMotorScale = 0;

float maxMotorScale = 0; //holds the mixed output scaling factor

int deadZone = 10; //jostick dead zone 
int ch[3];  //Array to store and display the values of each channel

void setup()  { 
 //initialization of pins  
 Serial.begin(115200);
 pinMode(controllerFA, OUTPUT);
 pinMode(controllerRA, OUTPUT);
 pinMode(controllerFB, OUTPUT);
 pinMode(controllerRB, OUTPUT);  

 pinMode(chA, INPUT);
 pinMode(chB, INPUT);
} 

void loop()  { 
  ch[0] = pulseIn (chA,HIGH);  //Read and store channel 1
  ch[1] = pulseIn (chB,HIGH);

  for (int i=0; i<=2; i++)      //Signal Conditioning loop
  {
   if (ch[i] <= RXLo)             //Trim Noise from bottom end
   {
    ch[i] = RXLo;
   }
  
   if (ch[i] <= RXDeadHi && ch[i] >= RXDeadLo)     //Create Dead-Band
   {
    ch[i] = RXCenter;
   }
  
   if (ch[i] >= RXHi)            //Trim Noise from top end
   {
     ch[i] = RXHi;
   }
   ch[i]=map(ch[i],RXLo,RXHi,-255,255);
  }

 throttle = ch[0];
 direction = ch[1];
 //mix throttle and direction
 leftMotor = throttle+direction;
 rightMotor = throttle-direction;

 //print the initial mix results
 Serial.print("LIN:"); Serial.print( leftMotor, DEC);
 Serial.print(", RIN:"); Serial.print( rightMotor, DEC);

 //calculate the scale of the results in comparision base 8 bit PWM resolution
 leftMotorScale =  leftMotor/255.0;
 leftMotorScale = abs(leftMotorScale);
 rightMotorScale =  rightMotor/255.0;
 rightMotorScale = abs(rightMotorScale);

 Serial.print("| LSCALE:"); Serial.print( leftMotorScale,2);
 Serial.print(", RSCALE:"); Serial.print( rightMotorScale,2);

 //choose the max scale value if it is above 1
 maxMotorScale = max(leftMotorScale,rightMotorScale);
 maxMotorScale = max(1,maxMotorScale);

 //and apply it to the mixed values
 leftMotorScaled = constrain(leftMotor/maxMotorScale,-255,255);
 rightMotorScaled = constrain(rightMotor/maxMotorScale,-255,255);

 Serial.print("| LOUT:"); Serial.print( leftMotorScaled);
 Serial.print(", ROUT:"); Serial.print( rightMotorScaled);

 Serial.print(" |");

 //apply the results to appropriate uC PWM outputs for the LEFT motor:
 if(abs(leftMotorScaled)>deadZone)
 {
   if (leftMotorScaled > 0)
   {
     Serial.print("F");
     Serial.print(abs(leftMotorScaled),DEC);

     analogWrite(controllerRA,0);
     analogWrite(controllerFA,abs(leftMotorScaled));            
   }
   else 
   {
     Serial.print("R");
     Serial.print(abs(leftMotorScaled),DEC);

     analogWrite(controllerFA,0);
     analogWrite(controllerRA,abs(leftMotorScaled));  
   }
 }  
 else 
 {
 Serial.print("IDLE");
 analogWrite(controllerFA,0);
 analogWrite(controllerRA,0);
 } 

 //apply the results to appropriate uC PWM outputs for the RIGHT motor:  
 if(abs(rightMotorScaled)>deadZone)
 {
   if (rightMotorScaled > 0)
   {
     Serial.print("F");
     Serial.print(abs(rightMotorScaled),DEC);

     analogWrite(controllerRB,0);
     analogWrite(controllerFB,abs(rightMotorScaled));            
   }
   else 
   {
     Serial.print("R");
     Serial.print(abs(rightMotorScaled),DEC);

     analogWrite(controllerFB,0);
     analogWrite(controllerRB,abs(rightMotorScaled));  
   }
 }  
 else 
 {
 Serial.print("IDLE");
 analogWrite(controllerFB,0);
 analogWrite(controllerRB,0);
 } 

 Serial.println("");

 //To do: throttle change limiting, to avoid radical changes of direction for large DC motors and alter PWM base frequency.

 delay(10);

}