低功耗蓝牙(BLE)
概述
蓝牙模块向用户提供扫描、连接、广播、传输数据等接口功能,用于短距通讯。 蓝牙由一个或多个task执行体组成,依靠蓝牙中断驱动运行。目前栈大小为3K。 蓝牙有多个event和callback,这些构成了用户调用的反馈。
角色
一般来讲,主动连接的设备称之为central/master/client,被连接的设备称之为peripheral/slaver/server。 一旦两端连接关系确定下来,则基本不会变化。
API调用注意事项
大部分API具有callback参数,应当等待callback执行完成后再进行下一步。 callback、event callback的处理不应有阻塞操作。 callback的调用栈不能太深。
重要
应极力避免蓝牙task被阻塞,否则会出现断连、扫不到、连不上等异常现象。
常用使用场景
作为slaver,创建ATT数据库供对端浏览
ble通过ATT数据库作为双端的操作实体,所有的读写通知等操作都是对ATT数据库进行的。 为了建立一个符合标准的数据库,需要了解服务、特征、UUID的概念。
记录:数据库的一条数据称之为记录,由handle,类型、值组成。
服务:每个ATT数据库具有一个或多个服务,例如HID、HeartRate。
特征:每个服务包含一个或多个特征,例如HID包括HID map、HID report,前者是按键映射表,后者是按键上报,具体操作是先读取HID map,再根据map解析HID report就能知道按键具体值。
UUID:以上几个均以记录的形式存在于ATT数据库中,为了知晓这些特殊记录,要用蓝牙标准规定的UUID值赋予记录的type。例如,DECL_PRIMARY_SERVICE_128(0x2800)表示这条记录为服务声明。
以下为具体示例
//服务声明
#define DECL_PRIMARY_SERVICE_128 {0x00,0x28,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
//特征声明
#define DECL_CHARACTERISTIC_128 {0x03,0x28,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
//特征client配置声明。这是一个特殊的UUID,表示这条记录用于配置被描述的特征,一般有notify、indicate
#define DESC_CLIENT_CHAR_CFG_128 {0x02,0x29,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
//数据库下标:
enum {
TEST_IDX_SVC,
TEST_IDX_CHAR_DECL,
TEST_IDX_CHAR_VALUE,
TEST_IDX_CHAR_DESC,
TEST_IDX_NB,
};
//数据库.
//BK_BLE_PERM_SET用于设置该记录的权限,例如BK_BLE_PERM_SET(RD, ENABLE)表示这条记录可被读
ble_attm_desc_t test_service_db[TEST_IDX_NB] = {
// Service Declaration
[TEST_IDX_SVC] = {DECL_PRIMARY_SERVICE_128, BK_BLE_PERM_SET(RD, ENABLE), 0, 0},
// Characteristic declare
[TEST_IDX_CHAR_DECL] = {DECL_CHARACTERISTIC_128, BK_BLE_PERM_SET(RD, ENABLE), 0, 0},
// 特征的真实值记录,这里表示这是一个type为0x1234的特征,BK_BLE_PERM_SET(NTF, ENABLE)表示具备notify的权限
// BK_BLE_PERM_SET(RI, ENABLE)表示如果这条记录被读,则开启nofity
[TEST_IDX_CHAR_VALUE] = {{0x34, 0x12, 0}, BK_BLE_PERM_SET(NTF, ENABLE), BK_BLE_PERM_SET(RI, ENABLE) | BK_BLE_PERM_SET(UUID_LEN, UUID_16), 128},
//Client Characteristic Configuration Descriptor
//具备被读被写的权限,一旦通过这条记录把nofify的位置1,则TEST_IDX_CHAR_VALUE会开启notiify。
[TEST_IDX_CHAR_DESC] = {DESC_CLIENT_CHAR_CFG_128, BK_BLE_PERM_SET(RD, ENABLE) | BK_BLE_PERM_SET(WRITE_REQ, ENABLE), 0, 0},
};
struct bk_ble_db_cfg ble_db_cfg;
const uint16_t service_uuid = 0xffff;
ble_db_cfg.att_db = (ble_attm_desc_t *)test_service_db;
ble_db_cfg.att_db_nb = TEST_IDX_NB;
//app handle,每次创建数据库,应当不同。
ble_db_cfg.prf_task_id = g_test_prf_task_id;
ble_db_cfg.start_hdl = 0;
//服务记录的UUID的类型,这里为16bit
ble_db_cfg.svc_perm = BK_BLE_PERM_SET(SVC_UUID_LEN, UUID_16);
//给服务具体值复制
os_memcpy(&(ble_db_cfg.uuid[0]), &service_uuid, 2);
//设置回调
bk_ble_set_notice_cb(ble_at_notice_cb);
//创建数据库
bk_ble_create_db(&ble_db_cfg);
到此,我们得到一个0xfff的服务,该服务包含一个0x1234的特征,该特征可以通过读,或写TEST_IDX_CHAR_DESC 来开启notify的行为。
我们还需要在ble_at_notice_cb处理一些事件。
void ble_at_notice_cb(ble_notice_t notice, void *param)
{
switch (notice) {
//对端的写事件,w_req->att_idx对应着数据库下标
case BLE_5_WRITE_EVENT: {
if (w_req->prf_id == g_test_prf_task_id)
{
//
switch(w_req->att_idx)
{
case TEST_IDX_CHAR_DECL:
break;
case TEST_IDX_CHAR_VALUE:
break;
case TEST_IDX_CHAR_DESC:
//TEST_IDX_CHAR_DESC的写法有标准定义,这里简单地认为只要写入,就开启TEST_IDX_CHAR_VALUE的notify
//通过bk_ble_send_noti_value notify对端。
//write_buffer = (uint8_t *)os_malloc(s_test_data_len);
//bk_ble_send_noti_value(s_test_data_len, write_buffer, g_test_prf_task_id, TEST_IDX_CHAR_VALUE);
break;
default:
break;
}
}
break;
}
case BLE_5_CREATE_DB:
//bk_ble_create_db 创建成功。
break;
}
}
开启广播
设定好数据库后,需要开启广播以让对端扫描到我们。
ble_adv_param_t adv_param;
adv_param.own_addr_type = 0;//BLE_STATIC_ADDR
adv_param.adv_type = 0; //ADV_IND
//一般为7
adv_param.chnl_map = 7;
adv_param.adv_prop = 3;
//最小interval
adv_param.adv_intv_min = 0x120; //min
//最大interval,一般越小,被扫描到的概率越高
adv_param.adv_intv_max = 0x160; //max
adv_param.prim_phy = 1;// 1M
adv_param.second_phy = 1;// 1M
//获取当前空闲的active index,用于开启广播
actv_idx = bk_ble_get_idle_actv_idx_handle();
if (actv_idx != UNKNOW_ACT_IDX) {
bk_ble_create_advertising(actv_idx, &adv_param, ble_at_cmd_cb);
}
//在ble_at_cmd_cb中,等待BLE_CREATE_ADV事件
...
//
//蓝牙广播数据,请参考ble标准格式
const uint8_t adv_data[] = {0x02, 0x01, 0x06, 0x0A, 0x09, 0x37 0x32, 0x33, 0x31, 0x4e, 0x5f, 0x42, 0x4c, 0x45};
bk_ble_set_adv_data(actv_idx, adv_data, sizeof(adv_data), ble_at_cmd_cb);
//在ble_at_cmd_cb中,等待BLE_SET_ADV_DATA事件
...
//
//扫描响应数据,请参考ble标准格式
const uint8_t scan_data[] = {0x02, 0x01, 0x06, 0x0A, 0x09, 0x37 0x32, 0x33, 0x31, 0x4e, 0x5f, 0x42, 0x4c, 0x45};
bk_ble_set_scan_rsp_data(actv_idx, scan_data, sizeof(scan_data), ble_at_cmd_cb);
//在ble_at_cmd_cb中,等待BLE_SET_RSP_DATA事件
...
//
//开启广播
bk_ble_start_advertising(actv_idx, 0, ble_at_cmd_cb);
//在ble_at_cmd_cb中,等待BLE_START_ADV事件
...
//
开启扫描
ble_scan_param_t scan_param;
scan_param.own_addr_type = 0;//BLE_STATIC_ADDR
scan_param.scan_phy = 5;
//一般interval越小,windows越大,越有可能扫描到数据
scan_param.scan_intv = 0x64; //scan interval
scan_param.scan_wd = 0x1e; //scan windows
//获取当前空闲的active index,用于开启扫描
actv_idx = bk_ble_get_idle_actv_idx_handle();
bk_ble_create_scaning(actv_idx, &scan_param, ble_at_cmd);
//在ble_at_cmd_cb中,等待BLE_CREATE_SCAN
...
//
bk_ble_start_scaning(actv_idx, ble_at_cmd);
//在ble_at_cmd_cb中,等待BLE_START_SCAN
...
//
//在ble_notice_cb_t中处理BLE_5_REPORT_ADV ,为广播数据
建立连接
ble_conn_param_t conn_param;
//一般interval越小,该链路性能越好,但其他链路、扫描、广播性能会差
conn_param.intv_min = 0x40; //interval
conn_param.intv_max = 0x40; //interval
conn_param.con_latency = 0;
conn_param.sup_to = 0x200;//supervision timeout
conn_param.init_phys = 1;// 1M
//获取当前空闲的active index,用于建立连接
con_idx = bk_ble_get_idle_conn_idx_handle();
bk_ble_create_init(con_idx, &conn_param, ble_at_cmd);
//在ble_at_cmd_cb中,等待BLE_INIT_CREATE
...
//
//设置对端地址类型,不匹配会导致连接不上
bk_ble_init_set_connect_dev_addr(con_idx, bt_mac, 1);
bk_ble_init_start_conn(con_idx, ble_at_cmd)
//在ble_at_cmd_cb中,等待BLE_INIT_START_CONN
...
//