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 配置以下项目:
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
编译和运行该示例请遵循以下步骤:
打开项目文件。
要编译目标文件,请参考 快速入门 的 编译 APP Image 中列出的步骤。
编译成功后,目录
board\evb\ble_central\mdk\bin
下会生成 app bin 文件app_MP_sdk_xxx.bin
。在 EVB 上按下 reset 键,程序将开始运行。
测试验证
将示例工程烧录到 EVB 后,开发者可以使用另一个运行 ble_peripheral 示例工程的开发板进行测试。
与另一块开发板对测
准备两块开发板分别命名为 DUT 和 Tester 。开发者可以使用 Debug Analyzer
做 Log 验证 。
准备步骤
使用 MP Tool 将 DUT 地址设为 [00:11:22:33:44:85],然后编译 ble_central 示例工程,并将 images 下载至 DUT 。 开发者可以在电脑的串口助手工具端输入用户命令,请参考 用户命令接口 中的 如何使用命令。
使用 MP Tool 将 Tester 地址设为 [00:11:22:33:44:80],然后编译 ble_peripheral 示例工程,并下载 images 至 Tester 。 更多信息请参考 LE Peripheral 中的 编译和下载。
更多关于如何修改 蓝牙地址 的细节请查阅 快速入门 中的 生成 System Config File。
测试步骤
按 Tester 上的 reset 键, Tester 将开始发送可连接的非定向广播。
如果成功启动广播,会打印下面的 Debug Analyzer 日志。
如果没有看到下列日志,说明广播启动失败。请检查软件和硬件环境。
[APP] !**GAP adv start
按 DUT 上的 reset 键,开发者可以在电脑的串口助手工具端输入用户命令。
如果 DUT 成功启动且串口辅助工具配置成功,开发者会看到如下日志。串口辅助工具端会显示本地地址。
如果没有看到下列日志,请检查软件和硬件环境。
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 相关的开发流程。本章将按照以下几个部分进行介绍:
源码路径 中介绍项目目录和源代码文件。
Bluetooth Host 介绍 中介绍了 Bluetooth Host 相关信息。
多链路管理 中介绍了链路管理模块。
GAP 消息处理 中介绍 Central 相关的 GAP 消息处理。
Profile 消息回调函数 中介绍了 Central 相关的 Profile 回调函数处理。
Discover GATT Services 流程 中介绍了本示例中查询服务的流程。
GATT Services 的 handles 存储 中介绍了 GATT Services 的 handles 信息存储流程。
源码路径
工程目录:
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;
}
le_gap_init()
用于初始化 GAP 并配置链接个数。app_le_profile_init()
用于初始化 profile。app_le_gap_init()
用于初始化 GAP 参数,开发者可以自行配置以下参数:Device name 和 device appearance,Scan 参数,GAP Bond Manager 参数(请参考 LE Host 中的 Device Name 和 Device Appearance 的配置 , Scan 参数的配置 , Pairing 参数的配置 )。
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()
的流程图如图所示:

Discover Services 流程图
当收到 DISC_GAPS_DONE
, DISC_SIMP_DONE
和 DISC_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()
流程图如图所示:

Discover Services 流程图 (F_BT_GATT_SRV_HANDLE_STORAGE)