Questions about PID (actually PD) tuning

I’m working on tuning my robot to follow a line using four QTR-1A sensors and I had a few questions.

  1. When I tune Kp should I be doing so on a straight line? I find that I can get it to follow a straight line quite nicely but it takes wide corners for the corners in my track. If I tune Kp for it to be able to navigate the corners staying on the line I get a lot more oscillation which I would think would be bad but again, not sure if I’m supposed to be basing results on a straight line or the actual line.

  2. Using the QTR-1A sensors do I need to have a delay time in my loop for the ADC settling or is that already in the library code? Seems like it didn’t make a difference whether I had a 10mS delay or no delay.

  3. Suggestions have been made to skip the integral term, can anyone explain why this is? I would think having the full capability of PID would make it faster and more accurate with corrections.

Hello.

  1. Ideally, you should be tuning your PID constants on the actual course you want to run it on, or at least on one that is as representative as possible of the kinds of conditions your robot needs to be able to handle. It is not very hard to follow a straight line, so your PID constants do not need to be particularly well tuned to accomplish the task. The difficulty arises when you have to take a sharp turn at high speed, because poorly tuned constants will cause you lose the line if you don’t react fast enough or overshoot if you react too strongly.

  2. I’m not sure I understand how a delay in your loop would allow for ADC settling. The voltages on the sensors are changing independent of your main loop. The QTR library code does everything necessary to accurately read the sensor voltage, so you can read the sensors repeatedly without any delays.

  3. The integral term is useful for situations where some external force is consistently working against you. For example, you would want to use the I term if you are programming a robotic hand to lift a weight to a specific height. Without I, it’s possible to settle on a steady-state situation where the error, E, is not zero due to the effect of gravity on the weight. Specifically, the system will settle to that value of E where P*E generates the proper motor power to compensate for the gravitational force of the weight. The I term acts to shrink E to zero over time by progressively increasing the motor power while E is not zero.

This is not really the situation you encounter when following a line. You could conceivably have some problems if your two motors are very poorly matched and want to make the robot drive in a tight arc when driven at the same duty cycle, but the net effect of leaving out the integral term is that the robot will follow the line off-center. If your robot looks like it’s centered over the line when following a straight segment, you don’t need to worry about the integral term, and I recommend leaving it off. Tuning three independent parameters is a lot harder than tuning two. In this case, the I term won’t get you much, and it can really hurt you if you pick a bad value for it.

- Ben

I read in the PDF that a delay was needed between readings, that was why I wasn’t sure if there was a delay required.

I am currently having issues finding a sufficient P value. I set it really low and it sways along the straight line but can’t make the corners, I increase it just a little bit (less than one) and then it jerks back and forth for about a foot and just shoots off the line so I assume the correct P value is somewhere in between but I’ve tried everything in between and it either can’t make the turn or jerks back and forth. Can this be caused by something other than my code? The tires have traction and aren’t slipping so that isn’t it.

My position is -1500 to 1500 so I assumed the P term would be somewhere around 0.17 since 1500*0.17~255 meaning when the farthest sensor sees the edge it will be to the point where the motor gets shut off. That said, I started my P at .01, made it up to .2 and tried everything in between.

I just thought of something, could it help if I set the motor setpoint to say 200, that way the outside motor could go faster than it normally would on the straight portion? Right now my code won’t allow the motor speed to exceed the setpoint so it’s basically only slowing one motor or the other.

#include <PololuQTRSensors.h>

#define NUM_SENSORS             4  // number of sensors used
#define NUM_SAMPLES_PER_SENSOR  4  // average 4 analog samples per sensor reading
#define EMITTER_PIN             QTR_NO_EMITTER_PIN

// sensors 0 through 3 are connected to analog inputs 0 through 5, respectively
PololuQTRSensorsAnalog qtra((unsigned char[]) {
  0, 1, 2, 3}
, 
NUM_SENSORS, NUM_SAMPLES_PER_SENSOR, EMITTER_PIN);
unsigned int sensorValues[NUM_SENSORS];
const float Kp = 0.2;
const float Kd = 0;
const int M1 = 255;
const int M2 = 255;
int lastError = 0;
int m1Pin = 9;
int m2Pin = 10;
int triggerPin = 8;
int buzzerPin = 7;
int redCount = 0;
int sensorValue;


void setup()
{
  delay(500);
  int i;
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);    // turn on LED to indicate we are in calibration mode
  for (i = 0; i < 400; i++)  // make the calibration take about 10 seconds
  {
    qtra.calibrate();       // reads all sensors 10 times at 2.5 ms per six sensors (i.e. ~25 ms per call)
  }
  digitalWrite(13, LOW);     // turn off LED to indicate we are through with calibration

  // print the calibration minimum values measured when emitters were on
  Serial.begin(9600);
  for (i = 0; i < NUM_SENSORS; i++)
  {
    Serial.print(qtra.calibratedMinimumOn[i]);
    Serial.print(' ');
  }
  Serial.println();

  // print the calibration maximum values measured when emitters were on
  for (i = 0; i < NUM_SENSORS; i++)
  {
    Serial.print(qtra.calibratedMaximumOn[i]);
    Serial.print(' ');
  }
  Serial.println();
  Serial.println();

  // Wait for user to push button
  pinMode(triggerPin, INPUT);
  pinMode(buzzerPin, OUTPUT);
  while(!digitalRead(triggerPin)){ 
  }
}


void loop()
{
  // read calibrated sensor values and obtain a measure of the line position
  // from 0 to 3000, 1500 is center due to the use of four sensors
  unsigned int position = qtra.readLine(sensorValues);

  // Calculate error based on center
  int error = position - 1500;

  // Calculate motor speed to correct the direction
  int turn = Kp * error + Kd * (error - lastError);
  int m1Speed = M1 + turn;
  int m2Speed = M2 - turn;

  // Ensure the motors do not go below 0 or above their set max
  if (m1Speed < 0)
    m1Speed = 0;
  if (m2Speed < 0)
    m2Speed = 0;
  if (m1Speed > M1)
    m1Speed = M1;
  if (m2Speed > M2)
    m2Speed = M2;

  // Set Motor Speeds
  analogWrite(m1Pin, m1Speed);
  analogWrite(m2Pin, m2Speed);
  lastError = error;


  // CODE FOR CHECKING COLOR SENSOR
  sensorValue = analogRead(A4);
  if (sensorValue > 60)
    redCount++;
  else
    redCount = 0;

  if (redCount > 3)
    digitalWrite(buzzerPin, HIGH);
  else
    digitalWrite(buzzerPin, LOW);

}

Thanks for clearing up that I term description, that made it very easy to understand

Which PDF? What exactly does it say?

How far apart are your reflectance sensors and how thick is your line? One thing I suggest you try if you haven’t already is printing out the line position/error as you slide your robot across the line. Make sure that the numbers you see are constantly (and, ideally, smoothly) increasing as you slide the bot. If there are local minima/maxima in your error function (as could happen if your sensors are too far apart relative to your line width), PID will probably not work well.

Here are a few tips/suggestions:

  1. Don’t start your PID tuning with your motors at full speed. It’s much easier to get a line-follower working at low speeds, and doing so can at least get you in the right ball park for your PID constants; from there you can gradually increase your speed and adjust the constants as necessary.

  2. The P term alone should be enough to let you follow a reasonably straight line at slow speeds (if your bot seems unstable in such situations, you might have a sign error), but it is generally not sufficient for a good, fast line follower on a real course. To get decent results, I expect you will need to add a differential term, D.

The logic you used to pick your proportional constant, Kp, is sound, so I suggest you go with that and start testing differential constants, Kd. Note that Kd is being multiplied by the difference in successive errors, which will generally be much smaller than the error itself, so Kd usually needs to be much bigger than Kp for it to have a comparable effect on the motor speed differential. I suggest you start with a Kd constant that is 10 to 20 times bigger than Kp. If the Kd term doesn’t make things better, check to make sure your signs are right.

If you want to be able to drive quickly around sharp turns, you are going to need a large P term so that your bot reacts sufficiently as it starts to drive off the course, but this in turn can cause a severe overshoot and subsequent oscillations without an appropriate D term. The D term takes history into account and contributes a response based on what is happening to the error term over time. If the error is getting smaller, it acts to weaken the response so you don’t overshoot, and the faster it is shrinking, the more it weakens the response. On the other end of the spectrum, if the error is getting bigger despite your proportional response, the D term makes the response even stronger, helping to keep you on the line as even as it’s sharply arcing away.

  1. Working with floats is very slow on an 8-bit MCU like the AVR, and it’s not really necessary for the kind of math you are doing. I suggest you consider working with integers instead. For example, if you want a proportional constant Kp of 1.7, you can do:

response = error * 17 / 10; // instead of response = error *1.7;

- Ben

I can’t seem to find it now, it’s probable that I am mixing information. I had thought I read it in the library PDF but couldn’t find it in there. It’s also possible it was an old one, the original PDF I had linked me to a library that had a last modified date of 2008 and rather than just having QTR library it had everything for Orangutang as well. It wasn’t until getting frustrated that it was not working that I found the newer library which had examples for the QTR and was last modified in late 2010. I will let you know if I find it.

The line is standard electrical tape width, I don’t have any with me so I’m not sure of the size, I would guess 3/4". My line sensors are about 5/8" center on center, I wasn’t sure how close to put them but since I am using four instead of eight I assumed spreading them slightly would give me more room to move. The sensors were about 1" from the line and I tried lowering them all the way to 1/4", I saw much more accurate results moving them closer but I’m not sure if it hindered it at all. I will definitely log the errors, I had done so prior to putting them on the bot and they were working fine but it can’t hurt to try again. I was actually thinking about making a graph in Excel after logging the errors as it was driving down the line.

I had the bot running well at half speed, the D term was about twenty times the P term so your comment reassures me that I had about the right numbers. After I increased the speed I went back to trying to tune only P and this is where my problem is.

So my response should be a float then and have all others be ints that are multiples of 10 (or 100 or so on).

I really appreciate your help, this is really helping me better understand how PID controllers work and especially how to tune them.

No, I’m suggesting you consider eliminating floats entirely from your program. The response basically a motor speed differential, and since your motor speeds are integers ranging from -255 to +255, there is no reason to represent the response as a float (you have plenty of resolution just with integers).

Your sensor spacing sounds good, but I suggest you mount your sensors much closer to the ground. You will probably not get a very good signal at a height of 1", and there’s more potential for interference from ambient IR (unless you add extra shielding). Have you seen our QTR sensor app note. Our oscilloscope captures show the effect of distance on our QTR-xA sensor output, and in our tests you can see that things were already getting pretty bad at just 3/8". Our library’s calibration routine can make it harder to notice loss of resolution that occurs you raise the sensor higher off the ground: at a height of 1", full white might result in a sensor output of 4.7 V compared to perhaps 200 mV at a height of 1/8".

By the way, if something of ours is still linking to outdated content, please let us know so we can fix it.

- Ben

That’s a good point, I will eliminate floats then.

I did see the document on the o-scope readings from the sensor, that was mainly why I had moved it closer. I was actually going to hook one up to an o-scope myself just to see what the optimal distance would be based on the actual track itself. I will be adding shielding, there have been cases where sun has been on the track so I almost have to.

I will try working on this again tonight and post some results, hopefully some better ones with all of your tips.

Here is a link to that file which has old QTR Sensor Library code in it.
pololu.com/file/download/pololua … e_id=0J127

I can’t find the link to the PDF that had that link in it but it is titled Arduino Library for the Pololu QTR Reflectance Sensors. Every attempt at finding it brings me to the current PDF file but I had printed the one with the link and it is in fact an old one because some of the wording is different. I’ll keep trying to find it so nobody else gets ahold of the old PDF and hence the old library link.

I cannot, for the life of me, find that PDF file I have printed. I went through my entire internet history and everything points to the PDF that has a copyright 2001-2010 but the PDF I have printed says copyright 2001-2009, there is no file name on the printed document either.

I may have printed it from a different computer but I will definitely post if I ever find it.

Dear Ben,
will You Please help me to tune my PID line follower.I am posting my code.and one thing there i have made one change in my header file…its pololuqtrsensor.h not qtrsensor.h…
code for PID controller.pdf (138 KB)

Hello.

It is not clear where you got your code for PID control or what components you are using. Did you write the code yourself? If not, could you post a link to where you found it? Could you also tell us more about your setup?

It is also not clear if you tried following the steps suggested by Ben above. Did you try following them? Could you describe what your robot is doing when it tries to follow the line?

By the way, you can use the [ code ] tags to post your code directly to the forum so others can easily view it.

- Jeremy

Thanks for the attention jeremy I am using Arduino Uno R3 which has atmega328p,Ardumotomotor driver of sprkfun,and Pololu Qtr8RC sensor array of 8 sensors.As I mentioned reffering some codes on net I have made this code.The code is good as it lets the robot follow the line at avgspeed255.My question is about the PID tuning.I have used the technics mentioned by Ben thats how I got Kp as 32 and Multiplying it by 10 I took Kd as 320.By this factors the bot is able to take turns on circular arc but if there is a sharp turn then it loses the path.Now My aim is to set Ki so that it can take sharp turns.

I do not expect a line follower using PID control will handle sharp turns well. You might try adding a subroutine that uses the outer sensors on the QTR-8RC array to detect those corners and turns the robot sharply accordingly. You might take a look at the 3pi maze solving example code to see how the 3pi detects and makes 90 degree turns by driving one of the wheels backwards. You can find more information in the “Example Project #2: Maze Solving” section of the 3pi user’s guide.

- Jeremy

I have reffered it it but its codung is not feasible for my bot because all sensors of my bot are in a straight line.So will you please suggest some changes in my code

You might have to adjust the timing for how your robot turns after detecting a corner. Since our Zumo robot also uses a straight reflectance sensor array, you might also find it helpful to refer to the “Maze solver” example from the Zumo Shield user’s guide.

- Jeremy