Getting Started with Zephyr: Low Energy Bluetooth

Getting Started with Zephyr: Low Energy Bluetooth

Overview

Low Energy Bluetooth (BLE) is one of the most popular communication protocols for IoT devices. While it shares a name with traditional Bluetooth technology, Bluetooth Classic, BLE is considered a completely different technology, designed for different types of devices. The main difference between Bluetooth Classic and BLE lies in the duty cycle.

BLE achieves its “low energy” characteristic by attempting to keep the radio module powered off as much as possible. The radio module is usually the component that consumes the most power on IoT devices, so keeping it off as much as possible can significantly reduce overall power consumption. In contrast, Bluetooth Classic is designed to keep the radio on as much as possible to minimize latency or maximize bandwidth. Although the details of BLE are beyond the scope of this article, the NovelBits website (https://novelbits.io/bluetooth-low-energy-ble-complete-guide/) provides a comprehensive introduction.The Zephyr Project RTOS (https://zephyrproject.org/) has a mature BLE subsystem that supports popular devices. In this blog post, I will demonstrate how to run our application on the Nordic nRF52840 development kit (https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk) to support BLE.Kconfig

Like most Zephyr-based applications, we need to use Kconfig to enable the necessary features. We need to set the following Kconfig options in the prj.conf file:

  • CONFIG_BT: Enable the BLE subsystem.
  • CONFIG_BT_PERIPHERAL: Configure the device as a BLE peripheral.
  • CONFIG_BT_DEVICE_NAME: The string to be advertised as the device name.
  • CONFIG_BT_HCI: Enable the Host Controller Interface subsystem.

Zephyr also includes configuration options to enable common BLE services. For example, the Device Information Service (DIS) provides information about the device’s manufacturer. We can enable DIS in our application by enabling the following configuration options:

  • CONFIG_BT_DIS: Enable DIS.

  • CONFIG_BT_DIS_MANUF: Set the name of the device manufacturer, which is advertised as part of DIS.
  • CONFIG_BT_DIS_MODEL: Set the model of the device.
  • CONFIG_BT_DIS_SERIAL_NUMBER: Set the serial number of the device.

Source Code

Zephyr provides some simple macros and functions that allow us to customize BLE products in our application. Before reviewing the source code, we need to go over some BLE terminology:

  • GATT: Stands for Generic Attribute Profile, which specifies the details of how devices send information via BLE.

  • Service Service: A collection of information transmitted via BLE, such as sensor data.

  • Characteristic Characteristic: The location and manner of presenting actual information.

  • UUID: Abbreviation for Universally Unique ID, a number used to identify services and characteristics.

  • Read Permission Read Permission: An attribute associated with information that can be queried asynchronously by a remote device via BLE.

  • Notify Permission Notify Permission: An attribute associated with information that can notify a remote device immediately when changes occur via BLE.

We can use several macros to implement custom services and characteristics. First, we need to define some base UUIDs that can serve as the basis for the UUIDs used by our services and characteristics.

We can define base UUIDs using the following macros:

#define CUSTOM_BASE_UUID_w32  0x12345678#define CUSTOM_BASE_UUID_w1   0x90AB#define CUSTOM_BASE_UUID_w2   0xCDEF#define CUSTOM_BASE_UUID_w3   0x0123#define CUSTOM_BASE_UUID_w48  0x456789ABCDEF

Then, we can use the macros available in Zephyr to define UUIDs corresponding to our services and characteristics, as shown below:

#define CUSTOM_SERVICE_UUID                   0x1#define CUSTOM_CHARACTERISTIC_UUID            0x2#define BT_UUID_CUSTOM_SERVICE             BT_UUID_128_ENCODE(CUSTOM_BASE_UUID_w32, CUSTOM_BASE_UUID_w1, CUSTOM_BASE_UUID_w2, CUSTOM_BASE_UUID_w3, CUSTOM_BASE_UUID_w48 + CUSTOM_SERVICE_UUID)#define BT_UUID_CUSTOM_CHARACTERISTIC      BT_UUID_128_ENCODE(CUSTOM_BASE_UUID_w32, CUSTOM_BASE_UUID_w1, CUSTOM_BASE_UUID_w2, CUSTOM_BASE_UUID_w3, CUSTOM_BASE_UUID_w48 + CUSTOM_CHARACTERISTIC)

Finally, we can use the macros defined above to initialize the data structures for the UUIDs that will ultimately be used to instantiate BLE services and characteristics.

static struct bt_uuid_128 custom_service_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_SERVICE);static struct bt_uuid_128 custom_characteristic_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_CHARACTERISTIC)

Then, we can use the macros provided by Zephyr to create a custom service with custom characteristics.

BT_GATT_SERVICE_DEFINE(custom_service,    BT_GATT_PRIMARY_SERVICE(&custom_uuid),    BT_GATT_CHARACTERISTIC(&custom_characteristic_uuid.uuid,                           BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,                           BT_GATT_PERM_READ,                           custom_data_callback, NULL, custom_data),    BT_GATT_CCC(nextiles_motion_notify_cb, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE));

The following list describes the permission macros and data types defined above:

  • BT_GATT_CHRC_READ: Allows a remote device to read a specific characteristic value.

  • BT_GATT_CHRC_NOTIFY: Allows notifying a remote device of corresponding data.

  • BT_GATT_PERM_READ: Sets the read permission attributes for the characteristic.

  • custom_data_callback: The function called when a remote device attempts to read the characteristic: A byte array used to send notifications to the remote device.

The response to characteristic reads and notifications is as follows:

static uint8_t custom_data[DATA_LEN];static ssize_t custom_data_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset){    return bt_gatt_attr_read(conn, attr, buf, len, offset, custom_data, DATA_LEN);}void some_thread(void *p1, void *p2, void *p3){    while (1) {        struct bt_gatt_attr *custom_char = bt_gatt_find_by_uuid(attr_custom_bt_service, 0 &custom_characteristic_uuid);        bt_gatt_notify(NULL, custom_char, custom_data, sizeof(motion_data));        k_msleep(MSEC(1000));    }}

In this article, I introduced how to set up a Zephyr application that supports BLE. I demonstrated the configuration options that need to be enabled and configured and showed how to use the macros and functions provided by Zephyr to configure custom BLE services and characteristics.

Leave a Comment