Two Motor Drive Steering of Vehicle Not Working As Expected

I'm a student drafter/welder on a team developing this project. We're attempting to improve the steering system, but the EE who wrote the original code is unavailable. I posted here previously when I was attempting to write the code for this project myself, but discovered I was out of my league, especially given the time constraints. Another more capable team member who lives overseas (and is stuck there due to the pandemic) took on the task. So I have all the hardware, and he has the know-how for this part of the project. This was our first attempt, and after I uploaded his code I was no longer getting any response from the vehicle when I manipulated the controls. We are trying to do this with distance and language as barriers, so please be kind as you spot errors, as I'm sure there are several.

It's a four wheel vehicle driven and steered by two separate motors on the front powered by up to 24V, and two passive caster wheels in the back (see first photo and wiring diagram). The radio controller (see photo) has a throttle control on the left, and a left/right control on the right. Steering design is achieved by having both motors operate at the same rpms until the left/right steer controller is maneuvered, at which point the rpms on the targeted side are to slow according to how far the stick is pushed (see Motor Voltage Data Tables, including table with example of a right turn with the throttle at 80% - illustration is profile of left/right stick in "action"). Code is below everything else.

#define MTR 5     //Motor right 
#define MTL 6     //Motor left
#define DIR_R 7   //right direction 
#define DIR_L 8   //left direction
#define FWD LOW
#define RVS HIGH
void setup() {
  pinMode(10,INPUT);
  pinMode(11,INPUT);
  pinMode(9, INPUT);
  pinMode(MTR,OUTPUT);
  pinMode(MTL,OUTPUT);
  pinMode(FWD,OUTPUT);
  pinMode(RVS, OUTPUT);
  Serial.begin(9600);
}

int steer;        //steering number 
int throttle;     //throtthle number 
int pwm = 0;      //pwm number 
int xpwm=0;      // pwm number for steer 
int pwmL=0;      //pwm for left motor 
int pwmR=0;      //pwm for right motor  
int fwd=1; 
int direc = analogRead(A0); 
void speedFunct(){
  //full power 
  if(pwmL>=250 && pwmR>=250 ){
    analogWrite(MTR, 255);
    analogWrite(MTL, 255);
    }
    //no power 
  if(pwmL<=0  && pwmR<=0){
    analogWrite(MTR, 0);
    analogWrite(MTL, 0);
    }
    //percentage of power (duty cycle)
    else{
    analogWrite(MTR, pwmR);   // send pwm signal to motor 
    analogWrite(MTL, pwmL);
    }
}


int rvs = 0 ;
 
void steerFunct(){
  //drive stright 
  if(1450 < steer < 1550){
      if(fwd==1){
        digitalWrite(DIR_R, FWD);
        digitalWrite(DIR_L, FWD);
      }
      else{
        digitalWrite(DIR_R, RVS);
        digitalWrite(DIR_L, RVS);
        }
        speedFunct(); 
        delay(5); 
  }
  //steer right
  if(steer >= 1550){
    digitalWrite(DIR_R, FWD);
    digitalWrite(DIR_L, FWD);
    speedFunct(); 
    delay(5); 
  }
  //steer left
  if(steer <= 1400){
    digitalWrite(DIR_R, FWD);
    digitalWrite(DIR_L, FWD);
    speedFunct(); 
    delay(5); 
  }
  delay(5);
}
void loop() {
  steer = pulseIn(10, HIGH, 250000);
  throttle = pulseIn(11, HIGH, 250000);
  direc = pulseIn (9, HIGH, 250000); 
  
  // dead zone 
  if(1400 < direc && direc < 1500){
    digitalWrite(MTR, LOW);
    digitalWrite(MTL,LOW);
    delay(5);
    Serial.print("Direction: ");
    Serial.println("Stop");
    Serial.print("\n");
  }
  //forward
  if(throttle <= 1000){
    rvs = 0;
    digitalWrite(DIR_R, FWD);
    digitalWrite(DIR_L, FWD);
    pwm = map(throttle, 990, 1983,0, 255);
      if (steer>= 1550){ //right 
      xpwm = map(steer, 1550, 1983,0, 255);
      pwmL=pwm;       //left keep same 
      pwmR=pwm-xpwm;  // decrease right motor 
      }
      else if(steer<= 1400){ //left turn 
      xpwm = map(steer, 1400, 0,0, 255);
      pwmR=pwm;        //right keep same 
      pwmL=pwm-xpwm; // decrease left motor  
      }
      else{
      xpwm=0;  
      }
    fwd=1;
    steerFunct(); 
    delay(5);
    Serial.print("Direction: ");
    Serial.println("Forward");
    Serial.print("\n");
  }
  //Reverse
  if(throttle >= 1900){
    digitalWrite(DIR_R, RVS);
    digitalWrite(DIR_L, RVS);
    pwm = map(throttle, 990, 1983,0, 255);
     if (steer>= 1550){ //right 
      xpwm = map(steer, 1550, 1983,0, 255);
      pwmL=pwm+xpwm;       //left keep same 
      pwmR=pwm-xpwm;  // decrease right motor 
      if(pwmL>255){
        pwmL=255; 
      }
      }
      else if(steer<= 1400){ //left turn 
      xpwm = map(steer, 1400, 0,0, 255);
      pwmR=pwm+xpwm;        //right keep same 
      pwmL=pwm-xpwm; // decrease left motor  
      if(pwmR>255){
        pwmR=255; 
      }
      }
      else{
      xpwm=0;  
      }
    fwd=0; 
    steerFunct();
    delay(5); 
    Serial.print("Direction: ");
    Serial.println("Reverse");
    Serial.print("\n");
  }
  
  delay(5);
}```

I'll loop you in, @StefanL38, as you've been working with me previously, which I appreciate.

Can you redesign so there is only one motor, making two motors match is an extremely difficult task. What happens if one motor does not follow the other, you will start messing up the tires and or losing control of the vehicle. Good Luck!

As you have discovered, this is not a good approach. A much better approach, used by most people, is to simply adjust the power to both motors so that the robot steers in the correct direction. The Right-Left joystick sets either the desired direction of travel, or increments/decrements it, while the Up-Down joystick sets forward_power or speed.

Do this differentially using a PID algorithm as follows (in pseudo code)

// in loop function
current_heading = measure_heading(); //for example, in magnetic compass degrees
heading_error = current_heading - desired_heading;
// apply PID correction. This assumes P only
left_motor_power = forward_power - Kp*heading; //if heading error is positive, bear left 
right_motor_power = forward_power + Kp*heading; //forward speed is maintained
set_motor_power (left_motor_power,right_motor_power);

l would like to know the purpose of this vehicle.

Is it just a "proof of concept" collecting some experience
building the mechanics and
in writing code for remote-controlling a vehicle

or will it be used after completing everything in the real-world
for a real-world task like transporting a paraplegic person or a person that has muscular dystrophy ?

The person who is writing the code should give information about his knowledge level about coding. One way to do this is to describe the most complex project he did in coding.

It will be difficult to communicate about it with technical terms if this guy has no idea what the meaning of these technical terms is. The language has to be adapted to his knowledge-level.

He should use google translate. Writing everything in his native language and let do google-translate the translation into english.

The result will be much better than beeing short on information in direct english just because he doesn't know the words himself.

best regards Stefan

As you have discovered, this is not a good approach. A much better approach, used by most people, is to simply adjust the power to both motors so that the robot steers in the correct direction. The Right-Left joystick sets either the desired direction of travel, or increments/decrements it, while the Up-Down joystick sets forward_power or speed.

Well, I'm not sure what I've discovered. It didn't respond at all. Wouldn't even spin its wheels, let alone steer. I don't honestly understand your code, but I'm curious about your design: So it just redistributes the voltage? If it's running at 12 volts and I push right a little, might the voltage on the right motor drop to, say, 11V while the voltage on the left increased to 13V?

@schpuz, your topic has been moved to a more suitable location on the forum.

1 Like

The problem with this approach is. Any PID-control approach needs feedback.

If you take a look at the wiring there is the 2,4Ghz receiver an arduino and outputs using the PWM-inputs of the motordriver but no feedback from the motordriver.

There is no measuring of the rpms. How would you ever determine measured heading?

The "PID-Loop" i.e. the feedback is realised through the person watching the vehicle's path making corrections with the sticks of the RC-transmitter.

best regards Stefan

It's coffee and I haven't had any early yet, but I did compile and format your code for a peek.

I notice that in no place do you print any of the values it appears you are gathering from the remote control system.

So, have you checked to see, at a basic level, if you are still getting good input?

While the current approach may not be the best, it seems insane to start taking a different one if you are simply dead in the water at this point.

Look at the values from your joy pots (print). Use small programs to just run the wheels from the those values directly without any steering algorithm. Keep these programs handy for diagnostic purposes all through development. Update these diagnostic programs as you go to keep up with any modifications to the hardware.

Basically, I am asking to take a step back and confirm all hardware and wiring. If you haven't.

a7

Get the basic motor control functions working before worrying about steering. Come back when that is fixed and we can continue the discussion.

void steerFunct(){
  //drive stright 
  if(1450 < steer < 1550){

This If statement is going to cause you problems.

They're good, yes. It runs and drives on another code written by someone else who is no longer with the project, but the steering was inadequate, so we're attempting another set of code.

I appreciate you taking the time to respond, but outside of confirming the above, I'm not entirely comprehending what you're getting at. Again, I'm a wrench with enough computer know-how to get the code onto the Arduino, but our programmer is in China. "Joy pots" sound like fun, though.

I sent him footage of how it's operating and we're meeting online later today when we're both in daylight hours. This could get tedious, I realize.

@david_2018:

You're the third person to point that out (see link above) and I completely believe that you're right about it, but when I tried changing that line in the old code it had no impact in the way the kart behaved. I'll be sure the change is in the next version of code, regardless. Thanks.

In which case you can show your complete updated code so people can see what you did.

@sterretje

In which case you can show your complete updated code so people can see what you did.

At the risk of getting off topic since this code is being completely overhauled, anyway - here it is/was:

/*  File: RC_V2p1
 *  Purpose: Control 2 motors with a remote control transmitter. The user must be able to control the throttle, steering,
 *  and direction. Therefore this softeware can be used to remotely control a kart.
 *  Version 2.0 : Updated software to work with new controller
 *  Developed by: 
 */


#define MTR 5 //motor right
#define MTL 6 //motor left
#define DIR_R 7 //right direction
#define DIR_L 8 //left direction
#define FWD LOW
#define RVS HIGH
void setup() {
  pinMode(10,INPUT); //Channel 1 (steer)
  pinMode(11,INPUT); //Channel 3 (throttle)
  pinMode(9,INPUT); //Channel 5 (direction)
  pinMode(MTR,OUTPUT);
  pinMode(MTL,OUTPUT);
  pinMode(FWD,OUTPUT);
  pinMode(RVS, OUTPUT);
  Serial.begin(9600);
}

int steer; //steering number
int throttle; //throttle number
int pwm = 0; // pwm number
int fwd = 1 ; 
int direc = analogRead(A0);
void speedFunct(){
  //full power
  if(pwm >= 250){
    analogWrite(MTR,255);
    analogWrite(MTL,255);
  }
  //no power
  else if(pwm <= 0){
    analogWrite(MTR,0);
    analogWrite(MTL,0);
  }
  //percentage of power (duty cycle)
  else{
    analogWrite(MTR,pwm);
    analogWrite(MTL,pwm);
  }
}
void steerFunct(){
  //drive straight
  if((1450 < steer) && (steer < 1550)){
    if(fwd == 1){
       digitalWrite(DIR_R, FWD);
       digitalWrite(DIR_L, FWD);
    }
    else{
       digitalWrite(DIR_R, RVS);
       digitalWrite(DIR_L, RVS);
    }
     speedFunct();
      delay(5);
  }
  //steer right
  if(steer >= 1550){
    digitalWrite(DIR_R, RVS);
    digitalWrite(DIR_L, FWD);
    speedFunct();
    delay(5);
  }
  //steer left
  if(steer <= 1450){ // 1400
    digitalWrite(DIR_R, FWD);
    digitalWrite(DIR_L, RVS);
    speedFunct();
    delay(5);
  }
}

void loop() {
  //Pin setup
  steer = pulseIn(10, HIGH, 250000);
  throttle = pulseIn(11, HIGH, 250000);
  direc = pulseIn(9, HIGH, 250000);
  Serial.println(throttle);
  Serial.println(steer);
  /***Direction control***/
  //Forward
  if(direc <= 1000){
    analogWrite(DIR_R, FWD);
    analogWrite(DIR_L, FWD);
    pwm = map(throttle, 990, 1983 ,0 ,255);
    fwd = 1;
    steerFunct();
    delay(5);
    Serial.print("Direction: ");
    Serial.println("Forward");
    Serial.println("\n");
    
  }
  //Reverse
  if(direc >= 1900){
    digitalWrite(DIR_R, RVS);
    digitalWrite(DIR_L, RVS);
    pwm = map(throttle, 990, 1983 ,0 ,255);
    fwd = 0;
    steerFunct();
    delay(5);
    Serial.print("Direction: ");
    Serial.println("Reverse");
    Serial.println("\n");
  }
  //Dead Zone(No movement)
  if(1400 < direc && direc < 1500){
    //pwm = 0;
    //speedFunct();
    digitalWrite(MTR, LOW);
    digitalWrite(MTL,LOW);
    delay(5);
    Serial.print("Direction: ");
    Serial.println("Stop");
    Serial.println("\n");
  }
  
  delay(5);
}

Do you have the original code that was working, of did you lose that along with the programmer?

Do you have the original code that was working, of did you lose that along with the programmer?

The code above works, and works as he intended. But I can say the same thing about Yugo's and their designers, if you catch my drift. But seriously, this code achieves skid steering, and that was fine at one time; however, the vehicle has evolved in terms of wheel layout and speed, and it's no longer adequate, so we're attempting an improvement.

Ah, sorry, did not realize you has just posted it, thought that was the latest version of your current code.

1 Like

The positions of joysticks on the remote control end up as values in your program. That's what I meant, the joysticks run potentiometers in the transmitter. Just r/c shorthand, joy-pots…

But in general I meant that you should use serial.print statements to detect and display both the flow through your code as well as the values of key variables, just plain old first-level debugging. No extra equipment or talent needed.

You do some printing, but never print the values of anything, so as far as you know, the code is operating perfectly but being mislead by, for example, bogus values or zeros from the receiver.

Edit: OK I see you do print some values and I assume you find them to be plausible and responding to the joysticks?

But the fact that the first program still works is in itself a test of sorts, and kinda rules out many things that such printing might turn up.

Still, strategically placed print statements including values of variables informing program flow can help you "see" what's going on.

  if(1450 < steer < 1550){

Has been pointed out, and you say you tried changing that in the old code. Don't mess with the old (working but no longer what you want/need) code!

Post the code you are wrestling with when it compiles and you have perhaps taken a good look or your distant colleague has weighed in.

Speaking of whom, has he any hardware at all to run any kind of tests on? If his only ability is to send you code that compiles but he cannot test makes a real challenge… I appreciate the difficulty of language, distance and time zone. No envy of you here right now, except playing with big motors sounds like fun even if you don't think you are having much just now. :wink:
7
a7

I suggest to use the servoinput-library

and the library that the manufactorer of the DC-motordriver offers

using these two libraries will simplify coding.
The servoInput-library can be used to provide the RC-input-signals in a convenient way.

and the MDDS30-library makes it quite easy to control the motors.

The datasheet of the DC-motordriver can be downloaded here
https://www.robotshop.com/media/files/pdf/smartdriveduo-smart-dual-channel-30a-motor-driver-datasheet.pdf

best regards Stefan

That servoInput library looks interesting, but if the OP's method, viz:

 steer = pulseIn(10, HIGH, 250000);
 throttle = pulseIn(11, HIGH, 250000);
 direc = pulseIn (9, HIGH, 250000); 

is working, then adding another library, esp. one based on interrupts, is at this point only going to complicate things.

I don't particularly like the pulseIn acquisition of PWM signals, and if PWM was all I had would def use the library. But I don't argue with success.

Modern serial based r/c protocol offers much better end-to-end fidelity and is fairly easily decoded. If a change to the r/c part of this is to be made, this would be where I would go, right past PWM.

So first things first. The current code malfunctions or doesn't even function at all for stupid and/or subtle reasons: I suggest getting it to work as the distant engineer intends would be the best place to spend time.

a7

Your code is mixing up the direction and steering controls in the if statements, and then doing some odd things in steerFunct while also attempting to implement steering in other parts of the code.

Spent a bit too much time rewriting the code, but try this for a test, it should allow you to go forward and backward without any implementation of the steering, and is a bit more concise than your original code.

#define MTR 5     //Motor right 
#define MTL 6     //Motor left
#define DIR_R 7   //right direction 
#define DIR_L 8   //left direction
#define FWD LOW
#define RVS HIGH
const byte steerPin = 10;
const byte throttlePin = 11;
const byte direcPin = 9;

const unsigned int pulseMin = 990; //minimum length of pulse
const unsigned int pulseMax = 1983; //maximum length of pulse
const unsigned int pulseMid = (990 + 1983) / 2; //centerpoint of pulse (1486);
const unsigned int deadband = 50; //dead band range for input pulses

unsigned int steer;        //steering number
unsigned int throttle;     //throttle number
unsigned int direc;

unsigned int pwmL;    //pwm for left motor
unsigned int pwmR;    //pwm for right motor

bool runMotors;

void setup() {
  pinMode(steerPin, INPUT);
  pinMode(throttlePin, INPUT);
  pinMode(direcPin, INPUT);
  pinMode(MTR, OUTPUT);
  pinMode(MTL, OUTPUT);
  pinMode(FWD, OUTPUT);
  pinMode(RVS, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  steer = pulseIn(steerPin, HIGH, 250000);
  throttle = pulseIn(throttlePin, HIGH, 250000);
  direc = pulseIn (direcPin, HIGH, 250000);
  Serial.print(F("pulse length steer: "));
  Serial.print(steer);
  Serial.print(F("\npulse length throttle: "));
  Serial.print(throttle);
  Serial.print(F("\npulse length direc: "));
  Serial.print(direc);
  Serial.print('\n');
  //ensure pulse lengths are within allowed range
  steer = constrain(steer, pulseMin, pulseMax);
  throttle = constrain(throttle, pulseMin, pulseMax);
  direc = constrain(direc, pulseMin, pulseMax);

  steerFunct();
  direcFunct();
  speedFunct();
}

void steerFunct() {
  unsigned int pwm = 0;
  if (throttle >= (pulseMin + deadband)) {
    pwm = map(throttle, (pulseMin + deadband), pulseMax, 0, 255);
  }
  pwmL = pwm;
  pwmR = pwm;

  //the actual steering code goes here
}

void direcFunct() {
  //forward
  if (direc <= (pulseMid - deadband)) {
    runMotors = true;
    digitalWrite(DIR_R, FWD);
    digitalWrite(DIR_L, FWD);
    Serial.print(F("Direction: Forward\n\n"));
  }
  //Reverse
  else if (direc >= (pulseMid + deadband)) {
    runMotors = true;
    digitalWrite(DIR_R, RVS);
    digitalWrite(DIR_L, RVS);
    Serial.print(F("Direction: Reverse\n\n"));
  }
  // dead zone
  else {
    runMotors = false;
    Serial.print(F("Direction: Stop\n\n"));
  }
}

void speedFunct() {
  if (runMotors == true) {
    // send pwm signal to motor
    analogWrite(MTR, pwmR);
    analogWrite(MTL, pwmL);
  } else {
    //stop motors
    analogWrite(MTR, 0);
    analogWrite(MTL, 0);
  }
}

It is fairly common with radio controlled electric motors to have an "arming" sequence to prevent the motors turning on when the radio is switched on with the throttle up. This usually involves the receiver needing to see a minimum throttle setting for a certain length of time before allowing the motor to run. You may want to implement that for safety reasons.