Thought I would edit this and put it back on the front page. There seems to be quite a lot of PID talk at the moment, may be helpful to someone.
I have built several robots which were capable of avoiding obstacles and driving around without bumping into anything. For a university project i wanted to make a robot that could build some kind of map of it environment. During this project i found i needed better control of my robot, to allow it to drive in a straight line and follow walls. So i started experimenting with PID control. I thought i would write a walkthrough to share what i have achieved.
Before using PID control i was simply telling the robots wheels to drive at a certain speed, set by a PWM output. I was then assuming that the robot wheels would turn at the same speed and the robot would travel in a straight line. This is known as open loop control. This means that you send an output to the motors with no feedback and assume they travel at the speed you set. It is very unlikely that two motors, even two identical motors will turn at the same speed. So some sort of feedback is required to control the speed. This is normally achieved using an encoder. When the speed of the motor is controlled using feedback it is known as closed loop control.
I have implemented PID control to control both wheels of my robot, meaning the wheels turn at the same speed and the robot can travel in a straight line. I have also used it to allow my robot to follow walls. In this case the feedback is not from the encoders on the motors but from a sensor looking at the wall being followed. The idea behind PID control is that you set a value that you want maintained, either a speed of a motor of a reading from a sensor. You then take readings from the encoder or the sensor and compare them to the setpoint. From this an error value can be calculated, i.e, (error = setpoint - actual reading). This error value is then used to calculate how much to alter the motor speed by to make the actual reading closer to the setpoint.
The maths behind PID control can be pretty heavy. However, the process can be simplified greatly if the frequency at which you sample the encoders or the sensor is fixed. This is an important point and one that i wish someone had told me when i started experimenting. I will now attempt to explain how i implemented PID control, its quite a tricky thing to explain so i will do my best and if anyone has any tips on how the explanation can be improved, let me know and ill try again.
Ill use an example of controlling one motor with encoder feedback to try and explain how ive implemented PID control. I use an ATMEGA32 on my robot, using 8bit PWM to drive a motor with an encoder that sends a series of pulses as the motor turns. These pulses are counted by the microcontroller. I use an internal interrupt that triggers when to sample the encoder. I use a sampling time of around 1/10th of a second. So i am taking a reading from the encoder 10 times a second, comparing the reading to the setpoint to give me an error value, and using this to calculate how much to alter the motor speed by.
So what do you do with the error value to calculate how much to change the motor speed by? i hear you ask. The simplest method is to simply add the error value to the PWM output to change the motor speed. And this would work, and is known as proportional control (the P in PID). It is often necessary to scale the error value before adding it to the output by using a gain contant. For example. Say the PWM output to the motor is 200, you have chosen a setpoint of 10. You are therefore expecting that when you sample the encoder it should have sent 10 pulses to the microcontroller since last time you sampled it. If the has only sent 6 pulses the motor is going to slow. The error (the difference between the actual reading and the setpoint) is therefore 4. You could add this value straight to the PWM output (200+4=204), which would speed up the motor. It may take many samples before the motor speed matched the setpoint, so it may be necessary to scale the error value, by multiplying it by 2 for example. This would improve the response time. As an equation this would look something like this:
c = E*Kp
where c is the value to be added to the PWM output, E is the error value and Kp is the gain constant. You only ever have to add c to the PWM output as if the motor was going to fast, the error value would be negative, and therefore c would also be negative.
AS i mentioned this approach would work, but you may find that if you want a quick response time, by using a large gain constant, or the error is very large, the motor speed will go much higher than the setpoint, known as overshoot. The motor speed may then go much lower than the setpoint, then higher again and so on. When this approach is used on a robot, the robot tends to oscillate in a jerky manner. This is when the D bit of the PID comes into play. D stands for Derivative. It is used to look at the rate of change of the error, i.e is the error changing quickly of slowly. Another error value is calculate which i call Ed, which is the difference between the previous error and the current error (Ed = E - Eprev). This gives a value that is larger if the error is changing quicky and a smaller value if the error is changing slowly. If this is used as well as the proportial control it is known as PD control, and an equation like this can be used:
c = (E*Kp)+(Ed*Kd)
where c, E and Kp are the same as before, Ed is calculated as shown above and Kd is the derivative gain constant.
The I bit of PID refers to Integral control. I have found that generally PD control is sufficient for controlling motor speeds and gives a good performance. The integral control improves steady state perfomance, that is when the motor speed has settled to a fairly consistant speed, how far away from the setpoint is it running. By adding together all prevoius errors it is possible to monitor if there are accumulating errors. As if the motor is turning stightly too fast all the time, the error will always be positive so the sum of the errors will get bigger, the inverse is true if the motor is always going to slowly. An addition error value, which i call Ei is simply the sum of all previous error. This can be added into the equation, again with a gain contant to give the full PID equation:
c = (E*Kp)+(Ed*Kd)+(Ei*Ki)
The values of the K gain contants affect how much of each error are used to alter the motor speed. Their values are usually found by trial and error. To give an example the picture i have used for this walkthrough is a graph of actual encoder values taken from my robot when it is moving, with a setpoint of 10, You can see that the readings increase above 10, oscillate around 10, before settling to around 10. This is a common PID control loop response. I used settings of:
Kp = 1
Kd = 0.5
Ki = 0.3
I hope this is helpful, as i said it is quite a tricky thing to explain and im not sure i have done a very good job, please ask if there is something i have not explained very well and i will try and improve it.
The example i have given using a motor and an encoder is just one application. If using a sensor to follow a wall the error value can be calculated from the sensor reading compared to a setpoint. I have done this using my two wheeled robot and just altered the speed of one wheel while the other ran at a set speed. It worked very well using just PD control. To make my two wheeled robot go in a straight line is had to alter the speed of both wheels. To do this i sampled the left and right encoders, and found an error value for each. I then put these values into the PID equation and found a value to add to each motor.
Below is the code i have used to control two motors on my robot. the doPID loop is called everytime the encoders are sampled and the motor speeds altered.
int preverrorl; //left motor previous error
int preverrorr; //right motor previous error
int Ierrorl=0; //left motor intergral error
int Ierrorr=0; //right motor intergral error
int encodercountl = 0;
int encodercountr = 0;
int setpoint = 20; //setpoint
int errorl; //error from left encoder
int errorr; //error from right encoder
int derrorl; //derivative error left
int derrorr; //derivative error right
int KP = 2; //PID proportional gain constant
float KD = 1; //PID derivative gain constant
float KI = 0.5; //PID intergral gain constant
errorl = setpoint - encodercountl; //calculate error values
errorr = setpoint - encodercountr;
encodercountl = 0; //reset encoder counts ready for next sample
encodercountr = 0;
derrorl = (errorl - preverrorl);
derrorr = (errorr - preverrorr);
cl = ((KP*errorl) + (KD*derrorl) + (KI*Ierrorl)); //PID equations
cr = ((KP*errorr) + (KD*derrorr) + (KI*Ierrorr));
if((OCR0 + cl) > 255) //prevent OCR0 and OCR2 from overrunning 255
OCR0 = 255;
OCR0 = OCR0 + cl; //use output from PID equations to alter motor speeds
if((OCR2 + cr) > 255) //where OCR0 and OCR2 are PWM output values
OCR2 = 255;
OCR2 = OCR2 + cr;
preverrorl = errorl; //set previous error to current error
preverrorr = errorr;
Ierrorl = Ierrorl + errorl; //add current error to integral error
Ierrorr = Ierrorr + errorr;