Course Module • Jillur Quddus

4. Program a Robot Car

Python Taster Course

Program a Robot Car

Python Taster Course

Jillur Quddus • Founder & Chief Data Scientist • 1st November 2022

  Back to Course Overview

Introduction

In this module we shall program our robot car to follow simple and advanced instructions including moving forwards and reversing, following black lines and routes, moving towards bright light sources, and automatically detecting and avoiding obstacles.

The source code for this module may be found in the public GitHub repository for this course. Where code snippets are provided in this module, you are strongly encouraged to type these Python statements in your own MakeCode (or similar Micro:bit code editor) instance.

1. Requirements

You will require the following hardware components and software services to program our robot car.

2. Micro:bit Code Editors

In order to program the Micro:bit (and hence our Ring:bit Car as it is controlled and driven by the Micro:bit) with instructions, we require a Micro:bit code editor. An online-based Micro:bit code editor enables you to develop the code for your program entirely within your internet browser. When your program is ready, you can transfer that program to the Micro:bit via USB - this process is called flashing and is discussed further in the next subsection. The two most popular online Micro:bit code editors are Microsoft MakeCode and the Python Micro:bit editor.

  • Microsoft MakeCode - a visual colour-coded block-based code editor for the Micro:bit, suited for beginner programmers. Microsoft MakeCode also supports JavaScript and Python text-based code editing.
  • Python Micro:bit - a code editor for the Micro:bit specifically for Python developers.

For the purposes of this taster module, we shall use the Microsoft MakeCode editor to develop Python programs for our Ring:bit car.

Microsoft MakeCode Editor
Microsoft MakeCode Editor
Python Micro:bit Code Editor
Python Micro:bit Code Editor

3. Flashing the Micro:bit

Flashing the Micro:bit refers to the process of transferring your program to the Micro:bit's flash memory. There are two ways to flash the Micro:bit, both of which involve USB.

3.1. USB File Transfer

  1. Connect the Micro:bit to your desktop or laptop computer (Windows, MacOS, Linux or Chrome OS) using a USB micro-B compatible data transfer USB cable. When plugged in, the Micro:bit will appear on your computer as an external device named MICROBIT.
  2. Download your program as a .hex file from the code editor onto your computer.
  3. Drag and drop, or copy, the .hex file from your computer to the MICROBIT drive.

The yellow LED on the back of the Micro:bit will blink indicating that the program is being transferred. When the yellow LED stops blinking, the program has been transferred and will automatically run on the Micro:bit. After the transfer has completed, the MICROBIT device will disconnect and reconnect as the Micro:bit resets. Note that the .hex file will not be listed on the MICROBIT drive after this (as the Micro:bit is not a normal flash storage device but appears as one when you connect it to your computer in order to make it easy to transfer .hex files).

3.2. WebUSB Direct Flashing

Direct flashing refers to the process of transferring your program directly from your internet browser to the Micro:bit's flash memory via the online code editor (using the WebUSB API - a protocol that provides a way to safely expose USB device services to the web).

At the time of writing, WebUSB direct flashing is only supported by the Google Chrome and Microsoft Edge internet browsers.

3.2.1. Python Micro:bit Code Editor

  1. Connect the Micro:bit to your desktop or laptop computer (Windows, MacOS, Linux or Chrome OS) using a USB micro-B compatible data transfer USB cable.
  2. In the Python Micro:bit code editor, select the "Send to micro:bit" button.
  3. Select your BBC Micro:bit device from the pop-up window, and then press the "Connect" button.
  4. Your code will now transfer directly to the Micro:bit every time you select the "Send to micro:bit" button.

3.2.2. Microsoft MakeCode Editor

  1. Connect the Micro:bit to your desktop or laptop computer (Windows, MacOS, Linux or Chrome OS) using a USB micro-B compatible data transfer USB cable.
  2. In the Microsoft MakeCode editor, select the three dots next to the "Download" button.
  3. From the resultant menu, select "Connect device".
  4. Select your BBC Micro:bit device from the pop-up window, and then press the "Connect" button.
  5. A USB logo will now appear on the "Download" button. Now every time you select the "Download" button, your code will transfer directly to the Micro:bit.

Regardless of whether you are using the Python Micro:bit or the Microsoft MakeCode editor, the yellow LED on the back of the Micro:bit will blink indicating that the program is being transferred. When the yellow LED stops blinking, the program has been transferred and will automatically run on the Micro:bit.

For the purposes of this taster module, we shall use direct flashing from the Microsoft MakeCode editor to transfer programs to our Ring:bit car.

4. Ring:bit Basics

In this subsection we will introduce and describe some of the key physical components of the Ring:bit electrical boards.

4.1. Ring:bit Board and GVS Pins

The Ring:bit board is a printed circuit board (PCB) that extends the Micro:bit's three general purpose input/output (GPIO) pins P0, P1 and P2. The Ring:bit board converts these GPIO signals to Ground, Voltage and Signal (GVS) signal pins capable of providing power and ground with each I/O signal, thus safely providing the power for the Ring:bit's external sensors and output devices including the add-on infrared tracking module and ultrasonic Sonar:bit module respectively.

Micro:bit V2 GPIO pins
Micro:bit V2 GPIO pins
Ring:bit board
Ring:bit board

In the previous module we connected the right servo's jump wire to the GVS pins labelled 2VG (GVS2). We connected the left servo's jump wire to the GVS pins labelled 1VG (GVS1). And we connected the special expansion board's jump wire to the GVS pins labelled 0VG (GVS0). When we develop programs for our Ring:bit car in section 5 below, we must initialise our programs by defining that the wheels of our Ring:bit are connected to the Micro:bit pins P1 (corresponding to GVS1) and P2 (corresponding to GVS2) respectively.

You must always ensure that the ground wire (usually the brown coloured wire, but sometimes also black coloured) is inserted into the G pin when connecting to the respective GVS pins. Otherwise when you insert batteries into the Ring:bit battery compartment and flash the Micro:bit's flash memory, the servos (and hence the wheels) will not work and it will appear that the Ring:bit device is broken or not responding to your coded instructions.

4.2. Special Expansion Board

In the previous module we attached the Ring:bit's special expansion board to the chassis of the car and the 0VG GVS pin in order to extend the features of the Ring:bit car through the installation of optional add-on modules such as the infrared tracking module that connect directly to the special expansion board. When we study the special expansion board in further detail, we see that there is a slide switch on the board - slide this switch to "Rainbow LED" to utilise the Ring:bit's two rainbow LEDs, and slide it to "Other modules" to utilise any installed add-on modules such as the tracking module.

Ring:bit special expansion board
Ring:bit special expansion board

5. Programming the Ring:bit

In this subsection we will start coding Python programs in order to instruct the Ring:bit car to perform both basic and advanced operations including moving forwards and reversing, following black lines and routes, moving towards bright light sources, and automatically avoiding obstacles.

5.1. Getting Started

For the purposes of this taster module, we shall use the Microsoft MakeCode editor to develop Python programs for our Ring:bit car and transfer them using the direct flashing method. To get started using the Microsoft MakeCode editor to develop Python programs for our Ring:bit car, please follow the steps below.

  1. Place 3 x AAA 1.5V Alkaline batteries into the battery compartment on the back of the Ring:bit board.
  2. Connect the Micro:bit to your desktop or laptop computer (Windows, MacOS, Linux or Chrome OS) using a USB micro-B compatible data transfer USB cable.
  3. Navigate to the Microsoft MakeCode editor in your internet browser (Google Chrome or Microsoft Edge).
  4. Select "New Project" and give your project a name (such as Ringbit). Then press "Create".
  5. Select "Python" from the editor options at the top of the screen.
  6. Select "Extensions" from the menu and search for "ringbitcar".
  7. Select the "ringbitcar" extension. This will import the Python libraries required to develop Python programs for the Ring:bit Car V2. Once successfully imported, you will see the "RingbitCar" option available in the menu.
  8. Select the three dots next to the "Download" button.
  9. From the resultant menu, select "Connect device".
  10. Select "BBC Micro:bit" from the resultant pop-up window, and then press the "Connect" button.
  11. A USB logo will now appear on the "Download" button. Now every time you select the "Download" button, your code will transfer directly to the Micro:bit.

5.2. Basic Instructions

In this subsection we will introduce relevant Python statements that will enable the Ring:bit car to follow basic instructions.

5.2.1. Initialising the Wheels

As described in section 4.1. Ring:bit Board and GVS Pins above, we must initialise our Ring:bit programs by defining that the wheels of our Ring:bit are connected to the Micro:bit pins P1 (corresponding to GVS1) and P2 (corresponding to GVS2) respectively. This can be achieved in Python using the init_wheel(left wheel pin, right wheel pin) method as follows.

# Initialise the wheels of the Ring:bit car
RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2)

5.2.2. Moving Forwards

The following Python statement will instruct the Ring:bit car to move forwards (at full speed) using the forward() method.

# Move forwards at full speed
RingbitCar.forward()

The following Python statement will instruct the Ring:bit car to move forwards for 5 seconds using the running_time(direction, duration) method.

# Move forwards for 5 seconds
RingbitCar.running_time(RingbitCar.Direction_run.FORWARD, 5)

5.2.3. Reversing

The following Python statement will instruct the Ring:bit car to reverse (at full speed) using the back() method.

# Reverse at full speed
RingbitCar.back()

The following Python statement will instruct the Ring:bit car to reverse for 5 seconds using the running_time(direction, duration) method.

# Reverse for 5 seconds
RingbitCar.running_time(RingbitCar.Direction_run.BACKWARD, 5)

5.2.4. Moving Distance

The following Python statement will instruct the Ring:bit car to move forwards 10cm using the running_distance(direction, distance) method.

# Move forwards 10cm
RingbitCar.running_distance(RingbitCar.Direction_run.FORWARD, 10)

5.2.5. Braking

The following Python statement will instruct the Ring:bit car to brake using the brake() method.

# Brake
RingbitCar.brake()

5.2.6. Turning

The following Python statement will instruct the Ring:bit car to turn left (at full speed) using the turnleft() method.

# Turn left at full speed
RingbitCar.turnleft()

The following Python statement will instruct the Ring:bit car to turn right (at full speed) using the turnright() method.

# Turn right at full speed
RingbitCar.turnright()

5.2.7. Wheel Speed

The following Python statement will instruct the left wheel of the Ring:bit car to move at speed 50, and will instruct the right wheel of the Ring:bit car to move at speed 0 (i.e. spin in a circle) using the freestyle(left wheel speed, right wheel speed) method.

# Freestyle i.e. spin in a circle
RingbitCar.freestyle(50, 0)

5.3. Example - Drive and Reverse

In the following complete example, our Python program will instruct the Ring:bit car to move forwards when the "A" button is pressed on the Micro:bit board, reverse when the "B" button is pressed, and brake when both the "A" and "B" buttons are pressed together.

# Initialise the wheels
RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2)

# Define a function that provides instructions to perform when the A button is pressed
def on_button_pressed_a():
    RingbitCar.forward()

# Define a function that provides instructions to perform when the B button is pressed
def on_button_pressed_b():
    RingbitCar.back()

# Define a function that provides instructions to perform when the A+B buttons are pressed
def on_button_pressed_ab():
    RingbitCar.brake()

# Bind the functions to the relevant input event handlers
# Reference: https://makecode.microbit.org/reference/input/on-button-pressed
input.on_button_pressed(Button.A, on_button_pressed_a)
input.on_button_pressed(Button.B, on_button_pressed_b)
input.on_button_pressed(Button.AB, on_button_pressed_ab)

We begin this example by initialising the wheels using RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2). We then define three custom functions that provide the instructions to perform when the relevant buttons on the Micro:bit board are pressed. Note that we can call these functions anything we like as long as they conform to Python naming rules but, as we studied in the Quick Introduction to Python module, by convention they should be all lowercase, words separated with the underscore _ character, short, concise and meaningful. In our case, our functions are called on_button_pressed_a, on_button_pressed_b and on_button_pressed_ab respectively.

Finally, we define three event handlers using the on_button_pressed(button, function name) method. Event handlers are functions that will run in response to an event or action. In this case, the event handler on_button_pressed will run when a button is pressed. We then pass the relevant button and function name to the on_button_pressed method in order to bind our custom functions to the relevant button press event.

To learn more about the On Button Pressed event handler, please refer to the on-button-pressed docs. And to learn more about all input event handlers available for the Micro:bit, please refer to the input docs.

5.4. Example - Follow a Route

In the following complete example, our Python program will instruct the Ring:bit car to automatically follow a black line.

To follow this example, it is required that you have installed the tracking module on the Ring:bit car. Please refer to the previous module for instructions on how to install the tracking module, ensuring that you connect the special expansion board to the Ring:bit board using the jump wire via the 0VG GVS pin, and that the brown ground wire is connected to the G pin. Please also ensure that the slide switch on the special expansion board is set to "Other modules".

# Initialise the wheels
RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2)

# Perform custom instructions (indefinitely) - follow a black line
def on_forever():

    # Left infrared sensor detects deviation from the black line.
    # Stop the right wheel and slow the left wheel in order to turn.
    if RingbitCar.tracking(RingbitCar.TrackingStateType.TRACKING_STATE_2):
        RingbitCar.freestyle(25, 0)
        basic.pause(250)

    # Right infrared sensor detects deviation from the black line.
    # Stop the left wheel and slow the right wheel in order to turn.
    if RingbitCar.tracking(RingbitCar.TrackingStateType.TRACKING_STATE_1):
        RingbitCar.freestyle(0, 25)
        basic.pause(250)

    # Normal moving speed
    RingbitCar.freestyle(50, 50)

# Move indefinitely when the A button is pressed
def on_button_pressed_a():

    # Move forwards
    RingbitCar.freestyle(50, 50)

    # Run a given function body continuously in an event-based forever loop
    # Reference: https://makecode.microbit.org/reference/basic/forever
    basic.forever(on_forever)

# Bind the function to the relevant input event handler
# Reference: https://makecode.microbit.org/reference/input/on-button-pressed
input.on_button_pressed(Button.A, on_button_pressed_a)

We begin this example again by initialising the wheels using RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2). We then define a custom function called on_forever that contains our custom instructions based on the conditional state of the infrared sensors attached to the tracking module. Using control flow and the if statement, if the left infrared sensor detects deviation from the black line then we instruct the Ring:bit to stop the right wheel and slow the left wheel in order to turn and to continue to follow the black line. Similarly, if the right infrared sensor detects deviation from the black line then we instruct the Ring:bit to stop the left wheel and slow the right wheel in order to turn and to continue to follow the black line. If neither of these conditions are evaluated as True, then we instruct the Ring:bit to move the left and right wheels at the same speed i.e. move forwards and continue to follow the black line.

Next we define a custom function called on_button_pressed_a that instructs the Ring:bit to start moving forwards. Then we bind our custom on_forever function containing our custom instructions to a forever loop which will run the given function body continuously i.e. our Ring:bit car will continue to follow the black line indefinitely (until it is turned off or until the batteries run out). Finally we bind the on_button_pressed_a function to the button A press event using an event handler so that we have to explicitly press the A button on the Micro:bit board to start moving the Ring:bit.

To learn more about the forever loop, please refer to the forever docs.

5.5. Example - Follow the Light

In the following complete example, our Python program will instruct the Ring:bit car to follow a light source.

# Initialise the wheels
RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2)

# Perform custom instructions (indefinitely) - follow a light source
def on_forever():

    # Check the light level using a comparison operator.
    # If the light level is greater than 40 then move forwards otherwise brake.
    # Reference: https://makecode.microbit.org/reference/input/light-level
    if input.light_level() > 40:
        RingbitCar.forward()
    else:
        RingbitCar.brake()

# Run a given function body continuously in an event-based forever loop
# Reference: https://makecode.microbit.org/reference/basic/forever
basic.forever(on_forever)

We begin this example again by initialising the wheels using RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2). We then define a custom function called on_forever that contains our custom instructions based on the conditional state of the LED light sensors attached to the Micro:bit board. Using control flow and the if statement, if the light level is greater than 40 (0 refers to complete darkness and 255 refers to bright white light) then we instruct the Ring:bit to move forwards at full speed (i.e. towards the light source), otherwise we instruct the Ring:bit to brake.

Finally, and similar to the previous example, we bind our custom on_forever function containing the custom instructions to a forever loop which will run the given function body continuously i.e. our Ring:bit car will continue to detect and move towards a light source.

To learn more about the light level input, please refer to the light-level docs.

5.6. Example - Obstacle Avoidance

In the following complete example, our Python program will instruct the Ring:bit car to automatically detect and avoid obstacles.

To follow this example, it is required that you have installed the Sonar:bit module on the Ring:bit car. Please refer to the previous module for instructions on how to install the Sonar:bit module, ensuring that you connect the Sonar:bit module to the Ring:bit board using the jump wire via the 0VG GVS pin, and that the brown or black ground wire is connected to the G pin.

5.6.1. Sonarbit Extension

To import the Python libraries required to develop Python programs for the Sonar:bit module, we first need to install the "Sonarbit" extension. To do this, please follow the steps below.

  1. In the Microsoft MakeCode editor, select "Extensions" from the menu and search for "https://github.com/elecfreaks/pxt-sonarbit".
  2. Select the "sonarbit" extension. Once successfully imported, you will see the "Sonarbit" option available in the menu.

5.6.2. Python Code

# Initialise the sonar variable
sonar = 0

# Initialise the wheels
RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2)

# Perform custom instructions (indefinitely) - obstacle detection and avoidance
def on_forever():

    # Read the distance (cm) to an object detected by the ultrasonic sensor
    # that is connected to the 0VS GVS pin.
    # Store the distance in the global sonar variable.
    global sonar
    sonar = sonarbit.sonarbit_distance(Distance_Unit.DISTANCE_UNIT_CM, DigitalPin.P0)

    # Display the current distance on the micro:bit board
    basic.show_number(sonar)

    # If the distance to the object is less than 15cm and is not 0cm
    # then avoid the object by turning.
    # Otherwise continue to move forwards.
    if sonar < 15 and sonar != 0:
        RingbitCar.freestyle(0, 50)
        basic.pause(500)
    else:
        RingbitCar.freestyle(50, 50)

# Indefinitely avoid obstacles when the A button is pressed
def on_button_pressed_a():

    # Move forwards
    RingbitCar.freestyle(50, 50)

    # Run a given function body continuously in an event-based forever loop
    # Reference: https://makecode.microbit.org/reference/basic/forever
    basic.forever(on_forever)

# Bind the function to the relevant input event handler
# Reference: https://makecode.microbit.org/reference/input/on-button-pressed
input.on_button_pressed(Button.A, on_button_pressed_a)

We begin this example again by initialising the wheels using RingbitCar.init_wheel(AnalogPin.P1, AnalogPin.P2). We also define a variable named sonar (which we will explain in a moment) and initialise it with the value of 0.

We then define a custom function called on_forever that contains our custom instructions. First we read the distance (cm) to an object that has been detected by the ultrasonic sensor in the Sonar:bit module and we store this distance measurement in the sonar global variable. We then display this distance measurement on the Micro:bit board. Then, using control flow and the if statement, we test whether the distance is less than 15 (cm) and not equal to 0 (cm). If these conditions evaluate to True, then we instruct the Ring:bit to stop the left wheel (but continue running the right wheel) in order to turn away from the object, otherwise we instruct the Ring:bit to continue moving forwards.

Next we define a custom function called on_button_pressed_a that instructs the Ring:bit to start moving forwards. Then we bind our custom on_forever function containing our custom instructions to a forever loop which will run the given function body continuously i.e. our Ring:bit car will continue to detect and avoid obstacles indefinitely (until it is turned off or until the batteries run out). Finally we bind the on_button_pressed_a function to the button A press event using an event handler so that we have to explicitly press the A button on the Micro:bit board to start moving the Ring:bit.

Summary

In this module we have used Python to program our robot car to follow simple and advanced instructions including moving forwards and reversing, following black lines and routes, moving towards bright light sources, and automatically detecting and avoiding obstacles.

Homework

  1. Write a Python program that displays the number 1 on the board of the Micro:bit. Then extend your program so that each time the A button on the Micro:bit board is pressed, the number displayed on the Micro:bit board is incremented by 1.
  2. Write a Python program that instructs the Ring:bit car to continuously move in a figure of eight when the A button on the Micro:bit board is pressed.
  3. Write a Python program that instructs the Ring:bit car to continuously move in random directions when the A button on the Micro:bit board is pressed.

What's Next

We hope that you have enjoyed this Python taster course! You now have an understanding of basic computing concepts, the ability to develop simple software applications in Python, and the ability to code and instruct a programmable robot car to follow simple and advanced instructions.

If this taster course has whetted your appetite to learn Python and embark upon a career in technology, then please check out our free and in-depth Introduction to Python course - an introductory course to the Python 3 programming language with a curriculum aligned to the Certified Associate in Python Programming (PCAP) examination syllabus.