BLE主从模式
📎 原始文档:https://www.kdocs.cn/l/cc8HNmAlgJ9Y BLE主从模式
- BLE主从模式 BLE主从模式是指ble同时做主机和从机,但目前暂时只支持同时1主1从,即1个master,1个slave。不开启该模式则是默认只做从机,不具备主动去扫描连接其他设备的ble功能。
- sdk开启该功能 在对应的板级配置board_xxxx_demo_cfg.h文件中,将下图中的宏开启,即是开启了该功能。

#define SMBOX_MULTI_BLE_EN 1 //蓝牙BLE多连:1主1从
2.按键开启该模式
目前sdk的开启方式是按下按键切换,因为sdk流程中默认是会先跑单从机的初始化,所以目前做的方式是按键切换。公版sdk中是开启该功能后,key0按键单击即可切换。
如果想上电就开启该模式,也可参考以下修改:
void bt_ble_init(void)
{
log_info("***** ble_init******\n");
if (ble_init_flag) {
return;
}
#if TCFG_PAY_ALIOS_ENABLE
upay_mode_enable = 0;
upay_new_adv_enable = 0;
#if UPAY_ONE_PROFILE
if (config_btctler_le_hw_nums < 2) {
log_info("error:need add hw to adv new device!!!\n");
ASSERT(0);
}
#endif
#endif
gap_device_name = bt_get_local_name();
gap_device_name_len = strlen(gap_device_name);
if (gap_device_name_len > BT_NAME_LEN_MAX) {
gap_device_name_len = BT_NAME_LEN_MAX;
}
log_info("ble name(%d): %s \n", gap_device_name_len, gap_device_name);
set_ble_work_state(BLE_ST_INIT_OK);
bt_ble_init_do();
ble_vendor_set_default_att_mtu(ATT_LOCAL_PAYLOAD_SIZE);
bt_ble_rcsp_adv_enable();
ble_module_enable(1);
ble_init_flag = 1;
//添加该部分,直接切换一次
#if SMBOX_MULTI_BLE_EN
app_change_key_is_press = 1; //处于切换app的模式
if (select_app1_or_app2) {
ble_profile_again_init(0);//关闭app1的adv
ble_app_disconnect();//断开app1的连接
} else {
smbox_multi_ble_module_enable(0);//断开app2的连接、关闭广播
}
select_app1_or_app2 = !select_app1_or_app2;
log_info("SELECT_APP1_OR_APP2: %d\n", select_app1_or_app2);
/* //重新注册函数 */
//!!!!!!!需要注意是adv状态还是连接状态--只要是连接切换就需要在断链之后才会切换
if (!smartbox_get_con_handle() && !mul_dev_get_conn_handle(0, SMBOX_MULTI_ROLE_SERVER) && !mul_dev_get_conn_handle(0, SMBOX_MULTI_ROLE_CLIENT)) {
log_info("To_switch_app:%d\n", select_app1_or_app2);
if (select_app1_or_app2) {
ble_profile_again_init(1);
} else {
smbox_multi_ble_profile_init(1);
}
}
#endif
}
二、主模式扫描
- 匹配连接模式 目前sdk支持3中匹配方式,分别是指定地址创建连接、指定设备名称创建连接、匹配厂家标识创建连接。

1.指定设备名称创建连接
//指定索索名字
static const u8 test_remoter_name1[] = "AC701N_watch_hzh";//
static const client_match_cfg_t match_dev01 = {
.create_conn_mode = BIT(CLI_CREAT_BY_NAME),
.compare_data_len = sizeof(test_remoter_name1) - 1 , //去结束符
.compare_data = test_remoter_name1,
.bonding_flag = 0,
};
2.指定地址创建连接
static const u8 mac1[6]={0x3A ,0x16,0x35 ,0x34,0x37, 0xD2};
static const client_match_cfg_t match_dev01 = {
.create_conn_mode = BIT(CLI_CREAT_BY_ADDRESS),
.compare_data_len = sizeof(mac1) , //去结束符
.compare_data = mac1,
.bonding_flag = 0,
};
3.匹配厂家标识创建连接
- 创建连接 除了采用1中的匹配的方式创建连接,也可以自定义发起连接。采用client_create_connect_api这个接口。mac地址和mac地址类型科才看2.3部分的广播数据部分中可以获取到。
/* --------------------------------------------------------------------------*/
/**
* @brief 创建ble连接
*
* @param addr ble的mac地址
* @param addr_type ble的mac地址类型,0--public address,1--random address
* @param mode 连接方式
* 1是采用有配对记录的发起最近1次连接的ble设备的连接,
* 0是根据传入的地址连接
*
* @return
*/
/* ----------------------------------------------------------------------------*/
static int client_create_connect_api(u8 *addr, u8 addr_type, u8 mode)
- 扫描到的广播数据 可以在GAP_EVENT_ADVERTISING_REPORT事件中,收到扫描到的广播回调。主要处理函数在client_report_adv_data中。如果还想看到更详细的分析,比如想看到广播的出来的ble的设备类型,16位的uuid之类的广播解析,可在resolve_adv_report这个函数中获取到更加详细的信息。


三、主模式通讯
- 主机扫描从机服务 连上ble后,如果是初次连接,就必须先扫描从机的profile,找到双发定义好通讯的handle。扫描的接口为client_profile_start。搜索到的回调是user_client_report_search_result函数,在该函数中可以看到从机profile中定义的各项服务,特征,以及其对应的handle。
static void client_profile_start(u16 conn_handle)
{
set_ble_work_state(cur_dev_cid, BLE_ST_CONNECT);
if (0 == client_config->security_en) {
client_search_profile_start(conn_handle);
}
}
//协议栈内部调用
void user_client_report_search_result(search_result_t *result_info)
{
if (result_info == (void *) - 1) {
log_info("client_report_search_result finish!!!\n");
do_operate_search_handle();
client_event_report(CLI_EVENT_SEARCH_PROFILE_COMPLETE, &client_con_handle[cur_dev_cid], 2);
//搜索完profile,尝试开新设备scan
/* ble_enable_new_dev_scan(); --碰一碰功能不需要继续打开*/
return;
}
log_info("\n*** services, uuid16:%04x,index=%d ***\n", result_info->services.uuid16, result_info->service_index);
log_info("{charactc, uuid16:%04x,index=%d,handle:%04x~%04x,value_handle=%04x}\n",
result_info->characteristic.uuid16, result_info->characteristic_index,
result_info->characteristic.start_handle, result_info->characteristic.end_handle,
result_info->characteristic.value_handle
);
if (!result_info->services.uuid16) {
log_info("######services_uuid128:");
log_info_hexdump(result_info->services.uuid128, 16);
}
if (!result_info->characteristic.uuid16) {
log_info("######charact_uuid128:");
log_info_hexdump(result_info->characteristic.uuid128, 16);
}
check_target_uuid_match(result_info);
}
- 主机对从机的profile的handle的处理 搜索完成后,需要对handle进行对应的处理。比如需要对具有notify和indicate属性的character进行使能操作,从机发消息过来,主机才能收到。
//操作handle,完成 write ccc
static void do_operate_search_handle(void)
{
u16 tmp_16;
u16 i, cur_opt_type;
opt_handle_t *opt_hdl_pt;
log_info("opt_handle_used_cnt= %d\n", opt_handle_used_cnt);
log_info("find target_handle:");
log_info_hexdump(&target_handle[cur_dev_cid], sizeof(target_hdl_t));
if (0 == opt_handle_used_cnt) {
goto opt_end;
}
/* test_send_conn_update();//for test */
for (i = 0; i < opt_handle_used_cnt; i++) {
opt_hdl_pt = &opt_handle_table[i];
log_info("do opt:service_uuid16:%04x,charactc_uuid16:%04x\n", opt_hdl_pt->search_uuid->services_uuid16, opt_hdl_pt->search_uuid->characteristic_uuid16);
cur_opt_type = opt_hdl_pt->search_uuid->opt_type;
switch ((u8)cur_opt_type) {
case ATT_PROPERTY_READ:
if (opt_hdl_pt->search_uuid->read_long_enable) {
tmp_16 = 0x55A2;//fixed
log_info("read_long:%04x\n", opt_hdl_pt->value_handle);
client_operation_send(opt_hdl_pt->value_handle, (u8 *)&tmp_16, 2, ATT_OP_READ_LONG);
} else {
tmp_16 = 0x55A1;//fixed
log_info("read:%04x\n", opt_hdl_pt->value_handle);
client_operation_send(opt_hdl_pt->value_handle, (u8 *)&tmp_16, 2, ATT_OP_READ);
}
break;
case ATT_PROPERTY_NOTIFY:
tmp_16 = 0x01;//fixed
log_info("write_ntf_ccc:%04x\n", opt_hdl_pt->value_handle);
client_operation_send(opt_hdl_pt->value_handle + 1, &tmp_16, 2, ATT_OP_WRITE);
if (opt_hdl_pt->search_uuid->read_report_reference) {
tmp_16 = 0x55A1;//fixed
log_info("read_report_reference:%04x\n", opt_hdl_pt->value_handle + 2);
client_operation_send(opt_hdl_pt->value_handle + 2, (u8 *)&tmp_16, 2, ATT_OP_READ);
}
break;
case ATT_PROPERTY_INDICATE:
tmp_16 = 0x02;//fixed
log_info("write_ind_ccc:%04x\n", opt_hdl_pt->value_handle);
client_operation_send(opt_hdl_pt->value_handle + 1, &tmp_16, 2, ATT_OP_WRITE);
break;
default:
break;
}
}
opt_end:
if(BLE_ST_SCAN != get_ble_work_state(cur_dev_cid))
{
set_ble_work_state(cur_dev_cid, BLE_ST_SEARCH_COMPLETE);
}
}
- 主机与从机的通讯
1.主机对从机读写
主机可以通过client_operation_send接口想从机进行读写操作。接口介绍以及例子如下:
/* --------------------------------------------------------------------------*/
/**
* @brief 主机操作接口
*
* @param handle 对应的Characteristic的handle值
* @param data 数据指针
* @param len 数据长度
* @param att_op_type handle的类型
* ATT_OP_READ 单次读
* ATT_OP_READ_LONG 多次读
* ATT_OP_WRITE 写发送(需要对方回答应包)
* ATT_OP_WRITE_WITHOUT_RESPOND 写发送(不需要对方回答应包)
* @return
* 0:发送成功
* 3:ble未连接或传入的handle有误
* 1:发送buffer满了
*/
/* ----------------------------------------------------------------------------*/
static int client_operation_send(u16 handle, u8 *data, u16 len, u8 att_op_type)
{
int ret = APP_BLE_NO_ERROR;
if (!client_con_handle[cur_dev_cid]) {
return APP_BLE_OPERATION_ERROR;
}
if (!handle) {
log_info("handle is null\n");
return APP_BLE_OPERATION_ERROR;
}
if (get_buffer_vaild_len(0) < len) {
log_info("opt_buff_full!!!\n");
return APP_BLE_BUFF_FULL;
}
ret = ble_op_multi_att_send_data(client_con_handle[cur_dev_cid], handle, data, len, att_op_type);
if (ret == BLE_BUFFER_FULL) {
ret = APP_BLE_BUFF_FULL;
}
return ret;
}
static int client_write_send(void *priv, u8 *data, u16 len)
{
return client_operation_send(target_handle[cur_dev_cid].write_handle, data, len, ATT_OP_WRITE);
}
static int client_write_without_respond_send(void *priv, u8 *data, u16 len)
{
return client_operation_send(target_handle[cur_dev_cid].write_no_respond, data, len, ATT_OP_WRITE_WITHOUT_RESPOND);
}
static int client_read_value_send(void *priv)
{
u16 tmp_flag = 0x55A1;
return client_operation_send(target_handle[cur_dev_cid].read_handle, (u8 *)&tmp_flag, 2, ATT_OP_READ);
}
static int client_read_long_value_send(void *priv)
{
u16 tmp_flag = 0x55A2;
return client_operation_send(target_handle[cur_dev_cid].read_handle, (u8 *)&tmp_flag, 2, ATT_OP_READ_LONG);
}