LE Central
LE central示例工程可以作为开发基于Central角色的APP的框架。
Central角色特征:
扫描广播数据。
作为Central角色发起建立连线。
与多个Peripheral设备建立连线。
环境需求
该示例支持以下开发工具包:
Hardware Platforms |
Board Name |
---|---|
RTL87x2G HDK |
RTL87x2G EVB |
为快速搭建起开发环境,可参考 快速入门 中提供的详细指导。
硬件连线
该示例工程需要支持用户命令接口。 具体连线介绍请参考 User Command Interface 中的 Data UART连接。
配置
配置选项
该示例工程全部配置选项在
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存储。
生成系统配置文件
开发者可以通过MP Tool配置以下项目:
Configurable Item |
Value |
LE Maser Link Num |
≥ APP_MAX_LINKS |
更多有关于MP Tool配置的信息请参考 快速入门 中的 生成System Config File。
编译和下载
该示例可以在SDK文件夹中找到:
Project file: samples\bluetooth\ble_central\proj\rtl87x2g\mdk
Project file: samples\bluetooth\ble_central\proj\rtl87x2g\gcc
编译和运行该示例请遵循以下步骤:
打开项目文件。
要编译目标文件,请参考 快速入门 的 编译APP Image 中列出的步骤。
编译成功后,目录
samples\bluetooth\ble_central\proj\rtl87x2g\mdk\bin
下会生成app bin文件app_MP_sdk_xxx.bin
。要下载app bin到 EVB,请参考 快速入门 的 MP Tool 下载 中列出的步骤。
在EVB上按下 reset 键,程序将开始运行。
测试验证
将示例工程烧录到EVB后,开发者可以使用另一个运行Peripheral示例工程的开发板进行测试。
与另一块开发板对测
准备两块开发板分别命名为 DUT 和 Tester 。开发者可以使用 Debug Analyzer
做 Log验证 。
准备步骤
使用MP Tool将 DUT 地址设为[00:11:22:33:44:85],然后编译 LE Central示例工程,并将images下载至 DUT 。 开发者可以在电脑的串口助手工具端输入用户命令,请参考 User Command Interface 中的 如何使用命令。
使用MP Tool将 Tester 地址设为 [00:11:22:33:44:80],然后编译LE 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 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。
Debug Analyzer shows:
[APP] !**GAP scan stop
3
showdev
显示扫描设备列表,设备列表根据simple LE 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
发起连接参数更新请求。
Debug Analyzer 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值。
Debug Analyzer shows:
[APP] !**BAS_READ_BATTERY_LEVEL: battery level 90
9
gapread 0 0
读设备名。
Debug Analyzer shows:
[APP] !**GAPS_READ_DEVICE_NAME: device name BLE_PERIPHERAL.
10
disc 0
和peripheral设备断线。
Serial port assistant tool shows:
Disconnect conn_id 0
代码介绍
本章的主要目的是帮助开发者熟悉Central Role相关的开发流程。本章将按照以下几个部分进行介绍:
源码路径 中介绍项目目录和源代码文件。
Bluetooth Host介绍 中介绍了Bluetooth Host相关信息。
初始化 中介绍Central相关参数和Bluetooth Host初始化。
多链路管理 中介绍了链路管理模块。
GAP消息处理 中介绍Central相关的GAP消息处理。
Profile消息回调函数 中介绍了Central相关的Profile回调函数处理。
Discover GATT Services流程 中介绍了本示例中查询服务的流程。
GATT Services的handles存储 中介绍了GATT Services的handles信息存储流程。
源码路径
工程目录:
samples\bluetooth\ble_central\proj
。源码目录:
samples\bluetooth\ble_central\src
。
LE central示例工程中的源文件当前被分为几个组,如下所示。
└── Project: central
└── 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_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,请参阅文档 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,Scan参数,GAP Bond Manager参数(请参考 LE Host 中的 Device Name和Device Appearance的配置 , Scan参数的配置 , Bond Manager参数的配置 )。
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);
}
多链路管理
应用的链路控制模块在 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从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()
的流程图如图所示:
当收到 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()
流程图如图所示: