基于 NXP FRDM-MCXW72 开发板实战经验,以 SHT40 温湿度传感器为例,详细说明从硬件配置到软件集成的完整流程。
一、Zephyr 传感器框架概述Zephyr 提供了统一的传感器抽象层(Sensor API),位于 include/zephyr/drivers/sensor.h。这套 API 的设计理念是驱动与应用解耦:应用代码不需要关心传感器具体是哪家厂商、通过什么总线通信,只调用统一的接口即可获取数据。这意味着,当你在项目中替换传感器型号(比如从 SHT40 换成 AHT20),只要新传感器的驱动支持同样的 channel 类型,应用代码几乎不需要改动。这大大降低了硬件迁移成本。
传感器框架的核心概念:
Device Tree - 描述硬件连接(I2C 地址、中断引脚等)
Driver - 厂商提供的驱动,实现 sensor_driver_api
Channel - 统一的数据通道,如 SENSOR_CHAN_AMBIENT_TEMP 表示温度
Sample Fetch - 触发传感器进行一次测量
Channel Get - 从传感器读取特定通道的数值
peripheral_hr/ ├── prj.conf # 应用层 Kconfig 配置,所有 CONFIG_* 在此定义 ├── CMakeLists.txt # CMake 构建文件,定义如何编译源码 ├── src/ │ └── main.c # 应用主逻辑 ├── boards/ │ └── frdm_mcxw72.overlay # 设备树覆盖文件,修改默认硬件配置 └── build_debug/ # 构建产物目录,编译后自动生成
prj.conf 是控制功能开关的核心文件。每一个 CONFIG_xxx 都可以在 Zephyr 的 Kconfig 系统中找到对应的选项。比如CONFIG_SENSOR=y 会让构建系统包含传感器驱动框架;CONFIG_I2C=y 会编译 I2C 总线驱动并初始化相关硬件。
boards/frdm_mcxw72.overlay 是设备树覆盖文件。Zephyr 默认只为开发板提供基础配置,如果接了额外的传感器,需要通过 overlay 文件"覆盖"默认配置。设备树是Zephyr硬件抽象的核心,后面会详细说明。
三、设备树配置 - 硬件层面的声明设备树(Device Tree)是 Zephyr 描述硬件配置的主要方式。它的语法类似 dts,描述了 CPU、外设、传感器等硬件节点及其连接关系。
在 boards/frdm_mcxw72.overlay 中添加 SHT40 传感器:
&lpi2c1 {
#address-cells = <1>;
#size-cells = <0>;
sht40: sht40@44 {
compatible = "sensirion,sht4x";
reg = <0x44>;
repeatability = <1>;
status = "okay";
};
};&lpi2c1 — 这是一个引用语法,引用的是 frdm_mcxw72.dts 中已经定义好的 lpi2c1 外设节点。选择 lpi2c1 而不是 lpi2c0 的原因很简单:frdm_mcxw72.dts 中 lpi2c1 默认 status = "okay"(已启用),而 lpi2c0 默认是 status = "disabled"(禁用)。既然 lpi2c1 已经能正常工作,就直接用。
sht40@44 — 节点名,44 是 I2C 从机地址(十六进制 0x44)。SHT40 的 I2C 地址是出厂固定的 0x44,无法修改。
compatible = "sensirion,sht4x" — 最重要的一行。这个字符串必须与驱动匹配。Zephyr 会在 drivers/sensor/sht4x.c 中查找同名的 compatible 字符串,如果找不到就报错 device not ready。如果你用的传感器没有现成驱动,这行就需要写成你自己的驱动名字。
reg = <0x44> — 和节点名的 44 对应,重复写一遍确认地址无误。
status = "okay" — 显式声明这个传感器可用。
为什么要通过设备树而不是代码配置? 因为设备树在编译前就确定了硬件连接关系,编译器可以根据设备树生成最优的初始化代码,且所有硬件配置集中在一处,不分散在代码的各个角落。
引脚是如何确定的可能好奇 &lpi2c1 的 SCL/SDA 引脚是哪些?这些不在 overlay 中配置,而是在 frdm_mcxw72.dts 中通过 pinctrl 绑定好的:
&lpi2c1 {
status = "okay";
pinctrl-0 = <&pinmux_lpi2c1>; // ← 引用引脚配置
pinctrl-names = "default";
};引脚定义在 boards/nxp/frdm_mcxw72/frdm_mcxw72-pinctrl.dtsi:
pinmux_lpi2c1: pinmux_lpi2c1 {
group0 {
pinmux = <LPI2C1_SCL_PTB5>, // PTA/PTC/PTB 是端口分组
<LPI2C1_SDA_PTB4>; // PTB5 = SCL, PTB4 = SDA
drive-open-drain; // I2C 必须开漏输出
};
};工程的 SHT40 使用的是 lpi2c1,引脚是 PTB5 (SCL) 和 PTB4 (SDA)`。硬件上这两个引脚已经接好,不需要在 overlay 中额外配置。
具体的连接如下:

四、Kconfig 配置 - 功能开关
Zephyr 使用 Kconfig 系统管理功能模块的编译开关。每个 CONFIG_xxx 对应一个 Kconfig 符号,=y 表示启用该模块,=n 表示禁用。
在 prj.conf 中添加:
CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_DEVICE_NAME="MCXW72-BLE" CONFIG_BT_GATT_NOTIFY_MULTIPLE=y CONFIG_BT_ZEPHYR_NUS=y CONFIG_LOG=y CONFIG_I2C=y CONFIG_SENSOR=y CONFIG_SHT40=y
这些配置会自动传递到构建系统,目前涉及的是蓝牙和传感器。可以在 build_debug/zephyr/.config 中查看所有配置的实际值,确认是否符合预期。
五、驱动获取与数据读取初始化#define SHT40_NODE DT_NODELABEL(sht40)
static const struct device *sensor_dev;
int sensor_init(void) {
sensor_dev = DEVICE_DT_GET(SHT40_NODE);
if (!device_is_ready(sensor_dev)) {
printk("SHT40 sensor device not ready\n");
return -ENODEV;
}
printk("SHT40 sensor initialized\n");
return 0;
}DT_NODELABEL(sht40) 从设备树中根据标签查找节点并获取其地址。 DEVICE_DT_GET() 将设备树节点转换为一个 struct device* 句柄,这是 Zephyr 所有设备操作的统一入口。
device_is_ready() 检查驱动是否已初始化成功。如果驱动返回错误,通常是硬件连接问题(I2C 地址错误、SCL/SDA 引脚接线问题、或总线根本没使能)。
读取数据int fetch_sensor_data(int32_t *temp, int32_t *humidity) {
struct sensor_value temp_val, hum_val;
int ret;
ret = sensor_sample_fetch(sensor_dev);
if (ret < 0) {
printk("Failed to fetch sensor sample (err %d)\n", ret);
return ret;
}
ret = sensor_channel_get(sensor_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp_val);
if (ret < 0) {
return ret;
}
ret = sensor_channel_get(sensor_dev, SENSOR_CHAN_HUMIDITY, &hum_val);
if (ret < 0) {
return ret;
}
// sensor_value 的 val1 是整数部分,val2 是小数部分(微精度)
*temp = temp_val.val1 * 1000000 + temp_val.val2;
*humidity = hum_val.val1 * 1000000 + hum_val.val2;
return 0;
}sensor_sample_fetch() 触发一次传感器测量(通常对应 I2C 通信)。某些传感器需要等待一段时间才能完成测量,驱动会负责这个延时。
sensor_channel_get() 从驱动返回的缓存中读取指定通道的值。注意:fetch 和 get 是两步分离的,这样做的好处是可以一次性触发多通道采样,保证数据的时间一致性。
sensor_value 的数据格式是 val1 * 10^6 + val2,比如温度 26.5°C 表示为 26500000(val1=26,val2=500000)。如果温度为负,val1 为负数,val2 始终为正。
六、编译与烧录# 编译 cd build_debug ninja # 烧录(通过 JLink) ninja flash # 调试(启动 JLink GDB Server) ninja debug
构建输出目录 build_debug/ 中:
zephyr/zephyr.elf - 编译后的固件(带调试信息)
zephyr/zephyr.bin - 纯二进制固件(用于烧录)
zephyr/zephyr.map - 符号映射表
ninja flash 会自动调用 JLink 工具将 zephyr.bin 烧录到开发板的内部 Flash 中。
七、重点检查当你需要换用新传感器或新增传感器时,按以下顺序检查:
设备树 - overlay 文件中节点是否正确添加?compatible 与驱动匹配?I2C 地址正确?
2. Kconfig - 对应总线的驱动是否开启(I2C/SPI/GPIO)?传感器框架是否开启?
3. 驱动可用性 - Zephyr 是否已有该传感器的驱动?路径在 zephyr/drivers/sensor/。如果没有,需要自己编写或找社区贡献。
4. Channel 类型 - 需要的 channel 是否被驱动支持?比如 SHT40 支持 SENSOR_CHAN_AMBIENT_TEMP 和 SENSOR_CHAN_HUMIDITY,但不支持光照或气压。
5. 数据解析 - 驱动返回的 sensor_value 格式是否理解?单位是什么?有些驱动返回原始 ADC 值,需要自行转换为物理量。
我要赚赏金
