5. Light Sensitive Robot

DO NOT USE MU TO UPLOAD THIS CODE TO THE MICROBIT, INSTEAD USE AN ONLINE MICROPYTHON EDITOR.

This is the final tutorial using the light sensors to either avoid or follow light. We will use all of the previous code that we’ve developed, add two methods lightMove() and setReference(). It will be possible at select each mode by pressing button A or button B.

First off I would like you to copy and paste the code from the previous example into Mu as a new project. Then delete the program loop,  delete leftLineSenor, rightLineSensor variables and the lineDetector() method.

Now we need to create our method lightMove(), this method will have an argument called direction that can be a 1 or a 0. 1 for avoid and 0 for follow. The method is shown below;

def lightMove(direction):
    sensorSelect.write_digital(0)  # select left sensor
    brightnessLeft = lightSensor.read_analog()  # read value
    sensorSelect.write_digital(1)  # select right sensor
    brightnessRight = lightSensor.read_analog()  # read value

   avgBrightness = int((brightnessLeft + brightnessRight) / 2)

   if (avgBrightness <= refLevel + 10):
       stop()  # stop Moving
       if (refLevel >= 900):  # LEDs are cyan to indicate very bright room
           leftLights(0, setBrightness(1), setBrightness(1))
           rightLights(0, setBrightness(1), setBrightness(1))
        else:  # LEDs are pinkish to indicate a dim room
            leftLights(setBrightness(1), 0, setBrightness(1))
            rightLights(setBrightness(1), 0, setBrightness(1))

    elif(brightnessLeft > brightnessRight - 25) and 
    (brightnessLeft < brightnessRight + 25):
        if (direction == 1):
        drive(-512)  # drive backwards
        else:
        drive(512)  # drive forwards
        leftLights(setBrightness(1), setBrightness(1), 0)
        rightLights(setBrightness(1), setBrightness(1), 0)

    else:  # turn the robot
       if (direction == 1):  # away from light
       move(brightnessLeft, brightnessRight, direction, direction)
       leftLights(setBrightness(1), 0, 0)
       rightLights(setBrightness(1), 0, 0)
       else:  # towards the light
           move(brightnessRight, brightnessLeft, direction, direction)
           leftLights(0, setBrightness(1), 0)
           rightLights(0, setBrightness(1), 0)

I also added the following method called setReference();

def setReference():
    sensorSelect.write_digital(0)  # select left sensor
    val1 = lightSensor.read_analog()  # read value
    sensorSelect.write_digital(1)  # select right sensor
    val2 = lightSensor.read_analog()
    return int((val1 + val2) / 2)

and finally I added the following variable declaration under sensorSelect = pin16

refLevel = 0

So what does it do. I will describe the setReference() method first; this method will be invoked when Bit:Bot is started. It takes readings from both the left and right sensors adds them together and divides by 2. This calculates an ambient light level that we will use as the reference level.

Now to the lightMove() method; this method will create an average value from the amount of light coming into the sensors, if the amount of light is less than the reference level + 10 then the robot will not move and the following if statement will be evaluated, if the light is bright it will light up the LEDs a cyan colour to indicate that the light levels are too bright and the room needs to be darker for the robot to perform properly. If the room is dark enough the lights on the robot will light a pinkish colour and the robot will wait for a light to be shone on it before it starts to move.

NOTE: elif means else if. You use this instead of the else statement if you have another condition to be evaluated.

If the ambient light is greater than the reference level then the robot will need to do something. Firstly the robot needs to decide if the light is coming from its front or its side. It uses the following if statement to evaluate this;

elif(brightnessLeft > brightnessRight - 25) and 
(brightnessLeft < brightnessRight + 25):

This looks at the light coming into each sensor and if the values are within 50 of each other the robot decides that the light is coming directly from its front and either moves forwards in follower mode or backwards in avoid mode and lights the LEDs a yellowish colour.

Otherwise  the robot will turn towards the light in follower mode and away from the light in avoid mode. The LEDs will be red for avoid and green for follow.

The LEDs can be one of five colours these are shown below;

  • Cyan – Room too bright robot will not respond the different light levels
  • Pink – Robot will respond to different light levels
  • Yellow – robot moving forward or backwards because light is originating in front of the robot
  • Green – robot is moving left or right towards the light source
  • Red – robot is moving left or right away from the light source

If you have any questions about the code put them in the comments section.

Now we need a way to choose which mode to put the robot in. For this we will use the A and B buttons. Please note that the A and B buttons share the same pins as the IR sensors. When using Bit:Bot in this mode make sure that its on a dark or evenly coloured surface so the buttons aren’t activated by the IR sensors.

The  below code is added to allow for this;

sleep(1000)
refLevel = setReference()
while True:
    buttonA = button_a.get_presses()  # Get how many times bA was pressed
    buttonB = button_b.get_presses()  # Get how many times bB was pressed
    if (buttonA > buttonB):  # If bA was pressed more than bB then mode 1
        mode = 1
    elif(buttonB > buttonA):  # If bB was pressed more go into mode 2
        mode = 2
    # If no buttons were pressed stay in whatever mode it was in before
    if(mode == 1):
        lightMove(0)  # If in mode 1 call lightFollower method
        display.show("F")
    elif(mode == 2):
        lightMove(1)
        display.show("A")
    else:
        display.show("N")

The above code will set the ambient light levels by calling the setReference() method and assigning the returned value to refLevel. The program loop is entered where the button presses are retrieved and the button with the most presses will dictate which  mode to put the robot in. mode 1 is for light follower and mode 2 is for light avoider. If no buttons are pressed then the robot will stay in its current mode. The Micro:Bits LEDs will display A for avoider, F for follower and N for no mode selected. lightMove() method will be called with either a 1 or 0 in the argument field to decide if its in follow or avoid mode.

The mode variable is declared under the refLevel variable near the start of the program.

The complete program listing is shown below;

# Light avoider/follower for the 4tronix Bit:Bot and BBC Micro:Bit
# Author David Bradshaw 2017

# Will either avoid or follow light

from microbit import *
import neopixel  # Neopixel Library so we can control the NeoPixels lights

np = neopixel.NeoPixel(pin13, 12)

# Motor pins; these tell the motor to go
# forward, backwards or turn
leftSpeed = pin0
leftDirection = pin8
rightSpeed = pin1
rightDirection = pin12


# Both light sensor are on the same pin so we also use a select pin
lightSensor = pin2
sensorSelect = pin16
refLevel = 0  # Reference light level
mode = 0


def leftLights(Red, Green, Blue):
    for pixel_id in range(0, 1):  # Start of for loop
        np[pixel_id] = (Red, Green, Blue)  # Code to be executed in the loop
    np.show()  # Change the NeoPixels colour


def rightLights(Red, Green, Blue):
    for pixel_id in range(6, 7):
        np[pixel_id] = (Red, Green, Blue)
    np.show()


def setBrightness(minValue):
    sensorSelect.write_digital(0)
    brightnessLeft = lightSensor.read_analog()
    sensorSelect.write_digital(1)
    brightnessRight = lightSensor.read_analog()

    brightness = int((brightnessLeft + brightnessRight) / 2)
    brightness = int(brightness / 25)
    if(brightness < minValue):
        brightness = minValue
    return brightness


# Motor control to tell the motor what direction and speed to move
def move(_leftSpeed, _rightSpeed, _leftDirection, _rightDirection):
    # speed values between 1 - 1023
    # smaller values == faster speed moving backwards
    # Smaller values == lower speeds when moving forwards
    # direction 0 == forwards, 1 == backwards
    leftSpeed.write_analog(_leftSpeed)  # Set the speed of left motor
    rightSpeed.write_analog(_rightSpeed)  # Set the speed of right motor
    if(_leftDirection != 2):
        leftDirection.write_digital(_leftDirection)  # left motor
        rightDirection.write_digital(_rightDirection)  # right motor


def drive(speed):
    if (speed > 0):
        move(speed, speed, 0, 0) # move the motors forwards
    else:
        speed = 1023 + speed
    move(speed, speed, 1, 1) # move the motors backwards


def sharpRight():
    move(100, 1023 + -200, 0, 1)


def sharpLeft():
    move(1023 + -200, 100, 1, 0)


def gentleRight():
    move(200, 0, 0, 0)


def gentleLeft():
    move(0, 200, 0, 0)


def coast():
    move(0, 0, 2, 2)


def stop():
    move(0, 0, 0, 0)


def lightMove(direction):
    sensorSelect.write_digital(0)  # select left sensor
    brightnessLeft = lightSensor.read_analog()  # read value
    sensorSelect.write_digital(1)  # select right sensor
    brightnessRight = lightSensor.read_analog()  # read value

    avgBrightness = int((brightnessLeft + brightnessRight) / 2)

    if (avgBrightness <= refLevel + 10):
        stop()  # stop Moving
        if (refLevel >= 900): # LEDs are blue to indicate very bright room
            leftLights(0, setBrightness(1), setBrightness(1))
            rightLights(0, setBrightness(1), setBrightness(1))
        else:  # LEDs are pinkish to indicate a dim room
            leftLights(setBrightness(1), 0, setBrightness(1))
            rightLights(setBrightness(1), 0, setBrightness(1))

    elif(brightnessLeft > brightnessRight - 25) and (brightnessLeft < brightnessRight + 25):
        if (direction == 1):
            drive(-512)  # drive backwards
        else:
            drive(512)  # drive forwards
            leftLights(setBrightness(1), setBrightness(1), 0)
            rightLights(setBrightness(1), setBrightness(1), 0)

    else:  # turn the robot
        if (direction == 1):
            move(brightnessLeft, brightnessRight, direction, direction)
            leftLights(setBrightness(1), 0, 0)
            rightLights(setBrightness(1), 0, 0)
        else:
            move(brightnessRight, brightnessLeft, direction, direction)
            leftLights(0, setBrightness(1), 0)
            rightLights(0, setBrightness(1), 0)


def setReference():
    sensorSelect.write_digital(0) # select left sensor
    val1 = lightSensor.read_analog() # read value
    sensorSelect.write_digital(1) # select right sensor
    val2 = lightSensor.read_analog()
    return int((val1 + val2) / 2)


sleep(1000)
refLevel = setReference()
while True:
    buttonA = button_a.get_presses()  # Get how many times bA was pressed
    buttonB = button_b.get_presses()  # Get how many times bB was pressed
    if (buttonA > buttonB): # If bA was pressed more than bB then mode 1
        mode = 1
    elif(buttonB > buttonA): # If bB was pressed more go into mode 2
        mode = 2
    # If no buttons were pressed stay in whatever mode it was in before
    if(mode == 1):
        lightMove(0)  # If in mode 1 call lightFollower method
        display.show("F")
    elif(mode == 2):
        lightMove(1)
        display.show("A")
    else:
        display.show("N")

The above solution isn’t the most elegant however it works and it gave us an opportunity to cover elif statements, button presses, displaying letters on the Micro:Bit and to use the code developed from the previous examples.

Now upload the code to your robot, put it in a room that’s not too bright and use a torch to make the robot follow or avoid the light. Play with the code and see how Bit:Bot behaves. Remember to use the online MicroPython editor to create a hex file and upload the code. Do not use Mu to upload the code as you have loads of issues with the PWM assignment.

You’ve done really well and should understand how to program the Bit:Bot to do stuff. All of these examples can be improved and expanded to give Bit:Bot more functionality. You can purchase an ultrasonic sensor for Bit:Bot from the 4Tronix website that will allow you to develop an obstacle sensing robot. Use the experience you’ve gained from these examples and expand on by coming up with your own ideas and coding them. Micro:Bits are good bits of hardware to cut your teeth with coding, when you get more experience maybe you could have a look at the Arduino family of products. This will let you do much more with many different types of micro-controllers from simple 8-bit AVR type controllers to more complex 32-bit ARM Cortex M4 and even the Atheros AR9331 (Arduino Yun) that can run a small distro of the Linux operating system.

I have put the code files below, if you have any comments, questions or code it can be posted in the comments section below.

 

Code Files

The below zip archive contains the code file from this example.

LightAvoidFollower.zip

 

5 thoughts on “5. Light Sensitive Robot

  1. Avatar
    Garry Bor says:

    Hi!

    I tried the example code but after following the light for maybe 1-2 seconds, the bit:bot goes into a death spin. It just goes round and round non-stop.

    I used it inside a completely dark room and used a torch as the light source.

    Any suggestions on what went wrong will be greatly appreciated!!

    • David Bradshaw
      David Bradshaw says:

      Hi,

      Did you use Mu to upload the code to your Micro:Bit. If you did try using the online MicroPython editor found at https://python.microbit.org/v/1

      Their is an error with the Device Abstraction Layer (DAL) thats bundled with Mu so when you use the PWM outputs for the motors and the NeoPixels you get issues like this. Let me know if this sorts your issue out if not I will have a look at the code. This code is just an example of how to implement such functionality try to improve on it and remember to use the online MicroPython editor when controlling the motors.

  2. Avatar
    Saral Tayal says:

    Hi I had the same problem with the robot spinning in a circle. there were other syntax errors in your code as well that i fixed on line 110 and 55 i think. Thanks for you help

    • David Bradshaw
      David Bradshaw says:

      Line 115 and 102 errors fixed the code will now compile. If you get errors from pasting code from the web page download the zip file and use that code. Sometimes when I format the code for the webpage I introduce indentation errors, etc but the zip code will always work.

      For me the Bit:Bot will only go into a death spin if I upload using Mu, when uploading using the on-line MicroPython editor it works as desired. This code isn’t that good for a better implementation look at the Arduino version found here.

      Maybe you could port the Arduino version to MicroPython !!!!!

Leave a Reply

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