基于上篇文章,大致了解的ESP32蓝牙收发的流程,我们知道接收端的代码不需要更改,接收端只需要开启扫描,拿到空中的来自于发送端的蓝牙广播数据包即可,那么我们对发送端的代码进行更改。
在发送前,我们需要填充广播包的数据,这需要调用乐鑫官网的API,而这个填充过程大致分为以下四步
这就是我们要做的事情,接下来只需要跟着这个逻辑去调用乐鑫官网的API就好了。
从直译来看,这就是配置广播数据的API
这是开始广播的API
开始填充main函数
但是我们并没有adv_data和adv_params这两个结构体,为了先搭框架,我们从
这个文件里复制粘贴过来,因为这个工程是官方demo,写好了发送时结构体示例,我们先拿过来,后续再更改
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = true,
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
.max_interval = 0x000C, //slave connection max interval, Time = max_interval * 1.25 msec
.appearance = 0x00,
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
.p_manufacturer_data = NULL, //&test_manufacturer[0],
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = 32,
.p_service_uuid = adv_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
//.peer_addr =
//.peer_addr_type =
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
把拿过来的结构体复制到main函数上方进行定义,这里注意.p_service_uuid = adv_service_uuid128,这个adv_service_uuid128是没有定义的,也应该去刚才的文件里拿过来进行粘贴定义,uuid给16位就够用了,没必要用源代码里的32位,所以将结构体中.service_uuid_len=32改成16。由于用的是谷歌的数据包格式(Eddystone),所以将uuid128这个数组的12,13位改成0xAA,0xFE
static uint8_t adv_service_uuid128[16] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xAA, 0xFE, 0x00, 0x00,
};
然后我们对服务数据进行封包,假设封包的对象是UUID,我们写一个封装UUID的代码
/********************************************************************
函数名: Eddystone_Set_UID
描述: 设置eddystone UID 帧格式参数
参数: uid - uid指针
power - 功率
name_space - 命名空间
instance - 实例
返回值: 错误 -1
正确 服务数据长度
********************************************************************/
int Eddystone_Set_UID(eddystone_uid_t *uid,char power,uint8_t * name_space,uint8_t *instance)
{
if(uid == NULL || name_space == NULL || instance == NULL)
{
return -1;
}
memset(uid,0,sizeof(eddystone_uid_t));
uid->uuid = EDDYSTONE_SERVICE_UUID;
uid->frame_type = EDDYSTONE_UID;
uid->tx_power = (uint8_t)power;
for(int i = 0;i <10;i++)
uid->name_space[i] = *name_space++;
for(int i = 0;i <6;i++)
uid->instance[i] = *instance++;
return sizeof(eddystone_uid_t);
}
这个函数设置了UID的帧格式,返回一个服务数据长度,这个长度是干嘛用的?我们再来看看刚才复制过来的结构体
实际上我们发送数据时需要填充这两个成员,分别是服务数据长度和服务数据,所以Eddystone_Set_UID需要返回一个服务数据长度以填充该结构体。Eddystone_Set_UID(eddystone_uid_t *uid,char power,uint8_t * name_space,uint8_t *instance)有四个参数,分别是eddystone_uid_t类型的uid,char类型power,u8类型的namespace命名空间和instance实例,uid定义一个eddystone_uid_t的变量即可,power是功率,用于定位,这里不需要定位功能,随便填即可,那么namespace和instance怎么去填?
我们先回过头看看UID帧格式是什么样子的。
命名空间和实例的定义:
简单来说,命名空间用于分组,像一个大的“文件夹”,比如在一个大型商场里,可能会有多个不同区域的蓝牙信标,像服装区、餐饮区、超市区等,就可以给每个区域的信标设置不同的命名空间。这样,当我们想要对某个区域的信标进行统一管理、操作或者获取信息时,只需要找到对应的命名空间就行。实例在一个命名空间这个大 “文件夹” 里面,实例就像是一个个具体的文件,用来唯一地标识每一个具体的蓝牙设备。比如在服装区这个命名空间下,每一个具体的服装店铺门口的蓝牙信标都有自己独特的实例号,这样我们就可以准确地找到想要的那个店铺的信标,获取到这个店铺特有的信息,比如正在进行的促销活动等。
命名空间大小是十个字节,实例为六个字节。那我们给命名空间填“Helloworld”,实例为"123456"。
根据这个逻辑,我们写代码
发送方代码流程基本就这些了,编译烧写就好了,至于接收方,只需要烧写官方demo就可以了
大致流程就这些,思路理清了和把帧格式弄明白就很好写了