杰理 SDK 文档中心
首页
首页
  • 概述与快速入门

    • 芯片平台与 SDK 概述
    • 环境搭建与编译工具链
    • 快速开始:选型、编译与烧录
    • 烧录与量产工具
  • 构建系统与板级工程

    • 顶层 Makefile 与编译目标
    • 板级工程与配置
    • 后处理与配置工具
  • HID 人机交互应用

    • HID 应用架构总览
    • 键盘、翻页器与遥控应用
    • 鼠标应用:单模、双模与低延迟
    • 空闲应用与初始化流程
  • BLE 透传与数传应用

    • 透传应用总览
    • 多连接与无连接传输
    • AT 命令模组应用
    • Dongle 适配器应用
  • BSP 公共模块

    • 蓝牙公共处理
    • 按键、LED 与红外
    • 传感器与编码器
    • 存储、VM 与文件系统
    • 电源管理与低功耗
    • 消息调度与通信外设
  • 协议栈与预编译库

    • 蓝牙协议栈库
    • 设备驱动与文件系统库
    • 音频、升级与其他库
  • 开发资料与补丁发布

    • 文档资料中心
    • 版本补丁与兼容性修复

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.cAT 命令表、参数解析、命令执行、响应发送[AT_CHAR_CMD]
at_char_uart.cUART 初始化、接收中断处理、命令/透传分发[at_char_uart]
ble_at_char_client.cBLE 主机侧透传通道(连接对端、收发数据)—
ble_at_char_server.cBLE 从机侧透传通道(被连接、收发数据)—
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 收到数据时被调用,执行以下步骤:

  1. 喂狗:维护一个计数 clear_dog_flag,每收满 WDT_CLEAR_TIMS 次即调用 wdt_clear(),防止长数据流传输期间看门狗误复位。
  2. 读取数据:通过 uart_get_recv_len(ct_uart_num) 获取本次接收长度,再 uart_recv_bytes() 拷贝到静态接收缓冲 pRxBuffer_static[UART_RX_SIZE](NOT_KEEP_RAM,4 字节对齐)。
  3. 溢出保护:data_length > UART_RX_SIZE 时丢弃整包并打印 cmd overflow;启用 FLOW_CONTROL 时还会检测环形缓冲溢出并打印 uart overflow, Data loss!!!。
  4. 通道切换检测:若数据长度大于 3 且以 at_change_channel_cmd[] = "AT>" 开头,执行 cur_atcom_cid += 9 强制进入命令解析模式。
  5. 模式分发:
    • cur_atcom_cid < 9(已绑定到 0-7 主机通道或 8 从机通道)→ 数据原样交给 at_uart_handler_callback 透传,不解析。
    • 否则按命令处理:要求数据以 \r 结尾(容忍最后一个字节为 \r 或倒数第二个字节为 \r 的两种行尾形式),补齐 \0 后交给回调。
  6. 流量控制恢复: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_GVERAT+GVER查询获取固件版本
STR_ID_GCFGVERAT+GCFGVER查询获取配置版本
STR_ID_NAMEAT+NAME查询/设置读取或修改设备名称
STR_ID_LBDADDRAT+LBDADDR查询读取本地蓝牙地址
STR_ID_BAUDAT+BAUD设置修改 UART 波特率(默认 TCFG_AT_UART_BAUDRATE)
STR_ID_ADVAT+ADVBLE 控制开启/关闭广播
STR_ID_ADVPARAMAT+ADVPARAMBLE 控制配置广播参数
STR_ID_ADVDATAAT+ADVDATABLE 控制配置广播数据
STR_ID_SRDATAAT+SRDATABLE 控制配置扫描响应数据
STR_ID_CONNPARAMAT+CONNPARAMBLE 控制配置连接参数
STR_ID_SCANAT+SCANBLE 控制启动/停止扫描
STR_ID_TARGETUUIDAT+TARGETUUIDBLE 控制设置目标服务 UUID
STR_ID_CONNAT+CONNBLE 控制主动连接目标设备
STR_ID_DISCAT+DISCBLE 控制断开当前连接
STR_ID_OTAAT+OTA升级进入 OTA 升级流程
STR_ID_CONN_CANNELAT+CONN_CANNELBLE 控制取消正在进行的连接
STR_ID_POWER_OFFAT+POWEROFF电源关机
STR_ID_LOW_POWERAT+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_BAUDRATEu32app_config.hUART 波特率,uart_baud 初值
PARSE_BUFFER_SIZE宏at_char_cmds.h命令解析缓冲区大小,决定单条命令参数总量上限
UART_RX_SIZE宏at_char_uart.hUART 接收缓冲区大小,超限整包丢弃
UART_CBUF_SIZE宏配置头流量控制下的环形缓冲容量(FLOW_CONTROL 时启用)
FLOW_CONTROL宏配置头是否启用 UART 硬件流控(RTS/CTS)
WDT_CLEAR_TIMS宏配置头接收中断中喂狗的间隔次数
TCFG_LOWPOWER_PATTERN枚举app_config.hSOFT_MODE / SOFT_BY_POWER_MODE 等低功耗策略
WAIT_DISCONN_TIME_MS宏app_at_char_com.hSOFT_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 子页)
Prev
多连接与无连接传输
Next
Dongle 适配器应用