Category Archives: micro:bit

Micro:bit Serial Communications

By using the serial connections on a Micro:bit, it is possible to interact with it by sending messages in text, rather than just reading the input from a sensor. You can even use serial connections to allow the Micro:bit to talk to other Micro:bits, or different types of microcontrollers, such as an Arduino.

Basic serial output

You can write from the serial line to a computer connected by USB, by using the ‘print’ statement.

from microbit import *

print("Serial test starting")

c = 0
while True:
    if (button_a.is_pressed()):
        print("Button A pressed "+str(c))
        c = c + 1
        sleep(50)

The above will print to the serial line when button A is pressed. If you are using MU editor, flash the code, click REPL, hit the reset button on the back of the Micro:bit then press A a few times.

Micro:bit to micro:bit serial communication

To allow two Micro:bits to communicate over a serial connection, we must make use of the ‘uart’ library. For a serial connection, two wires are needed, rx is used to receive and tx is used to transmit. When communicating between two devices, rx must attach to tx on the other device, and vice versa. It is also good practice to connect the gnd pins to ensure a common potential difference.

If we use pins 0 and 1 for our communication. we can connect two Micro:bits with a common ground, and wire each pin 0 to pin 1 on the other device.

Serial connection between two Micro:bits

Set one Micro:bit and the sender with the following code:

from microbit import *

# Micro:bit serial to serial - sender

# Initalise the UART
uart.init(baudrate=9600, tx=pin0, rx=pin1)

while True:
    if(button_a.is_pressed()):
        uart.write("A")
        sleep(1000)
    if(button_b.is_pressed()):
        uart.write("B")
        sleep(1000)

Use below on the receiver:

from microbit import *

# Micro:bit serial to serial - sender

# Initalise the UART
uart.init(baudrate=9600, tx=pin0, rx=pin1)

while True:
    msg = uart.readline()
    if(msg is not None):
        display.scroll(msg)

When a button is pressed on the sender, a message is sent over the serial line to the receiver which displays it on the LED matrix. While the message is short, e.g. “A”, this could be something larger. A more advanced system could parse messages to pull out commands or data.

Arduino to Micro:bit communications

While the Micro:bit is a very versitile device, the low amount of memory, available IO pins or readily available hardware libraries, can cause it to struggle. One option is to use an arduino to drive a single piece of hardware and pass messages back to the Micro:bit using the serial line. An example is an RFID reader.

There are two options for serial communications to an arduino, hardware serial or software serial. On most types of arduino, pin D0 doubles as serial rx and D1 as serial tx. In our receiver code above on the Micro:bit, we have our pin0 configured as tx and pin1 as rx. Connecting the ground of an arduino to the ground on the Micro:bit, then arduino D0 and D1 through to Micro:bit pin0 and pin1.

The following sketch on the aduino will say ‘Hi’ then increase numbers (a sketch is arduino for a program). Though, due to line ending differences it will print ‘??’ on the scrolling display of the receiver. We will deal with that in the more advanced example below.

void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Native USB only
  }
  Serial.println("Hi");
  delay(2000);
}

void loop() // run over and over
{
  int i=0;
  char msg[5];
  while (true) {
      sprintf(msg, "%d", i);
      Serial.println(msg);
      delay(5000);
      i++;
  }
}

There is another problem. As we now have hardware connected to the serial line, we will have problems programming our arduino. We have to remove the connections to D0 and D1 each time we want to flash a new sketch. Another option is to use the software serial library and use other pins.

Connect the aruino to the Micro:bit, mapping D2 to pin0 and D3 to pin1 as shown.

The following code now makes use of the arduino software serial library and writes over pins D2 and D3. The code on the Micro:bit does not need to change.

/* Based on example at https://www.arduino.cc/en/Tutorial/LibraryExamples/SoftwareSerialExample */
#include <SoftwareSerial.h>

SoftwareSerial mySerial(2, 3); // RX, TX

void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Native USB only
  }
  Serial.println("Hi");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  mySerial.println("Lo");
}

void loop() // run over and over
{
  int i=0;
  char msg[5];
  while (true) {
      sprintf(msg, "%d", i);
      mySerial.println(msg);
      Serial.println(msg);
      delay(5000);
      i++;
  }
}

You could create more ‘mySerial’ objects to communicate to multiple Micro:bits or connect one to the serial. Using the software serial is useful while you are still doing code development on the arduino. You could then switch to hardware serial later.

Arduino serial to Micro:bit – advanced

Now lets look at doing something more useful. The Micro:bit LED display is not the best thing to read longer messages with, so lets connect a small OLED display. We need the arduino doing something useful. This example adds an Adafruit PN532 RFID/NFC shield to the arduino and sends the serial number of cards and tags swiped over software serial to a Micro:bit.

Arduino code:

/* rfid_to_serial - Dave Hartburn December 2020
 *  Dumps the UID of a card to the serial console. Designed to be the RDIF workhorse for
 *  another microcontroller, such as a micro:bit.
 *  
 *  Using Arduino UNO and Adafruit PN532 RFID/NFC Shield
 *  Based on the readMifare Demo program
 */

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
#include <SoftwareSerial.h>   // Use the software serial library

SoftwareSerial mySerial(2, 3);  // RX, TX

#define PN532_IRQ   (2)
#define PN532_RESET (3)  // Not connected by default on the NFC Shield
// Define the board
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  
  // Init RFID board
  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // halt
  }
  // Got ok data, print it out!
  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); 
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); 
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
  
  // configure board to read RFID tags
  nfc.SAMConfig();
  
  //Serial.println("Waiting for an ISO14443A Card ...");  
}

void loop() {
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;                        // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
  int i,len;
  unsigned long uidl;
  char msg[50];
  
  // Wait for an ISO14443A type cards (Mifare, etc.).  When one is found
  // 'uid' will be populated with the UID, and uidLength will indicate
  // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  
  if (success) {
    // Display some basic information about the card
    //Serial.println("Found an ISO14443A card");
    //Serial.print("  UID Value: ");
    //nfc.PrintHex(uid, uidLength);

    // Convert array into single UID integer
    uidl=uid[0];
    for(i=1;i<uidLength;i++) {
      uidl = (uidl << 8) | uid[i];
    }
    //mySerial.print("CARD");
    sprintf(msg,"c=%lX",uidl);
    mySerial.print(msg);
    Serial.println(msg);

    // Wait 3 seconds
    delay(3000);
  }
}

Micro:bit code:

# Micro:bit card reader with arduino acting as
# RFID/NFC slave

from microbit import *
from ssd1306 import initialize, clear_oled
from ssd1306_text import add_text

# Init a SDD1306 screen
initialize()
clear_oled()

# Initalise the UART
uart.init(baudrate=9600, tx=pin0, rx=pin1)

while True:
    msg = uart.readline()
    if(msg is not None):
        clear_oled()
        add_text(0, 0, str(msg))

While this only reads the card serial number and displays it, you could do something more advanced such as a tick for known good cards and a cross for unknown cards.

Micro:bit OLED (SSD1306) Screen

Small OLED screens for microcontrollers are a cheap and easy way to display more detailed output for your Micro:bit than the scrolling LED matrix allows. Often sold as 0.96″ screens, these are just under 25mm wide and come in two formats. The larger square screen is 128×64 pixels or the slimmer model (pictured above) is half the height at 128×32 pixels. Some SPI connection versions are available, this post covers connections for I2C connections or the 4-pin version.

Connect up the screen as shown below. The two power pins, Vcc and GND connect to 3v and GND on the Micro:bit. On the Micro:bit, pins 19 and 20 are the I2C pins, connect SCL on the screen to pin 19 and SDA to pin 20. It is possible to connect multiple devices to I2C, while leaving the other IO pins free.

To use the OLED screen, we need to add additional libraries to MU editor. Libraries are additional software packages that add functions and hardware support, so you do not have to write everything yourself. Helpfully, ‘fizban99’ has produced a Micro:bit python library for OLED screens. Go to their Github page and using the green ‘Code’ button, select ‘Download ZIP’.

Open the ZIP file and copy all the python (.py) files into your mu_code folder (not a subdirectory). Use the following code to display some sample text:

from microbit import *
from ssd1306 import initialize, clear_oled
from ssd1306_text import add_text

initialize()
clear_oled()
add_text(0, 0, "Hello")
add_text(0, 2, "World")
add_text(2, 3, "Micro:bit")
display.show(Image.HAPPY)

This will still fail. Any libraries you need must also be copied to your Micro:bit. Hit the files button in MU and drag to your Micro:bit ‘ssd1306.py’ and ‘ssd1306_text.py’. Now you should have three lines of text displayed.

The libraries are a little limited in that you can’t control the font or font size, however there are a number of graphical functions in the library available. See the Github page for examples. If you are using a 128×32 screen, the same code will work, however you have to be careful not to use additional lines.

Where these screens are useful, is for displaying debugging information or detailed sensor output while developing.

Project: A Micro:bit calculator

Using a 4×4 keypad and a 16×2 LCD screen, can you construct a simple calculator?

Step 1: Connect up the hardware. Review the notes on keypads and LCD screens, make sure you understand how they work, connect them up and run some test programs. Both should be able to connect to pins 19 and 20 (the I2C pins) and operate independently.

Step 2: Can you make what you type appear on the screen, one character at a time? What about using * to clear the screen?

Step 3: Start with a very simple calculator that will accept a single digit, use A-D for +,-,x and รท, then calculate on the second digit pressed. Show the input on the top line and the answer on the second. Tip, you will find the function int(string) useful for converting strings to integers.

Step 4: Make the calculator more advanced. Allow you to put in multiple digit numbers, then use the # key for an =. You will need to break down the string into variables for each part. There is a list of string functions at w3schools. What might be useful is the split() function. Remember you can make a note of which operator is used as it is pressed. This avoids having to search the string for operators, or you could use the find() function.

Micro:bit Keypads

Keypads make a convenient way to add lots of buttons to a Micro:bit and they come in a range of sizes. The type above are known as membrane keypads. They have a sticky back, are lightly waterproof and can be wiped clean.

4 x 1 Keypad

A 4 x 1 keypad is named as it is 4 buttons in 1 row. It comes with a ribbon connector cable, with 5 pins. There is one pin for each key and one for ground. The wiring for these can always differ, so it is worth checking. Often ground is on a strip of cable on it’s own. The connector usually had a small number on each end denoting the pin number. On the keypad pictured above, the pinout, going from left to right is:

PinFunctionMicro:bit
1GroundGnd
2Key ‘2’pin0
3Key ‘1’pin1
4Key ‘4’pin2
5Key ‘3’pin8

You can see from the above the wiring does not go 1, 2, 3, 4 as you may expect. Sometimes these keypads can have different symbols or be blank.

Have a quick read up on Micro:bit buttons to see how a basic push button works on a Micro:bit. If we use a variable for each button and assign it to a pin we can set an internal pull up resistor then use read_digital() to get the state. The following code will show on the display which button is being pressed:

# 4x1 keypad

from microbit import *

# Define our wiring. The 5th connection goes to ground
key1 = pin1
key2 = pin0
key3 = pin8
key4 = pin2

# Set internal pullup resistor
key1.set_pull(key1.PULL_UP)
key2.set_pull(key2.PULL_UP)
key3.set_pull(key3.PULL_UP)
key4.set_pull(key4.PULL_UP)

while True:
    if ( key1.read_digital() == 0 ):
        display.show("1")
        sleep(500)
        display.clear()
    if ( key2.read_digital() == 0 ):
        display.show("2")
        sleep(500)
        display.clear()
    if ( key3.read_digital() == 0 ):
        display.show("3")
        sleep(500)
        display.clear()
    if ( key4.read_digital() == 0 ):
        display.show("4")
        sleep(500)
        display.clear()        

There is a lot of repeated code there and the program does not scale well if we were to use a different sized keypad. We can take advantage of arrays in Python to avoid writing the same code over and over again. Read up on arrays at w3schools. Rather than use a straightforward array of single values, we use a two dimensional array. Each value in the array keypad is a small array itself, with two elements, the pin number and the value. Lists start at 0, so we can access the value of the second pin with keypad[1][1].

# 4x1 keypad

from microbit import *

# Create our pinout as a list. Use pairs of values
# of [ pin, value ]
keypad = [ [pin1, "1"],
           [pin0, "2"],
           [pin8, "3"],
           [pin2, "4"] ]


# Set internal pullup resistor, looping through the list
for k in keypad:
    k[0].set_pull(k[0].PULL_UP)

while True:
    # Loop through list checking for keypress
    for k in keypad:
        if ( k[0].read_digital() == 0 ):
            display.show(k[1])
            sleep(500)
            display.clear()

4×4 Keypads

We learned above that smaller keypads have one pin for each button, plus an additional pin for ground. A 2 button keypad will have 3 pins, a 4 button keypad has 5 pins. A 4×4 keypad has 16 keys, so it should have 17 pins, right? No, it only has 8 pins, so what is going on here?

The image below shows a circuit diagram of a 4×4 keypad:

The wiring forms a matrix. If you trace the circuit through, you can see if the ‘5’ key is pressed, a connection is formed between pin 7 and pin 3. If we can detect these connections then we can work out what button is being pressed.

Rather than use 8 IO pins on the Micro:bit, we can use a PCF8574 module to easily plug in the keypad and use some binary or hex to quickly work out what buttons are being pressed. Read up on the PCF8574, binary and hex here. Connect the keypad to the PCF8574 board making sure pin 1 on the keypad goes to p0 on the board. P0 is labelled on the underside.

The PCF8574 has four connections, connect Vcc to 3v and GND to GND. The remaining two connections are for the I2C bus. SDA goes to pin 20 and SCL goes to pin 19. It does not matter if you already have a device plugged into these two pins on the Micro:bit. As I2C is a bus technology, it can support multiple devices, each with it’s own address. The three small switches on the PCF8574 are used to change the address of the board, leave then all as ‘Off’.

On the PCF8574, if a pin set to be high is connected to a pin set to be low, then the high pin will be dragged low. If we set all the pins to high, except one row (for example pin 5), we can detect a button press on that row by a pin 1 to 4 also going low. We can write one value to set the pins then read it back, if there are no key presses on that row the value will be the same. If the value is different then something is being pressed. Cycle through pins 5 to 8 in turn setting each to be low then inspect to see if something is pressed and return what.

We can follow the circuit diagram and make a table of what the two low values should be for each key press. As this forms an 8 bit binary string, we can also shorten each of these to give a unique hex value for each pin.

KeyP8P7P6P5P4P3P2P1Hex
1011101110x77
2011110110x7B
3011111010x7D
4101101110xB7
5101110110xBB
6101111010xBD
7110101110xD7
8110110110xDB
9110111010xDD
A011111100x7E
B101111100xBE
C110111100xDE
D111011100xEE
*111001110xE7
0111010110xEB
#111011010xED

Similar to an array in Python, we can also use a data structure called a dictionary to store these hex values. An array uses an index number 0, 1, 2… etc for each entry, where as a dictionary can use anything else, even text. Read up here. If we make a dictionary with each of the above hex values as keys and the key symbol as the value, e.g. keys[0xBB] = "5", we can quickly look up what key is pressed from the value returned from the PCF8574 board.

This gives us a process we can follow to detect key presses:

  • Create a variable with all the bits high except pin 5.
  • Send variable to the PCF8574 to set the pins.
  • Read data back from the PCF8574 and store in a variable.
  • If the value sent is different to the value received, a key on that row is being pressed
    • Lookup that value in the dictionary and return the key press
  • Skip to the next row and repeat if no press detected

This is done in the readKeypad function in the following code:

# 4x4 keypad
from microbit import *

PCF_ADDR = 0x20     # Use the base address of the PCF8574

# I2C send, function to simplify writing
def sendI2C(addr, value):
    # Convert sent value to a byte array, which is
    # required for I2C
    buf = bytearray(1)
    buf[0] = value
    # Send value
    i2c.write(addr, buf)
    # Short delay to send
    sleep(5)

def readI2C(addr):
    # Read in as a single byte and return
    v = i2c.read(addr, 1)[0]
    return v
    
def readKeypad():
    # Define array of keypress values
    # Curly brackets are a dictionary as we are not
    # using consecutive index values
    keys = {}
    keys[0x77] = "1"
    keys[0x7B] = "2"
    keys[0x7D] = "3"
    keys[0xB7] = "4"
    keys[0xBB] = "5"
    keys[0xBD] = "6"
    keys[0xD7] = "7"
    keys[0xDB] = "8"
    keys[0xDD] = "9"
    keys[0x7E] = "A"
    keys[0xBE] = "B"
    keys[0xDE] = "C"
    keys[0xEE] = "D"
    keys[0xE7] = "*"
    keys[0xEB] = "0"
    keys[0xED] = "#"
    
    # Set the default keypress to be an empty string
    # to mean nothing pressed
    kp = ""
    
    # Start with a zero on bit 5, all the rest 1
    setState = 0xEF
    
    for x in range(4):
        sendI2C(PCF_ADDR, setState)
        # Read value back
        r = readI2C(PCF_ADDR)
        
        # If the return state is different to the set state, a
        # key press has pulled one of the pins, 1-4 low
        if(setState != r):
            # print("setState=",hex(setState),", r=",hex(r))
            kp = keys[r]
            # Return key press
            return kp
            
        # Move the zero up to the next pin with a left shift.
        # We want to keep the value at 8 bits, so need to AND
        # with FF. Also, a zero will be moved in from the right.
        # OR with 0x01 to set this lower bit.
        # e.g. 1110 1111 << 1
        #   = 11101 1110 & 0xFF
        #   =  1101 1110 | 0x01
        #   =  1101 1111
        setState = ( ( setState << 1) & 0xFF) | 0x01
    
    # Return keypress value
    return kp

while True:
        # Read from keypad
        kp = readKeypad()
        # Clear if an empty string comes back
        if( kp == "" ):
            display.clear()
        else:
            # Otherwise show
            display.show(kp)
        sleep(200)
   

Using a dictionary this way also allows you to change the meaning of keys. For example if you were making a calculator you could change the dictionary to return ‘+’ instead of ‘A’. Remember you can use the bin() and hex() functions with print if you want to see what is going on for any stage of the above.

Micro:Bit More IO pins (PCF8574), binary and hex

The Micro:bit comes with 20 IO (input/output) pins which can be used to connect a range of hardware devices, however if you look at a pinout diagram, you find that 8 of these are used by the LED display and the buttons. If you start using devices which require a lot of pins (such as keypads), then you can quickly run out of usable pins.

One solution is to use devices such as PCF8574 modules which connect to the I2C (19 and 20) pins and will provide 8 additional pins. As you can connect multiple devices to I2C, you can either add more pin expansion modules or other types of device. For example, with a keypad attached to a PCF8574 and an I2C LCD screen, you can attach both to the Micro:bit and only use pins 19 and 20.

When all onboard switches are set to off, PCF8574 modules usually use the I2C address 0x20. If we connect LEDs to the first six pins of a module (P0 to P5) with 220 or 330 ohm resistors to ground on the other side (as pictured below). Build this circuit but ignore the buttons for now. We can flash all six LEDs with the following code:

# Testing of a IO expanded - PCF8574
from microbit import *

PCF_ADDR = 0x20     # Use the base address of the PCF8574

# I2C send, function to simplify writing
def sendI2C(addr, value):
    # Convert sent value to a byte array, which is
    # required for I2C
    buf = bytearray(1)
    buf[0] = value
    # Send value
    i2c.write(addr, buf)
    # Short delay to send
    sleep(5)
    
while True:
    sendI2C(PCF_ADDR, 0xFF)
    sleep(1000)
    sendI2C(PCF_ADDR, 0x00)
    sleep(1000)

What is going on here? The PCF8574 has 8 IO pins labelled P0 through to P7. If we want to set the output to one of those pins to high, we need to send it a 1, or a zero to be low. Where as with the normal Micro:Bit IO pins, we set them individually, with the PCF8574, we set them all in one go by sending a single byte containing all the information we need. An I2C write to the modules address with the value 0xFF means send all 1s, or turn all the IO pins on. Sending 0x00 means turn all the IO pins off. The above code flashes all the LEDs.

To really understand what is happening and have greater control of the LEDs, we need to understand binary and hexadecimal.

Understanding binary

Binary is another way of counting. We are used to using the numbers 0 to 9 (known as decimal), but in binary we only use 0 and 1. There is a BBC Bitesize module which gives a lot more information into understanding binary. Here we have a very brief description.

In decimal counting, a single digit 0 to 9 is not a lot of use if we want to count higher than 9. To go higher, we use the same digits again, arranged into columns increasing in value as we go from right to left. We start with units, then tens, hundreds, thousands and so on. We think of the number 157 as:

1000100101
0157

We are saying 157 is no thousands add 1 x 100 add 5 x 10 add 1 x 7. You will also notice that the columns as you go from right to left increase by a multiple of 10 each time.

In binary, as we only have the two digits 0 and 1, the columns increase by a multiple of 2 each time we move one to the left, which means we can think of numbers as:

1286432168421
11111111

With all numbers set to 1, 1111 1111 in decimal is 128+64+32+16+8+4+2+1, which is 255. If we changed all the values to 0, this would be 0 x 128 + 0 x 64 + 0 x 32 etc. The result would be 0, so in binary and decimal, 0 is the same. (So is 1).

How does this relate to our LEDs? Each of the IO pins is controlled by a single binary digit, so we can think of them all as:

1286432168421
P7P6P5P4P3P2P1P0
11111111

So, looking at the above, we know that 1111 1111 is 255 in decimal, and 0000 0000 is 0, so what would happen if we changed our loop to:

while True:
    sendI2C(PCF_ADDR, 255)
    sleep(1000)
    sendI2C(PCF_ADDR, 0)
    sleep(1000)

Give it a try.

No change. We are setting pins 7 and 6 when we have nothing attached, so why bother? What if we wanted to stop sending to those and only flash the lights on alternate pins with P5 on? What value would we need to send instead of 255? Consider it as a table:

1286432168421
P7P6P5P4P3P2P1P0
00101010

We need to add all the columns where there is a 1, which is 32+8+2. The answer is 42. Try changing this in the main loop and see what happens? What other patterns can you produce?

In python, you can write binary numbers directly, using the prefix ‘0b’. Another way to send the above example would be ‘0b101010’. Give that a try.

Binary Operators

In binary, we have a number of binary operators available. These are functions in the same way we have plus and multiply in decimal. Using these, we can get other effects. You need to be aware that these operate on bytes. A byte is 8 binary digits, so 42 should always be considered as 0010 1010, not just 101010. Our PCF8574 requires a single byte send to cover all 8 IO pins.

Python notationFunctionDescription
~ aNOTThe inverse of the value of the variable a. All 1s are changed to 0 and all 0 to 1. 0010 1010 becomes 1101 0101
a & bANDApplied in a column by column basis. If there is a 1 in the 4th column of ‘a’ and a 1 in the 4th column of ‘b’ then the result will be 1.
0010 1010
AND 0110 1001 becomes
0010 1000
because there are only 1s in both the 4th and 6th column, counting right from left.
a | bORApplied in the same way as AND only there will be a 1 in the result if there is a 1 in ‘a’ or in ‘b’. e.g.
0010 1010
OR 0110 1001 becomes
0110 1011
a << bLeft shiftMove all the binary digits to the left ‘b’ number of places, padding the end with zeros. Imagine this as more zeros coming in from the right to shove the numbers up. If we try a left shift of 2 we get
0010 1010 << 2
= 1010 1000
a >> bRight shiftMove all the binary digits in a to the right ‘b’ number of places. Same as left shift only the other way round.
0010 1010 >> 2
= 0000 1010
The two digits on the right that were pushed out disappear – like pushing them into the bin!

We can have a bit of fun with these. What if we wanted to create a pattern and flash the opposite? Just assign our pattern to the variable ‘p’ and use the NOT function:

while True:
    p = 0b101010
    sendI2C(PCF_ADDR, p)
    sleep(1000)
    sendI2C(PCF_ADDR, ~ p)
    sleep(1000)

Try changing the pattern.

What if we wanted to flash each light in turn then return to the start? We can work out that to light only the right most light, we need to send a 1 or 0b1, then to only light the next we can send a 2 or 0b10, then 4 or 0b100 etc, but that gets a bit repetitive. All we are doing each time is moving the 1 to the left and we know we have 6 LEDs, so we can keep the code short and use the left shift:

while True:
    # Set the initial pattern, right light on
    p = 1

    # Loop 6 times
    for x in range(6):
        sendI2C(PCF_ADDR, p)
        sleep(500)
        # Shift the pattern 1 place to the left
        p = p << 1

Hexadecimal

So, what about hexadecimal?

Hexadecimal is another numbering system. Rather than use the digits 0-9 or just 1 and 0, hexadecimal has the numbers 0 to 15. Going into double digits gets confusing so we use the letters A to F. In Python, we use the 0x prefix to mean something is hexadecimal. It was this we were using in the original code snippet. There is a BBC Bitesize module on hexadecimal.

HexadecimalDecimalBinary
000000
110001
220010
330011
440100
550101
660110
770111
881000
991001
A101010
B111011
C121100
D131101
E141110
F151111

When counting in hexadecimal (or hex for short), the columns go up in multiples of 16. E.g.:

256161
02A

In the above, we have 2x 16 + 10 x 1, which is 42 again. We previously used the binary 0010 1010 to represent 42, but look at the table of hex to decimal to binary digits. A 2 in binary is 0010 and a A is 1010. If we put them together 2A = 0010 1010. Basically by looking at each hex digit we can quickly convert between hex and binary without worrying about how man 32s, how many 16s we have etc. This is why programmers often use hex. Computers operate using binary and hex gives a very short hand notation for writing binary.

In the very first example, we set all the outputs to 1 by sending 0xFF. Can you see why that works?

Reading inputs

What if we wanted to attach some buttons? Can we read inputs? Yes. Now connect up P6 and P7 to buttons as in the picture above. If we write a 1 to each of these pins to put them in a high state, connect then to one side of a button and the other side to ground, when you press the button, it will be pulled low and turn to zero.

The following code writes a 1 to P6 and P7, then reads it back and prints the value back as a decimal. If you convert the binary, 0b1100 0000 is 192. If no button is pressed it will return a 192. However if you press the left button, the left most bit will turn to zero. This is 128 less, so 64 will be reported on the REPL console. If you press the right button, 128 should be reported and both will set the returned state to zero.

# Testing of a IO expanded - PCF8574
from microbit import *

PCF_ADDR = 0x20     # Use the base address of the PCF8574

# I2C send, function to simplify writing
def sendI2C(addr, value):
    # Convert sent value to a byte array, which is
    # required for I2C
    buf = bytearray(1)
    buf[0] = value
    # Send value
    i2c.write(addr, buf)
    # Short delay to send
    sleep(5)

def readI2C(addr):
    # Read in as a single byte and return
    v = i2c.read(addr, 1)[0]
    return v
    
# Input test
# Set P6 and P7 to high
buttonSet = 0b11000000
sendI2C(PCF_ADDR, buttonSet)
while True:
    v = readI2C(PCF_ADDR)
    print ("Button input = ", v)
    sleep(1000)

If you want to look at the returned value as binary or hex, use bin(v) or hex(v) in the print statement. Note that it will always trim leading zeros off the binary string, so when you press the left button, the binary string shrinks by 1 bit.

Combining Input And Output

That is all very well, but now the LEDs are broken. If we send our LED pattern the buttons will stop working. If we send our pattern to set the button state, the LEDs will stop working. If you can set the button states and the LEDs together, when you read back the values, you will also read back the LED state, meaning our 192, 128 and 64 for the buttons no longer hold true. Their values will be higher depending on which LEDs are currently showing. How can we handle both?

This is where the bitwise operators come in very useful. An OR essentially combines two binary values together. If we have one value for our LED state and one value to set the buttons, then OR them together before sending, the two values are combined and we can have both. For example if we want the third LED from the right showing and to set our button states we can can see what happens with the following table:

ValueP7P6P5P4P3P2P1P0In Hex
LED set000001000x04
ORButton set110000000xC0
Combined110001000xC4

What about reading values back? The AND function is often described as a mask. If we only want to know the state of P7 then we can make a binary string where only the left most bit is set to 1. If we AND this with the returned value we ‘mask’ out the other bits and can only look at the state of the one bit. If the whole value is zero then the button is pressed and if the button is not pressed, it should be 128. However we do not really care what the value is, just if it is not equal to zero. In the following table, P7 is being pressed but P6 is not.

ValueP7P6P5P4P3P2P1P0In Hex
Returned value010001000x44
ANDP7 mask100000000x80
Result000000000x00

Can you see what the result would be if P7 were not being pressed?

Lets put this into our code. Reading the result on the REPL console is slow, so as we have made sure we can still use the LED display on the Micro:Bit, lets make use of it. If we say the left button on P7 has a value of 2 and P6 has a value of 1. We can say if no button is pressed show a 0, if P7 is pressed, show a 2, if P6 is pressed show a 1 and show a 3 if both are pressed. Meanwhile, show our marching LEDs from the previous example. Also use hax values as this is more common when coding. Change the main loop to:

# Input test
# Set P6 and P7 to high
buttonSet = 0xC0
while True:
    # Set the initial pattern, right light on
    p = 0x01

    # Loop 6 times
    for x in range(6):
        # Combine the two values with an OR and
        # send to the Micro:But. Use the variable
        # s for send
        s = buttonSet | p
        sendI2C(PCF_ADDR, s)
        
        # Shift the pattern 1 place to the left
        p = p << 1 
     
        # Check the button state, use the variable
        # bState. Initially set it to zero
        bState = 0
        
        # Read a value in from the PCF8574
        v = readI2C(PCF_ADDR)
        
        # If that value AND the binary 1000 000 is
        # zero then button on P7 is being pressed
        if( ( v & 0x80 ) == 0 ):
            # Button is pressed, add 2 to bState
            bState = bState + 2
        
        # Now do the same for P6
        if( ( v & 0x40 ) == 0 ):
            # Button is pressed, add 1 to bState
            bState = bState + 1
            
        # Show the button state on the display
        display.show(bState)

        sleep(500)

We can make the button check and the display set even more efficient with the following code. We could even combine everything to one line if we wanted. Can you describe what is happening here (I’ve deliberately left the comments out) and can you make this a one liner? Remember, you can use the REPL console for debugging and print values as binary to see what is going on, e.g. print ("v = ", bin(v)). A pencil and paper may help too.

# Input test
# Set P6 and P7 to high
buttonSet = 0xC0
while True:
    # Set the initial pattern, right light on
    p = 0x01

    # Loop 6 times
    for x in range(6):
        # Combine the two values with an OR and
        # send to the Micro:But. Use the variable
        # s for send
        s = buttonSet | p
        sendI2C(PCF_ADDR, s)
        
        # Shift the pattern 1 place to the left
        p = p << 1 
     
        # Check the button state, use the variable
        # bState. Initially set it to zero
        bState = 0
        
        # Read a value in from the PCF8574
        v = readI2C(PCF_ADDR)
        
        a = v >> 6 
        b = ~ a
        bState = b & 0x03

        # Show the button state on the display
        display.show(bState)

        sleep(500)

Micro:bit LCD Display Screen

16×2 LCD module with I2C adapter and external power supply

A LCD screen is a great way to give more feedback to a user, either for a text message or values back from a sensor. Known as a 1602 LCD, this common display gives two rows of 16 characters to work with, and can scroll text. However it comes with two issues for the Micro:bit. First is that it needs 5v input and the second is it uses a lot of pins.

The first problem can be overcome by using an external power supply. The Micro:bit can only supply 3.3v. If you try powering a LCD screen from this, it will light up but if you can see anything at all, it will be very faint. There are a number of ways to supply 5v, one of the easiest is using a ‘3.3v 5v breadboard power supply module’. One of these comes with the Elegoo 37 sensor kit, but searching for the above description will find a number of other suppliers. These are usually quite cheap.

Supply the board with anywhere between 6.5v and 12v. A 5v and 3.3v will be supplied by the power output pins.

The second issue was the amount of pins used by the LCD module. The LCD requires 16 pins. While some of these are for power, plugging directly into the Micro:bit will not leave many free pins for other hardware. The easiest solution is to buy a I2C LCD module, pictured above the screen in the title image. To buy one, search for “I2C 1602 LCD”. You will often find screens with these already fitted.

Wiring it up

If you screen or your module has female headers, you can plug the module directly into the back of the screen. In the picture above, both the screen and the I2C module had male headers. Plug these into breadboard making sure the left most pin on the LCD (often marked ‘1’) lines up with the pin on the left of the module when it has it’s four pins on the side pointing out to the left.

From the power supply module, connect a 5v pin to Vcc on the I2C module and connect Gnd to a ground strip on the breadboard. You must connect this to Gnd on the Micro:bit. If you wish, you can connect a 3.3v pin from the power supply board to the 3v pin on the Micro:bit to power it, or you can power it via a serial cable.

The LCD interface module uses I2C, which is a common protocol that can be used to reduce connecting various modules to two wires each. So long as the devices have different addresses (don’t worry about this for now), you can connect multiple devices to I2C. The Micro:bit has two I2C pins to support this, 19 and 20. Connect SDA on the LCD module to pin 20 and SCL to pin 19.

Displaying text in your code

At the time of writing, there does not appear to be a common I2C LCD library, however ‘shaoziyang‘ has produced on at github, which works quite nicely (Thank you!).

The following code displays a hello message (to our dog) and counts up the seconds the Micro:bit has been running. If this does not work first time, check your wiring but also try changing LCD_I2C_ADDR from 63 to 39. Some modules use a different address:

from microbit import *
import time

LCD_I2C_ADDR=63

class LCD1620():
    def __init__(self):
        self.buf = bytearray(1)
        self.BK = 0x08
        self.RS = 0x00
        self.E = 0x04
        self.setcmd(0x33)
        sleep(5)
        self.send(0x30)
        sleep(5)
        self.send(0x20)
        sleep(5)
        self.setcmd(0x28)
        self.setcmd(0x0C)
        self.setcmd(0x06)
        self.setcmd(0x01)
        self.version='1.0'

    def setReg(self, dat):
        self.buf[0] = dat
        i2c.write(LCD_I2C_ADDR, self.buf)
        sleep(1)

    def send(self, dat):
        d=dat&0xF0
        d|=self.BK
        d|=self.RS
        self.setReg(d)
        self.setReg(d|0x04)
        self.setReg(d)

    def setcmd(self, cmd):
        self.RS=0
        self.send(cmd)
        self.send(cmd<<4)

    def setdat(self, dat):
        self.RS=1
        self.send(dat)
        self.send(dat<<4)

    def clear(self):
        self.setcmd(1)

    def backlight(self, on):
        if on:
            self.BK=0x08
        else:
            self.BK=0
        self.setdat(0)

    def on(self):
        self.setcmd(0x0C)

    def off(self):
        self.setcmd(0x08)

    def char(self, ch, x=-1, y=0):
        if x>=0:
            a=0x80
            if y>0:
                a=0xC0
            a+=x
            self.setcmd(a)
        self.setdat(ch)

    def puts(self, s, x=0, y=0):
        if len(s)>0:
            self.char(ord(s[0]),x,y)
            for i in range(1, len(s)):
                self.char(ord(s[i]))

lcd = LCD1620()
lcd.puts("Hello Benji!")
while True:
    lcd.puts("Running=" + str(running_time()/1000), 0, 1)
    sleep(500)

Functions in the library

FunctionUsage
clear()Clears the display
backlight(0 or 1)Setting the backlight to 0 turns the backlight off, 1 turns it back on again.
off()Turns the LCD off, thought the backlight stays on
on()Turns the LCD on
char(c,x,y)
char(65,x,y)
char(ord(‘@’),x,y)
Prints a single character at coordinates x,y
Prints the ASCII value 65 (capital A) at coordinates x,y
Using ord, prints @ at coordinates x,y
puts(s,x,y)
puts(“Hello World”, 0,1)
Writes a text string at coordinates x,y
Writes “Hello World” at the start of the second line.

Project: Rotary Encoder & Servo

Can we control a servo with a rotary encoder? Can we make it turn in the same direction as we turn the rotary encoder? What could we attach to the servo arm? Perhaps use this to control a crane or fork lift? Can it be attached to some lego to make something interesting?

Step 1: Set up the rotary encoder. Review Micro:bit Rotary Encoder and connect up the hardware. Run the test code to make sure the encoder is working.

Step 2: Can you turn this code into a function? Sit in the loop and call a function checkEncoder(), which will report back the direction (if any) it is being turned. Perhaps return 0 for stationary, 1 for turning clockwise and 2 for turning anti-clockwise. Consider returning two values to also report the state of the button.

Step 4: Connect the servo. Follow Micro:bit SG09 Servo and test the servo. Note, both examples use pin0 so you will have to make adjustments.

Step 5: Can you turn the servo in the same direction as the rotary encoder? You may need to write a function to tell the servo to move by X number of degrees. Try playing with different values for X and different sensitivities to control how fast the encoder turns the servo. Don’t forget to make sure you do not try to turn the servo beyond it’s limits.

Micro:bit Rotary Encoder

A rotary encoder, such as the common KY-040 above, is a knob that you can turn continuously either clockwise or anti-clockwise and detect which direction it is being turned. Types such as the KY-040 are known as incremental encoders. These will tell you the direction they are being turned, but can not report which direction they are pointing. The shaft can be pushed down to act like a push button. A common application for this is to navigate menu screens on devices such as 3d printers.

To connect a KY-040 to a Micro:bit, + and GND go to 3V and GND on the Micro:bit. The other three pins go to any digital input pins. In the following examples, SW goes to pin0, DT to pin1 and CLK to pin2.

For a detailed explanation of how rotary encoders work, see wikipedia. To use one, all you need to know is if CLK changes, then the encoder is being turned. If CLK is the same as DT (could be high or low), then it is being turned anti-clockwise. If CLK is different to DT it is being turned clockwise. Generally the sensitivity is changed with a sleep statement. The button on the top acts as a simple push button. The CLK and DT pins have their own internal resistors so we must disable the internal pull-up resistor to work. For a description about this, see Micro:bit Buttons. The one on the switch is still required.

This gives us a basic procedure to follow to read input from a rotary encoder:

  • Define the pins in use
  • Disable the internal pull-up resistors.
  • Read CLK and store this in a variable for the last known state of CLK.
  • Start a loop:
    • Read all three input pins
    • If CLK has changed since the last known state of CLK (lastCLK), the encoder is moving.
      • If CLK is not equal to DT it is moving clockwise
      • Otherwise it is moving anti-clockwise.
      • Wait, according to the sensitivity you want
      • Set lastCLK equal to CLK
    • Check for button press
    • Return to start of loop

In code form, this makes:

# Rotary encoder test
from microbit import *

RSW = pin0
RDT = pin1
RCLK = pin2

# KY-040 has its own pull up resistors.
# Disable the Micro:bit internal ones but
# ensure we use the one for the switch pin
RCLK.set_pull( RCLK.NO_PULL )
RDT.set_pull ( RDT.NO_PULL )
RSW.set_pull ( RSW.PULL_UP )

clkLast = RCLK.read_digital()

print ( "Ready to start" )
display.show ( Image.HAPPY )

while True:
    clk = RCLK.read_digital()
    dt = RDT.read_digital()
    sw = RSW.read_digital()

    if ( clk != clkLast ) :
        if ( clk != dt ) :
            print ( "Clockwise" )
            display.show(Image.ARROW_E)
            sleep(300)
        else:
            print ( "Anti-clockwise" )
            display.show(Image.ARROW_W)
            sleep(300)

        clkLast = clk
    else:
        # Has the switch been pressed?
        if (sw == 0) :
            display.show('X')
            sleep(500)
        else :
            display.clear()

There is a problem with this approach. If you try it and watch either the arrows or serial lines, you find while turning the encoder, you occasionally get the wrong direction reported. This is known as ‘switch bounce’ and is often caused by cheaper components in encoders such as the KY-040. There are many ways to look at solving this, either in hardware using capacitors or in code. In a few tests with capacitors, I did not find a significant difference. However I always found that the wrong direction was reported once in a row, never twice.

A ‘quick and dirty’ solution is to wrap up the detection into a function which takes a number of readings then reports back which direction was detected the most. This does mean it will take a little longer to react to a turn or change in direction, but only by a fraction of a second. In the code below, the function readEncoder returns a pair of values for the direction and the state of the switch. A 0 represents no movement, 1 for clockwise movement and -1 for anti-clockwise.

In python, you can build lists of values in brackets with a comma, e.g. a=(2, 4, 8). To access the first element of the list, use square brackets, a[0]. For the third value, a[2]. This allows us to return the direction as the first value and the switch state as the second. The function in the below code takes a few attempts to read the encoder direction and returns a pair of values, the first for the direction and the second for the switch state.

# Rotary encoder test
from microbit import *

RSW = pin0
RDT = pin1
RCLK = pin2

# KY-040 has its own pull up resistors.
# Disable the Micro:bit internal ones but
# ensure we use the one for the switch pin
RCLK.set_pull(RCLK.NO_PULL)
RDT.set_pull(RDT.NO_PULL)
RSW.set_pull(RSW.PULL_UP)

def readEncoder():
    # Return a pair of values direction and switch state
    #   0 = no movement
    #   1 = clockwise
    #   2 = anti-clockwise
    
    # Set sensitivity - how long we wait between readings in ms
    sens = 50
    # Accuracy is how many checks we perform. This should at least
    # be 3. Note that the more checks, the longer it will take and
    # the greater the chance of the direction changing while we checks
    acc = 5
    
    # Record the last known state of the CLK line
    clkLast = RCLK.read_digital()
    
    # Set direction counters, clockwise and anti-clockwise
    cw = 0
    ac = 0
    
    # Checking loop
    for i in range(0, acc):
        # Read data from the encoder
        clk = RCLK.read_digital()
        dt = RDT.read_digital()
        sw = RSW.read_digital()
        
        if(clk != clkLast):
            # print ("clkLast = "+str(clkLast)+" - clk = "+str(clk)+" - dt = "+str(dt))
            if(clkLast != dt):
                # Clockwise, add 1 to clockwise count
                cw = cw+1
            else:
                # Anti-clockwise, add 1 to anti-clockwise count
                ac = ac+1

            # Record the last known state of the clock
            clkLast = clk
            # Wait according to sensitivity setting
            sleep(sens)
            
    # Initially assume the direction is 0
    d = 0
    # Check the clockwise and anti-clockwise counters
    # If they are the same, no change to d is made. We assume
    # the encoder is not moving
    if(cw > ac):
        # More clockwise movement than anti-clockwise
        d = 1
    elif(ac > cw):
        # More anti-clockwise movement than clockwise
        d = -1

    # Return data as a pair
    # We can return the value of the switch directly
    return (d, sw)
# End of readEncoder

print("Ready to start")
display.show(Image.HAPPY)
sleep(1000)

# Main loop
while True:
    r = readEncoder()
    
    # Check the direction and report. The screen
    # will show the last recorded direction
    if(r[0] == 1):
        # Clockwise movement
        print("Clockwise")
        display.show(Image.ARROW_E)
    elif(r[0] == -1):
        # Anti-clockwise movement
        print("Anti-clockwise")
        display.show(Image.ARROW_W)
     
    # Check for a switch press
    if(r[1] == 0):
        display.show('X')
        sleep(500)
        display.clear()

Micro:bit Buttons

About Buttons

Microcontrollers such as the Micro:bit all do one similar thing, accept an input (some sort of ‘message’ from the outside world), process (run some code) and output (show something to the outside world). The most common type of input everyone uses is a button.

What is a button? Essentially a button is a couple of bits of wire or metal plate that touch together when pressed. This allows electricity to flow between the connectors at either side, making the connection. There are a few different types of button each with different properties. Some stay on or stay off after being pressed (or toggled), like a light switch. Some only make a contact when the button or lever is being moved and some you push to break the circuit. The types we are looking at in this tutorial are momentary push buttons, which only make a connection while they are being pressed. The standard circuit symbols below shows a little about how they work.

There is a connection at either side, when the buttons are pressed, the two connections are joined and electricity flows.

Micro:bit Onboard Buttons

The easiest two buttons to use are the two build onto the Micro:bit board itself, labelled A and B. There is a lot of information on the Micropython tutorial page about using these. When using the microbit library, two objects are defined, button_a and button_b. You can check if these are pressed with the is_pressed() function. The following code loops checking for a button press then points an arrow to the last pressed button:

from microbit import *

while True:
    if ( button_a.is_pressed() ) :
        display.show(Image.ARROW_W)
    if ( button_b.is_pressed() ) :
        display.show(Image.ARROW_E)

While quick and easy to use, this does not help us add other buttons. By wiring in a button, it will not automatically create a button object. External buttons are connected to pins, but consider the Micro:bit pinout diagram. When you look at this, the buttons A and B are connected to pins 5 and 11. For us, this means two things. First is that when connecting hardware, if we want to use buttons A and B, we should not use pins 5 and 11 for anything else. But it also means we can read the buttons as if they were digital pins. When we read digital pins, the result can be a 1 or a 0. The following will show on the display the value of the A pin, pin 5:

from microbit import *

while True:
    display.show( pin5.read_digital() )

Try it. What does the value change to when you press the button? We often think of 1 as being on and 0 as being off, but find this works the other way round. There is a reason for this which will be revealed below. For now, as long as we are happy that 0 means a button is being pressed, if we want to rewrite the first program to use pin numbers, we can write it as:

from microbit import *

while True:
    if ( pin5.read_digital() == 0 ) :
        display.show(Image.ARROW_W)
    if ( pin11.read_digital() == 0 ) :
        display.show(Image.ARROW_E)

External Buttons

We can add external buttons to the Micro:bit to give a larger number of buttons, or to use different types of buttons. One end can connect to a data pin, but what do we connect the other end to? Consider the circuit diagram below:

pin0 is said to be ‘floating’. It is neither connected to ground (0v) or the positive 3v. If we read the value of this, it could be anything. Putting an open button on it would leave it in the same state. Imagine a kite free to fly in the wind. If we pin it to the ground, then we know it is on the ground. If it were possible to pin it into the air, again we would know where it was. Anything else, it is at the mercy of the wind and can keep flying and crashing all day long.

Electronic inputs are the same. Unless we decide to ‘pin’ them to something, we can not predict what state they would be in. In practice, I have found an unconnected Mirco:bit input always seems to be zero. The same is not true of other micro controllers, such as a Raspberry Pi or an Arduino, so it is good practice to make sure you know what state your inputs are in. This is done with the use of pull-up or pull-down resistors.

In the diagram on the left, we are using a pull-down resistor. (10k ohm is a common value for this.) When the button is not pressed, the resistor connects pin0 to ground, meaning a read_digital function will always return 0. However when the button is pressed, it connects pin0 to +3v, which the Micro:bit reads as 1. The resistor limits how much of the current can flow between +3v and GND, leaving a higher current to be detected on pin0.

In the diagram on the right, we do the opposite. pin0 is pulled high (read as 1), unless the button is pressed, at which point it is pulled low (read as 0). This means in our code, we detect a button press as a 0 and no button press as a 1.

The Micro:bit has a built in pull-up and pull-down resistors that you can turn on in the code, using the command set_pull. If you want to use your own external resistors with buttons, then you must disable these:

pin0.set_pull(pin0.NO_PULL)

However, as they are there it is best to make use of them. If you remember, reading the internal buttons give a 1 when not pressed and a 0 when pressed, meaning they must be using the internal pull-up resistor. If we use external buttons on pins 0 and 1, we can use the same code again.

from microbit import *

# Set the pull up state for the internal resistors
pin0.set_pull(pin0.PULL_UP)
pin1.set_pull(pin1.PULL_UP)

while True:
    if ( pin0.read_digital() == 0 ) :
        display.show(Image.ARROW_W)
    if ( pin1.read_digital() == 0 ) :
        display.show(Image.ARROW_E)

Button Modules

You can also use button modules such as the one in the middle of the picture above. Going from left to right, pin S connects to one of the IO pins of the Micro:bit, the middle to 3v and the right pin to GND. If you look closely, you can see a small black rectangle labelled 103 connecting S and 3v. This is a small pull-up resistor. As this has its own resistor, we need to make sure we disable the internal resistor. We can then check the button state after connecting it to pin2.

from microbit import *

# Disable the internal pull up
pin2.set_pull(pin2.NO_PULL)

while True:
    display.show ( pin2.read_digital() ) 

Button modules may come on their own or be included as part of a joystick module, game controller package, etc. If the instructions do not say if a pull-up or pull-down resistor is used, you should be able to check with the the above code.

Micro:bit SG09 Servo

SG09 servo

Servos are small motors with gearing that can add a high amount of torque (turning strength), and reliably turn to a precise angle. Servos such as the SG09 are small, cheap, run on low power and can rotate around 180 degrees, or a half circle. This makes them different to a normal motor which will spin round. You can commonly use this for things that need to turn by small amounts, such as a joint in a robotic arm, to lift the arm of a barrier or to steer a radio controlled car.

The SG09 only requires 3 connections two for power and one for data/control, but it does require 4.8v to operate. As the Micro:bit can only produce low current and 3v, an external battery pack is required. In the above wiring, brown is ground, red is +4.8v and orange is control. Connect a 3xAA battery pack to the two power pins and control to one of the Micro:bit pins, as shown below. Note a 4xAA battery pack is shown on the diagram.

There are a number of pages on controlling the SG09 with a Micro:bit, however none of these worked when I tried this. The servo operates by receiving pulses of 50Hz (50 pulses per second). If we divide one second by 50 pulses, we get a time of 20ms between pulses. We start our code by defining this. Next, if we write the analog value 1 to the servo, it will turn all the way to it’s clockwise limit. (If you send a 0, it does nothing.) Sending a high enough value will turn the servo the other way.

If you try a value of 150 or higher, you will feel the servo straining beyond it’s limits and you could damage it. Start with a low value for maxLimit in the following code (say 80) and gradually increase it until the motor just reaches it’s anti-clockwise limit. I found 122 to be a figure which does not strain the servo but reaches the other side.

from microbit import *

servo = pin0

# Motor requires a 50 Hz signal. 1 second / 50 gives 
# a pulse every 20 ms. Set this as the analog period
servo.set_analog_period(20)

maxLimit = 122


while True:
    servo.write_analog(0)      # Set the servo to 90 degrees - the middle
    sleep(500)
    for i in range(10,maxLimit):
        servo.write_analog(i)    # Turn the servo to the clockwise limits, 0 degrees
        sleep(50)
 

As the servo moves between 0 and 180 degrees, and values between 1 and 122 causes this movement, we can work out what value to analog write to achieve by dividing the angle we want by 180 then multiplying this by 122. We can wrap this up in a function and then call it to move the servo to a desired angle. The below code moves the servo to its limits and back again, like a windscreen wiper.

from microbit import *

servo = pin0


def servoAngle(s, a) :
    maxLimit = 122

    # Set the values into a sensible range
    # 1 is the smallest value
    if a < 1 :
        a = 1
    # Max limit is the highest value
    if a > 180 :
        a = 180
        
    v = (a/180) * maxLimit
    s.write_analog(v)

# Motor requires a 50 Hz signal. 1 second / 50 gives 
# a pulse every 20 ms. Set this as the analog period   
servo.set_analog_period(20)

while True:
    servo.write_analog(1)
    sleep(1000)
    #servo.write_analog(122)
    servoAngle(servo, 180)
    sleep(1000)

    

Note that this angle is an approximation and not exact. The design specs of the servo detail exactly what sort of pulses to send for precise control. This gives a very quick and simple way of achieving something close to what we require for a lot of applications.