Reading Thingy:53 sensor data using Bluetooth LE

Reading Thingy:53 sensor data using Bluetooth LE

The Thingy:53 is a bleeding edge IoT prototyping platform featuring the flagship nRF5340 dual-core Wireless SoC. It also contains several sensors and a rechargeable Li-Po battery, making it an excellent tool to rapidly prototype an application that can be deployed in the field.  To learn more about Thingy:53, please visit the product webpage and watch the webinar, which is available on demand.

In this blog post, we will set up the Thingy:53 as a sensor hub that takes measurements from the onboard BME688 environmental sensor, BH1749 color and light sensor, and the on-chip SAADC to read out the Li-Po battery voltage. The data will also be available to read over GATT by a connected Bluetooth LE central device.

We will go through all the necessary steps to get this prototype up and running in the section below, but if you wish to skip directly to the finished firmware to test it for yourself, it is linked at the end of this blog post. If you at any point are unsure about the Zephyr devicetree, application configuration, or macro-usage, then we would highly recommend going through the nRF Connect SDK Fundamentals course in the Nordic Developer Academy before proceeding. It is an excellent course for gaining an understanding of how the nRF Connect SDK works behind the scenes.

Setting up the project

Having installed the nRF Connect SDK v2.1.0 using the Toolchain Manager, we start by using the 'Create a new application' option in Visual Studio Code (VS Code) to create a freestanding application based on the peripheral LBS sample. Once the project has been created we need to add a build configuration for the thingy53_nrf5340_cpuapp_ns target, which means we are building the application to run on the application CPU in the non-secure environment. This might sound scary, but it is in fact making our firmware more secure as it will incorporate TF-M into the build and create a secured domain that is separate from the non-secure one. Steps for adding a build configuration can be found in the nRF Connect for VS Code documentation.

The main reason for choosing the peripheral LBS sample as our place to start is that it is already compatible with Thingy:53. So it already has the necessary DFU parts implemented and some of the Bluetooth LE functionality, which lets us focus on the data collection and transmission.

We need our application to feature DFU right from the beginning because Thingy:53 does not have an onboard debugger. Therefore we need to implement a means of updating the application on the Thingy:53. For convenience, we could, of course, have used a standalone debug-probe, or another DK's debugger, to perform the programming of the Thingy:53, but that would require additional hardware. It would have robbed us of the opportunity to see how easy it is to add DFU to our application.

After building the project, the DFU-ready application can be found in <project_path>/build/zephyr/dfu_application.zip. If you already have an existing application that you would like to add DFU to, you could follow this guide.

Trimming down to a minimal project

To highlight the necessary steps for the sensor hub implementation, we will start by trimming away the peripheral LBS-specific functionality from our application. In this case, we do not need any of the LBS specific code or the button functionality - we can keep the generic Bluetooth parts and the LEDs. Removing all the LBS service specific code leaves us with the following, minimal main.c which will be used as baseline for our project:

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <soc.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

We will also need to make some changes to the prj.conf - we'll keep the general Bluetooth parts, and remove the LBS specific parts. In addition, we have renamed the device to 'T53_sensor_hub' for convenience and easy discoverability by a BLE central device during scanning. The prj.conf which should look like this afterwards:

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#
# Copyright (c) 2018 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# Enable Bluetooth
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="T53_sensor_hub"
# Enable buttons and LEDs
CONFIG_DK_LIBRARY=y
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Lastly, you can delete the Kconfig and prj_minimal.conf files from the sample directory as well.

Setting up the BH1749 color and light sensor

We may now start to add the data collection functionality to our project. We will use the BH1749 to collect sensor readings of the ambient light in the environment, specifically the red/green/blue values. To make things easy for ourselves, we will here make use of the SENSOR module, which works with the variety of sensors already implemented in Zephyr. We start by adding the following configurations to our prj.conf:

Fullscreen
1
2
3
4
# Configure Thingy:53 sensors
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_BH1749=y
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The specific function of each of these configurations can be checked by either going to the kconfig reference search, or by hovering over the specific configuration in the prj.conf opened in your VS Code editor, as shown below.

Now we need to add the source code that brings the sensor functionality into our project. In main() we will retrieve the sensor from the devicetree and check for its readiness. In addition we will create a separate function sample_and_update_all_sensor_values for sampling all the sensors, which gets called from the main loop. In that function the sensor data is sent to sensor_hub_update_* functions which handle the BLE data transfer over GATT. We will go over that functionality in the sections below.

It is also required to make a slight modification to the connected/disconnected bluetooth callbacks and in the main loop, so that the sensor sampling is only done if there is a BLE central device connected to the Thingy:53 (allowing for energy savings), and when the LEDs are off. This is because the BH1749 is right next to the RGB LED on the Thingy:53 board, and that would influence the readings if we were to sample the sensor when the LED is on.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
#include <zephyr/drivers/sensor.h>
...
//BT globals and callbacks
struct bt_conn *m_connection_handle = NULL;
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %u)\n", err);
return;
}
m_connection_handle = conn;
printk("Connected\n");
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %u)\n", reason);
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Setting up the BME688 environmental sensor

Now we can also add the BME688 sensor, to gather the pressure, humidity and temperature data. We will also here make use of the SENSOR module, as shown in the previous step. Since we already have some of the sensor modules kconfigs added, we will only need to add the following to our prj.conf:

Fullscreen
1
CONFIG_BME680=y
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

At the time of writing, the BME688 sensor that we are gointhg to be using is not declared in the thingy53_nrf5340_common.dts file, which can be found in <sdk_path>\<sdk_version>\zephyr\boards\arm\thingy53_nrf5340. This is the devicetree file that contains the hardware definition for the board (e.g. buttons, LEDs, sensors). It would also be possible to add the BME688 sensor declaration into the overlay files in <project_path>/boards, but doing it on the SDK file will make the change available for any project targeting Thingy:53.

We will have to make a small amendment to include the missing sensor. As we see on the file, the i2c1 instance is already used for both BH1749 and the BMM150, but there is no mention of the BME688 that we are interested in using. Looking at the Thingy:53 hardware files, downloadable from here, tells us that these three sensors are on the same I2C bus, and so we can just add it to the declaration as so:

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&i2c1 {
compatible = "nordic,nrf-twim";
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
pinctrl-0 = <&i2c1_default>;
pinctrl-1 = <&i2c1_sleep>;
pinctrl-names = "default", "sleep";
bmm150@10 {
compatible = "bosch,bmm150";
label = "BMM150";
reg = <0x10>;
};
bh1749@38 {
compatible = "rohm,bh1749";
label = "BH1749";
reg = <0x38>;
int-gpios = <&gpio1 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Similarly to the previous section, we need to add the source code that brings the sensor functionality into our project. In main() we will retrieve the sensor from the devicetree and check for its readiness. In addition we will add the BME688 sampling to a the function sample_and_update_all_sensor_values.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Main loop
void main(void)
{
...
//Setting up BME688 environmental sensor */
const struct device *bme688SensorDev = DEVICE_DT_GET_ONE(bosch_bme680);
if (!device_is_ready(bme688SensorDev))
{
printk("Sensor device not ready\n");
return;
}
...
for (;;)
{
if(!(blink_status % 2) && m_connection_handle)
{
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Setting up ADC to read Li-Po battery voltage

The final peripheral functionality to be added to our project is the ADC, which allows us to read the on-kit Li-Po battery voltage battery. Since the battery voltage exceeds the nRF5340 supply voltage, it will be read through an on-board voltage divider with two resistors that is enabled via a GPIO. This can be found on page 4 of the boards schematics.

This voltage divider is also defined in the device tree, on the earlier mentioned thingy53_nrf5340_common.dts file. The properties in the vbatt node tell us that ADC channel 2 is being used to sample the voltage through a 1M5+180k ohm resistor ladder, and also that P0.16 is used to enable the ladder.

Fullscreen
1
2
3
4
5
6
7
vbatt {
compatible = "voltage-divider";
io-channels = <&adc 2>;
output-ohms = <180000>;
full-ohms = <(1500000 + 180000)>;
power-gpios = <&gpio0 16 0>;
};
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
To bring the ADC into our project first we need to add the following kconfig to your prj.conf file:
Fullscreen
1
CONFIG_ADC=y
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Then we need to add the ADC settings and and voltage divider, initialize, and finally sample the ADC and calculate the battery voltage based on the resistor ladder configuration.
Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
#include <drivers/adc.h>
#include <hal/nrf_saadc.h>
// ---- ADC settings START ---- //
#define ADC_RESOLUTION 14
static const struct adc_channel_cfg adc_channel_cfg = {
.gain = ADC_GAIN_1_6,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
.input_positive = NRF_SAADC_INPUT_AIN2,
};
static struct adc_sequence adc_seq = {
.channels = BIT(0),
.oversampling = 4,
.calibrate = true,
.resolution = ADC_RESOLUTION,
};
// ---- ADC settings END ---- //
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Implementing a custom sensor hub GATT service

Now that we have got all of our sensor readings up and running we can shift our focus over to transferring the data to our connected BLE central device over GATT. To do this, we will create our own BLE service to transfer the different measurements to our central device. We will be following the steps as thoroughly detailed in this blogpost series. In our case, we will need to diverge slightly from the tutorial in the blogpost series, since we will need a couple more characteristics, and no characteristic for receiving data from the central device. We begin by creating our sensor_hub_service.c and sensor_hub_service.h files under a new services project folder.

Using a UUID generator we created the following custom UUIDs which will be used as detailed on the table below.

UUID Description Units
a5b46352-9d13-479-9fcb-3dcdf0a13f4d Thingy:53 Demo Service N/A
06a55c4-b5e7-46fa-8326-8acaeb1189eb Temperature Reading Characteristic Celsius (C)
51838aff-2d9a-b32a-b32a-8187e41664ba Pressure Reading Characteristic Kilopascal (kPa)
753e3050-df06-4b53-b090-5e1d810c4383 Humidity Reading Characteristic Percentage (%)
82754bbb-6ed3-4d69-a0e1-f19f6b654ec2 Red Color Reading Characteristic N/A
db7f9f36-92ce-4509-a2ef-af72ba38fb48 Green Color Reading Characteristic N/A
f5d2eab5-41e8-4f7c-aef7-c9fff4c544c0 Blue Color Reading Characteristic N/A
fa3cf070-d0c7-4668-96c4-86125c8ac5df Battery Reading Characteristic mV

If one would like to add an UUID for the Air Quality Index (AQI) measured by the BME688, one could also do that, for example with the UUID: ad79cbce-65df-4bc6-bdd4-8ce2b6b75c59. 

This yields the following sensor_hub_service.h:

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// sensor_hub_service.h
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr.h>
#include <soc.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
//Declaration of custom GATT service and characteristics UUIDs
#define SENSOR_HUB_SERVICE_UUID \
BT_UUID_128_ENCODE(0xa5b46352, 0x9d13, 0x479f, 0x9fcb, 0x3dcdf0a13f4d)
#define TEMP_CHARACTERISTIC_UUID \
BT_UUID_128_ENCODE(0x506a55c4, 0xb5e7, 0x46fa, 0x8326, 0x8acaeb1189eb)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

We also need to add the 'notify' property to each of these characteristic, so that we may alert our central device (if it has enabled notifications for that specific measurement) whenever there is a new measurement available. We also need to implement that functions that we will use to update the characteristics and to send the notifications when new measurements are available. These are the functions called from sample_and_update_all_sensor_values, each time the sensors get samples.

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// sensor_hub_service.c
#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 <soc.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/addr.h>
#include <bluetooth/gatt.h>
#include "sensor_hub_service.h"
#define BT_UUID_SENSOR_HUB BT_UUID_DECLARE_128(SENSOR_HUB_SERVICE_UUID)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Now that we have a new source file in our project services\sensor_hub_service.c,  we need to remember to add it to our project by modifying our CMakeLists.txt to contain the following:

Fullscreen
1
2
3
4
# NORDIC SDK APP START
target_sources(app PRIVATE
src/main.c services/sensor_hub_service.c
)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Testing

Now, if we head over to nRF Connect Bluetooth Low Energy desktop app, we can check out of newfound service and characteristics, to see all the sensor data roll in once we enable each of the characteristics notification property. I will use an nRF52840 DK as the connectivity device - plugging it into the USB, choosing it in the Device Select menu, and clicking 'yes' to the re-programming prompt that appears. If you don't have a spare kit then nRF Connect for Mobile can be used in a similar way as described here.

When the programming finishes I click Start scan, and Connect when I see the T53_sensor_hub name appear in the list of devices. After the connection is made we see a list of available services and characteristics appear in the T53_sensor_hub list!

As you can see, all of our customer UUIDs show up as unknown, which makes them hard to read out directly. As an added bonus, we will therefore make this a little easier for ourselves by quickly adding them to the nRF Connect SDK BLE application's list of recognized UUIDs. To achieve this, navigate to: C:\Users\USERNAME\AppData\Roaming\nrfconnect\uuid_definitions.json, and adding in the following:

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"uuid16bitDescriptorDefinitions": {},
"uuid128bitServiceDefinitions": {
"6E400001B5A3F393E0A9E50E24DCCA9E": {
"name": "UART over BLE"
},
"A5B463529D13479F9FCB3DCDF0A13F4D": {
"name": "Thingy:53 Sensor Hub"
}
},
"uuid128bitCharacteristicDefinitions": {
"6E400002B5A3F393E0A9E50E24DCCA9E": {
"name": "UART RX"
},
"6E400003B5A3F393E0A9E50E24DCCA9E": {
"name": "UART TX"
},
"506A55C4B5E746FA83268ACAEB1189EB": {
"name": "Temperature"
},
"51838AFF2D9AB32AB32A8187E41664BA": {
"name": "Pressure"
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
We can now see all of our characteristics proper name and value displayed in the nRF Connect SDK BLE application, and we see that the characteristics that we have enabled notify for, updates every second. It should look like this:

Versions used

The following software and tools versions were used to create this blog post. If you are using newer versions then the information shown here might be slightly different.

Software/Tool Version
nRF Connect SDK 2.1.0
nRF Connect for Desktop Bluetooth Low Energy 4.0.0
nRF Connect for mobile (iOS) 2.5.3
nRF Connect for VS Code 2022.10.30

Closing

The source code for this project is hosted on GitHub: https://github.com/nordic-tiago/thingy53_sensor_hub.

We hope you have found this post useful to get you up and running with your Thingy:53 development. If you should have any questions about the topics covered in this tutorial, or if you require technical support with your Thingy:53 development, please do not hesitate to open a ticket in DevZone.

thingy53_sensor_hub.zip