How to Hookup and Program a QTR-8 Sensor Array

If you haven’t been following along from the start, feel free to start on the main article Build A Custom PID Line Following Robot From Scratch which includes every step regarding how to build this robot. Maybe you just found this article while trying to find help attaching a QTR sensor to your robot and that’s fine to, this article should help you. I’ll show you how to program a QTR sensor array, let’s get started

Parts

As far as parts go, assuming you’ve already built the robot we just need the following items

  • QTR-8 sensor (can be an 8A or an 8RC)
  • Breadboard wires, in this case I used Dupont style wires with pins attached
  • Two screws with nuts to mount the QTR sensor (you could just simply use hot glue or double sided tape)
  • Finally, our robot that we’ve built so far

Watch the video

As always, there is a video you can watch if you’re more into that sort of thing. This video covers attaching the sensor and testing the line array. I actually go through the process showing you any issues I ran into and how I resolved them.

The hookup

I attached the power and ground to 5V and ground and I also attached my “led on” pin or emitter pin to 5V since I don’t care to turn the emitters on and off, I just want them on all the time. For the sensor readings, I fed those into teensy pins 16-23 which equates to analog pins A2-A9, either value will work. For full disclosure, I had pin A9 (or 23) attached to sensor 8 and A2 (or 16) was attached to sensor 1

The code

Honestly, as shown in the video I just used the example code from the qtr-sensors-arduino library. Also, as mentioned in the video, there is a bug in the code which may only be with Teensy boards but it causes the reading to overflow and bounce between 0 and 1000 which gives very bad output. With that said, you can use my library located at https://github.com/gberl001/qtr-sensors-arduino and has the fix in place.

Example

Here’s the example I used, don’t forget you need to update the following pieces to work with your robot.

  • NUM_SENSORS – This will be the number of sensors you have
  • EMITTER_PIN – If you aren’t using one, be sure to change this to QTR_NO_EMITTER_PIN to save 400µs
#include <QTRSensors.h>

// This example is designed for use with six QTR-1A sensors or the first six sensors of a
// QTR-8A module. These reflectance sensors should be connected to analog inputs 0 to 5.
// The QTR-8A's emitter control pin (LEDON) can optionally be connected to digital pin 2, 
// or you can leave it disconnected and change the EMITTER_PIN #define below from 2 to 
// QTR_NO_EMITTER_PIN.

// The setup phase of this example calibrates the sensor for ten seconds and turns on
// the LED built in to the Arduino on pin 13 while calibration is going on.
// During this phase, you should expose each reflectance sensor to the lightest and 
// darkest readings they will encounter.
// For example, if you are making a line follower, you should slide the sensors across the
// line during the calibration phase so that each sensor can get a reading of how dark the
// line is and how light the ground is. Improper calibration will result in poor readings.
// If you want to skip the calibration phase, you can get the raw sensor readings
// (analog voltage readings from 0 to 1023) by calling qtra.read(sensorValues) instead of
// qtra.readLine(sensorValues).

// The main loop of the example reads the calibrated sensor values and uses them to
// estimate the position of a line. You can test this by taping a piece of 3/4" black
// electrical tape to a piece of white paper and sliding the sensor across it. It
// prints the sensor values to the serial monitor as numbers from 0 (maximum reflectance) 
// to 1000 (minimum reflectance) followed by the estimated location of the line as a number
// from 0 to 5000. 1000 means the line is directly under sensor 1, 2000 means directly
// under sensor 2, etc. 0 means the line is directly under sensor 0 or was last seen by
// sensor 0 before being lost. 5000 means the line is directly under sensor 5 or was
// last seen by sensor 5 before being lost.


#define NUM_SENSORS 8 // number of sensors used
#define NUM_SAMPLES_PER_SENSOR 4 // average 4 analog samples per sensor reading
#define EMITTER_PIN QTR_NO_EMITTER_PIN // emitter is controlled by digital pin 2

// sensors 0 through 5 are connected to analog inputs 0 through 5, respectively
QTRSensorsAnalog qtra((unsigned char[]) {A9, A8, A7, A6, A5, A4, A3, A2}, NUM_SENSORS, NUM_SAMPLES_PER_SENSOR, EMITTER_PIN);
unsigned int sensorValues[NUM_SENSORS];


void setup()
{
 delay(500);
 pinMode(13, OUTPUT);
 digitalWrite(13, HIGH); // turn on Arduino's LED to indicate we are in calibration mode
 for (int i = 0; i < 3000; 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 Arduino's LED to indicate we are through with calibration

 // print the calibration minimum values measured when emitters were on
 Serial.begin(9600);
 for (int 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 (int i = 0; i < NUM_SENSORS; i++)
 {
 Serial.print(qtra.calibratedMaximumOn[i]);
 Serial.print(' ');
 }
 Serial.println();
 Serial.println();
 delay(1000);
}


void loop()
{
 // read calibrated sensor values and obtain a measure of the line position from 0 to 5000
 // To get raw sensor values, call:
 // qtra.read(sensorValues); instead of unsigned int position = qtra.readLine(sensorValues);
 unsigned int position = qtra.readLine(sensorValues);
 
 // print the sensor values as numbers from 0 to 1000, where 0 means maximum reflectance and
 // 1000 means minimum reflectance, followed by the line position
 for (unsigned char i = 0; i < NUM_SENSORS; i++)
 {
 Serial.print(sensorValues[i]);
 Serial.print('\t');
 }
 //Serial.println(); // uncomment this line if you are using raw values
 Serial.println(position); // comment this line out if you are using raw values
 
 delay(250);
}

The Rollover Bug

The bug I encountered may only be related to the compiler for Teensy, I’m not sure. I would assume that if this were a bug on Arduino boards then there would be lots of people complaining. However, the issue was that there was a mix of unsigned and signed values in the calibration adjustment calculation. This resulted in an unsigned final value so if your sensor ever read below the minimum calibration, the calibrated value would roll over from 0 to 4,294,967,295 and then be constrained to 1000.

The simple fix was to change the unsigned calmin, calmax variables to signed however I changed denominator as well for good measure, we don’t need the variable to be unsigned so I’m not sure why it was explicitly set to unsigned.

// Returns values calibrated to a value between 0 and 1000, where
// 0 corresponds to the minimum value read by calibrate() and 1000
// corresponds to the maximum value.  Calibration values are
// stored separately for each sensor, so that differences in the
// sensors are accounted for automatically.
void QTRSensors::readCalibrated(unsigned int *sensor_values, unsigned char readMode)
{
    int i;

    // if not calibrated, do nothing
    if(readMode == QTR_EMITTERS_ON_AND_OFF || readMode == QTR_EMITTERS_OFF)
        if(!calibratedMinimumOff || !calibratedMaximumOff)
            return;
    if(readMode == QTR_EMITTERS_ON_AND_OFF || readMode == QTR_EMITTERS_ON)
        if(!calibratedMinimumOn || !calibratedMaximumOn)
            return;

    // read the needed values
    read(sensor_values,readMode);

    for(i=0;i<_numSensors;i++)
    {
        signed int calmin,calmax;
        signed int denominator;

        // find the correct calibration
        if(readMode == QTR_EMITTERS_ON)
        {
            calmax = calibratedMaximumOn[i];
            calmin = calibratedMinimumOn[i];
        }
        else if(readMode == QTR_EMITTERS_OFF)
        {
            calmax = calibratedMaximumOff[i];
            calmin = calibratedMinimumOff[i];
        }
        else // QTR_EMITTERS_ON_AND_OFF
        {

            if(calibratedMinimumOff[i] < calibratedMinimumOn[i]) // no meaningful signal
                calmin = _maxValue;
            else
                calmin = calibratedMinimumOn[i] + _maxValue - calibratedMinimumOff[i]; // this won't go past _maxValue

            if(calibratedMaximumOff[i] < calibratedMaximumOn[i]) // no meaningful signal
                calmax = _maxValue;
            else
                calmax = calibratedMaximumOn[i] + _maxValue - calibratedMaximumOff[i]; // this won't go past _maxValue
        }

        denominator = calmax - calmin;

        signed int x = 0;
        if(denominator != 0)
            x = (((signed long)sensor_values[i]) - calmin)
                * 1000 / denominator;
        if(x < 0)
            x = 0;
        else if(x > 1000)
            x = 1000;
        sensor_values[i] = x;
    }

}

 

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *