
前一阵我把手头这片ST Nucleo LoRa Sensor开发板成功接上了腾讯云物联网开发平台IoT Explorer数据从板载温湿度、气压传感器一路传到云端控制台再通过控制台下发指令回设备链路完全打通。说实话这套流程拆开看每一步都有对应文档但真正从零做一遍还是有不少地方要靠自己试错。这篇文章就把整个接入过程完整梳理一遍云侧产品怎么建、设备怎么注册、LoRa网关怎么配、固件这边怎么采集数据并按TLV格式上报以及实测中遇到的坑和排查思路给准备做LoRa传感上云的开发者一个能直接参考的样板。1. 这套方案的核心链路传感器、LoRa发射、网关转发与云端解析1.1 开发板上有哪些东西一片板子能干什么ST的Nucleo LoRa Sensor开发板常见的型号是B-L072Z-LRWAN1在我理解里就是一块面向超低功耗广域网场景的传感器节点原型板。板上集成了STM32L073RZ主控这是一颗Cortex-M0内核的超低功耗MCU主频32MHz工作电压范围1.8V到3.6V配合LoRa收发器SX1276可以在470MHz到915MHz的多个ISM频段下工作。板载传感器包括HTS221温湿度传感器、LPS22HB气压传感器以及加速度计和磁力计基本把环境监测场景里常用的物理量都覆盖了。更实用的是这块板子带Arduino兼容排针和ST-Link调试器一条USB线就能完成烧录、调试和供电。对于想快速验证LoRa节点、不愿意一开始就自己画PCB的人来说它是很称手的验证平台。实际能力上它适合做电池供电、小数据量的广域传感器比如农业大棚的温湿度采集、仓储环境监控、冷链运输记录这类场景。1.2 LoRa不是WiFi也不是NB-IoT它适合什么场景很多人第一次接触LoRa的时候容易把它和NB-IoT放在一起比较。两者都属于低功耗广域网但本质差别很大。LoRa是自组网技术你买一个LoRa网关放在自己的场地里节点数据直接通过网关送到云端不依赖运营商基站NB-IoT则要插运营商SIM卡走的是蜂窝网络覆盖。这意味着LoRa更适合那些不想被运营商套餐绑定、需要自主掌控基础设施、节点数量又比较可观的项目。还有一个经常混淆的点LoRa和LoRaWAN不是一回事。LoRa是Semtech的扩频调制技术解决的是物理层“无线电信号怎么调制成形”的问题LoRaWAN是MAC层协议解决的是设备怎么入网、数据怎么加密、帧怎么确认的联网逻辑。SX1276完成的是LoRa调制解调而LoRaWAN协议栈则运行在MCU里。腾讯云物联网开发平台上的LoRa产品云端担当的角色就是一个LoRaWAN网络服务器负责处理入网请求、解密、帧校验和重复包过滤。1.3 数据从传感器到云端的完整流向整个链路的走向是这样的传感器通过I2C总线把温湿度、气压数据读进STM32固件把这些数值按TLV格式打包成一个小payloadSX1276把payload调制成LoRa无线帧发出去LoRa网关在无线侧收到帧通过Packet Forwarder协议用UDP转发到腾讯云IoT的LoRa网络服务器云端解密校验后解析TLV更新物模型属性值你才能在控制台看到数据或者通过API、规则引擎继续处理。这条链路里最让你容易忽略的是网关这个中间环节。网关并不是简单地把无线数据“透传”到互联网它要把LoRa物理帧解析成JSON格式的UDP数据包和云端网络服务器做握手和校验。任何一个环节的协议版本或频段配置对不上数据就静默丢在中间设备状态还显示得挺好。1.4 为什么最终选这块板子和这个平台选这片板子主要是因为它和腾讯云IoT的LoRa方案刚好对得上。腾讯云IoT平台要求LoRa设备端走标准LoRaWAN协议数据格式用TLV而ST官方为这块板子提供了X-CUBE-SUBG1扩展包和现成的LoRaWAN End Node例程可以直接在此基础上改产品逻辑不用从零移植协议栈。平台侧的优势是省掉了自建LoRaWAN网络服务器的工作量。LoRaWAN后端不是随便写点代码就能跑稳的它要处理OTAA入网、会话密钥管理、MAC命令、多网关去重这些底层细节。腾讯云把这些做好了你在控制台定义好设备EUI和密钥固件侧参数写对剩下的协议交互交给平台这是很省心的模式。2. 腾讯云IoT平台侧配置产品、物模型、设备与网关注册2.1 创建产品时的几个关键选项登录腾讯云物联网开发平台进入产品列表新建一个产品。这里注意节点类型要选择“LoRa”而不是普通的“设备”。这个选项决定了后续整个生产流程是否走LoRaWAN通道选错的话后面就没有LoRa专属的设备和网关管理入口了。选完节点类型接入协议会锁定为LoRaWAN数据格式我选了TLV。频段选择上国内合规使用LoRa通常落在CN470频段平台会让你选择区域频段这里就按CN470来。网关固件、设备端协议栈以及平台选定的频段必须三者一致否则空中信号根本对不上后面会非常折腾。还有一点创建完产品后建议先记下产品的ProductID后面在SDK配置里有时会用到。2.2 物模型设计给每个数据分配TLV TypeLoRa的TLV格式本质上就是一种二进制协议Type告诉云端这个字段是什么Length表示数据长度Value是具体数值。我在物模型里定义了这样一个属性列表TLV Type属性标识符数据类型含义0x01temperaturefloat温度摄氏度0x02humidityfloat相对湿度百分比0x03pressurefloat气压hPa0x04batteryint32电池电压mV0x05accel_xfloatX轴加速度0x06accel_yfloatY轴加速度0x07accel_zfloatZ轴加速度这里面的Type值不能为0也不能互相重复。更重要的是Type号一旦确定下来后面固件侧和云端就必须严格保持一致。如果中途改了物模型但没改固件云端解析时会把整个TLV流拆错位出现一个属性把另一个属性的长度字节当成值的起始位置这种问题。所以我建议在一开始就先把属性清单定死后期尽量不要改。2.3 设备注册与网关注册设备注册时需要准备DevEUI、AppEUI现在也叫JoinEUI和AppKey。在LoRaWAN OTAA入网流程里设备端用这三个参数发起加入请求云端检查通过后返回入网许可随后设备才能开始上报数据。这些参数在控制台创建设备后会生成也可以自己填入但一定要保证设备端固件里写进去的值和控制台里的值完全一致。LoRa网关也需要先在平台登记。并不是所有“能收LoRa信号的硬件”都能直接用网关需要支持LoRaWAN Packet Forwarder协议并有一个网线或4G口接入互联网。注册网关时通常要填写网关EUI选择所属区域网关侧再配置网络服务器的IP地址和UDP端口配置完成后平台里这台网关应该显示为在线状态。2.4 云端配置完先检查这三点云端这部分配置完成之后先别急着写固件。我习惯做三个检查第一产品节点的确选择了LoRa类型这样设备列表里才会有LoRaWAN相关的设备信息展示第二设备注册信息里DevEUI、AppEUI、AppKey都已经生成且能正常查看不是在“草稿”状态第三网关已经配置并在平台显示为在线。网关在线是关键中的关键很多项目最后查半天发现是网关根本没接入平台空中信号再强也是白搭。3. 固件侧准备CubeMX工程、LoRaWAN协议栈与频段配置3.1 为什么用STM32CubeMX起步STM32CubeMX最大的价值是把时钟树、引脚复用和外设初始化一次性生成好让人不用去翻参考手册手工配寄存器。对于B-L072Z-LRWAN1这类板子CubeMX可以直接选择目标板按默认布局生成工程骨架。需要初始化的外设包括SPI连接SX1276 LoRa收发器、I2C连接板载传感器、UART调试日志输出、以及一个定时器作为LoRaWAN协议栈的时钟节拍。GPIO部分重点检查SX1276的复位引脚和DIO0到DIO3这几根线LoRaWAN协议栈收发中断事件都依赖这些引脚配错一个就收不到RF中断现象是节点一直“发呆”。3.2 LoRaWAN协议栈集成方式LoRaWAN MAC层协议栈自己从零写基本不现实直接用现成的就好。ST官方X-CUBE-SUBG1扩展包里带LoRaWAN End Node例程Semtech老牌的loramac-node也可以两者核心都差不多。用官方例程的好处是SX1276底层驱动、LoRaWAN协议栈以及BSP接口都已经验证过你要改的只是应用层逻辑。协议栈配置里重点检查几个宏区域选择LORAMAC_REGION_CN470激活方式选OTAALoRaWAN版本按平台文档选择对应版本设备类型设Class A。举例来说如果区域宏没改成CN470协议栈默认按欧洲868MHz的频段规划来发Join请求而网关在国内CN470频段监听那设备发出的信号在网关看来就属于另一个世界的频率永远入不了网。3.3 频段与信道配置细节OTAA入网的第一步是设备在默认上行信道上广播Join Request。如果网关只在某个信道上监听而协议栈的默认信道掩码又没有启用这些频点那么节点会反复重试加入直到能量耗尽也入不了网。LoRaWAN协议栈一般会提供默认的ChannelMask除非你明确知道自己在做什么否则不建议手动裁剪信道。我见过为了“提高速率”把信道数改少的情况结果入网包刚好落在被屏蔽的频点问题表现又像是EUI配置错了浪费了大半天排查时间。这类问题排查起来特别烦因为设备端日志显示“正在发帧”网关日志却什么都收不到。3.4 编译烧录与串口日志观察工程生成后我用STM32CubeIDE直接编译通过板载ST-Link烧录。串口日志用115200波特率连接LoRaWAN协议栈的debug输出能直接看到当前频率、扩频因子、帧收发状态。入网成功时日志里会出现类似“JOINED”的关键字上报之后能看到端口2的帧已正确发出。我建议在开发阶段始终保持串口日志打开不要急着做低功耗。第一版先把通信调通确认每一帧都能从设备到网关再到云端再考虑关外设、睡MCU这些降低功耗的事。通信链路没通之前谈功耗没有任何意义。4. 数据从板子到云端的核心代码传感器采集、TLV编码、上行与下行4.1 用I2C读取温湿度与气压传感器HTS221和LPS22HB都挂在同一条I2C总线上读取方式很常规。用ST官方BSP驱动的话基本上就是调用一个现成函数返回已经转换好的工程单位数值。float read_temperature(void) { HTS221_Data_t data; if (HTS221_GetData(HTS221_I2C_ADDRESS, data) ! 0) { return 0.0f; } return data.temperature_deg; } float read_humidity(void) { HTS221_Data_t data; if (HTS221_GetData(HTS221_I2C_ADDRESS, data) ! 0) { return 0.0f; } return data.humidity_pct; } float read_pressure(void) { LPS22HB_Data_t data; if (LPS22HB_GetData(LPS22HB_I2C_ADDRESS, data) ! 0) { return 0.0f; } return data.pressure_hpa; }这些传感器的转换结果已经做了内部校准温度直接是摄氏度、湿度是百分比、气压是hPa对于做验证原型来说足够。4.2 TLV编码云端数据契约的核心LoRa单帧能携带的payload很有限CN470区域默认的帧负载上限一般也就几十字节到一百多字节。用TLV的好处是结构紧凑每个字段都有明确的类型和长度云端解析时不容易混淆。按照腾讯云IoT平台对LoRa设备的TLV约定浮点数按IEEE754四字节大端编码int32也是四字节大端。static void append_tlv(uint8_t *buf, uint8_t type, uint8_t *value, uint8_t len, uint8_t *idx) { buf[(*idx)] type; buf[(*idx)] len; memcpy(buf *idx, value, len); *idx len; } static uint32_t float_to_ieee754(float value) { uint32_t bits; memcpy(bits, value, sizeof(bits)); return bits; } void pack_payload(float temperature, float humidity, float pressure, uint32_t battery_mv, uint8_t *buf, uint8_t *len) { uint8_t idx 0; uint8_t v[4]; uint32_t t float_to_ieee754(temperature); v[0] (t 24) 0xFF; v[1] (t 16) 0xFF; v[2] (t 8) 0xFF; v[3] t 0xFF; append_tlv(buf, 0x01, v, 4, idx); uint32_t h float_to_ieee754(humidity); v[0] (h 24) 0xFF; v[1] (h 16) 0xFF; v[2] (h 8) 0xFF; v[3] h 0xFF; append_tlv(buf, 0x02, v, 4, idx); // pressure, battery 同理 *len idx; }编码顺序和Type必须和云端物模型定义完全一致一个字段错位后面全部错位这是整个接入过程中最容易出问题的地方。4.3 OTAA入网与周期上报的主循环主循环的逻辑不复杂先检查设备有没有入网没入网就发起OTAA加入请求入网成功之后再进入周期上报流程。McpsReq_t mcps_req; MlmeReq_t mlme_req; while (1) { LoRaMacProcess(); if (!app_joined) { mlme_req.Type MLME_JOIN; mlme_req.Req.Join.DevEui dev_eui; mlme_req.Req.Join.AppEui app_eui; mlme_req.Req.Join.AppKey app_key; mlme_req.Req.Join.Dr DR_0; LoRaMacMlmeRequest(mlme_req); } else { uint8_t buf[32]; uint8_t frame_len 0; pack_payload(temp, humid, press, battery_mv, buf, frame_len); mcps_req.Type MCPS_UNCONFIRMED; mcps_req.Req.Unconfirmed.fPort 2; mcps_req.Req.Unconfirmed.fBuffer buf; mcps_req.Req.Unconfirmed.fBufferSize frame_len; LoRaMacMcpsRequest(mcps_req); } HAL_Delay(REPORT_PERIOD_MS); }上报周期我用了5分钟一包。LoRaWAN本身就不是为高频上报设计的它的功耗和吞吐都决定了它适合小数据量的周期上报。如果你需要秒级的数据刷新还是老实考虑用4G或Wifi方案别在电池供电的LoRa节点上强求。4.4 Class A下行数据接收与处理Class A设备只有在主动上行后的两个接收窗口才会监听下行所以云端下发指令后不会立即到达设备而是要等设备下一次上报网络服务器在RX1或RX2窗口把下行帧发下来。固件侧在LoRaMac事件回调里可以拿到下行数据。收到下行帧之后不要在中断回调里做耗时处理把数据缓存到全局变量等主循环再解析。如果你需要几乎实时的下行控制Class A肯定不合适得考虑Class B或Class C但随之而来的是功耗和设备复杂度的大幅上升。对这个项目来说Class A完全够用因为大多数LoRa应用都是“上报传感器数据为主偶尔改一下上报参数”。5. 实测跑通后的关键验证控制台数据、指令下发与距离测试5.1 控制台看到数据的完整步骤设备入网成功后控制台的设备详情状态会从“未激活”变成在线。进入“最新上报数据”区域就能看到刚才定义的temperature、humidity、pressure这些属性值按照物模型显示出来。如果数据一直没有出现不要急着怀疑设备坏了先去看网关在线状态和Packet Forwarder日志。很多时候设备侧日志显示“帧已发送”网关因为频段配置问题没收到云端自然什么都看不到。这种问题从设备端看是“成功发出”从云端看是“什么都没收到”中间的断层正好适合用网关日志来定位。5.2 从云端向板子下发一条指令控制台的下行指令操作很简单选择对应的设备填写要下发的字段或自定义Topic数据。但对LoRa Class A设备来说下发不是“秒达”。我第一次操作的时候点击下发后开发板串口半天没有反应还以为指令没发出去后来才发现下行数据是要等设备下一次主动上行之后才随RX窗口到达。实测下来如果把设备上报周期设成5分钟那么从点击下发到设备收到最坏情况要等5分钟加接收窗口偏移的时间。因此要对LoRa应用的下行延迟预期做足它注定不是实时控制通道。你也可以临时把上报周期调短测完再调回来。5.3 距离与信号质量实测我找了一片相对开阔的场地做了简单的距离测试网关放在二楼窗台节点逐渐远离。实际结果如下200米左右网关接收RSSI在-95到-100dBm之间数据基本稳定偶尔丢一两包。800米左右RSSI降到-115dBm附近SF12扩频因子下还能解调但余量已经不大。超过800米且中间隔了几栋楼丢包明显上升有些周期完全收不到。这组数据只能说明在城区环境下的表现具体项目一定要做现场信号勘测。遮挡物对LoRa信号的影响比距离更关键一个墙角就可能让信号衰减十几个dB更不用说一座楼了。5.4 功耗观察与优化方向开发板直接用USB供电测功耗没有实际意义USB口本身就供着ST-Link和一堆外围电路。要想评估真实电池寿命应该直接把3V到3.3V的电池接到板子供电输入断开ST-Link部分的消耗并且让MCU在空闲时进入stop模式、SX1276进入sleep模式。LoRa发射瞬间电流确实不小SX1276在最大功率发射时可以到上百毫安但由于单包发送时间很短以百毫秒计把上报周期拉长后平均功耗会被摊得很低。我的建议是第一版先做好通信稳定性功耗优化放到第二版再做。先能上云再考虑纽扣电池用几年。6. 整个接入过程中我踩过的坑和排查思路6.1 入网反复失败EUI大小端和频段信道的问题设备一直处于“Join中”的状态是我在整个项目里花时间最多的一个问题。排查链路要一层一层来网关是否真的在线网关天线是否接好设备端日志有没有发出Join Request云端有没有收到Join帧。我最终的问题是DevEUI的字节序。LoRaWAN协议栈在发送Join Request时对EUI的处理有一定的字节序要求而控制台显示的字符串是给人看的十六进制格式。直接把控制台的字符串原样填进固件实际发送的字节序就和云端期望的对不上入网就被拒绝了。尝试把DevEUI按字节翻转之后再填很快就“Join Success”了。如果你遇到类似情况翻转EUI试一下是最快的验证手段。6.2 数据在云端显示乱码TLV编码不一致第一次上报数据时控制台显示的数值出现了巨大的正负数比如温度变成了几亿度气压变成了负数。基本可以断定是TLV解析错位。我逐条核对发现有一个字段在固件里的Type值和云端物模型不一致导致后面所有字段整体错位。这个问题的排查方法其实很朴素先在PC上用Python按同一套TLV规则解析一遍固件准备发送的原始字节流看解析结果是否正常。如果PC解析正常而云端异常那就去看云端的物模型和TLV Type是否一致如果PC解析也不正常问题就在固件编码。两端都正常但云端还是乱码再检查浮点字节序是不是反了IEEE754浮点必须按四字节大端发出这是最容易忽略的细节。6.3 网关丢包率高天线位置与固件频段网关丢包率高有两个常见原因。第一是天线位置我把网关从一楼室内挪到二楼窗台同一个节点位置下的RSSI直接改善了大概20dB这个提升比换什么功率放大器都明显。LoRa网关的天线一定要尽量放高、远离金属物有条件就放屋顶。第二是网关固件本身的问题。市面上有些LoRa网关默认是按欧洲868MHz或美国915MHz烧录的固件你拿到国内直接配网设备侧发CN470频段的信号网关自然“聋了”。这类网关需要在底层配置里把射频频段切换到CN470并重新指定上行下行频点。表面上网关能上网、显示在线但无线侧的工作频率根本不对这类坑排查起来特别迷惑。6.4 后续扩展规则引擎、子设备与OTA数据上云之后能做的事就比较灵活了。腾讯云IoT平台的规则引擎可以把设备上报的属性转发到MySQL、HTTP服务、消息队列这些基础产品省去自己写定时器去拉数据的步骤。这样你可以在外面搭一个简单的数据面板或者告警系统。LoRa协议栈本身也支持多子设备也就是一台网关下挂多个节点设备平台会按DevEUI区分设备。做大规模部署时可以先把一台节点跑通验证数据流再复制出几十台节点逐个或批量注册设备统一管理。固件OTA在LoRa设备上需要注意下行通道带宽有限通常只能做小包差量升级这个要提前做好固件分块的协议设计。整套走进来之后最大的体会是硬件端并不是最难的部分。最难的是两端的数据契约云端物模型和固件TLV编码必须严格一致任何一边改一个字段链路就会静默失败设备日志显示发送成功云端什么也收不到。如果你准备做LoRa上云先把数据契约设计清楚再动硬件会省下大把时间。