Ring:bit - Developer Guide Image by Freepik

Ring:bit - Developer Guide

In this brief two-part series of tutorials, we will be using the Python programming language to instruct a robot car, the Elecfreaks Ring:bit car (v2), to perform a range of tasks including basic movements, line tracking, and light & object detection. In the previous tutorial, we provided instructions for assembling the Ring:bit car. In this tutorial, we will develop Python programs to instruct the Ring:bit car to undertake a variety of tasks including following a route, light detection, and object detection and avoidance.

1. Requirements

Please ensure that you have read and followed the instructions described in our Ring:bit - Assembly Guide tutorial in order to assemble your Ring:bit car (v2), including integrating the Micro:bit controller and installing the optional Ring:bit add-on modules.

2. Micro:bit Code Editors

In order to program the Micro:bit with instructions, and hence instruct our Ring:bit car to undertake tasks, 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-based code editing.
  • Python Micro:bit - a code editor for the Micro:bit specifically for Python developers.

For the purposes of this tutorial, 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 to Micro-USB (Type B) 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).

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 to Micro-USB (Type B) 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 to Micro-USB (Type B) 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 tutorial, 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. 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 tutorial 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 below, we must initialise our programs by specifying 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 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 instructions.

4.2. Special Expansion Board

In the previous tutorial 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 section we will develop Python programs to instruct the Ring:bit car to perform both basic and advanced tasks including moving forwards and reversing, following black lines and routes, moving towards bright light sources, and automatically detecting & 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 with 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 to Micro-USB (Type B) cable.
  3. Navigate to the Microsoft MakeCode editor in your internet browser.
  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 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. Board and GVS Pins above, we must initialise our Ring:bit programs by specifying 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. Travelling a 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 e.g. 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)

# Move forwards
def on_button_pressed_a():
    RingbitCar.forward()

# Reverse
def on_button_pressed_b():
    RingbitCar.back()

# Brake
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, 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 tutorial 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)

# 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 at a constant speed 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)

# Follow a light source
def on_forever():

    # Check the light level using a comparison operator.
    # If the light level is greater than 200 then move forwards otherwise brake.
    # Reference: https://makecode.microbit.org/reference/input/light-level
    if input.light_level() > 200:
        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 200 (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 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 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 tutorial 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 that utilise 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 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 measure the distance, in centimetres (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 greater than 0cm and less than 15cm. 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.

Jillur Quddus
Written by

Jillur Quddus

Computational Mathematician @ HyperLearning AI