一、项目背景
翻开任何一本物联网教材,温湿度传感器几乎都是入门章节的第一个例子。这并不是因为它简单,而是因为它足够基础——无论是粮仓、机房、温室大棚,还是病房、冷链货车、博物馆的文物储藏间,温湿度的精确感知都是一切智能决策的前提。
然而,"能测"和"好用"之间存在一道鸿沟。传统有线部署方案在固定场所尚可接受,一旦面对大空间多点布置、移动设备监测或临时性部署需求,动辄上百米的布线工程便会让方案变得既昂贵又脆弱。蓝牙低功耗(BLE)技术的成熟给出了另一条路:设备直接用纽扣电池供电,通过手机或小程序随时读取数据,整个部署过程不需要任何网络基础设施。
但"低功耗"三个字说起来轻巧,真正落地时却藏着很多细节。以本项目使用的SHT30模拟输出传感器为例,即便处于空闲状态,只要持续供电就会维持约0.6mA的静态电流。在一块200mAh的纽扣电池上,这一数字意味着不到两周的续航。如何把传感器的通电时间精确压缩到"刚好够采一次样"的16毫秒窗口,让其余时间彻底断电,正是本项目着重解决的工程问题。
本项目选用NXP MCXW71作为核心芯片。这颗芯片内置独立的无线子系统,将BLE协议栈的运行与应用主核完全隔离,既保证了蓝牙通信的实时性,又让应用层可以毫无顾虑地进入深度睡眠。配合Zephyr RTOS成熟的电源管理框架,整个系统的低功耗潜力得以充分释放。
二、项目目标与要求
本项目的最终交付物是一套可以实际使用的无线温湿度节点:上电即广播,手机小程序连接即可看到实时数据,放在那里不管它,电池也能撑得住。为达到这个目标,整个开发过程围绕以下几个核心问题展开:
传感器信号怎么读?
SHT30采用模拟比率式输出,温度和湿度分别对应两路0~VDD范围内的电压信号,需要通过ADC双通道同步采集,再套用数据手册给出的比率式公式换算。本项目使用FRDM-MCXW71板载的LPADC(12位,VREFH = 3.3V),通道映射关系需结合原理图与芯片参考手册三方核对,不能仅凭引脚名称推断。
传感器电源怎么控?
直接给传感器持续供电是最省事的方案,但也是最费电的。本项目引入一个高电平导通的MOS管开关模块,由GPIOC.4控制其通断,将传感器的供电窗口精确限制在"上电稳定(16ms)+ ADC采样(~1ms)"这17ms以内,其余时间传感器引脚与电源完全断开。
MCU睡眠怎么配?
Zephyr的k_sleep()在启用CONFIG_PM=y与CONFIG_TICKLESS_KERNEL=y之后,会由PM子系统自动选择当前可用的最深睡眠态,并依赖片上RTC定时唤醒,对应用层完全透明。开发者无需手动操作任何寄存器,只需确保配置正确、外设约束不阻止深度睡眠即可。
数据怎么传到手机?
本项目定义了两个独立的128-bit UUID自定义GATT服务(温度服务与湿度服务),各含一个支持Notify的float类型特征。微信小程序通过订阅Notify通知被动接收数据,开发板每完成一次采样即推送一次,无需小程序轮询,进一步减少BLE通信对系统功耗的影响。
围绕上述四个核心问题,本项目的具体任务分解如下:
搭建Zephyr开发环境,验证工程可正常编译并烧录至FRDM-MCXW71
完成SHT30传感器与开发板的硬件连接,编写ADC驱动并验证串口输出数据正确
编写MOS管供电控制逻辑,实现"采样窗口内供电、采样完成立即断电"的精确时序
配置BLE外设角色与自定义GATT服务,验证微信小程序可正常接收温湿度Notify通知
启用Zephyr电源管理与Tickless内核,测量低功耗模式下的实际功耗并与连续供电方案对比
三、项目硬件
模块主要硬件选型如下:
NXP FRDM-MCXW71开发板
SHT30模拟输出温湿度传感器模块
MOS管开关模块(高电平导通型)
Nordic PPK2(低功耗分析)
在主要硬件选择完毕后,查阅开发板原理图与官方设备树头文件,确定引脚连接方案如下:SHT30模块VCC通过MOS管开关模块受控接入3.3V,GND直连开发板GND;温度输出引脚T接至ADC0通道(CH1B),湿度输出引脚H接至ADC0通道(CH2A);MOS管开关模块控制引脚接至ARDUINO_HEADER_R3_D2(对应gpioc 4),高电平时MOS管导通、传感器上电,低电平时传感器完全断电。具体硬件连线图如下图所示。

四、项目软件
完整项目软件包括两个工程,一是低功耗蓝牙温湿度计Zephyr工程(ble_thermo),二是上位机微信小程序。下面将对两个工程进行详细描述。
A:ble_thermo工程
NXP FRDM-MCXW71开发板支持VS Code、IAR、Keil等多工具开发。但是本次我们要围绕Zephyr操作系统展开,所以选择一个Zephyr开发的通用工具VS Code + Zephyr IDE插件。
在编写模块软件代码之前,需要了解利用Zephyr搭建工程所需的文件架构,如图所示:

完成项目工程所需编写的代码文件共有七个:
1. main.c —— 应用程序主逻辑main.c是核心逻辑实现文件,负责:
(1)初始化硬件和协议栈:
调用sht30_init()初始化ADC通道与电源控制GPIO(传感器初始断电)
调用bt_enable()初始化蓝牙协议栈,注册连接/断开回调
调用ble_th_init()静态注册GATT温度服务与湿度服务
(2)实现业务功能:
每30秒调用一次sht30_read(),内部自动完成"上电→等待16ms→ADC采样→立即断电"的完整时序
将int16_t类型的temp_x100/hum_x100转换为float后,通过printk输出至串口
调用ble_th_notify_temp()和ble_th_notify_hum()向已订阅的BLE客户端推送数据
(3)低功耗与电源管理:
主循环中调用k_sleep(K_SECONDS(30)),配合CONFIG_PM=y与CONFIG_TICKLESS_KERNEL=y,使系统在空闲时自动进入最深可用睡眠态,由片上RTC定时唤醒,全程对应用层透明
(4)连接状态管理:
使用原子位STATE_CONNECTED/STATE_DISCONNECTED在回调与主循环间安全传递连接事件,断连后自动重新开始广播
由于main.c代码较长,本文不再详述,完整代码见文末附件。
2. prj.conf —— 项目Kconfig配置文件prj.conf用于配置Zephyr内核及各子系统的编译选项。代码如下:
# 蓝牙核心 CONFIG_BT=y CONFIG_BT_SMP=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_DIS=y CONFIG_BT_DIS_PNP=n CONFIG_BT_BAS=y CONFIG_BT_DEVICE_NAME="Zephyr TH Sensor" CONFIG_BT_DEVICE_APPEARANCE=512 # ADC / GPIO CONFIG_ADC=y CONFIG_GPIO=y # 电源管理(低功耗核心配置) CONFIG_PM=y CONFIG_PM_DEVICE=y CONFIG_PM_DEVICE_RUNTIME=y # Tickless内核(消除周期性tick中断,最大化睡眠时间) CONFIG_TICKLESS_KERNEL=y CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000 # 日志与串口 CONFIG_LOG=y CONFIG_SERIAL=y CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y CONFIG_PRINTK=y CONFIG_CBPRINTF_FP_SUPPORT=y
各内容具体作用如下:
启用蓝牙功能:CONFIG_BT=y、CONFIG_BT_PERIPHERAL=y使能蓝牙协议栈并使设备作为外设角色;CONFIG_BT_DEVICE_NAME设置广播名称;CONFIG_BT_DEVICE_APPEARANCE=512设置设备外观类别为Generic Sensor。
启用必要外设驱动:CONFIG_ADC=y、CONFIG_GPIO=y使能ADC驱动与GPIO框架,支持模拟电压采集和MOS管控制。
配置电源管理:CONFIG_PM=y启用Zephyr PM子系统;CONFIG_TICKLESS_KERNEL=y关闭周期性tick中断,使k_sleep()能够触发真正的硬件深度睡眠;CONFIG_PM_DEVICE=y允许ADC等外设参与电源状态切换。
设备树(Device Tree)描述了硬件的外设、引脚连接和资源。.overlay文件在不修改官方板级设备树的前提下,添加或覆盖节点以适应自定义硬件连接。代码如下:
#include <zephyr/dt-bindings/adc/adc.h>
#include <zephyr/dt-bindings/adc/mcux-lpadc.h>
/ {
aliases {
adc0 = &adc0;
};
/*
* 传感器供电控制 GPIO
*
* ARDUINO_HEADER_R3_D2 → gpioc 4(来自官方 arduino-connector gpio-map)
* GPIO_ACTIVE_HIGH:写 1 → MOS管导通 → 传感器上电
* 写 0 → MOS管截止 → 传感器完全断电
*/
zephyr,user {
sensor-pwr-gpios = <&gpioc 4 GPIO_ACTIVE_HIGH>;
};
};
&lpi2c1 {
status = "disabled";
};
&adc0 {
#address-cells = <1>;
#size-cells = <0>;
voltage-ref = <0x0>; /* Alt1 = VREFH = 3.3V */
nxp,references = <&vref 3300>; /* 参考电压 3300mV */
channel@0 { /* 温度通道 */
reg = <0>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_EXTERNAL0";
zephyr,vref-mv = <3300>;
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
zephyr,input-positive = <MCUX_LPADC_CH1B>;
};
channel@1 { /* 湿度通道 */
reg = <1>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_EXTERNAL0";
zephyr,vref-mv = <3300>;
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
zephyr,input-positive = <MCUX_LPADC_CH2A>;
};
};上述代码关键要点:voltage-ref = <0x0>选择VREFH(3.3V)作为ADC参考电压,若不覆盖则默认使用1.8V导致换算结果严重偏高;nxp,references = <&vref 3300>明确告知NXP驱动参考电压值,确保adc_raw_to_millivolts()函数计算正确。MOS管控制GPIO(gpioc 4)直接在C代码中通过DT_NODELABEL(gpioc)获取设备,规避了文件作用域静态初始化时DEVICE_DT_GET的符号解析问题。
4. src/sht30.h / src/sht30.c —— 传感器驱动sht30.h声明两个API函数:
int sht30_init(void); /* 初始化ADC通道与电源控制GPIO(传感器初始断电),返回0成功 */ int sht30_read(int16_t *temp_x100, int16_t *hum_x100); /* 完整采集流程:上电→等待16ms→ADC采样→立即断电→换算 */ /* temp_x100:温度,单位0.01°C(如2530 = 25.30°C) */ /* hum_x100 :湿度,单位0.01%RH(如5120 = 51.20%RH) */
sht30.c的核心实现要点:
通道配置从设备树自动生成:使用ADC_CHANNEL_CFG_DT宏展开channel子节点,参考电压从zephyr,vref-mv属性读取,驱动代码与硬件通道号完全解耦
比率式换算公式(SHT30数据手册):T(°C) = -66.875 + 218.75 × (V_T / V_DD),RH(%) = -12.5 + 125.0 × (V_RH / V_DD),以V_DD为基准的比率式计算确保VDD轻微波动时结果仍准确
断电保证:gpio_pin_set(pwr_port, SENSOR_PWR_PIN, 0)位于adc_read()返回后立即执行,使用goto结构确保即使ADC失败也必然断电,不存在传感器持续供电的风险
ble_th.c静态注册两个独立的128-bit UUID自定义服务:
服务 | 服务UUID | 特征UUID | 属性 |
温度服务 | ADAF0100-C332-42A8-93BD-25E905756CB8 | ADAF0101-... | Notify + Read |
湿度服务 | ADAF0700-C332-42A8-93BD-25E905756CB8 | ADAF0701-... | Notify + Read |
每个特征均附带CCCD(Client Characteristic Configuration Descriptor)。小程序调用notifyBLECharacteristicValueChange()时写入CCCD,触发temp_ccc_changed/hum_ccc_changed回调置位通知使能标志。ble_th_notify_temp()与ble_th_notify_hum()内部通过memcpy将float的原始字节写入发送缓冲区,ARM Cortex-M小端架构保证字节序与小程序getFloat32(0, true)期望的完全一致,无需额外字节序转换。
6. CMakeLists.txt —— 构建系统配置cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ble_thermo)
target_sources(app PRIVATE
src/main.c
src/sht30.c
src/ble_th.c
)引入Zephyr构建系统:find_package(Zephyr ...)加载Zephyr的CMake模块,自动处理编译器、链接器、设备树生成等全部流程
指定源文件:三个.c文件分别负责主逻辑、传感器驱动、BLE服务,职责清晰,便于独立维护
B:上位机:微信小程序
本项目使用微信开发者工具编写微信小程序。完整工程包括如下代码文件:

index.js是页面的JavaScript逻辑文件,主要承担以下职责:
(1)蓝牙生命周期管理:
初始化蓝牙适配器(wx.openBluetoothAdapter)
直接通过设备ID连接指定设备(wx.createBLEConnection)
获取服务和特征值(wx.getBLEDeviceServices、wx.getBLEDeviceCharacteristics)
(2)数据接收与解析:
通过wx.notifyBLECharacteristicValueChange()订阅温度和湿度特征的Notify通知,数据到达时自动触发回调,无需轮询
在wx.onBLECharacteristicValueChange回调中,通过new DataView(res.value).getFloat32(0, true)将4字节小端float解析为温湿度数值
(3)UI数据绑定:
通过this.setData()更新页面的temperature、humidity、连接状态等
(4)资源释放:
页面卸载时调用wx.closeBLEConnection()关闭连接,释放蓝牙资源
index.wxml定义以下界面元素:
连接按钮:未连接时显示"连接设备"按钮,连接后隐藏
数据面板:连接成功后展示,包含温度卡片(含温度计图标、数值{{temperature}} ℃)和湿度卡片(含水滴图标、数值{{humidity}} %)
index.wxss负责控制页面外观,包括flex布局、数据卡片渐变背景色、温湿度数值大字号显示以及通过rpx单位实现的移动端适配。
4. app.json —— 全局应用配置app.json主要配置了页面路径("pages/index/index"为启动页)、导航栏标题("环境监测")以及蓝牙权限声明(scope.bluetooth),后者是调用微信BLE API的必要前提。
微信小程序工程的完整代码见文末附件。
五、传感器外设低功耗优化分析
5.1 优化方案设计
本项目低功耗策略的核心在于通过MOS管开关模块对传感器实施精确的间歇供电控制,结合Zephyr电源管理框架实现MCU深度睡眠,将传感器的通电时间压缩至最短必要窗口(约17ms),其余约29.98秒内传感器完全断电。
每30秒完整时序: |<───16ms───>|<─1ms─>|<──────────── ~29983ms ──────────────>| ┌────────────┐┌──────┐ ┌─ │ 传感器上电 ││ ADC │ MCU深度睡眠(PM自动选择最深态) │ └────────────┘└──────┘ └─ ↑ RTC唤醒↑ GPIO=HIGH 30s到期
MCU侧,CONFIG_PM=y与CONFIG_TICKLESS_KERNEL=y协同工作:主循环k_sleep(K_SECONDS(30))触发后,Zephyr PM子系统自动评估所有线程状态与外设约束,选择MCXW71支持的最深可用睡眠态,由片上LPTMR/RTC在30秒后触发唤醒中断,恢复执行上下文,全程对应用层完全透明。
5.2 功耗测量基准(30秒统一周期)
为公平对比,以30秒为一个完整统计周期,计算两种供电策略下传感器部分消耗的总电量(单位:mAs)。

5.3 方案A:连续供电模式(2Hz频率读取)
30秒内传感器执行60次读取,其余时间维持低功耗空闲态(但始终保持供电)。

读取状态:
每次读取时间:12ms = 0.012s;读取平均电流:1.05mA
30秒内总读取消耗:
空闲供电状态:
空闲总时间:
;空闲电流:0.6mA
总空闲消耗:
5.4 方案B:MOS管间歇供电低功耗模式(30秒读一次)
30秒内MOS管仅导通1次,其余时间传感器引脚完全与电源断开,漏电流降至nA级(理论取100nA = 0.0001mA)。

开关导通瞬态尖峰(PPK2实测):
尖峰持续时间:76µs;尖峰平均电流:约20mA
尖峰消耗:
上电读取工作状态:
读取时间:16ms(含上电初始化等待,比连续供电模式多4ms);平均电流:0.9mA
读取消耗:
彻底断电休眠状态:
休眠时间:≈29.984s;漏电流:0.0001mA
休眠消耗:
5.5 数据对比与结论
功耗产生项 | 方案B消耗电量 | 在方案B总功耗中的占比 |
开关瞬态尖峰(浪涌) | 0.00152 mAs | 8.03% |
16ms传感器读取工作 | 0.01440 mAs | 76.11% |
断电休眠漏电 | 0.00300 mAs | 15.86% |
合计 | 0.01892 mAs | 100% |
核心结论:
尖峰电流影响微乎其微:76µs的导通浪涌电量仅占方案B总消耗的8%,完全不影响低功耗大局
省电效率高达99.9%:方案B将传感器30秒周期内的电量消耗从18.324mAs压缩至0.01892mAs,根本原因在于MOS管彻底斩断了连续供电模式下持续29.28秒、0.6mA的空闲电流,而低功耗模式下这部分功耗直接降至nA级
上电初始化开销可接受:虽然比率式模拟传感器需要16ms上电稳定时间(比数字传感器的即读即取多约4ms),但与节省的空闲供电电量相比完全可以忽略
六、成果演示
1. 打开串口监视器,设置波特率115200。

可以看到串口每30秒打印一次温湿度数据,两次打印之间MCU进入深度睡眠,开发板功耗显著降低。
2. 打开手机微信,进入"温湿度监测"小程序,点击"连接设备"。

小程序成功连接后,温度与湿度数值实时刷新显示,每当开发板完成一次采样并发送Notify通知,界面数值即自动更新。
至此,整个低功耗蓝牙温湿度计模块演示完成。
七、小结
综合上述内容,本研究实现了以下功能:
1. 环境数据采集
硬件连接:SHT30模拟输出温湿度传感器的T、H引脚分别连接至FRDM-MCXW71的ADC0双通道(CH1B、CH2A),参考电压通过设备树overlay覆盖为VREFH = 3.3V
驱动实现:基于Zephyr ADC API,利用DT_FOREACH_CHILD_SEP宏从设备树自动生成通道配置,通过adc_raw_to_millivolts()完成原始值到电压的换算,再应用SHT30比率式公式(以V_DD为基准)输出温湿度数值
串口输出:每隔30秒通过printk输出格式化数据,例如:Temperature: 23.5°C, Humidity: 45.2%RH
2. 低功耗优化
传感器间歇供电:通过MOS管开关模块(控制引脚GPIOC.4)精确控制传感器供电窗口,仅在ADC采样前16ms上电,采样完成后立即断电,传感器静态功耗降至nA级,节电效率达99.9%
MCU深度睡眠:主循环使用k_sleep(K_SECONDS(30)),配合CONFIG_PM=y、CONFIG_TICKLESS_KERNEL=y,系统自动进入最深可用睡眠态,由片上RTC定时唤醒,无需应用层额外干预
3. 蓝牙功能
外设角色:配置为BLE外设,设备名称为"Zephyr TH Sensor",可被手机等中心设备扫描发现
自定义GATT服务:定义温度服务(ADAF0100-...)与湿度服务(ADAF0700-...),各含一个支持Notify+Read的128-bit特征,蜂鸣器控制服务已移除,结构简洁
数据格式:float类型温湿度值通过memcpy以小端序写入4字节发送缓冲区,与小程序getFloat32(0, true)完全匹配,无需额外字节序转换
4. 微信小程序功能
BLE连接管理:通过设备ID直连,获取服务与特征后自动订阅Notify通知,无需定时轮询
实时数据展示:接收到4字节float数据后即时解析并刷新界面温湿度数值
用户界面:包含连接状态指示、数据卡片展示,界面简洁且适配移动端
我要赚赏金
