
1. AT命令通信解析模块设计与实现AT命令集作为调制解调器、无线通信模组GSM/GPRS、Wi-Fi、蓝牙、NB-IoT等的标准控制接口已沿用数十年并持续演进。其文本协议特性降低了硬件交互门槛但实际工程中却面临响应解析不确定性高、URCUnsolicited Result Code处理复杂、多阶段命令流程耦合度强、实时性与资源约束矛盾突出等问题。尤其在资源受限的裸机系统中缺乏操作系统提供的同步原语与任务调度机制传统轮询超时等待方式易导致CPU空转或响应丢失。本项目提出一种分层、可移植、轻量级的AT命令通信解析模块支持裸机at_chat与带OSat双运行环境通过链式队列管理、异步回调驱动、URC主动上报缓冲及状态机化命令执行流程系统性解决上述工程痛点。1.1 模块定位与核心设计思想该模块并非通用AT协议栈而是面向嵌入式设备端通信控制的中间件抽象层。其设计遵循三个核心原则解耦通信与业务逻辑将串口底层读写、模组物理连接细节封装为适配器Adapter接口上层应用仅需关注命令构造、响应解析与业务回调不感知具体模组型号或串口驱动实现。资源确定性管理摒弃动态内存分配所有作业项Work Item、URC缓冲区、接收缓冲区均在编译期静态声明避免裸机环境下内存碎片与分配失败风险。状态驱动而非阻塞等待在裸机环境中以固定周期建议≤20ms轮询AT控制器状态在OS环境中由独立线程驱动状态机演进。所有命令执行均通过回调函数通知结果主线程/任务无需挂起等待。模块支持两类典型应用场景Modem类模组如SIM800L、EC20、HL8518等涉及网络注册、信号查询、短信收发、TCP/UDP Socket建立与数据传输Wi-Fi/BLE模组如ESP32-WROOM、BK7231、nRF52840等用于Wi-Fi连接配置、AP扫描、BLE广播控制等。1.2 系统架构与模块划分模块采用清晰的分层架构自底向上分为硬件适配层、核心引擎层与应用接口层--------------------- | 应用层 (Application) | ← 用户业务逻辑WiFi初始化、Socket发送、信号查询 --------------------- | 接口层 (API Layer) | ← at_send_singlline(), at_do_cmd(), at_do_work() --------------------- | 核心引擎层 (Core Engine) | ← at_poll_task(), at_process(), 命令状态机, URC解析器 --------------------- | 硬件适配层 (Adapter) | ← uart_write(), uart_read(), 信号量/延时OS接口 --------------------- | 硬件层 (Hardware) | ← UART外设, GPIO控制线如RESET、POWER_ON ---------------------其中at_chat裸机版与atOS版共享同一套核心引擎逻辑差异仅体现在适配层接口定义与任务调度方式上极大提升了代码复用率与跨平台迁移效率。2. 裸机环境下的AT通信引擎at_chat裸机环境无任务调度与同步原语at_chat模块通过静态内存池与双链表队列实现高效、确定性的命令调度。2.1 作业项Work Item与内存池管理每个待执行的AT命令被抽象为一个at_item_t作业项其结构体定义如下typedef struct { unsigned int state : 3; // 当前状态AT_ITEM_IDLE, AT_ITEM_READY, AT_ITEM_BUSY, AT_ITEM_DONE unsigned int type : 3; // 作业类型AT_ITEM_TYPE_SINGLE, AT_ITEM_TYPE_MULTI, AT_ITEM_TYPE_CUSTOM unsigned int abort : 1; // 中止标志 void *param; // 通用参数指针如发送缓冲区地址 void *info; // 通用信息指针如用户回调函数指针 struct list_head node; // 链表节点用于挂载到空闲/就绪链表 } at_item_t;模块默认预分配10个作业项构成静态内存池。通过两个双向链表进行生命周期管理空闲链表Free List存储所有未被使用的作业项初始时全部挂载于此就绪链表Ready List存储已提交、等待执行的命令作业项。当用户调用at_send_singlline()时引擎从空闲链表摘取一个作业项填充命令字符串、回调函数等字段再挂入就绪链表尾部。执行完成后该作业项被移回空闲链表供后续复用。此机制确保了内存使用上限严格可控10 × sizeof(at_item_t)且无动态分配开销。2.2 核心状态机与轮询机制at_chat引擎的核心是at_poll_task()函数需在主循环或定时中断中以固定周期推荐10–20ms调用。其内部状态机流转如下就绪队列扫描遍历就绪链表查找首个state AT_ITEM_READY的作业项命令发送调用适配器write()接口发送AT命令字符串同时启动超时计时器默认3000ms响应接收与解析持续调用适配器read()接口将串口数据逐字节存入临时接收缓冲区对接收流进行行缓冲以\r\n为界对每行进行模式匹配若匹配到OK、ERROR、FAIL等终结响应则标记作业项为AT_ITEM_DONE触发用户回调若匹配到预注册的URC前缀如CSQ:则立即调用对应URC处理函数超时处理若计时器超时且未收到终结响应则置作业项state AT_ITEM_DONEret AT_RET_TIMEOUT回调通知失败状态清理回调执行完毕后将作业项从就绪链表移除归还至空闲链表。该状态机完全非阻塞单次at_poll_task()调用耗时极短微秒级符合裸机系统对实时性的严苛要求。2.3 关键API详解at_send_singlline()用于发送标准单行AT命令如ATCSQ默认等待OK响应// 示例查询信号质量 static void csq_callback(at_response_t *r) { if (r-ret AT_RET_OK) { printf(Signal query succeeded.\r\n); // 解析r-buf中CSQ: xx,yy字段 } else { printf(Signal query failed, ret%d\r\n, r-ret); } } at_send_singlline(at, csq_callback, ATCSQ);at_send_multiline()用于发送多行命令如ATCIPSEND后需输入数据自动处理提示符等待// 示例向TCP服务器发送数据 char data[] Hello, Server!; at_send_multiline(at, data_callback, ATCIPSEND, data, strlen(data));at_do_cmd()提供最大灵活性允许用户自定义发送格式、期望匹配的响应字符串及超时时间// 示例发送自定义命令并等待特定响应 at_respond_t resp {READY, recv_buf, sizeof(recv_buf), 5000}; if (at_do_cmd(at, resp, ATCUSTOM_CMD) AT_RET_OK) { // 成功recv_buf中包含READY后的数据 }at_do_work()适用于需要多阶段交互的复杂命令如Socket数据发送用户可编写状态机式处理函数static bool socket_send_worker(at_work_ctx_t *ctx) { // 阶段1发送ATKTCPSND命令 ctx-printf(ctx, ATKTCPSND%d,%d, session_id, len); if (ctx-wait_resp(ctx, CONNECT, 5000) ! AT_RET_OK) return false; // 阶段2发送实际数据 ctx-write(ctx, data_ptr, len); ctx-write(ctx, \r\n, 2); // 阶段3等待最终OK return (ctx-wait_resp(ctx, OK, 5000) AT_RET_OK); } at_do_work(at, socket_send_worker, user_context);3. 带操作系统环境下的AT通信引擎atOS环境下at模块利用操作系统提供的同步原语信号量、互斥锁、延时与线程机制将命令执行模型从“轮询”升级为“事件驱动”显著降低CPU占用率并提升编程模型简洁性。3.1 适配器接口增强OS版适配器at_adapter_t在裸机版基础上增加了OS相关接口typedef struct { // ... 裸机版已有字段write, read, debug ... // OS特有接口 void (*sem_take)(void *sem, uint32_t timeout_ms); // 获取信号量 void (*sem_give)(void *sem); // 释放信号量 void (*delay_ms)(uint32_t ms); // 任务延时 void *urc_sem; // URC事件信号量 char *urc_buf; // URC专用缓冲区 size_t urc_bufsize; utc_item_t *urc_tbl; // URC回调函数表 size_t urc_tbl_count; } at_adapter_t;其中urc_sem是关键设计当串口接收到URC数据时URC解析器会将数据拷贝至urc_buf并give该信号量。用户可创建独立线程专门take此信号量实现URC的零延迟、高优先级处理彻底分离URC与主命令流。3.2 线程化处理与状态机演进OS版推荐创建一个专用AT处理线程其主循环如下void at_thread(void *arg) { at_obj_create(at, adap); // 初始化AT对象含信号量创建等 while (1) { at_process(at); // 执行一次状态机演进 vTaskDelay(1); // 微小延时避免忙等 } }at_process()函数内部逻辑与裸机版at_poll_task()高度一致但关键区别在于超时等待不再依赖软件计时器而是调用adap-sem_take(adap-rx_sem, timeout_ms)使线程在无数据时挂起CPU资源让渡给其他任务URC唤醒URC解析器giveurc_sem后用户可另启线程take它实现URC的异步、高优先级响应线程安全所有对at_obj_t内部状态的访问均通过adap-sem_take/give保护确保多线程并发调用at_do_cmd()的安全性。3.3 URC主动上报机制详解URC是模组主动发起的、非请求的异步通知如来电CLIP:、网络附着CGREG: 1、数据到达KURC:。at模块通过三级机制保障URC可靠投递硬件层捕获串口中断服务程序ISR将接收到的每个字节送入环形缓冲区驱动层解析at_process()在每次调用时从环形缓冲区读取数据按\r\n切分行并与urc_tbl中预注册的前缀如CSQ:进行字符串匹配应用层投递匹配成功后将整行URC数据拷贝至urc_bufgiveurc_sem并调用对应的URC处理函数如csq_updated_handler。用户URC处理函数示例void csq_updated_handler(const char *urc_line) { int rssi, ber; if (sscanf(urc_line, CSQ: %d,%d, rssi, ber) 2) { printf(URC: RSSI%d, BER%d\r\n, rssi, ber); // 更新全局信号强度变量触发UI刷新等 } }此机制确保URC处理与主命令流完全解耦即使主命令执行耗时较长URC也能得到及时响应。4. 硬件适配与串口驱动集成模块本身不绑定任何特定MCU或串口外设其硬件适配完全通过at_adapter_t结构体实现。以下以常见STM32 HAL库与ESP32 IDF为例说明关键接口实现要点。4.1 串口读写接口实现write()与read()接口需满足非阻塞、线程安全OS环境下要求// STM32 HAL示例裸机 static int32_t uart_write(const void *buf, uint32_t len) { HAL_UART_Transmit(huart1, (uint8_t*)buf, len, HAL_MAX_DELAY); return len; } static int32_t uart_read(void *buf, uint32_t len) { // 使用HAL_UART_Receive_IT开启接收中断数据存入ring buffer // 此处返回实际从ring buffer读取的字节数 return ring_buffer_read(rx_ring, buf, len); }// ESP32 IDF示例OS static int32_t uart_write(const void *buf, uint32_t len) { return uart_write_bytes(UART_NUM_1, buf, len); } static int32_t uart_read(void *buf, uint32_t len) { // 使用uart_read_bytes带超时 return uart_read_bytes(UART_NUM_1, buf, len, 10 / portTICK_PERIOD_MS); }关键点write()必须保证原子性发送避免命令被截断read()应基于环形缓冲区或DMA避免在at_poll_task()中调用HAL_UART_Receive()等阻塞函数OS环境下read()需考虑线程安全环形缓冲区操作应加互斥锁。4.2 电源与复位控制可选部分模组如SIM800L需通过GPIO控制电源与复位。可在适配器中扩展typedef struct { // ... 其他字段 ... void (*power_on)(void); // 拉高POWER_KEY void (*power_off)(void); // 拉低POWER_KEY void (*reset)(void); // 触发RESET引脚 } at_adapter_t;初始化时调用adap-power_on()确保模组上电完成后再开始AT通信。5. 典型应用案例分析5.1 Wi-Fi模组AT通信M169模组M169是一款兼容ESP-AT指令集的Wi-Fi模组。使用at_chat模块实现Wi-Fi连接流程如下// 1. 初始化AT控制器 static at_obj_t at; static const at_adapter_t adap { .write uart_write, .read uart_read, .debug printf }; at_obj_init(at, adap); // 2. 连接Wi-Fi三步复位、设置模式、连接 void wifi_connect(const char *ssid, const char *pwd) { // 复位模组 at_send_singlline(at, NULL, ATRST); // 设置Station模式 at_send_singlline(at, NULL, ATCWMODE1); // 连接AP char cmd[64]; snprintf(cmd, sizeof(cmd), ATCWJAP\%s\,\%s\, ssid, pwd); at_send_singlline(at, wifi_connect_cb, cmd); } // 连接结果回调 static void wifi_connect_cb(at_response_t *r) { if (r-ret AT_RET_OK) { printf(Wi-Fi connected!\r\n); // 启动DHCP或获取IP at_send_singlline(at, ip_acquired_cb, ATCIFSR); } else { printf(Wi-Fi connect failed.\r\n); } }5.2 GPRS模组TCP数据发送HL8518模组HL8518模组的TCP数据发送需经历ATKTCPSND命令、CONNECT提示、数据发送、OK确认四阶段at_do_work()完美匹配此流程typedef struct { uint8_t session_id; uint8_t *data; uint16_t len; } tcp_send_ctx_t; static bool hl8518_tcp_send_worker(at_work_ctx_t *ctx) { tcp_send_ctx_t *p (tcp_send_ctx_t*)ctx-params; // 阶段1发送ATKTCPSND命令 char cmd[64]; snprintf(cmd, sizeof(cmd), ATKTCPSND%d,%d, p-session_id, p-len); ctx-printf(ctx, cmd); // 阶段2等待CONNECT if (ctx-wait_resp(ctx, CONNECT, 10000) ! AT_RET_OK) { return false; } // 阶段3发送数据 ctx-write(ctx, p-data, p-len); ctx-write(ctx, \r\n, 2); // 阶段4等待OK return (ctx-wait_resp(ctx, OK, 10000) AT_RET_OK); } // 调用入口 tcp_send_ctx_t ctx {1, (uint8_t*)GET / HTTP/1.1\r\n, 18}; at_do_work(at, hl8518_tcp_send_worker, ctx);6. BOM与资源占用分析本模块为纯软件中间件无专属硬件BOM。其资源占用取决于用户配置典型值如下资源类型裸机版at_chatOS版at说明RAM静态~2.5 KB~3.0 KB含10个作业项、128B URC缓存、接收缓冲区等ROM代码~8 KB~9 KB含核心引擎、字符串解析、状态机逻辑最大并发命令数1010由作业项池大小决定最小堆栈需求—1.5 KB线程栈OS线程栈需容纳状态机调用深度所有资源均为编译期确定用户可通过修改AT_WORK_ITEM_NUM宏调整作业池大小在资源与并发能力间灵活权衡。7. 移植指南与最佳实践7.1 移植步骤复制源文件将at_chat.c/h或at.c/h/at_util.h/comdef.h加入工程实现适配器编写uart_write/read等底层函数填充at_adapter_t结构体声明静态对象在.c文件中定义at_obj_t at及所需缓冲区如urc_buf[128]初始化与启动裸机at_obj_init()后在主循环中周期调用at_poll_task()OSat_obj_create()后创建专用线程运行at_process()循环编写业务逻辑调用at_send_singlline()等API实现具体通信功能。7.2 工程最佳实践超时时间设定模组响应时间差异大ATCSQ通常100msATCGATT可能达30s务必根据命令特性设置合理超时避免过长挂起URC缓冲区大小urc_buf需容纳最长URC行如KURC: 192.168.1.100,80,GET / HTTP/1.1建议≥128字节接收缓冲区大小at_do_cmd()的recvbuf应大于预期最大响应如ATCIPSERVER?返回可能很长避免截断错误恢复在at_send_singlline()回调中检测r-ret ! AT_RET_OK可执行ATRST复位模组日志调试启用adap-debug接口输出详细AT交互日志快速定位通信问题。该模块已在STM32F103、GD32F303、ESP32、nRF52832等多平台验证稳定支撑工业网关、智能电表、车载终端等产品量产。其设计哲学——以确定性内存管理对抗资源约束以状态机模型替代阻塞等待以适配器模式屏蔽硬件差异——为嵌入式通信中间件开发提供了可复用的工程范式。