RIOT OS 中 HM330X 激光颗粒物传感器测试应用实战:编译、运行与输出解读
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
导读
本文围绕 RIOT OS 仓库中的 tests/drivers/hm330x/README.md 展开,详细介绍 HM330X(HM-3300/3600 激光粉尘传感器)在 RIOT 上的官方测试应用:从编译选型(hm3301 / hm3302 两种型号)、烧录运行,到每秒一次的传感器数据读取与表格化输出的完整解读。读完本文,你将掌握该测试程序的构建方式、两种型号输出格式的区别(尤其是 hm3302 独有的粒子数统计),并理解测试代码背后的 I2C 数据帧解析、CRC 校验与参数配置等底层驱动原理。
一、测试应用概览:它验证什么
tests/drivers/hm330x/目录是 RIOT OS 为 HM330X 颗粒物传感器提供的官方测试应用,其 README 开宗明义地写道:
This is a simple test application for the HM330X I2C particle sensor. The test will read and print the sensor data every second.
即:这是一个基于I2C 接口的 HM330X 颗粒物传感器测试程序,运行后每秒读取一次传感器数据并打印输出。同时 README 特别强调了一条关键限制:
Note that particle counting is only available with hm3302.
——粒子计数(number concentration)功能仅 hm3302 型号支持,hm3301 只输出 PM 质量浓度。
该测试目录共包含 4 个文件:
| 文件 | 作用 |
|---|---|
| main.c | 测试主程序:初始化传感器并按秒循环读取、打印 |
| Makefile | 构建配置:选择板卡与驱动型号(hm3301/hm3302) |
| Makefile.ci | CI 用的板卡内存约束说明 |
| README.md | 测试应用说明与运行输出样例 |
二、编译与运行:如何选择 hm3301 / hm3302
测试应用的 Makefile 非常简洁,核心构建逻辑如下:
BOARD ?= nrf52840-mdk include ../Makefile.drivers_common DRIVER ?= hm3301 USEMODULE += $(DRIVER) USEMODULE += ztimer_usec USEMODULE += ztimer_msec USEMODULE += fmt USEMODULE += fmt_table include $(RIOTBASE)/Makefile.include关键点解读:
DRIVER ?= hm3301:默认编译hm3301驱动模块。如需启用 hm3302 的粒子计数功能,编译时通过命令行覆盖即可,例如make DRIVER=hm3302;- 默认板卡
nrf52840-mdk:可用BOARD=...覆盖为其他支持 I2C 的板卡; - 依赖模块:
ztimer_usec/ztimer_msec(驱动复位时序与测试轮询延时使用)、fmt/fmt_table(输出表格化打印); - Makefile.ci 中声明了
atmega8因内存不足(BOARD_INSUFFICIENT_MEMORY)不适合运行本测试。
编译运行(以默认配置为例):
make -C tests/drivers/hm330x flash term # 切换为 hm3302 型号: make -C tests/drivers/hm330x DRIVER=hm3302 flash term将传感器接到板卡 I2C 总线(默认I2C_DEV(0),地址0x40)并复位后,串口终端便会开始以每秒一次的频率刷新输出。
三、主程序流程:初始化、循环读取与打印
测试的核心逻辑在 main.c 中,流程清晰,可分三步:
1. 初始化传感器(使用默认参数)
hm330x_t dev; print_str("+------------Initializing------------+\n"); /* initialize the sensor with default configuration parameters */ if (hm330x_init(&dev, &hm330x_params[0])) { print_str("Initialization failed\n"); return 1; }这里直接使用驱动提供的默认参数数组hm330x_params[0](定义于 hm330x_params.h),初始化失败则打印错误并退出。
2. 打印表头(按型号区分)
#if IS_USED(MODULE_HM3302) /* 打印 6 列 PM 浓度 + 6 列粒子数,共 12 列 */ #else /* 仅打印 6 列 PM 浓度 */ #endif通过IS_USED(MODULE_HM3302)宏判断当前编译的是哪个型号,从而决定表头宽度。
3. 每秒循环读取并打印
hm330x_data_t data; while (1) { ztimer_sleep(ZTIMER_MSEC, 1 * MS_PER_SEC); /* read the data and print them on success */ if (hm330x_read(&dev, &data) == 0) { print("|", 1); print_col_u32_dec(data.mc_pm_1, 7); /* ... 依次打印 amc_pm_1 / amc_pm_2p5 / amc_pm_10, 以及 hm3302 专属的 nc_pm_0p3 ~ nc_pm_10 ... */ } else { print_str("Could not read data from sensor\n"); } }循环体先ztimer_sleep(ZTIMER_MSEC, 1 * MS_PER_SEC)等待 1 秒,再调用hm330x_read()读取数据;成功则用fmt_table的print_col_u32_dec()按固定列宽打印,失败则提示 "Could not read data from sensor"。
四、输出格式深度解读:两套浓度与六档粒子数
README 给出了hm3302 与 hm3301 两种型号的完整运行输出,下面逐一解析。这是理解传感器数据模型的最佳入口。
4.1 hm3302 输出(完整样例,逐行保留)
main(): This is RIOT! (Version: 2021.04-devel-1342-g93325-pr_driver_hm330x) HM330X test application +------------Initializing------------+ +------------------------+------------------------+----------------------------------------------+ | Standard concentration | Atmospheric Environment| # Particles in 0.1l air of diameter >= | | PM1.0 | PM2.5 | PM10.0 | PM1.0 | PM2.5 | PM10.0 | 0.3µm | 0.5µm | 1.0µm | 2.5µm | 5.0µm | 10µm | +-------+-------+--------+-------+-------+--------+-------+-------+-------+-------+-------+------+ | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0| | 5| 7| 7| 5| 7| 7| 0| 0| 0| 0| 0| 0|hm3302 的表格分为三个区域:
- Standard concentration(标准/室内校准浓度):
mc_pm_1、mc_pm_2p5、mc_pm_10; - Atmospheric Environment(大气环境浓度):
amc_pm_1、amc_pm_2p5、amc_pm_10; - # Particles in 0.1l air(0.1 升空气中的粒子数):按粒径下界分为
0.3µm / 0.5µm / 1.0µm / 2.5µm / 5.0µm / 10µm六档,对应数据结构中的nc_pm_0p3、nc_pm_0p5、nc_pm_1、nc_pm_2p5、nc_pm_5、nc_pm_10。
上述字段全部定义在 drivers/include/hm330x.h 的hm330x_data_t结构体中,其中mc_*为浓度(单位 µg/m³),nc_*为粒子数浓度(单位 #/cm³)。
4.2 hm3301 输出(完整样例,逐行保留)
main(): This is RIOT! (Version: ) HM330X test application +------------Initializing------------+ +------------------------+------------------------+ | Standard concentration | Atmospheric Environment| | PM1.0 | PM2.5 | PM10.0 | PM1.0 | PM2.5 | PM10.0 | +-------+-------+--------+-------+-------+--------+ | 8| 11| 11| 8| 11| 11| | 8| 11| 11| 8| 11| 11| | 8| 11| 11| 8| 11| 11| | 9| 13| 13| 9| 13| 13| | 9| 13| 13| 9| 13| 13| | 9| 13| 13| 9| 13| 13| | 9| 13| 13| 9| 13| 13| | 9| 13| 13| 9| 13| 13|hm3301 仅输出两组 PM 浓度(室内/标准 与 大气环境),不含粒子数统计列——这正是 README 强调 "particle counting is only available with hm3302" 的直接体现。
4.3 两个型号的差异本质
从测试程序与驱动代码可以推断:是否输出粒子数完全由编译期宏MODULE_HM3302决定。在 main.c 中,表头打印与数据列打印都被#if IS_USED(MODULE_HM3302)包裹;在驱动解析 hm330x.c 中,nc_pm_*六个字段同样仅在MODULE_HM3302使能时从数据帧解析。因此默认DRIVER=hm3301编译时,输出只有 6 列浓度数据。
五、源码级原理:I2C 数据帧、CRC 与字段解析
测试程序能正确打印,依赖 drivers/hm330x/hm330x.c 中扎实的驱动实现。理解了它,也就理解了为什么输出表格长这样。
5.1 通信基础(常量定义)
hm330x_constants.h 定义了三个关键常量:
#define HM330X_I2C_ADDRESS (0x40U) /* I2C 器件地址 */ #define HM330X_DATA_LENGTH (29U) /* 一次读取的数据帧长度(字节) */ #define HM330X_CMD_I2C_MODE (0x88U) /* 切换 I2C 模式的命令字节 */5.2 初始化:复位引脚与 I2C 模式切换
hm330x_init()(hm330x.c)的执行顺序为:
- 校验并保存参数;
- 若配置了
reset_pin,初始化为输出并执行hm330x_reset()——该函数将复位引脚拉低约 10 µs(HM330X_RESET_TIME_US)再拉高,实现对传感器的硬件复位; - 若配置了
set_pin,初始化为输出并拉高(使能器件); - 调用
_set_i2c_mode()向地址0x40写入命令字节0x88,把传感器切换到 I2C 模式。
需要说明的是:HM330X 本身支持 UART 与 I2C 两种模式,而 RIOT 驱动只实现 I2C 通路(详见 drivers/include/hm330x.h 中的 About 说明),因此初始化时通过命令字节显式切换到 I2C 模式。初始化失败时返回值含义为:GPIO 初始化失败返回-EIO,I2C 模式设置失败返回-EPROTO。
5.3 读取:29 字节数据帧的校验与拆解
hm330x_read()(hm330x.c)是核心:
uint8_t buf[HM330X_DATA_LENGTH] = { 0 }; if (i2c_read_bytes(dev->params.i2c, HM330X_I2C_ADDRESS, buf, HM330X_DATA_LENGTH, 0)) { return -EPROTO; } /* calculate crc */ uint8_t crc = 0; for (uint8_t i = 0; i < HM330X_DATA_LENGTH - 1; i++) { crc += buf[i]; }- 一次 I2C 突发读取29 字节(数据帧前 2 字节为帧头,真正的测量数据从偏移 4 开始);
- CRC 校验:对前 28 个字节求和,与第 29 个字节(校验字节)比对;不匹配时打印
crc mismatch调试信息。注意当前实现只做告警、不拒绝数据; - 字段拆解:
mc_*、amc_*与nc_*共 12 个 16 位无符号整数,均按大端序(高字节在前)从帧中取出,例如mc_pm_1 = (buf[4] << 8) | buf[5],依次对齐到偏移 4~27。
这也解释了测试表格的列顺序:先室内/标准浓度(mc),再大气环境浓度(amc),最后(hm3302 专属)六档粒子数(nc)。
5.4 低功耗接口
驱动还提供了hm330x_sleep()/hm330x_wakeup()(hm330x.c):通过拉低/拉高set_pin(Set/Enable 引脚,高有效)控制器件睡眠与唤醒,便于在电池供电节点上按需采样。
六、参数配置:默认值与覆盖方式
测试程序直接使用hm330x_params[0],其默认值定义在 hm330x_params.h:
| 参数 | 默认值 | 说明 |
|---|---|---|
HM330X_PARAM_I2C_DEV | I2C_DEV(0) | 使用的 I2C 总线 |
HM330X_PARAM_RESET_PIN | GPIO_UNDEF | 复位引脚(低有效),默认未接线 |
HM330X_PARAM_SET_PIN | GPIO_UNDEF | Set/Enable 引脚(高有效),默认未接线 |
HM330X_SAUL_INFO | { .name = "hm330x" } | SAUL 注册名称 |
所有参数均可在板级或应用级通过宏覆盖(#ifndef ... #endif结构),例如在应用Makefile中追加CFLAGS += -DHM330X_PARAM_I2C_DEV=I2C_DEV(1)即可更换 I2C 总线。
此外,传感器的两套 PM 浓度(室内校准 vs 大气环境)如何取舍,由 Kconfig 中的HM330X_INDOOR_ENVIRONMENT配置项决定,对应头文件中的默认宏CONFIG_HM330X_INDOOR_ENVIRONMENT 1(hm330x.h)。该选项主要影响 SAUL 读数接口返回哪一组浓度值。
七、延伸:SAUL 集成与上层应用
测试程序展示的是最直接的驱动 API 用法。在实际应用中,HM330X 还通过 hm330x_saul.c 接入 RIOT 的SAUL(Sensor Actuator Uber Layer)传感器抽象层,从而可以配合saulshell 命令或auto_init使用:
- PM 浓度读数以
UNIT_GPM3(µg/m³,scale 为 -6)发布,注册为SAUL_SENSE_PM类型,包括mc_pm_1/mc_pm_2p5/mc_pm_10三个驱动条目; - hm3302 的粒子数读数以
UNIT_CPM3(#/cm³,scale 为 4)发布,注册为SAUL_SENSE_COUNT类型; - 浓度读数会根据
CONFIG_HM330X_INDOOR_ENVIRONMENT决定读取mc_*(室内)还是amc_*(大气)字段。
因此,同一份驱动既可以在 tests/drivers/hm330x 这类裸 API 测试中逐秒打印原始数据,也可以通过 SAUL 无缝接入gnrc网络栈或其他物联网应用,实现空气质量数据的上报。
八、总结
本文以 tests/drivers/hm330x/README.md 为骨架,完整继承了该文档的两份真实运行输出,并深入其背后的实现:
- 构建要点:
DRIVER ?= hm3301决定型号,make DRIVER=hm3302启用粒子计数; - 输出模型:mc(室内/标准浓度)、amc(大气环境浓度)、nc(仅 hm3302 的六档粒子数)三组数据,对应 hm330x_data_t 结构体;
- 底层原理:I2C 地址
0x40、29 字节数据帧、累加和 CRC、大端序字段拆解(hm330x.c); - 扩展能力:参数可覆盖的默认配置(hm330x_params.h)与 SAUL 抽象层集成。
对需要快速验证 HM330X 传感器硬件、或以此为模板编写自有采样任务的开发者来说,该测试应用是 RIOT OS 中最直接的参考实现。
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考