基于nRF52832的低功耗洗手液监测器:BLE广播与传感器选型实战

基于nRF52832的低功耗洗手液监测器:BLE广播与传感器选型实战 公共区域的洗手液分配器有个很尴尬的问题你永远不知道它什么时候就空了。保洁阿姨每天来回检查好几趟但高峰时段瓶子依然经常见底洗手间门口的摩丝瓶上午十点还是满的下午两点就只剩一层泡沫。这个项目最初就是为解决这个痛点而起做一个低功耗的洗手液监测器基于Nordic Semi的BLE SoC我用的是nRF52832模块用蓝牙低功耗把液位状态和按压次数广播出去管理人员通过手机或BLE网关就能看到哪些点位需要补充。它不改变原有分配器结构电池供电装上去就不用管整体物料成本控制在几十元以内。这个项目适合谁如果你在做智慧后勤、智慧楼宇的单品或者对Nordic蓝牙低功耗SoC开发感兴趣又或者你想把传感器数据用BLE广播而不是组网传输那这篇分享里从选型、硬件设计、固件架构到量产坑点的内容应该都能帮你少走几步弯路。以下内容按照我实际做这个项目的时间线来写不是教程文档而是一份能落地执行的参考笔记。1. 项目从哪来洗手液监测器到底在解决什么痛点先说需求背景。公共卫生间的洗手液消耗本质上是个“看不见的故障”没有电子化手段之前后勤只能靠固定频次巡检但真实的消耗速度受时段、人流、节假日影响非常大。去年我做某个智慧园区项目时甲方提了一个很具体的诉求能不能给普通挂壁式洗手液分配器加一个“空瓶提醒”不要改动原设备也不要每天充电。当时市面上的智能皂液机基本都是整机方案存量设备没法后装换新成本又高于是才有了这个外挂式监测器。1.1 最初的需求拆解刚开始需求非常发散我把它拆成四件事液位感知能够判断瓶内洗手液是否低于某个阈值最好能区分“正常”“偏低”“已空”三档。使用频率统计每台分配器每天被按压的次数用来推断消耗速度和补货计划。无线回传监测数据不需要现场查看通过手机或网关隔墙也能收到。续航约束外壳完全封闭最好一颗纽扣电池撑半年以上不能频繁换电池。这些需求听起来简单但放到真实环境里就有不少冲突。比如液位传感器如果精度要求高成本和功耗就会上去按压次数如果用机械开关统计触点寿命和安装位置都会变成问题。所以第一版我定的原则是与其追求高精度不如追求高可靠输出的状态量足够让后勤人员做决策就好。1.2 痛点背后的技术挑战真正的技术挑战其实有三个。第一是功耗预算。因为设备装在分配器侧面没有外部电源只能靠电池供电。以CR2032纽扣电池为例容量大约220mAh如果按平均电流20uA计算才能跑一年多。这意味着系统大部分时间必须处于睡眠状态BLE不能始终维持连接传感器也要用待机功耗极低的型号。第二是安装环境的一致性。市场上的洗手液瓶形态各异有透明PET瓶、有白色HDPE瓶、还有壁挂式分配器自带的不可见内胆这导致液位检测方案没法做到“一招鲜”。第三是通信范围。分配器通常放在洗手间、走廊尽头离管理人员办公室隔了好几堵墙普通BLE手机直连不一定时刻在线所以方案不能只依赖手机App主动扫描还需要能配合BLE网关做长时间监听。我把这些限制摆到桌面上后硬件选型和固件架构的方向就清晰了必须选择一颗低功耗、自带BLE协议栈的单芯片SoC而不是用普通MCU外接蓝牙芯片的方案。2. 为什么选中Nordic的BLE SoC而不是Wi-Fi模组或MCU加蓝牙芯片方案其实最早考虑过ESP32因为它的Wi-Fi直连服务器非常方便代码生态也丰富。但查了一圈数据手册之后就放弃了这个方向ESP32在低功耗模式下要么保留Wi-Fi连接平均电流动辄几十毫安要么深度睡眠但要靠定时唤醒数据实时性很差。而洗手液监测器要的是“平时零活动、有事立刻上报”的状态BLE的广播模式天然适合这种场景Wi-Fi方案在这个场景里就像拿电暖器烤面包功耗和复杂度都不划算。2.1 选型时的几个硬性指标我把候选SoC限制在支持BLE 5.0、内核带浮点单元、能在1.8V到3.6V电压范围内稳定工作的产品上当时主要对比了nRF52832、nRF52840和国产的某款BLE SoC。我最终选了Nordic nRF52832原因是它的资源足够价格适中社区资料多而且SoftDevice协议栈的稳定性经过了大量批量设备验证。指标nRF52832nRF52840某国产BLE SoC内核Cortex-M4F 64MHzCortex-M4F 64MHzCortex-M4F 96MHzFlash/RAM512KB/64KB1MB/256KB512KB/64KB最大发射功率4dBm8dBm8dBm接收灵敏度-96dBm-96dBm-97dBm待机电流约1.5uA约1.5uA约2uABLE协议栈S132/S112S140厂商私有SDK批量单价参考约14元约28元约12元对于这个项目nRF52840的性能富余太多价格高了一倍没必要。国产那颗的SDK虽然价格低但BLE广播和多连接网关的兼容性我踩过几次坑在实际项目中不敢贸然用。选择nRF52832还有一个原因它可以搭配现成的通过FCC/CE认证的贴片模块比如Raytac MDBT42Q模块自带天线和匹配电路省掉了射频调试环节这对小团队来说非常关键。2.2 SoftDevice和SDK生态Nordic的BLE协议栈叫SoftDevice比如S132支持中央和外围设备S112只支持外围设备编译固件时和用户代码一起链接运行在ARM Cortex-M4的privileged模式下。第一次接触时我有点不适应因为它是预编译的二进制看不到协议栈源码但好处是Nordic把链路层、主机层、GAP/GATT都封装好了你只需要处理事件回调不需要自己实现蓝牙协议栈出问题的概率小很多。我使用的是nRF5 SDK 17.1.0版本搭配S132 SoftDevice开发环境是Keil MDK。SDK里的几个例程质量非常高尤其是ble_app_uart、ble_app_blinky这些例程已经把SoftDevice初始化、GAP参数配置、GATT服务注册都写好了直接改服务UUID和特征值就能快速跑通。对于从零开始的项目我建议不要去看那些过于复杂的多连接示例先把一个最简的外围设备工程编译烧录到芯片上手机App能扫描到、能连接上再逐步加传感器逻辑。3. 硬件设计与传感器选型从液位监测到使用次数统计硬件部分我设计了三个功能模块无线SoC最小系统、液位检测传感器、按压检测传感器。供电直接使用CR2032电池没有加LDO因为nRF52832的工作电压范围是1.8V到3.6V纽扣电池满电3.0V放电到2.0V左右时系统已经需要提醒更换电池了所以不需要升压或降压电路直接并联一个100nF和一个10uF滤波电容就很稳。3.1 传感器选型对比和取舍液位检测我研究了三种方式。第一种是红外对管贴在瓶壁外利用空气和液体对红外光的折射率不同来判断有没有液体优点是成本低、非接触但问题是对透明瓶和半透明瓶的阈值差别很大贴装位置稍有偏移就误判。第二种是电容式液位传感器把两个铜箔电极贴在瓶身外侧利用液体覆盖时电容变化来判断液位这种做法受瓶壁材质影响较小但需要比较复杂的校准。第三种是称重传感器直接在分配器底座加一个悬臂梁称重模块精度最高但结构改动大不适合后装。按压次数统计反而是更容易解决的。我选了ADI的ADXL362加速度计它内置运动检测功能能把静态功耗做到3uA左右检测到加速度变化超过阈值后通过INT引脚唤醒nRF52832。分配器被按压时泵头会有一瞬间的震动这个震动特征可以被加速度计捕捉到。为了避免误触发我在固件里做了“检测到运动后等待500ms再判断是否出现第二次震动”两步确认才会计数。所有方案权衡之后我最终的第一版硬件选了电容式液位传感器加ADXL362加速度计。虽然电容方案需要校准但它可以应付透明和不透明的瓶子只要外壳结构固定批量生产时做一次出厂标定就好。3.2 硬件原理图关键引脚nRF52832最小系统的原理图参考官方参考设计需要注意几件事芯片供电脚都要加滤波电容DEC引脚必须接1.8V退耦电容晶振使用32.768kHz和16MHz天线区域周围要保持净空。使用模块的时候这些都已经处理好了所以我不再操心射频匹配。以下是第一版原理图里比较关键的引脚连接引脚功能说明P0.02液位传感器数字输出高电平有液低电平液位不足带外部弱上拉P0.04I2C SCLADXL362时钟P0.06I2C SDAADXL362数据P0.08按键产测模式和手动唤醒P0.11蓝色LED状态指示正式版可去掉省电P0.13ADXL362 INT1运动检测中断P0.14蜂鸣器控制可选空瓶时发出短促提示电源设计上CR2032电池座紧贴PCB背面电池负极走线先过0欧串联电阻再接入系统地这样调试时可以用电流表串进去量功耗。为了抑制瞬态电流电池正极接10uF钽电容和0.1uF陶瓷电容各一个。BLE天线位置在PCB右上角外壳对应区域要开窗口不能有金属物遮挡。4. 固件架构nRF5 SDK下的BLE服务、通知与低功耗调度固件结构并不复杂初始化时钟和SoftDevice初始化传感器设置广播和GATT然后进入睡眠。所有事件都通过中断唤醒CPU处理完再睡回去。这样设计的核心思路是“事件驱动”而不是“轮询驱动”。轮询会让CPU周期性地醒来检查传感器看起来功耗不高但一旦定时器频率设置不当系统平均电流会成倍上升。4.1 自定义GATT Service和特征值设计GATT服务我定义了一个自定义服务UUID设为0xFFE0包含四个特征液位状态特征0xFFE1只读通知值0表示正常1表示偏低2表示已空。按压计数特征0xFFE2只读通知32位无符号整数记录累计按压次数。电量特征0xFFE3只读0-100百分比。状态确认特征0xFFE4读写手机端写1可以清除按压计数。这里有一个细节如果使用Android或iOS的BLE库读取通知必须正确处理Characteristic User Description有些手机系统会优先显示描述符文字如果没有配置App端看到的是空名称容易让用户觉得设备异常。我在特征定义里补写了0x2901描述符。4.2 低功耗的核心事件驱动和唤醒调度低功耗并不仅仅是选一个低功耗芯片更关键的是系统里每个模块的睡眠策略。nRF52832的System ON模式待机电流在数据手册上约1.5uA但如果GPIO电平不稳、I2C总线悬空、或者某颗传感器还处于测量模式整体电流会明显上升。我在固件里做了三件事第一把所有用不到的GPIO配置为低功耗状态或者设为输出低电平避免浮空导致的漏电。第二I2C总线在读数结束后把SCL/SDA都拉成低电平因为ADXL362在待机模式下总线上高电平靠上拉电阻维持如果上拉电阻太小会白白浪费电流。第三使用RTC0做定时唤醒每30分钟采集一次电池电压并更新广播包而不是让SoftDevice的定时器频繁唤醒CPU。ADXL362的中断处理是低功耗设计中最关键的一环。它检测到运动后INT1引脚拉高nRF52832收到EXTI中断从System ON模式唤醒。这时我才读取加速度计FIFO数据判断是否是一次真正的按压事件。如果判断为误触发继续回去睡觉。用这种方式设备平时只有加速度计处于运动检测模式加上RTC唤醒系统平均电流可以控制在20uA以内。4.3 关键代码和配置固件中比较关键的代码是GATT服务初始化和广播数据更新。下面这段是我从工程里简化出来的服务初始化函数static void ble_handsan_init(void) { ret_code_t err_code; ble_handsan_t *p_handsan m_handsan; p_handsan-conn_handle BLE_CONN_HANDLE_INVALID; p_handsan-liquid_level HAND_SAN_LEVEL_NORMAL; p_handsan-press_count 0; ble_uuid_t ble_uuid; ble_uuid128_t base_uuid {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; err_code sd_ble_uuid_vs_add(base_uuid, p_handsan-uuid_type); APP_ERROR_CHECK(err_code); ble_uuid.type p_handsan-uuid_type; ble_uuid.uuid BLE_HANDSAN_SERVICE_UUID; // 0xFFE0 err_code sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, ble_uuid, p_handsan-service_handle); APP_ERROR_CHECK(err_code); // ... 省略四个特征值的add流程 }广播数据更新的思路是每次液位或计数变化时调用sd_ble_gap_adv_set_configure()重新设置广播包然后更新广播内容。要注意广播包总长度不能超过31字节如果超过就必须启用二级广播但很多老网关不支持所以我的广播包只放了device name、服务UUID和一个字节的状态位计数通过GATT读取不在广播包里带全量数据。连接参数方面建议设置连接间隔为30ms至50ms从机延迟设为3这样可以在设备连接时保证数据交互的实时性同时降低功耗。如果不需要连续连接可以把从机延迟设得更大会更省电但手机端体验会变差具体需要根据自己的场景调。5. 实测中的常见坑连接稳定性、功耗校准和外壳干扰硬件和固件做完之后才是真正的考验。这里讲几个我在实测中遇到的比较有代表性的问题希望能帮你避开。5.1 一次真实的排查过程为什么设备广播数据没更新有一次我调整了按压计数功能烧录后拿着手机在设备旁边走来走去能扫描到设备但App里显示的按压计数始终是0进了GATT服务读到的也是0。当时我第一反应是传感器没有触发中断于是加了日志看ADXL362中断标志结果日志显示中断次数在涨说明传感器是正常的问题出在固件更新广播数据的逻辑上。然后我打开协议分析仪抓广播包发现广播包里的状态字节确实没有变化。再检查代码发现我更新广播包时调用的是sd_ble_gap_adv_set_configure()但这个函数需要传入BLE_GAP_ADV_SET_HANDLE_NOT_SET参数而且必须在广播停止时才能调用。我当时没有停止广播就直接改配置SoftDevice返回了NRF_ERROR_INVALID_STATE而我的错误处理代码在release版本里被APP_ERROR_CHECK跳过了等于这个问题被整个吞掉没有任何现象。定位到根因后我在更新广播数据前先调用sd_ble_gap_adv_stop()再更新配置最后重新sd_ble_gap_adv_start()。这段逻辑虽然看起来繁琐但解决了95%的“广播数据不更新”问题。如果你也遇到类似现象建议先确认返回值不要把错误处理全部关掉。5.2 功耗校准流程功耗必须用工具实测不能只依赖数据手册估算。我用Nordic Power Profiler Kit直接串在电池供电回路上看到睡眠电流是3.8uA比预想高了不少。逐个引脚排查后发现其中一个GPIO没有配置成输出低而是浮空导致漏电约2uA。改掉之后睡眠电流降到1.6uA符合预期。实测过程中我记录了不同工作状态下的电流状态电流持续时间System ON睡眠1.6uA平时RTC唤醒处理3.8uA约2ms广播事件8.2uA平均每次广播约1.5ms连接事件420uA每个连接间隔ADXL362运动唤醒480uA约20ms电容传感器测量90uA约10ms根据这个数据假设每天按压次数20次、每30分钟RTC唤醒一次、广播间隔设为200ms粗略计算平均电流大约在18uA左右。220mAh电池理论可用约12000小时折算一年以上。这里没有把电池自放电算进去CR2032自放电每年约3%所以实际寿命会稍微短一些。这个续航对后装设备来说完全可以接受。5.3 机械外壳对传感器的影响还有一个容易被忽略的点外壳机械结构会直接影响传感器读数。我刚开始用3D打印外壳做测试时电容液位传感器贴在瓶子外侧外壳从外面再包一层结果传感器读数一直乱跳。后来发现是外壳的卡扣筋位刚好压在传感器感应区改变了电容耦合。最后把感应区对应的外壳局部掏空留出1mm间隙问题才解决。加速度计的安装方向也需要注意。ADXL362对重力方向很敏感如果传感器安装角度和PCBA基准不同运动检测阈值要重新标定。我在产品外壳上加了一个定位柱确保PCBA只能以一个方向卡入这样产线就不需要逐台调整算法参数。6. 从原型到批量认证、产测与后续迭代原型跑通只是第一关如果要做成小批量产品还有一些事情需要提前规划。6.1 使用通过认证的模块是捷径直接使用芯片做射频设计产品上市前要做FCC/CE/SRRC等认证周期长、费用高。而我选用的Raytac MDBT42Q模块本身已经通过了这些认证产品在使用模块时只要天线设计和模块原厂保持一致就可以直接继承认证ID省掉大量认证成本。如果团队没有射频背景建议优先选这种带认证的贴片模块而不是自己画射频电路。需要特别注意即使模块认证了如果产品的天线方向、外壳金属结构改变了射频性能也可能差异很大所以量产前还是要做传导功率测试和辐射杂散测试。我用的是开源工具搭配一个基础频谱仪做校准虽然谈不上实验室级但至少能确保功率在正常范围内。6.2 产测项目如何设计小批量生产不能指望每一台都用调试器去烧录固件。我在产测模式里设计了一个简洁流程设备上电后按住按键3秒进入产测模式此时设备会打开一个独立的产测GATT服务产测工装通过BLE连上设备后依次执行RF测试、传感器校准和计数器清零。传感器校准其实很有意思。电容液位传感器在不同瓶子上的空杯和满杯读数不一样所以产测固件里需要保存两个标定值。我在UICR区预留了8字节存储空间一个存满杯ADC值一个存空杯ADC值设备运行时根据这两个值计算归一化的液位百分比。产测工装会通过串口或BLE下发一组标定指令工人手里拿个装满水的标准瓶靠近传感器点一下“满杯校准”再拿掉水瓶点一下“空杯校准”两步搞定。这样能大幅减少人工干预带来的误差。6.3 后续可以怎么扩展做完这个项目后我最大的体会是BLE SoC的潜力远超一个简单的空瓶提醒。如果你需要把多个监测器数据统一汇总可以引入BLE网关用nRF52840做网关节点扫描周围的广播设备统一上报到服务器。这样部门楼层的所有洗手液监测器就可以在一个仪表盘里展示状态超过阈值自动生成工单。另外Nordic的SDK现在已经支持Zephyr RTOS和Matter协议如果后续希望把监测器纳入更大的智能楼宇平台可以顺势迁移到nRF52840用它来做Thread边界路由器节点联动其他传感器也不会太费力。不过从硬件成本角度洗手液监测器本身的数据量很小BLE广播方式其实已经足够不需要为了“先进”而上更复杂的协议栈。最后再分享一个小经验量产固件里一定要预留DFU OTA升级通道。我第一次做的时候觉得产品简单不需要OTA结果第一批货出去后偶然发现某个传感器的阈值在低温环境下偏了只能派人去现场拆壳刷机非常痛苦。如果提前设计好Bootloader和DFU空中升级功能这类问题后台就能解决。每次发布固件前先在开发板上完整测一遍升级流程确认升级失败能回滚再正式推送。这个习惯能让你避免很多不必要的售后。