Tuesday 25 December 2018

Coding the BinaryBots Totem sensor board with MakeCode

Introduction

CBisEducation is a relative newcomer in the world of STEM education. Their first products were BinaryBots Dimm and UFO cardboard robots, a robotic arm and a remote controlled car, all using the BBC micro:bit. The newest kits in the BinaryBots range are the three Planet Totem kits - Crab, Spider and Tortoise. All three kits have the same electronics – just the mechanics differ between them.

The Totem custom-designed boards are made for the BBC micro:bit and comprise a “power board” and a “sensor board”.

The power board consists of an edge connector into which you slot the micro:bit, a 3xAAA battery holder, three motor ports (one of which controls the crab's claw) and an on/off switch. The sensor board contains 4 RGB LEDs, a piezo-sounder, vibration motor, a light-level sensor, temperature sensor and two touch button/slider sensors.

Whether you follow the instructional videos on the BinaryBots website, or use the booklet provided (“Inventors Manual”), the first bit of coding you will do will be to make the claw open and close using the excellent MakeCode for micro:bit block-based coding environment.

As far as I could see, after that, you are on your own unless you then move to the Python environment as there are no further examples or tutorials using MakeCode. Specifically, there is no way to control any of the components on the sensor board using MakeCode blocks. For younger children, this could be disappointing, as they would want to make use of the sensor board with its RGB LEDs, vibration motor and piezo-sounder. It could be quite a big leap to go straight into Python coding. This blogpost is about how to control the sensor board with MakeCode.

Hardware description

Power board

Rather than use a small servo to control the positioning of the claw pincers, BinaryBots have opted for a standard DC micro-metal motor which uses a worm gear to slow it down (and increase torque). One micro metal motor is provided in the kit. This operates one of the crab’s claws (the other claw is fixed). Motor port 1 on the power board connects to pins P13 & P14 on the inserted micro:bit. Pins P19 & P20 on the micro:bit are reserved for the I2C port. The three most commonly used pins (P0, P1 AND P2) plus 3V and GND and brought out onto banana plug terminals at the front of the power board.

Power board pin assignments
Board function Micro:bit pins
Motor 1 Pins P13 and P14
Motor 2 Pins P15 and P16
Motor 3 Pins P8 and P12
Pin 0 (front of board) P0
Pin 1 (front of board) P1
Pin 2 (front of board) P2
3V (front of board) 3V
GND (front of board) GND

Sensor board

The sensor board is actually a standalone microcontroller board based on the STM32F051K6 ARM Cortex-M0 Access line MCU with 32 Kbytes Flash and 48 MHz CPU. The micro:bit communicates with the sensor board CPU via I2C and the sensor board CPU, in turn, communicates with its own sensors and actuators and passes the data back along the I2C bus to the micro:bit. This means that the micro:bit only needs to know the I2C address of the board – not of the individual sensors and actuators. The firmware on the board listens on the I2C bus and sends the data requested. The touch button/slider sensors are quite complex; along each strip there are three “button points” and 100 “slider points” that can be detected.

Coding with Python

BinaryBots have provided several examples of Python code on their website [https://www.binarybots.co.uk/activities] to move the crab’s pincers, control the LEDs and read the touch and temperature sensors. For beginners, I think it would have been more helpful if BinaryBots had written a Python module with functions to which you can pass parameters, rather than having to use the ‘struct’ data format, which can be quite complicated.

The Python struct.pack function converts all the data into two formats (‘B’ for 8-bit data and ‘H’ for 16-bit data).

The board uses command numbers to identify which board device to use. Here are all the valid commands and data expected:
Function
Command number
Data Format
LED
0
‘<BBBBB’
Buzzer
1
‘<BBH’’
Vibrator
2
‘<BHB’
Read Buttons
4
‘B’
Read Sliders
5
‘B’
Read Light sensor
6
‘B’
Read Temperature sensor
7
‘B’
Reset
8
‘<BB’


 Where there is more than one byte, the ‘<’ symbol signifies that the byte values are to be passed in ‘little-endian’ order. In the case of the LEDs, the five bytes (BBBBB) are:

[0],[LED#], [R], [G], [B].


LED# can take one of five values (one for each LED or 0 for all four). For example, to turn all four sensor board LEDs green, the example Python code is:

i2c.write(42,struct.pack('<BBBBB',0,0,0,255,0))

Although the sensor board has a light sensor and temperature sensor, there is no documentation provided on either sensor, apart from some sample code. Although BinaryBots provide some sample code for reading these sensors, they do not provide any information on what the numbers represent. The Python code for the light and temperature sensors suggest that the outputs are 16-bit signed integers (0-1023) so it appears they are analogue sensors. The light sensor is clearly visible and marked on the board but there is no sign of a temperature sensor. I conclude that the temperature reading is actually the ARM Core CPU temperature, which is supported by the fact that placing my finger directly onto the MCU caused the reading to rise.


Here is the micropython code for reading the temperature sensor and printing the raw analogue value via the USB cable to the PC console (REPL):

from microbit import * 
import struct 
sleep(150) 
while True: 
     temp = i2c.write(42,struct.pack('B',7)) 
     sleep(150) 
     i2c.write(42,struct.pack('B',7)) 
     sleep(10) 
     rx = i2c.read(42,2) 
     temp = rx[0] + (256 * rx[1])  
     print (temp)

Substituting (42,struct.pack('B',6)) with (42,struct.pack('B',7)) will print the raw light sensor readings, instead of the CPU temperature, to the REPL.

Coding with MakeCode

As discussed in the Introduction, MakeCode does not provide an easy way of controlling the sensor board. Ideally, BinaryBots would have done what other companies like Seeed Studios have done, and produced a MakeCode extension so that blocks were available for controlling the sensors and actuators on the sensor board. Although there are no MakeCode blocks available that can control the sensor board, the MakeCode editor provides a Javascript/Typescript coding window. By converting the Python code into Javascript, it is possible to control the sensor board with MakeCode.

The equivalent Javascript of the example given above to turn on all four LEDs in a green colour...

i2c.write(42,struct.pack('<BBBBB',0,0,0,255,0))

is

let buf = pins.createBuffer(5); 
 buf.setNumber(NumberFormat.Int8LE, 0, 0); 
 buf.setNumber(NumberFormat.Int8LE, 1, 0); 
 buf.setNumber(NumberFormat.Int8LE, 2, 0); 
 buf.setNumber(NumberFormat.Int8LE, 3, 255); 
 buf.setNumber(NumberFormat.Int8LE, 4, 0); 
 pins.i2cWriteBuffer(42, buf, false);

The first line sets up a 5 byte buffer ‘buf’. The next five lines put 5 bytes into the buffer and the last line writes the buffer ‘buf’ to device address 42 on the i2c bus (i.e. the sensor board). Two of the components on the sensor board (the buzzer and the vibrator) require 2 bytes plus one word (i.e. 4 bytes in total). In those cases, the word data format is NumberFormat.Int16LE.

If you go back to the Blocks window, you'll see something like this:



As can be seen, the Python code is much more economical. However, I wanted to use MakeCode because I have not yet found a reliable way to use the Grove Ultrasonic Ranger in micropython and I wanted one of those to make my Crab more entertaining. Also, I wanted to show that it is possible!

To save some typing and make my code more efficient, I created some MakeCode functions out of the Javascript. Once the Javascript functions have been defined, MakeCode turns them into blocks, as per the image below, which sends commands to the sensor board to make some noises with the piezo sounder.



Close

Once you have created the custom blocks and functions that you need using Javascript, you can then go back to the Blocks window and use them with the normal MakeCode blocks. The screenshot below shows a load of my custom blocks/functions combined with other blocks, including Seeed Studio blocks to control their 'ultrasonic ranger', as well as a neopixel ring.

Custom blocks

Although it takes a little while to work out the correct Javascript to do what you want with the sensor board, it can be done! Hopefully, with the information provided here, other people will be able to do the same.


Thursday 12 July 2018

Using an I2C backpack with 16 x 2 LCD screen and a micro:bit

Introduction

I couldn't resist buying some I2C LCD backpacks (based on the PCF8574 remote 8-bit i/o expander chip) from China recently as they were less than £1 each. 

They work fine at 3.3v as well as 5v, so I thought they'd be great for the 16 x 2 LCD screens that I have converted to run at 3v

I then discovered that someone in the Chinese micro:bit community had written an I2C LCD package for MakeCode for micro:bit. That makes everything very easy!

Connecting things up

I usually fit a 16 x 1 female socket to my LCD displays, as this makes it very easy to connect to various backpacks, or indeed to a breadboard using jumper wires. The backpacks I bought came with male header pins already soldered in place so it was just a matter of pushing the backpack into place on the LCD display's female socket strip. Remember that you need to use a 3v LCD screen with the micro:bit.

To connect the backpack to a micro:bit, you need access to the I2C pins by using an edge connector  breakout board for the micro:bit such as the bread:bit edge connector board from Proto-Pic. Connect the VCC pin on the LCD backpack to 3v on the micro:bit, GND to GND, SCL to pin 19 on the micro:bit and SDA to pin 20. 

Coding with MakeCode for micro:bit

Before you start, you need to know the I2C address for the LCD backpack. By default it is set to 0x3F (hex) or 63 in decimal. You can change its I2C address by shorting out one or more of the three pairs of pads A0, A1 and A2 on the back of the backpack, just below the contrast potentiometer. Refer to Table 5 of the datasheet for details. (n.b. there is a different variant of this backpack whose default address is 0x27. If you have this one, refer to Table 4 instead).

Armed with that knowledge, now open MakeCode on your browser. You will need to "Add Package" from the middle palette, then cut and paste the following URL into the box that appears: https://github.com/microbit-makecode-packages/i2cLCD1602 . Now you should see a new (green) entry in the middle palette (I2C_LCD1602) which, if you click on it, reveals the blocks you need to control the LCD. The code below just uses a loop to display an incrementing number on the screen.
















That's the easiest (and cheapest) way I have found yet to add an LCD screen to a micro:bit.

Sunday 27 May 2018

Using an 8 x 7-segment SPI display with the BBC micro:bit


Introduction
I got sent ten (!) of these MAX7219 displays by accident when I had ordered something completely different on Aliexpress, so I thought I'd better learn how to use them. As usual, there are some Arduino libraries for these, and also for the Raspberry Pi but I could only find one example for the BBC micro:bit. That example is a mashup of two different drivers but, when I tried it, I found that quite a lot of things didn't work properly. In any case, I don't really like just plugging in someone else's driver because I prefer to understand what is going on "under the hood". Also, using modules with the micro:bit is a bit of a pain as it's a two-stage process which slows down program development and debugging.

So, armed with the MAX7219 data sheet, I decided to work things out for myself.

Connecting things up
This is very straightforward. The display has five pins. Apart from VCC and GND (it's 3.3v-friendly), there is DIN (data in), CS (chip select) and CLK (clock). As it is an SPI device, the default connections on the micro:bit are CLK=P13 and DIN=P14 (although these are just the defaults - you can use any pins). In addition, you'll need to connect CS to any micro:bit pin (I used P0).

Micropython code
The good news is that testing your connections is easy because the MAX7219 has a test mode which overrides any other settings so it should work every time. The photo above shows "test mode", where each segment of each digit is lit up at full intensity. The MAX7219 is programmed by sending 16 bits, of which the 4 "most significant bits" are ignored, the next 4 bits are the register addresses and the 8 "least significant bits" are the data to send to the registers. Referring to page 10 of the data sheet, to enable the "display test" mode, we need to send the register address 0xXf (where X is "don't care), followed by 0x01. Try this (using the Mu editor):

from microbit import * 
spi.init(baudrate=1000000,bits=8,mode=0, sclk=pin13, mosi=pin15, miso=pin14) #setup SPI
spi.write(b'\x0f\x01')#enable test mode

Whoa! That doesn't work though. The reason is that (to send anything to the display) you need to take the chip select pin (P0) low, then send the data, then take the chip select pin high again. So, this should work:

from microbit import * 
spi.init(baudrate=1000000,bits=8,mode=0, sclk=pin13, mosi=pin15, miso=pin14) # setup SPI
pin0.write_digital(0) # take CS pin low
spi.write(b'\x0f\x01')# enable test mode 
pin0.write_digital(1) # take CS high to latch data

That's the easy bit! The tricky thing I found about using this display is that (apart from "display test" mode), nothing will work as you want it to, unless you make some essential settings. The data sheet explains what each of the registers is for, but it took me a while to realise that nothing much works unless you configure the following registers as below:
  • Test mode (0x0f) set to off (0x00)
  • Decode mode (0x09) set to off (0x00)
  • Set "scan limit" (0x0b) for 8 columns (0x07)
  • Shutdown register (0x0c) enabled (0x01)
  • Intensity register (0x0a) set to something between 1 (0x01 - dim) and 15 (0x0f - very bright)

In addition to that problem, some of the settings are persistent after a power-down, so you also need to clear all the registers to start with (because there is no way to find out what the existing settings are). So, the things to remember are that you need:

  1. Before sending commands/data, to take CS low, then send your commands/data, then take CS high.
  2. To clear all the registers so you start with a "clean slate".
  3. To make the essential settings.
  4. Then to send the data you want to display.
Here's an example program showing how to put it all together. In the example, I have used a python dictionary to hold the bit patterns for all the numbers and most of the letters of the alphabet.




from microbit import *
spi.init(baudrate=1000000,bits=8,mode=0, sclk=pin13, mosi=pin15, miso=pin14) #setup SPI
# initialisation code

pin0.write_digital(0)
spi.write(b'\x0f\x00')#enable normal mode (disable test mode)
pin0.write_digital(1) #latch data
sleep(300)

# zero-out all registers
for cmd in range(16):
    pin0.write_digital(0)
    packet = cmd << 8
    # e.g., if cmd is 0101, 0101 << 8 becomes 010100000000
    spi.write(bytearray(packet))
    pin0.write_digital(1)

# set some essential parameters
pin0.write_digital(0)
spi.write(bytearray([0x09,0x00])) #enable no decode
pin0.write_digital(1)

pin0.write_digital(0)
spi.write(bytearray([0x0b,0x07])) #enable 8 cols
pin0.write_digital(1)

pin0.write_digital(0)
spi.write(bytearray([0x0c,0x01])) # enable shutdown register
pin0.write_digital(1)


# set intensity
pin0.write_digital(0)
spi.write(b'\x0a\x0f')# set intensity to 15 (max)
pin0.write_digital(1) #latch data

# dictionary of bit patterns
# n.b. decimal point can be added to any character by adding 128 to its decimal value
DIGITS = {
    ' ': 0,
    '-': 1,
    '_': 8,
    '\'': 2,
    '0': 126,
    '1': 48,
    '2': 109,
    '3': 121,
    '4': 51,
    '5': 91,
    '6': 95,
    '7': 112,
    '8': 127,
    '9': 123,
    'a': 125,
    'b': 31,
    'c': 13,
    'd': 61,
    'e': 111,
    'f': 71,
    'g': 123,
    'h': 23,
    'i': 16,
    'j' : 24,
    # 'k' Can't represent
    'l': 6,
    # 'm' Can't represent
    'n': 21,
    'o': 29,
    'p': 103,
    'q': 115,
    'r': 5,
    's': 91,
    't': 15,
    'u': 28,
    'v': 28,
    # 'w' Can't represent
    # 'x' Can't represent
    'y': 59,
    'z': 109,
    'A': 119,
    'B': 127,
    'C': 78,
    'D': 126,
    'E': 79,
    'F': 71,
    'G': 94,
    'H': 55,
    'I': 48,
    'J': 56,
    # 'K' Can't represent
    'L': 14,
    # 'M' Can't represent
    'N': 118,
    'O': 126,
    'P': 103,
    'Q': 115,
    'R': 70,
    'S': 91,
    'T': 15,
    'U': 62,
    'V': 62,
    # 'W' Can't represent
    # 'X' Can't represent
    'Y': 59,
    'Z': 109,
    ',': 128,
    '.': 128,
    '!': 176,
}

def write_segs(col,char): # turn on the relevant segments
    pin0.write_digital(0)
    spi.write(bytearray([col,char]))
    pin0.write_digital(1) #latch data

def letter(charx): # Look up character in DIGITS dictionary & return
    value = DIGITS.get(str(charx))
    return value

def blank_cols(): # blank out all columns
for col in range(8):
pin0.write_digital(0)
spi.write(bytearray([col+1,0x00])) # range is 0-7, cols are 1-8!
pin0.write_digital(1)

def write_str(disp_str): #
len_str=len(disp_str) # find length of string
if len_str>8:
len_str=8 #truncate if too long for display
c=len_str #start column
for x in range(len_str):
n=disp_str[x]
write_segs(c,letter(n))
c-=1 #Next (i.e. previous) COLUMN

################################################################ now send some stuff to the display!
while True:
    blank_cols() # blank all columns
    write_str('Hello  ') # write another string
    sleep(2000)
    write_str('there  ') #write a string
    sleep(2000)
    for displ_count in range(1,500): #count up from 0 to 500
        write_str("{0:0=8d}".format(displ_count)) #turn into 8-digit str with leading spaces# 

Tuesday 8 May 2018

MCP9808 digital temperature sensor, BBC micro:bit and LCD screen


Introduction
In my previous post, I described how to connect and code the Microchip MCP9808 digital temperature sensor. That allowed you to send accurate temperature data via the REPL to a connected PC.

In this post, I shall describe how to send the temperature readings to an LCD screen. This could be the basis of a BBC micro:bit weather station or data logging project, or maybe for environmental control. N.B. I wasn't going to risk leaving my micro:bit in the freezer long enough to see how cold it really is - apart from anything else, I doubt whether the LCD would be too happy at -18 °C!


I came across a post on the excellent MultiWingSpan website showing how the micro:bit could be used with a Sparkfun Serial Enabled 3.3V 16x2 LCD Character Display. Unfortunately, those displays are quite expensive (around £24), whereas you can pick up a parallel (5v) 16x2 LCD screen for as little as £2. If you already have one of the Sparkfun ones or don't mind buying one, you can skip the next section.

So how can we make our own (cheaper) equivalent to the Sparkfun serial LCD?
The microbit is a 3.3v logic level device, so first we need an LCD screen that will work at that voltage. You can either pay a bit more and get a 3.3v screen (but they are hard to come by in the UK), or convert a 5v one, like I described in an earlier post.

Next, we need a serial UART LCD backpack like the Sparkfun one. I happened to have bought one from Proto-Pic in a sale last year for the princely sum of £2.40. They have discontinued that now but Hobbytronics in the UK sell a similar one (and, as a bonus, it also works over I2C) for £6. All of these backpacks have their own microprocessor that converts serial UART data into the correct parallel format for any display based on the Hitachi HD44780 compatible interface.

Adding up the cost of components (using the Hobbytronics backpack), it comes to about £9.

Connecting things up
Please refer to my previous post for connecting and programming the MCP9808 digital temperature sensor. Also have a look at the MultiWingSpan post for how to use the serial backpack. Depending on which serial backpack you are using, you may need the datasheet. My Proto-Pic one was very similar to the Sparkfun one but not identical so, if you find characters not appearing on the right row or column, that could be the reason. For convenience, I connected the data connection on my backpack to micro:bit pin 12 - the same as in the MultiWingSpan post.

Micropython code
I borrowed MultiWingSpan's code for the function "move_cursor(row,col)" for sending data to the serial backpack as it is very neat! I have extended it to work with 20x4 displays as well as 16x2. As mentioned above, the Proto-Pic backpack uses a slightly different addressing scheme from the Sparkfun display so I had to adjust the numbers a little as characters weren't coming out in the right place. I also had to look up in the HD44780 datasheet how to send special characters such as the degree symbol ° (decimal 223).

from microbit import *
# set up screen
# command set 1
bl_full = [254,02,255] # backlight on full
bl_half = [254,02,80] # half
bl_off = [254,02,0] # off
clr_scr = [254,01]

# command set 2
col_wide = [124,03] # 20 cols
col_narr = [124,04] # 16 cols
rows_four = [124,05] # 4 rows
rows_two = [124,06] # 2 rows

# choose cursor position - row 0,1,2,3 col, 0-15
def move_cursor(row,col):
    cmd = [254,128]
    if row==1:
        cmd[1]+=16 # adds 16 to cmd and stores it back
    if row==2:
        cmd[1]+=40 # adds 40 to cmd and stores it back
    if row==3:
        cmd[1]+=60 # adds 60 to cmd and stores it back
    cmd[1]+=col    
    uart.write(bytes(cmd))
    sleep(100)
  
# define character code for degree symbol
deg_symb = [223] #from HD44780 datasheet

# wait half a second for the splash screen
sleep(500)

# initialise uart
uart.init(baudrate=9600, bits=8, parity=None, stop=1, tx=pin12)
uart.write(bytes(rows_two)) # set 16x2
uart.write(bytes(col_narr)) # set 16x2
uart.write(bytes(bl_full))  # set backlight to full brightness

# clear display
move_cursor(0,0)
uart.write(bytes(clr_scr))
sleep(200)


# wait half a second for the splash screen
sleep(500)

# n.b. cursor is still at 0,0
uart.write("Temp.: ")
# sleep(50)
move_cursor(0,11)
uart.write(bytes(deg_symb))
uart.write("C")
move_cursor(1,0)
uart.write("This is line two") # change this to something useful!
move_cursor(0,7) #move cursor back again for temp. data
##########################################################
# Measure temperature and send to LCD
while True:
    i2c.write(0x18,bytearray([0x05])) # select ambient temp. register
    sleep(200) #  small pause needed to allow the device to update
    t_amb=i2c.read(0x18,2) # read two bytes from the ambient temp. register
    t_hi = (t_amb[0] & 0x0f) << 4 # mask upper 4 bits (alarm settings)
    t_lo = t_amb[1] / 16          # lower 8 bits (LSB)
    if t_amb[0] & 0x10 == 0x10: # take twos complement for -ve readings
        temp = (t_hi + t_lo) - 256.0
    else:
        temp = t_hi + t_lo
    uart.write('%.1f' % temp)  # display to 1 dec place
    move_cursor(0,7) #move cursor back again ready for next reading
    sleep(500) 
uart.init(115200) # reset uart

Monday 7 May 2018

Using an MCP9808 digital temperature sensor with the BBC micro:bit

Introduction
MakeCode for the BBC micro:bit has a Temperature block that provides a temperature reading in °C. There are two problems with this:
  1. The block infers the temperature from the temperature of the silicon die on the main CPU. This tends to overestimate the temperature due to self-heating of the CPU and the degree of self-heating depends on the load on the processor. Typically, the reading will be 3 degrees Celsius higher than the ambient temperature.
  2. You might, in any case, want to monitor the temperature somewhere other than where the micro:bit is situated (e.g. outside).
The way around this is to connect a separate temperature sensor to the micro:bit. Several tutorials exist for doing this, but the ones I have seen are all for analogue devices such as the TMP36 where the voltage output by the sensor is proportional to the temperature. These are relatively low-precision devices (accurate to ±1°C at +25°C).

I wanted to use a more accurate, digital temperature sensor and found the Microchip MCP9808, which has a typical accuracy ±0.25°C from -40°C to +125°C. This uses the I2C bus and works over a 2.7V ~ 5.5V logic level voltage range. Another possibility would be the Maxim/Dallas DS18B20, which uses a "one-wire protocol" but it takes at least 0.75 secs for each conversion, plus I haven't found out an easy way of using this with the micro:bit. In contrast, the MCP9808 is very easy to use, has a conversion time of only 250ms at maximum resolution and doesn't interfere with any other devices. The easiest way to use it with the micro:bit is to buy a breakout board that exposes its connections via breadboard-friendly pins. Adafruit makes a popular MCP9808 breakout board but I just went for a cheap Chinese board (costing around £2.50) as they do the same job.


The I2C protocol is a serial protocol that allows different data to be sent to different devices along a "bus". Each device must have its own address on the bus. The sensor has a default I2C address of 0x18 but this can be changed to one of seven other addresses by connecting one of more of the additional connections A0, A1, A2 to VCC, via the inbuilt resistors. This would allow you to have up to eight temperature sensors running from the same micro:bit, and controlled separately. See my previous blog posts on using I2C devices with the micro:bit.

Connecting the sensor to the micro:bit
Although there are eight pins, we need just four connections to get going. 

VCC to 3v on the micro:bit, GND to GND, SCL to pin 19 on the micro:bit and SDA to pin 20. To access pins 19 and 20, we need an edge connector breakout board for the micro:bit such as the bread:bit edge connector board from Proto-Pic.

The other four pins on the MCP9808 are for changing its I2C address and a pin that can be used as a trigger if, for example, certain temperature thresholds are reached. 

The micro:bit already has internal pullup resistors on the I2C lines so no other components are needed if you just want to measure temperature and are happy with the default I2C address.

Configuration
By default, the sensor's I2C address is 0x18, its resolution is +0.0625°C and its conversion time is 250ms. For the purposes of this guide, we will leave these defaults as they are.

Micropython code
You'll need the excellent Mu editor for this, and you must also have REPL set up because we will be sending the temperature readings from the micro:bit via the programming cable to the REPL window in Mu. If you have  Windows PC, you'll need to install the driver for this (download it from the Mu website).

The micro:bit implementation of I2C in micropython is slightly different from other boards. The I2C bus does not need to be separately initialised unless the default parameters are unsuitable for the device you wish to connect. As with some other I2C devices, the required configuration registers are written to, then data is read from the bus. In our case, we tell the MCP9808 what function we require by an "i2c.write" to the relevant register address (in this case 0x05 - the ambient temperature register). Then we "i2c.read" a specified number of bytes.

The datasheet for the MCP9808 explains that the temperature is stored as two bytes (one word). The digital word is loaded to a 16-bit read-only ambient temperature register that contains 13-bit temperature data in two’s complement format. The other 3 bits (i.e. bits 7, 6 and 5 of the first byte) contain the alert temperature settings. Bit 4 of the first byte is the sign (i.e. positive or negative Celsius value). If bit 4 is 1, the temperature is negative. This is equivalent to saying that negative temperatures will be represented as 256 plus the numeric value of bits 3 to 0 of the first byte. The second byte of the word contains the 8 least significant bits of the ambient temperature.

Before you flash the code to the micro:bit, remember to click the REPL button so that you can see the data coming back from the micro:bit.


from microbit import *
sleep(1000)
while True:
    i2c.write(0x18,bytearray([0x05])) # select ambient temp. register
    sleep(100) #  small pause needed to allow the device to update
    t_amb=i2c.read(0x18,2) # read two bytes from the ambient temp. register
    t_hi = (t_amb[0] & 0x0f) << 4 # mask upper 4 bits (alarm settings)
    t_lo = t_amb[1] / 16          # lower 8 bits (LSB)
    if t_amb[0] & 0x10 == 0x10: # take twos complement for -ve readings
        temp = (t_hi + t_lo) - 256.0
    else:
        temp = t_hi + t_lo
    print('%.2f' % round(temp,2))  # round and print to 2 places
    sleep(500)

In my next post, I will describe how to connect a 3V LCD screen instead of sending the temperature readings to the REPL. Then you will have a stand-alone digital thermometer which can be used, for example, as part of a weather station project, data logging, or for environmental control.

Saturday 21 April 2018

Converting 5v LCD displays to 3v

I wanted to attach a 16x2 LCD screen to a BBC micro:bit, which uses 3.3v logic (like the Raspberry Pi). You can buy 3.3v LCD screens, but they are a bit pricey and not so easy to find. A very cheap and ubiquitous 5v LCD is the "QAPASS 1602a 16x2". I don't know who makes these things and QAPASS may just refer to some quality control test, rather than the device itself. The important thing is that under one of the black "blobs" is a Hitachi HD44780 display controller.



These displays have unused pads on them including space for an 8-pin voltage convertor chip. This is designed to allow a 3v version of the display to be sold by adding a negative voltage generator to the LCD module.

Converting the module to 3v is simply a matter of soldering the correct voltage converter chip (ICL7660) onto the space provided, plus two 10µF capacitors across pads marked C1 and C2. Finally, the voltage convertor needs to be enabled by soldering across the jumper J3 and removing the solder from across J1.

The ICL7660 voltage convertor can be obtained for around £1. Finding the correct capacitors in small quantities was a bit more tricky. Unless you have access to some already, you'll probably have to buy 100. Here are some suitable ones.

It can be seen that the voltage converter and additional capacitors are surface-mounted. Although they are more fiddly to solder than "through the hole" components, a normal soldering iron is fine (preferably with a fine tip). The technique I used is to melt some solder onto one of the pads for the 7660 chip then, holding the chip down against the pads, apply the soldering iron briefly to the relevant leg of the chip to remelt it and "fix" the chip in position. At this stage it is important to ensure that the chip is correctly aligned with all of the pads. If not, repeat this procedure until it is. Now, the other 7 legs of the chip can be soldered in place by heating each leg in turn and then dabbing the solder onto the leg. Miraculously, the solder will flow onto the pad and over the leg! Once you have soldered all 8 legs, check that there are no "bridges" between adjacent legs. If there are, a quick dab with the soldering iron should sort it out - if not, hold some solder wick between the affected bridge and the soldering iron and that should suck up the excess.

Now use a similar process for each of the capacitors; melt some solder onto one of the pads, hold the capacitor in place on top of it, then reapply the soldering iron to melt it into position. Check the alignment, and repeat.

To remove the solder from J1, heat up the pad with the soldering iron and either use a "solder sucker" or a piece of solder wick until there is a clear gap between each half of the pad J1. Then heat up the J3 pad and apply some solder to bridge the gap.

Job done!

The picture below shows the new components in place as well as the change in jumper position. You'll also notice that I have soldered a 16-way socket onto the back of the LCD circuit board. I find that this makes it easy to connect serial/parallel convertor boards if required.