LE Central

ble_central 示例工程可以作为开发基于 Central 角色的 APP 的框架。

Central 角色特征:

  • 扫描广播数据。

  • 作为 Central 角色发起建立连线。

  • 与多个 Peripheral 设备建立连线。

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

为快速搭建起开发环境,可参考 快速入门 中提供的详细指导。

硬件连线

请参考 快速入门 中的 RTL8752H EVB 接口和模块

该示例工程需要支持用户命令接口。 具体连线介绍请参考 用户命令接口 中的 Data UART 连接

配置

配置选项

该示例工程全部配置选项在 src\sample\ble_central\app_flags.h 中, 开发者可以根据实际需求配置。

/** @brief  Config APP LE link number */
#define APP_MAX_LINKS  2
/** @brief  Config GATT services storage: 0-Not save, 1-Save to flash
*
* If configure to 1, the GATT services discovery results will save to the flash.
*/
#define F_BT_GATT_SRV_HANDLE_STORAGE            0

开发者可以通过 F_BT_GATT_SRV_HANDLE_STORAGE 宏打开或关闭 GATT Services 的 handles 存储模块。更多信息请参考 代码介绍 中的 GATT Services 的 handles 存储

生成系统配置文件

开发者可以通过 MP Tool 配置以下项目:

MP Tool 配置

Configurable Item

Value

LE Maser Link Num

APP_MAX_LINKS

更多有关于 MP Tool 配置的信息请参考 快速入门 中的 生成 System Config File

编译和下载

该示例可以在 SDK 文件夹中找到:

Project file: board\evb\ble_central\mdk

Project file: board\evb\ble_central\gcc

编译和运行该示例请遵循以下步骤:

  1. 打开项目文件。

  2. 要编译目标文件,请参考 快速入门编译 APP Image 中列出的步骤。

  3. 编译成功后,目录 board\evb\ble_central\mdk\bin 下会生成 app bin 文件 app_MP_sdk_xxx.bin

  4. 要下载 app bin 到 EVB,请参考 快速入门MP Tool 中列出的步骤。

  5. 在 EVB 上按下 reset 键,程序将开始运行。

测试验证

将示例工程烧录到 EVB 后,开发者可以使用另一个运行 ble_peripheral 示例工程的开发板进行测试。

与另一块开发板对测

准备两块开发板分别命名为 DUTTester 。开发者可以使用 Debug AnalyzerLog 验证

准备步骤

  1. 使用 MP Tool 将 DUT 地址设为 [00:11:22:33:44:85],然后编译 ble_central 示例工程,并将 images 下载至 DUT 。 开发者可以在电脑的串口助手工具端输入用户命令,请参考 用户命令接口 中的 如何使用命令

  2. 使用 MP Tool 将 Tester 地址设为 [00:11:22:33:44:80],然后编译 ble_peripheral 示例工程,并下载 images 至 Tester 。 更多信息请参考 LE Peripheral 中的 编译和下载

更多关于如何修改 蓝牙地址 的细节请查阅 快速入门 中的 生成 System Config File

测试步骤

  1. Tester 上的 reset 键, Tester 将开始发送可连接的非定向广播。

    1. 如果成功启动广播,会打印下面的 Debug Analyzer 日志。

    2. 如果没有看到下列日志,说明广播启动失败。请检查软件和硬件环境。

    [APP] !**GAP adv start
    
  2. DUT 上的 reset 键,开发者可以在电脑的串口助手工具端输入用户命令。

    1. 如果 DUT 成功启动且串口辅助工具配置成功,开发者会看到如下日志。串口辅助工具端会显示本地地址。

    2. 如果没有看到下列日志,请检查软件和硬件环境。

    local bd addr: xx:xx:xx:xx:xx:xx
    

    一条连线的测试流程如下:

    Step

    DUT User Command

    Description

    DUT Log

    1

    scan 0

    开始扫描附近的 LE 设备

    Debug Analyzer 显示:

    [APP] !**GAP scan start
    
    [APP] GAP_MSG_LE_SCAN_INFO: bd_addr 00::11::22::33::44::80, bdtype 0, adv_type 0x0, rssi -17, data_len 23
    

    2

    stopscan

    停止 scan

    Debug Analyzer 显示:

    [APP] !**GAP scan stop
    

    3

    showdev

    显示扫描设备列表,设备列表根据 simple LE service 的 UUID 筛选

    Serial port assistant tool 显示:

    Advertising and Scan response: filter uuid = 0xA00A dev list
    
    RemoteBd[0] = [00:11:22:33:44:80] type = 0
    

    4

    condev 0

    发起建立连线

    Serial port assistant tool 显示:

    Connected success conn_id 0
    

    5

    showcon

    显示连接信息

    Serial port assistant tool 显示:

    ShowCon conn_id 0 state 0x00000002 role 1
    
    RemoteBd = [00:11:22:33:44:80] type = 0
    

    6

    conupdreq 0 1

    发起连接参数更新请求

    Debug Analyzer 显示:

    [APP] !**app_handle_conn_param_update_evt update success:conn_id 0, conn_interval 0xa, conn_slave_latency 0x0, conn_supervision_timeout 0x3e8
    

    7

    sauth 0

    发送配对请求

    Serial port assistant tool 显示:

    Pair success
    

    8

    basread 0 0

    读 battery level 值

    Debug Analyzer 显示:

    [APP] !**BAS_READ_BATTERY_LEVEL: battery level 90
    

    9

    gapread 0 0

    读设备名

    Debug Analyzer 显示:

    [APP] !**GAPS_READ_DEVICE_NAME: device name BLE_PERIPHERAL
    

    10

    disc 0

    和 peripheral 设备断线

    Serial port assistant tool 显示:

    Disconnect conn_id 0
    

代码介绍

本章的主要目的是帮助开发者熟悉 Central Role 相关的开发流程。本章将按照以下几个部分进行介绍:

源码路径

  • 工程目录: board\evb\ble_central

  • 源码目录: src\sample\ble_central

ble_central 示例工程中的源文件当前被分为几个组,如下所示。

└── Project: central
    └── central
        └── include
        ├── lib                      Includes all binary symbol files that user application is built on.
            ├── ROM.lib
            ├── gap_utils.lib
            ├── lowerstack.lib
            ├── rtl8752h_sdk.lib
        ├── cmsis                    Includes startup code.
        ├── peripheral               Includes all peripheral drivers and module code used by the application.
        ├── profile                  Includes LE profiles or services used by the sample application.
        └── app                      Includes the ble_central user application implementation.
            ├── app_task.c
            ├── central_app.c
            ├── link_mgr.c
            ├── main.c
            ├── user_cmd.c
            ├── overlay_mgr.c
            ├── data_uart.c
            └── user_cmd_parse.c

示例工程使用与 upperstack_0_0 匹配的默认 GAP LIB,请参阅文档 Host Image 中的 GAP LIB 的使用方法 以获取更多信息。

Bluetooth Host 介绍

示例工程默认使用 Bluetooth Host image 版本 upperstack_0_0,更多信息可参阅 Host Image

关于 Bluetooth Host 所支持的蓝牙功能的详细信息,请参阅文件 bin\upperstack_img\upperstack_0_0\upperstack_config.h

初始化

当 EVB 启动并且芯片复位时, main() 函数将被调用,它执行以下初始化函数:

int main(void)
{
    board_init();
    le_gap_init(APP_MAX_LINKS);
    gap_lib_init();
    app_le_gap_init();
    app_le_profile_init();
    pwr_mgr_init();
    task_init();
    os_sched_start();

    return 0;
}
void app_le_gap_init(void)
{
    /* Device name and device appearance */
    ......


    /* Scan parameters */
    ......

    /* GAP Bond Manager parameters */
    ......

    /* Set device name and device appearance */
    le_set_gap_param(GAP_PARAM_DEVICE_NAME, GAP_DEVICE_NAME_LEN, device_name);
    le_set_gap_param(GAP_PARAM_APPEARANCE, sizeof(appearance), &appearance);

    /* Set scan parameters */
    le_scan_set_param(GAP_PARAM_SCAN_MODE, sizeof(scan_mode), &scan_mode);
    le_scan_set_param(GAP_PARAM_SCAN_INTERVAL, sizeof(scan_interval), &scan_interval);
    le_scan_set_param(GAP_PARAM_SCAN_WINDOW, sizeof(scan_window), &scan_window);
    le_scan_set_param(GAP_PARAM_SCAN_FILTER_POLICY, sizeof(scan_filter_policy),
                      &scan_filter_policy);
    le_scan_set_param(GAP_PARAM_SCAN_FILTER_DUPLICATES, sizeof(scan_filter_duplicate),
                      &scan_filter_duplicate);

    /* Setup the GAP Bond Manager */
    gap_set_param(GAP_PARAM_BOND_PAIRING_MODE, sizeof(auth_pair_mode), &auth_pair_mode);
    gap_set_param(GAP_PARAM_BOND_AUTHEN_REQUIREMENTS_FLAGS, sizeof(auth_flags), &auth_flags);
    gap_set_param(GAP_PARAM_BOND_IO_CAPABILITIES, sizeof(auth_io_cap), &auth_io_cap);
    gap_set_param(GAP_PARAM_BOND_OOB_ENABLED, sizeof(auth_oob), &auth_oob);
    le_bond_set_param(GAP_PARAM_BOND_FIXED_PASSKEY, sizeof(auth_fix_passkey), &auth_fix_passkey);
    le_bond_set_param(GAP_PARAM_BOND_FIXED_PASSKEY_ENABLE, sizeof(auth_use_fix_passkey),
                      &auth_use_fix_passkey);
    le_bond_set_param(GAP_PARAM_BOND_SEC_REQ_REQUIREMENT, sizeof(auth_sec_req_flags),
                      &auth_sec_req_flags);

    /* register gap message callback */
    le_register_app_cb(app_gap_callback);
}

更多关于 LE GAP 初始化和启动流程的信息可以查阅 LE Host 中的 GAP 参数的初始化

多链路管理

应用的链路控制模块在 link_mgr.h 中定义。

typedef struct
{
    T_GAP_CONN_STATE        conn_state;          /**< Connection state. */
    uint8_t                 discovered_flags;    /**< discovered flags. */
    uint8_t                 srv_found_flags;     /**< service founded flags. */
    T_GAP_REMOTE_ADDR_TYPE  bd_type;             /**< remote BD type*/
    uint8_t                 bd_addr[GAP_BD_ADDR_LEN]; /**< remote BD */
 } T_APP_LINK;
 extern T_APP_LINK app_link_table[APP_MAX_LINKS];

app_link_table 用于保存链路相关信息。

GAP 消息处理

当 APP 从 Bluetooth Host 接收到 GAP 消息时,函数 app_handle_gap_msg() 会被调用。 更多关于 GAP 消息的信息可以查阅 LE Host 中的 蓝牙状态消息

void app_handle_gap_msg(T_IO_MSG *p_gap_msg)
{
    T_LE_GAP_MSG gap_msg;
    uint8_t conn_id;
    memcpy(&gap_msg, &p_gap_msg->u.param, sizeof(p_gap_msg->u.param));

    APP_PRINT_TRACE1("app_handle_gap_msg: subtype %d", p_gap_msg->subtype);
    switch (p_gap_msg->subtype)
    {
    case GAP_MSG_LE_DEV_STATE_CHANGE:
        {
            app_handle_dev_state_evt(gap_msg.msg_data.gap_dev_state_change.new_state,
                                    gap_msg.msg_data.gap_dev_state_change.cause);
        }
        break;

    ......

    default:
        APP_PRINT_ERROR1("app_handle_gap_msg: unknown subtype %d", p_gap_msg->subtype);
        break;
    }
}

当收到 GAP_MSG_LE_CONN_STATE_CHANGE 消息后, app_handle_conn_state_evt() 会更新 app_link_table 中的信息。

void app_handle_conn_state_evt(uint8_t conn_id, T_GAP_CONN_STATE new_state, uint16_t disc_cause)
{
   if (conn_id >= APP_MAX_LINKS)
   {
       return;
   }

   APP_PRINT_INFO4("app_handle_conn_state_evt: conn_id %d, conn_state(%d -> %d), disc_cause 0x%x",
                   conn_id, app_link_table[conn_id].conn_state, new_state, disc_cause);

   app_link_table[conn_id].conn_state = new_state;
   switch (new_state)
   {
   case GAP_CONN_STATE_DISCONNECTED:
       {
           if ((disc_cause != (HCI_ERR | HCI_ERR_REMOTE_USER_TERMINATE))
               && (disc_cause != (HCI_ERR | HCI_ERR_LOCAL_HOST_TERMINATE)))
           {
               APP_PRINT_ERROR2("app_handle_conn_state_evt: connection lost, conn_id %d, cause 0x%x", conn_id,
                               disc_cause);
           }

           data_uart_print("Disconnect conn_id %d\r\n", conn_id);
           memset(&app_link_table[conn_id], 0, sizeof(T_APP_LINK));
       }
       break;

   case GAP_CONN_STATE_CONNECTED:
       {
           le_get_conn_addr(conn_id, app_link_table[conn_id].bd_addr,
                           &app_link_table[conn_id].bd_type);
           data_uart_print("Connected success conn_id %d\r\n", conn_id);
       }
       break;

   default:
       break;

   }
}

Profile 消息回调函数

当 APP 使用 xxx_add_client() 注册 specific client 时,应同步注册用于处理 specific client 消息的回调函数。 APP 可以针对不同的 clients 注册不同的回调函数,也可以注册通用回调函数以处理 specific clients 的消息。 app_client_callback() 是通用回调函数,根据 client ID 区分不同 clients,更多信息参见 LE Host 中的 GATT Profile Client

void app_le_profile_init(void)
{
   client_init(3);
   gaps_client_id  = gaps_add_client(app_client_callback, APP_MAX_LINKS);
   simple_ble_client_id = simp_ble_add_client(app_client_callback, APP_MAX_LINKS);
   bas_client_id = bas_add_client(app_client_callback, APP_MAX_LINKS);
}
  • GAP Service Client

    gaps_client_id 是 GAP Service Client 的 client ID。

    T_APP_RESULT app_client_callback(T_CLIENT_ID client_id, uint8_t conn_id, void *p_data)
    {
        T_APP_RESULT  result = APP_RESULT_SUCCESS;
        APP_PRINT_INFO2("app_client_callback: client_id %d, conn_id %d",
                        client_id, conn_id);
        if (client_id == gaps_client_id)
        {
            T_GAPS_CLIENT_CB_DATA *p_gaps_cb_data = (T_GAPS_CLIENT_CB_DATA *)p_data;
            switch (p_gaps_cb_data->cb_type)
            {
            case GAPS_CLIENT_CB_TYPE_DISC_STATE:
            ......
            }
        }
      }
    
  • Battery Service Client

    bas_client_id 是 battery service client 的 client ID。

    T_APP_RESULT app_client_callback(T_CLIENT_ID client_id, uint8_t conn_id, void *p_data)
    {
        T_APP_RESULT  result = APP_RESULT_SUCCESS;
        APP_PRINT_INFO2("app_client_callback: client_id %d, conn_id %d",
                        client_id, conn_id);
        else if (client_id == bas_client_id)
        {
            T_BAS_CLIENT_CB_DATA *p_bas_cb_data = (T_BAS_CLIENT_CB_DATA *)p_data;
            switch (p_bas_cb_data->cb_type)
            {
            case BAS_CLIENT_CB_TYPE_DISC_STATE:
            ......
            }
        }
      }
    
  • Simple LE Service Client

    simple_ble_client_id 是 simple LE service client 的 client ID。

    T_APP_RESULT app_client_callback(T_CLIENT_ID client_id, uint8_t conn_id, void *p_data)
    {
        T_APP_RESULT  result = APP_RESULT_SUCCESS;
        APP_PRINT_INFO2("app_client_callback: client_id %d, conn_id %d",
                        client_id, conn_id);
        else if (client_id == simple_ble_client_id)
        {
          T_SIMP_CLIENT_CB_DATA *p_simp_client_cb_data = (T_SIMP_CLIENT_CB_DATA *)p_data;
          uint16_t value_size;
          uint8_t *p_value;
          switch (p_simp_client_cb_data->cb_type)
          {
          case SIMP_CLIENT_CB_TYPE_DISC_STATE:
            ......
          }
        }
      }
    

Discover GATT Services 流程

当收到 GAP_MSG_LE_CONN_MTU_INFO 消息之后,Central 示例工程将发起查询服务流程。在 app_discov_services() 中定义 Discover GATT Services 流程。更多信息参见 central_app.c 中的 app_discov_services()

void app_handle_conn_mtu_info_evt(uint8_t conn_id, uint16_t mtu_size)
{
   APP_PRINT_INFO2("app_handle_conn_mtu_info_evt: conn_id %d, mtu_size %d", conn_id, mtu_size);
   app_discov_services(conn_id, true);
}

app_discov_services() 的流程图如图所示:

../../../../_images/app_discov_services_Flow_Chart.png

Discover Services 流程图

当收到 DISC_GAPS_DONEDISC_SIMP_DONEDISC_BAS_DONE 时,APP 将调用 app_discov_services() ,相关代码如下:

T_APP_RESULT app_client_callback(T_CLIENT_ID client_id, uint8_t conn_id, void *p_data)
{
   ......
   case DISC_GAPS_DONE:
       app_link_table[conn_id].discovered_flags |= APP_DISCOV_GAPS_FLAG;
       app_link_table[conn_id].srv_found_flags |= APP_DISCOV_GAPS_FLAG;
       app_discov_services(conn_id, false);
       /* Discovery Simple LE service procedure successfully done. */
       APP_PRINT_INFO0("app_client_callback: discover gaps procedure done.");
       break;
   ......
   case DISC_SIMP_DONE:
       /* Discovery Simple LE service procedure successfully done. */
       app_link_table[conn_id].discovered_flags |= APP_DISCOV_SIMP_FLAG;
       app_link_table[conn_id].srv_found_flags |= APP_DISCOV_SIMP_FLAG;
       app_discov_services(conn_id, false);
       APP_PRINT_INFO0("app_client_callback: discover simp procedure done.");
       break;
   ......
   case DISC_BAS_DONE:
       /* Discovery BAS procedure successfully done. */
       app_link_table[conn_id].discovered_flags |= APP_DISCOV_BAS_FLAG;
       app_link_table[conn_id].srv_found_flags |= APP_DISCOV_BAS_FLAG;
       app_discov_services(conn_id, false);
       APP_PRINT_INFO0("app_client_callback: discover bas procedure done");
       break;
   ......
 }

GATT Services 的 handles 存储

相关代码如下:

typedef struct
{
   uint8_t      srv_found_flags;
   uint8_t      bd_type;         /**< remote BD type*/
   uint8_t      bd_addr[GAP_BD_ADDR_LEN];  /**< remote BD */
   uint32_t     reserved;
   uint16_t     gaps_hdl_cache[HDL_GAPS_CACHE_LEN];
   uint16_t     simp_hdl_cache[HDL_SIMBLE_CACHE_LEN];
   uint16_t     bas_hdl_cache[HDL_BAS_CACHE_LEN];
} T_APP_SRVS_HDL_TABLE;
#if F_BT_GATT_SRV_HANDLE_STORAGE
uint32_t app_save_srvs_hdl_table(T_APP_SRVS_HDL_TABLE *p_info);
uint32_t app_load_srvs_hdl_table(T_APP_SRVS_HDL_TABLE *p_info);
#endif
  • app_save_srvs_hdl_table() 用于将 handles 信息保存到 Flash 中。

  • app_load_srvs_hdl_table() 用于从 Flash 中载入 handles 信息。

  • app_discov_services() 流程图如图所示:

../../../../_images/app_discov_services_Flow_Chart_F_BT_GATT_SRV_HANDLE_STORAGE.png

Discover Services 流程图 (F_BT_GATT_SRV_HANDLE_STORAGE)