因为AT响应是带噪声、非原子、无界的字节流,需用状态机区分接收中与就绪态,处理跨缓冲区、多行响应、自发上报等,而非简单字符串查找。

为什么不能直接用 std::string::find 做AT指令响应解析
因为真实模组(如SIM7600、EC20)返回的AT响应不是规整的文本流:中间可能夹杂乱码、超时重传、多行混合(OK 和 +CME ERROR: 4 可能紧挨着)、甚至串口底层丢字节。单纯靠 find 找关键词,容易在未收全帧时误触发,或在跨缓冲区边界时漏匹配。
真正要解决的是「如何把无界、非原子、带噪声的字节流,稳准地切分成语义完整的AT事件」——这本质是状态机问题,不是字符串查找问题。
- 必须区分「接收中」和「就绪态」,不能一收到
\r\n就认为一帧结束(有些响应含多行,比如AT+CGMI\r\n\r\nSierra Wireless\r\n\r\nOK\r\n) - 必须容忍不完整输入:一次
read()可能只拿到"AT+CSQ\r\n\r\n+CSQ:",下一批才到"23,99\r\n\r\nOK\r\n" - 必须识别并跳过非AT协议内容(如模组自发上报的
+UUSORD:,或调试日志前缀[DBG])
ATStateMachine 的核心状态定义与转移逻辑
状态机不追求覆盖所有AT指令,而是聚焦响应结构共性。四个主状态足够应对 95% 场景:
-
STATE_IDLE:等待AT命令发出后的首个响应字符,忽略一切前置垃圾数据 -
STATE_WAITING_FOR_RESPONSE:已收到首行(通常是空行或OK),开始收集后续内容直到终结符 -
STATE_IN_MULTI_LINE:遇到+xxx:或^xxx:等指示多行响应的提示符,持续累积直到下一个空行或终结符 -
STATE_COMPLETE:成功捕获一帧完整响应(含所有行、不含多余空行),交由上层分发
关键细节:终结符不是固定字符串,而是按上下文动态判断——命令若带 =?(查询类),期望 OK;若带 =(设置类),可能接受 OK 或 ERROR;而网络注册类(AT+CREG?)可能返回 +CREG: 0,1 后直接跟 OK,中间无空行。
立即学习“C++免费学习笔记(深入)”;
如何处理缓冲区跨帧与粘包
串口/USB CDC 的 read() 返回字节数不可控,必须支持增量解析。典型做法是维护一个滚动缓冲区(std::vector<uint8_t></uint8_t>),每次追加新数据后从头扫描,但「扫描起点」必须可重入。
- 绝不假设每帧以
\r\n结尾:有些模组用\n,有些在 Windows 下发\r\n\r\n,需统一 normalize 成\n再处理 - 每次解析只消费「已确认完整」的部分,剩余未决字节留在缓冲区头部(例如解析完
"OK\r\n"后,缓冲区还剩"\r\n+UUSORD:...",下一轮继续) - 设置硬性长度保护:单帧最大允许 2048 字节,超长则清空缓冲区并置
STATE_IDLE,避免内存耗尽或无限等待
示例片段(简化):
void parse(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; ++i) {
buf.push_back(data[i]);
if (state == STATE_WAITING_FOR_RESPONSE && is_terminator(buf)) {
emit_complete(std::move(buf));
buf.clear();
state = STATE_IDLE;
}
}
}
健壮性补丁:超时、重发与错误恢复
状态机本身不处理超时,但必须暴露钩子让调用方介入。例如:STATE_WAITING_FOR_RESPONSE 持续 3 秒未进入 STATE_COMPLETE,应通知上层「命令无响应」,而非卡死。
- 提供
on_timeout()回调,由上层决定是否重发 AT 命令、重启串口、或切换到故障模式 - 对
ERROR响应不做静默丢弃:提取错误码(如CME ERROR: 4中的4),映射为枚举AT_ERROR_SIM_FAILURE,供业务逻辑降级处理 - 检测连续 3 次
NO CARRIER或NO DIALTONE,自动触发AT+CFUN=1,1复位,避免陷入假死
最易被忽略的一点:模组在固件升级或低电量时,可能返回完全不符合规范的字符串(如 "USORD")。此时状态机应快速识别出非法 UTF-8 / ASCII,并强制回到 STATE_IDLE,而不是尝试解析乱码导致状态错乱。


















