AT 命令模组应用
AT 命令模组(at_char_com)是 AW31N BLE SDK 中一个完整的串口透传 + 命令控制应用:用户通过 UART 发送标准 AT+ 指令即可查询版本、配置广播、扫描并连接 BLE 设备、执行 OTA 升级与低功耗关机;同时 UART 与 BLE 通道之间支持数据透传(Transparent Transmission)。
Purpose and Scope
本页面向 apps/demo/transfer/examples/at_char_com/ 目录下的 AT 命令模组,覆盖:
- UART 数据接收、命令/透传两种模式的分发逻辑(
at_char_uart.c) - AT 命令解析器与命令表驱动的执行框架(
at_char_cmds.c) - 模组应用入口、软关机与低功耗处理(
app_at_char_com.c) - BLE 主机(Client)与从机(Server)透传通道的角色说明(
ble_at_char_client.c/ble_at_char_server.c)
本页不展开通用的 BLE GATT 协议细节、OTA 升级内部实现、RCSP 协议等其他能力,相关内容请参阅各自目录页(如 "BLE 从机服务"、"OTA 升级" 等)。
Overview
AT 命令模组是典型的"串口控制 + 无线透传"方案,常见于 BLE 透传模块、测试盒子、模组评估板等场景。其设计核心是一个双模式数据通路:
- 命令模式(Command Mode):UART 收到的数据以
AT+/AT>开头,进入命令解析器,匹配命令表后执行对应动作,并回送OK/ERR响应。 - 透传模式(Transparent Mode):当模组被切换到某个 BLE 连接通道(
cur_atcom_cid < 9)后,UART 数据不再被解析,而是直接通过回调转发到 BLE 通道;BLE 侧收到的对端数据同样通过 UART 输出。
模组通过编译宏 CONFIG_APP_AT_CHAR_COM 控制是否参与构建,命令表、解析缓冲区、波特率等均可通过 app_config.h 中的配置项调整,是一套"表驱动、可裁剪"的轻量实现。
Architecture
flowchart TD
subgraph sg_External["外部设备"]
Host["上位机 / MCU / 测试盒子"]
end
subgraph sg_ATModule["AT 命令模组 at_char_com"]
Uart["at_char_uart.c<br/>UART 收发与分发"]
Cmds["at_char_cmds.c<br/>命令解析器 + 命令表"]
App["app_at_char_com.c<br/>模组入口 / 电源管理"]
BLE_C["ble_at_char_client.c<br/>主机通道(0-7)"]
BLE_S["ble_at_char_server.c<br/>从机通道(8)"]
end
subgraph sg_BLEStack["BLE 协议栈"]
GATT["GATT / Controller"]
Peer["对端 BLE 设备"]
end
Host -->|"AT+xxx\\r\\n 或 透传数据"| Uart
Uart -->|"命令模式"| Cmds
Uart -->|"透传模式 (cid<9)"| BLE_C
Uart -->|"透传模式"| BLE_S
Cmds --> App
Cmds --> BLE_C
Cmds --> BLE_S
App -->|"控制/查询"| BLE_C
App -->|"控制/查询"| BLE_S
BLE_C --> GATT
BLE_S --> GATT
GATT --> Peer
Uart -->|"OK/ERR 与对端数据"| Host
架构说明: 上位机通过 UART 与模组交互,at_char_uart.c 是唯一的数据入口。它根据当前通道状态(cur_atcom_cid)决定数据走命令解析器还是直接透传:cur_atcom_cid == 9 为默认的 AT 指令通道,收到 AT> 前缀可强制回到命令模式;cur_atcom_cid < 9 表示已绑定到 0-7 号主机连接通道或 8 号从机通道,此时数据原样转发。命令解析器通过字符串表匹配出命令 ID,再分发到对应处理函数,最终经 at_cmd_send() → ct_uart_send_packet() 回送响应。BLE 通道由 client(主动连接)与 server(被连接)两条路径组成,透传数据与命令结果都复用同一 UART 通路。
模组组成与文件布局
AT 命令模组位于 apps/demo/transfer/examples/at_char_com/,由以下源文件协作完成:
| 文件 | 职责 | 日志标签 |
|---|---|---|
app_at_char_com.c | 应用入口、模块激活状态、软关机/低功耗处理 | [AT_COM] |
at_char_cmds.c | AT 命令表、参数解析、命令执行、响应发送 | [AT_CHAR_CMD] |
at_char_uart.c | UART 初始化、接收中断处理、命令/透传分发 | [at_char_uart] |
ble_at_char_client.c | BLE 主机侧透传通道(连接对端、收发数据) | — |
ble_at_char_server.c | BLE 从机侧透传通道(被连接、收发数据) | — |
at_char_cmds.h / ble_at_char.h | 命令 ID、参数结构、通道宏定义 | — |
整个模组通过 #if CONFIG_APP_AT_CHAR_COM 条件编译开关整体纳入或移出构建,log_config.c 中 AT_CMD 标签的日志开关亦单独可配(apps/demo/transfer/config/log_config.c 中 log_tag_const_v/i/d/w/e_AT_CMD)。
UART 接收与分发(at_char_uart.c)
at_char_uart.c 是模组的数据入口,其核心是 at_cmd_rx_handler() 接收处理函数。该函数在 UART 收到数据时被调用,执行以下步骤:
- 喂狗:维护一个计数
clear_dog_flag,每收满WDT_CLEAR_TIMS次即调用wdt_clear(),防止长数据流传输期间看门狗误复位。 - 读取数据:通过
uart_get_recv_len(ct_uart_num)获取本次接收长度,再uart_recv_bytes()拷贝到静态接收缓冲pRxBuffer_static[UART_RX_SIZE](NOT_KEEP_RAM,4 字节对齐)。 - 溢出保护:
data_length > UART_RX_SIZE时丢弃整包并打印cmd overflow;启用FLOW_CONTROL时还会检测环形缓冲溢出并打印uart overflow, Data loss!!!。 - 通道切换检测:若数据长度大于 3 且以
at_change_channel_cmd[] = "AT>"开头,执行cur_atcom_cid += 9强制进入命令解析模式。 - 模式分发:
cur_atcom_cid < 9(已绑定到 0-7 主机通道或 8 从机通道)→ 数据原样交给at_uart_handler_callback透传,不解析。- 否则按命令处理:要求数据以
\r结尾(容忍最后一个字节为\r或倒数第二个字节为\r的两种行尾形式),补齐\0后交给回调。
- 流量控制恢复:
FLOW_CONTROL开启时调用uart1_flow_ctl_rts_resume()恢复 RTS 信号。
关键设计点:分发回调只有一个函数指针 at_uart_handler_callback,由上层在初始化时注入。透传模式下它指向 BLE 数据发送函数;命令模式下它指向 at_char_cmds.c 的命令入口。这样 UART 层完全不关心数据内容,解耦清晰,扩展新通道只需更换回调。
命令解析器与执行框架(at_char_cmds.c)
命令层建立在两个核心数据结构之上:
str_info_t:字符串描述,包含str_id、str(字符串指针)、str_len(长度,由宏INPUT_STR_INFO(id,string)以sizeof(string)-1自动计算)。at_param_t:解析出的参数节点,包含len(参数长度)、data[](参数内容)、next_offset(下一个参数在parse_buffer中的偏移),通过宏AT_PARAM_NEXT_P(a)取下一个参数。
解析器在全局静态缓冲 parse_buffer[PARSE_BUFFER_SIZE](NOT_KEEP_RAM,4 字节对齐)中原地切分参数:
parse_param_split(packet, split_char, end_char):从输入串中按split_char(逗号)分割参数、end_char(\r或?)终止;每段以\0结尾并记录next_offset,返回链表式参数序列。若输入以end_char开头则直接返回 NULL(无参数)。compara_specialchar(packet):扫描特殊字符集合specialchar[] = {'+', '>', '=', '?', '\r', ','},返回"应参与匹配的长度"——遇到+或>返回 3,遇到其余特殊字符返回当前下标。这样AT+NAME会按 3 字节头部 + 命令体分离,避免把=后面的参数值卷入命令名匹配。at_check_match_string(packet, size, str_table, table_size):遍历命令表,仅当命令表项长度与compara_specialchar计算结果一致且memcmp匹配时命中,返回对应str_info_t,否则返回 NULL。
命令表 at_cmd_str_table[] 由 INPUT_STR_INFO 宏逐条构建,覆盖查询、BLE 控制、电源管理三大类(详见下节)。所有命令处理完成后统一通过 at_cmd_send() 发送响应:
void at_cmd_send(uint8_t *packet, int size)
{
log_info("###at_cmd_send(%d):", size);
log_info_hexdump(packet, size);
ct_uart_send_packet(packet, size);
}
at_cmd_send 是模组所有响应(OK / ERR / 查询结果 / 透传数据)的统一出口,最终落到 ct_uart_send_packet() 走 UART 发出。
命令集
命令表 at_cmd_str_table[] 按字符串 ID(STR_ID_*)注册了 18 条命令,头部固定为 AT+(STR_ID_HEAD_AT_CMD)或 AT>(STR_ID_HEAD_AT_CHL),命令以 \r\n 结束,响应为 OK 或 ERR:
| 字符串 ID | 命令 | 分类 | 功能 |
|---|---|---|---|
STR_ID_GVER | AT+GVER | 查询 | 获取固件版本 |
STR_ID_GCFGVER | AT+GCFGVER | 查询 | 获取配置版本 |
STR_ID_NAME | AT+NAME | 查询/设置 | 读取或修改设备名称 |
STR_ID_LBDADDR | AT+LBDADDR | 查询 | 读取本地蓝牙地址 |
STR_ID_BAUD | AT+BAUD | 设置 | 修改 UART 波特率(默认 TCFG_AT_UART_BAUDRATE) |
STR_ID_ADV | AT+ADV | BLE 控制 | 开启/关闭广播 |
STR_ID_ADVPARAM | AT+ADVPARAM | BLE 控制 | 配置广播参数 |
STR_ID_ADVDATA | AT+ADVDATA | BLE 控制 | 配置广播数据 |
STR_ID_SRDATA | AT+SRDATA | BLE 控制 | 配置扫描响应数据 |
STR_ID_CONNPARAM | AT+CONNPARAM | BLE 控制 | 配置连接参数 |
STR_ID_SCAN | AT+SCAN | BLE 控制 | 启动/停止扫描 |
STR_ID_TARGETUUID | AT+TARGETUUID | BLE 控制 | 设置目标服务 UUID |
STR_ID_CONN | AT+CONN | BLE 控制 | 主动连接目标设备 |
STR_ID_DISC | AT+DISC | BLE 控制 | 断开当前连接 |
STR_ID_OTA | AT+OTA | 升级 | 进入 OTA 升级流程 |
STR_ID_CONN_CANNEL | AT+CONN_CANNEL | BLE 控制 | 取消正在进行的连接 |
STR_ID_POWER_OFF | AT+POWEROFF | 电源 | 关机 |
STR_ID_LOW_POWER | AT+LOWPOWER | 电源 | 进入/退出低功耗模式 |
命令名匹配采用"特殊字符截断"策略:compara_specialchar 遇到 = 或 ? 即停止匹配,因此 AT+NAME=xxx 与 AT+NAME? 可复用同一条表项,参数值由后续解析流程处理。
核心数据流
一条完整命令的生命周期如下:
sequenceDiagram
participant Host as 上位机
participant Uart as at_char_uart.c<br/>RX 中断
participant Cmd as at_char_cmds.c<br/>解析器
participant BLE as BLE 通道<br/>(client/server)
participant Peer as 对端 BLE 设备
Host->>Uart: "AT+CONN=AA:BB:...\\r\\n"
activate Uart
Uart->>Uart: wdt_clear / 溢出检查<br/>memcmp "AT>" 通道切换
Uart->>Cmd: at_uart_handler_callback(data,len)
deactivate Uart
activate Cmd
Cmd->>Cmd: compara_specialchar 截断<br/>at_check_match_string 匹配 CONN
Cmd->>Cmd: parse_param_split 切分参数<br/>hex 转换 / 校验
Cmd->>BLE: 发起连接 (cid 0-7)
BLE->>Peer: LE Create Connection
Peer-->>BLE: 连接建立/失败
BLE-->>Cmd: 结果回调
Cmd->>Uart: at_cmd_send("OK"/"ERR")
deactivate Cmd
Uart->>Host: "OK\\r\\n"
流程要点: 解析是纯字符串操作,不做协议栈调用;命令执行通过表项 ID 分支到具体函数。连接类命令(CONN)发起后立即返回,结果通过 BLE 事件回调异步到达,再以 OK/ERR 回送,体现了"同步解析、异步执行"的设计。透传路径则完全绕过解析器:当 cur_atcom_cid 被设置为 0-8 时,UART 收到的数据经同一回调直接发往 BLE 对端,BLE 收到的数据也直接经 UART 输出,形成双向透明管道。
使用示例
以下示例均提取自模组实际源码,展示关键用法。
命令表注册与通道常量
命令表使用 INPUT_STR_INFO 宏注册,通道编号约定见注释(0-7 主机通道、8 从机通道、9 AT 指令通道):
#define INPUT_STR_INFO(id,string) {.str_id = id, .str = string, .str_len = sizeof(string)-1,}
#define AT_STRING_SEND(a) at_cmd_send((uint8_t *)a,strlen(a))
#define AT_PARAM_NEXT_P(a) (at_param_t*)&parse_buffer[a->next_offset]
//0-7:主机通道(主动连出通道); 8:从机通道; 9:AT指令通道
uint8_t cur_atcom_cid = 9;
static const str_info_t at_cmd_str_table[] = {
INPUT_STR_INFO(STR_ID_GVER, at_str_gver),
INPUT_STR_INFO(STR_ID_GCFGVER, at_str_gcfgver),
INPUT_STR_INFO(STR_ID_NAME, at_str_name),
INPUT_STR_INFO(STR_ID_LBDADDR, at_str_lbdaddr),
INPUT_STR_INFO(STR_ID_BAUD, at_str_baud),
INPUT_STR_INFO(STR_ID_ADV, at_str_adv),
INPUT_STR_INFO(STR_ID_ADVPARAM, at_str_advparam),
INPUT_STR_INFO(STR_ID_SCAN, at_str_scan),
INPUT_STR_INFO(STR_ID_CONN, at_str_conn),
INPUT_STR_INFO(STR_ID_DISC, at_str_disc),
INPUT_STR_INFO(STR_ID_OTA, at_str_ota),
INPUT_STR_INFO(STR_ID_POWER_OFF, at_str_power_off),
INPUT_STR_INFO(STR_ID_LOW_POWER, at_str_lowpower),
};
Source: at_char_cmds.c
参数切分解析
parse_param_split 在 parse_buffer 中原地切分参数,next_offset 将多个参数串成链表:
static at_param_t *parse_param_split(const uint8_t *packet, uint8_t split_char, uint8_t end_char)
{
uint8_t char1;
int i = 0;
at_param_t *par = (at_param_t *)parse_buffer;
if (*packet == end_char) {
return NULL;
}
par->len = 0;
while (1) {
char1 = packet[i++];
if (char1 == end_char) {
par->data[par->len] = 0;
par->next_offset = 0;
break;
} else if (char1 == split_char) {
par->data[par->len] = 0;
par->len++;
par->next_offset = &par->data[par->len] - parse_buffer;
//init next par
par = (at_param_t *)&par->data[par->len];
par->len = 0;
} else {
par->data[par->len++] = char1;
}
if (&par->data[par->len] - parse_buffer >= PARSE_BUFFER_SIZE) {
log_error("parse_buffer over");
par->next_offset = 0;
break;
}
}
return (void *)parse_buffer;
}
Source: at_char_cmds.c
UART 接收与通道切换
at_cmd_rx_handler 是数据分发中枢:AT> 前缀强制回到命令模式,cur_atcom_cid < 9 时数据直接透传:
static void at_cmd_rx_handler(void)
{
static uint8_t clear_dog_flag = 0;
clear_dog_flag++;
if (clear_dog_flag > WDT_CLEAR_TIMS) {
clear_dog_flag = 0;
wdt_clear();
}
s32 data_length = uart_get_recv_len(ct_uart_num);
log_info("rx[%d]", data_length);
uart_recv_bytes(ct_uart_num, pRxBuffer_static, data_length);
uint8_t *p_data = pRxBuffer_static;
if (data_length > UART_RX_SIZE) {
log_error("cmd overflow");
data_length = 0;
goto __cmd_rx_end;
}
if (data_length > 3 && 0 == memcmp(at_change_channel_cmd, p_data, 3)) {
cur_atcom_cid += 9; //收到AT>命令后, 强行进入解析
goto check_at_cmd;
}
if (cur_atcom_cid < 9) {
log_info("rx_data[%d]:", data_length, p_data);
if (at_uart_handler_callback) {
at_uart_handler_callback(p_data, data_length);
}
data_length = 0;
goto __cmd_rx_end;
}
...
}
Source: at_char_uart.c
软关机流程
atchar_set_soft_poweroff 先主动断开蓝牙链路,再按低功耗模式决定等待策略:
void atchar_set_soft_poweroff()
{
log_info("set_soft_poweroff\n");
#if (TCFG_LOWPOWER_PATTERN == SOFT_MODE)
atchar_app_is_active = 1;
#endif
//必须先主动断开蓝牙链路,否则要等链路超时断开
btstack_ble_exit(0);
//延时,确保BT退出链路断开
if (ble_comm_dev_is_connected(GATT_ROLE_SERVER) || ble_comm_dev_is_connected(GATT_ROLE_CLIENT)) {
#if (TCFG_LOWPOWER_PATTERN == SOFT_MODE)
//soft 方式非必须等链路断开
sys_timeout_add(NULL, app_power_set_soft_poweroff, WAIT_DISCONN_TIME_MS);
#elif (TCFG_LOWPOWER_PATTERN == SOFT_BY_POWER_MODE)
//must wait disconn
app_power_soft.wait_disconn = 1;
#endif
} else {
app_power_set_soft_poweroff(NULL);
}
}
Source: app_at_char_com.c
配置选项
| 配置项 | 类型 | 默认来源 | 说明 |
|---|---|---|---|
CONFIG_APP_AT_CHAR_COM | 宏开关 | app_config.h | 使能/禁用整个 AT 命令模组 |
TCFG_AT_UART_BAUDRATE | u32 | app_config.h | UART 波特率,uart_baud 初值 |
PARSE_BUFFER_SIZE | 宏 | at_char_cmds.h | 命令解析缓冲区大小,决定单条命令参数总量上限 |
UART_RX_SIZE | 宏 | at_char_uart.h | UART 接收缓冲区大小,超限整包丢弃 |
UART_CBUF_SIZE | 宏 | 配置头 | 流量控制下的环形缓冲容量(FLOW_CONTROL 时启用) |
FLOW_CONTROL | 宏 | 配置头 | 是否启用 UART 硬件流控(RTS/CTS) |
WDT_CLEAR_TIMS | 宏 | 配置头 | 接收中断中喂狗的间隔次数 |
TCFG_LOWPOWER_PATTERN | 枚举 | app_config.h | SOFT_MODE / SOFT_BY_POWER_MODE 等低功耗策略 |
WAIT_DISCONN_TIME_MS | 宏 | app_at_char_com.h | SOFT_MODE 下等待链路断开后关机的延时 |
故障模式、边界情况与并发
缓冲区溢出
- 命令溢出:
data_length > UART_RX_SIZE时整包丢弃并打印cmd overflow——接收缓冲是静态分配的,宁可丢包也不越界写内存。 - 解析缓冲溢出:
parse_param_split每写入一个字节都检查&par->data[par->len] - parse_buffer >= PARSE_BUFFER_SIZE,超限即终止并置next_offset = 0,防止参数拼接破坏parse_buffer尾部。 - 流控溢出:启用
FLOW_CONTROL后,若uart_get_recv_len超过UART_CBUF_SIZE会打印uart overflow, Data loss!!!,随后在收尾处调用uart1_flow_ctl_rts_resume()恢复 RTS,属于"降速止损"策略。
行尾与匹配边界
- 命令必须以
\r结尾:代码容忍两种形式——最后一个字节是\r,或倒数第二个字节是\r(末尾多一个字节时自动data_length--)。不符合行尾规则的数据不进入解析,直接丢弃,避免半包被误解析。 compara_specialchar只对+/>返回固定长度 3、对其他特殊字符返回首次出现位置,因此形如AT+NAME与AT+NAME=value能正确区分命令名与参数;但若命令名本身包含=/?/,等字符则无法注册,这是该简单匹配器的设计约束。cur_atcom_cid无符号累加:AT>触发cur_atcom_cid += 9,若连续收到多条AT>会不断累加,仅当值< 9时进入透传分支,因此AT>路径下始终满足解析条件;透传模式的恢复依赖上层命令(如连接建立/断开回调)把cur_atcom_cid重新设置为 0-8 或 9。
并发与中断上下文
- UART 接收处理运行在中断/回调上下文,命令执行与 BLE 事件回调可能异步交错;模组通过单一静态缓冲 + 顺序处理规避竞争——
pRxBuffer_static与parse_buffer均在处理函数内同步使用,且每个处理周期结束后data_length归零。 - 软关机路径显式处理了"关机 vs 链路仍连接"的竞态:先
btstack_ble_exit(0)主动断链,SOFT_MODE下用sys_timeout_add延时再关机,SOFT_BY_POWER_MODE下置app_power_soft.wait_disconn = 1等待断开事件,避免直接断电导致链路残留。 - 接收中断中周期性
wdt_clear()防止长数据流(例如大块透传数据)触发看门狗复位,但喂狗计数是静态变量,若中断长期不触发也不会误喂。
扩展点
- 新增命令:在
at_cmd_str_table[]中追加一条INPUT_STR_INFO(新ID, "CMD"),并在解析分发处按新STR_ID_*增加处理分支即可;命令字符串与头文件中的 ID 枚举需保持一致。 - 新增通道:UART 层只依赖
at_uart_handler_callback单一回调与cur_atcom_cid通道号约定(<9 透传、>=9 解析),新增一条 BLE/串口通道只需实现"设置回调 + 修改通道号"两个动作,无需改动分发骨架。 - 响应格式定制:所有输出汇聚于
at_cmd_send(),可在此统一增删日志或修改回送格式(如增加校验、分包)。
性能与运维提示
- 接收与解析全程零动态内存分配(静态缓冲 + 原地切分),适合低内存 BLE 模组;代价是单包大小受
UART_RX_SIZE/PARSE_BUFFER_SIZE硬限制,大块透传建议分片。 NOT_KEEP_RAM修饰的缓冲在休眠时可被释放/复用,配合TCFG_LOWPOWER_PATTERN实现低功耗;调试日志通过log_config.c中AT_CMD标签单独开关,量产固件可关闭以减少开销。AT+BAUD修改的是运行时uart_baud,如需掉电保存应确认上层是否同步写入用户配置区(user_cfg.h体系)。
Related Links
- at_char_cmds.c(命令表与解析器)
- at_char_uart.c(UART 接收分发)
- app_at_char_com.c(应用入口与电源管理)
- ble_at_char_client.c(BLE 主机透传通道)
- ble_at_char_server.c(BLE 从机透传通道)
- log_config.c(AT_CMD 日志开关)
- 相关目录页:BLE 从机服务 / OTA 升级 / 低功耗管理(详见各 catalog 子页)