External factors in line follower

I was curious if there was a way to determing if the inability to tune the proportional value is due to an external factor. It seems any time I increase the wheel size beyond a certain point, I can no longer tune P. No matter how I change P it never stays on the line so technically I can’t even start tuning the other values.

I’m wondering, is the wheel size causing the moment of inertia to increase and thus making P more erratic and unstable? Also, would it make a difference, in this case, if I had PWM at 16 bit or 8 bit?

I tried putting the sensors closer together which seemed to give me a slightly better result but still, no P value will keep the bot on the line.

Specifically, how would I determine:
+is my MCU reading the line fast enough to make accurate decisions?
+is 10 bit a high enough resolution for my ADC?
+is 8 bit PWM a high enough resolution?
+Using analog QTRs I am taking four samples, will it work better with more samples or less?
+Is my line follower too heavy? This one is tricky because I guess, if it isn’t skidding through the corners at the desired speed it should be okay right?

Sorry for so many questions all at once, I appreciate any help. I am trying to make a really fast line follower in hopes of entering a competition. I am assuming that there is a way to know all of this information otherwise the winners of these competitions wouldn’t all be running at speeds around 3ft/s, they would be running faster. I am only able to get up to about 2ft/s before P is not tuning properly.


I think the root of your problem is that there is no reason you should be expecting to be able to make a fast line follower with just the P term. You say that no P value will keep the bot on the line, but I think what you’re neglecting is the modifier “at high speeds”. You should be able to find a maximum motor speed at which the bot will stay on the line with just P. At that point, you should start adding in D rather than increasing motor speed. With D in place, you can increase your motor speed and tweak the constants as you go. The importance of D grows as your speed grows (or as the turns on your course get tighter for your given speed), since your robot starts requiring the ability to predict what will happen to the error in the future based on current trends (i.e. the derivative) in order to avoid having P lead to overshooting or undershooting.

To answer your other questions:

  1. If you know how long your MCU takes to read your line sensors and the maximum speed at which you plan on running your robot, you can compute approximately how far it will travel between readings. You should be able to judge from this number whether you’re update rate is reasonable. For example, if you can update at 1 kHz and your bot travels at a maximum of 3 m/s, you sample the line every 3 mm, which should allow for timely reactions to changes. If you update at 10 Hz, you sample the line every 30 cm, which does not allow for timely reactions to changes (you could be 30 cm off the course before your robot knows it lost the line).
  2. 10-bit resolution should be plenty for your ADC readings.
  3. 8-bit resolution should be fine for your PWM.
  4. Assuming you are using the QTR-A sensors, averaging is like a software low-pass filter; it makes the results more immune to noise, but it introduces lag (or decreases your sampling rate, depending on how you average). In general, I think the inertia of your motors can serve as this low-pass filter for you: a single noisy reading might cause you to very briefly drive the motors incorrectly, but they simply won’t have time to react over the short time scale of the noise. You might want to average a few readings to help keep noise low, but don’t let your averaging make your sampling rate unacceptably low.
  5. The weight of your bot will not manifest itself as skidding. The force that causes your bot to skid is proportional to its mass, as is the frictional force that keeps your robot from skidding, so mass cancels out of the equation when it comes to determining if your robot will skid. Depending on how the mass is distributed, your bot might roll, but this also not a function of the absolute mass, only its distribution. The main way mass will affect your bot is via inertia: heavier bots will accelerate more slowly than lighter bots with the same motors, so they will generally be less maneuverable and will take longer to react when they encounter sharp turns.

- Ben

Thank you for your response, since tutorials always tell you to get P first then D I just assumed I would have to get P working at the motor speed I desired and then work with D after the bot followed the line with P. I shouldn’t have said no P will work, I can get P to work when the motors are at half speed but any faster than that P won’t keep it on the line.

I will add a call in my loop to tell me how much time has passed since the last iteration and hopefully I can find out if my loop is efficient enough for the speed I want.

Thanks for all of your answers, this information is very helpful.

Just make sure your timing code doesn’t significantly affect the timing measurement! For example, printing to an LCD or sending serial data can be relatively slow, so try to account for that. You might want to time how long it takes to sample the line 100 or 1000 times and then divide that result to get a good estimate for the average sample rate. Also, note that you should be concerned with the worst-case delay between sampling, too. For example, does your main loop occasionally block execution for 50 ms to blink an LED, play music, or transmit data? Do you have any slow interrupts enabled? I suggest you look through your code to try to identify all the things that could delay your main loop and by how much. What microcontroller/development board are you using?

If there is a lot of variation in your sampling rate, you might want to incorporate time in to your derivative term. The derivative is intended to be a measure of the rate at which the error is changing, so time is inherently a part of it, but you can often just fold it into the constant if your main loop timing is relatively constant.

Perhaps contrary to what you might be reading in other PID tutorials, I suggest you just get in the right ballpark for P at low speeds and then start adding in D (make sure you get the sign right or tuning will be very frustrating!). I also suggest you make an educated guess when picking your terms. For example, if I have an 8-bit measure of where the line is (i.e. the error) and I am using 8-bit speeds for my motors, I might pick an initial P constant of 1, since that means motors will get full power when I detect the line is at an extreme. I can immediately see that a P value less than 1 is probably too small (I’ll never get full speed out of my motors), but I know I might need to make it bigger to get my bot to be more responsive, especially if my line sensor array is much wider than the actual line. Similarly, if my error ranges from -1000 to 1000 and my motor speeds range from -10 to 10, I might start with a P constant of 1/100. Once I know what P constant logically makes sense, I can take a stab at D by noting that the D constant usually needs to be much bigger than the P constant in order for it to have a comparable effect. This is because the successive difference in errors is typically quite small over short time scales. The ideal D will proportional to your sampling rate since it effectively has the sampling period in the denominator of the calculation, but in my experience it has usually be around 10 to 20 times the P constant.

One last bit of advice: if you aren’t already, I suggest you limit your motor speeds to be between 0 and MAX_SPEED. The PID calculation can lead to negative motor speeds, but letting your motors run backwards during line following is generally not helpful.

- Ben

Right now I have eliminated all code other than the PID, aside from one serial print for each iteration that displays the error value. I didn’t have a chance to time my readings last night but I will follow your suggestion to get a more accurate sample rate. I am using the Arduino.

I was able to get P and D functioning and get the bot following the line but when I tried to increase the motor speeds after that I couldn’t get my PD values tuned to work. That is why I was wondering if there was some sort of limit in speed that I could go, it seems I can get the bot line following at max speed with small wheels and an equal speed relative to large wheels but when I try to up the motor speed with the large wheels to get the bot faster than it was at max with smaller wheels I can’t seem to tune it. I am still a newb at this stuff so I’m sure it’s just a matter of trial and error it’s just that from the slowest setting to half way I can tune the thing in thirty minutes but any faster and I just can’t seem to tune it, even after hours and hours of different values.

I did do that with the proportional P value as you stated, my center line is at an error of 1500 and my PWM is 8 bit so I set an inital P of 0.17 so that is the minimum P to get full speed when centered on the line. At least I think I did that right. My array of four sensors is just over twice the line width with a line at .75". I have four sensors at about 0.4" apart on center and they are 0.125" above the line.

I do have if statements to only allow values between 0 and MAX_SPEED.

Thanks again for the information, I’ll post any progress and probably any problems, so far it seems I only post problems but I am learning a lot from doing so.

Given that it sounds like you have some relatively fast line following working, you must be generally doing it right, but this approach seems strange to me. The point of PID is to get your error to zero as quickly and stably as possible, so I expect the center line to be at an error of zero and your left and right extremes to correspond to something like -1500 and +1500. When doing line following with differential-drive robots, I have used the result of the PID computation as a speed differential parameter, so that the motor speeds become:

speed_differential = Perror + Ddelta_error;
left_motor_speed = MAX_SPEED - speed_differential;
right_motor_speed = MAX_SPEED + speed_differential

Note that you have to make sure you get the signs right so that the robot turns in the proper direction when it’s not over the center of the line, and also note that I would add some code to keep the motor speeds within the range of 0 and MAX_SPEED.

I know this is sort of a tritely obvious statement, but you cannot make a line follower arbitrarily fast; at some point, it either won’t be able to react quickly enough to hold the line on sharp turns or it won’t have enough friction to keep from skidding on the turns. Assuming your speed is not limited by your motors, it will then be limited by things like the friction of your tires and the responsiveness of your system. One problem with going to bigger wheels is that while your max speed goes up (which makes following the line harder in itself), your ability to accelerate goes down (you are trading wheel torque for speed), which can limit your robot’s ability to quickly respond to to the course.

Here are a few other miscellaneous thoughts:

  1. How far in front of your wheels are your sensors mounted? The farther out they are, the more advanced warning you get when a sharp turn is coming, which in turn means you can go faster and still have time to detect and react to turns. If this isn’t intuitively obvious to you, just consider how fast you would be be comfortable driving if your visibility was restricted to 20 feet.

  2. I expect you’ll need a bigger P value than 0.17 to hold the line while moving quickly around sharp turns.

  3. When you time your main loop, I suggest you do it once with your serial prints there and once without it. I would not be surprised to find out that the serial prints slow your main loop down by a non-negligible amount, depending on how many bytes you are printing and at what baud rate. For example, sending a 14-byte string like “error = -1773” at 9600 bps (as seems to be the default in a lot of sample sketches) will delay your main loop by approximately 15 ms and limit your update rate to something like 70 Hz, which is not very high.

  4. Working with floats on an AVR generally pretty slow and not necessary for PID algorithms. I suggest you modify your code to use only integers. For example, you can rewrite 0.17 as 17/100, so your P term would become:

P = 17 * error / 100;

If you do switch to integers, however, make sure your calculations don’t unexpectedly overflow!

  1. If you are not powering your motors with a regulated voltage source or using encoders, tuning your PID constants will be harder because your robot’s speed will be changing as the batteries discharge. Well-tuned constants might start behaving poorly as the robot’s batteries run down, at which point maybe you’ll retune it only to find that now it doesn’t work with freshly charged batteries.

  2. Keep your robot’s tires clean! When you are running a course near the limit of what speeds are possible, dust build-up on your tires will lower their traction and cause the robot to start behaving differently. In general, the key to tuning PID parameters by trial and error is to hold all other variables, like maximum motor speed and tire traction, constant. If these things are changing while you are tuning, it becomes difficult to discern the effects of the tuning from the effects of the other variables.

  3. Are you using our QTR Arduino library to get the line position/error? When doing PID, it is important to have a good, accurate, monotonic error function that offers enough resolution. If you are just looking at the four sensors to say that the line position is either 0, 1, 2, or 3 based on which sensor sees the most, you will not have very good results compared to using the analog readings of all the sensors to get a line position between 0 and 3000.

- Ben

This is interesting, apparently I have been doing this wrong and hadn’t noticed. I, for some reason, had the idea that my calculations were setting 1500 on the line and outsides being 0 and “-0” so if it was slightly left it would be 500 and slightly right would be -500 therefore by the time it reached the sides of the line, using a P of 0.17 the respective motor would shut off. I guess that would work if I were calculating my error that way but I just checked and you are right, it’s 0 on center and 1500 to -1500. This would make more sense anyway because a 0 would not be able to tell the bot which motor to shut off and it would shut both off. Well, that explains a lot.

  1. Funny you mention this, I just had that thought last night and I moved the wheels as far back as possible and was going to work them up to see if there was a “sweet spot”. They were about five inches from the sensor to the spindle, now they are about eight so it should make a pretty significant difference I think.

  2. I definitely need to recalculate my P value now that I realize I was making false assumptions.

  3. YIKES, I didn’t realize that could cause a delay like that, I will definitely fix that.

  4. I am using the division factor, I just said 0.17 because that is the “actual” after division.

  5. I am not currentlyl regulating the voltage, that is definitely in my list of to dos but I am recharging the batteries after every 10 or 15 runs. I do understand this can pose an issue using unregulated voltage.

  6. Will do

  7. Yes, I am using the QTR Arduino libary and I am getting the monotonic results, I checked this using the serial print in my loop.

I will definitely be working on this tonight, I have a lot of new stuff to look at and keep in mind, and I definitely need to change my P value, I can’t believe I was making that mistake the whole time without realizing it.

So, I had a few factors that I think were causing my problem. I moved the wheels back and almost instantaneously the robot was much smoother, it stayed on the track the whole time with just P.

The D value wasn’t doing much so I recorded my runs to see what was going on and noticed that the tires were slipping a lot. I put some grippy rubber on the wheels and it runs so smooth after tuning P and D. I have it currently at 1.5 m/s but I have maxed out my motor speed (they are slow motors but they were all I had on hand at the time).

My sample rate is 510Hz when taking four readings from four sensors. I don’t really think it’s possible to speed that up unless I take less readings or use external ADCs that are faster. Taking four readings from four sensors is 1.6ms, my readings rate is at 1.9 with all my code.

Thanks for all of the help and suggestions

Thanks for letting us know how your tuning process went, and I’m very happy to hear that things are working so well now. I think you can be pretty content with a 500 Hz update rate. Do you have any plans to try some more powerful motors? If so, I’d love to hear how it goes!

- Ben

I definitely do plan on using some faster motors. I will post any updates as they arise, thanks for your continued interest.

The display of the sensor array on the LCD is very clear and is great for debugging. However, according to your post:

  • How can I time my main loop ?
  • What’s the solution for this phenomenon ?

Thank you,

Hi, Alex.

You should be able to use one of your microcontroller’s hardware timers to keep track of how much time has elapsed. Record the time at the start of your main loop and then see how much time has passed when you get to the end. The specific way you do this will depend on the hardware you are using (for example, if you are asking specifically about how to do this on a 3pi, I could give you more targeted advice).

I am not sure I understand your second question. Are you asking how to avoid having debugging statements slow down your main loop? If so, the solution is to either remove them or to call them relatively infrequently (e.g. update the LCD once every 500 ms so that, on average, your main loop runs very quickly).

- Ben

Thank you for your reply, Ben.

I have successfully built a line-following robot with PID implementation according to your guide. But I have a new problem.

I need two power sources: 5V and 9V. 5V to power the IC and 9V to power the motors.
I am using 4* 1.2V battary as the main power supply for the bot.
The batteries are connected to a https://www.pololu.com/catalog/product/2116
to create a 9V power source. And a standard 7805 is used to step-down the voltage to 5V.

The U3V12F9 is connected with a 33uF Cap between the Vin and Ground pins.
The 7805 is connected with 100nF Cap in both Vin and Vout.

I have connected a standard 2-leg-ed switch between the batteries and the U3V12F9.
The bot works well for about 2 mins and then it suddenly stops.
I have measured the power outputs and everything seems to be working.

Any ideas to why it had happened?


It sounds like the regulator is overheating and shutting down, or maybe your batteries are drained (what do you get if you measure your battery pack voltage with a multimeter?). What is the stall current of your motors, and how many motors do you have? How are your motors being used? Does everything work again after a short period of being shut down? Does the problem still happen with your robot in the air or with your motors only running at part-speed?

- Ben

-The multimeter reads >2.5 V for the battery back and 9V for the output of the U3V12F9.

If in fact the U3V12F9 is overheating, how can I cool it down?
Why does it overheat in the first place?

-Stall currnet is 1.6A

  • 2 motors

-Yes, that is exactly what happens.

-I have an IF condition to stop the motors when ALL the five sensors see white or black.
When the robot is the air the motors do not move. They are not supposed to.

Come to think of it, the logical explanation is that the device overheats.
When I shut the bot down and wait for a few minutes and turn it back on, everything
seems to be working well until it stops again.

I guess it overheats, stops working, and when I shut it down, it cools down.

Any solution for overheating?


The nominal voltage for your battery pack is 4.8 V, and you should treat it as severely discharged when it gets close to 4 V. Can you try recharging your batteries?

- Ben

I have made a mistake.

I did not use the 4 battery back. I used a slightly drained 9V battery.
When I measured it’s voltage, it showed 8.7V.


So what were you measuring when you got a result of “>2.5V”?

9V batteries are not appropriate for high-current applications like this. Can you try a more suitable power supply, like the 4-cell NiMH pack you originally mentioned?

- Ben

The product’s description states that the operating voltage is
2.5 to 9V. I meant that the reading was above 2.5V, consistent with the
operating voltage

I will try the 4x1.2 batteries and report the results.


I see. Please note that I was asking for the actual voltage for a very specific reason (to see how discharged your batteries were), so your withholding specifics because they don’t seem relevant to you is not helpful.

- Ben