Moving a BLE application from Zephyr 2.6.0 to version 3.1.0

I have been using the Microbit V2 as a BLE development tool with our engineering students. Part of this involves looking below the covers at what is happening on the I2C bus. Students interact with the built-in accelerometer using I2C reads and writes (i.e. not using Zephyr’s high level driver). Version 2.6.0 of Zephyr allowed us to do this without many problems. Students were given a basic BLE step counter application which they analysed and extended in a practical session. Zephyr OS is under constant development of course and it has moved on to version 3.1 over the past few months. Naturally this broke all of my sample code 🙂

So what’s changed. Well, first of all, the Zephyr include path has gone from

#include <bluetooth/bluetooth.h>

to

#include <zephyr/bluetooth/bluetooth.h>

Now there is a project configuration variable that allows legacy include directory names but I decided that I would go in and make the changes to the various source files. This turned out to be the easiest change to make.

The next hurdle turned out to be the PINCTRL system built in to Device Tree. This is something I avoided in Zephyr 2.6 as it has a big enough learning curve, lacks some documentation and is very different to the bare-metal microcontroller way of managing I/O pins. The approach taken was instead to assign pins to functions in the app.overlay file as follows:


&i2c1 { 
	compatible = "nordic,nrf-twim"; 
	status = "okay"; 
	sda-pin = < 16 >;// P0.16 = I2C_INT_SDA 
	scl-pin = < 8 >; // P0.8 = I2C_INT_SCL 
}; 
&spi2 { 
	compatible = "nordic,nrf-spi"; 
	status = "okay"; 
	sck-pin = <17>; 
	mosi-pin = <13>; 
/* Redirecting MISO to a pin that is not connected on the microbit v2 board */ 
	miso-pin = <27>; 
	clock-frequency = <1000000>; 
}; 
&adc { 
	status = "okay"; 
}; 
&pwm0 { 
	status = "okay"; 
// P0.3 is labelled RING1 on the microbit. 
	ch0-pin = <3>; 
};


Attempting to do this with version 3.1 results in lots of errors of the following form:

static assertion failed: “/soc/spi@40023000 has legacy *-pin properties defined although PINCTRL is enabled

Why does this happen? When I build the code I use the following command:

west build -b bbc_microbit_v2 ble_stepcount -p

This uses the board definitions for the bbc_microbit_v2 device as defined in the directory zephyr/boards/arm/bbc_microbit_v2/

This directory contains a number of files that define the device tree and various project build options. The result is that the build system requires all pin definitions to be carried out using the PINCTRL mechanism. So what’s different? Here’s the PINCTRL version of my app.overlay file.

&pinctrl {
   /* IMPORTANT!  There should not be a space before the : in the next line (and 	similar below) */
    spi2_default_alt: spi2_default_alt {
        group1 {
            psels = <NRF_PSEL(SPIM_MOSI,0,17)>,
                    <NRF_PSEL(SPIM_SCK,0,13)>;
        };
        group2 {
        	psels = <NRF_PSEL(SPIM_MISO, 0, 27)>;
	      bias-pull-down;
        };
        
    };
    spi2_sleep_alt: spi2_sleep_alt {
        group1 {
            psels = <NRF_PSEL(SPIM_MOSI,0,17)>,
                    <NRF_PSEL(SPIM_SCK,0,13)>,
                    <NRF_PSEL(SPIM_MISO, 0, 27)>;
            low-power-enable;
        };
    };
    i2c1_default_alt: i2c1_default_alt {
        group1 {
            psels = <NRF_PSEL(TWIM_SDA,0,16)>,
                    <NRF_PSEL(TWIM_SCL,0,8)>;
            bias-pull-up;
        };
    };
    i2c1_sleep_alt: i2c1_sleep_alt {
        group1 {
            psels = <NRF_PSEL(TWIM_SDA,0,16)>,
                    <NRF_PSEL(TWIM_SCL,0,8)>;
            low-power-enable;
        };
    };
 };
 
 &spi2 {
    compatible = "nordic,nrf-spi";
    status = "okay";
    pinctrl-0 = <&spi2_default_alt>;
    pinctrl-1 = <&spi2_sleep_alt>;
    pinctrl-names = "default", "sleep";

    clock-frequency = <1000000>;
};
&i2c1 {
	compatible = "nordic,nrf-twim";
	status = "okay";
	pinctrl-0 = <&i2c1_default_alt>;
	pinctrl-1 = <&i2c1_sleep_alt>;
	pinctrl-names = "default", "sleep";
	label = "I2C_1";
};
&i2c0 {
    status="disabled";
};
&gpio0 {
    status="okay";
    label="GPIO_0";
};
&adc {
        status = "okay";
};
&pwm0 {
        status = "okay";
// P0.3 is labelled RING1 on the microbit. 
        ch0-pin = <3>; 
};


		

As you can see, there are lots of changes. The pinctrl section is new. This section allows us to redefine the pins used by the microcontroller peripherals spi2 and i2c1. We write definitions for each of these for the default powered up state and for the sleep state. (Definitions for sleep state are not required if you disable power management in the project configuration file). States contain one or more groups of pin definitions. These groups allocate actual pin numbers to peripheral functions and can set group properties such as whether there will be pull-up resistors etc. Pin assignment for SPI2 is follows:

psels = <NRF_PSEL(SPIM_MOSI,0,17)>,
<NRF_PSEL(SPIM_SCK,0,13)>,
<NRF_PSEL(SPIM_MISO, 0, 27)>;

This states that MOSI is on GPIO0, bit 17, SCK GPIO0, bit 13 etc. The trickiest part of this is finding out what are the correct names of the pins e.g. SPIM_MOSI, TWIM_SDA and so on. I found them in

zephyr/boards/arm/bbc_microbit_v2/bbc_microbit_v2-pinctrl.dtsi and

zephyr/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833-pinctrl.dtsi

Having defined the pin assignments the next sections (&spi2, &i2c1) allow you to apply them to the hardware in this project. In the case of spi2, the pinctrl settings for state 0 (default) are remapped to spi2_default_alt with this line:

pinctrl-0 = <&spi2_default_alt>;

The configuration for sleep mode are configured by assigning a value to pinctrl-1 as follows:

pinctrl-1 = <&i2c1_sleep_alt>;

In my C code I had acquired a device handle I2C1 and GPIO0 using the names “I2C_1” and “GPIO_0” respectively. This did not work with Zephyr 3.1 but inserting label directives as shown above fixed this error.

At this point, the code would build and appear to work with one exception: Bluetooth notify failed to work correctly. I saw lots of warnings of the following form in a serial terminal monitoring the microbit:

<wrn> Device is not subscribed to characteristic

This happened when the bt_gatt_notify function was called in response to a change in step count value. Fixing this error took a while! The original BLE characteristic and service definition was as follows:

#define BT_GATT_CHAR1 \ BT_GATT_CHARACTERISTIC(&stepcount_id.uuid,BT_GATT_CHRC_READ | \  BT_GATT_CHRC_WRITE |  BT_GATT_CHRC_NOTIFY, 	BT_GATT_PERM_READ | \ BT_GATT_PERM_WRITE, read_stepcount, write_stepcount, &stepcount_value)


// ********************[ Service definition ]********************
#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);

BT_GATT_SERVICE_DEFINE(my_service_svc,
	BT_GATT_PRIMARY_SERVICE(&my_service_uuid),
		BT_GATT_CHAR1
);

The new definition and additional code is as follows:

#define BT_GATT_CHAR1 BT_GATT_CHARACTERISTIC(&stepcount_id.uuid,BT_GATT_CHRC_READ | \ BT_GATT_CHRC_WRITE |  BT_GATT_CHRC_NOTIFY | BT_GATT_CCC_NOTIFY, \ BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, read_stepcount, write_stepcount, \ &stepcount_value)

static void step_changed(const struct bt_gatt_attr *attr,
                                 uint16_t value)
{   if (value==BT_GATT_CCC_NOTIFY)
    {
       printk("Subscribed\n");
       Subscribed = 1;
    }
    else
    {
        printk("Not Subscribed\n");
        Subscribed = 0;
    }
}



// ********************[ Service definition ]********************
#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);
BT_GATT_SERVICE_DEFINE(my_service_svc,
	BT_GATT_PRIMARY_SERVICE(&my_service_uuid),
		BT_GATT_CHAR1,
        BT_GATT_CCC(step_changed,BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)

Changes are highlighted in bold. What are the changes all about?

BT_GATT_CCC_NOTIFY : configures the attribute to send notifications if there are changes to attribute values.

Function step_changed : This function is a callback that is activated when there is a change to the subscription status of a BLE client. It updates a global variable called Subscribed which is used to later to determine whether a ble_gatt_notify event is sent.

BLE_GATT_CCC : This macro defines what looks like a new attribute of the characteristic BT_GAT_CHAR1 (the step counter). This attribute can be read and written by the client device to enable or disable notifications. When a BLE client subscribes or unsubscribes from notifications for this characteristic, the function step_changed is called (CCC stands for Client Characteristic Configuration).

All of the above took quite some time and I hope the description is of some help to other lost souls. Code is available over here on github. This will grow as a port the rest of my examples.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s