This post will discuss how to calibrate the downward facing light sensors, and to read the calibrated values. I went a little over the top on the calibration function, but it makes it that much nicer. You can certainly do something a little less fancy and simplify, but I like things looking nice.
For the calibration, I added some button functions and the buzzer. I’ll have a post later discussing more regarding the buttons and buzzer, but for now, you just need to know a couple of things. First of all, button A and the buzzer have their own class, and we should rename it like we did with the motors, LCD, and line sensor array. We also need the Zumo32U4 library file. The beginning of the program should look like this:
#include <Zumo32U4.h>
Zumo32U4LineSensors lineSensors;
Zumo32U4LCD lcd;
Zumo32U4ButtonA buttonA;
Zumo32U4Buzzer buzzer;
We also need to declare our 3 element array for the light sensors. We will use it in multiple spots in the program without passing parameters, so it should be a global variable. Therefore, let’s put it next:
unsigned int lineSensorValues[3];
Before we get to the rest of the program, let’s talk a little bit about functions, and the purpose of the setup() and loop() portion of the program. The setup() and loop() are meant to be high level portions of the program, and easily readable without distraction. We’ve been using basic commands in these portions of the program in previous posts simply because the programs have been very simple. In more complicated programs, these sections should be very short with high level function calls. All of the nitty-gritty details should be done in various functions. My rule of thumb is that if the setup and main cannot be basically understood by someone not familiar with the program, it should be simplified. Therefore, we should probably write a separate function for the light sensor calibration.
The light sensor calibration function lineSensors.calibrate()
takes a light sensor reading on all active light sensors and stores it. It doesn’t matter if it is high reflectivity, low reflectivity, or anywhere in between. You can run this function many times. Each time you run it, it looks to see if the new value is larger or smaller than any of the previous values. Unlike the EV3, where you need to specify if you are measuring a minimum or maximum, this function takes care of it for you. As a result, you could continuously run the calibration for a period of time, and scan the robot across an area that has a wide range of reflectance. Below is my version of the light sensor calibration function:
void calLightSensors(int CalTime) {
lcd.clear();
lcd.gotoXY(0,0);
lcd.print("Press A");
lcd.gotoXY(0,1);
lcd.print("to Cal");
buttonA.waitForButton();
buzzer.playFrequency(440, 200, 15);
lcd.clear();
lcd.print("Cal\'ing");
for (int i = 20*CalTime; i > 0; i--) {
lcd.gotoXY(0, 1);
if(i%20==0) {
lcd.print(i/20);
lcd.print(" ");
buzzer.playFrequency(440, 50, 15);
}
lineSensors.calibrate();
delay(20);
}
buzzer.playFrequency(600, 200, 15);
lcd.clear();
lcd.print("Cal Done");
delay(1000);
}
The overall flow of the function is pretty simple, but there is a lot added to make it more user friendly. The basic flow is
- Wait for the button to be pressed.
- Run the loop a lot of times.
- As the loop is running, display approximate time left during the calibration.
- Run a cal each time through the loop.
- End and let the user know the cal is done.
Starting at the beginning, the function name is calLightSensors and it does not return a value, which is why the function is defined as void. The function accepts one input parameter, which is an integer. The input parameter is the amount of time you want to give the function to light cal. In some cases, you want the cal to go quick, other times, you may want a lot of time to push the Zumo around the table to profile as much of the different lighting conditions possible.
The next step is to display a message to the user to press the A button to begin the calibration, then the program runs the command buttonA.waitForButton(). This function does exactly what it says. It pauses the program until button A is pressed. Whenever pressing a button, I like to have a little feedback, usually in the form of a beep, to tell me that the button was pressed. The function
buzzer.playFrequency(440, 200, 15);
plays a tone at 440 Hz for 200 ms at a volume level of 15 (15 is the highest). I’ll get into more functions of the buzzer in a later post, but for the time being, all I want to do is play a beep, and this accomplishes it.
The next section is a for loop. A for loop is a type of loop that is used for running a section of code repeatedly for a specific number of times. The syntax for a for loop is given below:
for (initialization; condition; increment) {
//statement(s);
}
The very first time through the for loop, it initializes a counter. The loop runs until the counter reaches a certain condition, and finally, the counter is either incremented or decremented. Assuming that the counter meets the condition, everything with the { } is executed. For my case, the for loop reads:
for (int i = 20*CalTime; i > 0; i--) {
// statements to be executed
}
For the initialization, the statement int i = 20*CalTime
says that I’m initializing integer i to be 20 times the amount of time I’d like to calibration for. Remember that CalTime was an input parameter to this function. If we assume 10 s, then we are initializing i to be 200. The condition here is i>0
. That is, we want to execute the contents of this for loop as long as i is greater than 0. The increment is i--
, which is shorthand for i=i-1
. Assuming that CalTime is 10, this loop would execute 200 times as i changes from 200 to 0, decrementing by a value of 1 each time through the loop.
We want to run a calibration each time through the loop, and have a slight delay. We could simply run the loop as:
for (int i = 20*CalTime; i > 0; i--) {
lineSensors.calibrate();
delay(20);
}
However, this provide no information to the user about how much time they have left on the calibration. By the way, the delay time was adjusted to provide approximately the correct amount of time for CalTime to work out to seconds. The calibration function takes a little time to run, and I knew I wanted about 20 calibration readings per second. It took a little trial and error and is not exact. What I’d like to have now is a countdown so I know how much time I have left. The easiest way to do that is to use the variable i. If I printed the value of i directly on the display, it would count down from 200 to 0 changing values about 20 times a second (50 ms). That isn’t terribly useful. I’d like to display in seconds. Every time i becomes a multiple of 20, it would represent a 1 second increment in time. If you take i and divide by 20, you can tell if i is a multiple of 20 if the remainder is 0. Arduino has an arithmetic operation called the modulo (or modulus), which displays the remainder of a division operation. The modulo operator is the percent sign (%). i%20
would give the remainder of i divided by 20. If this value is 0, then we know we are at a multiple of 20, and we are at a 1 second increment.
if(i%20==0) {
lcd.print(i/20);
lcd.print(" ");
buzzer.playFrequency(440, 50, 15);
}
The statement above says that if the modulus of i/20 is 0 (the == is the logical statement for equal to – note that this is very different to a single equal sign which means we are assigning a value), the we execute the statements within the { }. In this case, we are writing the number of seconds (i/20) to the display and playing a beep. Why do you think writing the ” ” (writing a space) is necessary?
After the calibration and countdown, the function simply tells the user that the calibration is done.
Now that the function calLightSensors() is written, it can be called in the setup() part of the program as we only want to execute this once.
In order to read the calibrated values of the light sensors, you can use the following command:
lineSensors.readCalibrated(lineSensorValues);
Displaying the values to the LCD is identical to the previous blog posting. However, to do this a more correct way, we should really make the display commands into a function called displayLCD(). The complete version of the program is below:
#include <Zumo32U4.h>
Zumo32U4LineSensors lineSensors;
Zumo32U4LCD lcd;
Zumo32U4ButtonA buttonA;
Zumo32U4Buzzer buzzer;
unsigned int lineSensorValues[3];
void setup() {
lineSensors.initThreeSensors();
calLightSensors(10);
lcd.clear();
lcd.gotoXY(0,0);
lcd.print("Press A");
lcd.gotoXY(0,1);
lcd.print("to Cont.");
buttonA.waitForButton();
buzzer.playFrequency(440, 200, 15);
lcd.clear();
}
void loop() {
lineSensors.readCalibrated(lineSensorValues);
displayLCD();
delay(50);
}
void calLightSensors(int CalTime) {
lcd.clear();
lcd.gotoXY(0,0);
lcd.print("Press A");
lcd.gotoXY(0,1);
lcd.print("to Cal");
buttonA.waitForButton();
buzzer.playFrequency(440, 200, 15);
lcd.clear();
lcd.print("Cal\'ing");
for (int i = 20*CalTime; i > 0; i--) {
lcd.gotoXY(0, 1);
if(i%20==0) {
lcd.print(i/20);
lcd.print(" ");
buzzer.playFrequency(440, 50, 15);
}
lineSensors.calibrate();
delay(20);
}
buzzer.playFrequency(600, 200, 15);
lcd.clear();
lcd.print("Cal Done");
delay(1000);
}
void displayLCD () {
lcd.clear();
lcd.gotoXY(2, 0);
lcd.print(lineSensorValues[1]);
lcd.gotoXY(0, 1);
lcd.print(lineSensorValues[0]);
lcd.gotoXY(4, 1);
lcd.print(lineSensorValues[2]);
}
Note that the functions can simply be placed after the loop() function. You can actually place the functions anywhere you want, but it is typical to have setup() and loop() first, then any functions after that. Another option, which is what I typically do, is to place groups of functions as separate tabs in the Arduino IDE. If you run this program, and move the robot around to a full range of reflectance values during the calibration period including the minimum reflectance and the maximum reflectance, you should have displayed values ranging between 0 and 1000 with 0 being the most reflective and 1000 being the least reflective. All three light sensors should also be more equal if they are on an object of similar reflectivity from one another.
The light sensor calibration stores the calibration factors in what’s called volatile memory. That is, when the robot is turned off or reset, it forgets the calibration. Therefore, if you want calibrated values, you need to calibrate once every time the robot is turned on.