NB-IoT温湿度采集实战:STM32L152+BC26+LWM2M数据上云全流程

📅 发布时间:2026/9/1 1:54:10
NB-IoT温湿度采集实战:STM32L152+BC26+LWM2M数据上云全流程
简介本资源是一套面向嵌入式物联网开发者的STM32L152单片机实战项目例程聚焦低功耗广域网场景解决温湿度传感器数据通过NB-IoT模块BC26接入电信云/华为云IoT平台的核心问题适用于高校课程设计、毕业设计及企业原型验证等中初级开发需求。压缩包共1119个文件含311个C源码与258个头文件构成完整KEIL标准库工程、201个JavaScript脚本用于平台侧解析或前端展示、157个PNG图标资源含LCD界面素材、91个文本说明文档以及调试配置类文件如uvproj、ld、icf等整体大小为20.32MB。已有78人学习下载体现其在NB-IoT终端接入领域的实用参考价值。读者可直接复用ADC采集、串口AT指令驱动BC26、LWM2M协议栈轻量集成、云平台注册与数据上报等关键模块代码所有外设引脚定义、通信时序注释详尽配套清除编译残余的批处理脚本与SDIO/LCD/RTC等底层驱动文件显著降低硬件适配与协议对接门槛。 搞这个项目之前我一直觉得NB-IoT是“运营商的玩意儿”离嵌入式开发很远。直到手头接了个环境监测需求要在几间办公室角落放温湿度采集点电池供电、不带网关、数据要能上云最好在手机或电脑上随时能看到实时曲线。看了一圈Wi-Fi在无路由的角落直接淘汰LoRa又得自己搭网关和协议栈最后把目光锁定在STM32L152 BC26-NB-IoT LWM2M这条链路上。标题里那句“温湿度ADC数据”就是整条链路的起点传感器输出模拟电压STM32L152的ADC多通道采样再经过DMA搬进内存换算成温度和湿度数值BC26模组负责走NB-IoT网络用LWM2M协议把数据写到物联网平台的对应资源上。整个过程不算复杂但涉及的环节很多硬件供电、ADC精度、模组AT指令、平台侧设备建模、报错排查……我把实际跑通项目的完整过程整理在下面希望能帮到正在折腾同类方案的人。1. 选型这台戏STM32L152、BC26和LWM2M到底是怎么凑到一起的1.1 为什么绕开Wi-Fi和LoRa选了NB-IoT很多人一提到物联网数据传输第一反应就是ESP8266接Wi-Fi便宜又简单。但这个项目的第一约束是“现场没有稳定网络”办公室角落、仓库货架附近Wi-Fi信号基本是残废状态。拉网线又不现实这时候NB-IoT的优势就体现出来了——只要运营商基站覆盖到位模组直接进网不需要用户侧配路由器也不需要自己维护网关。LoRa其实也在候选名单里但问题同样明显LoRa只解决了“最后一公里”的无线传输数据从网关到云端还是得靠以太网、4G或Wi-Fi。等于我得自己搭一个网关设备还要处理多节点接入、确认重传、服务器转发工程量直接翻倍。NB-IoT把“设备到平台”这条完整通道都打包好了我把数据交给模组模组通过基站发给平台平台再通过API或消息推送给我。对个人开发者或小团队来说省掉网关和服务器这层价值非常明显。1.2 为什么是STM32L152而不是F1/F4STM32L152是Cortex-M3内核的低功耗系列主频不高最高32MHz但胜在低功耗模式做得细STOP模式RTC唤醒典型电流能到3uA左右对电池供电系统非常友好。F103虽然资料多、用的人多但正常运行时电流就多出好几倍STOP模式下的唤醒电流也没法看。这个项目要求电池供电跑半年以上L152几乎是同价位里最稳妥的选择。BC26模组也同样考虑了低功耗它支持PSMPower Saving Mode和eDRX数据上报完可以进入微安级别的休眠状态配合L152的低功耗调度整套设备的待机电流能做到很低。这也决定了后面的软件架构——不能用“轮询跑满主频”的思路必须用状态机定时唤醒的方式来设计。1.3 LWM2M在这个链路里扮演什么角色LWM2M全称Lightweight M2M是OMA组织定义的设备管理协议跑在CoAP/UDP之上专门给资源受限的物联网设备用。这里得先理清一个概念LWM2M不是物理层的通信方式它更像“设备和平台之间的对话规则”。NB-IoT网络负责把数据包从基站传到核心网但数据包怎么组织、平台怎么解析就是LWM2M干的事。LWM2M的核心模型是“对象-实例-资源”。举个实际例子温度对象ID是3303实例0里面的5700资源就是“当前温度值”。我在MCU里执行“往3303/0/5700写入25.6”BC26模组就会把它封装成CoAP请求发给平台平台根据设备模型中定义好的对象ID去解析数据就能落库了。华为云IoT平台、电信云CTWing都支持这套标准模型所以用LWM2M还有个额外好处换平台时只要改服务器地址和鉴权参数MCU侧代码几乎不用动。整个系统的数据流向可以这样看传感器输出模拟电压STM32L152的ADC多通道扫描采样DMA搬运到内存MCU换算成温度和湿度数值通过UART把数值和对应的LWM2M资源路径发给BC26BC26封装CoAP报文走NB-IoT基站上行平台解析LWM2M资源写入设备属性应用侧通过平台API或消息推送拿到最终数据2. 温湿度ADC采集链路把模拟信号变成能上云的数值2.1 传感器信号链设计别一上来就调ADC先看前端电路标题里写的是“温湿度ADC数据”说明温度和湿度都是通过ADC采样的模拟量。我做的是两个通道温度用NTC热敏电阻加分压电阻湿度用模拟输出的湿度传感器比如HIH5030。这种方案的优点是电路简单、成本低MCU直接读ADC就行。温度通道的原理是NTC阻值随温度变化和固定电阻分压后节点电压也随之变化。选型时注意两点一是NTC的B值要选好B值越大温度分辨率越高二是分压电阻的阻值要和NTC在目标温度区间的阻值接近这样分压曲线最陡灵敏度最高。我用的NTC是10k25℃、B值3950分压电阻取10k在0~50℃范围内输出电压基本落在0.5V~2.8V正好适配3.3V供电的ADC量程。湿度通道用HIH5030这类电压输出型湿度传感器输出范围是0.8V~3.3V对应0%~100%RH线性度不错校准也很简单两三个湿度点就能拉出直线。这里有个电路细节传感器输出阻抗偏高时ADC内部采样电容充电时间不够会导致采样值偏低。解决办法是把ADC采样时间调到最长STM32L152 ADC采样时间可配置到最大档或者在传感器输出和ADC引脚之间加一个运放缓冲。2.2 STM32L152的ADC多通道扫描DMA配置STM32L152内置12位ADC采样通道够用关键是它支持扫描模式、连续转换和DMA。这套组合的价值在于硬件自动按通道顺序转换转换结果自动通过DMA搬进内存CPU完全不需要干预。我分配了三个通道温度通道、湿度通道、芯片内部VREFINT通道。为什么要把VREFINT也采进来因为电池供电时参考电压会随供电电压波动直接用3.3V当满量程算会引入误差。VREFINT是内部固定的基准电压通过它反推当前实际的VDDA换算出来的电压值更准。用STM32CubeMX配置很直观关键选项如下ADC扫描模式EnableADC连续转换EnableDMA连续请求EnableDMA模式CircularADC采样时间选最大档如239.5周期转换通道数3配置好后初始化代码会自动生成DMA的中断回调里我置一个标志位volatile uint16_t adc_values[3]; volatile uint8_t adc_complete_flag 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { adc_complete_flag 1; } } uint8_t app_adc_read_all(uint16_t *temp_raw, uint16_t *humi_raw, uint16_t *vdda_raw) { adc_complete_flag 0; HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_values, 3); // 等待转换完成这里可以加超时保护 while (!adc_complete_flag) { // 可插入看门狗喂狗 } HAL_ADC_Stop_DMA(hadc1); *temp_raw adc_values[0]; *humi_raw adc_values[1]; *vdda_raw adc_values[2]; return 1; }实际使用中有一点要注意DMA搬进数组的数值顺序和你配置通道的顺序一致别把温度通道和湿度通道搞反了。另外DMA模式下如果连续转换一直开着功耗比较浪费所以我是按需启动唤醒后启动一次DMA转换拿到结果马上关掉。2.3 定时器触发采样和数字滤波别把毛刺直接传给平台这个项目的数据频率不高10分钟上报一次但每次上报前我会连续采32次做滑动平均和中值滤波。为什么不一秒采一次存起来低功耗系统能少运行一秒是一秒集中采样反而能把ADC和CPU的运行时间压缩到最短。定时器触发ADC还有另一个好处可以精确控制采样时刻避开设备射频发射瞬间的电源干扰。我的经验是BC26发射时电源纹波比平时大很多如果ADC在这时候采样结果会被污染。所以软件流程是先采完ADC再唤醒BC26上报数据顺序不能反。滤波代码不复杂一个滑动平均加中值去毛刺#define FILTER_N 8 uint16_t app_adc_filter(uint16_t new_sample) { static uint16_t buf[FILTER_N]; static uint8_t idx 0; static uint8_t cnt 0; uint32_t sum 0; uint8_t i; buf[idx] new_sample; idx (idx 1) % FILTER_N; if (cnt FILTER_N) cnt; for (i 0; i cnt; i) sum buf[i]; return (uint16_t)(sum / cnt); }ADC原始码到温度的换算我用查表NTC公式相结合。NTC的阻值与温度关系可以用Steinhart-Hart方程描述但如果不想算对数直接做一张0~50℃的电压-温度对照表线性插值即可误差完全够用。湿度传感器线性度好直接两段式线性换算就行。3. BC26模组接入从串口AT指令到LWM2M注册3.1 硬件上电的坑BC26的峰值电流能把MCU拉复位BC26硬件连接不复杂VCC接3.6~4.2V电源UART的TX/RX和MCU交叉连接PWRKEY拉低100毫秒以上开机SIM卡座插NB卡天线焊好。但供电这关我确实踩过坑——BC26在NB-IoT发射瞬间电流峰值能到几百毫安甚至更高如果电源线太细、电容太少电压会瞬间跌破MCU的工作电压整机直接复位。解决方法是VBAT引脚附近加100uF钽电容10uF陶瓷电容100nF去耦电容电源走线尽量短粗。用电池供电的话最好在电池和负载之间并联一个大容量储能电容让发射瞬间的电流从电容里走而不是从电池内阻上硬拉。L152和BC26的UART电平都是3.3VBC26部分版本支持1.8V可以直接相连。但如果BC26是1.8V版本必须用电平转换芯片不能直接用电阻分压凑合否则模组识别不了高电平。3.2 网络附着检查SIM卡、信号和CEREG的返回值模组上电后第一件事是确认网络附着成功否则后面LWM2M注册想都别想。我常用的检查顺序是AT # 返回OK确认串口和模组正常 ATCIMI # 返回SIM卡的IMSI确认SIM卡能读 ATCSQ # 查信号强度返回值越大信号越好 ATCEREG? # 查网络注册状态ATCEREG?的返回值很多人看不懂我把常用的列一下返回值含义能不能继续0,0未注册正在搜索网络继续等0,1已注册本地网络可以0,3注册被拒绝检查SIM卡和APN0,5已注册漫游可以建议SIM卡选用运营商的正规NB-IoT卡APN一般是默认配好的但有的卡默认没配置需要手动设置。在我的项目里电信NB卡插上后ATCEREG?能自动返回0,1但我见过联通的卡要手动设APN才能附着这个坑等遇到了再排查也来得及关键是知道有这回事。3.3 LWM2M配置和上报的核心AT指令BC26内置了LWM2M协议栈我不用在MCU侧实现CoAP和LWM2M只需要通过AT指令配置服务器地址、开启注册、写入资源值。这里的配置思路是ATQLWSCFG0,平台CoAP接入地址,5683 ATQLWSREG1,300 ATQLWSDEV3303,0,5700,25.6QLWSCFG是配置第0个LWM2M服务器的IP和端口5683是CoAP的默认端口如果平台要求DTLS加密端口会变成5684还需要配置PSK密钥。QLWSREG1,300表示开启LWM2M注册生命周期300秒模组在周期内会主动向平台续注册平台才能维持设备在线状态。QLWSDEV就是往指定对象资源写入数据3303是温度对象实例05700资源写温度值3304是湿度对象资源ID同样是5700。温度、湿度各写一次之后平台侧就能看到两条最新属性值。这里有个经验如果平台和设备模型里没有定义3303/3304这些对象平台会直接丢弃或报错所以平台侧建模要和模组上报的资源路径一一对应这个在下一节详细说。4. 平台对接设备建模、鉴权和数据上行4.1 华为云IoT平台侧准备先建模设备才能“看得见”华为云物联网平台IoTDA的对接第一步不是写代码而是去控制台创建产品、定义物模型。进入IoTDA控制台后创建产品协议类型选LWM2M。添加服务比如“温湿度采集”。在服务下添加属性温度float只读、湿度float只读。这里要注意华为云IoT平台在LWM2M协议下物模型中的属性会自动映射到指定的LWM2M对象资源。但如果你用的传感器数据上报路径是3303/0/5700这样的标准资源华为云默认支持LWM2M标准对象也能直接解析到设备影子中。为了保险起见我在服务属性里特意设置了对应的LWM2M资源路径这样平台能准确把上报值和属性关联起来。设备注册时设备标识符device_id我填的是BC26的IMEI认证方式选密钥填一个自己定的密钥。这个IMEI和密钥要记住后面BC26侧可能要根据平台要求配置PSK或至少保证上报的设备标识能和平台匹配。4.2 从MCU buffer到平台显示数据上行全流程对齐平台侧准备完毕后MCU侧整个上报流程大概是这样的唤醒后app_adc_read_all获取原始ADC值换算成温度和湿度。按固定格式组包通过UART发送AT指令给BC26。模组返回OK后LWM2M模块内部把资源值封装成CoAP报文走NB-IoT上行。平台解析后写入设备属性。代码里上报函数我写得比较直白char temp_str[16]; char humi_str[16]; snprintf(temp_str, sizeof(temp_str), %.1f, temp_value); snprintf(humi_str, sizeof(humi_str), %.1f, humi_value); /* 上报温度 */ sprintf(cmd, ATQLWSDEV3303,0,5700,%s\r\n, temp_str); uart_send_string(cmd); delay_ms(100); /* 上报湿度 */ sprintf(cmd, ATQLWSDEV3304,0,5700,%s\r\n, humi_str); uart_send_string(cmd); delay_ms(100);需要注意两条AT指令之间要给模组留足处理时间。我一开始连续发指令第二条经常被丢弃后来改成每发一条就等待模组返回OK再发下一条问题就消失了。平台侧验证数据可以直接在控制台“设备详情”里看最新上报数据或者开启消息跟踪能看到上行消息的内容和时间。我建议把消息跟踪开着联调时能直观看到模组有没有把数据送上来。4.3 换成电信云CTWing时哪些地方不一样这个方案跑通后我其实也简单验证过电信云CTWing的接入。协议栈还是LWM2M over CoAP但有几个差异点接入地址和端口从电信云开发平台获取不一定都是5683。设备标识和鉴权需要在电信云平台先创建应用和产品生成设备ID和注册码。平台侧“数据上报”和“命令下发”的API结构不同但LWM2M资源读写的基本逻辑一样。所以标题里写“电信云或华为云物联网平台”本质上不影响MCU侧核心代码改的是平台配置和模组里的服务器地址。这也是我坚持用LWM2M而不是某平台私有协议的原因——通用性太好了。5. 联调中的三个典型坑从注册失败到数据丢失的完整排查链路5.1 第一个坑LWM2M注册总是不成功我遇到的现象是BC26的CEREG显示网络已注册但平台侧设备一直显示“未激活”或“离线”。排查链路是这样的先用ATQLWSREG?查看注册状态返回值如果是QLWSREG: 0说明LWM2M注册没开启或注册失败。检查服务器地址配置ATQLWSCFG?确认IP和端口和平台控制台上的一致。端口填错是最高频的错误5683和5684差一位就是“不加密”和“加密”的天壤之别。确认设备标识BC26的IMEI平台注册设备时填的设备标识必须是它。如果填错平台找不到对应设备注册请求直接被丢弃。如果配了PSK检查密钥是否一致。华为云IoT平台在部分接入方式下要求PSK匹配密钥错一个字符都不行。最后发现我的问题出在设备标识上平台侧创建设备时填了自定义字符串没填IMEI导致模组注册报文里的Endpoint Name和设备不匹配。改回来后秒注册成功。5.2 第二个坑数据上报偶发丢失平台端半小时没数据这个坑比注册失败更隐蔽。设备在线AT指令也返回OK但平台侧数据就是断断续续。排查思路先怀疑BC26处在PSM或eDRX休眠窗口数据包发出去后要等基站寻呼或下一个唤醒周期才能上行导致延迟或超时。解决办法是临时把PSM关掉ATCPSMS0再观察是否恢复正常。如果关掉PSM就好了说明是休眠策略和上报节奏的配合问题。我的处理是上报前先发一条ATCEREG?确认网络状态如果网络状态不对就不发等下次周期重试。还有一个容易忽略的点MCU串口发完AT指令后不要立刻进入低功耗模式要等模组返回最终OK或错误码。否则串口数据可能只发了一半模组还没处理完MCU就睡了。最终的解决办法是上报流程设计成一个带确认的状态机模组返回OK后再进入休眠如果超时未响应重新唤醒模组重试。5.3 第三个坑模组不定时自动重启设备反复上下线这个现象是最烦人的设备运行几小时或几小时就“消失”一次平台显示设备掉线过几分钟又重新上线看起来像模组复位了。我用示波器抓BC26的VBAT波形发现发射瞬间电压有瞬态跌落幅度超过了模组电源跌落保护阈值。原因就是供电回路储能不够NB-IoT射频发射脉冲电流太大把电压拉崩了。加上BC26的天线阻抗匹配不理想发射功耗进一步升高问题就更加频繁。解决办法VBAT处电容加到总容量500uF以上。电源走线加粗减少串联电阻。天线走线严格按照模组参考设计长度和阻抗匹配别偷懒。处理完后连续跑了72小时没再复现。这个坑给我的教训是NB-IoT模组的上电瞬间和发射瞬间电流变化剧烈电源设计必须按峰值电流而不是平均电流来算。6. 功耗预算、休眠策略和稳定运行的工程细节6.1 平均功耗不是靠猜的算一遍就清楚了电池供电设备先算功耗再定上报频率。我按10分钟上报一次来估算MCU唤醒采集100ms平均电流5mA耗电约0.5mAs。BC26发送数据一次约3秒平均电流120mA耗电约360mAs。休眠阶段MCU STOP模式3uA BC26 PSM模式3uA加起来6uA剩余597秒耗电约3.6mAs。一个周期总耗电约364mAs平均电流364/600 ≈ 0.6mA。一颗5000mAh的锂电池理论工作时间5000/0.6 ≈ 8333小时 ≈ 347天不到一年。如果换成30分钟上报一次平均电流能降到0.2mA左右理论工作时间超过两年。所以决定电池寿命的主要因素是上报频率而不是低功耗模式下那几微安电流。这也是为什么我建议不要把采集频率定得太高——NB-IoT一次上报的功耗顶得上休眠几十个小时。6.2 运行调度用状态机别用定时器裸奔低功耗设备和普通跑裸机while(1)的设备不一样关键是不能让CPU闲等。我推荐直接上状态机每个状态做一件事SLEEPMCU进入STOP模式RTC定时唤醒。WAKEUPRTC唤醒初始化ADC、UART。SAMPLE完成ADC采样、滤波、数值换算。REPORT唤醒BC26发送AT指令等待OK。RECHECK查CEREG如果网络异常则重试。DONE关闭外设进入SLEEP。状态之间的跳转由事件驱动比如SAMPLE完成后自动切到REPORTREPORT收到OK后自动切到DONE。用状态机的最大好处是任何状态都能设置超时退出不会因为某一步卡死导致整机功耗异常。6.3 工程细节日志、调试和稳定性项目联调阶段我习惯在UART上接一个USB转串口模块打印所有AT指令和模组返回方便复现问题。但正式部署时要关闭这些日志因为printf这类阻塞输出在低功耗调度里非常碍事。几个有价值的细节数据包格式给上报的数据加帧头和CRC虽然LWM2M和CoAP这类协议本身有校验但MCU和模组之间的串口通信也需要校验防止因为串口干扰导致发出去的数值已经是错的。看门狗独立看门狗在SLEEP模式下照样计数会定时复位所以要注意在唤醒期间喂狗或者选用窗口看门狗配合STOP模式的策略。这个细节很容易被忽视结果就是设备莫名其妙不断重启。SIM卡座NB-IoT卡尽量用带锁扣的卡座接触不良是导致“偶发注册失败”的元凶之一。天线位置天线不能紧贴MCU晶振和电源走线否则射频干扰会影响ADC采样和模组接收灵敏度。我实际调试中还发现用printf打印浮点数会显著拉长运行时间建议用整数定点数传输或者乘10转成整数打印这样串口传输快很多也省功耗。最后说一个个人觉得特别省力的技巧在做平台侧数据验证时我写了个简单的PC端串口小工具模拟MCU给BC26发AT指令先在PC上把模组和平台链路完全调通再接MCU。这样把问题隔离成“模组-平台”和“MCU-模组”两段出问题时不至于两头乱找。整套链路跑通之后你会发现LWM2M其实并不神秘它就是一个跑在CoAP上的标准资源模型真正花时间的永远是电源、信号和状态机这些底层细节。本文还有配套的精品资源点击获取