这个阶段的目标很简单:让FRDM-MCXW72这块板子能采到温湿度数据,并按2秒一次的节奏从串口打印出来。最终目标是做一个蓝牙温湿度计。这阶段只做温湿度采集这一段。
板子:NXP FRDM-MCXW72(恩智浦的一块开发板,自带lpi2c1在 Arduino 头上)
传感器:Grove-TH Sensor V2.0(板子上的核心其实是奥松电子的DHT20)
接口:I2C(默认地 0x38)
输出:串口(115200 8N1)每2秒打一次温度和湿度
工程基线是 Zephyr 自带的hello_world模板,原来只打一句Hello World!。这次要把它升级成真正能采数据的程序。
DHT20传感器
DHT20是奥松电子出的一款数字式温湿度传感器,I2C 接口。几个关键点:供电:2.0V~5.5V,3.3V 直接接上就行
I2C 地址:0x38(7-bit),速率标准 100kHz 或快速 400kHz 都行
数据格式:触发测量后读7个字节,里头20 bit湿度 +20 bit温度 + 1字节 CRC
转换公式(来自 datasheet):
温度 T = S/2^20 × 200 − 50(单位 °C)
湿度 RH = S/2^20 × 100(单位 %)
我们这次其实不用自己算公式,Zephyr SDK 里已经有人把 DHT20 的驱动写好了,我们直接用就行。但作为背景知识知道一下也好——下面写代码的时候你会看到这些公式。
这次手里的传感器板子实际上也是这次指定的传感器之一:

Zephyr 里怎么加一个传感器
Zephyr 的传感器接入方式非常标准化,整个套路几乎适用于所有传感器:
在设备树(devicetree)里声明这个设备——告诉Zephyr它在哪个总线上、地址多少
在prj.conf里打开对应驱动
应用层用统一的sensor API 读数据:sample_fetch触发一次采集 + channel_get取具体通道
实操:三步接入 DHT20
第 1 步:写设备树 overlay(app.overlay)
Zephyr板级设备树(frdm_mcxw72.dts)已经把&lpi2c1打开并配好引脚了(PTB4 SDA / PTB5 SCL,也就是Arduino接口的D14/D15)。我不需要再去动板级DTS,只要写一个overlay就能往这条总线上挂东西。&lpi2c1 { dht20: dht20@38 { compatible = "aosong,dht20"; reg = <0x38>; status = "okay"; }; };解释一下这几行:
&lpi2c1 { ... }:往 lpi2c1 总线节点里塞东西
dht20: dht20@38:节点名字叫 dht20(前面那个 dht20: 是 label,方便后面 C 代码用 DT_NODELABEL(dht20) 引用),@38 是它在总线上的地址
compatible = "aosong,dht20":这是关键。Zephyr 的 DHT20 驱动就是靠这串字符串匹配来认领这个节点的
reg = <0x38>:I2C 地址 0x38
status = "okay":默认设备树里所有节点都是 disabled,写上它才真的启用
第 2 步:开驱动(prj.conf)
prj.conf是Kconfig配置文件,告诉Zephyr这次构建要打开哪些功能。
CONFIG_I2C=y CONFIG_SENSOR=y CONFIG_DHT20=y
CONFIG_I2C:DHT20 是 I2C 设备,I2C 驱动得开
CONFIG_SENSOR:Zephyr 统一的 sensor 子系统,相当于一个"传感器框架"
CONFIG_DHT20:DHT20 的具体驱动。dht20.c 里那个 bool "DHT20 ..." 的 Kconfig 项就是它
这里有个小细节:DHT20 Kconfig里有这么一句
depends on DT_HAS_AOSONG_DHT20_ENABLED || DT_HAS_AOSONG_AHT20_ENABLED ||DT_HAS_AOSONG_AM2301B_ENABLED
意思就是"必须设备树里有这个节点,我才会被编进去"。所以overlay写错的话,驱动不会被链接,能少踩点坑。第 3 步:写应用代码(src/main.c)
#define DHT20_NODE DT_NODELABEL(dht20) static const struct device *dht20 = DEVICE_DT_GET(DHT20_NODE);
DT_NODELABEL(dht20)拿到设备树里那个dht20: label对应的节点,DEVICE_DT_GET把它转成一个device句柄。这一步是编译期完成的——如果节点不存在或者被disable了,直接编译报错,不用等到运行时才发现。
主循环就两件事:
sensor_sample_fetch(dht20); // 触发一次测量 sensor_channel_get(dht20, SENSOR_CHAN_AMBIENT_TEMP, &temperature); // 读温度 sensor_channel_get(dht20, SENSOR_CHAN_HUMIDITY, &humidity); // 读湿度
SENSOR_CHAN_AMBIENT_TEMP是「环境温度」的语义通道,温度、湿度、加速度、陀螺仪……每个传感器读什么,都是用这套枚举挑的,写代码时不用关心具体寄存器和协议。
每次读完之后k_msleep(2000)等2秒,就完成我们最初的目标了。坑1:Zephyr的printk不支持%f
第一次写完代码用%.2f打印温度,烧进去一看:
DHT20: *float* degC, *float* %RH DHT20: *float* degC, *float* %RH
那个*float*不是我的变量,是*%f这几个字符被原样打出来。原因是Zephyr默认链 picolibc(一个精简版的libc),为了省体积把浮点格式说明符砍了。
修复办法就一句:Zephyr的sensor_value用「整数 + 微值」两个int 表示一个数(val1 = 整数,val2 = 百万分之一精度的余项)。手动拼一下就行:
static void print_sensor_value(const struct sensor_value *v) { printk("%d.%02d", v->val1, abs(v->val2) / 10000); }跑起来之后串口就是这样的:

查datasheet拿I2C地址
overlay加节点(compatible 写对就行)
prj.conf 开对应驱动
应用层用sensor API读
我要赚赏金
