Light sensors are used in all kinds of robotics, industrial, and commercial applications. Next, let's teach it to navigate by visible light. By using a pair of small light sensors, called phototransistors, your ActivityBot can measure and compare the light levels on its right and its left. Then, it can turn toward the brighter side as it navigates. This way, the ActivityBot can be programmed to follow a flashlight, or find its way out of a dark room to a bright doorway.
The Propeller C Simple Circuits tutorial has a page devoted to the same phototransistor that is included in your ActivityBot kit.
Welcome back!
Now, it's time to build and test two phototransistor circuits to give your ActivityBot phototransistor "eyes." Leave the piezo speaker circuit in place.
Build the circuit shown below.
*P14/P15 resistors are only needed for ActivityBot kits using External Encoders (#32500).
This test will display raw light sensor readings in the SimpleIDE Terminal. You will need a flashlight or lamp that is brighter than the overall light level in the room.
The phototransistor circuits are designed to work well indoors, with fluorescent or incandescent lighting. Make sure to avoid direct sunlight and direct halogen lights; they would flood the phototransistors with too much infrared light.
The values shown above were taken in a room with overhead fluorescent lighting. Notice how the lightRight measurement is larger - this was caused by cupping a hand over the right phototransistor.
Mismatched Measurements
It is unlikely that your two phototransistor measurements will match exactly. Each individual sensor will vary slightly as a natural part of the manufacturing process, and the brightness levels are rarely precisely even in any environment However, if one gives a measurement about 100 times larger than the other when exposed to the same level of light, that phototransistor is probably in backwards. Check your circuit and try again.
This simple program starts by declaring two int variables, lightLeft and lightRight, to hold values returned by the left and right phototransistor circuits.
The action happens inside a while(1) loop, which takes two light sensor measurements and displays the results over and over again.
Recall from the Sense Light tutorial [1] that phototransistor measurement is a two-step process. First, high(9) connects the phototransistor circuit to 3.3 V, allowing the capacitor in the circuit to charge up like a tiny battery. It only takes a moment to do that; pause(1) allows enough time. Immediately after charging the circuit, the next line of code calls the rc_time function to take a measurement at I/O pin 9 and store the result in lightLeft. The same steps are taken to store a sensor measurement in lightRight.
After that, the print statement prints the name of each variable followed by its value in decimal. The control character HOME returns the cursor to the upper left corner of the SimpleIDE terminal each time, and CLREOL clears out any characters left over from the previous measurement.
/* Test Light Sensors.c */ #include "simpletools.h" int lightLeft, lightRight; int main() { while(1) { high(9); pause(1); lightLeft = rc_time(9, 1); high(5); pause(1); lightRight = rc_time(5, 1); print("%clightLeft = %d, lightRight = %d%c", HOME, lightLeft, lightRight, CLREOL); pause(250); } }
Light travels in waves so small that the distance between adjacent peaks is measured in nanometers (nm), which are billionths of meters. The figure below shows the wavelengths for colors of light we are familiar with, along with some the human eye cannot detect, such as ultraviolet and infrared. The phototransistor in your ActivityBot kit detects visible light, but is most sensitive to 850 nm wavelengths, which is in the infrared range.
Just for fun, let's see if the phototransistor can detect different colors.
From your observation, do you think a clear phototransistor alone, like this one, could be used as a color sensor? Why or why not?
For ActivityBot navigation, the raw sensor values don't matter directly. What matters is the difference between the light levels detected by each sensor, so the the ActivityBot can turn to the side of the one sensing brighter light. It is also nice to have values that will fall into a predictable range that can integrate easily with code to drive the servo motors.
Accomplishing this is surprisingly simple. First, just divide one sensor measurement into the sum of both. The result will always be in the 0 to 1 range. This technique is an example of a normalized differential measurement. Here’s what it looks like as an equation:
Two more simple operations will make the result easier to use. First, multiplying the result by 200 will make the result a whole number. Second, subtracting 100 from that whole number will always return values from -100 to 100. If the phototransistors sense the same level of light, regardless of how bright or dim, the final whole number will be zero. This is an example of a zero-justfied normalized differential measurement, let's call it "ndiff" for short. Now our equation looks like this:
The example code below will display the lightRight and lightLeft measurements, and also show the value of ndiff as an asterisk over a number line. This makes it easier to visualize how the ActivityBot navigation program uses the sensor values to aim for a new trajectory towards the brighter light.
Display Troubles?
If you have resized your SimpleIDE Terminal window smaller than this example program needs it to be, you will likely get garbled results. Just resize the terminal wider and then click Run with Terminal again.
It's a long read, but an interesting one if you want to understand how the graphical display was generated using print statements with the simpletools library.
This program builds on Test Light Sensors. Two more variables are declared in addition to lightLeft and lightRight; ndiff holds the result of the fancy-named equation, and position will be used to place the asterisk in the SimpleIDE terminal.
Inside main, a set of three print statements define the display layout for the SimpleIDE Terminal.
The first print displays the value of the variables listed, and the \n (new line) formatters make three blank lines to make room for the asterisk that will display over the number line.
The second print displays the number line, followed by a \n formatter.
The third print displays the labels for the number line, followed by a \n formatter.
Then, a char array named s is declared and initialized with 51 space characters, which will be used to position the asterisk over the number line.
Next, the code enters a while(1) loop. The next 8 lines, which obtain the raw phototransistor measurements, should be familiar by now.
The next line beginning with ndiff is the same as the ndiff equation shown above.
The three print statements below the ndiff expression (1) position the cursor, (2) display the updated decimal values of lightRight, lightLeft, and ndiff, and clear out any characters left from last time, and (3) move the cursor to the line the asterisk will use.
Next, position = (ndiff + 100) / 4; offsets ndiff with +100 to ensure it is a positive number, and then scales it with /4 to fit the scale of the number line. The result is a number that is useful for positioning an asterisk in the SimpleIDE Terminal, and it is assigned to the position variable.
This is how the asterisk gets placed: s[position] = '*'; redefines the "position-th" element in the s array to be an asterisk instead of a space. Then, the asterisk is displayed in the proper position with print(s); which displays all of the elements in the s array - a series of spaces with just one asterisk.
Immediately, s[position] = ' '; redefines the "position-th" element to be an empty space again, so the asterisk can be re-positioned on the next run through the loop. Without this line, the display would start accumulating asterisks.
The loop finishes up with pause(350); before repeating. If you read this far, thank you for your perseverance!
/* Test Light Sensors Graphical.c */ #include "simpletools.h" int lightLeft, lightRight, ndiff, position; int main() { print("lightLeft lightRight ndiff\n\n\n\n"); print("|------------------------|------------------------|\n"); print("-100 0 100\n"); char s[51] = {" "}; while(1) { high(9); pause(1); lightLeft = rc_time(9, 1); high(5); pause(1); lightRight = rc_time(5, 1); ndiff = 200 * lightRight / (lightRight + lightLeft) - 100; print("%c%c", HOME, CRSRDN); print("%d %d %d", lightLeft, lightRight, ndiff); print("%c%c%c", CLREOL, CRSRDN, CR); position = (ndiff + 100) / 4; s[position] = '*'; print(s); s[position] = ' '; pause(350); } }
Parentheses Matter. In C language mathematical expressions, like the one in this example program, multiplication and division operations are performed before addition and subtraction. However, surrounding an operation with parentheses causes it to be evaluated first, working from innermost operation on out in the case of nested parentheses. These rules are part of a larger ruleset called operator precedence; each programming language has its own operator precedence ruleset.
To see what difference a set of parentheses makes, create this small project that uses the same mathematical expression as the program above.
Are you convinced that parentheses make a difference yet?
Now, let's make the ActivityBot follow light! The following code should allow your robot to follow a flashlight beam, or navigate out of a dark room through a brightly lit doorway.
This program uses the simpletools and abdrive libraries. After initializing some variables, the program consists of an infinite loop which receives values from the phototransistors and uses them to adjust the servos' speeds to navigate toward whichever side detects brighter light.
First, the now-familiar int variables lightLeft, lightRight, and ndiff are declared for taking and using the sensor measurements. Then, int variables speedLeft and speedRight are declared for use with the drive_speed function later on.
The first six lines in the main function's while(1) loop are straight out the test programs; they take the light sensor measurements and store the values in lightLeft and lightRight.
The next line is the same ndiff equation used in Test Light Sensor Graphical. It divides lightRight by lightRight + lightLeft. The result is multiplied by 200, and then 100 is subtracted from it. This yields a value in the range of -100 and 100, which is assigned to ndiff. Positive ndiff values mean the left photoresistor is sensing brighter light, and negative ndiff values mean the right photoresistor is sensing brighter light. The farther the value is from zero, the greater the difference between what the two phototransistors are sensing.
The variables speedLeft and speedRight are then initialized to 100; keep this in mind when looking at the if code block that comes next.
The first condition translates to "if ndiff is greater than or equal to zero (brighter light on the the left), make speedLeft equal to 100 minus ndiff*4." The new value for speedLeft would be something less than 100, while speedRight remains 100, when those variables are used in the drive_speed function call right below the if block. For example, if ndiff = 50, this statement is the same as speedLeft = 100 - (50*4), which equals -100. The result is drive_speed(-100, 100) which makes the ActivityBot rotate to the left toward the light.
However, if ndiff is NOT positive, the code drops to else speedRight += (ndiff * 4). This translates to "make speedRight equal to 100 plus ndiff*4." This still yields a number smaller than 100; remember that ndiff is negative here. For example, if ndiff = -25, this statement is the same as speedRight = 100 + (-25*4) so speedRight ends up equal to zero, while speedLeft is still 100. This giving us drive_speed(100, 0), causing the ActivityBot to pivot right towards the brighter light.
/* Navigate by Light.c */ #include "simpletools.h" #include "abdrive.h" int lightLeft, lightRight, ndiff; int speedLeft, speedRight; int main() { while(1) { high(9); pause(1); lightLeft = rc_time(9, 1); high(5); pause(1); lightRight = rc_time(5, 1); ndiff = 200 * lightRight / (lightRight + lightLeft) - 100; speedLeft = 100; speedRight = 100; if(ndiff >= 0) speedLeft -= (ndiff * 4); else speedRight += (ndiff * 4); drive_speed(speedLeft, speedRight); } }
Phototaxis — This is the term that describes an organism moving its whole body in direct response to light stimulus. Moving towards brighter light is called positive phototaxis, and moving away from brighter light would then be negative phototaxis. While the Navigate by Light program makes the ActivityBot mimic positive phototaxis, in nature, it seems this behavior is usually found in microscopic organisms. Moths are attracted to light, but their varied and circling flight paths around a streetlamp demonstrate that their navigation systems are far more complex.
Would you like to make your ActivityBot mimic negative phototaxis? It can be done by replacing one single variable in the Navigate by Light code.
Links
[1] https://learn.parallax.com/propeller-c-simple-circuits/sense-light
[2] https://learn.parallax.com/activitybot/beeps
[3] http://learn.parallax.com/activitybot/software-and-programming
[4] http://learn.parallax.com/propeller-c-set-simpleide/update-your-learn-folder