# differential drive PID controller

I have a differential drive robot that works fine (good PD parameters) driving at say 1 m/s. Now, if it speeds up (to 1.2 m/s) it starts wobbling again. What would be a good strategy for a controller that is able to cope with the whole speed range of 0 - 4 m/s?

edit 14th of April:

The robot is a line follow robot but I do not see how this would be related to my question since a robot following a trajectory would have the same problem.

I recently talked to other developers of differential drive robots and they are facing similar issues e.g. they told me that they need to adjust PID parameters once the battery is not fully charged hence the robot drives at a different speed.

PID parameters are: P 0.31, D 0.59, I 0.00

PID controller programmed using C:

  // note: the inner wheel turns backwards for narrow curves
// cte is -128..128 depending on the robots position
// relative to a trajectory / black line
/** Execute the PID controller and update motor speeds */
void PID()
{
int32_t steer;
int32_t cte;
cte = 128 - get_segment_center(0);
// Compute PID equation
steer = (int)(
-P * (float)cte
-D * (float)(cte - diff_cte) / (float)PERIOD_MS
-I * (float)int_cte
);
if (steer < -5)
{
// turn left
turn = -1;
uXbot_move(MAX_SPEED + steer, MAX_SPEED);
}
else if (steer > 5)
{
// turn right
turn = 1;
uXbot_move(MAX_SPEED, MAX_SPEED - steer);
}
else
{
// go straight
turn = 0;
uXbot_move(MAX_SPEED, MAX_SPEED);
}
diff_cte = cte;
int_cte += cte;
}

• Why the +5/-5 threshold? What happens if you remove that? You should not need to introduce this sort of non-linearity. Try D=0, I=0 and ramp up P gradually. Apr 14 '14 at 18:32
• +5/-5 is from another feature. To disable the feature I can set it to 0 and I tried this already. I tried different PID values for the last two days (I have a simulator for this that tired thousands of different values over the last weeks). Apr 14 '14 at 19:17

You aren't properly mapping your steering value to the wheel speeds. In fact, I don't think you're applying the PID correctly at all.

From your code, my guess is that you're using get_segment_center to determine the adjustment that you need to make. My assumption is that this describes a distance measurement of how far the line sensor is off of the center of the line. If that's the case, you are lacking the necessary calculations.

The output of a PID takes an error and gives you a correction, so if your error is in inches then your correction will be in inches; if the error is in degrees, your correction will be in degrees, and so on. You need to pick either of those and stick with it. As written, you're taking a distance error and plugging it directly into a speed correction – no wonder it only works at one speed!

My recommendation would be to calculate the steering error as an angle, which you can get by measuring (1) the distance over which your sensor can pick up the line and (2) the distance between the axle and the sensor. With the tangent function, you can map sensor readings to angle error. Then, you can implement the function I wrote about in this post to convert the steering angle to wheel velocities.

• great! That is a really interesting observation. The approach taken in my code is similar to code I studied in other peoples line followers and I never really questioned it like you did. I thought the P factor would take care of the conversion. But this would only work if the conversion is linear. I will study this further during the Easter break and report back. If you do not mind I will keep the question open until then. Apr 14 '14 at 19:20
• I followed your advice and factored in (1) and (2) cte = 104.0 * atan((float)(128 - get_segment_center(0)) * 0.003); the reference to to the other post (nice visualizations) was not so helpful since it has some features I do not need (turning on the spot etc.). It also does NOT work with velocities as advertised. My new PID (using the above formula) works a little bit better but it still does not solve the problem. I currently can not send robot telemetry (I miss a tiny sprintf library). But as soon as this is resolved I will provide some data and graphs. Apr 21 '14 at 7:21
• How are you mapping your desired vehicle steering angle (+ velocity) to individual wheel velocities? What problems did you run into when you say that the other algorithm "does not work with velocities as advertised"?
– Ian
Apr 21 '14 at 15:38
• Your post works on "joystick-position" instead of velocity. I am not able to see wheel-velocity you are talking about:( Apr 22 '14 at 5:25
• Aah, in that post it was assumed that the left-right joystick position would be used to indicate the desired steering angle (-90 to +90), and the up-down joystick position would indicate the desired vehicle speed (-100% to +100%). What does your abbreviation "cte" stand for?
– Ian
Apr 22 '14 at 13:12

I see that his question is from a long time ago but, just in case it still gets attention, here are some thoughts based on running a small line follower:

Let's address the battery problem first:

I assume that the statement uXbot_move(MAX_SPEED + steer, MAX_SPEED); has parameters that map to PWM values - possibly by some scaling factor.

A fixed PWM value will produce a different drive voltage as the battery voltage sags. In your motor driver, measure the battery voltage with an ADC input. Use that value to adjust the actual PWM duty cycle so that the average voltage stays constant. Change the uxBot() to accept a Voltage rather than a PWM. Now you are able to command, say, 3 Volts to the motor and be confident that is what it gets regardless (within limits) of the actual battery voltage. If you had 8 bits of PWM resolution you might end up with code fragments like these:

void setLeftMotorVolts(float volts) {
int motorPWM = (255 * volts) / batteryVolts;
setLeftMotorPWM(motorPWM);
}

void setRightMotorVolts(float volts) {
// reverse the right motor
int motorPWM = (-255 * volts) / batteryVolts;
setRightMotorPWM(motorPWM);
}


How about the performance tuning with speed:

    if (steer < -5)
{
// turn left
turn = -1;
uXbot_move(MAX_SPEED + steer, MAX_SPEED);
} else if (steer > 5)
{
// turn right
turn = 1;
uXbot_move(MAX_SPEED, MAX_SPEED - steer);
} else {
// go straight
turn = 0;
uXbot_move(MAX_SPEED, MAX_SPEED);
}


Part of the problem here is that the robot forward speed will change as it turns. Any turning motion will reduce the forward speed. The effect is clearly visible in the video you linked to. While this can help navigate sharp turns it messes with the dynamics somewhat.

If, instead, you make changes to BOTH wheel speeds together, the forward speed will remain constant. Clearly, there must be some headroom left for the outer wheel to be able to increase speed. With both wheels changing speed, the code fragment above would reduce to just:

uXbot_move(MAX_SPEED + steer, MAX_SPEED - steer);


That will make the response consistent but the controller may still have trouble adapting to different speeds. To sort that out, consider scaling the steering correction with the current speed. That is, the correction will be larger at higher speeds.

Thinking about the controller, I have three observations.

First, it is important that the PID function is called at regular, constant intervals. It just does not work well unless that is true. Your code does not specify that so it may already be true. Indeed, the existence of the division by PERIOD_MS indicates that you are aware of this.

Second, for the specific line following task that you describe, you do not need an I term in the controller. In fact, because you do nothing to limit integral windup, it may be doing more harm than good.

Finally, unless you are seriously limited for processor power, do your calculations in floating point as much as possible. Only reduce to an int when you actually calculate the PWM duty cycle for the timer register.

Nice robot.

The +/- 5 threshold in your code is a red flag to me. This introduces a non-linearity/discontinuity which shouldn't be needed. What is the rationale for that?

If you have enough memory to capture some real-time data try logging your cte and steer and then review them as you tune.

Speed is going to have an impact because of the way you've set up your steering and the vehicle dynamics. As the speed increases your steering command is going to cause a different turning radius. Also as speed increases it becomes hard to steer the vehicle. This isn't a trivial system. When your battery voltage goes down and assuming that voltage is your motor power tail and that you're not actually closing a velocity loop the effect of the lower voltage would be lower speed and therefore the effect of your PID coefficients will be reduced linearly to that. Note that going at a lower speed with a full battery and going at a low speed due to a low battery will behave differently (the impact of your steer command will be different).

Here's what I would suggest as a first go:

1. Get rid of that +5/-5 threshold.
2. Add some data logging in and review while tuning.
3. Start with I=0, D=0 and pick a fixed velocity. Start on the slow end.
4. Ramp up P, keep doubling it until the system becomes unstable. Back off (e.g. take half or 1/4 of the unstable P.)
5. Experiment with adding a little I or D and see if they improve performance. If not leave at 0.
6. Repeat tuning at a few speeds and plot your tuned variables vs. speed.
7. Try fitting linear or 2nd order segments to that graph to determine the coefficients at different speeds if needed.
• I could explain what a "differential drive" robot is but I assumed that people here would know. I think my problem is very common to these kind of robots e.g. needing to change PID parameters when the battery power goes down... Apr 14 '14 at 5:36
• @mark: in your question you talk about speed, in this comment you talk about battery power. We can't read your mind. You could get more and better answers if you take the time to provide all the necessary information. It's up to you. You don't need to explain what a differential drive is but you do need to give details about your specific setup which is having your specific issues. Apr 14 '14 at 6:15