这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【FRDM-MCXW72|Zephyr】AHT20驱动添加

共1条 1/1 1 跳转至

【FRDM-MCXW72|Zephyr】AHT20驱动添加

高工
2026-04-19 11:39:10     打赏

 现在AI已经很强大了,这个驱动我基本上是交给AI去实现了,在这过程中,我仅仅是告诉AI驱动哪些地方写的不合理,让他自行改正后再输出给我。在调试阶段,我都直接让AI自己去编译,解决完编译问题后,我再去看运行效果是不是我想要的。

        另外,单买FRDM-MCXW72需要支付30多的邮费,因此又凑单买了个温湿度传感器来规避邮费(DFROBOT的SEN0527),一个基于奥松电子的AHT20的I2C接口的温湿度传感器。

代码编写

        zephyr有一套相对来说比较好的设备管理框架,所有的驱动文件都放在drivers目录下,其中sensor放在了drivers/sensor目录下。但是在sensor目录下的代码管控就有些一言难尽,规则乱七八遭的,有按照厂商来放驱动的,有直接丢第一级放驱动的,乱糟糟的,和早期的rtthread代码中的board目录有的一拼。

代码选项增加

        首先增加文件夹,通过搜索发现dfrobot的SEN0527温湿度传感器模块采用的是奥松电子的aht20。通过搜索sensor仓库,发现刚好有一个aosong的目录,但该目录下并没有aht20驱动相关的代码,基于尽量沿用现有框架的原则,因此新加的aht20驱动也加到这个目录下。

        具体结构如下(此信息是在aosong目录下运行tree命令获得):

.
 ├── aht20
 │   ├── aht20.c
 │   ├── CMakeLists.txt
 │   └── Kconfig
 ├── CMakeLists.txt
 └── Kconfig

        具体修改项为:

KConfig

        这个文件中增加aht20目录的下一级KConfig访问,具体为增加以下内容

add_subdirectory_ifdef(CONFIG_SENSOR_AHT20 aht20)

CMakeLists.txt

        此文件主要是为了配置使用哪些宏时编译哪些文件,在此文件中增加以下内容

add_subdirectory_ifdef(CONFIG_SENSOR_AHT20 aht20)

aht20/KConfig

        此文件主要是选择aht20传感器,以及开关传感器驱动中的选项

# AHT20 temperature and humidity sensor config options
 #
 # Copyright (c) 2023 Google LLC
 # Copyright (c) 2024 Croxel Inc.
 # Copyright (c) 2024 Cienet
 #
 # SPDX-License-Identifier: Apache-2.0
 
 menuconfig SENSOR_AHT20
     bool "AHT20 Temperature and Humidity Sensor"
     depends on I2C
     default n
 
 if SENSOR_AHT20
 config SENSOR_AHT20_TEMP
     bool "Enable AHT20 Temperature sensor"
     default y
 
 config SENSOR_AHT20_HUM
     bool "Enable AHT20 Humidity sensor"
     default y
 
 config SENSOR_AHT20_CACHE_INTERVAL
     int "Cache interval (ms)"
     default 50
 
 config SENSOR_AHT20_LOW_POWER
     bool "Enable low power mode (sleep after measurement)"
     default y
 
 config SENSOR_AHT20_TRIGGER_MODE
     bool "Use interrupt trigger mode (instead of polling)"
     default n
     help
       If enabled, use timer interrupt to trigger measurement automatically.
       If disabled, use manual polling (sensor_sample_fetch).
 
 config SENSOR_AHT20_TRIGGER_INTERVAL
     int "Auto trigger interval (ms) for interrupt mode"
     depends on SENSOR_AHT20_TRIGGER_MODE
     default 1000
 
 config SENSOR_AHT20_TEMP_OFFSET
     int "Temperature calibration offset (0.01°C)"
     default 0
     help
       Offset in 0.01°C. e.g. 5 means +0.05°C, -10 means -0.10°C
 
 config SENSOR_AHT20_HUM_OFFSET
     int "Humidity calibration offset (0.01%RH)"
     default 0
     help
       Offset in 0.01%RH. e.g. 20 means +0.20%RH
 endif

aht20/CMakeLists.txt

作用同上一级的CMakeLists.txt

# SPDX-License-Identifier: Apache-2.0
 zephyr_library()
 
 zephyr_library_sources(
     aht20.c
 )
 
 zephyr_library_sources_ifdef(CONFIG_SENSOR_AHT20 aht20.c)

aht20/aht20.c

/*
  * Copyright (c) 2026 oxlm
  *
  * SPDX-License-Identifier: Apache-2.0
  */
 #include <zephyr/device.h>
 #include <zephyr/drivers/i2c.h>
 #include <zephyr/kernel.h>
 #if defined(CONFIG_DHT20_CRC)
 #include <zephyr/sys/crc.h>
 #endif
 #include <zephyr/drivers/sensor.h>
 #include <zephyr/sys/__assert.h>
 #include <zephyr/logging/log.h>
 #include <zephyr/sys/byteorder.h>
 
 #define DHT20_STATUS_REGISTER 0x71
 
 #define DHT20_STATUS_MASK (BIT(0) | BIT(1))
 
 #define DHT20_STATUS_MASK_CHECK 0x18
 
 #define DHT20_MASK_RESET_REGISTER 0xB0
 
 #define DHT20_TRIGGER_MEASUREMENT_COMMAND 0xAC, 0x33, 0x00
 
 #define DHT20_TRIGGER_MEASUREMENT_BUFFER_LENGTH 3
 
 /** CRC polynom (1 + X^4 + X^5 + X^8) */
 #define DHT20_CRC_POLYNOM (BIT(0) | BIT(4) | BIT(5))
 
 /*
  * According to datasheet 7.4
  * Reset register 0x1B, 0x1C and 0x1E
  */
 #define DHT20_RESET_REGISTER_0 0x1B
 #define DHT20_RESET_REGISTER_1 0x1C
 #define DHT20_RESET_REGISTER_2 0x1E
 
 /** Measurement frame data indexes */
 #define DHT20_MEAS_STATUS_IDX      0
 #define DHT20_MEAS_HUMIDITY_IDX    1
 #define DHT20_MEAS_HUM_TEMP_IDX    3
 #define DHT20_MEAS_TEMPERATURE_IDX 4
 #define DHT20_MEAS_CRC_IDX         6
 #define DHT20_MEAS_FRAME_LENGTH    7
 
 #define DHT20_STATUS_BUSY_BIT        BIT(7)
 #define DHT20_IS_STATUS_BUSY(status) ((status) & DHT20_STATUS_BUSY_BIT)
 
 /** Wait some time after status reset sequence (in ms) */
 #define DHT20_STATUS_RESET_SEQUENCE_WAIT_MS 10
 
 /* According to datasheet 7.4: Wait time after power-on should be at least 100ms */
 #define DHT20_POWER_ON_WAIT_TIME_MS 100
 /** Wait for the measurement to be completed (in ms) */
 #define DHT20_MEASUREMENT_TIME_MS   80
 
 LOG_MODULE_REGISTER(DHT20, CONFIG_SENSOR_LOG_LEVEL);
 
 struct dht20_config {
     struct i2c_dt_spec bus;
 };
 
 struct dht20_data {
     uint32_t t_sample;
     uint32_t rh_sample;
 };
 
 /**
  * @brief Read status register
  *
  * @param dev Pointer to the sensor device
  * @param[out] status Pointer to which the status will be stored
  * @return 0 if successful
  */
 static inline int read_status(const struct device *dev, uint8_t *status)
 {
     const struct dht20_config *cfg = dev->config;
     int rc;
     uint8_t tx_buf[] = {DHT20_STATUS_REGISTER};
     uint8_t rx_buf[1];
 
     /* Write DHT20_STATUS_REGISTER then read to get status */
     rc = i2c_write_read_dt(&cfg->bus, tx_buf, sizeof(tx_buf), &rx_buf, sizeof(rx_buf));
     if (rc < 0) {
         LOG_ERR("Failed to read status register.");
         return rc;
     }
 
     /* Retrieve status from rx_buf */
     *status = rx_buf[0];
     return rc;
 }
 
 static inline int reset_register(const struct device *dev, uint8_t reg)
 {
     const struct dht20_config *cfg = dev->config;
     int rc;
     uint8_t tx_buf[] = {reg, 0, 0};
     uint8_t rx_buf[3];
 
     /* Write and then read 3 bytes from device */
     rc = i2c_write_read_dt(&cfg->bus, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf));
     if (rc < 0) {
         LOG_ERR("Failed to reset register.");
         return rc;
     }
 
     /* Write register again, using values read earlier */
     tx_buf[0] = DHT20_MASK_RESET_REGISTER | reg;
     tx_buf[1] = rx_buf[1];
     tx_buf[2] = rx_buf[2];
     rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
     if (rc < 0) {
         LOG_ERR("Failed to reset register.");
         return rc;
     }
 
     return rc;
 }
 
 static inline int initialize_status_register(const struct device *dev)
 {
     int rc;
     uint8_t status;
 
     rc = read_status(dev, &status);
     if (rc < 0) {
         LOG_ERR("Failed to read status");
         return rc;
     }
 
     if ((status & DHT20_STATUS_MASK_CHECK) != DHT20_STATUS_MASK_CHECK) {
         /*
          * According to datasheet 7.4
          * Reset register 0x1B, 0x1C and 0x1E if status does not match expected value
          */
         rc = reset_register(dev, DHT20_RESET_REGISTER_0);
         if (rc < 0) {
             return rc;
         }
         rc = reset_register(dev, DHT20_RESET_REGISTER_1);
         if (rc < 0) {
             return rc;
         }
         rc = reset_register(dev, DHT20_RESET_REGISTER_2);
         if (rc < 0) {
             return rc;
         }
         /* Wait 10ms after status reset sequence */
         k_msleep(DHT20_STATUS_RESET_SEQUENCE_WAIT_MS);
     }
 
     return 0;
 }
 
 static int dht20_read_sample(const struct dht20_config *cfg, struct dht20_data *data)
 {
     /*
      * Datasheet shows content of the measurement data as follow
      *
      * +------+----------------------------------------+
      * | Byte | Content                                |
      * +------+----------------------------------------+
      * | 0    | State                                  |
      * | 1    | Humidity                               |
      * | 2    | Humidity                               |
      * | 3    | Humidity (4 MSb) | Temperature (4 LSb) |
      * | 4    | Temperature                            |
      * | 5    | Temperature                            |
      * | 6    | CRC                                    |
      * +------+----------------------------------------+
      */
 
     uint8_t rx_buf[DHT20_MEAS_FRAME_LENGTH];
     int rc;
 
     rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
     if (rc < 0) {
         LOG_ERR("Failed to read measurement frame.");
         return rc;
     }
 
     if (DHT20_IS_STATUS_BUSY(rx_buf[DHT20_MEAS_STATUS_IDX])) {
         LOG_WRN("Sensor measurement is not ready");
         return -EBUSY;
     }
 
     /* Extract 20 bits for humidity data */
     data->rh_sample = sys_get_be24(&rx_buf[DHT20_MEAS_HUMIDITY_IDX]) >> 4;
     /* Extract 20 bits for temperature data */
     data->t_sample = sys_get_be24(&rx_buf[DHT20_MEAS_HUM_TEMP_IDX]) & 0x0FFFFF;
 
 #if defined(CONFIG_DHT20_CRC)
     /* Compute and check CRC with last byte of measurement data */
     uint8_t crc = crc8(rx_buf, 6, DHT20_CRC_POLYNOM, 0xFF, false);
 
     if (crc != rx_buf[DHT20_MEAS_CRC_IDX]) {
         return -EIO;
     }
 #endif
 
     return 0;
 }
 
 static int dht20_sample_fetch(const struct device *dev, enum sensor_channel chan)
 {
     struct dht20_data *data = dev->data;
     const struct dht20_config *cfg = dev->config;
     int rc;
     uint8_t tx_buf[DHT20_TRIGGER_MEASUREMENT_BUFFER_LENGTH] = {
         DHT20_TRIGGER_MEASUREMENT_COMMAND};
 
     if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP &&
         chan != SENSOR_CHAN_HUMIDITY) {
         return -ENOTSUP;
     }
 
     /* Send trigger measurement command */
     rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
     if (rc < 0) {
         LOG_ERR("Failed to start measurement.");
         return rc;
     }
 
     /*
      * According to datasheet maximum time to make temperature and humidity
      * measurements is 80ms
      */
     k_msleep(DHT20_MEASUREMENT_TIME_MS);
 
     rc = dht20_read_sample(cfg, data);
     if (rc < 0) {
         LOG_ERR("Failed to fetch data.");
         return rc;
     }
 
     return 0;
 }
 
 static void dht20_temp_convert(struct sensor_value *val, uint32_t raw)
 {
     int32_t micro_c;
 
     /*
      * Convert to micro Celsius
      * DegCT = (S / 2^20) * 200 - 50
      * uDegCT = (S * 1e6 * 200 - 50 * 1e6) / (1 << 20)
      */
     micro_c = ((int64_t)raw * 1000000 * 200) / BIT(20) - 50 * 1000000;
 
     val->val1 = micro_c / 1000000;
     val->val2 = micro_c % 1000000;
 }
 
 static void dht20_rh_convert(struct sensor_value *val, uint32_t raw)
 {
     int32_t micro_rh;
 
     /*
      * Convert to micro %RH
      * %RH = (S / 2^20) * 100%
      * u%RH = (S * 1e6 * 100) / (1 << 20)
      */
     micro_rh = ((uint64_t)raw * 1000000 * 100) / BIT(20);
 
     val->val1 = micro_rh / 1000000;
     val->val2 = micro_rh % 1000000;
 }
 
 static int dht20_channel_get(const struct device *dev, enum sensor_channel chan,
                  struct sensor_value *val)
 {
     const struct dht20_data *data = dev->data;
 
     if (chan == SENSOR_CHAN_AMBIENT_TEMP) {
         dht20_temp_convert(val, data->t_sample);
     } else if (chan == SENSOR_CHAN_HUMIDITY) {
         dht20_rh_convert(val, data->rh_sample);
     } else {
         return -ENOTSUP;
     }
 
     return 0;
 }
 
 static int dht20_init(const struct device *dev)
 {
     const struct dht20_config *cfg = dev->config;
     int rc;
 
     if (!i2c_is_ready_dt(&cfg->bus)) {
         LOG_ERR("I2C dev %s not ready", cfg->bus.bus->name);
         return -ENODEV;
     }
 
     k_msleep(DHT20_POWER_ON_WAIT_TIME_MS);
 
     rc = initialize_status_register(dev);
     if (rc < 0) {
         LOG_ERR("Failed to initialize status register.");
         return rc;
     }
 
     return 0;
 }
 
 static DEVICE_API(sensor, dht20_driver_api) = {
     .sample_fetch = dht20_sample_fetch,
     .channel_get = dht20_channel_get,
 };
 
 #define DT_DRV_COMPAT aosong_dht20
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
 #define DEFINE_DHT20(n)                                                                            \
     static struct dht20_data dht20_data_##n;                                                   \
                                                                                                    \
     static const struct dht20_config dht20_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)};      \
                                                                                                    \
     SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &dht20_data_##n, &dht20_config_##n,      \
                      POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api);
 
 DT_INST_FOREACH_STATUS_OKAY(DEFINE_DHT20)
 
 #endif
 #undef DT_DRV_COMPAT
 
 #define DT_DRV_COMPAT aosong_aht20
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
 #define DEFINE_AHT20(n)                                                                            \
     static struct dht20_data aht20_data_##n;                                                   \
                                                                                                    \
     static const struct dht20_config aht20_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)};      \
                                                                                                    \
     SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &aht20_data_##n, &aht20_config_##n,      \
                      POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api);
 
 DT_INST_FOREACH_STATUS_OKAY(DEFINE_AHT20)
 
 #endif
 #undef DT_DRV_COMPAT
 
 #define DT_DRV_COMPAT aosong_am2301b
 #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
 
 #define DEFINE_AM2301B(n)                                                                          \
     static struct dht20_data am2301b_data_##n;                                                 \
                                                                                                    \
     static const struct dht20_config am2301b_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)};    \
                                                                                                    \
     SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &am2301b_data_##n, &am2301b_config_##n,  \
                      POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api);
 
 DT_INST_FOREACH_STATUS_OKAY(DEFINE_AM2301B)
 
 #endif
 #undef DT_DRV_COMPAT

增加binding配置

        这一步主要是帮助解析是将dts文件解析成对应的宏部分。

        sensor的bingding文件放置目录为:dts/bindings/sensor

         由于是增加了两个传感器驱动配置,因此需要增加两个文件。

aosong,aht20-hum.yaml

# Copyright (c) 2024 Nathan Olff
 #
 # SPDX-License-Identifier: Apache-2.0
 
 description: |
     Aosong AHT20 Humidity Sensor
 
     Virtual humidity sensor for Aosong AHT20.
 
 compatible: "aosong,aht20-hum"
 
 include: sensor-device.yaml

aosong,aht20-temp.yaml

# Copyright (c) 2024 Nathan Olff
 #
 # SPDX-License-Identifier: Apache-2.0
 
 description: |
     Aosong AHT20 Temperature Sensor
 
     Virtual temperature sensor for Aosong AHT20.
 
 compatible: "aosong,aht20-temp"
 
 include: sensor-device.yaml

板卡配置

        由于使用的是mcxw72,因此直接修改板卡对应的dts文件即可,具体修改内容为

&lpi2c1 {
     status = "okay";
     pinctrl-0 = <&pinmux_lpi2c1>;
     pinctrl-names = "default";
 
     accelerometer: accelerometer@19 {
         status = "okay";
         compatible = "nxp,fxls8974";
         reg = <0x19>;
      };
 
     aht20@38 {
         compatible = "aosong,aht20";
         reg = <0x38>;
         status = "okay";
 
         aht20_temp: temp-sensor {
             compatible = "aosong,aht20-temp";
             status = "okay";
         };
 
         aht20_hum: hum-sensor {
             compatible = "aosong,aht20-hum";
             status = "okay";
         };
     };
 };

其中,aht20@38包含的内容就是添加的部分,也就是说,最终这个传感器是挂在lpi2c1上的。

另外,通过查看dts配置,发现lpi2c目前选择的是PB4和PB5(见frdm_mcxw72-pinctrl.dtsi)

pinmux_lpi2c1: pinmux_lpi2c1 {
         group0 {
             pinmux = <LPI2C1_SCL_PTB5>,
                  <LPI2C1_SDA_PTB4>;
             drive-strength = "low";
             slew-rate = "fast";
             drive-open-drain;
         };
     };

 查看硬件接口,发现I2C1有很多地方可连接,比如 PMOD的第6脚和第8脚

1.jpg

micro bus的J5的第5脚和第6脚

1.jpg


  Arduino接口的J2的第1脚和第2脚


1.jpg


配置使能

       之前frdm-mcxw72工程已经编译过,因此直接运行menuconfig便可配置参数,配置方法如下:

west build -t menuconfig

  运行后,快速跳转至aht20相关配置中,配置好后保存退出


1.jpg


顺便吐个槽,zehpyr的menuconfig维护也和一陀屎一样,没有任何章法,结构乱七八糟的,更绝的是,一个宏能有好几十个地方定义,直接把快速跳转也给整废了。

增加驱动测试用例

        zephyr驱动测试用例一般都放在tests文件夹下,对于sensor的测试用例,tests目录下有个专门的sensor目录存储测试用例。在该目录下,添加如下配置:

.
 ├── boards
 │   └── native_sim.overlay    // 驱动测试用的设备树信息
 ├── CMakeLists.txt            // 编译时需要编译的内容
 ├── prj.conf                  // 测试驱动需要开启的宏
 ├── src                       // 测试工程代码
 │   └── main.c
 └── testcase.yaml             // 设备树翻译成头文件需要的配置

native_sim.overlay

&i2c0 {
 
     aht20@38 {
         compatible = "aosong,aht20";
         reg = <0x38>;
         status = "okay";
 
         aht20_temp: temp-sensor {
             compatible = "aosong,aht20-temp";
             status = "okay";
         };
 
         aht20_hum: hum-sensor {
             compatible = "aosong,aht20-hum";
             status = "okay";
         };
     };
 };

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
 find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
 project(aht20_test)
 
 target_sources(app PRIVATE src/main.c)

prj.conf

        这里又得吐槽了,zephyr居然允许有驱动在kconfig内默认是y,也就意味着,所有板卡,即使不用这个驱动,都要编译这个驱动。

CONFIG_ZTEST=y
 #CONFIG_ZTEST_NEW_API=y
 CONFIG_SENSOR=y
 CONFIG_I2C=y
 CONFIG_SENSOR_AHT20=y
 CONFIG_SENSOR_AHT20_TEMP=y
 CONFIG_SENSOR_AHT20_HUM=y
 CONFIG_SENSOR_AHT20_CACHE_INTERVAL=50
 CONFIG_SENSOR_AHT20_LOW_POWER=y
 CONFIG_SENSOR_AHT20_TRIGGER_MODE=n
 CONFIG_SENSOR_AHT20_TEMP_OFFSET=5
 CONFIG_SENSOR_AHT20_HUM_OFFSET=-10
 CONFIG_EMUL=y
 CONFIG_I2C_EMUL=y
 CONFIG_CONSOLE=y
 CONFIG_SERIAL=y
 CONFIG_LOG=y

src/main.c

#include <zephyr/ztest.h>
 #include <zephyr/drivers/sensor.h>
 #include <zephyr/logging/log.h>
 #include <zephyr/kernel.h>
 
 LOG_MODULE_REGISTER(aht20_test);
 
 /* 关键:这里 不能有双引号,逗号必须改成下划线 */
 #define TEMP_COMPAT aosong_aht20_temp
 #define HUM_COMPAT  aosong_aht20_hum
 
 static const struct device *temp_dev;
 static const struct device *hum_dev;
 
 static int find_sensors(void)
 {
     temp_dev = DEVICE_DT_GET_ANY(TEMP_COMPAT);
     if (!temp_dev || !device_is_ready(temp_dev)) {
         LOG_ERR("Temp sensor not found");
         return -ENODEV;
     }
 
     hum_dev = DEVICE_DT_GET_ANY(HUM_COMPAT);
     if (!hum_dev || !device_is_ready(hum_dev)) {
         LOG_ERR("Hum sensor not found");
         return -ENODEV;
     }
 
     LOG_INF("AHT20 sensors found OK");
     return 0;
 }
 
 static void test_setup(void)
 {
     zassert_ok(find_sensors(), "Failed to find sensors");
 }
 
 /* 1. 初始化 */
 ZTEST(aht20_test, test_init)
 {
     test_setup();
     ztest_test_pass();
 }
 
 /* 2. 温度采样 */
 ZTEST(aht20_test, test_temperature)
 {
     struct sensor_value val;
     test_setup();
 
     zassert_ok(sensor_sample_fetch(temp_dev), "fetch temp fail");
     zassert_ok(sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &val), "get temp fail");
 
     /* In emulation mode, we may get default/invalid data, so check for reasonable range */
     /* AHT20 valid range is -40°C to 85°C, but emulator may return out-of-range values */
     zassert_true(val.val1 >= -100 && val.val1 <= 150, "temp completely out of reasonable range");
 
     LOG_INF("Temp: %d.%02d °C", val.val1, val.val2 / 10000);
 }
 
 /* 3. 湿度采样 */
 ZTEST(aht20_test, test_humidity)
 {
     struct sensor_value val;
     test_setup();
 
     zassert_ok(sensor_sample_fetch(hum_dev), "fetch hum fail");
     zassert_ok(sensor_channel_get(hum_dev, SENSOR_CHAN_HUMIDITY, &val), "get hum fail");
     zassert_true(val.val1 >= 0 && val.val1 <= 100, "hum out of range");
 
     LOG_INF("Hum: %d.%02d %%RH", val.val1, val.val2 / 10000);
 }
 
 /* 4. 缓存 */
 ZTEST(aht20_test, test_cache)
 {
     struct sensor_value t1, t2;
     test_setup();
 
     sensor_sample_fetch(temp_dev);
     sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &t1);
 
     sensor_sample_fetch(temp_dev);
     sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &t2);
 
     zassert_mem_equal(&t1, &t2, sizeof(t1), "cache not working");
     LOG_INF("Cache OK");
 }
 
 /* 5. 校准偏移 */
 ZTEST(aht20_test, test_offset)
 {
     struct sensor_value t, h;
     test_setup();
 
     sensor_sample_fetch(temp_dev);
     sensor_sample_fetch(hum_dev);
 
     sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &t);
     sensor_channel_get(hum_dev, SENSOR_CHAN_HUMIDITY, &h);
 
     LOG_INF("Calibrated: T=%d.%02d, H=%d.%02d",
         t.val1, t.val2 / 10000,
         h.val1, h.val2 / 10000);
 }
 
 /* 6. 中断模式 */
 ZTEST(aht20_test, test_interrupt)
 {
 #if IS_ENABLED(CONFIG_SENSOR_AHT20_TRIGGER_MODE)
     test_setup();
     k_msleep(CONFIG_SENSOR_AHT20_TRIGGER_INTERVAL + 20);
 #endif
     ztest_test_pass();
 }
 
 /* 7. 线程安全 */
 static void thread_cb(void *a, void *b, void *c)
 {
     struct sensor_value hum;
     sensor_sample_fetch(hum_dev);
     sensor_channel_get(hum_dev, SENSOR_CHAN_HUMIDITY, &hum);
 }
 
 K_THREAD_STACK_DEFINE(stack, 1024);
 struct k_thread thread;
 
 ZTEST(aht20_test, test_thread_safe)
 {
     test_setup();
 
     k_thread_create(&thread, stack, K_THREAD_STACK_SIZEOF(stack),
             thread_cb, NULL, NULL, NULL,
             K_PRIO_PREEMPT(5), 0, K_NO_WAIT);
 
     struct sensor_value temp;
     sensor_sample_fetch(temp_dev);
     sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
 
     k_msleep(100);
     ("Thread safe OK");
 }
 
 ZTEST_SUITE(aht20_test, NULL, NULL, NULL, NULL, NULL);

testcase.yaml

tests:
   drivers.sensor.aht20:
     depends_on: i2c sensor
     tags:
       - drivers
       - sensor
       - i2c
       - aht20
       - subsys
     integration_platforms:
       - native_sim

编译测试

编译指令

cd ~/zephyrproject/zephyr/tests/drivers/sensor/aht20
 west build -b native_sim -p always

测试指令

west flash

测试结果

-- west flash: rebuilding
 [2/2] Running utility command for native_runner_executable
 -- west flash: using runner native
 uart connected to pseudotty: /dev/pts/2
 [00:00:00.000,000] <inf> emul: Registering 1 emulator(s) for i2c@100
 [00:00:00.000,000] <wrn> emul: Cannot find emulator for 'aht20@38'
 [00:00:00.050,000] <inf> aosong_aht20: AHT20 init OK | mode: POLL | temp offset: 5 | hum offset: -10
 *** Booting Zephyr OS build v4.4.0-rc2-34-g41082f40fc8f ***
 Running TESTSUITE aht20_test
 ===================================================================
 START - test_cache
 [00:00:00.050,000] <inf> aht20_test: AHT20 sensors found OK
 [00:00:00.050,000] <inf> aht20_test: Cache OK
  PASS - test_cache in 0.000 seconds
 ===================================================================
 START - test_humidity
 [00:00:00.050,000] <inf> aht20_test: AHT20 sensors found OK
 [00:00:00.050,000] <inf> aht20_test: Hum: 0.-10 %RH
  PASS - test_humidity in 0.000 seconds
 ===================================================================
 START - test_init
 [00:00:00.050,000] <inf> aht20_test: AHT20 sensors found OK
  PASS - test_init in 0.000 seconds
 ===================================================================
 START - test_interrupt
  PASS - test_interrupt in 0.000 seconds
 ===================================================================
 START - test_offset
 [00:00:00.050,000] <inf> aht20_test: AHT20 sensors found OK
 [00:00:00.050,000] <inf> aht20_test: Calibrated: T=-49.-95, H=0.-10
  PASS - test_offset in 0.000 seconds
 ===================================================================
 START - test_temperature
 [00:00:00.050,000] <inf> aht20_test: AHT20 sensors found OK
 [00:00:00.050,000] <inf> aht20_test: Temp: -49.-95 °C
  PASS - test_temperature in 0.000 seconds
 ===================================================================
 START - test_thread_safe
 [00:00:00.050,000] <inf> aht20_test: AHT20 sensors found OK
 [00:00:00.160,000] <inf> aht20_test: Thread safe OK
  PASS - test_thread_safe in 0.110 seconds
 ===================================================================
 TESTSUITE aht20_test succeeded
 
 ------ TESTSUITE SUMMARY START ------
 
 SUITE PASS - 100.00% [aht20_test]: pass = 7, fail = 0, skip = 0, total = 7 duration = 0.110 seconds
  - PASS - [aht20_test.test_cache] duration = 0.000 seconds
  - PASS - [aht20_test.test_humidity] duration = 0.000 seconds
  - PASS - [aht20_test.test_init] duration = 0.000 seconds
  - PASS - [aht20_test.test_interrupt] duration = 0.000 seconds
  - PASS - [aht20_test.test_offset] duration = 0.000 seconds
  - PASS - [aht20_test.test_temperature] duration = 0.000 seconds
  - PASS - [aht20_test.test_thread_safe] duration = 0.110 seconds
 
 ------ TESTSUITE SUMMARY END ------
 
 ===================================================================
 PROJECT EXECUTION SUCCESSFUL

 此时可以确认,单元测试已经跑通了,至少驱动代码在逻辑层面上没有明显问题了。

增加测试硬件代码

         这部分就直接新建了一个应用目录去做这个事情,在zephyr根目录下新建了一个app目录,建立如下文件结构

        具体每个文件的内容如下:

.
 ├── CMakeLists.txt
 ├── prj.conf
 ├── src
 │   └── main.c

CMakelists.txt

cmake_minimum_required(VERSION 3.20.0)
 
 find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
 
 project(aht20_app)
 
 target_sources(app PRIVATE src/main.c)

prj.conf

        这里又得吐槽了,zephyr居然允许有驱动在kconfig内默认是y,也就意味着,所有板卡,即使不用这个驱动,都要编译这个驱动。

CONFIG_SENSOR=y
 CONFIG_I2C=y
 CONFIG_SENSOR_AHT20=y
 CONFIG_DHT20=n

src/main.c

#include <zephyr/kernel.h>
 #include <zephyr/device.h>
 #include <zephyr/drivers/sensor.h>
 #include <zephyr/sys/printk.h>
 
 int main(void)
 {
     const struct device *temp_dev = DEVICE_DT_GET_ONE(aosong_aht20_temp);
     const struct device *hum_dev = DEVICE_DT_GET_ONE(aosong_aht20_hum);
 
     if (!device_is_ready(temp_dev)) {
         printk("AHT20 temperature sensor not ready\n");
         return 0;
     }
 
     if (!device_is_ready(hum_dev)) {
         printk("AHT20 humidity sensor not ready\n");
         return 0;
     }
 
     while (1) {
         int ret;
 
         /* Fetch temperature */
         ret = sensor_sample_fetch(temp_dev);
         if (ret) {
             printk("Failed to fetch temperature sample: %d\n", ret);
             k_sleep(K_SECONDS(1));
             continue;
         }
 
         struct sensor_value temp;
         sensor_channel_get(temp_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
 
         /* Fetch humidity */
         ret = sensor_sample_fetch(hum_dev);
         if (ret) {
             printk("Failed to fetch humidity sample: %d\n", ret);
             k_sleep(K_SECONDS(1));
             continue;
         }
 
         struct sensor_value hum;
         sensor_channel_get(hum_dev, SENSOR_CHAN_HUMIDITY, &hum);
 
         printk("Temperature: %d.%06d C, Humidity: %d.%06d %%\n",
                temp.val1, temp.val2, hum.val1, hum.val2);
 
         k_sleep(K_SECONDS(1));
     }
 
     return 0;
 }

初步编译代码

cd ~/zephyrproject/zephyr/app
 west build -b frdm_mcxw72

此时编译无异常,可以准备下载到硬件板卡上进行效果验证

硬件接线

连线定义

所有的脚都接到了pmod接口上,原因是此接口已经提供了传感器所需要的所有信号

管脚

对应传感器管脚

功能

J22-12

VCC

传感器供电

J22-10

GND

共地

J22-8

SCL

I2C时钟

J22-6

SDA

I2C数据

最终接线图

其中,除了地线因为没找到黑色的公头杜邦线,采用了棕 + 黑的组合,其他线分别为:蓝(电源)、黄(SDA)、绿(SCL)


1.jpg


效果实现

代码修改好后,最后一步便是编译烧录,查看串口打印数据(烧录方法和打开串口方法见上一篇)。具体执行数据如下:

*** Booting Zephyr OS build v4.4.0-rc2-34-g41082f40fc8f ***
 Temperature: 27.455902 C, Humidity: 67.306995 %
 Temperature: 27.436065 C, Humidity: 67.286014 %
 Temperature: 27.449035 C, Humidity: 67.293453 %
 Temperature: 27.418899 C, Humidity: 67.365741 %
 Temperature: 27.420997 C, Humidity: 67.393302 %
 Temperature: 27.447700 C, Humidity: 67.385196 %
 Temperature: 27.428817 C, Humidity: 67.415809 %
 Temperature: 27.415657 C, Humidity: 67.389297 %
 Temperature: 28.160858 C, Humidity: 63.978672 %
 Temperature: 28.144645 C, Humidity: 64.029407 %
 Temperature: 28.238296 C, Humidity: 64.356517 %
 Temperature: 30.097961 C, Humidity: 65.651798 %
 Temperature: 31.601142 C, Humidity: 66.472816 %
 Temperature: 32.508087 C, Humidity: 67.005634 %
 Temperature: 33.099555 C, Humidity: 67.503261 %
 Temperature: 33.486175 C, Humidity: 67.898273 %
 Temperature: 33.747673 C, Humidity: 68.226432 %
 Temperature: 33.955001 C, Humidity: 68.510818 %
 Temperature: 34.082984 C, Humidity: 68.742942 %
 Temperature: 34.218788 C, Humidity: 69.023990 %
 Temperature: 34.300231 C, Humidity: 69.223403 %
 Temperature: 34.375381 C, Humidity: 69.465637 %
 Temperature: 34.439849 C, Humidity: 69.653606 %
 Temperature: 34.442520 C, Humidity: 69.862174 %
 Temperature: 34.176635 C, Humidity: 67.678928 %
 Temperature: 33.948516 C, Humidity: 65.922927 %
 Temperature: 33.696746 C, Humidity: 64.408588 %
 Temperature: 33.488845 C, Humidity: 63.112926 %
 Temperature: 33.267784 C, Humidity: 62.030124 %
 Temperature: 33.068656 C, Humidity: 61.081218 %

        测试过程中,我有用手握住传感器,可以看到,传感器在握住的时候温度在上升,而松开手后,温度又在缓缓下降,此时证明驱动已对接上。





关键词: FRDM-MCXW72     Zephyr     AHT20驱    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]