nRF9160 DK: Offloading Bluetooth Low Energy related tasks to nRF52840 SoC

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. This kit is equipped with both a nRF9160 SiP and a nRF52840 SoC. 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 nRF9160 SiP. 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 nRF52840 SoC 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 Bluetooth LE protocol stack. The remaining tasks, namely those associated with the host and application layer in the stack, is handled by the nRF9160 SiP. 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 nRF9160 DK, I had some reservations. The nRF52840 SoC 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 nRF9160 SiP 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 nRF9160 DK however it will in many cases be the nRF9160 SiP that is utilizing the nRF52840 SoC 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 Bluetooth LE 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 nRF52840 SoC, thus letting this device maintain the Bluetooth mesh related workload autonomously from the nRF9160 SiP. 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 nRF9160 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 nRF52840 SoC can be applied in several use cases, both within the Bluetooth LE and Bluetooth mesh application domain. It is especially well suited in cases where the nRF52840 SoC can work independently for most of its operational time, only needing to notify or be controlled by the nRF9160 SiP sporadically.

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

  1. Add the Bluetooth LE or Bluetooth mesh stack to the nRF52840 SoC : NCS offers numerous samples to use as reference for this operation.
  2. Add a serial interface on both the nRF52840 SoC and the nRF9160 SiP: 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 nRF9160 SiP transmits a message string every second over the UART. This string is then received by the nRF52840 SoC, printed, and then re-transmitted back to the nRF9160 SiP. When the nRF9160 SiP 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 nRF9160 SiP & nRF52840 SoC. 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