The BBC Microbit V2 and OpenThread

This is an initial posting about early progress I have made with the BBC Microbit V2 and OpenThread. Nordic Semiconductor has posted some good example code for the NRF52840 dongle and development kit. These examples involve the Zephyr operating system and work pretty well. In particular, the echo server example is easy enough to build and deploy on an NRF52840 dongle or XIAO NRF52840 board. This can then be controlled over IPv6 as I mentioned in a previous post.

Compiling the same code for the BBC Microbit V2 initially did not work. Setting the board type to nrf52833dk_nrf52833 (the same IC that is in the Microbit) allows compilation to work but flash programming the device is difficult. I was looking for a way to do this by setting the board type to bbc_microbit_v2. The code would build, flash but not run. It seems that the configuration files for the Microbit V2 in Zephyr do not enable the 802.15.4 radio required by the Thread network. I discovered that this could be enabled by adding an app.overlay file to the project root directory with the following contents:

/ {
	chosen {
		
		zephyr,ieee802154 = &ieee802154;
	};
};
&ieee802154 {
	status = "okay";
};

Compiling, flashing and running the echo_server example worked after adding this.

The next part of the journey was to add some Microbit specific I/O. I thought it would be nice to control the onboard LED matrix over the network. The echo_server code is a little complex and perhaps daunting for people starting out. I modified it a little so that a beginner could concentrate on a single C file which would handle UDP packets and I/O. This file is called usb_processor.c and is shown below:

#include <stdint.h>
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <errno.h>
#include <stdio.h>
#include <zephyr/net/socket.h>
#include <zephyr/drivers/gpio.h>
#include "matrix.h"

int initIO()
{
    int ret=0;
    ret=matrix_begin();
    return ret;
}
void udp_send_receive(uint8_t *buffer, uint32_t len)
{
    // Message is assumed to be at least 4 bytes long (a kluge for now!)
    // print the message out for debugging purposes
    if (len)
    {
        int index=0;
        while(index < len)
        {
            printk("%x ",buffer[index]);
            index++;
        }
    }
    matrix_put_pattern(buffer[0],buffer[1]);
    // pass some data back to the sender
    buffer[2]='a';
    buffer[3]='b';
}

The function initIO configures I/O devices (the LED matrix in this case – see matrix.c in the github link provided below). The function udp_send_receive is called when a UDP packet is received. In this primitive example, the first two bytes are treated as row and column bit masks for the LED matrix. The values are passed on to matrix_put_pattern. Just before returning, two characters are placed in the return packet just to verify that communications is bidirectional.

The NodeJS code that sends data to the Microbit is shown below:


var udp=require('dgram');
// -------------------- udp client ----------------

var buffer = require('buffer');

// creating a client socket
var client = udp.createSocket('udp6');

//buffer msg

client.on('message',function(msg,info){
  console.log('Data received from server : ' + msg.toString());
  console.log('Received %d bytes from %s:%d\n',msg.length, info.address, info.port);
});
//sending msg
    
var data = Buffer.from([0x1f,0x0,0x32,0x33]);

client.send(data,4242,'fd96:5e1e:4749:1:1fdb:ff05:1113:b755',function(error){
    if(error){
        client.close();
    } else{
        console.log('Data sent !!!');
    }
});

The IPv6 address of the Microbit has been hard-coded for now (working on network discovery next). The payload received by the Microbit is prepared in the data Buffer object. The first byte selects which rows are to be activated in the LED matrix (there are 5 of them). The second byte is selects which columns are active. In the case of columns, a ‘0’ in a particular bit activates that column.

Code (such as it is) is over here on github

A closing note for now: This is tricky stuff to set up and get working. I should probably put together a post that details the entire process of setting this up and running. In the meantime, if you have questions send me an email

Dublin Maker 2022 happened!

Dublin Maker 2022 took place on the 23rd of July in Merrion Square in Dublin. Despite some initial rain it turned out to be a great day out (as usual). Lots of photos of the event can be seen here:

https://photos.app.goo.gl/8twxRULEAhV5F2vp6

Thanks to all the committee for organizing this great event.

The DMB 2022 badge runs Zephyr!

I finally finished (sort of) the code for the badge. It contains a number of games including:

Galaga

Galaga

Brici (like breakout)

Brici

An adventure game called Microrealms

Microrealms

A version of the Battleship game for two players (we did this on paper when were in school)

And a communications “applet”

This uses BLE Mesh which is made possible by Zepher OS (version 2.6.0). When the badge is idle it shows the Dublin Maker logo which includes a city skyline containing “The spire”. This pretends to be a radio antenna as shown below 🙂

Code for all of this is over on github here https://github.com/fduignan/nrf52833

This will not be the end of the road for this badge. I plan to add additional software and upgrade to a more recent version of Zephyr although I will have to learn more about the PINCTRL mechanism first.

Badge board bring-up

The badge PCB’s arrived! With more than a little trepidation parts were gradually added (checking for the magic smoke at each step) and in the end, everything worked!

Next steps are to make a couple more of them (to test the BLE mesh functionality) and then comes the arguably larger task of developing some games that will exercise the capabilities of the system.

Dublin Maker Badge PCB design

Well, Dublin Maker is coming up on the 23rd of July this year. The unofficial electronic badge design is coming along well. I think (hope!) that the PCB design is fine and it will be sent off for manufacture soon. You can see the 3D viewer output from KiCad below. U3 is the pin header for the display, U2 is a pin header for the boost converter. U1 is an NRF52833 module. SW7 is shown as a pin header but is in fact an on/off switch with the same pin pitch. A “Simple Add-On” connector (SAO1) is also provided. This nearly conforms to the BadgeLife SAO 1.69 standard. It provides power, I2C and a single GPIO (rather than 2). I ran out of GPIO port bits in the design as I will not be using the pads underneath the NRF52833 module (can’t hand solder them). The IDC socket for the SAO interface will not be populated in the final badge but will instead be left to anyone who cares to solder one on.

All components for the build have now arrived except for the battery cases. These can hold a single AA battery and are mounted on the back of the badge. One last check and it will be off to PCBWay with the design!

DMB2022 Graphics subsystem

DMB2022 (Dublin Maker Badge for 2022 (not official)) is an electronic badge consisting of an NRF52833 module and a display. The display for the badge is an ST7789 module. It has an SPI interface that is operated at 32MHz. It is configured to use 16 bit RGB colour values arranged as follows:

5 most significant bits : Red

6 middle bits : Green

5 least significant bits : Blue

The NRF52833’s SPI3 is used to drive the display as SPI0 and SPI1 are limited to 8Mbs. SPI3 can operate up to 32Mbs.

The display.cpp module handles all interaction with the ST7789. Lots of LCD displays of this type have a similar pattern of operations.

At startup, the display is first configured which typically involves bringing the device out of low power mode and configuring its colour mode, orientation and optionally its colour palette. The initialization code achieves this by sending commands and data to the display. The D/C signal tells the display whether a command or data byte is being transmitted. If it is Low then a command is being sent; a High level implies data is being sent.

In order to put pixels on the display, the controlling program must first open up an aperture for drawing in. This aperture can range from the entire display to just a single pixel. Once opened, data can be written to the aperture as a continuous stream of bytes. The display performs a raster like operation on the incoming data with display lines auto-wrapping when the data stream reaches the right-hand side of the aperture.

All of the remaining graphic primitives are built on top of this mechanism (with a few performance optimizations). The display class includes the following functions (for now):

int begin();
	void command(uint8_t cmd);
	void data(uint8_t data);
	void openAperture(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
	void putPixel(uint16_t x, uint16_t y, uint16_t colour);
	void putImage(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t *Image);
	void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t Colour);
	int iabs(int x); // simple integer version of abs for use by graphics functions        
	void drawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t Colour);
	void fillRectangle(uint16_t x,uint16_t y,uint16_t width, uint16_t height, uint16_t colour);
	void drawCircle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t Colour);
	void fillCircle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t Colour);
	void print(const char *Text, uint16_t len, uint16_t x, uint16_t y, uint16_t ForeColour, uint16_t BackColour);
	void print(uint16_t number, uint16_t x, uint16_t y, uint16_t ForeColour, uint16_t BackColour);
	uint16_t RGBToWord(uint16_t R, uint16_t G, uint16_t B);
	void drawLineLowSlope(uint16_t x0, uint16_t y0, uint16_t x1,uint16_t y1, uint16_t Colour);
	void drawLineHighSlope(uint16_t x0, uint16_t y0, uint16_t x1,uint16_t y1, uint16_t Colour);    

I will not do a deep dive on all of the functions, instead I will just focus on two of them : putPixel and fillRectangle.

Getting a dot on the screen : putPixel

This badge uses Zephyr OS as the underlying operating system (principally to make use of its Bluetooth Low Energy features). As a result it makes use of Zephyr’s peripheral device drivers to interface with the onboard peripherals of the NRF52833.

// Configuration for the SPI port.  Note the 32MHz clock speed possible only on SPI 3
// Pin usage by SPI bus defined in app.overlay.
static const struct spi_config cfg = {
	.frequency = 32000000,
	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB |  SPI_MODE_CPOL | SPI_MODE_CPHA,
	.slave = 0,
};
void display::putPixel(uint16_t x, uint16_t y, uint16_t colour)
{
    this->openAperture(x, y, x + 1, y + 1);
	struct spi_buf tx_buf = {.buf = &colour, .len = 2};
	struct spi_buf_set tx_bufs = {.buffers = &tx_buf, .count = 1};
	DCHigh();
	spi_write(spi_display, &cfg, &tx_bufs);
}

The SPI configuration structure cfg sets the SPI mode, bit order and role. It also specifies the transfer speed of 32Mbps. This is used in calls to spi_write.

Inside putPixel we see that a 1 pixel size aperture is opened on the display. Next, an spi_buf structure is prepared which contains a pointer to the data being written as well as it’s length. This is wrapped in an spi_buf_set structure required by the spi_write function. Finally, the D/C line is driven high indicating to the ST7789 that data is being written. The spi_write function handles the actual write operation. The first parameter for spi_write is a Zephyr device structure called spi_display which identifies the SPI device being used. This was obtained when the SPI interface was initialized and is used in a manner similar to a FILE structure in C file operations

As you can see from the above there is a bit of overhead associated with writing a pixel to the display. Functions in display.cpp that write multiple pixels don’t necessarily call putPixel as this would be too slow. Instead they interface directly with Zephyr’s I/O functions. An example of this is fillRectangle.

Filling large areas of the screen: fillRectangle

void display::fillRectangle(uint16_t x,uint16_t y,uint16_t width, uint16_t height, uint16_t colour)
{
	// This routine breaks the filled area up into sections and fills these.
	// This allows it to make more efficient use of the control lines and SPI bus
#define PIXEL_CACHE_SIZE 64
	static uint16_t fill_cache[PIXEL_CACHE_SIZE]; // use this to speed up writes
	uint32_t pixelcount = height * width;
	uint32_t blockcount = pixelcount / PIXEL_CACHE_SIZE;
	
	this->openAperture(x, y, x + width - 1, y + height - 1);
	DCHigh();
	struct spi_buf tx_buf = {.buf = &colour, .len = 2};
	struct spi_buf_set tx_bufs = {.buffers = &tx_buf, .count = 1};   

	if (blockcount)
	{
  		  for (int p=0;p<PIXEL_CACHE_SIZE;p++)
		  {
			fill_cache[p]=colour;
		  }
	}
	while(blockcount--)
	{
	   tx_buf.buf=fill_cache;
	   tx_buf.len = PIXEL_CACHE_SIZE*2;
	   spi_write(spi_display, &cfg, &tx_bufs);
	}

	pixelcount = pixelcount % PIXEL_CACHE_SIZE;
	while(pixelcount--) 
	{
	  tx_buf.buf = &colour;
 	  tx_buf.len = 2;		
		  spi_write(spi_display, &cfg, &tx_bufs);
	}	
}

Filling a rectangle on screen consists of two steps:

Open an aperture for the rectangle

Write pixel values to the display.

In an attempt to reduce the number of Zephyr API function calls this function divides the pixel data into chunks of size PIXEL_CACHE_SIZE. This allows that number of pixels to be written in a single call to spi_write (and maybe to leverage any optimization within the driver such as DMA). For example, suppose you want to fill an area of the screen consisting of 200 pixels. Using the value of 64 as a block size this would require the writing of three 64 pixel block and 8 individual pixel writes. So 200 calls to spi_write have been reduced to 11. If the function were to use putPixel then it would suffer from the additional overhead of opening an aperture for each individual pixel as well as controlling the D/C line for each of them. The above mechanism is a lot faster.

Code for all of this is very much a work in progress and is available over here on github. It will be changing a lot over the next few weeks

Testing an NRF52833 Module

The (unofficial) Dublin Maker Badge for 2022 will hopefully be based on an NRF52833 module. I got hold of couple and asked a colleague to make a breakout PCB for it to allow me experiment.

The PCB is a little rough but is OK for evaluation purposes. I won’t be using the inner layer of contacts for the module as I can’t solder on to them manually however I should have enough pins in what remains. Luckily the extremely flexible NRF52833 allows for routing of signals to just about any pin. Next step is to the module to the board.

The drill holes for the pin headers have more or less wiped out all of the pads for the pin headers but with some careful soldering I think I got everything wired up. Next step Blinky!

VDD and VDDH are connected to a 3.3V DC supply; the SWD interface is connected to a JLink EDU probe. A simple Zephyr based LED Blinky program was downloaded and tested. The JLink software complained a little about the SWD interface being unstable and it automatically dropped its speed to a lower value. Blinky seemed to work fine; how about a simple BLE example I previously used on the BBC Microbit V2? Well that worked fine too without any changes 🙂

Next step: Interface with a display to see if the SPI interface can be operated at full speed.

Dublin maker badge 2022

It looks like Dublin Maker will actually happen this year :). I had planned a second badge for 2020 but that never happened. I initially thought that I might simply move that plan to this year however testing of the radio link proved to be inadequate for my needs (it used an NRF24L01 module). In the meantime I have been working with Zephyr OS and the BBC Microbit V2. The Microbit is based on an NRF52833 MCU which is capable of doing BLE Mesh networking. This looks quite attractive so I have begun moving my design to this platform. Basic Mesh networking seems to be ok and the graphics routines are working. I need to do a full hardware prototype next using some NRF52833 modules before a batch of badges is produced.

This is the first in a set of posts that will follow the development of the badge.