1. 项目背景与整体设计思路
1.1 这个组合到底在做什么
BC28 是一颗支持多频段的窄带物联网通信模组,ONENET 是一个面向物联网设备的云平台,MQTT 则是设备与云平台之间常用的轻量级消息传输协议。这三者凑在一起,本质上就是让一台嵌入式设备通过窄带网络把数据上报到云端,同时也能接收云端下发的指令。听起来链路很短,但真正上手之后你会发现,从模组上电到云端能看到第一条数据,中间能踩的坑远比想象中多。
我这次的项目需求很明确:用 BC28 模组做终端,通过 MQTT 协议接入 ONENET 平台,实现周期性的数据上报和命令下发。硬件侧是一块低功耗主控加 BC28,走串口 AT 指令控制;云端侧在 ONENET 上创建产品、设备,配置物模型和数据流。整个方案选型没有太多纠结,因为 BC28 在窄带场景里功耗和成本都合适,ONENET 的 MQTT 接入文档也算齐全,MQTT 本身又是物联网里最成熟的选择之一。
1.2 为什么这套组合容易出问题
很多人以为 MQTT 接入就是“填个 IP、端口、三元组就完事”,但 BC28 这类模组和普通 Wi-Fi 模组有个本质区别:它内部有一套自己的协议栈和 AT 指令状态机。你发给模组的每一条 AT 指令,模组不一定立刻回你期望的响应,中间可能夹杂着网络注册、附着、PDP 激活等一堆异步事件。如果主控的串口收发逻辑写得不够健壮,就会出现“指令发出去了,响应收错了”“上一条还没处理完,下一条又来了”这类问题。
再加上 ONENET 的 MQTT 接入对报文格式、心跳间隔、主题命名都有具体要求,任何一个参数对不上,表现就是连接失败或者连上就掉。我前后调了大概一周,把遇到的坑一个个记下来,下面按“设计思路—核心细节—实操过程—问题排查”的顺序展开,尽量把每个坑的成因和解决办法都讲透。
1.3 整体架构与数据流向
先把这个项目的整体结构说清楚,后面排查问题才有参照。硬件上,主控通过 UART 和 BC28 相连,波特率一般用 9600 或 115200,具体看模组固件版本。BC28 负责和基站交互,完成网络注册后激活 PDP 上下文,拿到 IP 地址,然后才能建立到 ONENET 的 TCP 连接。MQTT 协议跑在 TCP 之上,所以顺序是:网络注册 → PDP 激活 → TCP 连接 → MQTT CONNECT → 订阅/发布。
数据流向上,设备采集到的数据先缓存在主控里,通过 AT 指令写入 BC28 的发送缓冲区,BC28 再通过 MQTT PUBLISH 发到 ONENET。云端下发的命令则反过来,ONENET 通过 MQTT PUBLISH 发到设备订阅的主题,BC28 收到后通过串口上报给主控,主控解析后执行动作。整个链路里,任何一个环节的状态没同步好,都会导致数据丢失或连接中断。
提示:建议在项目初期就把“网络注册—PDP 激活—TCP 连接—MQTT 连接”这四个状态做成显式的状态机,不要用延时等待的方式硬等,否则后期问题会非常难定位。
2. 核心细节解析与实操要点
2.1 BC28 的 AT 指令状态机怎么理
BC28 的 AT 指令有一个特点:很多指令的响应不是单一的 OK,而是会先回一个中间态,再回最终态。比如激活 PDP 上下文的AT+CGACT=1,1,可能先回 OK,然后过几秒才通过 URC(非请求结果码)上报激活成功。如果你在主控里只等一个 OK 就往下走,后面必然出错。
我的做法是给每条关键指令都定义一个“期望响应集合”,并且设置合理的超时。比如网络注册用AT+CEREG?查询,期望响应里要同时匹配+CEREG: 0,1和OK;如果只匹配 OK,可能在网络还没注册上时就误判成功。另外,BC28 的 URC 会上报很多异步事件,比如+CEREG: 2表示正在搜网,+CSCON: 1表示连接建立,这些都要在串口接收中断里做好缓冲和解析,不能简单丢弃。
实操中我建议把串口接收做成环形缓冲区,主循环里逐条解析,遇到完整的一行(以\r\n结尾)就交给状态机处理。这样即使 URC 和指令响应交错到达,也不会丢数据。我一开始用阻塞式读取,结果 URC 一来就把指令响应冲掉了,调了很久才发现是接收逻辑的问题。
2.2 ONENET 的 MQTT 接入参数怎么填
ONENET 的 MQTT 接入需要几个关键参数:设备 ID、产品 ID、鉴权信息(通常是 API Key 或三元组),以及服务器地址和端口。这里最容易出错的是 Client ID 的拼接格式和用户名密码的生成方式。不同版本的 ONENET 接入协议对这几个字段的要求不一样,有的要求 Client ID 用设备ID直接填,有的要求用产品ID+设备ID拼接,还有的要求密码用特定算法生成。
我的经验是:先去 ONENET 控制台把设备创建好,然后在“设备详情”里找到 MQTT 接入的示例参数,直接照着填。不要自己凭记忆拼,因为格式错一个字符,表现就是 CONNECT 返回0x05(未授权)或者直接断开。另外,ONENET 的 MQTT 服务器地址通常是域名形式,BC28 需要先做 DNS 解析,或者你直接填 IP。如果填域名,要确保AT+QIDNSGIP能解析成功,否则 TCP 连接根本建不起来。
还有一个细节:ONENET 对 MQTT 的 Keep Alive 时间有要求,一般建议 60 到 120 秒。如果你设得太短,比如 30 秒,可能会因为网络抖动频繁重连;设得太长,比如 300 秒,云端可能认为你掉线了。我实测下来 120 秒比较稳,既不会太频繁,也能及时感知断线。
2.3 MQTT 报文在 BC28 上怎么发
BC28 支持两种方式发 MQTT 报文:一种是用模组内置的 MQTT AT 指令,比如AT+QMTPUB;另一种是建立 TCP 连接后,自己组 MQTT 报文通过AT+QISEND发送。两种方式各有优劣。内置指令简单,但灵活性差,有些 ONENET 要求的报文格式它不支持;自己组报文灵活,但需要自己实现 MQTT 的编码解码,工作量不小。
我这次用的是内置 MQTT 指令,因为 ONENET 的报文格式比较标准,内置指令够用。但这里有个坑:AT+QMTPUB的参数顺序和长度计算很容易错。比如发布一条 JSON 数据,你要先算好 payload 的字节长度,填在指令里,如果长度算错,模组会返回 ERROR 或者发出去的数据被截断。我建议在代码里写一个函数专门计算 payload 长度,并且用strlen而不是sizeof,因为sizeof会把结尾的\0也算进去。
另外,BC28 的 MQTT 指令在发送较大报文时,可能需要分多次发送,或者等待模组返回>提示符后再输入数据。这个流程如果没处理好,就会出现“数据发了一半”的情况。我的做法是:先发指令头,等模组回>,再发 payload,最后等OK。每一步都设超时,超时就重试。
2.4 串口通信的稳定性怎么保证
串口是主控和 BC28 之间唯一的通道,它的稳定性直接决定整个项目的成败。我踩过的坑包括:波特率不匹配导致乱码、硬件流控没开导致数据丢失、电源纹波太大导致模组重启。先说波特率,BC28 默认通常是 9600,但有些固件版本是 115200,如果你不确定,可以先发AT测试,能回 OK 就说明波特率对了。
硬件流控方面,如果主控和 BC28 都支持 RTS/CTS,强烈建议打开。窄带网络下数据发送有时会有延迟,如果没有流控,主控发得太快,模组的缓冲区满了就会丢数据。我一开始没开流控,结果每次发长报文都会丢几个字节,后来打开AT+IFC=2,2才解决。
电源也是个大问题。BC28 在发射瞬间电流能到几百毫安,如果电源设计不好,电压会被拉低,导致模组重启或复位。我的经验是:在模组电源脚旁边并一个大电容(比如 100uF 以上),并且用示波器看一下发射时的电压跌落,如果跌超过 0.3V,就要考虑换更大电容或改进电源电路。
3. 实操过程与核心环节实现
3.1 从零开始:模组上电到网络注册
拿到 BC28 后,第一步是确认它能正常上电和响应 AT 指令。接线很简单:VCC、GND、TX、RX,如果需要流控再加 RTS、CTS。上电后先发AT,如果回OK,说明串口通了。如果不回,先检查波特率,再检查 TX/RX 有没有接反。我遇到过 TX/RX 接反的情况,表现就是完全没响应,换了线序就好了。
接下来是网络注册。先发AT+CFUN=1打开全功能,然后发AT+CEREG=1开启网络注册状态上报,再发AT+CGATT=1附着网络。附着成功后,用AT+CEREG?查询,期望看到+CEREG: 0,1或+CEREG: 0,5(5 表示漫游)。如果一直是0,2,说明还在搜网,可能是天线没接好或者信号太弱。我建议在代码里加一个循环,每隔 2 秒查一次,最多等 60 秒,超时就报错。
网络注册成功后,还要激活 PDP 上下文。发AT+CGACT=1,1,然后等 URC 上报+CGACT: 1,1。这一步成功后,模组就拿到了 IP 地址,可以用AT+CGPADDR=1查询。如果这一步失败,常见原因是 APN 没设对。BC28 一般用AT+CGDCONT=1,"IP","你的APN"设置,APN 具体是什么要看你的 SIM 卡运营商,填错了就激活不了。
3.2 建立 TCP 连接与 MQTT 握手
PDP 激活后,下一步是建立到 ONENET 的 TCP 连接。BC28 用AT+QIOPEN指令,参数包括连接 ID、服务类型(TCP)、服务器地址和端口。比如AT+QIOPEN=1,0,"TCP","mqtt.heclouds.com",1883,0,1。这里要注意:服务器地址如果是域名,模组会先做 DNS 解析,解析失败会返回错误。如果 DNS 不稳定,可以直接填 IP,但 ONENET 的 IP 可能会变,所以还是建议用域名。
TCP 连接建立后,模组会回+QIOPEN: 0,0,表示连接 ID 0 打开成功。然后就可以发 MQTT CONNECT 报文了。如果用内置 MQTT 指令,先发AT+QMTCFG配置参数,比如AT+QMTCFG="keepalive",0,120设置心跳为 120 秒,再发AT+QMTCONN=0,"clientID","username","password"发起连接。如果返回+QMTCONN: 0,0,0,说明连接成功。
这里有个细节:ONENET 的 username 和 password 有特定格式,username 通常是产品 ID,password 是鉴权信息。如果填错,会返回+QMTCONN: 0,0,5(未授权)。我建议先把参数在电脑上用 MQTT 客户端工具测通,再填到模组里,这样能排除是参数问题还是模组问题。
3.3 数据上报与命令下发的完整流程
MQTT 连接成功后,就可以发布数据了。BC28 用AT+QMTPUB指令,参数包括连接 ID、消息类型、QoS、保留标志、主题和 payload。比如AT+QMTPUB=0,0,0,0,"$dp","{\"temp\":25}"。这里主题$dp是 ONENET 的数据上报主题,payload 是 JSON 格式的数据。发完后模组会回+QMTPUB: 0,0,0,表示发布成功。
命令下发则是订阅主题。ONENET 的命令下发主题通常是$cmd或类似格式,具体要看产品配置。用AT+QMTSUB=0,1,"$cmd",0订阅,当云端有命令下发时,模组会通过 URC 上报+QMTSUB: 0,1,"$cmd",0和 payload。主控收到后解析 JSON,执行相应动作。
我实测下来,数据上报的周期建议不要低于 10 秒,因为窄带网络的传输延迟比较大,如果发得太频繁,模组的缓冲区容易满,导致后续数据被丢弃。如果确实需要高频上报,可以考虑用 QoS 1 或者做本地缓存,等网络空闲时再补发。
3.4 低功耗场景下的特殊处理
如果项目是电池供电,低功耗就很重要。BC28 支持 PSM 和 eDRX 两种省电模式,可以通过 AT 指令配置。PSM 模式下,模组在空闲时会进入深度睡眠,功耗可以降到微安级,但缺点是唤醒延迟大,适合对实时性要求不高的场景。eDRX 则是周期性唤醒,延迟小一些,但功耗比 PSM 高。
我的做法是:在数据上报完成后,发AT+QPSMS=1开启 PSM,然后发AT+CPSMS=1,,,"00000001","00000001"设置激活时间和周期。具体参数要根据运营商网络支持情况来调,设得太短省不了电,设得太长可能错过云端命令。我一般设激活时间 10 秒,周期 1 小时,实测下来平均电流能降到 100uA 以下。
注意:进入 PSM 前一定要确保所有数据都发送完成,否则模组睡着后数据会丢。我建议在发完最后一条数据后,等 2 秒再进 PSM,给模组留出处理时间。
4. 常见问题与排查技巧实录
4.1 连接失败类问题速查
连接失败是最常见的问题,表现有几种:TCP 连不上、MQTT CONNECT 返回错误码、连上就掉。我把遇到的情况整理成表格,方便对照排查。
| 现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| TCP 连接返回错误 | 服务器地址或端口错 | 用AT+QIOPEN看返回码 | 核对 ONENET 接入地址和端口 |
| MQTT CONNECT 返回 0x05 | 鉴权信息错 | 检查 username/password | 重新生成鉴权信息 |
| 连上后几秒断开 | Keep Alive 太短 | 查AT+QMTCFG配置 | 改为 120 秒 |
| 频繁重连 | 信号弱或电源不稳 | 查AT+CSQ和电压 | 改善天线和电源 |
这里重点说下信号问题。AT+CSQ返回的信号强度范围是 0 到 31,值越大越好。如果低于 10,说明信号很弱,可能导致连接不稳定。我遇到过在室内角落信号只有 6,模组频繁掉线,后来把天线移到窗边就好了。如果实在没法改善位置,可以考虑加外置天线或者换运营商。
4.2 数据丢失类问题排查
数据丢失的表现是:云端看不到数据,或者数据不完整。原因可能出在串口、模组缓冲区、网络传输任何一个环节。我的排查顺序是:先看串口有没有丢数据,再看模组有没有返回发送成功,最后看云端有没有收到。
串口丢数据通常是因为没有流控或者缓冲区太小。我建议把串口接收缓冲区设到至少 512 字节,并且打开硬件流控。如果主控的串口中断优先级太低,也可能导致数据丢失,可以适当提高中断优先级。
模组缓冲区满的话,AT+QMTPUB会返回错误或者超时。这时候可以加一个重试机制,等 1 秒再发。如果经常满,就要降低发送频率或者减小 payload 大小。ONENET 对单条数据的长度有限制,一般不超过 1KB,超了会被拒绝。
4.3 模组异常重启类问题
模组异常重启是个很烦人的问题,表现是突然不回 AT 指令,或者网络断开后重新注册。原因主要有两个:电源不稳和看门狗复位。电源问题前面说过,并大电容和检查电压跌落。看门狗复位则可能是模组固件的问题,可以尝试升级固件或者降低发送频率。
我遇到过一次模组每隔几分钟就重启,查了很久发现是电源芯片的限流设得太低,发射瞬间电流不够导致电压跌落。后来换了限流更大的电源芯片就好了。所以如果你也遇到类似问题,先用示波器看电源电压,再查模组的供电要求,确保电流余量足够。
4.4 实操心得与避坑清单
最后分享几条我踩坑后总结的经验,都是文档里不会写的:
- 不要用延时等待代替状态查询。比如发完
AT+CGATT=1后不要直接delay(5000),而是循环查AT+CGATT?,这样既快又稳。 - 串口接收一定要做环形缓冲,并且逐行解析。URC 和指令响应混在一起是常态,不做缓冲必丢数据。
- MQTT 的 Keep Alive 不要设太短,120 秒是实测比较稳的值。太短会频繁重连,太长云端会认为你掉线。
- 发数据前先算好长度,用
strlen不要用sizeof。长度错一个字节,模组就可能返回 ERROR。 - 低功耗场景下,进 PSM 前等 2 秒,确保数据发完。这个 2 秒是我试出来的,短了会丢数据,长了浪费电。
- ONENET 的接入参数一定要从控制台复制,不要手打。手打容易错,错了还很难查。
- 电源一定要留足余量,BC28 发射瞬间电流很大,电源不好会各种奇怪问题。
这个项目后续还可以扩展的地方不少,比如加个本地缓存,网络断开时先把数据存起来,等恢复后再补发;或者用 MQTT 的遗嘱消息,设备掉线时云端能及时感知。我目前还在调一个基于 eDRX 的低功耗版本,等稳定了再分享。