LE Peripheral

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

peripheral角色特征:

  • 可以发送可连接广播。

  • 可以作为Peripheral角色与其他设备建立连线。

环境需求

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

环境需求

Hardware Platforms

Board Name

RTL87x2G HDK

RTL87x2G EVB

该示例还需要一个设备运行一个LE GAP central角色,用于扫描广播数据。 例如运行Central示例工程的另一块开发板,或者装有 LightBlue 之类LE应用的手机。

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

硬件连线

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

配置选项

APP配置选项

APP所有需要配置的内容在 samples\bluetooth\ble_peripheral\src\app_flags.h 中, 开发者可以根据实际需求配置。

/** @brief  Config APP LE link number */
#define APP_MAX_LINKS  1
/** @brief  Config DLPS: 0-Disable DLPS, 1-Enable DLPS */
#define F_BT_DLPS_EN                        0
/** @brief  Config ANCS Client: 0-Not built in, 1-Open ANCS client function */
#define F_BT_ANCS_CLIENT_SUPPORT            0
#define F_BT_ANCS_APP_FILTER                (F_BT_ANCS_CLIENT_SUPPORT & 1)
#define F_BT_ANCS_GET_APP_ATTR              (F_BT_ANCS_CLIENT_SUPPORT & 0)
/** @brief  Config ANCS Client debug log: 0-close, 1-open  */
#define F_BT_ANCS_CLIENT_DEBUG              (F_BT_ANCS_CLIENT_SUPPORT & 0)

开发者可以通过 F_BT_ANCS_CLIENT_SUPPORT 宏打开或关闭ANCS Client模块。更多信息请参考 ANCS Client

生成系统配置文件

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

MPTool配置

Configurable Item

Value

LE Slave Link Num

APP_MAX_LINKS

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

编译和下载

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

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

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

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

  1. 打开项目文件。

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

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

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

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

测试验证

将示例工程烧录到EVB后,开发者可以使用另一个运行 LE Central 工程的开发板或者装有 LightBlue 之类LE应用的手机来进行测试。

与另一块开发板对测

请参考 LE Central 中的 与另一块开发板对测

与手机对测

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

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

[APP] !**GAP adv start
  1. 在iOS设备上打开 LightBlue 搜索 DUT,如图所示:

../../../../../_images/test_with_ios.jpg

与iOS设备对测

开发者可以在手机端与peripheral设备进行扫描,连接,配对,查服务,删除绑定信息,断线等交互。

代码介绍

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

源码路径

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

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

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

└── Project: peripheral
    └── 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_peripheral user application implementation
            ├── ancs.c
            ├── app_task.c
            ├── main.c
            └── peripheral_app.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 */
    ......


    /* Advertising parameters */
    ......

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

    /* Set advertising parameters */
    le_adv_set_param(GAP_PARAM_ADV_EVENT_TYPE, sizeof(adv_evt_type), &adv_evt_type);
    le_adv_set_param(GAP_PARAM_ADV_DIRECT_ADDR_TYPE, sizeof(adv_direct_type), &adv_direct_type);
    le_adv_set_param(GAP_PARAM_ADV_DIRECT_ADDR, sizeof(adv_direct_addr), adv_direct_addr);
    le_adv_set_param(GAP_PARAM_ADV_CHANNEL_MAP, sizeof(adv_chann_map), &adv_chann_map);
    le_adv_set_param(GAP_PARAM_ADV_FILTER_POLICY, sizeof(adv_filter_policy), &adv_filter_policy);
    le_adv_set_param(GAP_PARAM_ADV_INTERVAL_MIN, sizeof(adv_int_min), &adv_int_min);
    le_adv_set_param(GAP_PARAM_ADV_INTERVAL_MAX, sizeof(adv_int_max), &adv_int_max);
    le_adv_set_param(GAP_PARAM_ADV_DATA, sizeof(adv_data), (void *)adv_data);
    le_adv_set_param(GAP_PARAM_SCAN_RSP_DATA, sizeof(scan_rsp_data), (void *)scan_rsp_data);

    /* 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);
}

peripheral模式下的设备将发送可连接广播,因此 adv_evt_type 参数应被配置为 GAP_ADTYPE_ADV_IND 类型。

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

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;
    }
}

Peripheral示例工程会在收到 GAP_INIT_STATE_STACK_READY 时调用 le_adv_start() 来启动广播。 如果 le_adv_start() 返回 true ,说明Bluetooth Host已经接收了这个命令并准备执行,当这个命令执行完成后,Bluetooth Host将发送 GAP_ADV_STATE_ADVERTISINGapp_handle_dev_state_evt 。 当Peripheral示例工程在开发板上运行时,设备将发送可连接广播, 可以被对端设备扫描到并作为Peripheral角色建立连线。

void app_handle_dev_state_evt(T_GAP_DEV_STATE new_state, uint16_t cause)
{
    APP_PRINT_INFO3("app_handle_dev_state_evt: init state %d, adv state %d, cause 0x%x",
                    new_state.gap_init_state, new_state.gap_adv_state, cause);
    if (gap_dev_state.gap_init_state != new_state.gap_init_state)
    {
        if (new_state.gap_init_state == GAP_INIT_STATE_STACK_READY)
        {
            APP_PRINT_INFO0("GAP stack ready");
            /*stack ready*/
            le_adv_start();
        }
    }

    if (gap_dev_state.gap_adv_state != new_state.gap_adv_state)
    {
        if (new_state.gap_adv_state == GAP_ADV_STATE_IDLE)
        {
            APP_PRINT_INFO0("GAP adv stoped");
        }
        else if (new_state.gap_adv_state == GAP_ADV_STATE_ADVERTISING)
        {
            APP_PRINT_INFO0("GAP adv start");
        }
    }

    gap_dev_state = new_state;
}

Peripheral示例工程收到 GAP_CONN_STATE_DISCONNECTED 消息后,会再次调用 le_adv_start() 函数开始广播。 断连后,Peripheral示例工程会被重置为可发现且可连接的状态。

void app_handle_conn_state_evt(uint8_t conn_id, T_GAP_CONN_STATE new_state, uint16_t disc_cause)
{
   APP_PRINT_INFO4("app_handle_conn_state_evt: conn_id %d old_state %d new_state %d, disc_cause 0x%x",
                   conn_id, gap_conn_state, new_state, disc_cause);
   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_ERROR1("app_handle_conn_state_evt: connection lost cause 0x%x", disc_cause);
           }

           le_adv_start();
       }
       break;
   ......
   }
 }

Profile消息回调函数

当APP使用 xxx_add_service 注册specific service时,APP需要注册回调函数以处理specific service的消息。APP需要调用 server_register_app_cb() 注册回调函数以处理profile server layer的信息。

APP可以针对不同的services注册不同的回调函数,也可以注册通用回调函数以处理specific services和profile server layer的消息。

app_profile_callback() 是通用回调函数。 app_profile_callback() 可以通过 service id 识别不同的服务。

void app_le_profile_init(void)
{
   server_init(2);
   simp_srv_id = simp_ble_service_add_service(app_profile_callback);
   bas_srv_id  = bas_add_service(app_profile_callback);
   server_register_app_cb(app_profile_callback);
 }

更多信息可查阅 LE Host 中的 Bluetooth LE Profile Server

  • 通用profile server回调函数
    SERVICE_PROFILE_GENERAL_ID 是profile server layer使用的service id。profile server layer包含两种消息类型:
    • PROFILE_EVT_SRV_REG_COMPLETE
      该消息用于通知GAP启动流程中的服务注册流程已经结束。
    • PROFILE_EVT_SEND_DATA_COMPLETE
      profile server layer使用该消息向APP通知发送notification/indication的结果。
    T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
    {
        T_APP_RESULT app_result = APP_RESULT_SUCCESS;
        if (service_id == SERVICE_PROFILE_GENERAL_ID)
        {
            T_SERVER_APP_CB_DATA *p_param = (T_SERVER_APP_CB_DATA *)p_data;
            switch (p_param->eventId)
            {
            case PROFILE_EVT_SRV_REG_COMPLETE:// srv register result event.
                APP_PRINT_INFO1("PROFILE_EVT_SRV_REG_COMPLETE: result %d",
                                p_param->event_data.service_reg_result);
                break;
    
            case PROFILE_EVT_SEND_DATA_COMPLETE:
                ......
                break;
            ......
    }
    
  • Battery Service
    bas_srv_id 是battery service的service id。
    T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
    {
        T_APP_RESULT app_result = APP_RESULT_SUCCESS;
        ......
        else if (service_id == bas_srv_id)
        {
          T_BAS_CALLBACK_DATA *p_bas_cb_data = (T_BAS_CALLBACK_DATA *)p_data;
          switch (p_bas_cb_data->msg_type)
          {
          case SERVICE_CALLBACK_TYPE_INDIFICATION_NOTIFICATION:
                ......
    }
    
  • Simple BLE Service
    simp_srv_id 是simple Bluetooth LE service的service id。
    T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
    {
        T_APP_RESULT app_result = APP_RESULT_SUCCESS;
        ......
        else  if (service_id == simp_srv_id)
        {
          TSIMP_CALLBACK_DATA *p_simp_cb_data = (TSIMP_CALLBACK_DATA *)p_data;
          switch (p_simp_cb_data->msg_type)
          {
          case SERVICE_CALLBACK_TYPE_INDIFICATION_NOTIFICATION:
              ......
    }
    

ANCS Client

ancs.cancs.h 中定义ANCS程序。若其他APP需要使用该功能,流程如下:

  1. ancs.hancs.c 拷贝到APP目录中。

  2. auth_sec_req_enable 参数配置为 true

    void app_le_gap_init(void)
    {
    #if F_BT_ANCS_CLIENT_SUPPORT
        uint8_t  auth_sec_req_enable = true;
    #else
        uint8_t  auth_sec_req_enable = false;
    #endif
        uint16_t auth_sec_req_flags = GAP_AUTHEN_BIT_BONDING_FLAG;
        ......
    }
    
  3. app_le_profile_init() 中初始化ANCS Client。

    void app_le_profile_init(void)
    {
        ......
    #if F_BT_ANCS_CLIENT_SUPPORT
        client_init(1);
        ancs_init(APP_MAX_LINKS);
    #endif
    }
    
  4. 当authentication流程完成后,启动ANCS discovery流程。

    void app_handle_authen_state_evt(uint8_t conn_id, uint8_t new_state, uint16_t cause)
    {
        ......
        case GAP_AUTHEN_STATE_COMPLETE:
          {
                if (cause == GAP_SUCCESS)
                {
        #if F_BT_ANCS_CLIENT_SUPPORT
                    ancs_start_discovery(conn_id);
        #endif
                    APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_COMPLETE pair success");
    
                }
                else
                {
                    APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_COMPLETE pair failed");
                }
          }
          break;
        ......
    }
    
  5. ancs.c 中自定义ANCS程序。

常见问题

  1. 如何修改advertising data参数?

Advertising data定义在数组 adv_data 中, 开发者可以直接修改数组的内容来修改广播数据。

static const uint8_t adv_data[] =
{
    /* Flags */
    0x02,             /* length */
    GAP_ADTYPE_FLAGS, /* type="Flags" */
    GAP_ADTYPE_FLAGS_LIMITED | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
    /* Service */
    0x03,             /* length */
    GAP_ADTYPE_16BIT_COMPLETE,
    LO_WORD(GATT_UUID_SIMPLE_PROFILE),
    HI_WORD(GATT_UUID_SIMPLE_PROFILE),
    /* Local name */
    0x0F,             /* length */
    GAP_ADTYPE_LOCAL_NAME_COMPLETE,
    'B', 'L', 'E', '_', 'P', 'E', 'R', 'I', 'P', 'H', 'E', 'R', 'A', 'L',
};