ENG | Zephyr RTOS: Sensors
Playing with weather sensors.
Now, how to know if our sensor is directly supported by Zephyr?
Well. When playing with them with Arduino and Raspberry Pi Pico, I sometimes found out that dealing with library is overkill, as I usually need only to read some bytes from some address, occasionally trigger measurement from code and perform some data conversion to human readable values.
Overlay files
Let’s start with this part of our overlay from last article. How to make sense of it?
1
2
3
4
5
pcf8563_rtc: pcf8563@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
status = "okay";
};
Let’s try it. There are some weird files in zephyr/dts/bindings/
folders, such as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Copyright (c) 2023 Alvaro Garcia Gomez <[email protected]>
# SPDX-License-Identifier: Apache-2.0
description: NXP PCF8563 RTC
compatible: "nxp,pcf8563"
include:
- name: rtc-device.yaml
- name: i2c-device.yaml
properties:
int1-gpios:
type: phandle-array
description: |
GPIO connected to the PC8563 INT1 interrupt output. This signal is open-drain, active low.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Copyright (c) 2021, Leonard Pollak
# SPDX-License-Identifier: Apache-2.0
description: Sensirion SHT4x humidity and temperature sensor
compatible: "sensirion,sht4x"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
repeatability:
type: int
required: true
description: |
Repeatability of the T/RH Measurement
0 = low -> 1.7 ms
1 = med -> 4.5 ms
2 = high -> 8.2 ms
enum:
- 0
- 1
- 2
1
2
3
4
5
6
7
8
9
10
11
12
13
# Copyright (c) 2017, Linaro Limited
# SPDX-License-Identifier: Apache-2.0
# Common fields for I2C devices
include: [base.yaml, power.yaml]
on-bus: i2c
properties:
reg:
required: true
description: device address on i2c bus
Uff…in general needed parameters are
status
:"okay"
or"disabled"
.compatible
: required array of compatible drivers by specificityreq
: array of adresses of device, required for I2C. Int values are enclosed in<
,>
Then there are like 20 optional parameters in base/base.yaml
Now I’m even more convinced that using devices directly would be easier 🙄
Some popular sensors are
sensirion,sht21
aliashtu21
, popular arduino module for temperature, humiditybosch.bme280-i2c
, pressure, temperature, (humidity - BME version, not BMP version)bosch.bmp388-i2c
which has some keys affecting accuracy, pressure, temperatureti,ina219
- current and power sensor, requiresshunt-milliohm
andlsb-microamp
Let’s try to create overlay file for Raspberry Pi Pico with I2C(1) on pins 14,15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/ {
chosen {
};
aliases {
env-sensor = &bmp280; /* CHANGE AS NEEDED */
};
};
&pinctrl {
i2c1_custom: i2c1_custom {
group1 {
pinmux= <I2C1_SDA_P14>, <I2C1_SCL_P15>; /* CHANGE AS NEEDED */
input-enable;
input-schmitt-enable;
bias-pull-up;
};
};
};
&i2c1 {
status = "okay";
pinctrl-0 = <&i2c1_custom>;
pinctrl-names = "default";
clock-frequency = <I2C_BITRATE_STANDARD>;
bmp280: bmp280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
status = "okay";
};
sht4x: sht4x@44 {
compatible = "sensirion,sht4x";
reg = <0x44>;
status = "disabled";
repeatability=<2>;
};
};
Here I included two sensors. It seems they can be both enabled (status="okay";
), I gradually added second sensor. What matters is alias on overlay file resolved later in the source code via DEVICE_DT_GET(DT_ALIAS(env_sensor))
.
Source code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
const struct device *const sensor = DEVICE_DT_GET(DT_ALIAS(env_sensor));
int sensor_print_helper(const struct device *const sensor, int channel, const char *name)
{
struct sensor_value value;
int ret = sensor_channel_get(sensor, channel, &value);
if (ret == 0) {
printk("%12s : %d.%06d\n", name, value.val1, value.val2);
} else {
printk("%12s : N/A\n", name);
}
return ret;
}
int main(void)
{
if (!device_is_ready(sensor)) {
printk("Temperature sensor not ready!\n");
return -1;
}
while (1) {
// Fetch sensor data ...
int ret= sensor_sample_fetch(sensor);
if (ret != 0) {
printk("sensor_sample_fetch returned %d\n", ret);
continue;
}
// ... and print them
printk("---\n");
sensor_print_helper(sensor, SENSOR_CHAN_AMBIENT_TEMP, "Temperature");
sensor_print_helper(sensor, SENSOR_CHAN_PRESS, "Pressure");
sensor_print_helper(sensor, SENSOR_CHAN_HUMIDITY, "Humidity");
k_sleep(K_SECONDS(2));
}
return 0;
}
Config files
Now we need two more files.
CONFIG_I2C=y
and CONFIG_SENSOR=y
seem to be compulsory and can be moved to common prj.conf
.
Maybe there can be common overlay file and one that differs by alias as well. I don’t know the best practices.
There is also a question whether to put these file into git repository as they are sort of specific for each board/prototype. In my case it means pretty much unique. At least these definitions are not hardcoded across C/CPP files.
1
2
3
CONFIG_BME280=y
CONFIG_I2C=y
CONFIG_SENSOR=y
1
2
3
CONFIG_SHT4X=y
CONFIG_I2C=y
CONFIG_SENSOR=y
CMakeLists.txt
I copied this one from other project, name seems irrelevant
1
2
3
4
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(rtc)
target_sources(app PRIVATE src/main.c)
Compiling project
For SHT40 change alias and status of sensors and save file as rpi_pico_sht4x.overlay
.
1
2
3
4
5
west build -p always -b rpi_pico -S cdc-acm-console -- -DCONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y -DOVERLAY_CONFIG=bme280.conf -DDTC_OVERLAY_FILE=rpi_pico_bme280.overlay
west build -p always -b rpi_pico -S cdc-acm-console -- -DCONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y -DOVERLAY_CONFIG=sht4x.conf -DDTC_OVERLAY_FILE=rpi_pico_sht4x.overlay
udisksctl mount -b /dev/sda1
west flash -r uf2
mpremote a1
Output example
BMP280 sensor
In my opinion, temperature readings are not reliable and reported temperature is usually 1°C or 1.5°C (2°F to 3°F) higher than reported by SHT40. (Here “usually” = “Based on two observations”)
Note that BMP280 and BME280 sensors exist. BME280 includes humidity measurement, BMP280 does not.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Connected to MicroPython at /dev/ttyACM1
Use Ctrl-] or Ctrl-x to exit this shell
*** Booting Zephyr OS build v4.1.0-5582-g2c5c0e24e8fd ***
---
Temperature : 26.520000
Pressure : 98.819832
Humidity : N/A
---
Temperature : 26.510000
Pressure : 98.818585
Humidity : N/A
---
Temperature : 26.510000
Pressure : 98.817898
Humidity : N/A
---
Temperature : 26.510000
Pressure : 98.817726
Humidity : N/A
---
Temperature : 26.500000
Pressure : 98.817554
Humidity : N/A
SHT40 Sensor
Sensor was not conditioned, so it overreports humidity.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Connected to MicroPython at /dev/ttyACM1
Use Ctrl-] or Ctrl-x to exit this shell
*** Booting Zephyr OS build v4.1.0-5582-g2c5c0e24e8fd ***
---
Temperature : 25.523384
Pressure : N/A
Humidity : 61.246505
---
Temperature : 25.542076
Pressure : N/A
Humidity : 61.242691
---
Temperature : 25.515373
Pressure : N/A
Humidity : 61.275115
---
Conclusion
This was easy. Unresolved question is how to write prj.conf
and similar files and where to get documentation for sensors, besides copying existing examples.
I really assume that for simple project, using I2C directly is quite easy, for example it’s not clear if we can condition SHT40 sensor without reading source code.
While I questioned use of sensor library maybe multiple times, I can perfectly understand it. For hobby project it might be useless overburden, which is hard to debug. For larger projects it may be needed to mock sensors for testing application logic and handling of extreme values without hardware.