AT 指令串口协议
本文档介绍 fw-AC630N_BT_SDK 中基于 AT 指令的串口控制协议实现:经典蓝牙 SPP(串口仿真 Profile)与 BLE GATT 两条数据通道如何承载 AT 指令流,以及上层如何通过事件回调与 edr_at_* 系列 API 控制蓝牙状态。
Purpose and Scope
本页覆盖 AT 指令协议在 SDK 中的完整实现机制,包括:
- AT 指令传输载体:经典蓝牙 SPP 通道(
spp_at_com.c)与 BLE GATT 通道(le_at_com.c); - AT 指令框架:两个模块共同依赖的
at.h解析框架; - 事件回调机制:
edr_at_register_event_cbk/edr_at_send_event如何把蓝牙状态变化与数据接收上报给应用层; - 上层控制 API:
spp_at_com.h暴露的地址、名称、可见性、配对、断开、SPP 透传等接口; - 编译开关与配置项:
TRANS_AT_COM、USER_SUPPORT_PROFILE_SPP、DEF_BLE_DEMO_AT_COM等。
以下内容属于其他页面,不在本页展开:RCSP(JL 私有双端控制协议)、sig_mesh 组网、音频链路与编解码、升级(OTA)流程。若需了解这些能力,请参考对应目录(apps/common/third_party_profile/jieli/JL_rcsp/、apps/common/third_party_profile/sig_mesh/、apps/common/update/)的独立文档。
Overview
协议设计意图
在嵌入式蓝牙耳机/音箱等设备上,外部主机(如手机 APP、测试工装、上位机)需要一种简单、稳定、与具体 Profile 解耦的方式来控制设备行为。AT 指令协议正是为此设计:主机通过蓝牙虚拟串口(SPP,经典蓝牙)或 GATT 特性(BLE)发送形如 AT+XXX 的文本指令,设备端解析后执行,并把结果/事件以文本或二进制形式回传。
这种设计带来三个关键收益:
- 协议可读性强:AT 指令是行业通用格式(源自 Modem 控制),调试、抓包、人工验证都方便;
- 传输通道可替换:指令语义与承载通道解耦,同一套
at.h解析框架既可跑在 SPP 上,也可跑在 BLE GATT 上; - 事件上报统一:设备主动上报(连接建立、断开、数据到达)通过单一事件回调出口
at_send_event_callbak汇聚,应用层只需注册一次回调。
两个传输载体
| 载体 | 实现文件 | 使能条件 | 说明 |
|---|---|---|---|
| 经典蓝牙 SPP | apps/common/third_party_profile/jieli/spp_at_com.c | TRANS_AT_COM 且 USER_SUPPORT_PROFILE_SPP==1 | 蓝牙串口仿真 Profile,主机侧表现为虚拟 COM 口 |
| BLE GATT | apps/common/third_party_profile/jieli/le_at_com.c | TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_AT_COM | 通过自定义 GATT 服务特性收发 AT 数据 |
关键术语
- AT 指令:以
AT开头、\r\n结尾的文本命令,如AT+NAME=abc; - 事件(Event):设备主动上报的异步消息,如
AT_EVT_BT_CONNECTED、AT_EVT_SPP_DATA_RECEIVED; - edr_at_*:EDR(Enhanced Data Rate,经典蓝牙)侧的 AT 控制 API 前缀;
- TRANS_AT_COM:SDK 中"AT 指令透传模式"的总开关宏。
Architecture
下图展示 AT 指令协议的整体架构:主机通过两条蓝牙通道进入 SDK,AT 模块解析后驱动应用逻辑,并把事件回传给上层。
flowchart TD
subgraph sg_Host["上位机 / 手机 APP"]
Host["AT 指令发送端"]
end
subgraph sg_BT["蓝牙协议栈 (BTStack)"]
SPP["SPP Profile<br/>经典蓝牙串口"]
GATT["BLE GATT<br/>DEF_BLE_DEMO_AT_COM"]
end
subgraph sg_AT["AT 指令层"]
SPP_AT["spp_at_com.c<br/>EDR AT 服务"]
LE_AT["le_at_com.c<br/>LE AT 服务"]
Parser["at.h 解析框架"]
end
subgraph sg_App["应用层"]
EventCbk["事件回调<br/>at_send_event_callbak"]
AppLogic["应用逻辑<br/>连接 / 配对 / 名称 / 透传"]
end
Host -->|"AT 指令流 (SPP)"| SPP
Host -->|"AT 指令流 (BLE)"| GATT
SPP -->|"at_spp_recieve_cbk"| SPP_AT
GATT -->|"GATT RX"| LE_AT
SPP_AT --> Parser
LE_AT --> Parser
SPP_AT -->|"AT_EVT_* 事件"| EventCbk
LE_AT -->|"AT_EVT_* 事件"| EventCbk
EventCbk --> AppLogic
AppLogic -->|"edr_at_set_name / at_spp_send_data 等"| SPP_AT
架构说明
- **
spp_at_com.c(EDR AT 服务)**是经典蓝牙侧的核心:持有spp_operation_t *spp_api句柄(spp_at_com.c L37),通过它完成数据收发与状态感知;SPP 连接状态与数据到达都会转换为 AT 事件上报。 - **
le_at_com.c(LE AT 服务)**是 BLE 侧的等价物:仅当 BLE demo 被配置为DEF_BLE_DEMO_AT_COM时编译(le_at_com.c L48),其 GATT 服务通过固定 handle(如ATT_CHARACTERISTIC_ae02_01_VALUE_HANDLE)收发 AT 数据。 - 事件回调汇聚:两个模块共用同一个静态函数指针
at_send_event_callbak(spp_at_com.c L40),由edr_at_register_event_cbk注册,应用层据此统一接收AT_EVT_*事件。 - 应用层反向控制:上层执行 AT 指令后,通过
spp_at_com.h暴露的edr_at_set_name、edr_at_set_address、at_spp_send_data等 API 写回蓝牙栈,形成闭环。
核心实现解析
1. 编译开关与模块边界
spp_at_com.c 的整体编译受双重宏控制,缺一不可:
#if TRANS_AT_COM
#if (USER_SUPPORT_PROFILE_SPP==1)
TRANS_AT_COM是 AT 指令透传模式的总开关,需要在app_config.h中全局使能;USER_SUPPORT_PROFILE_SPP==1保证经典蓝牙的 SPP Profile 已注册。
这种双层守卫的设计意图是:即使 SDK 集成了 AT 代码,只要产品不需要该功能,编译器会直接裁掉整段实现,不占用任何 RAM/Flash。对应的 BLE 侧则通过 TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_AT_COM(le_at_com.c L48)选择 BLE demo 类型,使 GATT 服务按 AT 透传模式初始化。
2. EDR AT 服务:全局状态与依赖注入
模块顶部维护三份核心状态(spp_at_com.c L37-L40):
static struct spp_operation_t *spp_api = NULL; // SPP 操作句柄(依赖注入)
static u8 spp_state; // SPP 内部状态
static u8 bt_edr_status; // EDR 蓝牙状态位图(ST_BIT_*)
static void (*at_send_event_callbak)(u8 event_type, const u8 *packet, int size);
spp_api由 SPP Profile 初始化时注入,模块内部不直接调用 btstack 底层 API,而是统一走spp_operation_t接口——这是典型的依赖倒置:便于在无 SPP 环境(例如纯 BLE 产品)下裁剪,也便于替换为其他传输层做单元测试;bt_edr_status是位图式状态机,ST_BIT_SPP_CONN等位标记连接状态,edr_at_get_staus()直接返回它,供上层轮询;at_send_event_callbak是唯一的事件出口,通过edr_at_register_event_cbk注册,类型为裸函数指针,事件用(event_type, packet, size)三元组传递。
3. 数据发送路径:at_spp_send_data
int at_spp_send_data(u8 *data, u16 len)
{
if (spp_api) {
bt_sniff_ready_clean();
log_info("spp_api_tx(%d) \n", len);
/* log_info_hexdump(data, len); */
return spp_api->send_data(NULL, data, len);
}
return SPP_USER_ERR_SEND_FAIL;
}
要点:
- 发送前清理 sniff 状态:调用
bt_sniff_ready_clean(),避免设备处于低功耗 sniff 模式时数据发送延迟或丢失——这是低功耗蓝牙音频设备上常见的坑,发送路径主动退出低功耗窗口,保证 AT 响应实时性; - 句柄为空时返回错误码
SPP_USER_ERR_SEND_FAIL,而不是空指针崩溃,属于防御式编程; - 配套的
at_spp_send_data_check(u16 len)(spp_at_com.c L64-L72)在发送前查询spp_api->busy_state(),供上层决定是否排队/丢弃,防止协议栈缓冲溢出。
4. 数据接收路径:at_spp_recieve_cbk
static void at_spp_recieve_cbk(void *priv, u8 *buf, u16 len)
{
log_info("spp_api_rx(%d) \n", len);
log_info_hexdump(buf, len);
#if TEST_SPP_DATA_RATE
if ((buf[0] == 'A') && (buf[1] == 'F')) {
spp_test_start = 1;//start
} else if ((buf[0] == 'A') && (buf[1] == 'A')) {
spp_test_start = 0;//stop
}
#endif
//loop send data for test
/* if (at_spp_send_data_check(len)) { */
/* at_spp_send_data(buf, len); */
/* } */
edr_at_send_event(AT_EVT_SPP_DATA_RECEIVED, buf, len);
}
要点:
- 收到 SPP 数据后原样转发为
AT_EVT_SPP_DATA_RECEIVED事件,不做任何字节级解析——真正的 AT 指令解析由应用层回调里的at.h框架完成。这种"传输层与协议层分离"的设计让 AT 指令集可以自由增删而不动传输代码; TEST_SPP_DATA_RATE数据率测试分支用AF/AA两个魔术前缀控制测试启停,属于内嵌的自测能力,默认关闭;- 被注释掉的回环测试代码揭示了作者曾经的调试手段(收到即回发),保留注释便于回归。
5. 状态回调与事件映射
static void at_spp_state_cbk(u8 state)
{
switch (state) {
case SPP_USER_ST_CONNECT:
log_info("SPP_USER_ST_CONNECT ~~~\n");
bt_edr_status |= BIT(ST_BIT_SPP_CONN);
edr_at_send_event(AT_EVT_BT_CONNECTED, NULL, 0);
break;
case SPP_USER_ST_DISCONN:
log_info("SPP_USER_ST_DISCONN ~~~\n");
bt_edr_status &= ~BIT(ST_BIT_SPP_CONN);
edr_at_send_event(AT_EVT_BT_DISCONNECTED, NULL, 0);
break;
default:
break;
}
}
设计要点:
- SPP 连接状态 → 位图 + 事件双路通知:
bt_edr_status供同步轮询(edr_at_get_staus()),AT_EVT_BT_CONNECTED/DISCONNECTED供异步事件驱动,两种消费方式并存; - 状态回调在 btstack 的协议栈上下文中执行,事件通过回调同步抛出,应用层回调实现必须保持轻量,避免阻塞蓝牙协议栈线程。
6. LE AT 服务(BLE GATT 通道)
le_at_com.c 是 BLE 侧的对应实现,其 GATT 服务按固定 handle 定义收发端点:
#define TEST_SEND_HANDLE_VAL ATT_CHARACTERISTIC_ae02_01_VALUE_HANDLE
/* #define TEST_SEND_HANDLE_VAL ATT_CHARACTERISTIC_ae05_01_VALUE_HANDLE */
#define EXT_ADV_MODE_EN 0
#define TEST_AUDIO_DATA_UPLOAD 0 //测试文件上传
GATT 收发缓冲配置(le_at_com.c L78-L80):
#define ATT_LOCAL_PAYLOAD_SIZE (200) //note: need >= 20
#define ATT_SEND_CBUF_SIZE (512) //note: need >= 20,缓存大小,可修改
#define ATT_RAM_BUFSIZE (ATT_CTRL_BLOCK_SIZE + ATT_LOCAL_PAYLOAD_SIZE + ATT_SEND_CBUF_SIZE)
ATT_LOCAL_PAYLOAD_SIZE=200:单包 GATT 载荷上限(≥20 是 BLE 4.x 默认 MTU 的最低要求,200 表明该 SDK 支持扩展 MTU/长包);ATT_SEND_CBUF_SIZE=512:发送环形缓冲,吸收对端读取速度波动,注释明确提示"可修改"——这是典型的资源-吞吐权衡点;- 整个 RAM 占用由
ATT_CTRL_BLOCK_SIZE+ 载荷 + 发送缓存三部分叠加计算,便于预估内存。
7. 事件类型与状态机
状态迁移与事件映射如下:
stateDiagram-v2
[*] --> Idle
Idle --> Connected: SPP_USER_ST_CONNECT<br/>bt_edr_status \|= BIT(ST_BIT_SPP_CONN)<br/>AT_EVT_BT_CONNECTED
Connected --> Idle: SPP_USER_ST_DISCONN<br/>bt_edr_status &= ~BIT(ST_BIT_SPP_CONN)<br/>AT_EVT_BT_DISCONNECTED
Connected --> Connected: 数据到达 AT_EVT_SPP_DATA_RECEIVED
Connected --> Connected: 数据发送 at_spp_send_data
事件常量(AT_EVT_*)与 ST_BIT_* 位定义由 at.h / bt_common.h 提供,事件在 edr_at_send_event 中统一转发给已注册的应用回调(spp_at_com.c L50)。
核心流程
端到端指令交互时序
以下时序图展示一条典型 AT 指令(例如"查询/设置设备名称")从主机发出到响应回传的完整路径:
sequenceDiagram
participant H as 上位机 (Host)
participant S as SPP 协议栈
participant A as spp_at_com.c
participant C as 事件回调 at_send_event_callbak
participant P as 应用层 (at.h 解析)
Note over H,S: 1. 建立连接
H->>S: 经典蓝牙 SPP 连接
S->>A: at_spp_state_cbk(SPP_USER_ST_CONNECT)
A->>A: bt_edr_status \|= BIT(ST_BIT_SPP_CONN)
A->>C: edr_at_send_event(AT_EVT_BT_CONNECTED, NULL, 0)
C->>P: 通知上层"已连接"
Note over H,P: 2. 发送 AT 指令
H->>S: 发送 "AT+NAME=MyDevice\r\n"
S->>A: at_spp_recieve_cbk(buf, len)
A->>C: edr_at_send_event(AT_EVT_SPP_DATA_RECEIVED, buf, len)
C->>P: at.h 解析指令并执行
Note over P,S: 3. 执行与响应
P->>A: edr_at_set_name(name, len) / at_spp_send_data(resp, len)
A->>A: bt_sniff_ready_clean()
A->>S: spp_api->send_data(NULL, resp, len)
S-->>H: 响应数据
Note over H,S: 4. 断开连接
H->>S: 断开 SPP 连接
S->>A: at_spp_state_cbk(SPP_USER_ST_DISCONN)
A->>A: bt_edr_status &= ~BIT(ST_BIT_SPP_CONN)
A->>C: edr_at_send_event(AT_EVT_BT_DISCONNECTED, NULL, 0)
C->>P: 通知上层"已断开"
流程要点
- 连接阶段:协议栈把连接/断开事件通过
at_spp_state_cbk汇入 AT 模块,模块同步更新bt_edr_status位图并抛事件。应用层若关心连接状态,可注册回调或轮询edr_at_get_staus(); - 指令阶段:SPP 收到的原始字节流不做任何解析地包装成
AT_EVT_SPP_DATA_RECEIVED事件。解析责任完全落在应用层回调中的at.h框架——这是"薄传输层 + 厚协议层"的典型分层; - 响应阶段:响应文本通过
at_spp_send_data回发。发送前调用bt_sniff_ready_clean()确保链路处于活跃状态;若协议栈忙(at_spp_send_data_check返回 0),上层需自行决定重试策略; - 断开阶段:与连接阶段对称,位图清除并抛
AT_EVT_BT_DISCONNECTED。应用层应在此事件中清理会话状态(例如丢弃未完成的指令队列)。
使用示例
示例 1:SPP 数据发送(AT 响应回传)
int at_spp_send_data(u8 *data, u16 len)
{
if (spp_api) {
bt_sniff_ready_clean();
log_info("spp_api_tx(%d) \n", len);
/* log_info_hexdump(data, len); */
return spp_api->send_data(NULL, data, len);
}
return SPP_USER_ERR_SEND_FAIL;
}
应用层解析完一条 AT 指令后,把响应文本(如 OK\r\n)以 u8* 缓冲区传入此函数即可回传给主机。注意返回值为 int:非零为成功,SPP_USER_ERR_SEND_FAIL 表示 spp_api 尚未初始化(SPP 未注册或未连接)。
示例 2:接收数据并上报事件
static void at_spp_recieve_cbk(void *priv, u8 *buf, u16 len)
{
log_info("spp_api_rx(%d) \n", len);
log_info_hexdump(buf, len);
...
edr_at_send_event(AT_EVT_SPP_DATA_RECEIVED, buf, len);
}
buf/len 是协议栈回调传入的原始数据,模块不拷贝、不解析,直接以事件形式抛给应用回调。应用回调中的 AT 解析框架据此识别 AT+... 命令字并分发。
示例 3:SPP 连接状态变化映射为 AT 事件
static void at_spp_state_cbk(u8 state)
{
switch (state) {
case SPP_USER_ST_CONNECT:
bt_edr_status |= BIT(ST_BIT_SPP_CONN);
edr_at_send_event(AT_EVT_BT_CONNECTED, NULL, 0);
break;
case SPP_USER_ST_DISCONN:
bt_edr_status &= ~BIT(ST_BIT_SPP_CONN);
edr_at_send_event(AT_EVT_BT_DISCONNECTED, NULL, 0);
break;
default:
break;
}
}
应用层可在回调中根据 AT_EVT_BT_CONNECTED 主动下发设备信息(如固件版本、设备名称),实现"连接即握手"的协议风格。
示例 4:向主机发送唤醒字符
static void at_spp_send_wakeup(void)
{
putchar('W');
}
当设备侧的 UART 调试/控制口需要被唤醒时,发送单字符 'W'。该函数体现了 AT 协议与物理串口的衔接点:BT 数据与 UART 调试口共用同一套指令语义。
配置选项
| 配置项 | 类型 | 默认值 | 作用域 | 说明 |
|---|---|---|---|---|
TRANS_AT_COM | 宏开关 | 0/1(由产品配置) | 全局 | AT 指令透传模式总开关,为 0 时 spp_at_com.c 整段代码被裁剪 |
USER_SUPPORT_PROFILE_SPP | 宏开关 | 1(SPP 产品) | 全局 | 是否注册经典蓝牙 SPP Profile,必须为 1 才编译 EDR AT 模块 |
TCFG_BLE_DEMO_SELECT | 枚举 | DEF_BLE_DEMO_AT_COM(AT 产品) | BLE demo | 选择 BLE demo 类型,等于 DEF_BLE_DEMO_AT_COM 时编译 LE AT 服务 |
ATT_LOCAL_PAYLOAD_SIZE | 宏 | 200(需 ≥20) | le_at_com.c | GATT 单包载荷上限,影响单条 AT 指令的最大分片长度 |
ATT_SEND_CBUF_SIZE | 宏 | 512(需 ≥20) | le_at_com.c | GATT 发送环形缓冲大小,注释标注"可修改",按吞吐需求调整 |
TEST_SPP_DATA_RATE | 宏 | 0 | spp_at_com.c | 数据率自测开关,开启后启用 SPP_TIMER_MS 定时统计 |
SPP_TIMER_MS | 宏 | 100 | spp_at_com.c | 仅测试模式:数据率统计定时器周期(ms) |
配置入口以各产品 app_config.h 为准;TRANS_AT_COM 与 TCFG_BLE_DEMO_SELECT 的具体取值定义于 SDK 的公共配置模板中,修改后需重新编译并确认裁剪结果符合预期(可查看编译日志中 spp_api_rx 等日志是否出现)。
API 参考
以下接口全部声明于 spp_at_com.h L7-L19,供应用层(AT 指令解析回调)调用。
int edr_at_set_address(u8 *addr)
设置经典蓝牙设备地址(BD_ADDR)。
参数: addr(u8*)— 6 字节蓝牙地址。 返回: int — 0 表示成功;非 0 表示失败(地址非法或底层写入失败)。
int edr_at_get_address(u8 *addr)
读取当前蓝牙地址。
参数: addr(u8*)— 输出缓冲区,至少 6 字节。 返回: int — 0 成功;非 0 失败。底层最终映射到 bt_get_mac_addr()(spp_at_com.c L44)。
int edr_at_set_name(u8 *name, u8 len)
设置蓝牙广播/查询名称。
参数:
name(u8*):新名称字符串;len(u8):名称长度。 返回:int— 0 成功;非 0 失败。底层调用bt_set_local_name()与lmp_hci_write_local_name()(spp_at_com.c L43/L47)。
int edr_at_get_name(u8 *name)
读取本地名称。
参数: name(u8*)— 输出缓冲区。 返回: int — 0 成功;非 0 失败。底层来自 bt_get_local_name()(spp_at_com.c L42)。
int edr_at_set_visibility(u8 inquiry_en, u8 page_scan_en)
设置设备可被发现(inquiry scan)与可连接(page scan)属性。
参数:
inquiry_en(u8):0=不可发现,非 0=可发现;page_scan_en(u8):0=不可连接,非 0=可连接。 返回:int— 0 成功;非 0 失败。
int edr_at_set_pair_mode(u8 mode)
设置配对模式(可配对/不可配对等)。
参数: mode(u8)— 配对模式枚举值,取值定义见 bt_common.h。 返回: int — 0 成功;非 0 失败。
int edr_at_set_pincode(u8 *pincode)
设置经典蓝牙 PIN 码(传统配对)。
参数: pincode(u8*)— PIN 码字符串。 返回: int — 0 成功;非 0 失败。
int edr_at_disconnect(void)
主动断开当前经典蓝牙(ACL/SPP)连接。
返回: int — 0 成功;非 0 表示当前无连接或断开失败。
int edr_at_set_cod(u8 *cod_data)
设置 Class of Device(设备类别,决定设备图标/服务类型)。
参数: cod_data(u8*)— COD 数据。底层调用 lmp_hci_write_class_of_device()(spp_at_com.c L48)。 返回: int — 0 成功;非 0 失败。
int edr_at_send_spp_data(u8 *data, u8 len)
通过 SPP 通道向主机发送数据(AT 响应)。
参数:
data(u8*):待发送数据;len(u8):数据长度。 返回:int— 0 成功;非 0 失败(如SPP_USER_ERR_SEND_FAIL)。
void edr_at_register_event_cbk(void *cbk)
注册 AT 事件回调。回调原型为 void (*)(u8 event_type, const u8 *packet, int size),事件类型包括 AT_EVT_BT_CONNECTED、AT_EVT_BT_DISCONNECTED、AT_EVT_SPP_DATA_RECEIVED 等。
参数: cbk(void*)— 回调函数指针,传 NULL 可注销。
void at_spp_init(void)
初始化 EDR AT 服务:注册 SPP 收包/状态回调、初始化内部状态位图。应在系统启动、SPP Profile 就绪后调用一次。
u8 edr_at_get_staus(void)
返回当前 EDR 蓝牙状态位图(ST_BIT_* 组合),供上层同步轮询。注意函数名为 get_staus(拼写沿用了 SDK 原始命名),非 get_status。
返回: u8 — 状态位图;BIT(ST_BIT_SPP_CONN) 置位表示 SPP 已连接。
失败模式、边界与并发
发送失败与忙碌处理
spp_api == NULL(SPP 未初始化/未注册)时at_spp_send_data直接返回SPP_USER_ERR_SEND_FAIL。上层 AT 响应逻辑必须处理该返回值,避免"指令已执行但无响应"的假死状态;- 协议栈发送缓冲忙时
at_spp_send_data_check()返回 0。此时继续发送会丢包或阻塞,推荐上层实现简单的重试/队列机制;代码中保留的回环测试注释即展示了"先 check 再 send"的用法。
低功耗竞态
发送路径调用 bt_sniff_ready_clean() 以退出 sniff 低功耗窗口。若在窗口外发送(或该函数未生效),可能出现首包延迟。竞态风险:edr_at_send_event 在协议栈上下文同步调用回调,若回调内部再触发 at_spp_send_data,属于同线程重入,一般安全;但回调若做阻塞操作(如 flash 擦写、长延时),会直接拖慢蓝牙协议栈。
数据边界
- SPP 侧对单帧长度没有额外拆分逻辑,超过底层 MTU 的数据由
spp_operation_t->send_data自行处理——AT 指令文本较短(通常 <64B)因此风险低,但二进制透传大数据块时需自行分帧; - BLE 侧由
ATT_LOCAL_PAYLOAD_SIZE(200B)限定单包载荷,超过需走发送缓冲(512B)排队。
并发模型
AT 模块没有独立线程:所有回调(at_spp_recieve_cbk、at_spp_state_cbk)均在 btstack 协议栈上下文执行,edr_at_* API 亦多在应用任务中调用。跨任务访问 bt_edr_status 位图属于读多写少,未加锁——若产品在多任务间同时读写,建议用原子读或临界区保护。
边界案例
- 未注册回调:
edr_at_send_event在at_send_event_callbak == NULL时应有空指针保护(实现见 spp_at_com.c L50 下方定义),接入方务必先edr_at_register_event_cbk再使能连接; - 重复连接/断连风暴:状态回调幂等性依赖
bt_edr_status位操作(|=/&=),重复事件不会破坏位图一致性; at_spp_send_wakeup的putchar('W'):在未接 UART 调试口或putchar被重定向的平台上可能产生副作用,裁剪时应一并移除。
性能与运维
- 吞吐:SPP 侧数据率由协议栈决定,模块内仅做透传,CPU 开销集中在
log_info_hexdump(调试打印,发布版建议关闭——spp_at_com.c顶部#if 1即日志总开关,spp_at_com.c L15-L22); - 内存:BLE 侧 AT 服务 RAM 占用为
ATT_CTRL_BLOCK_SIZE + ATT_LOCAL_PAYLOAD_SIZE + ATT_SEND_CBUF_SIZE(默认约 200+512+控制块)。调大ATT_SEND_CBUF_SIZE可提升大指令吞吐,但会增大 RAM 占用; - 日志:模块用
log_info/log_info_hexdump输出收发字节,配合printf_buf可抓取完整 AT 报文,是联调主机侧指令集的第一手段; - 自测:
TEST_SPP_DATA_RATE+AF/AA魔术指令可做回环吞吐测试,量产前建议跑一遍确认链路质量。
扩展点
- 新增 AT 指令:无需改动
spp_at_com.c——在应用层回调(edr_at_register_event_cbk注册的函数)中通过at.h框架注册新命令字,收到AT_EVT_SPP_DATA_RECEIVED后由框架分发执行,响应走at_spp_send_data/edr_at_send_spp_data回传; - 替换传输层:
spp_operation_t句柄抽象了收发与状态,理论上可注入其他面向连接的传输(如 UART、USB CDC)复用同一套 AT 语义;le_at_com.c已证明同一协议可跑在 GATT 上; - 扩展事件类型:
edr_at_send_event的event_type参数为u8,可在at.h的AT_EVT_*枚举基础上追加自定义事件,回调签名无需变更; - 发送策略定制:在
at_spp_send_data之上封装排队/重试层,配合at_spp_send_data_check实现背压(backpressure),可应对高速透传场景。
相关链接
- spp_at_com.h(EDR AT 接口声明)
- spp_at_com.c(EDR AT 服务实现)
- le_at_com.h(LE AT 接口声明)
- le_at_com.c(LE AT 服务实现)
- 相关源码:
at.h(AT 解析框架)、spp_trans_data.h/le_trans_data.h(透传数据 demo)、spp_user.h(SPP Profile 用户接口) - 相关主题:BLE GATT 服务与特性(见 BLE 相关页面)、RCSP 控制协议(
apps/common/third_party_profile/jieli/JL_rcsp/)