9
\$\begingroup\$

I am looking for information about how to implement proper mixing of 2 analog joystick signals (X an Y axis) to control a dual differential motor drive ("tank like" drive) using a uC (ATMega328p in my case, but same should apply to any uC with ADC inputs and PWM outputs):

I have a analog stick, that gives 2 analog values:

(direction)X: 0 to 1023
(throttle)Y: 0 to 1023

enter image description here

Rest position is (direction and throttle neutral) 512,512
Throttle forward/direction left is 0,0
Full forward-full right is 1023,0
etc.

The motors are controlled by 2 H-bridge drivers, 2 PWM pins for each (Forward, backward), like so:
Left Motor: -255 to 255
Right Motor: -255 to 255
(positive values enable forward PWM pin, negative enable reverse PWM pin, 0 disables both)

The goal is to mix joystick analog signals to achive following response :

a)Throttle forward, direction neutral = vehicle moving forward
b)Throttle forward, direction left = vehicle moving forward and turning left
c)Throttle neutral, direction left = vehicle turning left IN PLACE that is right motor full forward, left motor full reverse

...and similarly for other combinations. Of course, the output should be "analog" that is, it should allow gradual transition from for example from option a) to b) to c).

The concept is:

http://www.lynxmotion.com/images/html/build123.htm

\$\endgroup\$
  • \$\begingroup\$ (1) Note that my basic algorithm allows speed control of 'turn in place' when joystick is pushed eg left by a % of full scale. (2) This requirement must have been re-solved many many times by now. The model community should have answers to this. (3) If the receiver translates the commands into track velocity using feedback the vehicle will behave about the same as ground conditions change. BUT if the commands are translated into motor power or drive voltage etc the vehicle performance will vary with ground conditions. - presumably 91) is preferable. \$\endgroup\$ – Russell McMahon Sep 20 '11 at 3:08
  • \$\begingroup\$ Russell, I googled a lot for the anwser and I have found a lot of ready to go motor controllers to connect directly to RC receiver but not much information about the algorithm inside. \$\endgroup\$ – Kamil Zadora Sep 20 '11 at 13:44
  • \$\begingroup\$ good day! renho a cousin who has been trying infantile paralysis and a construction of a wheelchair their programming worked well, but the output voltage is too low! help me! I'm using an arduino uno. \$\endgroup\$ – user22423 Apr 10 '13 at 14:09
  • \$\begingroup\$ @Johnny welcome to Electronics.Stackexchange! Please look the FAQ to understand how this site works, and if you have a question to ask please use the specific button on the top-right corner of the page. \$\endgroup\$ – clabacchio Apr 10 '13 at 14:33
  • \$\begingroup\$ Did it work ??? \$\endgroup\$ – Russell McMahon Sep 21 '16 at 2:59
4
\$\begingroup\$

"Proper" mixing is open to debate :-).

An issue is that you have to make decisions about how fast a track is moving under pure signals from a single pot and what to do when signals from the other pot are included. For example, if you push the FB (Forward-Backward pot fully forwards, and if both motors then run at full speed ahead, how do you deal with the addition of a small amount of LR (Left-Right) pot being added. To get rotation you have to have one track going faster that the other. So, if you are already running at maximum forwards speed on both motors you must decrease one or other track speed in order to turn. But, if you had been standing still you would have accelerated one or other track to achieve the same result.

So, all that said, here is a simple off-the-cuff starting solution out of my head which seems like good start.

If pots are mechanically independant then both can be at 100% simultaneously.
If both are on a joystick type arrangement, if Yaxis = 100% and Xaxis = 0%, then adding some B will usually reduce A. A joystick could be constructed where the above is not true, but these are unusual.
Assume that the joystick is of the type that increasing Y% when X = 100% will reduce X. Other assumptions can be made.

FB = front-back pot. Centre zero, +Ve for forward motion of pot

LR = Left right pot. Centre zero. +Ve for pot at right.

K is a scale factor initially 1.
If any result exceeds 100% then adjust K so result = 100% and use same K value for other motor also.

  • eg if Left motor result = 125 and Right motor result = 80 then.
    As 125 x 0.8 = 100, set K = 0.8. Then.
    Left = 125 x 0.8 = 100%. Right = 80 x 0.8 = 64%.

Then:

  • Left motor = K x (Front_Back + Left_Right)

  • Right motor = K x (Front_Back - Left_Right)

Sanity checks:

  • LR = 0 (centered), FB = full fwd -> Both motors run full forwards.

  • LR = full left, FB = 0 ->
    Left motor runs full backwards,
    Right motor runs full forwards.
    Vehicle rotates anti clockwise.

  • FB was 100%, Lr = 0%. Add 10% of LR to right.
    L = FB+LR = 100%- + 10% R = FB-LR = 100%- - 10%

If largest axis < 100%, scale until = 100%.
Then scale other axis by same amount.

\$\endgroup\$
  • \$\begingroup\$ Thank you Russell - I will try to implement this on my model setup. BTW, My joystick is able to keep full forward while panning it left to right and the other way around, it is very similar to this: static.sparkfun.com/images/products/09032-03-L_i_ma.jpg \$\endgroup\$ – Kamil Zadora Sep 19 '11 at 19:06
  • 1
    \$\begingroup\$ I am currently tasked with solving the same problem at work. I have a wii nunchuk 2-axis controller, and it needs to control 2 motors exactly as described in the question. I'm having a bit of trouble understanding the logic here. What exactly does k1/K1 refer to? One is lowercase and one is uppercase - are they different? What is +Ve? \$\endgroup\$ – Tal Sep 20 '16 at 22:53
  • 1
    \$\begingroup\$ Cool - thanks for the clarification. I needed this written in Python, so if I understand correctly, this should do it: pastebin.com/sWDakvLp. Does it look like I'm missing anything? Seems to work in my test environment - I'll need to actually connect it to the final motors I'll be using to know for sure. \$\endgroup\$ – Tal Sep 21 '16 at 17:17
  • 1
    \$\begingroup\$ 1) Motor speed is controlled by PWM, which only takes values from 0 - 100, which is why I used 100 as max value. 2) I use abs to find if scaling is needed (like you said) and to get the scale_factor. If I end up with a scale factor of 0.8 for example, and use it on a negative number, -125 * 0.8 = -100. Direction is maintained. I think it works, unless I am missing something. I still haven't had a chance to try it on the final motors - my boss will be building a test platform with motors attached that I'll be able to test on. \$\endgroup\$ – Tal Sep 25 '16 at 19:07
  • 1
    \$\begingroup\$ I wasn't sure if my code would actually work, so I set the previous pastebin link to expire after a week. Since it seems to work, here's a more permanent link with a few more comments if anyone comes across the problem again: pastebin.com/EKguJ1KP. I'd put this into an answer, but apparently I don't have enough rep to post an answer. All code is based on Russel McMahon's answer - credit goes to him - thanks Russel. \$\endgroup\$ – Tal Oct 2 '16 at 18:11
5
\$\begingroup\$

Here's a solution that doesn't require complicated if/else chains, doesn't reduce the power when moving full forward or rotating in place, and allows for smooth curves and transitions from moving to spinning.

The idea is simple. Assume the (x,y) joystick values are cartesian coordinates on a square plane. Now imagine a smaller square plane rotated 45º inside it.

example plane

The joystick coordinates give you a point in the larger square, and the same point superimposed in the smaller square gives you the motor values. You just need to convert coordinates from one square to the other, limiting the new (x,y) values to the sides of the smaller square.

There are many ways to do the conversion. My favorite method is:

  1. Convert the initial (x,y) coordinates to polar coordinates.
  2. Rotate them by 45 degrees.
  3. Convert the polar coordinates back to cartesian.
  4. Rescale the new coordinates to -1.0/+1.0.
  5. Clamp the new values to -1.0/+1.0.

This assumes the initial (x,y) coordinates are in the -1.0/+1.0 range. The side of the inner square will always be equal to l * sqrt(2)/2, so step 4 is just about multiplying the values by sqrt(2).

Here's an example Python implementation.

import math

def steering(x, y):
    # convert to polar
    r = math.hypot(x, y)
    t = math.atan2(y, x)

    # rotate by 45 degrees
    t += math.pi / 4

    # back to cartesian
    left = r * math.cos(t)
    right = r * math.sin(t)

    # rescale the new coords
    left = left * math.sqrt(2)
    right = right * math.sqrt(2)

    # clamp to -1/+1
    left = max(-1, min(left, 1))
    right = max(-1, min(right, 1))

    return left, right

The original idea for this method -- with a much more complicated transformation method -- came from this article.

\$\endgroup\$
0
\$\begingroup\$

Below is example of mixing algorithm implementation as described by Russel McMahon answer:

http://www.youtube.com/watch?v=sGpgWDIVsoE

//Atmega328p based Arduino code (should work withouth modifications with Atmega168/88), tested on RBBB Arduino clone by Modern Device:
const byte joysticYA = A0; //Analog Jostick Y axis
const byte joysticXA = A1; //Analog Jostick X axis

const byte controllerFA = 10; //PWM FORWARD PIN for OSMC Controller A (left motor)
const byte controllerRA = 9;  //PWM REVERSE PIN for OSMC Controller A (left motor)
const byte controllerFB = 6;  //PWM FORWARD PIN for OSMC Controller B (right motor)
const byte controllerRB = 5;  //PWM REVERSE PIN for OSMC Controller B (right motor)
const byte disablePin = 2; //OSMC disable, pull LOW to enable motor controller

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 

void setup()  { 

  //initialization of pins  
  Serial.begin(19200);
  pinMode(controllerFA, OUTPUT);
  pinMode(controllerRA, OUTPUT);
  pinMode(controllerFB, OUTPUT);
  pinMode(controllerRB, OUTPUT);  

  pinMode(disablePin, OUTPUT);
  digitalWrite(disablePin, LOW);
} 

void loop()  { 
  //aquire the analog input for Y  and rescale the 0..1023 range to -255..255 range
  analogTmp = analogRead(joysticYA);
  throttle = (512-analogTmp)/2;

  delayMicroseconds(100);
  //...and  the same for X axis
  analogTmp = analogRead(joysticXA);
  direction = -(512-analogTmp)/2;

  //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

  delay(10);

}
\$\endgroup\$
  • \$\begingroup\$ Interesting, this code looks like it is feeding 2 analog pin to 2 different motor controller. I will try to adapt the code and modify for my settings. Arduino Uno + 1 Sabertooth driver board. 1 joystick to analog pinA0(x) pinA1(y) reading and passing values to PWM pin 10&3 going to S1&S2 of Sabertooth. I think I am close but I am getting confuse on how to setup the dipswitch on Sabertooth board. For now I am teting with switch setup to receive analog input, switch 4 is still in position for differential drive but will put it back to independant mode later for further testing. I think this orig \$\endgroup\$ – user20514 Mar 21 '13 at 15:51
  • \$\begingroup\$ @user20514 welcome to electronics.stackexchange! As you might notice, this is not a forum but a Q&A site, therefore answers' space is not meant for discussion. Please be free to ask a new question if you have something to ask, or use comments to (indeed) comment about existing questions and answers. \$\endgroup\$ – clabacchio Mar 21 '13 at 16:03
  • 1
    \$\begingroup\$ @Kamil - The video is shown as private. Is it still avaiable ? youtube.com/watch?v=sGpgWDIVsoE \$\endgroup\$ – Russell McMahon Sep 21 '16 at 2:58
  • \$\begingroup\$ @RussellMcMahon reactivated :) \$\endgroup\$ – Kamil Zadora Sep 28 '16 at 21:21

protected by Community Aug 30 '13 at 21:47

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.