More Microbit V2 and Zephyr code

I was in touch with the Zephyr developers about a bug in the driver for the magnetometer used on the BBC microbit. They kindly fixed it and I have modified my previous magnetometer example. I have also been working on a stripped down BLE example which provides a single service with a single Read/Write/Notify characteristic. The original zephyr set of examples has a very good but also quite complex BLE example. The example makes use of lots of macros that construct various structures and arrays. These can be a little daunting for a beginner. I have tried to remove anything that is non-essential for this example and have added additional comments and references to header files and web resources that will hopefully explain what is going on a little better.

The listing for main.c is shown below. The full set of examples is over here on github. Feel free to post questions in the comments section.

/* main.c - Application main entry point */

/* Based on an example from Zephyr toolkit, modified by frank duignan
 * Copyright (c) 2015-2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */
/* This example advertises three services:
 * 0x1800 Generic ACCESS (GAP)
 * 0x1801 Generic Attribute (GATT - this is part of the software device and is not used nor is it apparently removable see https://devzone.nordicsemi.com/f/nordic-q-a/15076/removing-the-generic-attribute-0x1801-primary-service-if-the-service-changed-characteristic-is-not-present
 * And a custom service 1-2-3-4-0 
 * This custom service contains a custom characteristic called char_value
 */
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <sys/printk.h>
#include <sys/byteorder.h>
#include <zephyr.h>

#include <settings/settings.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
#include <device.h>
#include <drivers/sensor.h>
#include <stdio.h>



#define BT_UUID_CUSTOM_SERVICE_VAL BT_UUID_128_ENCODE(1, 2, 3, 4, (uint64_t)0)
static struct bt_uuid_128 my_service_uuid = BT_UUID_INIT_128( BT_UUID_CUSTOM_SERVICE_VAL);
static struct bt_uuid_128 char_id=BT_UUID_INIT_128(BT_UUID_128_ENCODE(1, 2, 3, 4, (uint64_t)5)); // the 128 bit UUID for this gatt value
uint32_t char_value; // the gatt characateristic value that is being shared over BLE	
static ssize_t read_char(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset);
static ssize_t write_char(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags);

/* The bt_data structure type:
 * {
 * 	uint8_t type : The kind of data encoded in the following structure
 * 	uint8_t data_len : the length of the data encoded
 * 	const uint8_t *data : a pointer to the data
 * }
 * This is used for encoding advertising data
*/
/* The BT_DATA_BYTES macro
 * #define BT_DATA_BYTES(_type, _bytes...) BT_DATA(_type, ((uint8_t []) { _bytes }), sizeof((uint8_t []) { _bytes }))
 * #define BT_DATA(_type, _data, _data_len) \
 *       { \
 *               .type = (_type), \
 *               .data_len = (_data_len), \
 *               .data = (const uint8_t *)(_data), \
 *       }
 * BT_DATA_UUID16_ALL : value indicates that all UUID's are listed in the advertising packet
*/
// bt_data is an array of data structures used in advertising. Each data structure is formatted as described above
static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), /* specify BLE advertising flags = discoverable, BR/EDR not supported (BLE only) */
	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_CUSTOM_SERVICE_VAL /* A 128 Service UUID for the our custom service follows */),
};
	
/*
 * #define BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, _read, _write, _value) 
 * 
 */
BT_GATT_SERVICE_DEFINE(my_service_svc,
	BT_GATT_PRIMARY_SERVICE(&my_service_uuid),
		BT_GATT_CHARACTERISTIC(&char_id.uuid,		
		BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |  BT_GATT_CHRC_NOTIFY,
		BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
		read_char, write_char, &char_value),
);


struct bt_conn *active_conn=NULL; // use this to maintain a reference to the connection with the central device (if any)


// Callback that is activated when the characteristic is read by central
static ssize_t read_char(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset)
{
	printf("Got a read %p\n",attr);
	// Could use 'const char *value =  attr->user_data' also here if there is the char value is being maintained with the BLE STACK
	const char *value = (const char *)&char_value; // point at the value in memory
	return bt_gatt_attr_read(conn, attr, buf, len, offset, value, sizeof(char_value)); // pass the value back up through the BLE stack
}
// Callback that is activated when the characteristic is written by central
static ssize_t write_char(struct bt_conn *conn, const struct bt_gatt_attr *attr,
			 const void *buf, uint16_t len, uint16_t offset,
			 uint8_t flags)
{
	uint8_t *value = attr->user_data;
	printf("Got a write\n");
	memcpy(value, buf, len); // copy the incoming value in the memory occupied by our characateristic variable
	return len;
}
// Callback that is activated when a connection with a central device is established
static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err) {
		printk("Connection failed (err 0x%02x)\n", err);
	} else {
		printk("Connected\n");
		active_conn = conn;
	}
}
// Callback that is activated when a connection with a central device is taken down
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	printk("Disconnected (reason 0x%02x)\n", reason);
	active_conn = NULL;
}
// structure used to pass connection callback handlers to the BLE stack
static struct bt_conn_cb conn_callbacks = {
	.connected = connected,
	.disconnected = disconnected,
};
// This is called when the BLE stack has finished initializing
static void bt_ready(void)
{
	int err;
	printk("Bluetooth initialized\n");

// start advertising see https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/reference/bluetooth/gap.html
/*
 * Excerpt from zephyr/include/bluetooth/bluetooth.h

 * #define BT_LE_ADV_CONN_NAME BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | \
                                            BT_LE_ADV_OPT_USE_NAME, \
                                            BT_GAP_ADV_FAST_INT_MIN_2, \
                                            BT_GAP_ADV_FAST_INT_MAX_2, NULL)

 Also see : zephyr/include/bluetooth/gap.h for BT_GAP_ADV.... These set the advertising interval to between 100 and 150ms
 
 */
// Start BLE advertising using the ad array defined above
	err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
	if (err) {
		printk("Advertising failed to start (err %d)\n", err);
		return;
	}
	printk("Advertising successfully started\n");
}

void main(void)
{
	int err;

	err = bt_enable(NULL);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	bt_ready();
	bt_conn_cb_register(&conn_callbacks);
	printk("Zephyr Microbit V2 minimal BLE example! %s\n", CONFIG_BOARD);			
	while (1) {
		k_sleep(K_SECONDS(1));
		char_value++;
		// int bt_gatt_notify(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, u16_t len)
		// conn: Connection object. (NULL for all)
		// attr: Characteristic Value Descriptor attribute.
		// data: Pointer to Attribute data.
		// len: Attribute value length.				
		if (active_conn)
		{
			bt_gatt_notify(active_conn,&my_service_svc.attrs[2], &char_value,sizeof(char_value));			
		}	
	}
}

Accessing the BBC Microbit V2 test points

The BBC Microbit V2’s I2C interface usage is different to it’s predecessor. It has two I2C interfaces : an internal one to talk to the on-board accelerometer/magnetometer and an external one for user supplied sensors. Traffic on the internal I2C bus is only visible on tespoints (TP20 and TP21). This makes it difficult to debug/view the internal I2C bus traffic. I had no springloaded test pins to press on to the testpoints so a quick hack as shown above provides just enough pressure on the pads to make an electrical connection. The orange and purple wires are coiled like a spring which causes them to press into the board. These wires are then connected to a logic analyzer via the breadboard. The analyzer displays the following data:

The I2C clock frequency seems to be 400kHz. There appears to be some kind of clock stretching going on also. The trace shows a read from the on-board accelerometer.

Zephyr OS on the BBC Microbit V2

In previous years I used mbed OS to program the BBC Microbit (V1). As far as I can tell, the V2 board is not supported in mbed’s web compiler (yet?). So I began to look around at alternatives operating systems that would help me develop BLE peripheral applications. I considered install size and system requirements and decided that Zephyr looked like a good fit. I have begun writing examples for the various peripherals on the microbit v2 source code for which is over on github.

You will need to install zephyr to compile these. I found that the Getting started guide worked well. I compile my examples within the zephyrproject/zephyr directory (copy them from github to here) with the following command:

west build -b bbc_microbit_v2 magnetometer_serial_microbit_v2 –pristine

This will wipe the build directory and recompile the magnetometer example. Change “magnetometer_serial_microbit_v2” to one of the other directory names when you want to try them out. The output from the application on the microbit is sent to UART at 115200 bps.

I’m using Zephyr SDK version 0.12.4

An 8 digit VFD for the ESP32

I got hold of a Vacuum Fluorescent Display module from Aliexpress. It comes in two versions : one with an SPI interface, one without. I went with the SPI interface version. The display reminded me of a clock radio I had growing up so it was natural to put it to work as a clock. I wired it to an ESP32-CAM module as shown below:

Details about how the display is programmed were found over here. I wanted to use the SPI hardware interface instead of bit-banging the data and so developed the following program using Arduino for ESP32:

#include <SPI.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <HTTPClient.h>

/*
  ESP32 based clock.
  Uses Vacuum Fluourescent Display (VFD)
  Gets time from an NTP server
*/

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

const char* ssid = "***********";
const char* password =  "****************";

class VFDisplay
{
  public:
    VFDisplay() {};
  void begin()
  {     
    SPI.begin(SCK,MISO,MOSI,SS);
    pinMode(Reset,OUTPUT);
    pinMode(CS,OUTPUT);
    digitalWrite(Reset, LOW);
    delayMicroseconds(5);
    digitalWrite(Reset, HIGH);
    
    setDigitCount(8);
    setBrightness(127);    
    Serial.printf("VFD_init\n");
  }  
  void putChar(unsigned char x, char chr)
  {
    digitalWrite(CS, LOW); 
    writeDisplay(0x20 + x); 
    writeDisplay(chr);
    digitalWrite(CS, HIGH); 
    show();    
  }
  void printString(char *str)
  {
    int x = 0;
    while(*str)
    {
      putChar(x++,*str++);
    }
  }      
  private:
  void setDigitCount(uint8_t count)
  {
      digitalWrite(CS, LOW);
      writeDisplay(0xe0);
      delayMicroseconds(5);
      writeDisplay(count-1);
      digitalWrite(CS, HIGH);
      delayMicroseconds(5);  
  }
  void setBrightness(uint8_t brightness)
  {
    digitalWrite(CS, LOW);
    writeDisplay(0xe4);
    delayMicroseconds(5);
    writeDisplay(brightness);
    digitalWrite(CS, HIGH);
    delayMicroseconds(5);
  }
  void show()
  {    
    digitalWrite(CS, LOW);
    writeDisplay(0xe8);     
    digitalWrite(CS, HIGH); 
  }
  void writeDisplay(uint8_t b)
  {
    SPI.beginTransaction(SPISettings(1000000, LSBFIRST, SPI_MODE0));
    SPI.write(b);
    SPI.endTransaction();
  }

  uint8_t Reset=15;
  uint8_t CS = 12;
  uint8_t SCK = 4;
  uint8_t MOSI = 2;
  uint8_t MISO = 1;
  uint8_t SS = 12; // not actually used - see CS above

};

VFDisplay vfdisplay;
void setup() {
    
  Serial.begin(115200);  
  vfdisplay.begin();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    vfdisplay.printString("WifiWait");    
    delay(1000);    
  }
  vfdisplay.printString("WifiDone");
  timeClient.begin();
  timeClient.setTimeOffset(3600);
  timeClient.update();
  timeClient.setUpdateInterval(5*60*1000); // update from NTP only every 5 minutes  
}

void loop() {
  timeClient.update();     // This will only go to the Internat if the update interval has passed (set to 5 minutes above)
  String timeString = timeClient.getFormattedTime();
  char TimeCharArray[20];
  timeString.toCharArray(TimeCharArray,19);
  vfdisplay.printString(TimeCharArray);
  delay(100);
}

The system appears to work well and could be extended to include a Bluetooth interface to set an alarm time or time zone etc.