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.
Light sensing has many uses. Think of a street light turning on at dusk, or a camera knowing when to use auto-flash. Phototransistors are a common type of light sensor used in these kinds of applications. Let’s try one with the Propeller for measuring light levels, before using it with the ActivityBot.
In this example, the photoresistor is paired with a capacitor in a charge transfer or QT circuit. It is a simple option for a variety of analog sensors. (You might see a resistor and an A/D converter used with these analog light sensors elsewhere. However, the circuit here offers the ability to sense a much wider range of light levels, without needing A/D conversion. )
Think of the 0.01 µF capacitor in the circuit as a very small battery, and the phototransistor as a light-controlled current valve. The Propeller will charge the capacitor-battery by sending an output-high signal to I/O P5. Then, the Propeller will change P5 to input and just watch what happens in the QT circuit. The phototransistor, our light controlled current valve, will control how quickly the capacitor loses its charge. More light = quicker capacitor discharge, less light = slower discharge. The Propeller’s job will be to measure the discharge time, and report that as a light measurement.
(1) Phototransistor (#350-00029)
(1) 0.01 µF capacitor (labeled 103, #200-01031)
(1) 0.1 µF capacitor (labeled 104, #200-01040)
(1) 220 ohm resistor (red-red-brown, #150-02210)
The Sense Light application will display a number that corresponds with the light level the QT circuit detects.
First, high(5) makes P5 send a 3.3 V high signal. Just as this type of signal can make an I/O pin deliver current to turn on the light, it can also deliver current to charge the capacitor. The pause(1) call gives the I/O pin time to charge the capacitor.
After that, int t = rc_time(5, 1) sets the variable t equal to the value returned by the rc_time function. This function from the simpletools library is designed to set an I/O pin to input, and then measure how long it takes for the voltage on that I/O pin to decay past the 1.65 V logic threshold.
The logic threshold is the dividing line between 1 and 0 for a Propeller input. Remember when you pressed a pushbutton and got a 1, or released it and got a 0? When the button was pressed, its I/O pin saw 3.3 V, well above 1.65 V, so the Propeller reported 1. Likewise, when the button was released, the I/O pin saw 0 V, well below 1.65 V, so the Propeller reported 0. Here, as soon as the voltage the circuit applies to the I/O pin decays to 1.65, the rc_time function stops tracking time and returns its time measurement. That time measurement gets stored in the t variable, and then displayed by print.
/* Sense Light.side Display light sensor levels. */ #include "simpletools.h" // Include simpletools int main() // main function { while(1) // Endless loop { high(5); // Set P5 high pause(1); // Wait for circuit to charge int t = rc_time(5, 1); // Measure decay time on P5 print("t = %d\n", t); // Display decay time pause(100); // Wait 1/10th of a second } }
Phototransistor — The C, B, and E labels in the phototransistor schematic stand for collector, base, and emitter. These are the terminals of a transistor called a bipolar junction transistor, or BJT. The amount of current entering the base terminal controls how much current passes from C to E. Tens to hundreds of times the current entering B is what the transistor conducts from C to E. By removing the wire and placing the transistor in a clear case, the base terminal acts like a small solar collector, supplying current to the B terminal. More light means more current into B, and therefore lots more current conducted from C to E.
Capacitor — A capacitor is constructed of two metal plates that placed very close together. When voltage is applied, it charges as electrons leave one of the plates and accumulate on the other. The amount of charge a capacitor can store is measured in terms of farads (abbreviated F). Capacitors in electronic designs tend to measure in fractions of a farad. Here are some of the fraction abbreviations you might see:
The 103 on the 0.01 µF capacitors case is the number of picofarads. It’s 10 + 3 zeros or 10,000, which is 1×104. A trillionth, or pico is 1×10-12. Multiply the two together, and you get 1×10-8, which is also 0.01×10-6 , or 0.01 µF.
If you use a capacitor that’s 10 times as large, your decay measurements will take ten times as long. So, the value rc_time returns will be 10 times larger.
You have now experimented with blinking lights and light brightness, and you may also have tried speaker tones. Pick one of those and make it respond to light. Maybe with less light shining on the phototransistor, an LED will get brighter, or maybe blink faster. Or you could make a speaker tone go lower as more light shines on the phototransistor.
Hint: You may have to divide or multiply t by some value to make it useful for your output device.
Now, it's time to build and test two phototransistor circuits to give your ActivityBot phototransistor "eyes." Leave the piezo speaker circuit in place.
As you build the circuit shown below:
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 [3] 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 abdrive360 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 "abdrive360.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] http://learn.parallax.com/activitybot/software-and-programming
[2] http://learn.parallax.com/propeller-c-set-simpleide/update-your-learn-folder
[3] https://learn.parallax.com/propeller-c-simple-circuits/sense-light