这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【e起DIY】基于Zephyr的蓝牙温湿度计

共1条 1/1 1 跳转至

【e起DIY】基于Zephyr的蓝牙温湿度计

工程师
2026-06-05 17:27:18     打赏

基于 Zephyr 的蓝牙温湿度计

一、项目背景介绍

1.1 选题背景

        在现代智能家居、工业监测、农业大棚、冷链物流等众多场景中,温湿度始终是最基础、最常用的环境参数。传统的"有线温湿度计"存在部署不灵活、布线成本高、覆盖范围有限等痛点;而基于蓝牙低功耗(BLE)的无线温湿度计则具备部署自由、低功耗、可手机直连等天然优势。

        随着 BLE 5.0+ 在智能手机上的全面普及,以及 NORDIC、TI、NXP 等主流芯片厂商不断推出高集成度、低功耗的 SoC,"无线温湿度计"已经是一项成熟可量产的方案。但在实际项目开发过程中,从"裸 MCU"到"稳定可用的产品"之间,仍然有大量的工程化工作要做:协议栈移植、传感器驱动、协议封装、低功耗优化、稳定性测试等。

本项目选择"基于 Zephyr 的蓝牙温湿度计"作为最终目标,出发点如下:

  1. 学习 BLE 协议栈的真实工程落地——区别于单纯的"协议学习",本次开发直面"从代码到烧录到手机 APP 联调"的全链路流程;

  2. 探索 Zephyr RTOS 在 BLE 物联网领域的开发模式——Zephyr 作为一款由 Linux 基金会托管、得到 NXP/Intel/Nordic 等多家支持的现代 RTOS,其模块化设计、设备树驱动模型与主流 Linux 内核思想一脉相承,是嵌入式工程师值得掌握的开源生态;

  3. 积累"传感器+BLE"的端到端经验——为后续更复杂的无线传感节点(多传感器、低功耗优化、安全加密等)打基础。

1.2 项目目标

本项目以"一个能稳定运行、可用手机读取的蓝牙温湿度计"为最终交付目标,具体分解为以下几条:

目标维度详细要求



功能性板载传感器周期性采集环境温湿度,通过 BLE 透传至手机 APP
兼容性与主流 BLE 调试助手(nRF Connect / EFR Connect / 蓝牙调试宝)兼容,无需自定义手机 App
实时性数据更新周期 1 秒,手机端可观察到连续数据流
稳定性支持手机断开后设备自动恢复广播并允许新连接

1.3 项目主要成果

经过方案设计、代码改造、硬件接线、固件烧录与功能验证后,本项目最终实现并验证了以下功能:

  • 板载温度采样精度 ±0.5℃,湿度采样精度 ±3 %RH(DHT20 标称);

  • 广播名为 Zephyr_NUS 的 BLE 外设,启动后自动可发现

  • 移动端调试助手(nRF Connect 等)连接后,每秒推送一条形如 T:25.32 H:60.45 的字符串

  • 手机端主动断开后,板载 LED 恢复闪烁、串口提示 Disconnected、设备自动重启广播,新连接后数据流立刻恢复;

  • 完整工程文件组织清晰,包含 prj.conf / app.overlay / src/main.c / drivers/ 等模块,便于二次开发。

二、硬件介绍

2.1 主控开发板:NXP FRDM-MCXW72

FRDM-MCXW72 是 NXP 半导体(恩智浦)2024 年推出的官方评估板,搭载 MCX W72 系列无线 MCU,是 NXP 在 Matter / Thread / Zigbee / BLE 等无线连接领域的主力平台之一。

2.2 温湿度传感器:Grove DHT20

Grove DHT20 是一款基于 Aosong(奥松电子)DHT20 数字温湿度传感器的 Seeed Studio Grove 标准化模组。

2.3 辅助设备

设备用途说明




Micro-USB 数据线供电 + J-Link 调试 + 串口必须为数据线,纯充电线无法识别
nRF Connect 手机 APP蓝牙调试助手Android / iOS 都有,扫描、连接、NUS Notify 一应俱全
任意 Windows 10/11 PC编译与烧录主机已装 Zephyr SDK 1.0.1 + Python 3.13 + West + CMake 3.30

硬件连接如下:

图片.png

三、方案框图和项目设计思路

3.1 系统总体框图

┌─────────────────┐  I2C (SDA=PTB4, SCL=PTB5, 100/400kHz)   ┌────────────────────────────┐
│  DHT20 传感器  │◄───────────────────────────────────────►│  FRDM-MCXW72 开发板        │
│  (地址 0x38)   │                                            │  ┌──────────────────────┐  │
│                │                                            │  │  MCXW727CMFTA        │  │
│  VCC=3V3       │                                            │  │  ┌────────────────┐  │  │
│  GND           │                                            │  │  │ Cortex-M33 CPU │  │  │
└────────────────┘                                            │  │  └───────┬────────┘  │  │
                                                              │  │          │           │  │
                                                              │  │  ┌───────▼────────┐  │  │
                                                              │  │  │ LPI2C1 控制器 │──┼──┘
                                                              │  │  └────────────────┘  │
                                                              │  │                      │
                                                              │  │  ┌────────────────┐  │
                                                              │  │  │ 2.4G 射频模块  │──┼──► 板载天线 ──► 空中 BLE 5.3
                                                              │  │  └────────────────┘  │
                                                              │  │                      │
                                                              │  │  ┌────────────────┐  │
                                                              │  │  │ LPUART1 串口  │──┼──► J-Link CDC ──► USB ──► PC 串口
                                                              │  │  └────────────────┘  │
                                                              │  │                      │
                                                              │  │  ┌────────────────┐  │
                                                              │  │  │ GPIO 控制器   │──┼──► 蓝色 LED (PTB1) / 用户按键 (PTC6)
                                                              │  │  └────────────────┘  │
                                                              │  └──────────────────────┘  │
                                                              │                            │
                                                              │  板载 J-Link 调试器         │
                                                              │  (USB <--> SWD + UART)     │
                                                              └──────────┬─────────────────┘
                                                                         │ USB
                                                              ┌──────────▼─────────────────┐
                                                              │  PC 编译/烧录 + 手机 APP    │
                                                              │  - west build / west flash  │
                                                              │  - 串口监视 (COM83)         │
                                                              │  - nRF Connect (扫描/连接)  │
                                                              └────────────────────────────┘

3.2 软件架构

Zephyr 工程的代码分层非常清晰,从底层到上层依次为:

┌───────────────────────────────────────────────────────┐
│ 应用层 (本项目的 src/main.c)                          │
│   ├─ 广播管理   (bt_le_adv_start)                     │
│   ├─ 连接管理   (BT_CONN_CB_DEFINE)                   │
│   ├─ NUS 服务   (bt_nus_send / bt_nus_cb_register)    │
│   └─ 传感器读取 (sensor_sample_fetch / channel_get)    │
├───────────────────────────────────────────────────────┤
│ Zephyr 服务层 (subsys/)                               │
│   ├─ bluetooth/services/nus   (NUS GATT 实现)         │
│   ├─ bluetooth/host           (GATT/ATT/GAP 协议栈)  │
│   └─ sensor                   (Sensor Framework)      │
├───────────────────────────────────────────────────────┤
│ Zephyr 驱动层 (drivers/)                              │
│   ├─ sensor/aosong/dht20      (DHT20 I2C 驱动)        │
│   ├─ i2c/nxp_lpi2c            (LPI2C1 控制器驱动)     │
│   ├─ bluetooth/nxp            (MCXW72 BLE 控制器)     │
│   ├─ gpio/...                 (LED GPIO 驱动)         │
│   └─ uart/...                 (J-Link 串口)           │
├───────────────────────────────────────────────────────┤
│ 硬件抽象 (HAL, 由 NXP MCUXpresso SDK 提供)            │
│   ├─ nxp,mcxw7 clock / pinctrl / peripheral           │
│   └─ 直接寄存器操作                                   │
├───────────────────────────────────────────────────────┤
│ ARM Cortex-M33 内核 + Zephyr 内核                     │
└───────────────────────────────────────────────────────┘

3.3 关键设计

在动手编码前,有几个关键决策点需要明确。这些选择直接影响后续的开发难度和最终效果。

为什么选 Zephyr?

  • 原生支持 BLE 5.3 协议栈——Zephyr 的 Bluetooth 子系统是经过 Bluetooth SIG 认证的完整 Host 层;

  • 统一驱动模型——所有传感器、外设都通过标准 API(sensor_*、i2c_*)访问,方便在不同 MCU 上移植;

  • 设备树驱动——硬件描述(I2C 总线、传感器节点、引脚复用)全部写在 .dts/.overlay 中,代码与硬件解耦

  • 维护活跃——NXP 官方支持,官方文档齐全,issue 响应快。

为什么选 NUS 作为传输协议?

  • 手机调试助手原生支持——nRF Connect 等 APP 在列出 GATT 服务时,会自动识别 NUS 并把 TX/RX 字符画成 "UART" 视图,免去自己开发 APP;

  • 协议简单——NUS 仅 2 个 characteristic(TX Notify / RX Write),是 BLE 上最简洁的"流式通道"之一;

  • 官方有现成模块——CONFIG_BT_ZEPHYR_NUS=y 直接打开,bt_nus_send() 即可发送,不用手写 GATT 表

为什么以 peripheral_hr 为起点?

Zephyr 自带的蓝牙示例大致分为两类:

  • 通用外设(peripheral、peripheral_gap_svc):只有一个空的 GATT 表,需要自己加服务

  • 标准服务(peripheral_hr、peripheral_bas、peripheral_dis):内置标准 profile,但不能直接用来传输应用数据

本项目先用 peripheral_hr 学习 Zephyr BLE 工程的完整骨架(初始化、连接回调、状态机、主循环),再改造成 NUS 透传。这是初学者最平滑的上手路径——比直接看 peripheral_nus 那种"已经做完的"例子学得更多。

为什么把 DHT20 驱动放在工程目录下而不是 SDK?

  • 学习目的——把驱动放在 drivers/sensor/aosong/dht20/ 下作为参考,未来想修改驱动逻辑(如增加 CRC 校验、改采样流程)时非常方便;

  • 解耦产品工程——业务代码与驱动代码分目录管理,符合实际产品工程的代码组织规范;

  • 最终选择 SDK 版本——Zephyr SDK 中已经包含完全相同的 DHT20 驱动,并且会被设备树节点自动启用;本地副本仅作为参考、备份存在,不参与编译。这样可以避免符号冲突的麻烦

四、调试软件、流程图和关键代码

开发环境

工具版本用途




Zephyr RTOSv3.7+RTOS + BLE 协议栈 + 驱动
Zephyr SDK1.0.1 (GCC 14.3.0)交叉编译工具链
MCUXpresso Toolsv25.xIDE + 烧录辅助
Visual Studio Code最新版代码编辑(推荐)
CMake3.30+构建系统
Ninja1.10+构建执行器
Python3.13west 工具链
West1.5+Zephyr 多仓管理 + 构建命令
J-LinkV8.94烧录 + 调试
nRF Connect任意版本手机端 BLE 调试 APP

软件流程图

主循环流程

                          ┌──────────────────┐
                          │   上电 / 复位     │
                          └────────┬─────────┘
                                   ▼
                  ┌────────────────────────────────┐
                  │  Zephyr 内核初始化              │
                  │  (drivers / subsystems / main) │
                  └────────────────┬───────────────┘
                                   ▼
                  ┌────────────────────────────────┐
                  │ DHT20 init:                    │
                  │  - 检查 I2C 总线是否 ready      │
                  │  - 等待 100ms 上电稳定          │
                  │  - 读取并校正 status 寄存器     │
                  │ (init 失败: device 不 ready)    │
                  └────────────────┬───────────────┘
                                   ▼
                          ┌────────────────┐
                          │   main()       │
                          └────────┬───────┘
                                   ▼
                  ┌────────────────────────────────┐
                  │ bt_enable(NULL)                 │
                  │  - 初始化 HCI 传输层            │
                  │  - 初始化 Host 层协议栈          │
                  │  - 设置本地 BLE 地址             │
                  └────────────────┬───────────────┘
                                   ▼
                  ┌────────────────────────────────┐
                  │ bt_nus_cb_register(&nus_cb)     │
                  │  - 注册 NUS 通知/接收回调         │
                  └────────────────┬───────────────┘
                                   ▼
                  ┌────────────────────────────────┐
                  │ bt_le_adv_start(...)            │
                  │  - 启动 Legacy 可连接广播         │
                  │  - 间隔 30~60ms                 │
                  │  - 广播数据: flags + NUS UUID    │
                  └────────────────┬───────────────┘
                                   ▼
                  ┌────────────────────────────────┐
                  │      while (1) 主循环           │◄──────────┐
                  │  k_sleep(K_SECONDS(1))          │           │
                  │  if (current_conn):             │           │
                  │    - read DHT20                 │           │
                  │    - bt_nus_send("T:xx.x H:xx") │           │
                  │  if (DISCONNECTED 位):           │           │
                  │    - restart advertising         │           │
                  └────────────────┬───────────────┘           │
                                   │                            │
              ┌────────────────────┴───────────────────┐        │
              ▼                                        ▼        │
   ┌──────────────────┐                  ┌──────────────────┐  │
   │  手机连上         │                  │  手机断开          │  │
   │  connected()     │                  │  disconnected()  │  │
   │  - bt_conn_ref() │                  │  - bt_conn_unref │  │
   │  - 置 CONNECTED  │                  │  - 置 DISCONNECT │──┘
   │  - LED 常亮       │                  │  - LED 闪烁      │
   └──────────────────┘                  └──────────────────┘

DHT20 单次读取流程

   ┌──────────────────────────────────┐
   │ sensor_sample_fetch(dev)         │
   │  触发测量: 写 [0xAC, 0x33, 0x00] │
   └────────────┬─────────────────────┘
                ▼
   ┌──────────────────────────────────┐
   │ k_msleep(80)                    │
   │  等待 DHT20 完成测量              │
   └────────────┬─────────────────────┘
                ▼
   ┌──────────────────────────────────┐
   │ i2c_read_dt(bus, buf, 7)        │
   │  读取 7 字节:                    │
   │  buf[0]: 状态                    │
   │  buf[1..3]: 湿度原始值 (20 bit)  │
   │  buf[3..5]: 温度原始值 (20 bit)  │
   │  buf[6]: CRC                    │
   └────────────┬─────────────────────┘
                ▼
   ┌──────────────────────────────────┐
   │ 校验 buf[0] 的 bit7:            │
   │  - 1: busy, 报错                │
   │  - 0: 测量完成                  │
   └────────────┬─────────────────────┘
                ▼
   ┌──────────────────────────────────┐
   │ 计算公式:                       │
   │   T = (raw/2^20)*200 - 50       │
   │   H = (raw/2^20)*100            │
   │ 结果存为 struct sensor_value     │
   │   val1: 整数部分                 │
   │   val2: 小数部分 (×1e6)         │
   └────────────┬─────────────────────┘
                ▼
         返回到应用层

关键代码详解

设备树 Overlay(app.overlay)

设备树是 Zephyr 描述硬件的核心方式。下面的 overlay 文件把 DHT20 挂载到 lpi2c1 总线:

&lpi2c1 {
    status = "okay";
    pinctrl-0 = <&pinmux_lpi2c1>;
    pinctrl-names = "default";

    dht20: dht20@38 {
        compatible = "aosong,dht20";
        reg = <0x38>;
        status = "okay";
    };
};
  • &lpi2c1 —— 引用板级 dts 中的 lpi2c1 节点

  • pinctrl-0 = <&pinmux_lpi2c1> —— 引用板级 pinmux 配置(PTB4/PTB5)

  • compatible = "aosong,dht20" —— 与 Zephyr SDK 中 DHT20 驱动匹配

  • reg = <0x38> —— I2C 设备地址

Kconfig 配置(prj.conf)

# Bluetooth
CONFIG_BT=y
CONFIG_LOG=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_ZEPHYR_NUS=y
CONFIG_BT_DEVICE_NAME="Zephyr_NUS"

# I2C bus
CONFIG_I2C=y

# Sensors
CONFIG_SENSOR=y
CONFIG_DHT20=y
  • CONFIG_BT_ZEPHYR_NUS=y —— 关键,使能 NUS 服务模块

  • CONFIG_I2C=y —— 使能 I2C 子系统

  • CONFIG_SENSOR=y —— 使能 Sensor Framework

  • CONFIG_DHT20=y —— 选择 aosong_dht20 驱动(实际由设备树触发自动选中)

主循环核心代码

int main(void)
{
    int err;
    static const char hello_msg[] = "hello\n";   // 早期调试用

    err = bt_enable(NULL);          /* (1) 初始化 BLE 协议栈 */
    if (err) { return 0; }

    bt_nus_cb_register(&nus_cb, NULL); /* (2) 注册 NUS 回调 */

    start_advertising();            /* (3) 启动广播 */

    while (1) {
        k_sleep(K_SECONDS(1));

        if (current_conn) {        /* (4) 已连接则发数据 */
            struct sensor_value temp, hum;
            char buf[64];
            int len = snprintf(buf, sizeof(buf),
                "T:%d.%02d H:%d.%02d\n",
                temp.val1, abs(temp.val2) / 10000,
                hum.val1, abs(hum.val2) / 10000);
            bt_nus_send(current_conn, buf, len);
        }

        if (STATE_DISCONNECTED 位) { /* (5) 断线重启广播 */
            start_advertising();
        }
    }
}

5 个关键步骤

  1. bt_enable(NULL) —— 同步等待协议栈初始化完成

  2. bt_nus_cb_register() —— 注册 NUS 通知状态/接收回调

  3. start_advertising() —— 启动可发现广播

  4. bt_nus_send() —— 只有连接后才发数据(避免无连接时返回 ENOTCONN 刷屏)

  5. 断线后重启广播 —— 这是"支持断线重连"的关键

传感器读取函数

static int read_dht20(struct sensor_value *temp, struct sensor_value *hum)
{
    const struct device *dev = DEVICE_DT_GET(DT_INST(0, aosong_dht20));

    if (!device_is_ready(dev)) {     /* 检查驱动 init 是否成功 */
        printk("DHT20 device not ready\n");
        return -ENODEV;
    }

    if (sensor_sample_fetch(dev) < 0) return -EIO;  /* 触发测量 + 读回 */
    sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, temp);
    sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, hum);
    return 0;
}
  • DT_INST(0, aosong_dht20) —— 取得设备树中第 0 个 aosong,dht20 节点

  • DEVICE_DT_GET() —— 把节点转换为 struct device *

  • device_is_ready() —— 关键防御,驱动 init 失败时返回 false(这是调试时排查故障的入口)

  • sensor_sample_fetch() —— 触发一次完整测量(~80ms)

  • sensor_channel_get() —— 分别取得温湿度通道值

五、硬件功能展示

串口输出信息:

图片.png

手机app接收打印展示:

图片.png







关键词: Zephyr     湿度计    

共1条 1/1 1 跳转至

回复

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