Post

ENG | Zephyr RTOS: Sensors

Playing with weather sensors.

ENG | Zephyr RTOS: 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 specificity
  • req: 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 alias htu21, popular arduino module for temperature, humidity
  • bosch.bme280-i2c, pressure, temperature, (humidity - BME version, not BMP version)
  • bosch.bmp388-i2c which has some keys affecting accuracy, pressure, temperature
  • ti,ina219 - current and power sensor, requires shunt-milliohm and lsb-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.

This post is licensed under CC BY 4.0 by the author.