Monday 29 May 2017

Tutorial - using Amazon Dash with a Pi Zero and node.js

Introduction

In MagPi Magazine (issue 57) there was an article about how you can subvert an Amazon Dash button and use it as a Internet of Things ("IoT") button. However, the code in the article had several syntax errors. Once I fixed the syntax errors I still couldn't get it to work with my button. Then I found that a key ingredient, tcpdump (needed by scapy), was missing - so I installed that too. I ended up with some working code but it wasn't very reliable, especially using wireless on the Raspberry Pi Zero W. After doing a bit of research and experimentation, I got it working better by using "dasher", a node.js script.

Node.js is a server-based runtime environment for executing JavaScript code. It allows non-blocking multi-threading applications to be deployed. This is a fancy way of saying that you can leave it running in the background on your computer (Raspberry Pi in this case) and it will just sit there listening until something triggers it (without consuming lots of processing power).

A companion application, NPM, is a package manager for node.js scripts.

As in the MagPi article, dasher would allow the Dash button to toggle an IoT bulb such as the Lifx or Philips Hue. I don't have one of those so I'll use a simple LED and then move on to IFTTT. Rather than tie up a Raspberry Pi 3, I thought this would be a good use for the cheap-as-chips Raspberry Pi Zero W. Incidentally, although this works well on a Pi Zero W, it doesn't seem to be very reliable using a basic Pi Zero with a wireless dongle attached to the USB port. If you've only got a non-wireless Pi Zero, it does work well with a wired network connection (e.g. a USB hub with a network port) if you happen to have one lying around.

Before we install dasher, the Dash button needs to be configured following the Amazon instructions so that it stores the SSID and password of your home wireless network (but with the last step omitted - so that no product is associated with the button). Strangely, you need to use an Android or iOS mobile phone to do this - not a tablet. If, like me, you've only got a tablet, you can use a little-known Android app called Amazon Underground on an Android tablet. I forgot to say - if you order something with the button of your choice, you get the price of the button refunded, so it makes sense to get a button for something you use anyway, place one order then de-activate the button. Then go through the "add button" process again and quit the app before the final step.

How will we know if we have got our Dash button working? It makes sense to test it by controlling a GPIO pin first.

Let's get started

I started by following the instructions on the dasher git-hub page. However, my initial attempt didn't work; also, the version of node.js that you end up with is v4.0, although the current  (at the time of writing) version is 6.10.3. I thought I'd do it properly and install the latest version of node.js. First, I prepared my Raspberry Pi Zero W with a new image of Raspbian (date 10 April 2017). The current version of Raspbian comes with a very old version of  node, and this has to be updated for dasher to work properly. After configuring the timezone and WiFi interface, I installed a couple of pre-requisites (libpcap-dev and npm).
  • Open console and install node, libpcap-dev and npm
    #Install node.js to remove nodejs legacy 
    sudo apt-get install node
    sudo apt-get install libpcap-dev
    sudo apt-get install npm

    • Install newer version of Node.JS (I used ARM v6)
    wget https://nodejs.org/dist/v6.10.3/node-v6.10.3-linux-armv6l.tar.gz
    tar -xvf node-v6.10.3-linux-armv6l.tar.gz
    cd  node-v6.10.3-linux-armv6l
    sudo cp -R * /usr/local/
    sudo node --version


    This should respond with v6.10.3. If so, node is installed and up to date so we can proceed with installing dasher.
    • Install dasher
    cd ..
    sudo git clone https://github.com/maddox/dasher.git
    cd dasher
    sudo npm install

    N.B. ignore gyp warnings (doesn't seem to matter)
    Go and make a cup of coffee - installation takes a long time

    When that is finished, check that your button can be found (make a note of the MAC address if you haven't already done so)....
    sudo ./script/find_button

    Toggle a GPIO pin using dasher

    We can configure dasher to monitor the home network and run a shell script if it sees a "UDP" request from the MAC address of the Dash button to join the network (which it will do when the button is pressed). To do this, we need to modify the config.json file according to the example given in the dasher documentation. Mine looks like this:

    {"buttons":[
        {
           "name": "Command Exec Button",
           "address": "[MY MAC ADDRESS]",
           "cmd": "/home/pi/dash_button.sh"
        }
    ]}

    Now we need to write the shell script (dash_button.sh) to toggle a GPIO. I connected an LED (with resistor) to GPIO14 so that, when the Dash button is pressed, it toggles on/off.

    #!/bin/bash
    GPIO=14
    GPIODIR=/sys/class/gpio/gpio$GPIO

    # i.e. GPIODIR = /sys/class/gpio/gpio14
    # use $GPIODIR to save typing long folder trees
    echo "Configuring GPIO $GPIO"

    #check if gpio14 is already exported.
    # If not, export gpio14 then set as output
    if [ ! -e "$GPIODIR" ] #tests for the (not !) existence of gpio14 folder
    then
            echo "Exporting GPIO"
            echo $GPIO > /sys/class/gpio/export
    echo out > $GPIODIR/direction #set as output
    else
            echo "GPIO already exported"
    fi

    #now that gpio14 is configured, toggle on/off depending on state
    read state < $GPIODIR/value
    sleep 1
    echo $(( ! state )) > $GPIODIR/value


    Before you can run this shell script, you need to make it executable and add it to your path:
    sudo chmod -u+x dash_button.sh
    sudo cp dash_button.sh /usr/local/bin


    To run dasher:
    sudo npm run start

    IFTTT

    Now that we have got the button working with GPIO pins, we can use it to set up a trigger action using IFTTT instead (you can't do both with the same button, unfortunately). IFTTT allows you to connect web-enabled "services" together so that, IF something happens on one service (a "THIS" trigger), THEN an action will occur on another service (a "THAT" action).

    Assuming you have a Gmail account, a good one for testing purposes is to add a row to a Google Sheet each time the button is pressed.

    In this case, the "dasher" script will monitor the home network and trigger an HTTP request if it sees a "UDP" request from the MAC address of the Dash button to join the network (which it will do when the button is pressed).

    The next step is to configure the IFTTT "Maker Webhooks" service to "watch" for the HTTP request that the dasher script will generate when it sees the Dash button join the network. Assuming you have set up an account on IFTTT, you will need to activate the Maker Webhooks service which will generate your own private key (you will need this later).

    Now, in IFTTT you can build your applet. Click on the "+" in the applet builder and choose the Maker Webhooks service (Receive a web request) as the trigger. Give it a name (in my case "trigger_dash"). Now click on the "+" to add an action. Choose the "Google Drive" service, then "Add row to spreadsheet". You can leave all the defaults as they are and just press "Create action", then (on the next page that appears) "Finish". If you click on  the down arrow below your IFTTT user name on the main IFTTT page (top right) and choose Services, then choose the Maker Webhooks service you will be able to click on the Documentation button for that service (top right again) you will see the full URL that you need to generate (in dasher) in order to trigger the applet. This takes the format

    https://maker.ifttt.com/trigger/[EVENT NAME]/with/key/[YOUR KEY]

    Next make your own config.json file containing the mac address of your button and Maker Webhooks key. I recommend that you first rename or copy the config.json file you made to toggle a GPIO pin to config2.json or something else, so that you can always revert back to it if you need to test things are working.

    sudo nano /config/config.json

    Mine looks like this (note that "trigger_dash" is the name I used  in Maker Webhooks when setting up the IFTTT applet):
    {"buttons":[
       {
            "name": "trigger_dash",
            "protocol": "udp",
            "address": "[YOUR MAC ADDRESS]",
            "url": "https://maker.ifttt.com/trigger/trigger_dash/with/key/[YOUR KEY]",
            "method": "POST"
        }
    ]}


    I have kept it simple for testing purposes. Once you get it working you can add more buttons, or pass more information to IFTTT.
    sudo npm run start

    With any luck, you should see dasher start running and confirm that the button has been added. Now, we can configure it so that it runs automatically, every time that you start up the Raspberry Pi.

    Run from startup

    Just follow the instructions here. These instructions also show you how to check that your Dash button is being detected. Now, you should be able to reboot your Raspberry Pi then, every time the Dash button is pressed, Google will add a new line to a spreadsheet. If it doesn't work, open a console and type:
    tail -f /var/log/dasher.log

    That should show you if dasher detected the button press, and what it did. I have found that the IFTTT Google service doesn't always work. Good luck!

    Saturday 13 May 2017

    Transistor logic gates - where analogue becomes digital

    Introduction



    We all know that computers are made using thousands (or millions) of transistors, but not many of us have "hands on" experience of constructing the fundamental building-blocks of digital logic ourselves. Those building blocks are "logic gates".

    I decided to experiment with making my own logic gates using some of the many Snap Circuits components I have accumulated over the years.

    In electronics, a logic gate is a device implementing a Boolean function. Every possible logical function can be represented by a combination of only NAND gates or only NOR gates. In practice, other common logic gates such as AND, OR, NOT and XOR are also used as building blocks.

    Transistors


    Turning now to the transistor, if a sufficient voltage is applied between the collector and base (normally ~ 0.7V), this will allow current to flow between the collector and the emitter (i.e. it will "turn on" the transistor).


    A judicious choice of base resistor is necessary to prevent excessive current flowing through the base (destroying the transistor). Also, a "load" resistor should be chosen that allows sufficient but not excessive current to flow through the load device (i.e. output device). For my circuit, I used a 4.5V battery pack and NPN transistors (with base resistors of 1k Ohms) driving an LED through a 100 Ohm load (plus the 33 Ohm internal resistor). By applying sufficient (analogue) base voltage until current flows from the collector through the emitter, we have made a digital device (an electronic switch). This is the starting point for digital electronics.

    Logic gates

    The first, and simplest logic gate I built was the NOT gate (also known as an inverter). This needs just one transistor. If the input voltage is "high" (in logic terms "1"), then the output voltage is "low" (in logic terms "0") and vice versa.



    Next I built a NAND gate. This needs two transistors in series, with the output load being driven between the collector and ground (0V). nb. You can turn it into an AND gate by placing the load between the supply voltage and the collector. 



    The function of a NAND gate can be expressed in the truth table below (see All About Circuits):


    The final "building block" logic gate I built was a NOR gate which needs two transistors in parallel.



    The truth table is as follows:


    Now for something a bit more complicated - an Exclusive-OR ("XOR") gate. This outputs a “high” ("1") logic level if the inputs are at different logic levels, either 0 and 1 or 1 and 0. Conversely, it outputs a “low” ("0") logic level if the inputs are at the same logic levels.

    XOR gates can be constructed from various combinations of AND, OR, NAND, NOR or NOT gates. I made mine from pairs of NAND and NOT gates.

    Adders

    Finally, I made a "half adder" circuit by adding an AND gate in parallel with an XOR gate.


    Inspecting the truth table reveals that this circuit enables two one-bit binary numbers to be added together, giving a "sum" and a "carry".
    Finally, the full significance of all these logic gates can be appreciated. Using only transistors and resistors, a circuit can be created that adds two binary numbers together. Although there is a "carry" output, there is no "carry" input (hence the name half adder), so only one-bit numbers can be added together, which somewhat limits its usefulness.

    A full adder allows for "carry in" signals as well as "carry out" signals. This makes it possible to add larger numbers together, as there is a mechanism for carrying from a "less significant bit" to a "more significant bit". For example, by cascading eight full adders together, eight-bit binary numbers can be added together (i.e. numbers up to 255 in decimal).

    Friday 7 April 2017

    Driving an Adafruit 4x14-segment I2C display with the BBC micro:bit

    Introduction

    I have previously posted about using  Adafruit 8x8 displays with the BBC micro:bit. I also recently bought an Adafruit 4x14-segment display so I could experiment with displaying text as well as just numbers. Like the 8x8 LED matrix, the 4x14-segment display is soldered onto an I2C "backpack" that contains an HT16K33 LED driver/controller.

    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 Adafruit backpack has a default address of 0x70 but this can be changed to one of seven other addresses by soldering across one or more of the three jumpers on the backpack. This would allow you to have several 4x14-segment displays running from the same micro:bit,and controlled separately.

    My post about the 8x8 LED matrix provided instructions for using a Micropython library for the HT16K33 to simplify the Python code. However, that library doesn't support the various bit-patterns needed to form each of the 60-plus characters that can be displayed on this particular display, so it doesn't help much. In any case, I find it a slow process using libraries with the micro:bit as you have to upload the Python code first (which generates an error message that takes a long time to disappear) THEN copy the library to the micro:bit and reboot. If you just want to make a small change to your code, this gets quite tedious.

    The micro:bit Micropython reference page shows there are only three micro:bit I2C functions. Sending data to an I2C device is not complicated as there is only really one function that you need to know about. Assuming you are not changing the default configuration, you don't need to initialise the I2C device; neither do you normally need to read from a 4x14-segment display, which only leaves the function:
    microbit.i2c.write(addr, buf, repeat=False) 
    
    In our case, we can also omit the "repeat=False" parameter. So, all we need to do is call:
    i2c.write(0x70, bytearray(buf))
    
    ... where 0x70 is the I2C address of the HT16K33 and bytearray(buf) contains the data that you want to send to the HT16K33 LED controller.

    The HT16K33 uses a number of command codes to control the display, and an area of RAM to store the segment display data. The main command codes we need are:

    0x21  Start oscillator
    0x80  Blank the display
    0x81  Send the RAM data to the display
    0x83  Blink the display @2Hz
    0xE0  Dim the display
    0xEF  Set brightness to maximum

    To display characters, we need to write the data to the HT16K33 RAM and then send the data to the display itself. Since we are always going to send a whole set of data for each of the four segments, we will always preface the data for which LED segments to switch on by the starting address of the RAM on the HT16K33, i.e. 0x0. This is then followed by four two-byte pairs (one pair for each "digit", working from left to right). The second byte for each digit is essentially the traditional 7-segment bit pattern (except that the middle segment is split into two). The first byte consists of the extra segments (e.g. the diagonal ones) needed to form the letters of the alphabet. Then you send the control byte $81 to "latch" the data to the display.
    The bit mapping shown above can be represented as two eight-bit binary numbers where A is the least significant bit, as per the sequence below (the first bit isn't used; you can make it 0 or 1). Bytes 1 and 2 are sent in reverse:
        --- byte 2 ---    ,   --- byte 1 ---
    0 DP N M L K J H , G2 G1 F E D C B A

    To turn on just the A segment, use binary 00000000,00000001 and reverse-> hex 0x1, 0x0
    To turn on just the G1 segment, use binary 00000000,01000000 and reverse-> hex 0x40, 0x0
    To make "0", turn on segments F E D C B A - use binary 00000000,00111111 and reverse-> hex 0x3F, 0x0
    To make "K", turn on segments N K , G1 F E - use binary 00100100,01110000 and reverse-> hex 0x70,0x24

     

    Connecting things up

    This assumes you have already assembled the display by soldering the two pairs of 14-segment displays to the backpack. If not, refer to this Adafruit page.

    The first step is to connect the display to the micro:bit. Compared with the other Adafruit I2C backpack displays, this device has an extra pin which connects to the logic level voltage of the processor you are using (i.e. 3.3v). The display works best at 5v, so a power supply that provides both 5v and 3.3v is ideal. I used a YwRobot MB102 USB breadboard power supply module (pictured). This allows you to power either side of a solderless breadboard with a different regulated voltage by plugging it into a USB port.

    You'll need to use an edge-connector board to access the I2C pins (P19 and P20). Some ready-made edge-connectors don't have those two pins soldered in place so you might need to get the soldering iron out. Assuming you have them, connecting is easy (SDA=P20, SCL=P19).  Since I was using solderless breadboard, I used the very neat bread:bit edge connector board from Proto-Pic. Combined with the USB breadboard power supply, this makes a great prototyping setup.

    Micropython on the micro:bit automatically enables the internal pullup resistors on the I2C lines, so the usual 5.1k resistors on the SCL and SDA connections are not needed.

    Python code

    There are two types of command used:
    1. Send a control byte to the display to turn it on/off/blink etc. This takes the form
    i2c.write(0x70, bytearray([control byte])) 
    
    2 Send character data to the display controller RAM
    i2c.write(0x70,([0x0,digit1,digit2,digit3,digit4]))
    
    Here's some code I used to test the various functions that the display uses. It clears the display, then shows all the printable characters in groups of four (some of the lower case letters are not that convincing to be honest). Finally it displays a message that dims and then flashes, followed by succession of "wheel spokes" giving the appearance that they are spinning around. I have included virtually the whole character set for reference, but in practice you would just include the characters that you are going to use.

    from microbit import i2c, sleep
    # address of HT16K33 is 0x70

    sleep(1) # allow time for display to settle after power on/reset
    # start oscillator
    i2c.write(0x70, bytearray([0x21]))


    # To display something:
    # send     (i2c device address,([RAM start addr,    four pairs of bytes    ]))
    # i2c.write(      0x70        ,([0x0           ,digit1,digit2,digit3,digit4]))
    # turn on the display


    # Clear the display
    i2c.write(0x70, bytearray([0x80]))
    sleep(1000)


    # data for each character
    #   byte1,byte2
    #0 = 0x3f,0x0
    #1 = 0x6,0x0
    #2 = 0xdb,0x0
    #3 = 0xcf,0x0
    #4 = 0xe6,0x0
    #5 = 0xed,0x0
    #6 = 0xfd,0x0
    #7 = 0x1,0xc
    #8 = 0xff,0x0
    #9 = 0xc7,0x8
    #. = 0x0,0x40
    #+ = 0xc0,0x12
    #- = 0xc0,0x0
    #/ = 0x0,0xc
    #| = 0x0,0x10
    #\ = 0x0,0x21
    #0x = 0x8d,0xc
    #all segments = 0x3f,0x3f
    #A 0xF7,0x0
    #B 0x8F,0x12
    #C 0x39,0x0
    #D 0xF,0x12
    #E 0xF9,0x0
    #F 0xF1,0x0
    #G 0xBD,0x0
    #H 0xF6,0x0
    #I 0x9,0x12
    #J 0x1E,0x0
    #K 0x70,0x24
    #L 0x38,0x0
    #M 0x36,0x5
    #N 0x36,0x21
    #O 0x3F,0x0
    #P 0xF3,0x0
    #Q 0x3F,0x20
    #R 0xF3,0x20
    #S 0xED,0x0
    #T 0x0,0x12
    #U 0x3E,0x0
    #V 0x30,0xC
    #W 0x36,0x28
    #X 0x0,0x2D
    #Y 0x0,0x15
    #Z 0x9,0xC
    #a 0x58,0x10
    #b 0x78,0x20
    #c 0xD8,0x0
    #d 0x8E,0x8
    #e 0x58,0x8
    #f 0x71,0x0
    #g 0x8E,0x4
    #h 0x70,0x10
    #i 0x0,0x10
    #j 0xE,0x0
    #k 0x0,0x36
    #l 0x30,0x0
    #m 0xD4,0x10
    #n 0x50,0x10
    #o 0xDC,0x0
    #p 0x70,0x1
    #q 0x86,0x4
    #r 0x50,0x0
    #s 0x88,0x20
    #t 0x78,0x0
    #u 0x1C,0x0
    #v 0x4,0x20
    #w 0x14,0x28
    #x 0xC0,0x28
    #y 0xC,0x28
    #z 0x48,0x8

    while True:

    # Turn on all segments
        i2c.write(0x70,bytearray([0x0,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep(1000)

    # print out the character set four at a time
    #0 1 2 3
        i2c.write(0x70, bytearray([0x0,0x3f,0x0,0x6,0x0,0xdb,0x0,0xcf,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #4 5 6 7
        i2c.write(0x70, bytearray([0x0,0xe6,0x0,0xed,0x0,0xfd,0x0,0x1,0xc]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #8 9 0x all
        i2c.write(0x70, bytearray([0x0,0xff,0x0,0xe7,0x0,0xed,0x12,0x3f,0x3f]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #- / . +
        i2c.write(0x70, bytearray([0x0,0x0,0x40,0xc0,0x12,0xc0,0x0,0x0,0xc]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #A B C D
        i2c.write(0x70, bytearray([0x0,0xF7,0x0,0x8F,0x12,0x39,0x0,0xF,0x12]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #E F G H
        i2c.write(0x70, bytearray([0x0,0xF9,0x0,0xF1,0x0,0xBD,0x0,0xF6,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #I J K L
        i2c.write(0x70, bytearray([0x0,0x9,0x12,0x1E,0x0,0x70,0x24,0x38,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #M N O P
        i2c.write(0x70, bytearray([0x0,0x36,0x5,0x36,0x21,0x3F,0x0,0xF3,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #Q R S T
        i2c.write(0x70, bytearray([0x0,0x3F,0x20,0xF3,0x20,0xED,0x0,0x1,0x12]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #U V W X
        i2c.write(0x70, bytearray([0x0,0x3E,0x0,0x30,0xC,0x36,0x28,0x0,0x2D]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #Y Z all all
        i2c.write(0x70, bytearray([0x0,0x0,0x15,0x9,0xC,0x3f,0x3f,0x3f,0x3f]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #a b c d
        i2c.write(0x70, bytearray([0x0,0x58,0x10,0x78,0x20,0xD8,0x0,0x8E,0x8]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #e f g h
        i2c.write(0x70, bytearray([0x0,0x58,0x8,0x71,0x0,0x8E,0x4,0x70,0x10]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #i j k l
        i2c.write(0x70, bytearray([0x0,0x0,0x10,0xE,0x0,0x0,0x36,0x30,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #m n o p
        i2c.write(0x70, bytearray([0x0,0xD4,0x10,0x50,0x10,0xDC,0x0,0x70,0x1]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #q r s t
        i2c.write(0x70, bytearray([0x0,0x86,0x4,0x50,0x0,0x88,0x20,0x78,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #u v w x
        i2c.write(0x70, bytearray([0x0,0x1C,0x0,0x4,0x20,0x14,0x28,0xC0,0x28]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)
    #y z
        i2c.write(0x70, bytearray([0x0,0xC,0x20,0x48,0x8,0x0,0x0,0x0,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep (1000)

    # display a message
    #                             |   L    |    O   |    V     |   E  
        i2c.write(0x70,bytearray([0x00,0x38,0x0,0x3f,0x0,0x30,0xC,0xf9,0x0]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep(1000)

    #                             |   B    |    E    |     E   |   R  
        i2c.write(0x70,bytearray([0x00,0x8F,0x12,0xf9,0x00,0xf9,0x00,0xf3,0x20]))
        i2c.write(0x70, bytearray([0x81])) # display on
        sleep(1000)


        i2c.write(0x70, bytearray([0xE0])) # dim the display (range 0xE0 to 0xEF)
        sleep (1000)
        i2c.write(0x70, bytearray([0xEF])) # full brightness again
        sleep (1000)
        i2c.write(0x70, bytearray([0x83])) # blink the display (0x83=2Hz blink, 0x85=1Hz, 0x87=0.5Hz)
        sleep (3000)

    # Clear the display
        i2c.write(0x70, bytearray([0x80]))
        sleep(1000)

    # make a spinney-round thing!
        for iterations in range(20):
            i2c.write(0x70, bytearray([0x0,0x0,0x12,0x0,0x12,0x0,0x12,0x0,0x12]))
            i2c.write(0x70, bytearray([0x81])) # display on
            sleep (21)
       
            i2c.write(0x70, bytearray([0x0,0x0,0xc,0x0,0xc,0x0,0xc,0x0,0xc]))
            i2c.write(0x70, bytearray([0x81])) # display on
            sleep (20)
       
            i2c.write(0x70, bytearray([0x0,0xc0,0x0,0xc0,0x0,0xc0,0x0,0xc0,0x0]))
            i2c.write(0x70, bytearray([0x81])) # display on
            sleep (20)
       
            i2c.write(0x70, bytearray([0x0,0x0,0x21,0x0,0x21,0x0,0x21,0x0,0x21]))
            i2c.write(0x70, bytearray([0x81])) # display on
            sleep (20)

    # turn off the display
        i2c.write(0x70, bytearray([0x80]))
        sleep(1000)


    Friday 31 March 2017

    Driving Adafruit I2C 8x8 LED matrices with the BBC micro:bit

    Introduction

    I bought a couple of Adafruit 8x8 displays in a sale and tried them out with various microcontrollers. This post is about using them with the BBC micro:bit. It assumes you have already assembled the display by soldering the LED matrix to the backpack. If not, refer to this Adafruit guide.



    They consist of an 8x8 LED matrix soldered onto an I2C "backpack" that contains an HT16K33 LED driver/controller. 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 Adafruit backpack has a default address of 0x70 but this can be changed to one of seven other addresses by soldering across one or more of the three jumpers on the backpack. This would allow you to have several LED matrices running from the same micro:bit,and controlled separately.

    Connecting things up

    The first step is to connect the display to the micro:bit. You'll need to use an edge-connector board to access the I2C pins (P19 and P20). Some ready-made edge-connectors don't have those two pins soldered in place so you might need to get the soldering iron out. Assuming you have them, connecting is easy (SDA=P20, SCL=P19) but I used a separate power supply for the display as it will struggle to light up all the pixels if you draw power from the micro:bit supply. Don't forget to connect the earth (- supply) connections on the two power sources togther (i.e. connect an extra wire from -ve on the backpack to -ve on the micro:bit edge connector). Micropython on the micro:bit automatically enables the internal pullup resistors on the I2C lines, so the usual 5.1k resistors on the SCL and SDA connections are not needed.

    Python code

    I was happy to find that deshipu from the MicroPython forum had already ported a Python library for the HT16K33 LED driver/controller to work with MicroPython. This library allows the same I2C backpack to be used with three different display types. You will need to copy the library to the users\mu_code folder on your PC.

    Here's some code I used to test the various functions that the display uses. It clears the display, then fills it again, then dims the display in steps and clears it again. Then it draws a "smiley face" (using the byte array image() expressed in binary to make it more obvious which pixels should be lit up) before flashing the display on and off before finally clearing it again.

    You can design your pattern on a piece of paper and put a '1 where you want the pixel lit and a '0' where you want a pixel off. Once you have got the pattern you want, you could convert the binary to hex to save space as Micropython on the micro:bit doesn't leave much space for programs/data.


    from microbit import i2c, sleep
    from ht16k33 import Matrix8x8
    # address of HT16K33 is 0x70
    display = Matrix8x8(i2c, address=0x70)

    # Clear the display
    display.fill(0x00)
    display.show()
    sleep(1000)

    # Fill the display
    display.fill(0xff)
    display.show()
    sleep(1000)

    #step through some different brightness levels
    display.brightness(1)
    sleep(3000)
    display.brightness(7)
    sleep(3000)
    display.brightness(15)
    sleep(3000)


    # Clear the display
    display.fill(0x00)
    display.show()
    sleep(1000)

    # define the bitmap image to be displayed  (in this case a smiley face)
    image = (
        0b01111110,
        0b10000001,
        0b10100101,
        0b10000001,
        0b10100101,
        0b10011001,
        0b10000001,
        0b01111110,
    )

    #draw the image
    for y, line in enumerate(image):
        for x in range(8):
            if line & (1 << x):
                display.pixel(y, x, 1)
    display.show()
    sleep(3000)
    # Blink the display
    display.blink_rate(0x83)
    sleep(3000)
    # Stop blinking?
    display.blink_rate(0x81)
    sleep(3000)

    # Clear the display
    display.fill(0x00)
    display.show()
    sleep(1000)


    Getting it onto the micro:bit is a two-stage process. First "flash" the test code to the micro:bit in the usual way using Mu. This will result in an error as the required library is not on the micro:bit yet. Once the error message has finished scrolling on the micro:bit's display, press the "Files" button in Mu. If you don't see that button, you probably have an old version of Mu, so replace it with the current version. This will open up a new window below the code window. Provided you have put the library file in the mu_code folder on your PC, you should see it in the right-hand panel in Mu (along with any other files in your mu_code folder). Using the mouse, drag it and drop it into the left-hand window. This should copy it to the micro:bit. Now, if you disconnect the programming cable, power the micro:bit off and on again and the code should run.


    Saturday 18 March 2017

    Driving DotStar APA102 LED strings with the BBC micro:bit

    Introduction


    Although NeoPixels are very popular and relatively cheap nowadays, there is another type of individually-addressable LED string that offers some distinct advantages.



    The APA102 and APA102C LEDs are small super-bright RGB LED lighting modules. Each LED is a single light source and LEDs can be chained to create light strips of varying length. Light strips are commonly available in 10, 20, 30 and 60 LED lengths (and also in other form factors such as the Pimoroni Blinkt). Strips can be cut and joined as desired. These LEDs are marketed by Adafruit under the name 'DotStar'. The APA102 and APA102C modules use an industry standard SPI interface. This is significantly easier to use than the single wire interface of the NeoPixel (WS2812) and other similar modules (all of which require specific interface timing which can be difficult to achieve).

    By using SPI, the APA102 offers much faster data and PWM rates than NeoPixels - which allows "persistence of vision" effects. The higher data rate is particularly noticeable on long strings of LEDs.

    Since I already had some APA102s, I decided to try them out with the BBC micro:bit. The only example I found on the Internet was a re-write of the Pimoroni Python library. However, it just uses "bit banging" to switch the LEDs on and off, and, whilst this was just about acceptable on a Blinkt with 8 LEDs, it was very slow on a long string of 30 LEDs. I decided to write my own Python code using the BBC micro:bit's built-in SPI support. I'm no Python expert so I expect the examples below could be improved but they should demonstrate the basic principles.

    After a bit of research, I discovered what is needed to drive an APA102 device. Lighting data is sent to the LED strip by sending a 'start frame', brightness data for each LED and then an 'end frame'.

    The 'start frame' is 32 zero bits sent as four zero value bytes.

    00000000 00000000 00000000 00000000

    The brightness data for each LED consists of four bytes, a start byte which has its three most significant bits set and the least significant five bits setting the overall brightness, then three bytes which set the intensity of the LED's blue, green and red brightness; 0 is off, 255 ($FF) is fully on.

    111xxxxx bbbbbbbb gggggggg rrrrrrrr

    The first byte of the LED's data, the overall brightness setting, has a byte value of 224 ($E0) to 255 ($FF); this allows for 32 levels of brightness from lowest to maximum respectively. It is reported that, on some devices, LED flicker may be more noticeable when the overall brightness is set less than maximum and, to avoid this, it is recommended to send $FF as the brightness setting and solely control the LED brightness by adjusting the blue, green and red byte values.

    The 'end frame' is 32 one bits sent as four 255 ($FF) value bytes. Because of the way the LEDs pass data through themselves, it is necessary to send additional clock pulses to latch the sent data into the final LED. Sending the 'end frame' provides for this.

    11111111 11111111 11111111 11111111

    An 'end frame' needs to be sent for every set of 64 LEDs in the light strip. A light strip of 1 to 64 LEDs requires a single 'end frame', a light strip of 65 to 128 LEDs require two, and so on.

    Note that if the LED count for a strip is incorrectly set the 'end frame' is equivalent to setting a LED fully on. It is therefore advised to ensure the 'end frame' is not sent before all data for the entire light strip is sent or else this will set a LED within the light strip fully on.

    If the 'end frame' is not sent, the last LED in a light strip may not have its colour or brightness updated until the next set of brightness data is sent to the light strip.

    Connecting things up

    APA102s normally have four connections:
    • 5v
    • GND
    • Clock
    • Data
    Note that the BBC micro:bit is a 3.3V device and, in any case, its outputs cannot provide enough current to light the LEDs so a separate 5v power source should be provided (with a common GND connection to the micro:bit). LED strips can be chained together so the clock and data signals should be provided to the correct end of the strip (this is normally marked using arrows on the strip itself).

    In this situation, the micro:bit will be the SPI Master and the APA102 will be the SPI Slave. On the micro:bit, the SPI connections are therefore:
    • Clock (SCK) - pin P13
    • Data out (MOSI) - pin P15
    I used a small breadboard to make the connections. In the case of the Blinkt (or other Raspberry Pi versions of the APA102, I plugged the Blinkt into a "cobbler" inserted into the breadboard and used wires to connect to the micro:bit's pins (you'll need an edge connector from Kitronik or elsewhere to access P13 and P15). After I had written this, deshipu on the MicroPython forum pointed out that you don't need to use P13 and P15 for the SPI connections - it's actually just a convention - and you can use P0, P1 or P2, so the edge connector would not be needed. [Don't forget to change the spi.init() parameters if you do this]



    Python code

    I used the excellent Mu editor to write the code and flash it to the micro:bit.

    Example 1

    The first snippet below shows how to switch on one LED in red (change the values of the variables r,g,b for other colours). For testing purposes I used a Blinkt with 8 LEDs (addressed as 0 to 7). You might need to "tweak" the indents if you copy and paste from here.

    from microbit import *
    # initialise SPI
    spi.init(baudrate=1000000,bits=8,mode=0, sclk=pin13, mosi=pin15, miso=pin14) # setup SPI
    # number of pixels in the chain
    num_pixels = 8
    on_pixel = 5 # define which pixel to turn on (e.g. 5)

    x = 0xff # full brightness
    r = 0xff # red fully on
    g = 0x00 # green fully off
    b = 0x00 # blue fully off

    # start frame
    spi.write(b'\x00\x00\x00\x00') #start frame
    for i in range(num_pixels): # for each pixel
        spi.write(b'\xff\x00\x00\x00') # all colours off
    spi.write(b'\xff\xff\xff\xff') #end frame

    # light up LEDs
    while True:
        buf=bytearray([x,b,g,r]) #send the colours in reverse order
        sleep(1)
        spi.write(b'\x00\x00\x00\x00') #start frame
        for i in range(num_pixels): #check each value of i
            if i==on_pixel:
               spi.write(buf)
            else:
                spi.write(b'\xff\x00\x00\x00') # off
    # end frame
    spi.write(b'\xff\xff\xff\xff')

    Example 2

    In this example, the first four LEDs are set to red, green, blue, white and the rest are set to purple then they are all blinked on and off every second.

    from microbit import *
    # init
    spi.init(baudrate=1000000,bits=8,mode=0, sclk=pin13, mosi=pin15, miso=pin14) #setup SPI
    # number of pixels in the chain
    num_pixels = 8

    #### turn all off ####
    # start frame
    spi.write(b'\x00\x00\x00\x00') #start frame
    for i in range(num_pixels): # for each pixel
        spi.write(b'\xff\x00\x00\x00') # all colours off
    spi.write(b'\xff\xff\xff\xff') # end frame

    ##### light up LEDs ####
    while True:
        sleep(1000)
        spi.write(b'\x00\x00\x00\x00') # start frame
        spi.write(b'\xff\x00\x00\xff') # red n.b. colours sent in reverse order (b,g,r)
        spi.write(b'\xff\x00\xff\x00') # green
        spi.write(b'\xff\xff\x00\x00') # blue
        spi.write(b'\xff\xff\xff\xff') # white
        for i in range(4,8,1): #for the last 4 pixels purple
            spi.write(b'\xff\xff\x00\xff') # all colours off
        spi.write(b'\xff\xff\xff\xff') # end frame

    #all off again
        sleep(1000)
        spi.write(b'\x00\x00\x00\x00') # start frame

        for i in range(num_pixels):
            spi.write(b'\xff\x00\x00\x00') # all off
        spi.write(b'\xff\xff\xff\xff') # end frame


    Example3

    This example is best demonstrated on a long strip of LEDs (I have used 30) as it "bounces" a coloured pixel up and down the whole length of the strip. It builds on Example 1 by turning on each pixel in turn, until it reaches the end of the strip, then comes back down again. The two sleep(15) commands can be changed to alter the speed with which the pixel appears to travel up and then back down again. It will obviously still work on a shorter strip but the best effect is on a long strip. If you are feeling adventurous, you might like to experiment with changing the colour as it goes up and down.

    from microbit import *
    # initialise SPI
    spi.init(baudrate=1000000,bits=8,mode=0, sclk=pin13, mosi=pin15, miso=pin14) #setup SPI
    # number of pixels in the chain
    num_pixels = 30
    x = 0xff # brightness control
    r = 0xff # red value
    g = 0x0f # green value
    b = 0xab # blue value
    buf=bytearray([x,b,g,r]) # colour mix

    #### start with all pixels off ####
    spi.write(b'\x00\x00\x00\x00') # start frame
    for i in range(num_pixels): # for each pixel
        spi.write(b'\xff\x00\x00\x00') # all colours off
    spi.write(b'\xff\xff\xff\xff') # tail

    #### now loop up and down ####
    while True:
        for j in range(num_pixels): # going up!
        # light up LEDs

            sleep(15)
            spi.write(b'\x00\x00\x00\x00') # start frame
            for i in range(num_pixels): # check each value of i
                if i==j:
                    spi.write(buf) # colour
                else:
                    spi.write(b'\xff\x00\x00\x00') # off
        spi.write(b'\xff\xff\xff\xff') # end frame
       
        for k in range(num_pixels-1,0,-1): # going down!
            sleep(15)
            spi.write(b'\x00\x00\x00\x00') # start frame
            for i in range(num_pixels): # check each value of i
                if i==k:
                    spi.write(buf) # colour
                else:
                    spi.write(b'\xff\x00\x00\x00') # off
        spi.write(b'\xff\xff\xff\xff') #end frame





    Enjoy!