LE Central

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

central角色特征:

  • 扫描广播数据。

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

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

环境需求

该示例支持以下开发工具包:

环境需求

Hardware Platforms

Board Name

RTL87x2G HDK

RTL87x2G EVB

该示例还需要一个设备运行一个LE peripheral角色,用于发送广播数据。 例如运行Peripheral示例工程的另一块开发板。

更多要求,请参考 快速入门

硬件连线

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

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

配置选项

APP配置选项

APP所有需要配置的内容在 samples\bluetooth\ble_central\src\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存储

生成系统配置文件

开发者可以通过MPTool配置链接个数:

MPTool配置

Configurable Item

Value

LE Maser Link Num

APP_MAX_LINKS

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

编译和下载

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

Project file: samples\bluetooth\ble_central\proj\rtl87x2g\mdk

Project file: samples\bluetooth\ble_central\proj\rtl87x2g\gcc

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

  1. 打开项目文件。

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

  3. 编译成功后,目录 samples\bluetooth\ble_central\proj\rtl87x2g\mdk\bin 下会生成app bin文件 app_MP_sdk_xxx.bin

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

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

测试验证

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

与另一块开发板对测

准备两块开发板分别命名为 DUTTester 。开发者可以使用 DebugAnalyzer 抓取Soc端Log

准备步骤

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

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

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

测试步骤

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

如果成功启动广播,会打印下面的 DebugAnalyser 日志。 如果没有看到下列日志,说明广播启动失败。请检查软件和硬件环境。

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

如果 DUT 成功启动且 serial port assistant tool 配置成功, 开发者会看到如下日志。串口调试助手端会显示本地地址。 如果没有看到下列日志,说明广播启动失败。请检查软件和硬件环境。

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

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

Step

DUT user command

Description

DUT Log

1

scan 0

开始扫描附近的LE设备

DebugAnalyser shows:

[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

DebugAnalyser shows:

[APP] !**GAP scan stop

3

showdev

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

Serial port assistant tool shows:

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 shows:

Connected success conn_id 0

5

showcon

显示连接信息

Serial port assistant tool shows:

ShowCon conn_id 0 state 0x00000002 role 1

RemoteBd = [00:11:22:33:44:80] type = 0

6

conupdreq 0 1

发起连接参数更新请求

DebugAnalyser shows:

[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 shows:

Pair success

8

basread 0 0

读battery level值

DebugAnalyser shows:

[APP] !**BAS_READ_BATTERY_LEVEL: battery level 90

9

gapread 0 0

读设备名

DebugAnalyser shows:

[APP] !**GAPS_READ_DEVICE_NAME: device name BLE_PERIPHERAL.

10

disc 0

和peripheral设备断线

Serial port assistant tool shows:

Disconnect conn_id 0

代码介绍

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

源码路径

  • 工程目录: samples\bluetooth\ble_central\proj

  • 源码目录: samples\bluetooth\ble_central\src

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

└── Project: central
    └── secure_only_app
        └── Device                   includes startup code
        ├── CMSE Library             Non-secure callable lib
        ├── Lib                      includes all binary symbol files that user application is built on
            ├── ROM_NS.lib
            └── lowerstack.lib
            └── rtl87x2g_sdk.lib
            └── gap_utils.lib
        ├── 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
            ├── main.c
            ├── central_app.c
            ├── data_uart.c
            ├── link_mgr.c
            ├── user_cmd.c
            └── user_cmd_parse.c

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

BT Host介绍

示例工程使用 bt_host_0_0 中的默认BT Host image,更多信息可参阅 BT Host Image

关于BT Host所支持的蓝牙功能的详细信息,请参阅文件 bin\rtl87x2g\bt_host_image\bt_host_0_0\bt_host_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 flogs. */
    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从GAP层接收到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 BLE Service Client
    simple_ble_client_id 是simple BLE 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

app_discov_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 BLE service procedure successfully done. */
       APP_PRINT_INFO0("app_client_callback: discover gaps procedure done.");
       break;
   ......
   case DISC_SIMP_DONE:
       /* Discovery Simple BLE 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

app_discov_services() 流程图 (F_BT_GATT_SRV_HANDLE_STORAGE)