这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 蓝牙低功耗温湿度计-成果帖

共1条 1/1 1 跳转至

蓝牙低功耗温湿度计-成果帖

助工
2026-06-22 19:23:36     打赏

蓝牙低功耗温湿度计

基于 FRDM-MCXW71 + DFR0588 的 ADC + BLE 无线温湿度采集方案

成果贴

本文介绍基于 NXP FRDM-MCXW71 开发板和 DFRobot DFR0588 模拟温湿度传感器的蓝牙低功耗温湿度计方案。该方案通过 ADC 采集 SHT30 芯片的模拟电压输出,换算为温度和湿度值,并通过 BLE GATT Notify 以 JSON 格式实时无线传输,同时支持 UART Shell 本地交互和 Web Bluetooth 浏览器前端实时显示。整体方案兼具低功耗无线传输、本地调试便利和可视化前端三大特性,适用于物联网温湿度监测场景。完整代码基于模块化设计,核心分为 ADC 初始化与采样、BLE GATT 服务与通知、RGB LED 状态指示、Shell 命令、采样线程五大模块,其中 BLE 通知采用 CCC(Client Characteristic Configuration)订阅机制,兼顾功耗与实时性,实现过程如下。

一、硬件原理

1.1 DFRobot DFR0588 模拟温湿度传感器

DFRobot DFR0588 是 Gravity 系列模拟温湿度传感器,内部采用 SHT30 芯片,但与常规 SHT30 的 I2C 接口不同,该模块将 SHT30 的数字信号通过片上 DAC 转换为两路模拟电压输出:

• T 引脚(温度输出):输出电压范围约 0.3 V ~ 2.7 V,对应温度 -40 °C ~ 125 °C,线性关系如下:

T( °C) = -66.875 + 72.917 × V_T

其中 V_T 为 T 引脚输出电压(单位:V)

 

• RH 引脚(湿度输出):输出电压范围约 0.3 V ~ 2.7 V,对应相对湿度 0% RH ~ 100% RH,线性关系如下:

RH(% ) = -12.5 + 41.667 × V_RH

其中 V_RH 为 RH 引脚输出电压(单位:V)

 

为提高计算效率,代码中采用定点数运算(放大 1000 倍)替代浮点运算,避免 MCU 浮点协处理器开销:

#define DFR0588_TEMP_OFFSET_X1000 (-66875)   // -66.875 × 1000

#define DFR0588_TEMP_SLOPE_X1000   72917      // 72.917  × 1000

#define DFR0588_HUM_OFFSET_X1000  (-12500)    // -12.5   × 1000

#define DFR0588_HUM_SLOPE_X1000   41667       // 41.667  × 1000

 

static int32_t dfr0588_temp_c_x10(int32_t mv)

{

    return (DFR0588_TEMP_OFFSET_X1000 * 10

            + DFR0588_TEMP_SLOPE_X1000 * mv / 100) / 1000;

}

 

static int32_t dfr0588_hum_rh_x10(int32_t mv)

{

    int32_t rh_x10 = (DFR0588_HUM_OFFSET_X1000 * 10

                     + DFR0588_HUM_SLOPE_X1000 * mv / 100) / 1000;

    if (rh_x10 < 0) rh_x10 = 0;

    if (rh_x10 > 1000) rh_x10 = 1000;

    return rh_x10;

}

 

1.2 硬件连接设计

本方案中 MCXW71 FRDM 开发板与 DFR0588 传感器、外设的核心连接关系如下:

• ADC 输入通道:MCU 的 LPADC 模块通过两路模拟输入引脚连接 DFR0588 的模拟输出:

温度通道:ADC0_A6 / PTD2  → DFR0588 T 引脚

         Zephyr 通道 ID: 6,LPADC 通道: MCUX_LPADC_CH2A

湿度通道:ADC0_B6 / PTD3  → DFR0588 RH 引脚

         Zephyr 通道 ID: 7,LPADC 通道: MCUX_LPADC_CH2B

 

• 电压参考:采用外部 VREFH 引脚(= VDDA = 3.3 V)作为 ADC 参考电压。DFR0588 最高输出电压 2.7 V,必须使用 3.3 V 外部参考而非 SoC 内部 VREF 调节器(最大 2.1 V)。

 

• RGB LED 状态指示:

红色 PWM  → PTB0 (red_pwm_led)

绿色 PWM  → PTB1 (green_pwm_led)

蓝色 PWM  → PTB2 (blue_pwm_led)

 

• BLE 通信:板载 NBU(NXP Bluetooth Unit)协处理器,通过 UART HCI 协议与主核 Cortex-M33 通信,实现蓝牙 5.2 外设功能。

 

• 串口调试:板载 DAPLink 虚拟串口,通过 USB 连接 PC 提供 UART Shell 交互界面,波特率与 BLE 配置无关,用于本地调试和状态查询。


二、软件实现

2.1 开发环境

• 开发框架:Zephyr RTOS 4.x(基于 Zephyr 主线版本)

• 目标平台:NXP FRDM-MCXW71(MCXW716C 双核 MCU:Cortex-M33 + NBU 蓝牙子系统)

• 工具链:Zephyr SDK(ARM GCC)或 ARM GCC 交叉编译工具链

• 构建系统:West + Ninja + CMake,采用 CMakePresets 管理构建配置

• 核心功能:ADC 初始化与多通道采样、BLE GATT 服务注册与 Notify 推送、RGB PWM LED 状态指示、UART Shell 交互命令、定时采样线程

• 板级 BSP:Zephyr 官方 frdm_mcxw71 板级支持包,含 LPADC、FlexPWM、LPUART、BLE 等外设驱动

 

2.2 代码架构与核心模块解析

完整代码基于模块化设计,核心分为 ADC 初始化与采样、BLE GATT 服务与通知、RGB LED 状态指示、Shell 命令、采样线程五大模块。以下是关键代码解析:

 

1)全局变量与数据结构定义

全局变量是整个程序的状态基石,既要管理温湿度采样状态,也要为 BLE 通知和 Shell 交互提供数据支持,具体代码及对应功能说明如下:

#include <zephyr/kernel.h>

#include <zephyr/device.h>

#include <zephyr/drivers/adc.h>

#include <zephyr/drivers/pwm.h>

#include <zephyr/bluetooth/bluetooth.h>

#include <zephyr/bluetooth/gatt.h>

#include <zephyr/shell/shell.h>

// Zephyr 标准头文件,包含内核、ADC、PWM、蓝牙、Shell 等核心模块

 

// ---------- 板级资源 ----------

static const struct pwm_dt_spec led_r =

    PWM_DT_SPEC_GET(DT_ALIAS(red_pwm_led));

static const struct pwm_dt_spec led_g =

    PWM_DT_SPEC_GET(DT_ALIAS(green_pwm_led));

static const struct pwm_dt_spec led_b =

    PWM_DT_SPEC_GET(DT_ALIAS(blue_pwm_led));

 

static const struct adc_dt_spec adc_temp =

    ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), temperature);

static const struct adc_dt_spec adc_hum =

    ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), humidity);

// 通过 Devicetree API 获取板级外设资源,避免硬编码引脚号,实现与板级描述解耦

 

// ---------- BLE 自定义 UUID ----------

#define BT_UUID_TH_SVC_VAL  \

    BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, \

                       0x1234, 0x56789abcdef0)

#define BT_UUID_TH_DATA_VAL \

    BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, \

                       0x1234, 0x56789abcdef1)

// 自定义 128 位 UUID:服务 UUID 和数据特征 UUID,避免与标准 BLE 服务冲突

 

// ---------- 应用状态 ----------

#define SAMPLE_PERIOD K_SECONDS(2)    // 采样周期:2 秒

#define PAYLOAD_MAX_LEN 160            // JSON 载荷最大长度

 

struct th_sample {

    uint32_t seq;          // 采样序号

    int16_t  temp_raw;     // 温度 ADC 原始值

    int16_t  hum_raw;      // 湿度 ADC 原始值

    int32_t  temp_mv;      // 温度通道毫伏值

    int32_t  hum_mv;       // 湿度通道毫伏值

    int32_t  temp_c_x10;   // 温度值(°C,放大 10 倍)

    int32_t  hum_rh_x10;   // 湿度值(%RH,放大 10 倍)

};

 

static struct bt_conn *current_conn;     // 当前 BLE 连接

static bool notify_enabled;               // CCC 通知使能标志

static struct th_sample latest_sample;    // 最新采样数据

static char latest_payload[PAYLOAD_MAX_LEN] = "{}";

// th_sample 结构体统一管理所有采样相关数据,seq 字段用于前端追踪采样连续性

 

2)ADC 初始化与采样模块

该模块是温湿度数据采集的核心,包含 ADC 通道初始化、原始值读取、毫伏转换、传感器公式计算等子模块。采用 Zephyr ADC DT API 实现与设备树的自动绑定,简化移植工作:

// ========== ADC 通道初始化 ==========

static int init_adc_channel(

    const struct adc_dt_spec *spec,

    const char *name)

{

    int err;

    if (!adc_is_ready_dt(spec)) {

        printk("[ADC] %s device not ready\n", name);

        return -ENODEV;

    }

    if (!spec->channel_cfg_dt_node_exists) {

        printk("[ADC] %s channel config missing\n", name);

        return -EINVAL;

    }

    err = adc_channel_setup_dt(spec);

    if (err < 0) {

        printk("[ADC] %s setup failed: %d\n", name, err);

        return err;

    }

    printk("[ADC] %s ready: %s logical ch %u\n",

           name, spec->dev->name, spec->channel_id);

    return 0;

}

// 通过 adc_is_ready_dt / adc_channel_setup_dt 等 DT API 完成通道初始化,adc_dt_spec 结构体从 app.overlay 自动获取引脚、参考电压、分辨率等参数

 

// ========== ADC 读取与毫伏转换 ==========

static int adc_read_raw_mv(

    const struct adc_dt_spec *spec,

    int16_t *raw_out, int32_t *mv_out)

{

    uint32_t buf = 0;

    struct adc_sequence sequence;

    int err;

 

    adc_sequence_init_dt(spec, &sequence);

    err = adc_read_dt(spec, &sequence);

    if (err < 0) return err;

 

    int32_t mv = (int32_t)buf;

    err = adc_raw_to_millivolts_dt(spec, &mv);

    if (err < 0) return err;

 

    if (mv < 0) mv = 0;

    if (mv > (int32_t)spec->vref_mv)

        mv = (int32_t)spec->vref_mv;

    *raw_out = (int16_t)buf;

    *mv_out = mv;

    return 0;

}

// 使用 adc_sequence_init_dt + adc_read_dt + adc_raw_to_millivolts_dt 完整 DT 驱动链,毫伏值限幅到 [0, vref_mv] 防止异常数据

 

// ========== 完整采样流程 ==========

static int take_sample(struct th_sample *sample)

{

    int err;

    err = adc_read_raw_mv(&adc_temp,

            &sample->temp_raw,

            &sample->temp_mv);

    if (err < 0) return err;

 

    err = adc_read_raw_mv(&adc_hum,

            &sample->hum_raw,

            &sample->hum_mv);

    if (err < 0) return err;

 

    sample->seq = latest_sample.seq + 1;

    sample->temp_c_x10 =

        dfr0588_temp_c_x10(sample->temp_mv);

    sample->hum_rh_x10 =

        dfr0588_hum_rh_x10(sample->hum_mv);

    return 0;

}

// 依次采集温度和湿度通道 → 毫伏转换 → 应用传感器公式 → 递增采样序号

 

3)BLE GATT 服务与通知模块

该模块是蓝牙无线传输的核心,采用 Zephyr BT GATT API 注册自定义温湿度服务,支持 Read 和 Notify 两种数据获取方式。数据载荷采用 UTF-8 JSON 格式,便于 Web Bluetooth 前端直接解析:

// ========== GATT 服务定义 ==========

BT_GATT_SERVICE_DEFINE(th_svc,

    BT_GATT_PRIMARY_SERVICE(&uuid_th_svc),

 

    BT_GATT_CHARACTERISTIC(&uuid_th_data.uuid,

        BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,

        BT_GATT_PERM_READ,

        th_read, NULL, latest_payload),

 

    BT_GATT_CCC(th_ccc_changed,

        BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),

);

// 注册一个自定义 GATT 服务,包含一个可读+可通知的数据特征,通过 CCC 管理通知开关

 

// ========== CCC 回调(通知使能控制)==========

static void th_ccc_changed(

    const struct bt_gatt_attr *attr,

    uint16_t value)

{

    notify_enabled = (value == BT_GATT_CCC_NOTIFY);

    printk("[BLE] CCC value=0x%04x, Notify %s\n",

           value,

           notify_enabled ? "enabled" : "disabled");

}

// 当手机/前端订阅通知时,更新 notify_enabled 标志;取消订阅时自动停止推送

 

// ========== JSON 载荷生成 ==========

static void update_payload(

    const struct th_sample *sample)

{

    char temp_buf[16], hum_buf[16];

    format_x10(temp_buf, sizeof(temp_buf),

               sample->temp_c_x10);

    format_x10(hum_buf, sizeof(hum_buf),

               sample->hum_rh_x10);

    snprintk(latest_payload, sizeof(latest_payload),

        "{\"seq\":%u,\"temp_c\":%s,\"rh\":%s,"

        "\"temp_mv\":%d,\"hum_mv\":%d}",

        sample->seq, temp_buf, hum_buf,

        sample->temp_mv, sample->hum_mv);

}

// JSON 格式载荷,包含序号、温度(°C)、湿度(%RH)、ADC 原始毫伏值

 

// ========== BLE Notify 发布 ==========

static void publish_sample(

    const struct th_sample *sample)

{

    latest_sample = *sample;

    update_payload(sample);

    

    if (current_conn && notify_enabled) {

        int err = bt_gatt_notify(current_conn,

            &th_svc.attrs[2],

            latest_payload,

            strlen(latest_payload));

        if (err < 0) {

            printk("[BLE] notify failed: %d\n", err);

        }

    }

}

// 仅在 BLE 已连接且 CCC 已使能时发送 Notify,避免无效推送浪费无线资源

 

4)RGB LED 状态指示模块

LED 状态指示采用三色 PWM LED,通过不同的颜色和闪烁模式直观反馈设备工作状态,支持三种状态模式:

// ========== LED 状态控制 ==========

static void all_leds_off(void)

{

    pwm_set_dt(&led_r, led_r.period, 0);

    pwm_set_dt(&led_g, led_g.period, 0);

    pwm_set_dt(&led_b, led_b.period, 0);

}

 

static void set_rgb(uint32_t r, uint32_t g, uint32_t b)

{

    pwm_set_dt(&led_r, led_r.period, r);

    pwm_set_dt(&led_g, led_g.period, g);

    pwm_set_dt(&led_b, led_b.period, b);

}

// PWM 占空比 = 0 时 LED 熄灭,占空比 = period 时最亮

 

// ========== 广播蓝色闪烁 ==========

static void adv_blink_handler(struct k_work *work)

{

    if (current_conn != NULL) return;

    if (adv_led_on) {

        set_rgb(0, 0, led_b.period); // 蓝色亮

    } else {

        all_leds_off();             // 灭

    }

    adv_led_on = !adv_led_on;

    k_work_schedule(&adv_blink_work,

                    K_MSEC(500));

}

LED 状态对应关系:

  蓝色 500ms 闪烁 — 蓝牙广播中,等待连接

  绿色常亮 — BLE 已连接,数据传输中

  全灭 — 初始化阶段或断开连接后的过渡态

 

5)Shell 命令模块

UART Shell 提供本地交互能力,支持两条命令用于调试和状态查询,方便开发者在不使用手机/前端的情况下直接确认设备状态:

// ========== Shell 命令:th read ==========

static int cmd_th_read(const struct shell *sh,

    size_t argc, char **argv)

{

    struct th_sample sample = latest_sample;

    int err = take_sample(&sample);

    if (err < 0) {

        shell_error(sh, "ADC read failed: %d", err);

        return err;

    }

    publish_sample(&sample);

    shell_print(sh, "%s", latest_payload);

    return 0;

}

// 立即触发一次 ADC 采样 → 更新 JSON 载荷 → 通过 BLE 和串口同时输出

 

// ========== Shell 命令:th status ==========

static int cmd_th_status(const struct shell *sh,

    size_t argc, char **argv)

{

    shell_print(sh, "BLE device : %s",

                CONFIG_BT_DEVICE_NAME);

    shell_print(sh, "BLE state  : %s",

                current_conn ? "connected"

                             : "advertising");

    shell_print(sh, "Notify     : %s",

                notify_enabled ? "enabled"

                               : "disabled");

    shell_print(sh, "Sensor     : DFRobot DFR0588");

    shell_print(sh, "ADC latest : temp %d mv"

                " hum %d mv",

                latest_sample.temp_mv,

                latest_sample.hum_mv);

    shell_print(sh, "Latest     : %s",

                latest_payload);

    return 0;

}

 

SHELL_STATIC_SUBCMD_SET_CREATE(sub_th,

    SHELL_CMD(read, NULL,

        "Take one ADC sample", cmd_th_read),

    SHELL_CMD(status, NULL,

        "Show TH status", cmd_th_status),

    SHELL_SUBCMD_SET_END

);

SHELL_CMD_REGISTER(th, &sub_th,

    "Temperature/humidity commands", NULL);

// Shell 注册了 th 命令组,包含 read 和 status 子命令,提示符为 mcxw71-th:~$

 

6)采样线程与主函数

主函数是程序的入口,负责整合所有模块。后台采样线程每 2 秒自动采集一次温湿度数据并通过 BLE 推送,所有外设由中断驱动,主循环仅由 Zephyr RTOS 调度管理:

// ========== 后台采样线程 ==========

static void sample_thread(

    void *p1, void *p2, void *p3)

{

    while (1) {

        struct th_sample sample = latest_sample;

        if (take_sample(&sample) == 0) {

            publish_sample(&sample);

        }

        k_sleep(SAMPLE_PERIOD); // 2 秒

    }

}

// 独立线程每 2 秒执行一次采样→发布流程,采样失败跳过本轮、下周期继续尝试

 

// ========== 启动延迟 ==========

static void startup_delay(void)

{

    printk("[BOOT] Safety delay %d seconds\n",

           STARTUP_DELAY_SEC);

    for (int i = STARTUP_DELAY_SEC; i > 0; i--) {

        printk("[BOOT] Init in %d s...\n", i);

        k_sleep(K_SECONDS(1));

    }

}

// 上电 10 秒安全延迟,给 DAPLink 调试器充足的连接窗口,避免错过早期启动日志

 

// ========== main 函数 ==========

int main(void)

{

    printk("FRDM-MCXW71 DFR0588 "

           "ADC + UART + BLE TH Demo\n");

    printk("Build: %s %s\n",

           __DATE__, __TIME__);

 

    startup_delay(); // 10 秒启动延迟

 

    // 1. 初始化 PWM LED

    all_leds_off();

    k_work_init_delayable(&adv_blink_work,

                         adv_blink_handler);

 

    // 2. 初始化 ADC 通道(温/湿)

    init_adc_channel(&adc_temp, "temperature");

    init_adc_channel(&adc_hum, "humidity");

 

    // 3. 初始化 BLE

    bt_enable(bt_ready);

 

    // 4. 启动采样线程

    start_sample_thread();

 

    return 0;

}

// 启动流程:打印 Banner → 安全延迟 → PWM LED → ADC 双通道 → BLE → 采样线程

 


三、Devicetree ADC 引脚映射

ADC 通道引脚映射在 app.overlay 文件中定义,通过 Devicetree 覆盖层将 DFR0588 的 T 和 RH 引脚映射到 LPADC 的物理输入通道。关键配置要点:

• A6/B6 特殊路由:ADC0_A6 和 ADC0_B6 都映射到 LPADC 的 channel 2(不同侧 A/B),而非 channel 6。这是通过实验验证的 MCXW71 特有路由。

• 电压参考选择:voltage-ref = <0> 在控制器级选择外部 VREFH(VDDA 3.3V),因为 DFR0588 最高输出 2.7V,SoC 内部 VREF 调节器仅支持最大 2.1V。

• 12 位分辨率:采样精度 12 位,增益 1,默认采集时间,无过采样。

&adc0 {

    #address-cells = <1>;

    #size-cells = <0>;

    voltage-ref = <0>;

 

    channel@6 {  // Zephyr 逻辑通道 6 → 温度

        reg = <6>;

        zephyr,input-positive = <MCUX_LPADC_CH2A>;

        zephyr,reference = "ADC_REF_EXTERNAL0";

        zephyr,vref-mv = <3300>;

        zephyr,resolution = <12>;

    };

 

    channel@7 {  // Zephyr 逻辑通道 7 → 湿度

        reg = <7>;

        zephyr,input-positive = <MCUX_LPADC_CH2B>;

        zephyr,reference = "ADC_REF_EXTERNAL0";

        zephyr,vref-mv = <3300>;

        zephyr,resolution = <12>;

    };

};

 

四、Kconfig 项目配置要点

prj.conf 中启用了以下关键配置:

• 基础外设:SERIAL、CONSOLE、UART_CONSOLE、GPIO、PWM、ADC

• Shell:完整 Shell 支持,提示符 mcxw71-th:~$,含历史记录和 Tab 补全

• BLE:BT_PERIPHERAL(外设角色)、BT_GATT_DYNAMIC_DB(动态 GATT 数据库)、设备名 MCXW71-TH、外观码 833(温湿度传感器)

• MTU 优化:ATT MTU 128 字节、ACL TX/RX 缓冲 132 字节,以容纳 JSON 载荷在一次通知中完整发送

• 栈大小:主线程栈 2048 字节、系统工作队列 2048 字节,满足 Zephyr BLE 协议栈运行需求

 

五、数据流与结果展示

完整的温湿度数据采集与无线传输链路如下:

DFR0588 模拟输出 (T/RH)

  │ 0.3 ~ 2.7 V 模拟电压

  

PTD2 (ADC0_A6) / PTD3 (ADC0_B6)

  │ NXP LPADC 12 位逐次逼近采样

  

adc_raw_to_millivolts_dt()

  │ 转换为毫伏值

  

DFR0588 定点数公式

  │ temp(°C) = -66.875 + 72.917 × V_T

  │ RH(%)    = -12.5   + 41.667 × V_RH

  

JSON 字符串序列化

  │ {"seq":1,"temp_c":25.3,"rh":51.2,...}

  

  ├──→ BLE GATT Notify(每 2 秒自动推送)

  │     └──→ Web Bluetooth 前端实时显示

  

  └──→ UART Shell 输出(th read / th status)

 

 

截屏2026-06-22 19.22.42.png




共1条 1/1 1 跳转至

回复

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