LEARN.PARALLAX.COM
Published on LEARN.PARALLAX.COM (https://learn.parallax.com)
Home > Visible Light Navigation for the cyber:bot

Visible Light Navigation for the cyber:bot

]

What it’s about

Light sensors are used in all kinds of robotics, industrial, and commercial applications. Let’s teach your cyber:bot to navigate using visible light. Using a pair of small light sensors, called phototransistors, your cyber:bot can measure and compare the light levels on its right and its left.  It can then turn toward the brighter side as it navigates. This way, the cyber:bot can be programmed to find its way out of a dark space, or to follow a flashlight.

What you need to get started

These activities are written assuming you are completing the cyber:bot Prerequisite tutorials first and then doing the Main tutorials in sequence.  You should complete through Navigation and Circuits at a minimum first.

For this tutorial you will need:

  • Your programming software set up on your computer, with your micro:bit ready to use the cyberbot library modules
  • A fully built and tested cyber:bot robot
  • Phototransistors and other parts from your robot kit’s Small Robot Electronics Pack

After you finish

Once you have learned how to use phototransistor sensors, you will be able to include visible light seeking or avoiding behavior in your robot applications.

About phototransistors

Introducing the Phototransistor

A transistor is like a valve that regulates the amount of electric current that passes through two of its three terminals.  The third terminal controls just how much current passes through the other two.  Depending on the type of transistor, the current flow can be controlled by voltage, current, or in the case of the phototransistor, by light.

The image below shows the schematic and part drawing of the phototransistor in your cyber:bot kit.  The brightness of the light shining on the phototransistor’s base (B) terminal determines how much current it will allow to pass into its collector (C) terminal, and out through its emitter (E) terminal.  Brighter light results in more current; dimmer light results in less current.

The phototransistor looks a little bit like an LED.  The two devices do have two similarities.  First, if you connect the phototransistor in the circuit backwards, it won’t work right.  Second, it also has two different length pins and a flat spot on its plastic case for identifying its terminals.  The longer of the two pins indicates the phototransistor’s collector terminal. The shorter pin connected to the flat spont on the plastic case indicates the emitter.

Light Wavelengths

In the ocean, you can measure the distance between the peaks of two adjacent waves in feet or meters.  Light also travels in waves, but 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 the cyber:bot kit is most sensitive to 850 nm wavelengths, which is in the infrared range.  Infrared light is not visible to the human eye, but many different light sources emit considerable amounts of it, including halogen and incandescent lamps and especially the sun.  This phototransistor also responds to visible light, though it’s less sensitive, especially to wavelengths below 450 nm.

The phototransistor circuits in this chapter work well indoors, with fluorescent or incandescent lighting. They are less sensitive to LED lighting.  Make sure to avoid direct sunlight and direct halogen lights; they would flood the phototransistors with too much infrared light.

  • In your robotics area, close window blinds to block direct sunlight, and point any halogen lamps upward so that the light is reflected off the ceiling.

Simple Light to Voltage Sensor

Imagine that your cyber:bot is navigating a course, and there’s a dark or shady area at the end, such as a cardboard box garage.  Your robot’s final task in the course is to stop inside that dark area.  Or, perhaps you want the robot to search for and stop under bright light.  There’s a simple phototransistor circuit you can use that lets the micro:bit module know it detected bright or dim light by comparing the ambient light to some specified level. 

Ambient means ’existing or present on all sides’ according to Merriam Webster’s dictionary.  For the light level in a room, think about ambient light as the overall level of brightness.

Parts List

(1) phototransistor
(2) jumper wires
(1) resistor, 2 kΩ (red-black-red)
(1) cardboard box “garage” that fits the cyber:bot
(1) Optional – a flashlight or desk lamp, incandescent works best

After some testing, and depending on the light conditions, you might end up replacing the 2 kΩ resistor with one of these resistors, so keep them handy:

(1) resistor, 220 Ω (red-red-brown)
(1) resistor, 470 Ω (yellow-violet-brown)
(1) resistor, 1 kΩ (brown-black-red)
(1) resistor, 4.7 kΩ (yellow-violet-red)
(1) resistor, 10 kΩ (brown-black-orange)

The drawing below will help you tell apart the phototransistor and infrared LED, since they look similar.

Building the Voltage Detector

The schematic and wiring diagram below show the schematic and wiring diagram of a circuit very similar to the ones in streetlights that turn on automatically at night.  The circuit outputs a voltage that varies depending on how much light shines on the phototransistor.  The cyber:bot will monitor the voltage level with one of its analog to digital pins.

  • Set the cyber:bot board’s PWR switch to 0 — always do this before building or modifying circuits on the board.
  • Build the circuit shown, using the 2 kΩ resistor.
  • Double-check to make sure you connect the phototransistor’s emitter lead (by the flat spot) to the resistor, and its collector to 3.3V.
  • Also double-check that the phototransistor’s leads are not touching each other.

 

Measuring Phototransistor Voltage

In the circuit you just built, a wire connects A/D2 to the row where the phototransistor’s emitter and resistor meet.  The voltage at this part of the circuit will change as the light level sensed by the phototransistor changes. The phototransistor_voltage script measures the voltage at A/D2— one of the micro:bit module ’s three analog to digital input channels—and scrolls that value on the micro:bit module’s display.  You will use this sketch to take and write down voltage readings of the ambient light, shade cast by your hand, and optionally of bright light from a flashlight if you have one handy.

Hardware Setup

  • Set the cyber:bot board’s power (PWR) switch to Position 0.
  • Make sure the battery holder is loaded with 5 AA batteries.
  • Make sure the battery holder’s barrel plug is firmly plugged into the cyber:bot board’s barrel jack. 
  • Connect your micro:bit module to your computer with a USB cable.

Software Setup

  • In a Google Chrome or Microsoft Edge browser, go to python.microbit.org to open the micro:bit Python Editor.
  • Make sure the cyberbot.py module is added to the Project Files.
    (See Add modules to your micro:bit).

Example script: phototransistor_voltage

  • Set the project’s name to phototransistor_voltage, enter the script below, and then click Save.
    (See Save & Edit Scripts and Flash Scripts with Python Editor.)
  • Click Send to micro:bit.  
    (See Flash Scripts with Python Editor.)
  • Set the cyber:bot board’s PWR switch to 1.
# phototransistor_voltage

from cyberbot import *

while True:
    ad2 = pin2.read_analog()
    volts = ad2 * (3.3/1024)
    out = "volts = " + str(volts) + "V"
    display.scroll(out, 80)
    sleep(500)

Measurements will vary with ambient light levels, but in general, brighter measurements will tend toward 3.3, and darker ones will tend toward 0 V.  If you point the phototransistor at the sun or a bright flashlight, the measurements should be close to 3.3 V.  As you block the light and cast darker shade, the measurements should decrease.

  • Look at the display, and record the value you see for the ambient light in the room.
  • Use your hand to cast shade on the phototransistor, and watch the display change.  Brighter light should cause larger voltage values and dimmer light should cause smaller voltages.  Record the value for the room’s ambient light.
  • If you have a flashlight, shine it at the phototransistor and watch the display change again, and record the value.
  • If the ambient light is bright, you may need to replace the 2 kΩ resistor with a smaller value.  Try 1 kΩ, 470 Ω, or even 220 Ω, and be sure to record your values.
  • If the ambient light is low, you may need to change the 2 kΩ resistor to 4.7 kΩ, or even 10 kΩ, and be sure to record your values.

Once you have a resistor value in place that works well for differentiating ambient light and shade in your area, you are ready for the next step.

Setting PWR switch to 0 vs. Unplugging Battery Holder   

Taking a break, building a circuit, or writing a script?    

  • Just set the cyber:bot board’s PWR switch to 0 until it’s time to test the next script.

If you are done for the day, always follow these steps.

  • Set PWR to 0.
  • Unplug the battery holder’s barrel plug from the cyber:bot board’s barrel jack.

Park in the Dark

The script halt_under_shadow  will make the cyber:bot go forward until the phototransistor detects a shadow that’s dark enough to make the voltage applied to D/A2 drop below 0.1 V, or a different threshold you choose.

  • Use the micro:bit Python Editor to open a script that already has the cyber:bot module, like phototransistor_voltage or cyberbot-template-with-blink.
  • Set the project’s name to halt_under_shadow, and enter the script below over the existing script.
  • Using the measurements you just took with the script phototransistor_voltage, update the threshold value 0.1 in if volts < 0.1 with a value consistent with “shade” in your ambient lighting.
  • Click Save, and then click Send to micrso:bit.   
#halt_under_shadow

from cyberbot import *

bot(18).servo_speed(75)
bot(19).servo_speed(-75)

while True:
    ad2 = pin2.read_analog()
    volts = ad2 * (3.3/1024)
    
    if volts < 0.1:               # Update voltage threshold value here           
        bot(18).servo_speed(None)
        bot(19).servo_speed(None)
  • Place your cyber:bot on the floor in ambient light, pointing straight towards the cardboard box “garage” or other dark spot.
  • Set the cyber:bot board’s PWR switch to 2 so the cyber:bot will drive forward.  How close did it get to stopping directly in the box-garage?
  • If needed, try making adjustments to the threshold you set in the if volts <… statement to get the cyber:bot to stop directly the box-garage.

 

How the volts Function Works

The micro:bit module’s A/D0, A/D1, and A/D2 sockets are connected to its Nordic chip’s pins that are configured for analog to digital conversion.  Analog to digital conversion, called “A to D” and abbreviated A/D, is how microcontrollers measure voltage. The microcontroller splits a voltage range into many numbered, equal divisions, with each number representing a voltage level. More divisions provide a higher resolution, with more precise voltage measurements.

Each of the micro:bit module’s analog inputs has a 10-bit resolution, meaning that it uses 10 binary digits to describe its voltage measurement.  With 10 binary digits, you can count from 0 to 1023; that’s a total of 1024 voltage levels if you include zero.

By default, the cyber:bot’s read_analog function is configured to use the 0…1023 values to describe where a voltage measurement falls in a 3.3 V scale.  If you split 3.3 V into 1024 different levels, each level is 3.3/1024ths of a volt apart.  3.3/1024ths of a volt is approximately 0.00322266 V, or about 3.22 thousandths of a volt.  So, to convert a value returned by read_analog to a voltmeter-style value, all the volts function has to do is multiply by 3.3 and divide by 1024.

Example: The read_analog function returns 742; how many volts is that?
Answer:
V = 742 x 3.3 V / 1024
= 2.39121094 V
= 2.39 V

The previous scripts have been storing the read_analog function values as the variable ad2.  This variable is stored as an integer since it returns the values between 0 and 1023.  It is then multiplied by 3.3 and divided by 1024 to get the voltage value (just like we converted 742), and this is being stored as the float variable volts.

    ad2 = pin2.read_analog()
    volts = ad2 * (3.3/1024)

The script phototransistor_voltage displays the value returned by volts with the display.scroll function.

The script halt_under_shadow script uses that value to bring the cyber:bot to a stop when it detects a shadow (if volts < 0.1).

Binary vs. Analog and Digital

A binary sensor can transmit two different states, typically to indicate the presence or absence of something.  For example, a whisker sends a high signal if it is not pressed, or a low signal if it is pressed.

An analog sensor sends a continuous range of values that correspond to a continuous range of measurements.  The phototransistor circuits in this activity are examples of analog sensors. They provide continuous ranges of values that correspond to continuous ranges of light levels.

A digital value is a number expressed by digits.  Computers and microcontrollers store analog measurements as digital values.  The process of measuring an analog sensor and storing that measurement as a digital value is called analog to digital conversion.  The measurement is called a digitized measurement.  Analog to digital conversion documents will also call them quantized measurements.

 

How the Phototransistor Circuit Works

A resistor “resists” the flow of current.  Voltage in a circuit with a resistor can be likened to water pressure.  For a given amount of electric current, more voltage (pressure) is lost across a larger resistor than a smaller resistor that has the same amount of current passing through it.  If you instead keep the resistance constant and vary the current, you can measure a larger voltage (pressure drop) across the same resistor with more current, or less voltage with less current.

The micro:bit module’s analog inputs are invisible to the phototransistor circuit.  So, a circuit plugged into the A/D2  socket on the cyber:bot board is monitored by the micro:bit, but the micro:bit has no affect on the circuit.

Take a look at the circuit below.  With 3.3 volts (3.3 V) at the top and GND (0 V) at the bottom of the circuit, 3.3 V of electrical pressure (voltage) makes the supply of electrons in the cyber:bot’s batteries want to flow through it.

The reason the voltage at A/D2 (VAD2)  changes with light is because the phototransistor lets more current pass when more light shines on it, or less current pass with less light.  That current, which is labeled I in this circuit, also has to pass through the resistor.  When more current passes through a resistor, the voltage across it will be higher.  When less current passes, the voltage will be lower. Since one end of the resistor is tied to GND = 0 V, the voltage at the VAD2 end goes up with more current and down with less current.

If you replace the 2 kΩ resistor with a 1 kΩ resistor, VAD2 will see smaller values for the same currents.  It will take twice as much current to get VAD2 to the same voltage level, which means the shadow will have to be twice as dark to reach the 0.1 V level, which is the default voltage in the script halt_under_shadow that makes the cyber:bot stop.

So, a smaller resistor in series with the phototransistor makes the circuit less sensitive to light.  If you instead replace the 2 kΩ resistor with a 10 kΩ resistor, VAD2 will be 5 times larger with the same current, and it’ll only take 1/5th the light to generate 1/5th the current to get VAD2 past the 0.1 V level.  So, a larger resistor makes the circuit more sensitive to light.

Connected in Series  — When two or more elements are connected end-to-end, they are connected in series.  The phototransistor and resistor in this circuit are connected in series.

A Look at Ohm's Law

Two properties affect the voltage at VAD2: current and resistance, and Ohm’s Law explains how it works.  Ohm’s Law states that the voltage (V) across a resistor is equal to the current (I) passing through it multiplied by its resistance (R).  So, if you know two of these values, you can use the Ohm’s Law equation to calculate the third:

                       V = I x R

Voltage (V) is measured in units of volts, which are abbreviated with an upper-case V.  Current (I) is measured in amperes, or amps, which are abbreviated A. Resistance (R) is measured in ohms which is abbreviated with the Greek letter omega (Ω).  The current levels you are likely to see through this circuit are in milliamps (mA).  The lower-case m indicates that it’s a measurement of thousandths of amps.  Similarly, the lower-case k in kΩ indicates that the measurement is in thousands of ohms.

In some textbooks, you will see E = I × R instead. E stands for electric potential, which is another way to say “volts.”
Let’s use Ohm’s Law to calculate VAD2 with the phototransistor, letting two different amounts of current flow through the circuit:

  • 0.92 mA, which might happen as a result of fairly bright light
  • 0.15 mA, which would happen with less bright light

The examples below show the conditions and their solutions.  When you try these calculations, remember that milli (m) is thousandths and kilo (k) is thousands when you substitute the numbers into Ohm’s Law.

Example 1: I = 0.92 mA and R = 2 kΩ

Example 2: I = 0.25 mA and R = 2 kΩ

Your Turn – Ohm’s Law and Resistor Adjustments

Let’s say that the ambient light in your room is twice as bright as the light that resulted in VA3 = 3.1 V for bright light and 0.5 V for shade.  Another situation that could cause higher current is if the ambient light is a stronger source of infrared.  In either case, the phototransistor could allow twice as much current to flow through the circuit, which could lead to measurement difficulties.

Question: What could you do to bring the circuit’s voltage response back down to 3.1 V for bright light and 0.5 V for dim?
Answer: Cut the resistor value in half; make it 1 kΩ instead of 2 kΩ.

  • Try repeating the Ohm’s Law calculations with R = 1 kΩ, and bright current I = 3.1 mA and dim current I = 0.5 mA. 

Does it bring VA3 back to 3.1 V for bright light and 0.5 V for dim light with twice the current?  (It should; if it didn’t for you, check your calculations.)

 

Light Levels over Larger Ranges

The A/D circuit for Park in the Dark works over a limited light range.  You might get that circuit all nice and calibrated in one room, then take it to a brighter room and find that all the voltage measurements will stay above 0.1 V.  Or, maybe you’ll take it into a darker room, and the voltages will end up never making it past 0.1 V.

Let’s try a different kind of phototransistor circuit that the cyber:bot can use to measure a much wider range of light levels.  This circuit and script can return values ranging from 0 to over 75,000. Just note: with this circuit, smaller values indicate bright light, and large values indicate low light, the opposite of our previous circuit.  This new circuit uses a new component: a capacitor.

Introducing the Capacitor

A capacitor is a device that stores an electrical charge. It is a fundamental building block of many circuits.  Batteries are also devices that store charge, and for these activities it will be convenient to think of capacitors as tiny batteries that can be charged, discharged, and recharged.

How much charge a capacitor can store is measured in farads (F).  A farad is a very large value that’s not practical for use with these cyber:bot circuits.  The capacitors in your kit store fractions of millionths of farads.  A millionth of a farad is called a microfarad, and it is abbreviated μF.  This one stores one hundredth of one millionth of a farad: 0.01 μF.

Common Capacitance Measurements

  • microfarads: millionths of a farad, abbreviated μF. 1 μF = 1×10-6 F
  • nanofarads:  billionths of a farad, abbreviated nF. 1 nF = 1×10-9 F
  • picofarads:  trillionths of a farad, abbreviated pF. 1 pF = 1×10-12 F

Your cyber:bot kit’s electronic component pack comes with two different capacitors. One is marked 103 and the other is marked 104.  These marks are a measurement in picofarads.  In this labeling system,  the value is the number 10 followed by the specified number of zeros added.

For example, 103 is the number 10 with 3 zeros added capacitor’s case is a measurement in picofarads or (pF).  In this labeling system, 103 is the number 10 with three zeros added, so the capacitor is 10,000 pF, which is 0.01 μF.

(10,000) × (1 × 10-12) F  =   (10 × 103) × (1 × 10-12) F
           10 × 10-9 F    =  0.01 × 10-6 F
                         = 0.01 μF.

The capacitor labeled 103 with the value of 0.01 µF is the value we will use next.

Build the Light Sensor Eyes

The circuits shown here can respond independently to the light level reaching each phototransistor. Each one naturally adapts to ambient light levels without having to swap out resistors. The phototransistors will be pointing upward at about 45°, one forward-left and the other forward-right.  This way, a script monitoring the values of both phototransistor circuits can determine which side of the cyber:bot is exposed to brighter light. Then, this information can be used for navigation decisions.

Parts List

(2) phototransistors
(2) capacitors, 0.01 μF (marked 103)
(2) resistors, 220 Ω  (red-red-brown)
(2) jumper wires

  • Put the cyber:bot board’s power switch in position 0.
  • Remove the old phototransistor circuit, and build the circuits shown below.

  • Double-check your circuits against the wiring diagram to make sure your phototransistors are not plugged in backwards, and that the leads are not touching.

The roaming examples in this chapter will depend on the phototransistors being pointed upward and outward to detect differences in light levels from different directions. Adjust the phototransistors to point upward at a 45° from the breadboard, and outward about 90° apart,  as shown below.

About Charge Transfer and the Phototransistor Circuit

Think of each capacitor in this circuit as a tiny rechargeable battery, and think of each phototransistor as a light-controlled current valve.  Each capacitor can be charged to 3.3 V and then allowed to drain through its phototransistor.  The rate that the capacitor discharges depends how much current the phototransistor (current valve) allows to pass, which in turn depends on the brightness of the light shining on the phototransistor’s base.  Again, brighter light results in more current passing, shadows result in less current.

This kind of phototransistor/capacitor circuit is called a charge transfer circuit.  The cyber:bot will determine the rate at which each capacitor loses its charge through its phototransistor by measuring how long it takes the capacitor’s voltage to decay, that is, to drop below a certain voltage value.  The decay time corresponds to how wide open that current valve is, which is controlled by the brightness of the light reaching the phototransistor’s base.  More light means faster decay, less light means slower decay.
QT Circuit — A common abbreviation for charge transfer is QT.  The letter Q refers to electrical charge (an accumulation of electrons), and T is for transfer.

Connected in Parallel — The phototransistor and capacitor shown in the figure above are connected in parallel; each of their leads are connected to common terminals (also called nodes).  The phototransistor and the capacitor each have one lead connected to GND, and they also each have one lead connected to the same 1 kΩ resistor lead.

Test the Light Sensor Eyes

Example script: left_light_sensor

The script left_light_sensor charges the capacitor in the P8 circuit, measures the time it takes for the capacitor to discharge, and displays that value on the micro:bit module’s LED matrix.  Remember, with this circuit and script, lower numbers mean brighter light now!

  • Set the cyber:bot board’s PWR switch to 0.
  • Use the micro:bit Python Editor to open a previous cyber:bot project, like halt_under_shadow or cyberbot-template-with-blink.
  • Set the project’s name to left_light_sensor, update the script so that it matches the one below, and then click Save.
  • Click Send to micro:bit.  
# left_light_sensor

from cyberbot import *

while True:                       
    bot(8).write_digital(1)        
    qt_left = bot(8).rc_time(1)      
    
    display.scroll(str(qt_left))           
    sleep(500)   
  • Set the cyber:bot board’s PWR switch to position 1.
  • Point your cyber:bot towards the room’s light source, then shade the left phototransistor with your hand. 
  • Watch the micro:bit module’s display. It should be showing numbers between 0 and 77,000, with brighter light making for smaller numbers.  If not, check wiring and rerun the program.

Your Turn

The other phototransistor circuit on the right side of the robot also needs testing!

  • Change the project name from left_light_sensor as right_light_sensor.
  • Change the both of the bot(8) commands to bot(6).
  • Change the variable qt_left to qt_right in both places.
  • Click the Save button to save your changes.
  • Click Send to micro:bit.
  • Set the cyber:bot board’s PWR switch to position 1.
  • Make sure the micro:bit module’s display is changing its numbers as you point the right phototransistor towards the light source and then shade it with your hand.

These steps are important!  
Your circuits and code must pass these tests before continuing.  The rest of the examples in this chapter rely on both light sensors working correctly.

 

RC-time and Voltage Decay

The charge transfer measurement is done with these two lines of code:

    bot(8).write_digital(1)        
    qt_left = bot(8).rc_time(1)

The first line, bot(8).write_digital(1), does two things.  First, it charges the capacitor by setting Propeller I/O pin P8 to output high (3.3 volts). Then, it waits one millisecond for the capacitor to fully charge – more than enough time for this tiny one. 

The next line, qt_left = bot(8).rc_time(1), is doing a lot more.  The rc_time function:

  1. Marks the time on the Propeller system clock.
  2. Sets P8 to an input, which allows the charge in the capacitor to start draining.
  3. Watches the voltage sensed at P8  Remember, P8 is a digital I/O pin; instead of sensing a range of analog values, it reports either a high (1) or a low (0) signal with 1.6 volts being the threshold in between.
  4. Marks the time again when the signal at P8 changes from 1 to 0.
  5. Subtracts the first time marked from the second time marked. The result is how long it took capacitor to discharge from 3.3 V to the 1.6 V voltage threshold.
  6. Stores the discharge time in the variable qt_left.  

Look Deeper: Voltage Decay Graphs

Here is an example of a charge transfer measured with an oscilloscope, a device that displays voltage as it changes over time.  The vertical axis on the left is volts, and time increases by milliseconds along the bottom. 

The red line that’s graphing voltage is called a trace.  The trace plots the capacitor’s voltage in the P8 QT circuit; that’s the left light sensor.  In response to bot(8).write_digital(1), the voltage quickly rises from 0 V to almost 3.3 V at about the 1 ms mark, and stays there for 1 millisecond.  Then, at the 2 ms mark, the rc_time call allows the voltage in the capacitor to start draining, or “decay.”  The rc_time function measures the time it takes the voltage to decay to the Propeller I/O pin’s logic threshold of about 1.6 V. This measured rc decay time gets stored in the qt_left variable.  In the plot, it looks like that decay took about 1 ms, so the qt_left variable would store a value close to 1000.

Keep in mind that these are not the voltage measurements from Activity 1, they are time measurements.  When the voltage decay takes a short time the number will be small, and that means bright light.  When the voltage decay takes a long time, the number will be larger, and that means dimmer light, shade, or for really large numbers, darkness.

Keep in mind that these are not the voltage measurements from Activity 1, they are time measurements.  When the voltage decay takes a short time the number will be small, and that means bright light.  When the voltage decay takes a long time, the number will be larger, and that means dimmer light, shade, or for really large numbers, darkness.

Light Measurements for Roaming

The rc-time circuits in this tutorial can work under a variety of lighting conditions. Now we need some code that can adapt as well.  An example of script code that cannot adapt to change would be:

if qt_left > 2500)...          # Not good for navigation.

That statement might work well for turning away from shadows in one room, but take it to another with brighter lights, and it might never detect a shadow.  Or, take it to a darker room, and it might think it’s seeing shadows all the time. 

For the cyber:bot to navigate with phototransistors, the exact light levels measured aren’t really important. What matters is which sensor detects the brightest light.

The next example script takes a light level measurement from each sensor. Then, it uses an equation to turn the two measurements into a single value between -0.5 and +0.5.   A value of zero means the two photoresistors are detecting equal brightness.

Let’s run the script first, then examine what it is doing.

ERROR FALSE ALARM! On line 11 of the script flash norm_diff_shade_value, you may see a compiler error warning “Operator “+” not supported for this combination of types.”  This is just a false alarm.  The script actually works just fine, so go ahead and send it to the micro:bit.

Example script: flash norm_diff_shade_value

  • Make sure there is no direct sunlight streaming in nearby windows.  Indoor lighting is good, but direct sunlight will still flood the sensors.
  • Use the micro:bit Python Editor to open a previous cyber:bot project, like left_light_sensor or cyberbot-template-with-blink.
  • Set the project’s name to norm_diff_shade_value, update the script so that it matches the one below, and then click Save.
  • Click Send to micro:bit.  
# norm_diff_shade_value

from cyberbot import *
 
while True:                       
    bot(8).write_digital(1)        
    qt_left = bot(8).rc_time(1)   
    bot(6).write_digital(1)        
    qt_right = bot(6).rc_time(1)   
    
    norm_diff_shade = (qt_right / (qt_right + qt_left)) - 0.5
    
    display.scroll(norm_diff_shade)            
    sleep(500)
  • Set the cyber:bot board’s PWR switch to 1.
  • Cast shade over the cyber:bot’s right phototransistor.  The the micro:bit should display a positive value; the darker the shade, the larger the value.
  • Cast shade over the cyber:bot’s left phototransistor.  The the micro:bit should display a negative value.
  • Position the cyber:bot so both phototransistors see about the same level of bright light. The micro:bit should display a value close to 0.
  • Cast equal shade over both sensors.  Even though the overall light level dropped, the display should still report a value close to zero.
  • Set the cyber:bot board’s PWR switch to 0.

 

How norm_diff_shade_value Works

For navigation, what matters is the difference in how much light the two photoresistors detect, so the robot can turn toward the sensor seeing brighter light (or away from it, depending on what you want.)  The script norm_diff_shade_value finds this difference by dividing the right 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 this technique looks like as an equation:

For example, a normalized differential measurement of 0.25 would mean  “the light is 1/2 as bright over the right sensor as it is over the left.” The actual values for qt_right and qt_left might be small in a bright room or large in a dark room, but the answer will still be 0.25 if the light is 1/2 as bright over the right sensor.  A measurement of 0.5 would mean that the qt_right and qt_left values are equal.  They could both be large, or both be small, but if the result is 0.5, it means the sensors are detecting the same level of brightness.

But, the script norm_diff_shade_value adds one more step: it subtracts 0.5 from the normalized differential shade measurement.  That way, the results range from –0.5 to +0.5 instead of 0 to 1, and a measurement of 0 means equal brightness.  The result is a zero-justified normalized differential shade measurement, that looks like this as an equation:
 

But why bother with the extra step? The value range –0.5 to +0.5 is great for navigation scripts because the positive and negative values can be used to scale the wheels speeds.  Here is how the zero-justified normalized differential shade equation appears in the script:

 norm_diff_shade = (qt_right / (qt_right + qt_left)) - 0.5

This line is used in the next two example scripts, which will show its usefulness.

 

Light Measurements Graphic Display

The micro:bit module’s display can be used to show which side of the cyber:bot is detecting brighter light.  The following program shows a visual display of where the brigher light is hitting, reflecting the value of the norm_diff_shade variable.  If the light is on the right side, the display will light up a pixel on the right side, if the light is in the middle it will light up a pixel in the middle, and so on.

ERROR FALSE ALARM! On line 11 of the script norm_diff_shade_display, you may see a compiler error warning “Operator “+” not supported for this combination of types.”  This is just a false alarm.  The script actually works just fine, so go ahead and send it to the micro:bit.

Example script: norm_diff_shade_display

  • Use the micro:bit Python Editor to open a previous cyber:bot project, like norm_diff_shade_value or cyberbot-template-with-blink.
  • Set the project’s name to norm_diff_shade_display, update the script so that it matches the one below, and then click Save.
  • Click Send to micro:bit.  
# norm_diff_shade_display

from cyberbot import *
 
while True:                       
    bot(8).write_digital(1)        
    qt_left = bot(8).rc_time(1)   
    bot(6).write_digital(1)        
    qt_right = bot(6).rc_time(1)   
    
    norm_diff_shade = (qt_right / (qt_right + qt_left)) - 0.5
    display.clear()
    
    if norm_diff_shade > -0.5 and norm_diff_shade <= -0.35:
        display.set_pixel(0, 2, 9)
    elif norm_diff_shade > -0.35 and norm_diff_shade <= -0.1:
        display.set_pixel(1, 1, 9)
    elif norm_diff_shade > -0.1 and norm_diff_shade <= 0.1:
        display.set_pixel(2, 0, 9)
    elif norm_diff_shade > 0.1 and norm_diff_shade <= 0.35:
        display.set_pixel(3, 1, 9)
    elif norm_diff_shade > 0.35 and norm_diff_shade < 0.5:
        display.set_pixel(4, 2, 9)
    sleep(50)
  • Set the cyber:bot board’s PWR switch to 1.
  • Try casting different levels of shade over each light sensor, and watch how the pixel in the micro:bit module’s display responds. It should match the illustrations below.
  • Set the cyber:bot board’s PWR switch to 0.

       

Navigate by Light

Normal Differential Shade Values for Roaming

To make the cyber:bot roam toward light, the code’s job is to get from the raw sensor values to a norm_diff_shade value that can be used to adjust the servo speeds.  We want cyber:bot turn a little—or a lot—when the contrast between the light detected on each side is a little—or a lot.  The following equation in code makes the norm_diff_shade value to integer between -100 and 100. 

norm_diff_shade = (200 * qt_right) / (qt_right + qt_left + 1) - 100

Let’s confirm this strategy works before using it in a navigation script.

ERROR FALSE ALARM! On line 12 of the script test_integer_shade_value, you may see a compiler error warning “Operator “+” not supported for this combination of types.”  This is just a false alarm.  The script actually works just fine, so go ahead and send it to the micro:bit.

Example script: test_integer_shade_value

  • Use the micro:bit Python Editor to open a previous cyber:bot project, like norm_diff_shade_display or cyberbot-template-with-blink.
  • Set the project’s name to test_integer_shade_value, update the script so that it matches the one below, and then click Save.
  • Click Send to micro:bit.  
# test_integer_shade_value

from cyberbot import *

while True:

    bot(8).write_digital(1)        
    qt_left = bot(8).rc_time(1)
    bot(6).write_digital(1)        
    qt_right = bot(6).rc_time(1)

    norm_diff_shade = (200 * qt_right) / (qt_right + qt_left + 1) - 100
    display.scroll(norm_diff_shade)
  • Cover any windows letting in direct light, so the sun doesn’t overwhelm the phototransistors.
  • Set the cyber:bot board’s PWR switch to 1.
  • Cast shade over each phototransistor, and also try a flashlight if you have it, and watch the values on the micro:bit display.
  • Set the cyber:bot board’s PWR switch to 0.

Make sure the micro:bit displays values between -100 and 100 depending on which sides the light and shadow are on. The values do not necessarily have to reach the -100 and 100 end points, but they should get close.

Shady Navigation Decisions

Now, using this new strategy, we can manipulate the servos to drive based on which direction the light is hitting the cyber:bot. The light-seeking script and some ideas for adapting it are included below. How this script works is on the next page.

ERROR FALSE ALARM! On line 12 of the script seek_bright_light, you may see a compiler error warning “Operator “+” not supported for this combination of types.”  This is just a false alarm.  The script actually works just fine, so go ahead and send it to the micro:bit.

  • Use the micro:bit Python Editor to open test_integer_shade_value.
  • Set the project’s name to seek_bright_light, update the script so that it matches the one below, and then click Save.
  • Click Send to micro:bit.  
# seek_bright_light

from cyberbot import *

while True:

    bot(8).write_digital(1)        
    qt_left = bot(8).rc_time(1)
    bot(6).write_digital(1)        
    qt_right = bot(6).rc_time(1)

    norm_diff_shade = (200 * qt_right) / (qt_right + qt_left + 1) - 100

    if norm_diff_shade > 0:
        left_speed = 75 - norm_diff_shade
        right_speed = -75
    else:
        left_speed = 75
        right_speed = -75 - norm_diff_shade

    bot(18).servo_speed(left_speed)
    bot(19).servo_speed(right_speed)
  • Unplug the USB cable from the micro:bit, set the cyber:bot robot on the floor, and set its PWR switcht to position 2. 
  • The cyber:bot should start moving towards the brightest area of the room.
  • Cast shade over one phototransistor, then over the other. The cyber:bot should turn away from from the shaded side.
  • Use a flashlight to drive the cyber:bot and verify that the cyber:bot is navigating towards the bright light.
  • If it is not working, check the script and the wiring to make sure everything is correct.
  • Remember to set the cyber:bot robot’s PWR switch to 0 when it does not need to run a script, and make sure to unplug the battery holder’s plug from the cyber:bot board’s barrel jack when you are done for the day.

Try This: Changing Responsiveness

You can change the cyber:bot’s responsiveness by changing the value 200 in this line of the script:

norm_diff_shade = (200 * qt_right) / (qt_right + qt_left + 1) - 100
  •  To increase responsiveness to light, change 200 to a larger value and re-flash the script.  Does the cyber:bot’s behavior change?
  • To decrease responsiveness, change 200 to a smaller value and re-flash the script.  Now how does the cyber:bot behave?

Your Turn

Here are several more light-sensing navigation ideas for your cyber:bot that can be made with adjustments to the while True: function:

  • To make your cyber:bot follow shade instead of light, place norm_diff_shade = -norm_diff_shade right above the if statement.
  • End roaming under a bright light or in a dark cubby by detecting very bright or very dark conditions.  Add qt_left and qt_right together, and compare the result to either a really high (dark) threshold value or a really low (bright) threshold value.
  • Make your cyber:bot function as a light compass by remaining stationary and turning toward bright light sources.

 

How Navigation by Light Works

Take a look at the image below. It illustrates how shade over one of the photoresistors affects the cyber:bot wheels’ speeds.

Let’s take a closer look.  The navigation code is in an infinite while True loop.  At the beginning of each loop, the familiar rc_time function takes a reading from each phototransistor circuit. The P8 circuit measurement is stored in at_left and the P6 circuit measurement is stored in qt_right.

from cyberbot import *

while True:

    bot(8).write_digital(1)       
    qt_left = bot(8).rc_time(1)
    bot(6).write_digital(1)       
    qt_right = bot(6).rc_time(1)

Then, the values of qt_right and qt_left are used in an equation to get an integer value representing which side of the cyber:bot is in brighter light. The value is stored in the norm_diff_shade variable.

    norm_diff_shade = (200 * qt_right) / (qt_right + qt_left + 1) - 100

Now take another look at the illustration above. Light is hitting the left photoresistor but shade is over the right one. If the light is hitting the left side, then we want to slow down the left servo down so the cyber:bot moves more towards the left.  This was done with the following statement:

    if norm_diff_shade > 0:
        left_speed = 75 - norm_diff_shade
        right_speed = -75

Since light is hitting the left side, the variable norm_diff_shade will be greater than 0, and the if statement will run.  The variable right_speed will stay at -75 (full speed clockwise to go forward) while the variable left_speed will subtract the value of norm_diff_shade from 75. The brighter the light on the left side, the larger the value subtracted.

If it light were hitting the right side of the cyber:bot and instead norm_diff_shade was less than or equal to 0, the c else: condition code would execute instead. It works the same way, to slow down right_speed by subtracting norm_diff_shade, but keeps the variable left_speed at 75 (full speed counterclockwise to go forward).

    else:
        left_speed = 75
        right_speed = -75 - norm_diff_shade

 

Then, the following script uses the updated left_speed and right_speed values to control each servo’s speed:

    bot(18).servo_speed(left_speed)
    bot(19).servo_speed(right_speed)

These lines simply run the servos at the speeds from the if statement.  And, that is all it takes!

 

DISCUSSION FORUMS | PARALLAX INC. STORE

About | Terms of Use | Feedback: learn@parallax.com | Copyright©Parallax Inc. 2024


Source URL:https://learn.parallax.com/courses/visible-light-navigation-for-the-cyberbot/
Links