nRF 9160DK: Offloading Bluetooth related tasks to nRF52

Background

Some time ago I was working on a project that involved communication between Bluetooth Mesh and a home automation platform. The goal of this project was to enable control and observability of Bluetooth Mesh light sources from the home automation platform. For this purpose, I decided to use the nRF9160 development kit (nRF91 DK). This kit is equipped with both a nRF9160 SiP (nRF91) and a nRF52840 SoC (nRF52). The former component provides the means of cellular communication, while the latter provides Bluetooth capabilities.

During my preliminary research I discovered that the nRF Connect SDK (NCS) did not provide any libraries or samples that enabled the use of Bluetooth Mesh directly from the nRF91. This meant that I would have to perform the interfacing between the two chips myself. Before starting the implementation, I studied the nRF9160: LTE Sensor Gateway sample in NCS. In this sample the nRF52 device is used as a Bluetooth controller, meaning that the chip is used purely for tasks associated with the link layer and the physical layer in the BLE protocol stack. The remaining tasks, namely those associated with the host and application layer in the stack, is handled by the nRF91. The connection between the two chips is carried out by a serial host controller interface (HCI) over UART, thus allowing communication between the host and controller part of the stack.

Figure 1: Original configuration using HCI on nRF91DK

HCI Benefits & Drawbacks

While a similar approach would be possible for the implementation of the Bluetooth Mesh stack on the nRF91 DK, I had some reservations. The nRF52 is fully capable of running the entire Bluetooth Mesh stack by itself. It therefore seems wasteful to only use this device for the lower layers of the stack, while the nRF91 would have to handle the majority of the Bluetooth Mesh workload, as well as the work regarding the cellular communication.

The main advantage of using HCI is that it offers a high degree of interoperability. This implies that it is easy to exchange the device that is communicating with the HCI controller. In the case of the nRF91 DK however it will in many cases be the nRF91 that is utilizing the nRF52 device for Bluetooth capabilities, the reason being that both devices are hardwired to the development kit PCB. In addition to the poor resource utilization, HCI also has some further drawbacks. The partitioning of the BLE stack (as shown in figure one) implies that all information that is associated with Bluetooth communication has to be passed over the serial line, which adds a certain amount of latency to each operation. The partitioning also deteriorates the principle of modularity to some extent. From a modularity perspective it would be desirable to contain all Bluetooth related functionality on one single device.

Bluetooth Mesh Offloading solution

The solution for my implementation was to put the entire Bluetooth Mesh stack on the nRF52, thus letting this device maintain the Bluetooth Mesh related workload autonomously from the nRF91. To enable communication between the two devices I implemented a generic UART interface. This interface was used at application level on both devices, allowing exchange of data and control messages.

The reason I decided to create a custom UART interface was that I was not able to find any existing sample or library in NCS that provided the desired functionality for a serial interface between the two devices on the nRF91 DK. I required a serial interface with the capability to send arbitrary byte-arrays reliably from the sender to the recipient. I also desired that the serial interface should support multiple channels so that it could be used for several purposes at once. The final solution for this interface was an interrupt driven, multi channel UART interface, combined with a preemptive thread for handling and buffering incoming serial traffic. To ensure reliable transfer of data I used SLIP encoding for the serial transfer, in addition to a CRC16_ANSI checksum to check the data consistency of every message transmission. The SLIP encoding library that was used was extracted from the legacy nRF5 SDK and slightly altered to comply with NCS. All other functionality was implemented with existing NCS intrinsics.

The final task that needed to be handled was the conversion of message formats between the serial interface and the Bluetooth Mesh. In my case, this task consisted of converting Bluetooth Mesh SIG model messages to a JSON MQTT format and vice versa. The reason why I decided to use this format is that many home automation platforms has support for connecting custom made IoT devices through JSON formatted MQTT messages. In my case I was using the MQTT integration on the Home Assistant platform.

BTM implementation with offloading solution.

General Bluetooth Offloading Approach

This approach for offloading Bluetooth related work to the nRF52 can be applied in several use cases, both within the BLE and Bluetooth Mesh application domain. It is especially well suited in cases where the nRF52 can work independently for most of its operational time, only needing to notify or be controlled by the nRF91 sporadically.

These are the general steps that is required to achieve offloading of Bluetooth related tasks to the nRF52 device:

  1. Add the BLE or Bluetooth Mesh stack to the nRF52 device: NCS offers numerous samples to use as reference for this operation.
  2. Add a serial interface on both the nRF52 and the nRF91 device: For this you could either find/develop your own solution, or you could use the interface I developed for my implementation.
  3. Create a message interface scheme that suits your application: This includes all data and control messages that you need for your particular use case.

Serial example

This section shows the application level code of a sample I created to demonstrate the functionality of my implementation of the serial interface. In this sample the nRF91 transmits a message string every second over the UART. This string is then received by the nRF52, printed, and then re-transmitted back to the nRF91. When the nRF91 receives this loop-back message it is also printed.

/* main.c of the nRF9160 SiP */

#include <zephyr.h>
#include <stdio.h>
#include <net/buf.h>
#include "uart_simple.h"

void mqtt_rx_callback(struct net_buf *get_buf);

static struct uart_channel_ctx mqtt_serial_chan = {
	.channel_id = 1,
	.rx_cb = mqtt_rx_callback,
};

void mqtt_rx_callback(struct net_buf *get_buf)
{
	int len = get_buf->len;
	char *msg = net_buf_pull_mem(get_buf, get_buf->len);

	printk("Loopback msg: ");
	for (size_t i = 0; i < len; i++) {
		printk("%c", msg[i]);
	}
}

static struct k_delayed_work serial_send_work;

static void serial_send(struct k_work *work)
{
	static uint16_t cnt;
	char msg[30];
	int len = sprintf(msg, "Serial message: %d\n", cnt++);
	uart_simple_send(&mqtt_serial_chan, (uint8_t*)msg, len);
	k_delayed_work_submit(&serial_send_work, K_MSEC(1000));
}

void main(void)
{
	printk("nrf9160DK_9160 serial sample has started\n");

	uart_simple_init();
	uart_simple_channel_create(&mqtt_serial_chan);
	k_delayed_work_init(&serial_send_work, serial_send);
	k_delayed_work_submit(&serial_send_work, K_NO_WAIT);
}

/* main.c of the nrf52840 SoC */

#include <zephyr.h>
#include <stdio.h>
#include <net/buf.h>
#include "uart_simple.h"

void mqtt_rx_callback(struct net_buf *get_buf);

static struct uart_channel_ctx mqtt_serial_chan = {
	.channel_id = 1,
	.rx_cb = mqtt_rx_callback,
};

void mqtt_rx_callback(struct net_buf *get_buf)
{
	int len = get_buf->len;
	char *msg = net_buf_pull_mem(get_buf, get_buf->len);

	for (size_t i = 0; i < len; i++) {
		printk("%c", msg[i]);
	}
	uart_simple_send(&mqtt_serial_chan, (uint8_t*)msg, len);
}

void main(void)
{
	printk("nrf9160DK_52840 serial sample started\n");

	uart_simple_init();
	uart_simple_channel_create(&mqtt_serial_chan);
}

The complete source code for this sample can be found on this branch in my fork of the NCS repository.

To observe the printed result of the sample you may use a terminal program like PuTTY or Teraterm to connect to the nRF91 & nRF52 devices. The default serial configuration is as following:

  • Baudrate: 115200
  • Data: 8 bits
  • Parity: None
  • Stop bits: 1
  • Flow Control: None

The result should look something like this when running the sample:


Anonymous
  • I think this approach could be adapted to other home automation platforms fairly easy.

    During my research I found that both Home Assistant and openHAB had decent support for MQTT. The advantage with these two choices is of course that they are open source, where the documentation is fully available and a greater extent of community support for application development.

    I also know that Google Home and HomeKit has support for connecting devices through MQTT interfacing.

  • This is a really interesting article. How difficult would it be to have a similar approach to other HA protocols?