LE Peripheral
LE peripheral示例工程可以作为开发基于Peripheral角色的APP的框架。
Peripheral角色特征:
可以发送可连接广播。
可以作为Peripheral角色与其他设备建立连线。
环境需求
该示例支持以下开发工具包:
Hardware Platforms |
Board Name |
---|---|
RTL87x2G HDK |
RTL87x2G EVB |
为快速搭建起开发环境,可参考 快速入门 中提供的详细指导。
硬件连线
配置
配置选项
该示例工程全部配置选项在
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 。
生成系统配置文件
开发者可以通过MP Tool配置以下项目:
Configurable Item |
Value |
---|---|
LE Slave Link Num |
≥ APP_MAX_LINKS |
更多有关于MP Tool配置的信息请参考 快速入门 中的 生成System Config File。
编译和下载
该示例可以在SDK文件夹中找到:
Project file: samples\bluetooth\ble_peripheral\proj\rtl87x2g\mdk
Project file: samples\bluetooth\ble_peripheral\proj\rtl87x2g\gcc
编译和运行该示例请遵循以下步骤:
打开项目文件。
要编译目标文件,请参考 快速入门 的 编译APP Image 中列出的步骤。
编译成功后,目录
samples\bluetooth\ble_peripheral\proj\rtl87x2g\mdk\bin
下会生成app bin文件app_MP_sdk_xxx.bin
。要下载app bin到 EVB,请参考 快速入门 的 MP Tool 下载 中列出的步骤。
在EVB上按下 reset 键,程序将开始运行。
测试验证
将示例工程烧录到EVB后,开发者可以使用另一个运行 LE Central 工程的开发板或者装有 LightBlue
之类LE应用的手机来进行测试。
与另一块开发板对测
请参考 LE Central 中的 与另一块开发板对测。
与手机对测
代码介绍
本章的主要目的是帮助开发者熟悉Peripheral Role相关的开发流程。本章将按照以下几个部分进行介绍:
源码路径 中介绍项目目录和源代码文件。
Bluetooth Host介绍 中介绍了Bluetooth Host相关信息。
初始化 中介绍Peripheral相关参数和Bluetooth Host初始化。
GAP消息处理 中介绍Peripheral相关的GAP消息处理。
Profile消息回调函数 中介绍了Peripheral相关的Profile回调函数处理。
ANCS Client 介绍了如何使用ANCS Client模块。
源码路径
工程目录:
samples\bluetooth\ble_peripheral\proj
。源码目录:
samples\bluetooth\ble_peripheral\src
。
LE peripheral示例工程中的源文件当前被分为几个组,如下所示。
└── Project: peripheral
└── secure_only_app
└── Device includes startup code
├── CMSE Library Non-secure callable library
├── 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,请参阅文档 Bluetooth Host Image 中的 GAP LIB的使用方法 以获取更多信息。
Bluetooth Host介绍
示例工程默认使用Bluetooth Host image版本 bt_host_0_0
,更多信息可参阅
Bluetooth Host Image。
关于Bluetooth 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;
}
le_gap_init()
用于初始化GAP并配置链接个数。app_le_profile_init()
用于初始化profile。app_le_gap_init()
用于初始化GAP参数,开发者可以自行配置以下参数:Device name和device appearance,Advertising参数,GAP Bond Manager参数(请参考 LE Host 中的 Device Name和Device Appearance的配置, Advertising参数的配置, Bond Manager参数的配置)。
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
类型。
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;
}
}
Peripheral示例工程会在收到 GAP_INIT_STATE_STACK_READY
时调用 le_adv_start()
来启动广播。
如果 le_adv_start()
返回 true
,说明Bluetooth Host已经接收了这个命令并准备执行,当这个命令执行完成后,Bluetooth Host将发送
GAP_ADV_STATE_ADVERTISING
给 app_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 中的 GATT Profile Server。
通用profile server回调函数
SERVICE_PROFILE_GENERAL_ID
是profile server layer使用的service ID。Profile server layer包含两种消息类型:-
该消息用于通知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 LE 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.c
和 ancs.h
中定义ANCS程序。若其他APP需要使用该功能,流程如下:
将
ancs.h
和ancs.c
拷贝到APP目录中。将
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; ...... }
在
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 }
当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; ...... }
在
ancs.c
中自定义ANCS程序。
常见问题
如何修改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',
};