尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Nordic BLE SoC的零售IoT系统设计与实践

基于Nordic BLE SoC的零售IoT系统设计与实践 最近在跟一个连锁零售客户的IoT项目差不多就是标题里写的Connected Retail IoT System。整套系统的感知层没有用传统的Zigbee也没有上Wi-Fi模组而是清一色用了Nordic的BLE SoC。项目做完之后我盘了一下这个选择在零售场景里是真的合适成本压得住功耗低到可以电池供电两年而且调试BLE协议栈的坑虽然不少但都还有清晰的破解路径。这个项目要解决的问题很直接门店货架上的商品缺没缺货、电子价签有没有同步、冷链柜温度是否超限、资产有没有被挪动。传统做法是店员周期性巡场或者用有线传感器但门店改造难度大、成本高。我们最终做了一套以低功耗蓝牙为核心的低成本无线感知网络每个“会说话”的标签节点都基于Nordic的BLE SoC广播自身状态由边缘网关统一采集后上云再落到可视化看板和告警系统里。如果你们也在做类似零售IoT、仓库资产管理或者冷链监测这套技术路线和踩坑记录应该用得上。1. 项目背景与整体设计思路1.1 为什么选择Nordic BLE SoC先解释一个基础问题为什么是BLE SoC而不是Wi-Fi、经典蓝牙或者Zigbee。零售场景的末端节点通常是传感器标签、价签、资产标签它们的共性是数据量小、实时性要求适中、节点数量多、电池不希望经常换。Wi-Fi模组功耗大一个节点如果持续联网普通锂电池撑不过半年经典蓝牙功耗也不低而且不适合大量节点并发低功耗广播Zigbee组网能力强但网关成本、模组成本、整体方案复杂度都比BLE高不少。BLE的广播模式天然适合这类场景。节点不建立连接定时把温度、电量、事件计数、ID信息打进广播包网关在附近扫描就行。单网关可以同时接收几百个节点的广播包不需要路由组网网络层几乎零维护。为什么选Nordic首先是低功耗能力nRF52系列在休眠时电流可以做到微安级搭配纽扣电池跑两年是常规操作其次是协议栈成熟nRF5 SDK和Zephyr都有大量现成例程从广播、扫描到GATT、DFU远程升级都覆盖得很全再就是射频性能稳定小体积天线方案下灵敏度也够好在卖场这种复杂环境里不容易翻车。回顾整个选型过程还有一个隐性原因团队对Nordic的生态更熟。硬件参考设计直接沿用Nordic官方的参考电路可以少走很多弯路软件层面无论是SDK的API还是调试工具文档都很齐全。相比之下我之前用别的BLE方案踩过不少坑比如广播并发多了之后协议栈处理不过来或者低功耗模式下射频启动时间不可控。这些在Nordic平台上虽然也会遇到但可查的案例和社区讨论更多调试起来快很多。1.2 系统拓扑与数据链路整个系统的拓扑分三层末端节点、边缘网关、云平台。末端节点是基于Nordic BLE SoC的智能标签负责采集温湿度、门磁状态、运动状态和电池电量并且主动广播数据。网关部署在门店的天花板、货架端头或弱电间使用树莓派加USB蓝牙适配器或者直接使用Nordic官方nRF52840 Dongle作为蓝牙接收端。网关跑Linux用BlueZ或nRF Sniffer读取广播包解析完成后通过MQTT协议把结构化数据上送到云端。云端再经过规则引擎、时序数据库和告警服务最终呈现在门店看板或手机上。数据链路看起来不复杂但实际部署时有一些细节很关键。比如节点广播的数据包除了负载本身还带RSSI信号强度网关可以据此判断节点离哪个接收端更近也就是粗略的室内定位。生鲜区货架上的温度标签每隔几秒广播一次网关扫描到后直接判断温度是否超限超限就在本地先做一次蜂鸣提醒同时把告警事件发到云端。这样即使云端网络中断终端门店也能第一时间发现问题。这套架构最大的好处是“解耦”。末端节点只负责广播不需要维护连接状态网关只负责扫描和转发也不需要对每个节点做连接管理。连接型BLE应用里常见的设备掉线、重连、参数协商问题在这里都被绕开了。代价是数据是单向的要控制节点动作比如远程更新电子价签就需要另外设计连接通道。我后面会单独讲这一部分。2. 硬件选型与核心参数2.1 nRF52840 与 nRF52832 怎么选Nordic的BLE SoC产品线很宽零售项目里最常用的是nRF52832和nRF52840低成本的nRF52810在某些简单标签上也能用。选型时不能只看“都支持BLE”这个层面Flash、RAM、GPIO数量、外设类型、封装尺寸都需要对着需求表过一遍。参数nRF52832nRF52840nRF52810内核Cortex-M4F 64MHzCortex-M4F 64MHzCortex-M4 64MHzFlash512KB1MB192KBRAM64KB256KB24KBBLE版本5.05.05.0其他无线无IEEE 802.15.4、NFC-A无USB无USB 2.0无GPIO最多32最多48最多32典型封装QFN48QFN73QFN48在项目里我们定了两条选型原则。功能简单的广播标签比如贴在冷柜门上的门磁标签只需要采集开合状态、偶尔广播一次用nRF52810甚至更小的nRF52805就够但如果需要本地处理传感器算法或者要通过GATT连接做远程配置、OTA固件升级至少得用nRF52832。而nRF52840更适合做网关侧的蓝牙接收端因为它自带USB口插到树莓派上就是现成的协议转换器如果未来还想让节点同时支持Thread或ZigbeenRF52840的多协议能力也更有扩展性。实际报价上nRF52832比nRF52840便宜不少但两者差价在整机成本里占比并不高。我建议项目早期直接用nRF52840做验证把功能跑通后再根据量产成本做替换。这样能避免开发中途换芯片带来的软件重写风险。千万不要一开始就为了省几块钱选了Flash最小的型号后面加功能加不进去被迫换方案反而浪费更多时间和金钱。2.2 硬件设计中的几个关键点硬件设计方面Nordic官方参考设计已经做得比较完善但直接抄参考设计也有可能踩坑。我先说几个零售节点上特别值得注意的地方。第一个是天线部分。BLE使用的是2.4GHz频段天线的净空、匹配网络、阻抗控制都会直接影响灵敏度。如果你的产品外壳是金属材质或者会贴在金属货架上天线区域必须做净空并远离金属。我有个项目里节点贴在货架金属立柱上实测RSSI比悬空时低了10dB以上节点距离网关超过6米就丢失。后来把天线调整到标签边缘并加了一个约5mm的塑料垫片问题才解决。如果是PCB天线一定要严格按照Nordic参考设计的尺寸做不要为了外壳美观随意缩短天线长度。第二个是晶振。nRF52系列需要外部32MHz高频晶振而RTOS或者协议栈还需要32.768kHz低频晶振。很多人为了省成本只用内部低频RC但内部RC在睡眠唤醒时序上误差偏大会影响广播间隔和连接时序进而导致扫描端丢包率上升。我们的经验是凡是需要长时间睡眠、间歇广播的节点都必须贴32.768kHz晶振。第三个是电源去耦。BLE射频发射瞬间电流尖峰很大如果电源纹波过高接收灵敏度会明显下降。每个电源引脚旁边都要放100nF的高速陶瓷电容并靠近引脚放置如果芯片DC-DC模式使用外部电感电感选型不能随意。之前用了一款国产绕线电感代替参考设计中的叠层电感结果DCDC转换效率低发射时纹波大灵敏度掉了差不多5dB最后换回村田的叠层电感才恢复正常。第四个是测试与调试接口。量产节点上不一定需要接SWD但开发阶段最好把所有调试引脚包括SWDIO、SWCLK、RXD、TXD都引到测试点。遇到问题临时飞线比现改板子快得多。我们还在主板上预留了一个0欧电阻位来控制LED调试灯的供电正常工作时去掉这个电阻避免LED一直耗电。3. BLE协议栈设计与嵌入式实现3.1 广播包格式与连接参数设计零售IoT项目里末端节点大部分时间处于不可连接广播模式数据通过广播包传输。广播包格式必须设计得紧凑、可扩展并且要考虑后续协议升级。我们用的方式是厂商自定义数据段即AD Type 0xFF里面放厂商ID和自定义载荷。自定义载荷的字段顺序如下表所有多字节整数统一采用大端序偏移长度字段说明01协议版本当前为0x0111设备类型0x01温度标签、0x02门磁、0x03电子价签21电池电量百分比0~10032温度值有符号整数单位0.1℃51事件标记bit0门状态、bit1运动、bit2低电量64事件计数器每次事件自增用于网关去重广播间隔我们设置为250ms兼顾实时性和功耗。如果间隔太短比如20ms广播功耗会明显增加如果太长比如1秒以上门磁开关这类事件不够及时。这里要理解BLE广播的时间单位广播间隔以0.625ms为单位250ms对应的参数值是400。发射功率在普通货架区域设为0dBm覆盖半径10~15米在靠近生鲜冷柜的区域因为金属反射严重会适当降到-4dBm反而减少无线信号在金属间来回反射造成的多径干扰。对于需要双向控制的节点比如电子价签则会建立GATT连接。连接参数设计也有讲究连接间隔我们设为30ms到50ms从机延迟设为2这样标签在连接状态下大部分时间可以睡过去只在要收发数据时醒来。连接超时时间设为4秒避免因为无线丢包导致链路被长时间挂着。3.2 基于nRF5 SDK的代码实现软件开发我们用的nRF5 SDK 17.1.0配合SoftDevice S140。广播初始化的关键逻辑如下这段代码基本是从官方例程改出来的#include ble_advdata.h #include ble_gap.h #include nrf_power.h #include nrf_delay.h static uint8_t mfr_data[8]; static ble_gap_adv_handle_t m_adv_handle; static ble_advdata_man_specific_data_t man_spec_data; static ble_advdata_t advdata; static void advertising_init(void) { uint8_t flags BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; // 填充自定义厂商数据所有多字节字段都转成大端 mfr_data[0] 0x01; // 协议版本 mfr_data[1] DEVICE_TYPE_TEMP_TAG; // 设备类型 mfr_data[2] battery_percent; // 电量 mfr_data[3] (int8_t)(temperature_celsius * 10); // 温度整数值 mfr_data[4] event_flags; // 事件标记 mfr_data[5] (event_counter 24) 0xFF; mfr_data[6] (event_counter 16) 0xFF; mfr_data[7] (event_counter 8) 0xFF; mfr_data[8] event_counter 0xFF; man_spec_data.company_identifier 0xFFFF; // 替换为实际厂商ID man_spec_data.data.p_data mfr_data; man_spec_data.data.size sizeof(mfr_data); memset(advdata, 0, sizeof(advdata)); advdata.name_type BLE_ADVDATA_NO_NAME; advdata.flags.size sizeof(flags); advdata.flags.p_data flags; advdata.p_man_specific_data man_spec_data; ble_gap_adv_params_t adv_params {0}; adv_params.type BLE_GAP_ADV_TYPE_ADV_NONCONN_IND; adv_params.p_peer_addr NULL; adv_params.fp BLE_GAP_ADV_FP_ANY; adv_params.interval MSEC_TO_UNITS(250, UNIT_0_625_MS); adv_params.timeout 0; sd_ble_gap_adv_set_configure(m_adv_handle, advdata, adv_params); sd_ble_gap_adv_start(m_adv_handle, BLE_CONN_MODE_NON); }代码里的几个细节要注意。MSEC_TO_UNITS宏会把毫秒值除以0.625所以250ms换算后是400。sd_ble_gap_adv_set_configure可以反复调用但更新广播数据后通常需要先停止广播再重新启动否则部分SoftDevice版本会返回NRF_ERROR_INVALID_STATE。另外节点进入低功耗之前要调用nrf_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE)这个函数会打开内部DC-DC转换器能显著降低工作电流。没有打开DCDC时nRF52832在发射状态电流可能多出好几毫安。广播逻辑跑通之后低功耗睡眠就简单了。温度标签可以用RTC定时器每10秒醒来一次读一次温度更新广播包然后继续睡眠。要注意的是读取温度和更新广播之间要预留足够时间避免在射频发送还没完成时就进入睡眠导致广播包被截断。3.3 双向控制GATT服务与上位机接入纯广播模式只能上报不能下发指令。到电子价签这类需要远程更新屏幕内容的节点就必须用GATT连接。我们在每个价签上实现了一个简洁的自定义服务包含一个“控制指令”特征和一个“应答状态”特征。网关先通过扫描找到目标设备地址然后发起连接连接成功后向控制指令特征写入待显示的内容编码价签执行完刷新操作后在应答特征里返回状态。GATT服务设计上有一个值得注意的点服务和特征的UUID不要随便用自定义128位UUID但又不能直接用BLE标准服务因为标准服务不能满足零售价签的字段需求。我们的做法是用Nordic的UUID基地址然后自行定义短UUID这样逻辑清晰也能被多数扫描工具正确识别。上位机方面我用C#写过WinForms测试工具引用32Feet.NET库就可以枚举设备、连接服务、读写特征。Android端则直接使用系统自带的BluetoothLeScanner和BluetoothGatt逻辑也是类似的。如果你做的是纯测试工具建议先下载nRF Connect手机App在真机上直接看服务列表和特征值再回头写自己的上位机能省很多调试时间。4. 边缘网关与云端接入4.1 用树莓派做BLE扫描网关末端节点数据要出店就得靠边缘网关。我用树莓派4B加USB蓝牙适配器做了一版网关系统跑Raspberry Pi OS Lite蓝牙协议栈是BlueZ。最原始的调试方法是命令行敲hcitool lescan --duplicates但生产环境不能这么干还是得写真正的扫描程序。Python方案最方便我用了bluepy库一个简单扫描器的核心代码如下from bluepy.btle import Scanner, DefaultDelegate class ScanDelegate(DefaultDelegate): def __init__(self): DefaultDelegate.__init__(self) def handleDiscovery(self, dev, is_new_dev, is_new_data): if is_new_dev: print(fnew device: {dev.addr}, rssi: {dev.rssi}) for adtype, desc, value in dev.getScanData(): if adtype 0xFF: # 解析厂商自定义数据 payload bytes.fromhex(value) version payload[0] dev_type payload[1] battery payload[2] temp_raw int.from_bytes(payload[3:5], big, signedTrue) temperature temp_raw / 10.0 print(ftemp tag: {temperature} C, battery: {battery}%) scanner Scanner().withDelegate(ScanDelegate()) devices scanner.scan(5.0)scanner.scan(5.0)表示扫描5秒实际生产里不会阻塞式扫描而是把扫描放到一个线程里持续运行每次扫描窗口200ms扫描周期500ms也就是40%的占空比。这样既保证能发现广播间隔250ms的节点又不会让树莓派的CPU一直满负荷。大规模部署时树莓派自带的蓝牙模块理论上也能用但我实测下来在高密度标签场景下单网关超过150个节点自带蓝牙模块会出现广播丢包因为扫描窗口不够宽。后来换成Nordic nRF52840 Dongle作为USB接收器在对应的Linux驱动和扫描程序配合下并发扫描数量明显提升。如果节点数量再往上走也可以考虑用四路nRF52840 Dongle并接到一台网关上把不同的扫描器绑定到不同信道虽然成本高点但性能很稳。4.2 数据上云与远程OTA网关解出广播数据后下一步是上云。我们用MQTT协议网关通过Wi-Fi或者有线网口连接云端Broker按门店和节点维度发布主题。主题格式类似retail/store001/temp/A2:F1:3C:12发布的消息体用JSON字段包含设备地址、设备类型、温度、电量、事件计数、RSSI和时间戳实例如下{ device_addr: A2:F1:3C:12:44:55, type: temp, value: 25.6, battery: 92, event_seq: 1203, rssi: -55, ts: 1700000000 }MQTT的QoS等级我们统一用1保证消息至少送达一次。但要注意QoS1会产生重复消息云端消费者那边要做幂等处理最简单的方法就是使用节点里的event_seq字段去重。另一个经验是网关和云端之间最好做本地缓存网络抖动时先写到SQLite或内存队列恢复后再补发避免现场数据断档。远程OTA可能是整个项目里最容易被低估的一块。Nordic的BLE DFU协议已经很成熟但你要做的是“网关侧远程批量升级”不是拿手机一个个刷。我们的做法是云端把固件包下发到网关网关通过BLE GATT连接到目标节点使用Nordic DFU服务完成镜像传输。这里有一个很关键的坑DFU过程会把节点切到Bootloader模式如果升级过程中断电或连接中断设备会卡在Bootloader区需要现场恢复。所以网关侧的升级策略必须做“先小范围灰度再全量推送”而且每台设备升级完成后要立即回读应用固件版本确保升级成功再推送下一台。5. 现场常见问题与排查实录5.1 货架场景下的断连与丢包零售卖场里最常见的干扰源是Wi-Fi路由器、无线条码枪、微波炉和其他2.4GHz无线设备。BLE广播信道有三条37、38、39它们分散在2.4GHz频段内设计初衷就是为了提高抗干扰能力。但实际卖场中Wi-Fi的20MHz信道会占用大量频带如果Wi-Fi信道恰好和BLE广播信道重叠丢包就会明显增加。我们遇到过一个典型案例某生鲜门店的移动支付POS机收发数据时附近的温度标签广播丢包率从不到5%飙升到20%以上。排查过程是用nRF Connect App现场扫描看RSSI和信道状况发现POS机的Wi-Fi信道正好和BLE广播信道38重叠。解决方法是调整门店里Wi-Fi路由器的固定信道避开BLE的37、38、39同时把温度标签的广播间隔从250ms缩短到100ms重发次数多了丢包的影响自然就降下来了。另外一个现场经验是货架上的金属横梁会严重影响天线辐射方向。如果标签贴着金属面安装尽量把天线指向通道一侧不要指向货架内部。我们还在部分标签上加过一条醒目标注“天线方向朝外”要求施工人员按图安装否则后期信号弱很难排查。5.2 功耗比预期高一倍按照芯片手册nRF52832在System ON idle模式电流应该在1~2uA左右广播时平均电流根据间隔不同约10~30uA。如果实测功耗比预期高一倍先不要怀疑芯片有问题绝大多数情况是外设电路在偷偷耗电。我排查过的项目里最常见原因是GPIO悬空。某个节点的外接传感器有中断引脚但固件没有配置内部上拉或下拉导致该引脚在悬空状态反复翻转芯片无法进入深度睡眠。解决办法是把所有未使用的GPIO统一配置为输入下拉或者设置为输出低电平。其次是调试工具的影响开发板上如果还连着J-Link或者调试串口目标芯片无法进入真正睡眠状态测量时必须拔掉调试器。供电链路里的DC-DC电感也可能导致功耗偏高。nRF52的DCDC模式需要外部电感如果电感阻值过大转换效率会下降同样的发射电流在电池端就会高不少。我建议在硬件改版阶段把电源路径上的采样电阻预留出来方便后期用示波器电流探头测真实功耗波形。5.3 广播数据出现乱码与字节序问题广播包乱码是个经典问题尤其是温度显示成异常值的时候。我们有一次现场看到某个标签温度跳到320℃但节点实际温度只有32℃排查下来是固件里把温度数据按小端序发送网关按大端序解析导致高字节和低字节对调。这里要统一约定BLE空中数据没有天然字节序概念一定要在协议文档里明确写清“所有多字节整数一律使用大端序”。C语言结构体转发时也要小心不要直接用结构体指针转成uint8_t*因为编译器会对齐填充字节。正确做法是每个字段手工移位填充或者使用__packed属性但在nRF5 SDK里我还是建议手工移位最稳。还有一次乱码的原因更隐蔽协议版本升级后网关还在用旧版解析器读到新增字段时把后面的字节全对齐错了。后来我们规定广播包第一个字节固定放协议版本网关遇到不认识的版本号就丢包并告警避免静默解析错误。5.4 干扰与信道规划速查表根据现场排查经验整理了一张问题速查表做类似项目时可以直接对照症状可能原因快速定位方法解决建议节点偶发丢失Wi-Fi信道重叠nRF Connect看各信道丢包率调整Wi-Fi信道或发射功率RSSI普遍偏低天线方向被金属遮挡观察部署位置和方向调整标签朝向加垫片功耗异常偏高GPIO悬空或外设未关电流探头测量各状态电流配置GPIO上下拉关闭外设广播数据乱码字节序不一致或结构体填充对比网关日志与固件源码统一大端序手工移位多个网关互相干扰网关扫描参数不一致检查扫描窗口和间隔统一配置错开扫描周期节点进入BootloaderDFU异常中断回读版本号灰度升级增加失败重试这张表看起来简单但每条背后都是实实在在的现场问题。我建议团队在项目一开始就建立这样的排查手册并让现场工程师随时补充遇到类似问题可以少走很多弯路。6. 落地后的几点坚持6.1 给每台设备建立唯一ID和元数据项目上线初期我们只用设备MAC地址做标识结果很快就出了问题。BLE MAC地址在部分平台下可能随机化而且单看MAC根本看不出这个节点是贴在哪个冷柜、哪个货架。后来我们强制要求每台设备都有三重标识出厂写入的设备序列号、广播包里的设备类型、以及部署时的位置绑定信息。网关解析广播后会把序列号和位置ID关联起来再上报云端。这样出问题时可以直接定位到“某门店某冷柜的第三个标签”而不是面对一堆无意义的MAC地址。6.2 用远程日志代替现场抓包最后再分享一个经验。零售门店分布在不同城市出了问题如果都要到现场抓包成本太高。我们给每台网关加了远程日志通道网关运行中会把扫描丢包率、节点数量、异常设备列表、DFU升级结果等关键事件实时上传到云端。一旦有门店反馈数据不对先看远程日志大部分问题都能定位到是网络、供电还是固件版本问题实在需要抓包再安排人远程复现。这个习惯帮我们省了大量差旅成本也大大缩短了问题响应时间。整套系统从硬件选型到云端上线前前后后迭代了三个版本踩过的坑不少但整体技术路线是稳的。Nordic的BLE SoC生态确实适合这种零售IoT大规模部署场景只要前期把广播协议、硬件天线、网关部署还有OTA策略想清楚后面的路就顺了。
返回列表