蓝牙低功耗温湿度计
基于 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 → 采样线程
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)

我要赚赏金
