Secret Code

Learning MicroPython on micro:bit - Part 2

Coding the Bit:Bot

Liam Davies

Issue 34, May 2020

This article includes additional downloadable resources.
Please log in to access.

Log in

In part 1 of our learning MicroPython on micro:bit, we looked at how to write code for the micro:bit using Mu, a simple but fully-featured development environment that’s great for beginners. In this second half, we’re going to be learning how to use the Bit:Bot. Let’s get started!

CODE TYPE: MICROPYTHON
SKILL LEVEL: BEGINNER

The Bit:Bot Robot is a kit originally made by 4tronics that can be assembled in minutes and is controlled by a micro:bit. It has a wide variety of features including environment sensors, rainbow Neopixel LEDs, a buzzer, and two geared drive motors. If you want to get your hands on a Bit:Bot and follow along (or have a go making your own inventions!), the Bit:Bot is available from www.core-electronics.com.auCore Electronics and other online retailers.

Note: The Bit:Bot doesn’t come with its own micro:bit. You’ll need to use one you already have or purchase separately.

The Bit:Bot has a Grove connector that you need to insert the micro:bit into, which allows the micro:bit to both operate from the Bit:Bot’s battery and communicate with its motors and lights.

A powered up Bit:Bot Robot by 4troncis

WHEELS IN MOTION

Let’s get the Bit:Bot moving! For this first project, we’ll make it move in a square. Easy, right?

We’re going to be using some of the coding techniques we learnt in Part 1, so if you’re starting out, it might be a good idea to read last month’s issue.

Before we get too far into the technical stuff, it’s a good idea to understand how the micro:bit interfaces with the Bit:Bot. The gold pins on the bottom of the micro:bit is connected to various features of the Bit:Bot through the connector underneath the battery pack. This means that the smaller gold pins we couldn’t use in Part 1 (due to the size of alligator clips) are now usable with the Bit:Bot for its wide variety of lights, motors and sensors. Awesome!

You will need to insert the micro:bit with the two A and B buttons facing upwards, which will also give access to the micro:bit’s integrated LED panel. You can look at how pins on the micro:bit are interconnected by looking at the underside of the Bit:Bot:

To control the motors on the Bit:Bot using our micro:bit, we need to use pins 0, 8, 1 and 12. These four motor pins on the Bit:Bot are used for two motors, each of which has a “direction” pin and a “speed” pin.

* If the motor is in reverse, this needs to be subtracted from 1023.

This table shows the pins used for each of the motors on the micro:bit. The first thing we’re going to do is write some functions in MicroPython that will allow us to easily control the movement of our Bit:Bot. This will be super handy because we don’t want to have to keep writing all the digital and analog pin control code each time!

To begin, we’ll write our forward function.

def drive_forward(speed):
  pin8.write_digital(0)
  pin0.write_analog(speed)
    
  pin12.write_digital(0)
  pin1.write_analog(speed)

This forward function, when called, sets all of the Bit:Bot’s motors to forward and runs them at the “speed” parameter we pass in. Notice that we have set both “direction” pins to 0 to signify both motors need to run forward. Our PWM (analog) pins on pin 0 and pin 1 control the speed of the motor (from values 0 to 1023). It’s important to note that this function turns on the motors indefinitely so the motors will have to be stopped elsewhere in code. Next, we’ll write our left and right functions that will rotate the Bit:Bot left and right on the spot.

def drive_left(speed):
  pin8.write_digital(1)
  pin0.write_analog(1023 - speed)
    
  pin12.write_digital(0)
  pin1.write_analog(speed)    
def drive_right(speed):
  pin8.write_digital(0)
  pin0.write_analog(speed)
    
  pin12.write_digital(1)
  pin1.write_analog(1023 - speed)

You’ll notice these two functions look very similar to the forward function! However, depending on the direction travelled, we set one motor to be in reverse and one motor to be rotating forwards. This results in “tank-tread” steering, where the Bit:Bot can turn around on the spot by rotating its motors in opposite directions.

Readers with a keen eye may be curious as to why some “speed” pins are written differently – that is, the speed value we pass to function has been subtracted from 1023! When a motor runs in reverse, it’s speed pin is inverted such that 1023 is not moving at all and 0 is full speed in reverse.

Finally, we wrote a stop function and a reverse function that operate very similarly to what we just described – you can look at these functions in the project files.

These functions aren’t very useful unless we run them! Let’s write a program using these functions that moves our Bit:Bot in a square. (The code below runs beneath all of our other functions.)

move_speed = 300
for i in range(0,4):
  drive_forward(move_speed)
  sleep(2000)
  drive_right(move_speed)
  sleep(450)
drive_stop()

Alright, let’s look through it! We first define a variable called move_speed that is passed into all of our movement functions. This saves hassle if we would like to change the movement speed of our bot – we don’t have to go through and change every value. We then start a For loop that repeats the intended code four times – one for each side of the square! After driving forward for two seconds, we turn right for 450ms and then repeat. After our Bit:Bot has moved to form sides of the square, we call “drive_stop” to stop the Bit:Bot’s motors.

Caring for your Bit:Bot

  • The Bit:Bot has small motors and gears in the wheels. You’ll need to be careful handling and using it because they aren’t indestructible!
  • Don’t run the Bit:Bot into a wall and leave the wheels spinning forward. This will put a lot of stress on the motors trying to move!
  • Don’t twist the wheels on the Bit:Bot by hand. You could generate a voltage in the motor and damage it’s electronics.
  • Don’t run the Bit:Bot over dirty floors or carpets – it’s a great way to get fluff and dirt into the gears and axles of the Bit:Bot.
  • Don’t leave the battery pack switched on! Always turn it off after you’re done using the Bit:Bot.

TESTING... TESTING...

Uploading code to the Bit:Bot is the same as uploading code to a standalone micro:bit. Simply plug in a micro-USB cable and hit “Flash” in the Mu code editor. Then, once the yellow light on the underside of the micro:bit has stopped blinking, disconnect the cable and place the Bit:Bot on a flat, smooth surface (Clean floorboards work well – avoid tiles, carpets and dirty surfaces). Flick the switch on the back of the battery pack and the Bit:Bot should spring to life with a blue LED and an enthusiastic square dance routine!

You may notice that depending on your specific Bit:Bot, battery level and motor quality, it may not draw a perfect square. This is because we are moving and turning our Bit:Bot based on time, not distance or degrees turned. In technical terms, this might be called an “open-loop control system” – that is, the Bit:Bot does exactly what it is programmed to do, and ignores the real world performance of its program! Small variations in its environment, like a floor that is harder to move on, are enough to completely throw off the results of the program. A better “closed-loop control system” would use the sensors in the micro:bit to determine how many degrees the Bit:Bot has turned, and cut the motors when it has reached its target.

A quick word on programming the Bit:Bot: The programming micro-USB port for the micro:bit is frustrating to access due to its horizontal orientation on the Bit:Bot. If you have the Ultrasonic sensor add-on for the Bit:Bot, you may find it impossible to insert the cable! This problem can be fixed by purchasing an inexpensive right angle-connector for the micro:bit which holds it upwards, making it easier to access the micro:bit (We bought ours from Little Bird).

IMAGE CREDIT: Little Bird
IMAGE CREDIT: Little Bird

Alternatively, you can do what we did and solder extended headers on the Ultrasonic add-on to allow more space for the micro-USB cable.

LIGHT IT UP!

The Bit:Bot includes 12 multi-coloured LEDs, known as NeoPixels. These can be controlled individually with code to change both their brightness and colour. In this section, we'll program the Bit:Bot to light up each of its LEDs a random colour every second. We'll also demonstrate how to change the colours in time with some music!

Note: A quick warning before we start. Don’t run these LEDs at full brightness for long periods of time! All twelve NeoPixels set to white and full brightness will not only heat up fast but will drain your Bit:Bot’s battery in no time flat (pun intended).

Alright, let’s get to the code. The libraries included within Mu are super handy and are ready to support NeoPixels right out of the box. Just type the following lines at the top of your code to import the NeoPixel code:

import neopixel
np = neopixel.NeoPixel(pin13, 12)

The second line of the code creates a NeoPixel object which is imported from the neopixel library. The first argument is the pin the NeoPixels we’re using (pin13), and the second argument is the number of NeoPixels used on the Bit:Bot (12).

To change the state of pixels on the Bit:Bot, we first need to assign a pixel an RGB value, where values from 0-255 represent brightness values of Red, Green and Blue respectively. For example, we could represent green as (0,255,0) and magenta as (255, 0, 255). To turn off pixels completely, all colour channels need to be set to 0 and to turn on all pixels to full bright-white, we need to set all colour channels to 255. This is why setting the pixels to full bright-white draws so much power – we’re running all three LEDs in each NeoPixel at full brightness! Anyway, here’s how we set a specific pixel to be the colour we want:

np[0] = (255, 128, 0)
np.show()

In this case, we’re updating the first pixel in the 12-long string to an orange colour. Note that the index in np[0] corresponds to which pixel we want to update. Like we discussed in the last part of the micro:bit guide, this is zero-based, so the last pixel would be np[11]. Additionally, we have to remember to call np.show()! Although this isn’t required after every NeoPixel we update, we need to make sure we call it when we actually want to update the LED display.

To begin, let’s write a random colour to each LED every second! We can do this by using the random module included with the MicroPython language.

from microbit import *
import neopixel
import random
np = neopixel.NeoPixel(pin13, 12)
while True:
  for i in range(0,12):
    random_r = random.randint(0,255)
    random_g = random.randint(0,255)
    random_b = random.randint(0,255)
    np[i] = (random_r, random_g, random_b)
    np.show()
  sleep(1000)

In the code we’ve written here, we’re using both a While loop and a For loop to control our NeoPixels. The first While loop repeats indefinitely, sleeping for a second after every time our For loop finishes. So, what does our For loop do? We iterate over a list of 12 elements, from 0 to 11, being the indexes we are using to address our NeoPixels – so the code in the For loop does something to each NeoPixel individually! We then generate three variables; random_r, random_g and random_b. These values are between 0 and 255 and represent each of our colour channels. After we’ve assigned these colours to that NeoPixel, we then show it on the display and continue generating random colours.

So then, it will come as no surprise that our Bit:Bot is disco ready! Simply upload the code using the Mu editor and remember to switch on the Bit:Bot’s battery pack – and maybe grab a pair of sunglasses too. No, seriously – the NeoPixels are eye-wateringly bright!

To improve this program, there are a number of fixes you could make to our code. You may have noticed that using the randomisation method we’ve chosen still results in very bright LEDs – they probably still appear close to white, but with various other colours mixed in. If we’re choosing the values of our colour channels randomly, shouldn’t some LEDs be quite dark?

The reason why the LEDs appear bright, even at low brightness levels, isn’t actually because of the LEDs themselves. It’s our eyes – we do not perceive brightness on a linear scale! We perceive even very dim LEDs disproportionally bright when compared to other, brighter LEDs. To fix this problem, you can implement what is known as “gamma correction” into your code. The code will artificially correct the brightness curves to appear as linear (to us humans)! There is plenty of information available on the internet about this phenomenon and how to rectify it in software.

Something else you could do to really drive home the “disco robot” idea is to coordinate the flashes of LEDs to the rhythm of the music you are listening to! If you know the BPM (Beats Per Minute) of the music, you can calculate the duration between each beat (in milliseconds) with the following formula:

Duration = 60,000 / BPM

Then, simply change the “sleep(1000)” line of our code to this duration. Once the code is uploaded, watch your robot rock out!

FOLLOW THE LIGHT

Making light with the NeoPixels is fun, but the Bit:Bot has another feature that is just as cool. On the front of the Bit:Bot are two small light sensors that determine the amount of light hitting it and returns it to the micro:bit as a value between 0 and 1023. Let’s write some code that navigates our Bit:Bot towards a light!

First, we should discuss how the Bit:Bot reads the values from its light sensors. There is some circuitry already in Bit:Bot that controls which light sensor is connected to the micro:bit. Pin 16 on the micro:bit can switch between the left and the right sensor, and Pin 2 is able to read the value of the selected sensor. So, we need to remember to change Pin 16 before we read the value of the light sensor we want.

Since there is one on the right side of the Bit:Bot and one on the left side, we can use these to determine the direction of a light source. After rotating the Bit:Bot in the direction of the brighter sensor until the sensors are close to equal, we know we’re pointing directly at the light source!

Note: This code assumes that you’ve already written the movement functions in the first part of this article. Let’s get to it!

while True:
  pin16.write_digital(0)
  light_level_l = pin2.read_analog()
  pin16.write_digital(1)
  light_level_r = pin2.read_analog()
  sleep(50)
  if abs(light_level_l - light_level_r) > 4:
    if light_level_l < light_level_r:
      drive_right(200)
      sleep(100)
      drive_stop()
    if light_level_l > light_level_r:
      drive_left(200)
      sleep(100)
      drive_stop()
  drive_forward(200)
  sleep(300)

There is some fancy stuff going on here, so let’s break it all down. After we’ve entered our infinite while loop, we first want to gather our light values before deciding where to move. To read the left sensor, we write ‘0’ to pin16 to tell the Bit:Bot to switch to the left sensor. Then, we can read the analog value of pin2 and store it in a variable called ‘light_level_l’ - we’ll use this later!

Similar to the left side, we set pin16 to ‘1’ to read the right side, and we store it in ‘light_level_r’.

The next bits of code gets a bit mathematical, but what we are doing with the first “if” line is checking whether we are far enough from the light’s direction to correct the Bit:Bot’s course. By taking the absolute value of the difference between the readings, we are able to get an indicator of how close we are to facing towards the light. If we are facing directly towards the light, this value will be zero because both light sensor values will be identical. e.g.: abs(250-250) = 0

If, for example, one of the sensor values is higher than the other, we know we still have to turn some distance. e.g.: abs(0-250) = 250

We then take this value and determine whether it is above a certain “tolerance” value (in our code, 4.). The reason why we do this is so that the Bit:Bot doesn’t keep correcting its direction when it’s even slightly off-course! If our light sensor values are 253 and 254, for example, they are so close it’s not worth correcting - considering our Bit:Bot has to stop driving and rotate to fix the difference. It is worth pointing out, if we allow the tolerance value to be too high (e.g. 100), the Bit:Bot will veer off-course and form a zigzag pattern on its path to the light.

We then determine which direction we need to turn. i.e. in the direction of the light sensor with the highest value (and thus, the strongest light). Depending on whether we turn right or left, we then tell the program to turn in that direction for a small amount of time and then stop. Then, we continue driving and repeat the loop!

TIME TO TEST

Once the code is uploaded to the micro:bit, simply take a flashlight or desk lamp and face it towards the Bit:Bot. It should rotate around to where your light is facing, stopping periodically to check it has turned the correct direction, and then drive towards it! Note that more dispersed light sources, such as room downlights or sunlight, are generally the same intensity in all directions so our program may not work as well.

This program can be improved in several different ways. Almost every value in the program can be changed to alter the behaviour of the Bit:Bot on its path towards your light. You could change the speed of the drive functions to charge towards the light (at the expense of accuracy) or change the time the Bit:Bot waits until it checks its light sensor values again.

WHERE TO FROM HERE?

This guide is a demonstration of the possibilities of MicroPython with the micro:bit. Now, it’s time for you to make some gadgets of your own! The Bit:Bot has a huge variety of gadgets and capabilities that is awesome for consolidating your programming and electronics knowledge. Below is a bunch of ideas that you could create from the tutorials in this series. Enjoy!

AUTOMATIC NIGHT LIGHT: Program a Bit:Bot to automatically dimly illuminate its Neopixels when the room lights go out. You could add some mood lighting by changing the colour of the NeoPixel lights, or program the Bit:Bot to continuously beep when it’s time to wake up – an alarm clock and night light in one!

PATROLLING BIT:BOT: The Bit:Bot can be expanded by purchasing an Ultrasonic module which allows it to ‘see’ objects in front of it. Why not create a patrolling Bit:Bot that roams an open area of your workstations, avoiding obstacles. If it sees a moving object, warn the intruder with some flashing red NeoPixels and some buzzer beeps. If the intruder still doesn’t budge, charge with all motors at full speed!

REMOTE CONTROL CAR: The micro:bit actually has an inbuilt “radio” module that enables communication between itself and other micro:bits! If you purchase another micro:bit, why not use the A and B buttons on a standalone micro:bit to control the motors on a Bit:Bot? Run the motors at 75% speed normally, unless the user presses both micro:bit buttons at once to activate Boost Mode! When it’s activated, the Neopixels on the Bit:Bot flash and the motor speed is increased to 100%!

DANCE DANCE BIT:BOT: Who says coding doesn’t get you to exercise!? Program the Bit:Bot to dance in time with a song! If the Bit:Bot spins, you spin around too! If the Bit:Bot moves forward and back again, step forward and back again. You could even create colour-coordinated dance moves that light up LEDs on the Bit:Bot to show the user what moves are coming up.

Part 1

Liam Davies

Liam Davies

17-year old high school student.