Pololu Robotics & Electronics
My account Comments or questions? About Pololu Contact Ordering information Distributors

Qtr 8a usage to detect white line on black surface



i want to use the qtr 8a sensors to detect a white line on a black surface,
which means the robot has to follow a white line on a black background,
so for this type of line detection how to make the robot calibrate so that it returns a value of say 0 - 5000
once calibrated.

i am using arduino to code this so i want to know whether a function is already written to carry out this task in pololu arduino library

so a snippet of code on how to read a white line and calibrate the sensors to a white line,
would help me a lot, i read the documentation of qtrA found a function which is mentioned in the post below i dont know how to implement it on an arduino IDE .
pololu.com/docs/0J19/3 , i read this and functions are written but how to implement them in
arduino is my doubt.

thanks for any help,


[quote]unsigned int readLine(unsigned int *sensorValues, unsigned char readMode = QTR_EMITTERS_ON, unsigned char whiteLine = 0)

Operates the same as read calibrated, but with a feature designed for line following: this function returns an estimated position of the line. The estimate is made using a weighted average of the sensor indices multiplied by 1000, so that a return value of 0 indicates that the line is directly below sensor 0 (or was last seen by sensor 0 before being lost), a return value of 1000 indicates that the line is directly below sensor 1, 2000 indicates that it’s below sensor 2000, etc. Intermediate values indicate that the line is between two sensors. The formula is:

0value0 + 1000value1 + 2000*value2 + …

 value0  +  value1  +  value2 + ...

As long as your sensors aren’t spaced too far apart relative to the line, this returned value is designed to be monotonic, which makes it great for use in closed-loop PID control. Additionally, this method remembers where it last saw the line, so if you ever lose the line to the left or the right, it’s line position will continue to indicate the direction you need to go to reacquire the line. For example, if sensor 4 is your rightmost sensor and you end up completely off the line to the left, this function will continue to return 4000.

By default, this function assumes a dark line (high values) surrounded by white (low values). If your line is light on black, set the optional second argument whiteLine to true. In this case, each sensor value will be replaced by the maximum possible value minus its actual value before the averaging.[/quote]

i know that the above function is to be used,
so i want to know where to implement this function in the following code provided at the pololu arduino library. i want to make the robot calibrate for a white line on a black surface and also follow the white line on black surface.

[code]#include <PololuQTRSensors.h>
#define NUM_SENSORS 6 // number of sensors used
#define NUM_SAMPLES_PER_SENSOR 4 // average 4 analog samples per sensor reading
#define EMITTER_PIN 2 // emitter is controlled by digital pin 2

// sensors 0 through 5 are connected to analog inputs 0 through 5, respectively
PololuQTRSensorsAnalog qtra((unsigned char[]) {0, 1, 2, 3, 4, 5},
unsigned int sensorValues[NUM_SENSORS];

void setup()
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
for (i = 0; i < NUM_SENSORS; i++)
Serial.print(’ ');

// print the calibration maximum values measured when emitters were on
for (i = 0; i < NUM_SENSORS; i++)
Serial.print(’ ');

void loop()
// read calibrated sensor values and obtain a measure of the line position
// from 0 to 5000, where 0 means directly under sensor 0 or the line was lost
// past sensor 0, 1000 means directly under sensor 1, 200 means directly under sensor 2, etc.
// Note: the values returned will be incorrect if the sensors have not been properly
// calibrated during the calibration phase. To get raw sensor values, call:
// qtra.read(sensorValues);
unsigned int position = qtra.readLine(sensorValues);

// print the sensor values as numbers from 0 to 9, where 0 means maximum reflectance and
// 9 means minimum reflectance, followed by the line position
unsigned char i;
for (i = 0; i < NUM_SENSORS; i++)
Serial.print(sensorValues[i] * 10 / 1001);
Serial.print(’ ');
Serial.print(" ");


in the above code how to find out the position of a white line on a black background.

thanks for any help,

ashwin j



Try changing this line:

unsigned int position = qtra.readLine(sensorValues);


unsigned int position = qtra.readLine(sensorValues, QTR_EMITTERS_ON, 1);

By setting the whiteLine argument to 1, you are telling the library to compute the position of a white line on a black background. You don’t need to make any changes to the calibration code.

- Ben


hello ben,

i first of all want to thank the pololu and their engineers for their high quality products .
i won my first line following competion yesterday.
the rule was to follow a white line on a black background,
this was the robots performance on a black line which is really good

i actually changed the above function as you said but the robot had problems in detecting the line effeciently for whiteline functions
can u please tell me how does the proportional and integral change for a whiteline and the same pid constants wont work for white line
because the position would change and the robot was having problem to detect the line as you can see in this video

i think the problem is with some logic infinding the powerdifference for the white line which am not able to find,

[code]void loop()

unsigned int position = qtra.readLine(sensorValues, QTR_EMITTERS_ON, 1);

int power_difference = proportional/1+ integral/10000+ derivative*7;// these are the pid constants in the second video which worked for me

// Compute the actual motor settings. We never set either motor
// to a negative value.
const int maximum = 150;
…rest of the code goes the same as in pololu doc
am not able to hit high speeds like in the first video in which the robot follows a black line…
maybe am going wrong somewhere i guess please point out the mistake if something else is to be added in the code for white lines
in the above code i use the same coding method to caluclate power difference as mentioned in pololu documentation

may be the positon varies from black to white line? so may be even the pid constants also varies hence power difference also vary.
and are different in case of a white line…

thank u ben,

3pi white line follower

Thank you for sharing your video, and congratulations on your first-place finish!

I do not think your problems are due to a change from black line to white line, and there is nothing special about a white line that would require you to use different PID constants. Rather, I think you are just running your robot on a different course, possibly with a line that has a different thickness and/or a different surface material. I think you would probably see the same behavior if you ran on the same course laid out with a black line on a white background.

- Ben


thanks ben,
i am very thankful to you…

[quote]I think you are just running your robot on a different course, possibly with a line that has a different thickness and/or a different surface material. I think you would probably see the same behavior if you ran on the same course laid out with a black line on a white background.
actually the competition used printed sheets ,where a white line was printed over a black surface,which means no tapes or oil paints were used to make the track surface ,so if the sheets are printed then there is no change in thickness of white line to a black surface at all,
actually i have tested it in different surfaces ,like a whiteline over a black surface first i painted the sheet with an oil paint, oil paint gives a shiny finish which means it gives a good contrast with white surface ,same pid constants didnt work on 3pi as well as qtr 8a sensors which you have seen in the video.
actually i have exact same problem for 3pi also ,even 3pi the same pid constants arent working ben,am damn sure on this i atleast tested it on 3 types of surfaces
may be ill post pics of all the surfaces , by tommorow…
the pid constants in the first video is int power_difference = proportional/15+ integral/10000+ derivative*9;( these are pid constants for black line on a white surface.)

the pid constants in the second video which worked for a speed of around max = 120/255,
are int power_difference = proportional/1+ integral/10000+ derivative*7;these are pid constants which worked to a considerable extent for a white line on a black surface.
even i had to change 3pi’s pid constants to make it work…

i have conducted a series of tests to confirm this behaviour
i have used the exact same material to make a white line on a black surface that is electrical tape placed adjacently to make a black surface and highlight the white line , but is not working for this change in condition for same pid constants also,

actually the motor speed is 300rpm, and am using a wheel of diameter 6cms.

thanks for the reply ben,


I am surprised that the two cases lead to such different results. I think you’re just going to have to do some tests to see what is causing the difference. Specifically, you can look at the calibrated values of each sensor when it is on each course. The calibrated sensor values should range from 0 to 1000. These calibrated values are then used to compute the location of the line. If the line is white, the sensor values are first inverted by subtracting them from 1000. If I were you, I would compare inverted sensor values on your white-line course to non-inverted sensor values on your black-line course (with the two courses made out of the same material). Hopefully, this will help you understand why your robot behaves differently on the two courses.

Also, you probably don’t need an integral term in your PID calculation.

- Ben


yes i did that first time itself actually the position value isnt changed much for both actually maybe its difficult for you to know if i just say what i saw
ill post a video of my tests very soon. may be that might help you know what i might be doing wrong…
sorry for the delayed reply,mainly because i had been preparing for a national level event in my country

and i won it the performance of my bot in this video

this was a extremly difficult track with 12cm ±2 cm error width black line
and the distance between 2 adjacent lines being just 1centimeter minimum to 1.5 centimeter maximum error

my special thanks pololu team.


first sorry for jumping into that discussion, second sorry …I´m a starter with the 3pi …so my questions and thoughts might
be stupid and trivial …if you feel so …just ignore it …or point me to a thread which has discussed this already.

  1. I have some problems to understand the idea behind that formula.
    First of all I assume you want to get a kind of direction vector and …the “analog” results of the 5 …or 6 sensors
    should give you a “feeling” where the “weight” is …where the direction “guess” with the lowest error “should” be.
    multiplying the most left sensor with 0 destroys that idea. Anyhow for the 3pi …the most left and right sensors have
    a 3x greater distance to the center (2) than 1 and 3. I miss that physical fact in the formula.
    More over for the control … in case of 3pi …I would handle the most left and right sensors special …they have a boarder
    problem and when the line is there …yes you have a line but the “error” is much bigger than in case of 1 and 3.
    …so I do not have a proofen answer …to improve that …but at least my feeling says …here is some room for improvement.

  2. …here I´m not sure if the integrator does this already …but you might come to better direction prediction if you store
    the history of the last direction prediction and smooth the values a bit … we could make some assumption about the tape layout
    the min radius of the track …

  3. additional to the PID concept there is always a kind of dead-time. That is for sure in the robot because of processor speed
    but also physical mass and reaction time of motors etc. Here I have right now no idea if it is worth to include that in the formula.
    But for sure you have it as the sensor is 5 cm ahead …(some minutes later …ok the goal is not to keep the weels on the track but
    the IR LEDs …as you might get lost (getting blind) if you try to keep the wheels in the center …but then …would it make sense to
    put them close to the middle axis of the robot???)

  4. a bit similar to 3) the speed. Not sure how important is that, but the physics of the robot will change a bit
    over the speed. And comming back to the direction vector what you would also need to know …what way have been
    between the last and the current measurement …and that may vary over the speed of the robot and the processor (if you add some code
    for other stuff like LCD info)
    I have not studied the details of the sensor …but I guess the measurements are time descrete (0.8 ms) …ok and that might be the reason why
    it works stable … even at 1 m/s …you result in 1 measurement per 0.8 mm

  5. the motors … would it make sense to calibrate them too …or is the error somehow random …also depending on the speed and the ground?
    but at least if you want to get near the 100% speed …it would be good to know the max values for going streigth?
    by the way … the constant max …is also the min, as you always set one of the motors to that speed. I also did not get why
    maximum speed is limit to 100 … from the code …I assume max = 60
    how often it makes sense to adjust the speed? When we get in interference with the PWM? In the user guide it is shown with a scale of ms.
    again …here I should read a bit more …but first impression …with the PWM of 10KHz the programm loop might disturbe the 10KHz cycle?


adding my 2 cent guess work to the black/white mysterium. Again not having looked into the code …
How linear the sensors are working?
what is about the 0.8ms constant for the sensors …might be they go into sutteration if they are on the white line?
which does not hurt in case of the black line ??



You did not ask very many specific questions, but it seems like you are generally concerned that readLine and the PID algorithm are not rigorously defined in terms of physical parameters of the 3pi. Generally, this does not matter at all. Think about driving a car - does it matter that you do not know the precise formula for how the the angle of the steering wheel corresponds to the angle of your car’s wheels? No - without that, you can still do a perfectly good job of driving straight down the road. As long as you have some reasonable feedback from the line and some way of applying a response, you should be able to tune the parameters and get a pretty good speed. Maybe you will be able to get a slight improvement through some more careful analysis, but that is up to you to try!

For your items 3 and 4, one thing can say is that the “D” term of PID is specifically intended to work against the inertia and delay of the robot’s response - you should think of it as looking ahead to where the line will be a few milliseconds into the future. I am not sure whether that addresses your concerns.

For item 5, you can adjust the PWM as fast as you like using our set_motors() function, and it should not cause any interference or glitches.

Finally, I do not expect the sensors to be linear in any sense. But whether you have a black line on white or a white line on black, as long as the line is not too thin, you should see pretty much the same range of values on the 3pi.



I think you are right. And for sure I should check the real potential for improvements.
Regarding the b/w characteristics. I assume that you just make the inverse operation by subtract from the max calibrated value. That works if the b/w function is linear. But if it is like 1/x than you have again an error which might be ignored for small difference. As you wonder that their is such a big difference between white and black lines, I just guess that this might be one error to much for the system. I also guess that for slow speed it will work again, but if you want to increase the speed …you need to respect the exact behavior of the system.
But I also assume that your code has more a demonstration purpose.

Taking your example of the car driving. I guess that the human feedback system is much more complex as a PID. I started to learn driving on a truck with a steering wheel having 2 hands tolerance. I took a while to learn to move the wheel in a left right rhythm tipping to the edge of the tolerance and getting by that the fine tuning for the control ofbthe truck. You might see that typical pattern in old black white movies. First 20 min I was going a slalom from the right to the left boarder of the street. And that has been because my control mechanism need to adapted to the system. A bit off topic here a example where also a modern car at slow speed had to much errors in the feedback system.



comming back to the formula …what did confused me was the 0*value0 …as it just eleminates the
value from the formula. But looking more into the details … yes it works …
…just from the formula …if the values gets 0 (no black at all; max white)
it would devide by zero …but in the source code you see that this is prevented by the on_line check.

I was wondering why the robot gets not out of spinning around the left wheel once its has lost the line …
so I limit the integral value to the max power diff which is Ki*max (Ki is set to 10000 in the code)
as you can anyhow not get a bigger correction value.
So now the robot recovers once I put him back to the line for a while.

I assume that the integral runs to infinity when the lost line value 4000 is return.
And than it took a long time to reduce that value. The only thing I wonder …
why the overrun does not lead to a change in the direction …would need 2^31 / 4000 main sw loops … and than
jumps to -2^31 ??? just in the other direction …I´m still not sure how long a main() loop takes ?
0.8 ms? -> ~ 500 seconds … I did not wait that long …

so new code
const int max = 180; // it is also the min speed
const int32_t Ki = 10000;
const int32_t maxIntegral = maxKi;

if (integral > maxIntegral)
integral = maxIntegral;
if (integral < -maxIntegral)
integral = -maxIntegral;

int power_difference = proportional/20 + integral/Ki + derivative



You should notice that value0 goes into the formula in the denominator. But yes, your observation about on_line is correct. You can also do your own on_line check in your code to decide whether to take some measures to search for the line again rather than turning sharply.

Limiting the integral is a fine idea. We do that on our Jrk Motor Controllers.




I found that link. It discuss the IR sensors. I’m not sure if it helps
for the black white line discussion. But the method could be used
and then done for the black and the white line.


Doing some tests …I was not able to reach the max speed of 255 with my course, even playing around with the PID parameters.
Then I correct the position values for sensor 0 and sensor 4. And that helped a lot. Now my 3pi runs at max speed. For me the reason is, that sensor 0 and 4 have a 1.5x greater distance to their neigbours.
so what I did I added a correction factor.

if (position > 3000) position+= 250; if (position < 1000) position-= 250;

It would be better to respect the 1.5 factor …but the whole formula of readline needs to be reviewed.
And with the artical about the sensitivity and the impact of the neighbor IR-LEDs …it might not be better.

As often I´m not 100% sure and I will try to proof that more in details.
But before that I need to implement an emercency stop as it is meanwhile difficult to catch him.

Where I´m also not sure … if I should be more patient with the motor settings…the mechanical negative acceleration does not sound that good …I fear the gear did not like it that much.

What you can also see in the small curves …the sensors follows the line put the car wheels “overshoots”.

and here …why you should have a more solid “road”:


coming from the 3pi sample program I was wondering about some rough movings of the robot.
As a loss of the tape line is somehow a major issue I added a peep once the !on_line is detected.
And than I had to listen that it happen quite often, even at low speed.

if(!on_line) { play_frequency(800,250,14); return last_value; //lets assume we do the right if we are blind }

The reason seems to be that if the line (black tape) gets too close to the sensor it
might reflect to much IR and does not absorb it. So the black tape is handled as white.
I can show that with manual moving the tape closer to the sensor.

Right now I see 2 reasons for that distance change between tape and sensor:
at low speed it is my not perfect paper playground. At high speed it is the acceleration
of the robot.
What I changed to reduce the effect …instead of signal the max left or right, I return the last
valid position.

looks like I need a better IR tape.


some minutes later:
tested with the pre-installed demo programm, sensors behave a bit different.
at my 3pi sensor3 is the most sensible to ground distance. In general s0 and s4
are the most robust. Lets guess this is because the IR LED from the neighbours are far
away for that two.

what I also could show …if I can smooth the robot moving …!on_line does happen rarely.
And black laser printer toner works also good as line.