4.3.1. SPP+BLE

4.3.1.1. 概述

支持蓝牙双模透传传输功能。CLASSIC蓝牙使用标准串口SPP profile协议,BLE蓝牙使用自定义的profile协议,提供ATT的WRITE、WRITE_WITHOUT_RESPONSE,NOTIFY和INDICATE等属性传输收发数据。

支持的板级: br25、br23、bd19、br34

支持的芯片: AC636N、AC635N、AC632N、AC638N

4.3.1.2. 工程配置

代码工程:apps/spp_and_le/board/bd19/AC632N_spp_and_le.cbp

  • 先配置板级board_config.h和对应配置文件中蓝牙双模使能

*/\**

 *  板级配置选择

 */
#define CONFIG_BOARD_AC632N_DEMO
#include "board_ac632n_demo_cfg.h"
  • 在board_ac632n_demo_cfg.h中配置是否打开 edr 和ble模块

#define TCFG_USER_BLE_ENABLE                      1   \ *//BLE功能使能*
#define TCFG_USER_EDR_ENABLE                      1   \ *//EDR功能使能*

(2)配置app选择:“app_config.h”

*//apps example 选择,只能选1个,要配置对应的board_config.h*
#define CONFIG_APP_SPP_LE                 1 \ *//SPP + LE or LE's client*
#define CONFIG_APP_MULTI                  0 \ *//蓝牙LE多连 + spp*
#define CONFIG_APP_DONGLE                 0 \ *//usb + 蓝牙(ble 主机),PC hid设备*
#define CONFIG_APP_CENTRAL                0 \ *//ble client,中心设备*
#define CONFIG_APP_LL_SYNC                0 \ *//腾讯连连*
#define CONFIG_APP_BEACON                 0 \ *//蓝牙BLE ibeacon*
#define CONFIG_APP_NONCONN_24G            0 \ *//2.4G 非连接收发*
#define CONFIG_APP_TUYA                   0 \ *//涂鸦协议*
#define CONFIG_APP_AT_COM                 0 \ *//AT com HEX格式命令*
#define CONFIG_APP_AT_CHAR_COM            0 \ *//AT com 字符串格式命令*
#define CONFIG_APP_IDLE                   0 \ *//空闲任务*
  • 配置对应case需求:“app_config.h”

#if CONFIG_APP_SPP_LE
*//配置双模同名字,同地址*
#define DOUBLE_BT_SAME_NAME                0 \ *//同名字*
#define DOUBLE_BT_SAME_MAC                 0 \ *//同地址*
#define CONFIG_APP_SPP_LE_TO_IDLE          0 \ *//SPP_AND_LE To IDLE Use*

(3)蓝牙的BLE配置,保护GATT和SM的配置

*//蓝牙BLE配置*
#define CONFIG_BT_GATT_COMMON_ENABLE       1 \ *//配置使用gatt公共模块*
#define CONFIG_BT_SM_SUPPORT_ENABLE        0 \ *//配置是否支持加密*
#define CONFIG_BT_GATT_CLIENT_NUM          0 \ *//配置主机client个数 (app not support)*
#define CONFIG_BT_GATT_SERVER_NUM          1 \ *//配置从机server个数*
#define CONFIG_BT_GATT_CONNECTION_NUM      (CONFIG_BT_GATT_SERVER_NUM + CONFIG_BT_GATT_CLIENT_NUM) \ *//配置连接个数*

4.3.1.3. SPP数据通信

  • 推荐使用手机测试工具:“蓝牙串口”

  • 代码文件spp_trans.c

  • 接口说明:“spp_trans.c”

  • SPP模块初始化

void transport_spp_init(void)
{
    log_info("trans_spp_init\n");
    log_info("spp_file: %s", __FILE__);
#if (USER_SUPPORT_PROFILE_SPP==1)
    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);

#endif
#if TEST_SPP_DATA_RATE
spp_timer_handle  = sys_timer_add(NULL, test_timer_handler, SPP_TIMER_MS);
#endif
}
  • SPP连接和断开事件处理

static void transport_spp_state_cbk(u8 state)
{
    spp_state = state;
    switch (state) {
    case SPP_USER_ST_CONNECT:
        log_info("SPP_USER_ST_CONNECT ======\n");

        break;
        case SPP_USER_ST_DISCONN:
                log_info("SPP_USER_ST_DISCONN ======\n");
                spp_channel = 0;
                break;
        default:
                break;
        }
}
  • SPP发送数据接口,发送前先调用接口transport_spp_send_data_check检查

int transport_spp_send_data(u8 *data, u16 len)
{
    if (spp_api) {
       log_info("spp_api_tx(%d) \n", len);
       */* log_info_hexdump(data, len); */*
        */* clear_sniff_cnt(); */*
        bt_comm_edr_sniff_clean();
        return spp_api->send_data(NULL, data, len);
    }

return SPP_USER_ERR_SEND_FAIL;
}
  • SPP检查是否可以往协议栈发送数据

int transport_spp_send_data_check(u16 len)
{
    if (spp_api) {
        if (spp_api->busy_state()) {
            return 0;
        }
    }
    return 1;
}
  • SPP发送完成回调,表示可以继续往协议栈发数,用来触发继续发数

static void transport_spp_send_wakeup(void)
{
    putchar('W');
}
  • SPP接收数据接口

static void transport_spp_recieve_cbk(void *priv, u8 *buf, u16 len)
{
    spp_channel = (u16)priv;
    log_info("spp_api_rx(%d) \n", len);
    log_info_hexdump(buf, len);
    clear_sniff_cnt();
    ......
  • 收发测试:“spp_trans.c”,代码已经实现收到手机的SPP数据后,会主动把数据回送,测试数据收发。

*//loop send data for test*
if (transport_spp_send_data_check(len)) {
    log_info("-loop send\n");
    transport_spp_send_data(buf, len);
}
  • 串口的UUID:“lib_profile_config.c”。串口的UUID默认是16bit的0x1101。若要修改可自定义的16bit 或128bit UUID,可修改SDP的S信息结构体sdp_spp_service_data,具体查看16it和128bit UUID填写示例。主要channel id 默认是1,不能修改。

#if (USER_SUPPORT_PROFILE_SPP==1)
u8 spp_profile_support = 1;
SDP_RECORD_HANDLER_REGISTER(spp_sdp_record_item) = {
    .service_record = (u8 *)sdp_spp_service_data,
    .service_record_handle = 0x00010004,
};
#endif
  • 16it和128bit UUID填写示例如下:

/*128 bit uuid:  11223344-5566-7788-aabb-8899aabbccdd  */
const u8 sdp_test_spp_service_data[96] = {
    0x36, 0x00, 0x5B, 0x09, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x04, 0x09, 0x00, 0x01, 0x36, 0x00,
    0x11, 0x1C,

    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xaa, 0xbb, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, //uuid128

    0x09, 0x00, 0x04, 0x36, 0x00, 0x0E, 0x36, 0x00, 0x03, 0x19, 0x01, 0x00, 0x36, 0x00,
    0x05, 0x19, 0x00, 0x03, 0x08, 0x01, 0x09, 0x00, 0x09, 0x36, 0x00, 0x17, 0x36, 0x00, 0x14, 0x1C,
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xaa, 0xbb, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, //uuid128

        0x09, 0x01, 0x00, 0x09, 0x01, 0x00, 0x25, 0x06, 0x4A, 0x4C, 0x5F, 0x53, 0x50, 0x50, 0x00, 0x00,
};

//spp 16bit uuid  11 01
const u8 sdp_spp_update_service_data[70] = {
        0x36, 0x00, 0x42, 0x09, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x0B, 0x09, 0x00, 0x01, 0x36, 0x00,
        0x03, 0x19,

        0x11, 0x01, //uuid16
        0x09, 0x00, 0x04, 0x36, 0x00, 0x0E, 0x36, 0x00, 0x03, 0x19, 0x01, 0x00,
        0x36, 0x00, 0x05, 0x19, 0x00, 0x03, 0x08, 0x01, 0x09, 0x00, 0x09, 0x36, 0x00, 0x09, 0x36, 0x00,

        0x06, 0x19,

        0x11, 0x01, //uuid16
        0x09, 0x01, 0x00, 0x09, 0x01, 0x00, 0x25, 0x09, 0x4A, 0x4C, 0x5F, 0x53,
        0x50, 0x50, 0x5F, 0x55, 0x50, 0x00,
};

4.3.1.4. BLE数据通信

  • 推荐使用手机测试工具:“nRF Connect”

  • 代码文件ble_trans.c

  • Profile生成的trans_profile_data数据表放在ble_trans_profile.h。用户可用工具make_gatt_services(sdk的tools目录下)按照“make_gatt_services工具说明.pdf”自定义修改,重新配置GATT服务和属性等。

//////////////////////////////////////////////////////
//
// 0x0001 PRIMARY_SERVICE  1800
//
//////////////////////////////////////////////////////
0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18,

/* CHARACTERISTIC,  2a00, READ | WRITE | DYNAMIC, */
// 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,

/* CHARACTERISTIC,  ae01, WRITE_WITHOUT_RESPONSE | DYNAMIC, */
// 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,
  • 接口说明:“ble_trans.c”

  • 配置广播ADV数据

static int trans_make_set_adv_data(void)
{
  • 配置广播RESPONE数据

static int trans_make_set_rsp_data(void)
{
  • 配置发送缓存大小

#define ATT_LOCAL_PAYLOAD_SIZE    (64*2) \ *//note: need >= 20*
#define ATT_SEND_CBUF_SIZE        (512) \ *//note: need >= 20,缓存大小可修改*
  • 协议栈事件回调处理,主要是连接、断开等事件:“le_gatt_server.c”

static int trans_event_packet_handler(int event, u8 *packet, u16 size, u8 *ext_param)
{
    */* log_info("event: %02x,size= %d\n",event,size); */*
    switch (event) {
    case GATT_COMM_EVENT_CONNECTION_COMPLETE:
        log_info("connection_handle:%04x\n", little_endian_read_16(packet, 0));
        log_info("peer_address_info:");
        put_buf(&ext_param[7], 7);

                trans_con_handle = little_endian_read_16(packet, 0);
                trans_connection_update_enable = 1;

                log_info("con_interval = %d\n", little_endian_read_16(ext_param, 14 + 0));
                log_info("con_latency = %d\n", little_endian_read_16(ext_param, 14 + 2));
                log_info("cnn_timeout = %d\n", little_endian_read_16(ext_param, 14 + 4));
                break;

        case GATT_COMM_EVENT_DISCONNECT_COMPLETE:
  • ATT读事件处理:“ble_trans.c”

static uint16_t trans_att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_sie)
    {
        uint16_t  att_value_len = 0;
        uint16_t handle = att_handle;

        log_info("read_callback,conn_handle =%04x, handle=%04x,buffer=%08x\n", connection_handle, handle, (u32)buffer);

        switch (handle) {
        case ATT_CHARACTERISTIC_2a00_01_VALUE_HANDLE: {

            char *gap_name = ble_comm_get_gap_name();
            att_value_len = strlen(gap_name);
            if ((offset >= att_value_len) || (offset + buffer_size) > att_value_len) {
            break;
    }

    if (buffer) {
    memcpy(buffer, &gap_name[offset], buffer_size);
    att_value_len = buffer_size;
  • ATT写事件处理:“ble_trans.c”

static int trans_att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size)
{
    int result = 0;
    u16 tmp16;

    u16 handle = att_handle;

    log_info("write_callback,conn_handle =%04x, handle =%04x,size =%d\n", connection_handle, handle, buffer_size);

        switch (handle) {
        case ATT_CHARACTERISTIC_2a00_01_VALUE_HANDLE:
        break;
  • NOTIFY 和 INDICATE 发送接口,发送前调接口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)) {
        log_info("fail,no write ccc!!!,%04x\n", handle + 1);
        return APP_BLE_NO_WRITE_CCC;
        }

        ret = ble_op_multi_att_send_data(con_handle, handle, data, len, handle_type);
  • 检查是否可以往协议栈发送数据

*//收发测试,自动发送收到的数据;for test*
if (ble_comm_att_check_send(connection_handle, buffer_size)) {
  • 发送完成回调,表示可以继续往协议栈发数,用来触发继续发数

    case GATT_COMM_EVENT_CAN_SEND_NOW:
#if TEST_AUDIO_DATA_UPLOAD
        trans_test_send_audio_data(0);
#endif
        break;
  • 收发测试:“ble_trans.c”

  • 使用手机NRF软件,连接设备后;使能notify和indicate的UUID (AE02 和 AE05) 的通知功能后;可以通过向write的UUID (AE01 或 AE03) 发送数据;测试UUID (AE02 或 AE05)是否收到数据。

case ATT_CHARACTERISTIC_ae01_01_VALUE_HANDLE:
    log_info("\n-ae01_rx(%d):", buffer_size);
    put_buf(buffer, buffer_size);
    *//收发测试,自动发送收到的数据;for test*
    if (ble_comm_att_check_send(connection_handle, buffer_size)) {
        log_info("-loop send1\n");
        ble_comm_att_send_data(connection_handle, ATT_CHARACTERISTIC_ae02_01_VALUE_HANDLE, buffer, buffer_size, ATT_OP_AUTO_READ_CCC);
    }
        break;

case ATT_CHARACTERISTIC_ae03_01_VALUE_HANDLE:
        log_info("\n-ae_rx(%d):", buffer_size);
        put_buf(buffer, buffer_size);

        *//收发测试,自动发送收到的数据;for test*
        if (ble_comm_att_check_send(connection_handle, buffer_size)) {
                log_info("-loop send2\n");
                ble_comm_att_send_data(connection_handle, ATT_CHARACTERISTIC_ae05_01_VALUE_HANDLE, buffer, buffer_size, ATT_OP_AUTO_READ_CCC);
        }
        break;

4.3.1.5. BLE 搜索主机Profile服务示例

  • 在现有的已连接的链路,执行搜索对方的GATT 服务;操作GATT数据收发;目前支持指定的服务搜索,或者搜索苹果服务ANCS和AMS的支持。

*//BLE 从机扩展搜索对方服务功能,需要打开GATT CLIENT*
#if CONFIG_BT_GATT_CLIENT_NUM
#define TRANS_CLIENT_SEARCH_PROFILE_ENABLE  1\ */*配置模块搜索指定的服务*/*

#if !TRANS_CLIENT_SEARCH_PROFILE_ENABLE && CONFIG_BT_SM_SUPPORT_ENABLE \ */*定制搜索ANCS&AMS服务*/*
#define TRANS_ANCS_EN                       1\ */*配置搜索主机的ANCS 服务,要开配对绑定*/*
#define TRANS_AMS_EN                        0\ */*配置搜索主机的ANCS 服务,要开配对绑定*/*
#endif
#endif\ *//#if CONFIG_BT_GATT_CLIENT_NUM*
  • 指定服务搜索,参考文件 ble_trans_search.c

  • 搜索苹果服务ANCS和AMS参考文件 ble_trans_search.c

4.3.1.6. BLE通信提速配置

  • 提供配置支持设备大数据传输提速,主要是支持PDU长包DLE属性 (蓝牙V4.2版本以上,LE Data Length Extension)和2M包属性(蓝牙V5.0版本以上,LE 2M PHY)编码。需要对方也支持对应的Feature属性,传输提速才有效果。

#define CONFIG_BLE_HIGH_SPEED              0 \ *//BLE提速模式: 使能DLE+2M, payload要匹配pdu的包长*

4.3.1.7. BLE优先级高的ATT发送缓存支持

  • 基于有需求在现有的ATT发送缓存的基础上,再添加新的优先级高的ATT发送缓存通道的使用,SDK已添加了对应创建新的数据通道接口(ble_api.h),如下:

/*************************************************************************************************/
/*!
*  \brief      API: 配置ATT,第二套缓存发送模块初始化(优先级比默认缓存高).
*
*  \function   ble_cmd_ret_e ble_op_high_att_send_init(u8 *att_ram_addr,int att_ram_size).
*
*  \param      [in] att_ram_addr      传入ATT发送模块ram地址,地址按4字节对齐.
*  \param      [in] att_ram_size      传入ATT发送模块ram大小.
*
*  \return     see ble_cmd_ret_e.
*
*  \note       必须在原有的缓存初始化使用后,才能再初始化新的缓存使用.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_high_att_send_init(u8 *att_ram_addr,int att_ram_size) */
#define ble_op_high_att_send_init(att_ram_addr,att_ram_size)     \
        ble_user_cmd_prepare(BLE_CMD_HIGH_ATT_SEND_INIT, 2, att_ram_addr,att_ram_size)


/*************************************************************************************************/
/*!
*  \brief      API: ATT操作high 缓存 handle发送数据.
*
*  \function   ble_cmd_ret_e ble_op_high_att_send_data(u16 con_handle,u16 att_handle,u8 *data,u16 len, att_op_type_e att_op_type).
*
*  \param      [in] con_handle     连接 con_handle,range:>0.
*  \param      [in] att_handle     att操作handle.
*  \param      [in] data           数据地址.
*  \param      [in] len            数据长度  <= cbuffer 可写入的长度.
*  \param      [in] att_op_type    see  att_op_type_e (att.h).
*
*  \return     see ble_cmd_ret_e.
*
*  \note
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_high_att_send_data(u16 con_handle,u16 att_handle,u8 *data,u16 len, att_op_type_e att_op_type) */
#define ble_op_high_att_send_data(con_handle,att_handle,data,len,att_op_type)     \
        ble_user_cmd_prepare(BLE_CMD_HIGH_ATT_SEND_DATA, 5, con_handle,att_handle, data, len, att_op_type)

/*************************************************************************************************/
/*!
*  \brief      ATT操作,清high缓存发送的数据缓存
*
*  \function   ble_op_high_att_clear_send_data(void).
*
*  \return     see ble_cmd_ret_e.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_high_att_clear_send_data(void) */
#define ble_op_high_att_clear_send_data(void)     \
        ble_user_cmd_prepare(BLE_CMD_ATT_CLEAR_SEND_DATA, 1, 2)


/*************************************************************************************************/
/*!
*  \brief      API: 获取ATT发送模块,high 缓存cbuffer 可写入数据的长度.
*
*  \function   ble_cmd_ret_e ble_op_high_att_get_remain(u16 con_handle,int *remain_size_ptr).
*
*  \param      [in]  con_handle         range:>0.
*  \param      [out] remain_size_ptr    输出可写入长度值.
*
*  \return     see ble_cmd_ret_e.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_high_att_get_remain(u16 con_handle,int *remain_size_ptr) */
#define ble_op_high_att_get_remain(con_handle,remain_size_ptr)     \
        ble_user_cmd_prepare(BLE_CMD_HIGH_ATT_VAILD_LEN, 2,con_handle, remain_size_ptr)