不能靠裸写printf发送AT指令,必须采用中断+环形缓冲驱动、分档超时、URC与应答分离、状态机校验的完整链路设计,否则将因收发不稳、URC干扰、超时错配、状态错乱导致连接失败或数据丢失。

直接说结论:不能靠裸写 printf("AT+NSOCR=STREAM,6,0\r\n") 就完事,串口收发不稳、URC干扰、超时错配、状态错乱这四点会立刻让设备连不上或发不出数据。
串口驱动必须带中断 + 环形缓冲,不能用轮询收字节
很多初版代码在 while(1) 里反复调 HAL_UART_Receive() 或类似函数,结果模组一发长响应(比如 +CEREG: 2,1,"00000000","00000000",7)就丢字节。NB模组在注册/附着阶段常有几十到上百字节的多行返回,轮询间隔稍大就会截断。
实操建议:
- 接收必须走 UART 中断,每个字节进中断就塞进环形缓冲区(大小建议 ≥ 512 字节)
- 主循环只从缓冲区按行取数据(识别
\r\n或\n),绝不直接操作寄存器 - 务必检查缓冲区溢出:当新字节到来但缓冲已满,要丢弃最老字节并记录
uart_rx_overflow错误 - 波特率别硬设 9600 —— BC35-G/ML302 等主流 NB 模组上电默认是 115200,错配会导致看似“没回包”
AT指令链路必须分档超时 + 应答匹配,不能统一等 1s
查信号 AT+CSQ 通常 50ms 内返回,而激活 PDP AT+CGACT=1,1 在弱信号下可能耗时 8s。若所有命令都设固定 2s 超时,前者浪费时间,后者直接失败;若全设 10s,又会让正常流程卡顿。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 为每条指令定义超时档位:
AT+CSQ→ 200ms,AT+CGATT?→ 3s,AT+NSOCO=1→ 15s(DNS+TCP 握手) - 匹配应答不能只找
"OK"—— 要区分成功、失败、中间状态:"+CME ERROR: 50"是参数错误,"+CME ERROR: 4"是无服务,"+NSOCO: 1,0"才是连接成功 - 禁用全局回显(发
ATE0\r\n),否则返回流里混入你刚发的指令字符串,strstr()会误判 - 发送后立即清空接收缓冲区,避免残留旧数据干扰本次匹配
URC 必须剥离解析,不能和 AT 应答混在一起处理
模组主动上报如 +CEREG: 2,1、+NSONMI: 1,"data"、+NUESTATS: 会随时插入 AT 响应流。如果用一个缓冲区线性扫描,AT+NSOSD=1 的应答还没收完,一行 +NSONMI 就把它打断,导致后续解析全乱。
实操建议:
- 接收层按行切分后,先判断首字段是否为
+开头 —— 是则归为 URC,走独立回调;否则归为 AT 应答,送入当前等待队列 - URC 回调里只做标记或投事件(如置
g_nbiot_state.cereg = 1),绝不阻塞或发新 AT 指令 - 对关键 URC 如
+CEREG和+CGATT,需校验第二字段值(如1表示已注册,2表示已附着),不是所有 “OK” 都代表可用 - 不要忽略
+CME ERROR类 URC —— 它们是异步错误通知,比同步返回的ERROR更早暴露问题
Socket 数据发送前必须确认 PDP 已激活且 IP 已分配
常见现象:AT+NSOCR 成功、AT+NSOCO 返回 OK,但 AT+NSOSD 一直报 +CME ERROR: 21(网络不可达)。根本原因是 AT+CGACT? 返回 0(未激活)或 AT+CGPADDR 返回空,但代码没检查就往下走。
实操建议:
- 每次发数据前,必须顺序验证:
AT+CGATT?→AT+CGACT?→AT+CGPADDR,任一失败立即中止 -
AT+CGPADDR返回格式是+CGPADDR: 1,"10.123.45.67",注意提取引号内 IP,别直接拿整行 strcmp - TCP 发送用
AT+NSOSD时,第三个参数是数据长度(十进制),不是十六进制 —— 错写成0x10会导致模组只收 16 字节,实际发了 16 个 ASCII 字符 - 发完数据别立刻关 socket —— 等
+NSOSI: 1,1(发送完成通知)再调AT+NSOCL,否则部分数据可能被丢弃
真正难的从来不是“怎么发那条指令”,而是当 AT+NSOSD 返回 OK 却没收到平台数据时,你能否快速定位是模组没发、基站没转、核心网丢包,还是平台侧 CoAP Token 不匹配 —— 这需要每一层都留好可追溯的日志点,尤其是串口原始收发流和 URC 时间戳。


















