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

OpenThread experiments

I have been experimenting with OpenThread using a RaspberryPi with Nordic 52840 Dongle and a pair of Xiao BLE modules.

The topology looks like this:

What does this let me do? As it stands I can ping either of the Xiao BLE devices from any computer on my network using IPv6. The RaspberryPi+NRF52840 dongle behave as a border router and bridges between the OpenThread/6LowPan network and the wired Ethernet.

This has not been entirely straightforward so far. The XIAO-BLE devices are running Zephyr’s sample echo_server built with this command line:

west build -b xiao_ble echo_server -- -DCONF_FILE="prj.conf overlay-ot.conf" 

Prior to this, the project configuration file (prj.conf) was modified as follows:

# Generic networking options
CONFIG_NETWORKING=y
CONFIG_NET_UDP=y
CONFIG_NET_TCP=y
CONFIG_NET_IPV6=y
CONFIG_NET_IPV4=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_POSIX_MAX_FDS=6
CONFIG_NET_CONNECTION_MANAGER=y

# Kernel options
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_ENTROPY_GENERATOR=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_INIT_STACKS=y

# Logging
CONFIG_NET_LOG=y
CONFIG_LOG=y
CONFIG_NET_STATISTICS=y
CONFIG_PRINTK=y

# Network buffers
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=64
CONFIG_NET_BUF_TX_COUNT=64
CONFIG_NET_CONTEXT_NET_PKT_POOL=y

# IP address options
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4
CONFIG_NET_MAX_CONTEXTS=10

# Network shell
CONFIG_NET_SHELL=y
CONFIG_SHELL=y

# Network application options and configuration
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV6=y
#CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::3"
#CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::1"
#CONFIG_NET_CONFIG_NEED_IPV4=y
#CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
#CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"

# Number of socket descriptors might need adjusting
# if there are more than 1 handlers defined.
CONFIG_POSIX_MAX_FDS=12

# How many client can connect to echo-server simultaneously
CONFIG_NET_SAMPLE_NUM_HANDLERS=1

CONFIG_OPENTHREAD_DHCP6_SERVER=y
CONFIG_OPENTHREAD_SLAAC=y
CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=6
# need to add this so that the module can join the thread network
CONFIG_OPENTHREAD_JOINER=y

This creates a file called Zephyr.uf2 which is copied to the XIAO devices when they are in UF2 bootloader mode (press the button on them twice).

The NRF52840 dongle code was obtained from https://github.com/openthread/ot-nrf528xx/blob/main/src/nrf52840/README.md

This was compiled as follows:

./script/build nrf52840 USB_trans -DOT_BOOTLOADER=USB
arm-none-eabi-objcopy -O ihex build/bin/ot-rcp ot-rcp.hex
nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application ot-rcp.hex --application-version 1 ot-rcp.zip
nrfutil dfu usb-serial -pkg ot-rcp.zip -p /dev/ttyACM0

(The NRF52840 dongle has to be in DFU mode for the last line to work)

Finally, the raspberry pi 3 had to be set up. This is running Raspbian and the OpenThread border router services were obtained by cloning https://github.com/openthread/ot-br-posix, building and compiling (and much fiddling about!).

Where to from here? Well, the whole point of this experiment is to compare the BLE/GATT/GAP approach to IoT to one using IPv6 and “traditional” network function calls.