杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目概述与能力地图
    • 构建系统与编译流程
    • 芯片系列与规格
  • 应用示例

    • SPP 与 BLE 双模透传
    • AT 指令串口协议
    • HID 设备应用
    • 蓝牙 Mesh 应用
    • 公共组件与第三方协议
  • 芯片平台支持

    • 外设驱动
    • 电源与充电管理
    • 启动与链接脚本
    • 配置工具与 OTA 资源
  • 协议栈与系统库

    • 蓝牙控制器
    • BTStack 协议栈接口
    • 系统内核与服务
    • OTA 升级机制
  • 文档与参考

    • 蓝牙 AT 协议参考
    • 开发文档与认证信息

SPP 与 BLE 双模透传

SPP 与 BLE 双模透传(apps/spp_and_le)是杰理 AC631x(bd29)平台上的一个蓝牙透传参考应用:设备同时使能经典蓝牙(EDR/BR)的 SPP 串口协议与低功耗蓝牙(BLE)GATT 透传服务,实现手机 APP 通过任意一种蓝牙通道与设备侧 MCU/串口进行双向数据透传,并支持 AT 指令模式切换。

Purpose and Scope

本页面向开发者完整讲解 apps/spp_and_le 应用的双模透传能力,包括:

  • 应用层的初始化流程、状态机与蓝牙功能配置(app_spp_and_le.c);
  • SPP 透传数据通路:协议栈操作表注册、收发回调、流控与回环测试;
  • BLE 透传数据通路:GATT Profile 数据表、ATT 读写回调、Notify/Indicate 发送;
  • 模块使能宏与构建配置(Makefile、app_config.h);
  • 低功耗(sniff 模式)、错误处理与并发注意点。

不包含(属于其他页面的内容):RCSP 杰理私有协议、OTA 升级(rcsp_user_update)、充电管理(app_charge.c)、按键驱动(apps/common/key/)的细节,仅在涉及本应用联动时提及。AT 指令解析(at_cmds.c / at_uart.c)是 TRANS_AT_COM=1 编译路径下的兄弟能力,本页仅说明其与本应用的关系。

概述

透传(Transparent Transmission)是蓝牙芯片最常见的应用形态之一:手机 APP 与设备之间不需要理解蓝牙协议细节,只需把串口/自定义数据原封不动地搬到对端。本应用在两个蓝牙通道上同时提供该能力:

通道协议数据形态典型场景
经典蓝牙(EDR)SPP(Serial Port Profile,基于 RFCOMM)流式字节,类似串口配对简单、兼容老式蓝牙设备、大批量数据
低功耗蓝牙(BLE)GATT 自定义服务(UUID ae30,特征 ae01)ATT 包(Notify/Write-Without-Response)低功耗、现代手机/穿戴设备

应用通过编译期宏选择能力组合(TRANS_DATA_EN、TCFG_USER_EDR_ENABLE、TCFG_USER_BLE_ENABLE),并可通过 TRANS_AT_COM 在"纯透传"与"AT 指令控制"两种运行模式间切换。README 明确说明:代码已经实现收到手机的 SPP 数据后主动回送(loopback),用于收发测试,开发者可以此为基础扩展为自己的业务协议。

关键设计意图:

  1. 双模共存:BLE 地址由 EDR 地址派生(lib_make_ble_address),保证双模设备地址一致且唯一;EDR 连接后主动关闭 BLE 广播(bt_ble_adv_enable(0)),避免同设备双通道同时占用。
  2. 流控分离:SPP 用 busy_state() + 发送完成唤醒回调做背压;BLE 用协议栈发送缓冲区有效长度 + ATT_EVENT_CAN_SEND_NOW 事件做背压,二者都避免在回调上下文里阻塞协议栈。
  3. 低功耗优先:空闲 5 秒后自动进入 sniff 模式(SNIFF_CNT_TIME 5),收发数据时通过 clear_sniff_cnt() 退出。

架构

flowchart TD
    subgraph sg_App["应用层 apps/spp_and_le"]
        AppMain["app_main.c"]
        SppAndLe["app_spp_and_le.c<br/>(状态机/初始化/事件)"]
        AppIdle["app_idle.c"]
        AtCom["app_at_com.c / at_cmds.c / at_uart.c<br/>(TRANS_AT_COM=1 时启用)"]
        UserCfg["user_cfg.c / log_config.c / misc.c"]
    end

    subgraph sg_Trans["透传协议封装 apps/common/third_party_profile/jieli"]
        SppTrans["spp_trans_data.c + spp_user.c<br/>(SPP 收发封装)"]
        LeTrans["le_trans_data.c<br/>(BLE GATT 透传)"]
        LeAt["le_at_com.c / spp_at_com.c<br/>(AT 模式桥接)"]
    end

    subgraph sg_Stack["协议栈层 include_lib/btstack"]
        Btstack["btstack_init / btstack_task"]
        SppApi["SPP Operation Table<br/>(spp_get_operation_table)"]
        BleApi["BLE 控制器 + ATT 层<br/>(ble_user_cmd_prepare)"]
    end

    subgraph sg_Hw["硬件层"]
        Uart["UART (AT 指令口)"]
        Radio["蓝牙射频 (EDR + BLE 双模)"]
    end

    AppMain --> SppAndLe
    AppMain --> AppIdle
    SppAndLe -->|"user_spp_data_handler"| SppTrans
    SppAndLe -->|"TRANS_AT_COM 切换"| AtCom
    AtCom --> LeAt
    SppTrans -->|"spp_api 回调/发送"| SppApi
    LeTrans -->|"att_read/write 回调 + 发送"| BleApi
    Btstack --> SppApi
    Btstack --> BleApi
    LeAt --> Uart
    SppApi --> Radio
    BleApi --> Radio

架构说明:app_main.c 拉起应用后由 app_spp_and_le.c 完成蓝牙功能初始化(EDR/BLE 地址、连接参数、协议栈启动);透传逻辑封装在 apps/common/third_party_profile/jieli/trans_data_demo/ 的 spp_trans_data.c 与 le_trans_data.c 中(见 Makefile 的编译对象清单);二者分别通过 SPP 操作表与 BLE 命令封装与协议栈交互,最终数据流到射频。TRANS_AT_COM=1 时,app_spp_and_le.c 整个文件被 #if (!TRANS_AT_COM) 排除编译,改由 AT 指令路径接管(app_at_com.c、at_cmds.c、at_uart.c 与 spp_at_com.o、le_at_com.o)。

应用启动流程(来自 app_spp_and_le.c):

static void app_start()
{
    clk_set("sys", BT_NORMAL_HZ);
    u32 sys_clk =  clk_get("sys");
    bt_pll_para(TCFG_CLOCK_OSC_HZ, sys_clk, 0, 0);

    bt_function_select_init();
    bredr_handle_register();
    __change_hci_class_type(0);//default icon
    btstack_init();

#if TCFG_USER_EDR_ENABLE
    sys_auto_sniff_controle(1, NULL);
#endif
    sys_key_event_enable();
}

Source: app_spp_and_le.c

启动顺序的设计意图:先设置系统时钟并配置 PLL 参数(保证射频与系统时钟匹配),再初始化蓝牙功能选择(连接数、超时、配对参数、BLE 地址),注册 EDR 业务回调(SPP 数据、音量、电量),最后才 btstack_init() 拉起协议栈;sniff 自动低功耗在协议栈就绪后开启。

SPP 透传实现详解

初始化与回调注册

SPP 数据通路的核心封装在 spp_trans_data.c(与 spp_user.c 一起编译,见 Makefile)。初始化时通过 spp_get_operation_table() 拿到协议栈的 SPP 操作表,并注册三类回调:

void transport_spp_init(void)
{
    spp_state = 0;
    spp_get_operation_table(&spp_api);
    spp_api->regist_recieve_cbk(0, transport_spp_recieve_cbk);
    spp_api->regist_state_cbk(0, transport_spp_state_cbk);
    spp_api->regist_wakeup_send(NULL, transport_spp_send_wakeup);
}

Source: README.md

  • regist_recieve_cbk:手机 → 设备的数据入口,收到 SPP 数据后在此回调中处理;
  • regist_state_cbk:连接/断开状态通知,回调参数为 SPP_USER_ST_CONNECT / SPP_USER_ST_DISCONN;
  • regist_wakeup_send:协议栈发送缓冲空闲后的"可继续发送"通知,用于驱动流控。

应用层侧,app_spp_and_le.c 在 bredr_handle_register() 中把 SPP 数据处理器注册给协议栈数据分发:

static void bredr_handle_register()
{
#if (USER_SUPPORT_PROFILE_SPP==1)
    spp_data_deal_handle_register(user_spp_data_handler);
#endif
    /* bt_fast_test_handle_register(bt_fast_test_api);//测试盒快速测试接口 */
#if BT_SUPPORT_MUSIC_VOL_SYNC
    music_vol_change_handle_register(set_music_device_volume, phone_get_device_vol);
#endif
#if BT_SUPPORT_DISPLAY_BAT
    get_battery_value_register(bt_get_battery_value);   /*电量显示获取电量的接口*/
#endif
}

Source: app_spp_and_le.c

数据收发与流控

发送侧采用"先检查、后发送、回调唤醒"三段式流控,防止在协议栈忙时盲目发包:

int transport_spp_send_data(u8 *data, u16 len)
{
    if (spp_api) {
        log_info("spp_api_tx(%d) \n", len);
        clear_sniff_cnt();                     /* 有数据交互,退出/延迟进入 sniff */
        return spp_api->send_data(NULL, data, len);
    }
    return SPP_USER_ERR_SEND_FAIL;
}

int transport_spp_send_data_check(u16 len)
{
    if (spp_api) {
        if (spp_api->busy_state()) {           /* 协议栈发送忙,拒绝本次发送 */
            return 0;
        }
    }
    return 1;
}

Source: README.md

transport_spp_send_wakeup 是发送完成的唤醒钩子,示意可继续发送(示例实现仅 putchar('W'),实际产品中应触发上层发送队列继续发送)。接收侧 transport_spp_recieve_cbk 打印长度与十六进制内容,并调用 clear_sniff_cnt() 防止低功耗模式中断传输。

回环测试(loopback)

README 给出的收发测试逻辑:收到手机数据后立即原样回送,验证 SPP 链路双向可用:

static void transport_spp_recieve_cbk(void *priv, u8 *buf, u16 len)
{
    log_info("spp_api_rx(%d) \n", len);
    log_info_hexdump(buf, len);
    clear_sniff_cnt();
    //loop send data for test
    if (transport_spp_send_data_check(len)) {
        transport_spp_send_data(buf, len);
    }
}

Sources: README.md、README.md

SPP 数据通路时序

sequenceDiagram
    participant Phone as 手机 SPP 客户端
    participant Stack as btstack 协议栈 (SPP/RFCOMM)
    participant Spp as spp_trans_data.c
    participant App as app_spp_and_le.c (user_spp_data_handler)
    participant Uart as 设备侧业务/串口

    Note over Phone,App: 连接建立
    Stack-->>Spp: state_cbk(SPP_USER_ST_CONNECT)
    Spp-->>App: 状态更新

    Note over Phone,App: 手机 → 设备 数据下行
    Phone->>Stack: SPP 数据包
    Stack->>Spp: recieve_cbk(buf, len)
    Spp->>Spp: clear_sniff_cnt()
    Spp->>App: user_spp_data_handler(packet_type, ch, packet, size)
    App->>Uart: 业务处理/转发

    Note over Phone,App: 设备 → 手机 数据上行
    Uart->>App: 待发送数据
    App->>Spp: send_data_check(len)
    Spp-->>App: 返回 1 (协议栈空闲)
    App->>Spp: send_data(data, len)
    Spp->>Stack: spp_api->send_data(NULL, data, len)
    Stack-->>Spp: send_wakeup() (可继续发送)

关键点:所有回调都运行在 btstack 协议栈任务上下文,因此回调内不能做耗时操作,数据应尽快拷贝/转发;send_data_check 与 busy_state() 的存在就是为了让应用层自行实现发送队列背压,而不是在回调里阻塞等待。

BLE 透传实现详解

GATT Profile 数据表

BLE 透传封装在 le_trans_data.c,其 GATT 服务表由工具 make_gatt_services 生成后以字节数组形式放在 le_trans_data.h,开发者可通过该工具重新配置服务与属性。Profile 包含两个 Primary Service(见 README.md):

static const uint8_t profile_data[] = {
    // 0x0001 PRIMARY_SERVICE 1800 (GAP)
    0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18,
    // 0x0002 CHARACTERISTIC 2a00 READ | WRITE | DYNAMIC
    0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x0a, 0x03, 0x00, 0x00, 0x2a,
    // 0x0003 VALUE 2a00 READ | WRITE | DYNAMIC (设备名)
    0x08, 0x00, 0x0a, 0x01, 0x03, 0x00, 0x00, 0x2a,

    // 0x0004 PRIMARY_SERVICE ae30 (透传自定义服务)
    0x0a, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x28, 0x30, 0xae,
    // 0x0005 CHARACTERISTIC ae01 WRITE_WITHOUT_RESPONSE | DYNAMIC
    0x0d, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x28, 0x04, 0x06, 0x00, 0x01, 0xae,
    // 0x0006 VALUE ae01 WRITE_WITHOUT_RESPONSE | DYNAMIC
    0x08, 0x00, 0x04, 0x01, 0x06, 0x00, 0x01, 0xae,
};

Source: README.md

属性规划要点:

  • 0x1800 GAP 服务:标准服务,0x2a00 为设备名特征(ATT_CHARACTERISTIC_2a00_01_VALUE_HANDLE),读回调返回 gap_device_name;
  • 0xae30 自定义服务:0xae01 为透传特征,属性为 WRITE_WITHOUT_RESPONSE | DYNAMIC —— 手机下行采用"无响应写"(低延迟、无需 ATT 应答),DYNAMIC 表示值由应用动态管理而非静态表;
  • 透传特征没有配 Notify 属性位,但示例中 app_send_user_data 仍可对任意 handle 发 Notify/Indicate,前提是对端已使能对应 CCC(att_get_ccc_config(handle + 1))。

ATT 读写回调

协议栈通过两个回调把 ATT 层的读/写请求送到应用:

  • att_read_callback(connection_handle, att_handle, offset, buffer, buffer_size):对 ATT_CHARACTERISTIC_2a00_01_VALUE_HANDLE 返回设备名。注意其 offset + buffer_size > att_value_len 时的越界保护逻辑 —— 协议栈可能以分片方式多次调用,应用必须按 offset 正确返回子串(见 README.md);
  • att_write_callback(connection_handle, att_handle, transaction_mode, offset, buffer, buffer_size):处理手机写入,handle 命中透传特征时即收到下行数据(见 README.md)。

事件回调(cbk_packet_handler)

HCI 事件层回调统一分发协议栈事件,与透传相关的主要是:

static void cbk_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size)
{
    int mtu;
    switch (packet_type) {
    case HCI_EVENT_PACKET:
        switch (hci_event_packet_get_type(packet)) {
        case ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE:
            log_info("ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE\n");
        case ATT_EVENT_CAN_SEND_NOW:
            can_send_now_wakeup();      /* 协议栈允许继续发数,触发发送队列 */
            break;
        case HCI_EVENT_LE_META:
            switch (hci_event_le_meta_get_subevent_code(packet)) {
            case HCI_SUBEVENT_LE_ENHANCED_CONNECTION_COMPLETE:
                /* ... 连接建立,记录 con_handle ... */
            }
        }
    }
}

Source: README.md

ATT_EVENT_CAN_SEND_NOW 是 BLE 侧的发送唤醒信号,语义与 SPP 的 send_wakeup 完全对应:应用在回调里把待发队列继续送入协议栈。

Notify/Indicate 发送与检查

上行发送统一走 app_send_user_data,发送前必须先通过 app_send_user_data_check 确认协议栈缓冲足够:

static int app_send_user_data(u16 handle, u8 *data, u16 len, u8 handle_type)
{
    u32 ret = APP_BLE_NO_ERROR;
    if (!con_handle) {                          /* 无连接句柄,直接失败 */
        return APP_BLE_OPERATION_ERROR;
    }
    if (!att_get_ccc_config(handle + 1)) {      /* 对端未使能 CCC,通知无意义 */
        log_info("fail,no write ccc!!!,%04x\n", handle + 1);
        return APP_BLE_NO_WRITE_CCC;
    }
    ret = ble_user_cmd_prepare(BLE_CMD_ATT_SEND_DATA, 4, handle, data, len, handle_type);
    return ret;
}

static int app_send_user_data_check(u16 len)
{
    u32 buf_space = get_buffer_vaild_len(0);
    if (len <= buf_space) {
        return 1;
    }
    return 0;
}

Source: README.md

设计意图:ble_user_cmd_prepare(BLE_CMD_ATT_SEND_DATA, ...) 把发送请求打包进协议栈命令队列,在协议栈任务中异步执行,从而保证应用回调上下文不会被 ATT 发送阻塞;handle_type 用于区分 Notify 与 Indicate 两种发送模式。CCC 检查(att_get_ccc_config(handle + 1))防止对端未订阅时发送浪费空包。

BLE 数据通路时序

sequenceDiagram
    participant Phone as 手机 BLE 客户端
    participant Stack as btstack (ATT/HCI)
    participant Le as le_trans_data.c
    participant App as 应用层 (业务)
    participant Buf as 协议栈发送缓冲

    Note over Phone,Le: 下行: 手机写透传特征
    Phone->>Stack: ATT Write (0xae01, WRITE_WITHOUT_RESPONSE)
    Stack->>Le: att_write_callback(handle, buffer, size)
    Le->>App: 业务处理/转发到串口

    Note over Phone,Le: 上行: 应用发送 Notify
    App->>Le: app_send_user_data_check(len)
    Le->>Buf: get_buffer_vaild_len(0) 检查
    Buf-->>Le: 空间足够
    App->>Le: app_send_user_data(handle, data, len, type)
    Le->>Stack: ble_user_cmd_prepare(BLE_CMD_ATT_SEND_DATA,...)
    Stack-->>Le: ATT_EVENT_CAN_SEND_NOW (可继续发送)
    Le-->>App: can_send_now_wakeup() 驱动队列

BLE 与 SPP 两条通路共享同一套"检查 → 入队 → 唤醒"的异步流控模型,这是双模透传稳定性的关键:无论在哪个通道,都不会在协议栈回调上下文中同步阻塞。

低功耗(sniff)与连接管理

自动 sniff 控制

app_spp_and_le.c 实现了"空闲进 sniff、有数据退出"的经典低功耗策略(见 app_spp_and_le.c):

#define SNIFF_CNT_TIME                5      // 空闲5S之后进入sniff模式
#define SNIFF_MAX_INTERVALSLOT        800
#define SNIFF_MIN_INTERVALSLOT        100
#define SNIFF_ATTEMPT_SLOT            4
#define SNIFF_TIMEOUT_SLOT            1

void bt_check_enter_sniff()
{
    struct sniff_ctrl_config_t sniff_ctrl_config;
    u8 addr[12];
    u8 conn_cnt = 0;
    if (sniff_ready_status) {
        sniff_ready_status = 0;      /* 刚发过数据,本次检查跳过 */
        return;
    }
    conn_cnt = bt_api_enter_sniff_status_check(SNIFF_CNT_TIME, addr);
    ASSERT(conn_cnt <= 2);
    for (i = 0; i < conn_cnt; i++) {
        sniff_ctrl_config.sniff_max_interval = SNIFF_MAX_INTERVALSLOT;
        sniff_ctrl_config.sniff_mix_interval = SNIFF_MIN_INTERVALSLOT;
        sniff_ctrl_config.sniff_attemp = SNIFF_ATTEMPT_SLOT;
        sniff_ctrl_config.sniff_timeout  = SNIFF_TIMEOUT_SLOT;
        memcpy(sniff_ctrl_config.sniff_addr, addr + i * 6, 6);
        user_send_cmd_prepare(USER_CTRL_SNIFF_IN, sizeof(struct sniff_ctrl_config_t), (u8 *)&sniff_ctrl_config);
    }
}
  • 启动时 sys_auto_sniff_controle(1, NULL) 注册 1 秒周期定时器(sys_timer_add(NULL, bt_check_enter_sniff, 1000))持续检查;
  • bt_api_enter_sniff_status_check(SNIFF_CNT_TIME, addr) 统计连接的空闲时长,支持最多 2 台设备(ASSERT(conn_cnt <= 2),与 TCFG_BD_NUM 对应);
  • 数据收发路径(clear_sniff_cnt())把 sniff_ready_status 置位,让下一次检查跳过,等效于"数据活跃时暂缓进入 sniff";
  • 退出 sniff 通过 USER_CTRL_ALL_SNIFF_EXIT 命令(bt_check_exit_sniff())。

连接/断开事件处理

static void bt_hci_event_connection(struct bt_event *bt)      { bt_wait_phone_connect_control_ext(0, 0); }
static void bt_hci_event_disconnect(struct bt_event *bt)      { bt_wait_phone_connect_control_ext(1, 1); }
static void bt_hci_event_page_timeout(struct bt_event *bt)    { bt_wait_phone_connect_control_ext(1, 1); }
static void bt_hci_event_connection_timeout(struct bt_event *bt){ bt_wait_phone_connect_control_ext(1, 1); }

void bt_wait_phone_connect_control_ext(u8 inquiry_en, u8 page_scan_en)
{
#if TCFG_USER_EDR_ENABLE
    if (inquiry_en)  user_send_cmd_prepare(USER_CTRL_WRITE_SCAN_ENABLE, 0, NULL);
    else             user_send_cmd_prepare(USER_CTRL_WRITE_SCAN_DISABLE, 0, NULL);
    if (page_scan_en) user_send_cmd_prepare(USER_CTRL_WRITE_CONN_ENABLE, 0, NULL);
    else              user_send_cmd_prepare(USER_CTRL_WRITE_CONN_DISABLE, 0, NULL);
#endif
}

Sources: app_spp_and_le.c、app_spp_and_le.c

设计意图:连接成功后关闭可发现/可连接,防止被其他手机反复骚扰打断透传;断开/超时后恢复可发现与可连接,等待回连。bt_hci_event_handler 中还处理了测试盒事件(HCI_EVENT_VENDOR_REMOTE_TEST):EDR 连接建立时主动 bt_ble_adv_enable(0) 关闭 BLE 广播,避免同一设备双通道并存(见 app_spp_and_le.c)。

配置选项

宏/配置类型默认/取值说明
TRANS_DATA_ENint1蓝牙双模透传总开关(README 模块使能)
TCFG_USER_BLE_ENABLEint1BLE 功能使能;关掉后 lib_make_ble_address 与 ble_module_enable 分支不编译
TCFG_USER_EDR_ENABLEint1EDR(经典蓝牙)功能使能;关掉后 sniff 控制、可发现/可连接控制分支不编译
TRANS_AT_COMint01 时编译 AT 指令模式(app_at_com.c/at_cmds.c/at_uart.c),app_spp_and_le.c 整体被 #if (!TRANS_AT_COM) 排除
USER_SUPPORT_PROFILE_SPPint1是否注册 SPP 用户数据处理器(spp_data_deal_handle_register)
TCFG_BD_NUMint1~2同时连接设备数;为 2 时 __set_auto_conn_device_num(2),sniff 检查 ASSERT(conn_cnt <= 2)
BT_SUPPORT_DISPLAY_BATint0电量显示;开启时 __bt_set_update_battery_time(60) 并注册电量回调
BT_SUPPORT_MUSIC_VOL_SYNCint0音乐音量同步;开启时注册音量变化处理器
TCFG_CLOCK_OSC_HZint振荡器频率传给 bt_pll_para 的 OSC 参数
BT_NORMAL_HZint系统主频应用启动时 clk_set("sys", BT_NORMAL_HZ) 设定的系统时钟
WAIT_DISCONN_TIME_MSint300软关机前等待蓝牙链路断开的延时(ms)
SNIFF_CNT_TIMEint5空闲多少秒后进入 sniff 模式
SNIFF_MAX_INTERVALSLOTint800sniff 最大间隔(slot)
SNIFF_MIN_INTERVALSLOTint100sniff 最小间隔(slot)

编译对象与依赖路径见 Makefile:spp_trans_data.o、spp_user.o、le_trans_data.o、le_client_demo.o、spp_at_com.o、le_at_com.o 均来自 apps/common/third_party_profile/jieli/;配置项定义于 app_config.h 及 board/$(CPU) 板级目录,具体默认值以各板级 app_config.h 为准。

API 参考

SPP 侧(spp_trans_data.c)

函数签名要点说明
transport_spp_initvoid transport_spp_init(void)获取 SPP 操作表并注册接收/状态/唤醒回调
transport_spp_recieve_cbkstatic void (void *priv, u8 *buf, u16 len)接收回调:打印、clear_sniff_cnt()、回环测试
transport_spp_state_cbkstatic void (u8 state)连接状态回调(SPP_USER_ST_CONNECT/SPP_USER_ST_DISCONN)
transport_spp_send_dataint transport_spp_send_data(u8 *data, u16 len)发送数据;spp_api 为空返回 SPP_USER_ERR_SEND_FAIL
transport_spp_send_data_checkint transport_spp_send_data_check(u16 len)发送前检查:busy_state() 忙则返回 0
transport_spp_send_wakeupstatic void transport_spp_send_wakeup(void)发送完成唤醒钩子,驱动继续发送

BLE 侧(le_trans_data.c)

函数签名要点说明
cbk_packet_handlerstatic void (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size)HCI 事件分发:ATT_EVENT_CAN_SEND_NOW → can_send_now_wakeup();LE_ENHANCED_CONNECTION_COMPLETE → 记录连接
att_read_callbackstatic uint16_t (hci_con_handle_t, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size)读请求;0x2a00 返回设备名,含 offset 越界保护
att_write_callbackstatic int (hci_con_handle_t, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size)写请求;透传特征命中即收到下行数据
app_send_user_datastatic int app_send_user_data(u16 handle, u8 *data, u16 len, u8 handle_type)Notify/Indicate 发送;无连接返回 APP_BLE_OPERATION_ERROR,未使能 CCC 返回 APP_BLE_NO_WRITE_CCC
app_send_user_data_checkstatic int app_send_user_data_check(u16 len)发送前检查 get_buffer_vaild_len(0) 是否足够

应用层辅助接口(app_spp_and_le.c)

函数签名要点说明
app_startstatic void app_start(void)时钟/PLL 配置 → bt_function_select_init → bredr_handle_register → btstack_init → sniff 控制
bt_function_select_initstatic void bt_function_select_init(void)连接数、超时、简单配对参数(__set_simple_pair_param(3, 0, 2))、BLE 地址派生
bredr_handle_registerstatic void bredr_handle_register(void)注册 SPP 数据、音量、电量回调
bt_send_pairvoid bt_send_pair(u8 en)发送配对命令(USER_CTRL_PAIR)
sys_auto_sniff_controlevoid sys_auto_sniff_controle(u8 enable, u8 *addr)启停自动 sniff 检查定时器
bt_check_enter_sniff / bt_check_exit_sniffvoid (void)空闲进入 / 数据退出 sniff
app_set_soft_poweroffstatic void app_set_soft_poweroff(void)先 ble_module_enable(0) 关 BLE,再延时 300ms 软关机

失败模式、边界情况与并发

回调上下文与并发约束

SPP 的接收/状态/唤醒回调与 BLE 的 cbk_packet_handler、att_read_callback、att_write_callback 均运行在 btstack 协议栈任务上下文中。由此得出两条硬约束:

  1. 回调内禁止阻塞:不能等待互斥量、不能做慢速外设访问,数据应拷贝到应用缓冲后返回;
  2. 发送必须异步化:SPP 用 spp_api->send_data(协议栈内部排队),BLE 用 ble_user_cmd_prepare(BLE_CMD_ATT_SEND_DATA, ...) 把命令投递到协议栈任务队列,天然串行化,避免多任务并发写 ATT 通道。

典型失败场景与处理

场景触发条件代码行为
SPP 协议栈忙busy_state() 为真transport_spp_send_data_check 返回 0,上层应暂存数据,等待 transport_spp_send_wakeup
SPP 未初始化spp_api 为 NULLtransport_spp_send_data 返回 SPP_USER_ERR_SEND_FAIL
BLE 未连接con_handle 为 0app_send_user_data 返回 APP_BLE_OPERATION_ERROR,不发空包
对端未订阅通知att_get_ccc_config(handle + 1) 为 0返回 APP_BLE_NO_WRITE_CCC,日志打印 fail,no write ccc!!!
BLE 发送缓冲不足len > get_buffer_vaild_len(0)app_send_user_data_check 返回 0,等待 ATT_EVENT_CAN_SEND_NOW
ATT 读越界offset + buffer_size > att_value_lenatt_read_callback 直接 break,不拷贝数据(分片读取保护)
双设备连接conn_cnt > 2ASSERT(conn_cnt <= 2) 触发断言,与 TCFG_BD_NUM 配置联动
软关机时链路未断BLE 仍连接先 ble_module_enable(0) 主动断开,再延时 300ms 执行 power_set_soft_poweroff,避免链路超时等待

双模共存边界

  • 地址派生:BLE 地址由 EDR MAC 经 lib_make_ble_address 派生并 le_controller_set_mac 设置,保证双模设备地址关联(见 app_spp_and_le.c);
  • 通道互斥:EDR 连接建立时关闭 BLE 广播(bt_ble_adv_enable(0)),断开后由事件处理恢复可发现/可连接;同一时刻手机优先走其中一个通道,避免数据在两个通道间串扰;
  • sniff 与透传互斥:收发路径调用 clear_sniff_cnt() 延迟进入 sniff,保证连续数据传输不被低功耗打断。

性能与运维注意事项

  • 流控节奏:SPP 单包发送受 busy_state 约束,BLE 受发送缓冲约束;上层发送队列应设计为"唤醒驱动"模型(send_wakeup / CAN_SEND_NOW),而不是轮询,以降低协议栈负载。
  • sniff 参数:SNIFF_MAX_INTERVALSLOT 800(约 1s)适合低吞吐透传;高吞吐场景需调小间隔或延长 SNIFF_CNT_TIME,否则数据延迟增大。
  • 日志开销:log_info_hexdump(buf, len) 在接收回调中逐字节打印,高速透传时建议注释掉(README 中已有 /* log_info_hexdump(data, len); */ 的先例)。
  • 系统时钟:app_start 固定 clk_set("sys", BT_NORMAL_HZ) 并配置 PLL,改动时钟前需确认射频参数匹配(bt_pll_para(TCFG_CLOCK_OSC_HZ, sys_clk, 0, 0))。

扩展点

  1. 替换回环测试为业务处理:在 transport_spp_recieve_cbk / att_write_callback 中把数据转发到 UART 或业务模块,实现真正的"透传";上行数据分别调用 transport_spp_send_data 与 app_send_user_data。
  2. 修改 GATT 服务:用 make_gatt_services 工具重新生成 profile_data 表(le_trans_data.h),可增删特征、调整属性(如给透传特征加 Notify、增加 RX/TX 双特征)。
  3. AT 指令模式:将 TRANS_AT_COM 置 1 后编译 app_at_com.c/at_cmds.c/at_uart.c,通过 UART 下发 AT 指令控制连接、查询状态,app_spp_and_le.c 自动退出编译。
  4. 多连接扩展:TCFG_BD_NUM=2 时自动使能双设备连接(__set_auto_conn_device_num(2)),sniff 控制已支持双连接(ASSERT(conn_cnt <= 2))。
  5. 配对策略:__set_simple_pair_param(3, 0, 2)(io_capabilities=3 NoInputNoOutput、authentication=0)为免输入配对,可按产品安全需求调整。

相关链接

  • app_spp_and_le.c(应用主逻辑)
  • README.md(SPP/LE 透传接口说明)
  • Makefile(编译对象与依赖)
  • spp_trans_data.c / spp_user.c:apps/common/third_party_profile/jieli/trans_data_demo/(SPP 透传封装)
  • le_trans_data.c / le_at_com.c / le_client_demo.c:apps/common/third_party_profile/jieli/trans_data_demo/(BLE 透传封装)
  • AT 指令模式:app_at_com.c / at_cmds.c / at_uart.c(apps/spp_and_le/,TRANS_AT_COM=1 时启用)
Next
AT 指令串口协议