既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
服务端类似被蹭网的wifi
如果想要让ESP处于别人随时可以搜索连接的情况要配置为服务端;如果想让ESP通过扫描连接周围可连接的蓝牙设备,需要把它设置成客户端,正好和WiFi模式的设定相反
Server通过characteristic对数据进行封装,多个characteristic组成一个Service——Server是一个基本的BLE应用,如果某个Service是一个蓝牙联盟定义的标准服务,也可以称其为profile
要具体了解这些内容需要先了解属性协议层ATT
ATT简述
属性协议层ATT(Attribute Protocol)是GATT和GAP的基础,它定义了BLE协议栈上层的数据结构和组织方式;在层内,它定义了属性(Attribute)的内容,规定了访问属性的方法和权限
属性是一个数据结构,它包括了数据类型和数据值,可以像C语言的结构体那样构造
属性包括三种类型:服务项、特征值和描述符,三者呈包含关系:服务项包含一个或多个特征值,特征值包含一个或多个描述符,多个服务项组织在一起,构成属性规范(Attribute Profile)
属性的种类和分组
属性大致可以分为三种类型:服务项Profile、特征值Characteristic和描述符Descriptor
最顶级为Profile, 下面是多个服务项(Service), 服务项下面是多个特征值(Characteristic), 特征值下面是多个描述符(Descriptor)
每个设备都包含以下必要的特征值和服务项:
PROFILE
- Generic Access Service(Primary Service)
- Device Name(Characteristic)
- Appearance(Characteristic)
- Generic Attribute Service(Primary Service)
- Service Changed(Characteristic)
- CCCD(Descriptor)
- Service Changed(Characteristic)
服务项Service
服务项这种类型本身并不包含数据,仅仅相当于是一个容器,用来容纳特征值
特征值characteristic
特征值用于保存用户数据,但它也有自己的UUID——可以把它看作一个变量,变量里存着数据(用户数据),也有自己的地址信息(UUID)
使用特征值时,也要遵循“先声明再赋值”的步骤——先声明特征值自身,再声明它的项
一个characteristic包含三种条目:
- characteristic声明:每个characteristic的分界符,解析时一旦遇到一个声明,就可以认为接下来又是一个新的characteristic;声明还包含了接下来characteristic值的读写属性等
- characteristic值:数据的值
- characteristic描述符:数据的额外信息
一般BLE的属性体系在系统中以GattDB表示,即属性数据库,gattDB是BLE协议栈在内存中开辟的一段专有区域,会在特定的时候写入Flash进行保存,并在启动时读取出来回写到内存中去,但并非所有的BLE数据通信是操作gattDB
characteristic用Attribute数据结构来实现
属性Attribute的数据结构
Attribute由四部分组成:
-
属性句柄Attribute handle:可以视为指向属性实体的指针,对端设备通过属性句柄来访问某个属性,大小2字节,起始于0x0001,系统初始化时,各属性的句柄逐步+1,但最大不超过0xFFFF
-
属性类型Attribute type:用以区分当前属性是服务项或是特征值等,用通用唯一识别码(UUID)标识的16字节十六进制字符串(形如f6257d37-34e5-41dd-8f40-e308210498b4,从网上抄来的示例,如有雷同那就是雷同)表示。一个合法的UUID,一定是随机的、全球唯一的,不应该出现两个相同的UUID。属性类型分类如下:
-
首要服务项Primary Service
-
次要服务项Secondary Service
-
包含服务项Include
-
特征值Characteristic他们与UUID的映射关系如下:
-
0x1800 – 0x26FF :服务项类型
-
0x2700 – 0x27FF :单位
-
0x2800 – 0x28FF :属性类型
-
0x2900 – 0x29FF :描述符类型
-
0x2A00 – 0x7FFF :特征值类型为了减少传输的数据量,BLE协议做了一个转换约定,给定一个固定的16字节模板,只设置2个字节为变化量,其他为常量,2字节的UUID在系统内部会被替换,进而转换成标准的16字节UUID;反之,如果一个特征值的UUID是16字节的,在系统内部它的属性类型也可能写成第3、4字节组成的双字节
-
示例如下:
UUID模板为
0000XXXX-0000-1000-8000-00805F9B34FB
其中从左数第3、4个字节“XXXX”就是变化位,其他为固定位。如:UUID=0x2A00在系统内部会转换成00002A00-0000-1000-8000-00805F9B34FB。
3. 属性值Attribute value:真正的数据值,大小为0-512字节。如果该属性是服务项类型或者是特征值声明类型,那么它的属性值就是UUID等信息;如果是普通的特征值,则属性值是用户的数据,属性值需要预留空间以保存用户数据,可以将属性值的预留空间看做I2C的数据空间,操作特征值里的用户数据,就是对那块内存空间进行读写,所以启用蓝牙后会占用额外的内存
4. 属性权限Attribute permissions:Attribute的权限属性,主要有四种:
* 访问权限(Access Permission):只读或只写或读写
* 加密权限(Encryption Permission):加密或不加密
* 认证权限(Authentication Permission):需要认证或无需认证。指相互确认对方身份,BLE中所说的“认证”过程就是设备配对
* 授权权限(Authorization Permission):需要授权或无需授权。指对授信设备开放权利授权的管控等级要高于认证,认证的设备未必被授权,授权的设备一定是认证的——**认证是授权的充分不必要条件**。认证是设备配对,两边都符合协议规定就行,但是授权取决于Server设备对Client设备的主动许可。
一个没有经过认证的设备,被称为未知设备(Unknown Device);经过了认证,该设备会在绑定信息中被标记为Untrusted,被称为不可信设备(Untrusted Device);经过了认证,并且在绑定信息中被标记为Trusted的设备被称为可信设备(Trusted Device)
具体的权限示例如下所示:
* Open(随意读写)
* No Access(禁止读写)
* Authentication(需要配对才能读写,分成很多子类型用于适配配对的类型)
* Authorization(允许应用在回调函数中读写)
* Signed(签名后才能随意读写)
属性协议ATT PDU
拥有一组属性的设备称为服务端(Server);读写该属性值的设备称为客户端(Client)
Client和Server之间通过ATT PDU通信
属性协议ATT PDU共有6种,如下表所示:
ATT PDU种类 | 发送方向 | 触发响应 | 说明 |
---|---|---|---|
Command | Client -> Server | – | 客户端发送Command,服务器无需任何返回 |
Request | Client -> Server | Response | 客户端发送Request,服务器需要返回一个Response,表明服务器收到 |
Response | Server -> Client | – | |
Notification | Server -> Client | – | 服务器发送Notification,户端无需任何返回 |
Indication | Server -> Client | Confirmation | 服务器发送Indication,客户端需要返回一个Confirmation,表明客户端收到 |
Confirmation | Client -> Server | – |
BLE下,所有命令都是“必达”的,每个命令发送完毕后,发送者会等待ACK信息(类似I2C),如果收到了ACK包,发起方认为命令完成;否则发起方会一直重传该命令直到超时导致BLE连接断开(类似CAN的出错重发机制),可以说只要数据包放到了协议栈射频FIFO中,蓝牙协议栈就能保证该数据包“必达”对方,但是没有回复相对有回复就是“不太可靠”,这时候就需要特殊的“有回复属性”
Request后缀
特别地,如果一个命令需要response,那么可以在相应命令后面加上request后缀,这个response包在应用层有回调事件,可以用于触发特殊的功能,这是默认的协议ACK恢复不具有的,采用request/response方式,应用层可以按顺序地发送一些数据包;如果一个命令只需要ACK而不需要response,那么它的后面就不会带request
然而Request/response会大大降低通信的吞吐率,因为request/response必须在不同的连接间隔中出现,这就导致两个连接间隔最多只能发一个数据包,而不带request后缀的ATT命令就没有这个问题——一般情况下,在同一个连接间隔中可以同时发多个数据包,这样将大大提高数据的吞吐率
常用的带request命令:所有read命令,writerequest,indication等
常用的不带request命令:write command,notification等
通用属性协议GATT简述
GATT(Generic Attribute Profile),描述了一种使用ATT的服务框架。该框架定义了服务(Server)和服务属性(characteristic)的过程(Procedure)及格式,负责处理具体数据段通过蓝牙连接的发送和接收
现在的BLE大多建立在GATT协议之上,GATT建立在ATT和L2CAP之上,GATT需要使用通用访问协议GAP来确定设备的连接
通用访问协议GAP
GAP 使设备被其他设备可见,并决定了当前设备是否可以或者怎样与合同设备进行交互
GAP中,设备被分为外围设备Peripheral和中心设备Central
外围设备:性能相对较弱、功耗相对低的设备,他们通常被连接到更加强大的中心设备
中心设备:性能相对较强、功耗较高的设备
GAP广播
GAP 中外围设备不停向外广播以让中心设备知道它的存在。通过两种方式向外广播数据:
广播数据(Advertising Data Payload):必须的,外设需要以此来和中心设备取得连接
扫描回复(Scan Response Data Payload):可选的,中心设备可以向外围设备请求扫描回复,向其提供一些设备的额外信息
外围设备会设定一个广播建个,每个间隔中,它会重新发送自己的广播数据,广播间隔越长约省电,但同时更不容易被扫描到
基于GATT广播的BLE连接只能是一个外围设备连接一个中心设备,可以理解成一个蓝牙耳机只能连接一台手机,不能同时连接两台手机
GATT协议
GATT协议建立在ATT协议的基础上。将ATT协议中的Service、Characteristic 及对应数据都保存在一个查找表中,查找表使用16位的ID作为索引。建立GATT连接前必须先经过GAP协议
GATT连接是独占的,也就是说同一个BLE外设(外部设备)同时只能被一个中心设备连接,一旦外设被连接,它就会停止GAP广播,对其它设备不可见;当设备断开时它又开始广播。
如果中心设备和外设需要双向通信,唯一的方式就是建立GATT连接,GAP通信是单向的,只能让中心设备向外设发送信息
GATT通信双方是C/S关系,外设作为GATT的Server,维持ATT查找表、Service、Characteristic定义;中心设备作为GATT的Client,向Server发起请求,所有通信事件都由中心设备Client发起,从Server接收响应。一旦连接建立,外设将会给中心设备建议一个连接间隔,中心设备可以选择在每个连接间隔尝试重新连接,检查是否有新数据,不过这个间隔只是建议,中心设备可以不严格按照这个间隔执行请求。
GATT结构
GATT结构建立在ATT的属性Attribute数据结构之上(其实和ATT的那些东西一模一样)
Attribute结构体组成种类不同的Characteristic,多个Characteristic被封装在Servce容器中,Characteristic和Service容器都有着自己的UUID(有官方认证的16位UUID和自定义的128位UUID),各种常用的Service集合成Profile
BLE外设的通信主要通过Characteristic,通过在Characteristic中读写数据就实现了双向通信,也可以通过实现类似串口的Service来配置TxCharacteristic和RxCharacteristic,这些都是具体项目的选择了
BLE从初始化到建立连接的过程简述
- 外围设备开始广播,发送完一个广播包后T_IFS,开启射频Rx窗口接收来自中心设备的数据包
- 中心设备扫描到广播,在收取此广播T_IFS后如果开启了中心设备的扫描回复,中心设备将向外设发送回复
- 外设收取到中心设备的回复,做好接收准备,并返回ACK包
- 如果ACK包未被中心设备接收到,中心设备将一直发送回复直到超时,此期间内只要外设返回过一次ACK包就算连接成功
- 开始建立通信,后续中心设备将以收取到外设广播的时间为原点,以Connection Interval为周期向外设发送数据包,数据包将具有两个作用:同步两设备时钟和建立主从模式通信
外设每收到中心设备的一个包,就会把自己的时序原点重新设置,以和中心设备同步(Service向Client同步)
BLE通信在建立成功后变为主从模式,中心设备Central变为Master,外设Peripheral变为Slave,Slave只能在Master向它发送了一个包以后才能在规定的时间内把自己的数据回传给Master
- 连接建立成功
- 外设自动停止广播,其他设备无法再查找到该外设
- 按照以下时序进行通信,在中心设备发送包的间隔内,外设可以发送多个包
- 需要连接断开时只需要中心设备停止连接(停止发送包)即可
- 中心设备可以将外设的addr写入Flash或SRAM等存储器件,保持监听此addr,当再次收到外设广播时就可以建立通信。BLE Server设备为了省电,当一段时间内没有数据要发送时,可以不再发送包,双方就会因为连接超时(connection timeout)断开,这时需要中心设备启动监听,这样,当BLE Server设备需要发送数据时,就可以再次连接
ESP的蓝牙外设配置
蓝牙配置相关库函数
相关头文件及其作用
#include "bt.h"//蓝牙控制器和VHCI设置头文件
#include "esp\_gap\_ble\_api.h"//GAP设置头文件,广播和连接相关参数配置
#include "esp\_gatts\_api.h"//GATT配置头文件,创建Service和Characteristic
#include "esp\_bt\_main.h"//蓝牙栈空间的初始化头文件
蓝牙控制器
使用esp_bt_controller_init()
esp\_bt\_controller\_init(esp_bt_controller_config_t \*cfg);//esp\_bt\_controller\_config\_t是蓝牙控制器配置结构体
struct esp_bt_controller_config_t
{
uint16_t controller_task_stack_size;//蓝牙控制器栈大小
uint8_t controller_task_prio;//蓝牙控制器任务优先级
uint8_t hci_uart_no;//使用哪个UART作为HCI的IO,仅能选择UART1或UART2串口
uint32_t hci_uart_baudrate;//HCI串口波特率
uint8_t scan_duplicate_mode;//重复扫描模式
uint8_t scan_duplicate_type;//重复扫描类型
uint16_t normal_adv_size;//普通广播报文大小
uint16_t mesh_adv_size;//mesh广播报文大小
uint16_t send_adv_reserved_size;//蓝牙控制器最小的内存大小(保留出发送报文所需的内存大小)
uint32_t controller_debug_flag;//蓝牙控制器debug log的属性
uint8_t mode;//BR/EDR/BLE/Dual模式选择
uint8_t ble_max_conn;//BLE模式最多连接个数
uint8_t bt_max_acl_conn;//BR或EDR最大的ACL连接个数
uint8_t bt_sco_datapath;//SCO数据路径 用于HCI或PCM模块
bool auto_latency;//BLE自动延迟,用于降低传统蓝牙的功耗
bool bt_legacy_auth_vs_evt;//BR/EDR传统的授权完毕事件,用于防止BIAS攻击
uint8_t bt_max_sync_conn;//BR/EDR最多的ACL连接数目,也可以在menuconfig中配配置
uint8_t ble_sca;//BLE晶振准确度指数
uint8_t pcm_role;//PCM角色,选择master或slave
uint8_t pcm_polar;//PCM触发极性,选择下降沿或上升沿
uint32_t magic;//神奇数字
}
初始化蓝牙控制器,此函数只能被调用一次,且必须在其他蓝牙功能被调用之前调用
使用esp_bt_controller_deinit()来取消初始化,用于关闭蓝牙并清除其占用的内存,还会将蓝牙任务删除
下面是蓝牙控制器的常用API
esp\_bt\_controller\_enable(esp_bt_mode_t mode);//使能蓝牙控制器,mode是蓝牙模式,如果想要动态改变蓝牙模式不能直接调用该函数,应该先用下面的disable关闭蓝牙再使用该API来改变蓝牙模式
esp\_bt\_controller\_disable(void);//关闭蓝牙控制器
sp\_bt\_controller\_get\_status(void);//获取蓝牙控制器状态
esp\_bt\_get\_mac(void);//获取蓝牙MAC地址
esp\_bt\_controller\_mem\_release(esp_bt_mode_t mode);//释放蓝牙控制器的所有内存,包括BSS、数据和其他蓝牙使用的堆栈空间
//这个API仅仅应该再esp\_bt\_controller\_init()或after esp\_bt\_controller\_deinit()之前被调用
esp\_bt\_mem\_release(esp_bt_mode_t mode);
//释放蓝牙控制器和蓝牙数据的所有内存,比esp\_bt\_controller\_mem\_release()更彻底
esp\_bt\_sleep\_enable(void);//让蓝牙进入睡眠模式,这个函数应该在esp\_bt\_controller\_enable()被调用之后再调用
特别地,官方文档中给出了一套在线升级蓝牙设备软件时的关闭流程
esp\_bluedroid\_disable();
esp\_bluedroid\_deinit();
esp\_bt\_controller\_disable();
esp\_bt\_controller\_deinit();
esp\_bt\_mem\_release(ESP_BT_MODE_BTDM);
经典蓝牙
用于蓝牙运行的API如下所示
esp\_bluedroid\_get\_status(void);//获取蓝牙当前状态
//可能的状态如下所示
ESP_BLUEDROID_STATUS_UNINITIALIZED==0//未初始化
ESP_BLUEDROID_STATUS_INITIALIZED//已被初始化但是未开启
ESP_BLUEDROID_STATUS_ENABLED//初始化并开启
esp\_bluedroid\_enable(void);//使能蓝牙
esp\_bluedroid\_disable(void);//关闭蓝牙
esp\_bluedroid\_init(void);//初始化蓝牙并分配系统资源,它应该被第一个调用
esp\_bluedroid\_deinit(void);//取消初始化蓝牙并将系统资源释放,用于蓝牙结束工作后的收尾
用于设备蓝牙配置的API如下所示
esp\_bt\_dev\_get\_address(void);//获取当前设备蓝牙地址
esp\_bt\_dev\_set\_device\_name(const char \*name);//设置设备名
这些函数都应该在蓝牙启用后被调用
BLE-GAP相关库函数
外围设备库函数
esp\_ble\_gap\_start\_advertising(esp_ble_adv_params_t \*adv_params);//开始广播
esp\_ble\_gap\_stop\_advertising(void);//停止广播
esp\_ble\_gap\_config\_adv\_data(esp_ble_adv_data_t \*adv_data);//广播数据参数设置
//adv\_data数据结构如下
bool set_scan_rsp//设置是否需要扫描response
bool include_name//广播内容是否包括设备名
bool include_txpower//广播数据是否包括发射功率
int min_interval//最小广播时间间隔
//计算公式:connIntervalmin = Conn\_Interval\_Min \* 1.25 ms
//Conn\_Interval\_Min在0x0006到0x0C80之间,0xFFFF就是没有特定的最小值
int max_interval//最大广播间隔
//计算公式:connIntervalmax = Conn\_Interval\_Max \* 1.25 ms
//Conn\_Interval\_Max在0x0006到0x0C80之间,Conn\_Interval\_Max应大于等于Conn\_Interval\_Min
//0xFFFF代表没有特定的最大值
int appearance//设备外形(External appearance)
uint16_t manufacturer_len//生产商数据长度
uint8_t \*p_manufacturer_data//生产商数据指针
uint16_t service_data_len//Service数据长度
uint8_t \*p_service_data//Service数据指针
uint16_t service_uuid_len//Service的UUID长度
uint8_t \*p_service_uuid//Service的UUID数组指针
uint8_t flag//广播属性(flag)
esp\_ble\_gap\_config\_adv\_data\_raw(uint8_t \*raw_data, uint32_t raw_data_len);//设置空的广播数据包,用户需要自行设置包的内容
中心设备库函数
esp\_ble\_gap\_start\_scanning(uint32_t duration);//使用该函数让设备扫描附近正在广播的外设,duration为扫描间隔
esp\_ble\_gap\_stop\_scanning(void);//停止扫描
esp\_ble\_gap\_set\_scan\_params(esp_ble_scan_params_t \*scan_params);//设置扫描参数
esp\_ble\_gap\_register\_callback(esp_gap_ble_cb_t callback)//间隔回调函数
esp\_ble\_gap\_set\_pkt\_data\_len(esp_bd_addr_t remote_device, uint16_t tx_data_length);//设置最大数据包大小
esp\_ble\_gap\_set\_prefer\_conn\_params(esp_bd_addr_t bd_addr, uint16_t min_conn_int, uint16_t max_conn_int, uint16_t slave_latency, uint16_t supervision_tout);//设置当默认连接参数无法使用时的优先连接参数,这个库函数只能用在中心设备master上
esp\_ble\_gap\_config\_scan\_rsp\_data\_raw(uint8_t \*raw_data, uint32_t raw_data_len);//设置空的response数据包,用户需要自行设置数据
esp\_ble\_gap\_read\_rssi(esp_bd_addr_t remote_addr);//读取远程设备的RSSI,结果会在间隔回调函数中随ESP\_GAP\_BLE\_READ\_RSSI\_COMPLETE\_EVT事件返回
连接配置库函数
esp\_ble\_gap\_update\_conn\_params(esp_ble_conn_update_params_t \*params);//在连接建立后更新连接参数
esp\_ble\_gap\_clear\_rand\_addr(void);//清空应用的随机地址
esp\_ble\_gap\_update\_whitelist(bool add_remove, esp_bd_addr_t remote_bda, esp_ble_wl_addr_type_t wl_addr_type);//新建或移除白名单中的设备
esp\_ble\_gap\_get\_whitelist\_size(uint16_t \*length);//获取白名单的大小
esp\_ble\_gap\_set\_device\_name(const char \*name);//设置本机设备名
esp\_ble\_gap\_get\_local\_used\_addr(esp_bd_addr_t local_used_addr, uint8_t \*addr_type);//获取本机设备地址
GATT Server的配置
Server-Master
基本设置
esp_err_t ret;//用于debug
esp_bt_controller_config_t bt_cfg = BT\_CONTROLLER\_INIT\_CONFIG\_DEFAULT();//设置蓝牙为默认参数
ret = nvs\_flash\_init();//初始化NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP\_ERROR\_CHECK(nvs\_flash\_erase());
ret = nvs\_flash\_init();
}
ESP\_ERROR\_CHECK(ret);
ESP\_LOGI(TAG, "%s init NVS finished\n", \_\_func\_\_);
ESP\_ERROR\_CHECK(esp\_bt\_controller\_mem\_release(ESP_BT_MODE_BLE));//释放蓝牙所需空间
ret = esp\_bt\_controller\_init(&bt_cfg);//初始化蓝牙控制器
if (ret)
{
ESP\_LOGE(TAG, "%s enable controller failed: %s\n", \_\_func\_\_, esp\_err\_to\_name(ret));
return;
}
ret = esp\_bt\_controller\_enable(ESP_BT_MODE_BLE);//使能蓝牙控制器
if (ret)
{
ESP\_LOGE(TAG, "%s enable controller failed: %s\n", \_\_func\_\_, esp\_err\_to\_name(ret));
return;
}
ret = esp\_bluedroid\_init();//初始化蓝牙栈bluedroid stack
/\*
蓝牙栈bluedroid stack包括了BT和BLE使用的基本的define和API
初始化蓝牙栈以后并不能直接使用蓝牙功能,
还需要用FSM管理蓝牙连接情况
\*/
if (ret)
{
ESP\_LOGE(TAG, "%s init bluetooth failed: %s\n", \_\_func\_\_, esp\_err\_to\_name(ret));
return;
}
ret = esp\_bluedroid\_enable();//使能蓝牙栈
if (ret)
{
ESP\_LOGE(TAG, "%s enable bluetooth failed: %s\n", \_\_func\_\_, esp\_err\_to\_name(ret));
return;
}
ESP\_LOGI(TAG, "%s init bluetooth finished\n", \_\_func\_\_);
//建立蓝牙的FSM
//这里使用回调函数来控制每个状态下的响应,需要将其在GATT和GAP层的回调函数注册
ret = esp\_ble\_gatts\_register\_callback(BLE_gatts_event_handler);
if (ret)
{
ESP\_LOGE(TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp\_ble\_gap\_register\_callback(BLE_gap_event_handler);
if (ret)
{
ESP\_LOGE(TAG, "gap register error, error code = %x", ret);
return;
}
/\*BLE\_gatts\_event\_handler和BLE\_gap\_event\_handler处理蓝牙栈可能发生的所有情况,达到FSM的效果\*/
//下面创建了两个BLE GATT profile,相当于两个独立的应用程序
ret = esp\_ble\_gatts\_app\_register(GATT_APP_A_ID);
if (ret)
{
ESP\_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
ret = esp\_ble\_gatts\_app\_register(GATT_APP_B_ID);
if (ret)
{
ESP\_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
有限状态机FSM(finite state machine),或者说状态机SM(state machine)是一种特殊的控制算法,能够根据控制信号按照预先设定的状态进行状态转移
若输出只和状态有关而与输入无关,则称为Moore状态机;若输出不仅和状态有关而且和输入有关系,则称为Mealy状态机
控制蓝牙的状态机一般为Moore状态机,随蓝牙所处的状态进行不同的操作(代码中通过switch语句进行控制)
Server的profile利用一个结构体来定义,结构体成员取决于在这个profile中执行的service和characteristic,如下所示
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;//GATT回调函数
uint16_t gatts_if;//GATT接口
uint16_t app_id;//应用的ID
uint16_t conn_id;//连接的ID
uint16_t service_handle;//Service句柄
esp_gatt_srvc_id_t service_id;//Service ID
uint16_t char_handle;//Characteristic句柄
esp_bt_uuid_t char_uuid;//Characteristic的UUID
esp_gatt_perm_t perm;//属性Attribute 授权
esp_gatt_char_prop_t property;//Characteristic的优先级
uint16_t descr_handle;//Client的Characteristic配置句柄
esp_bt_uuid_t descr_uuid;//Client的Characteristic UUID
};
可以将这个结构体进一步组合为结构体数组
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gatts_cb = gatts_profile_a_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
[PROFILE_B_APP_ID] = {
.gatts_cb = gatts_profile_b_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
},
};
这样使用类似gl_profile_tab[i].gatts_if
的语句就可以访问结构体的成员,i用于指示第(i+1)个profile
使用上面的结构体数组来定义每个profile对应的GATT回调函数(gatts_profile_a_event_handler()、gatts_profile_b_event_handler()),就使得每个不同的profile使用不同的接口;初始化时,将gatts_if = ESP_GATT_IF_NONE,在之后通过各自的处理函数将profile连接到接口
最后使用esp_ble_gatts_app_register()这个API将应用的ID注册到GATT
ret = esp\_ble\_gatts\_app\_register(GATT_APP_A_ID);//run GATT app A register
if (ret)
{
ESP\_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
ret = esp\_ble\_gatts\_app\_register(GATT_APP_B_ID);//run GATT app B register
if (ret)
{
ESP\_LOGE(TAG, "gatts app register error, error code = %x", ret);
return;
}
GAP设置
使用esp_ble_adv_data_t结构体来配置GAP广播情况,并使用esp_ble_gap_config_adv_data()函数进行广播
typedef struct {
bool set_scan_rsp;//是否作为扫描的回应信号广播
bool include_name;//是否包括设备名
bool include_txpower;//是否包括信号的发射功率
int min_interval;//广播数据显示slave设备的连接最小时间间隔
int max_interval;//广播数据显示slave设备的连接最大时间间隔
int appearance;//设备外观(?)
uint16_t manufacturer_len;//附加数据长度
uint8_t \*p_manufacturer_data;//附加数据指针
uint16_t service_data_len;//Service数据长度
uint8_t \*p_service_data;//Service数据指针
uint16_t service_uuid_len;//Servic UUID长度
uint8_t \*p_service_uuid;//Servic UUID指针
uint8_t flag;//广播的发现模式,可选BLE\_ADV\_DATA\_FLAG枚举值
} esp_ble_adv_data_t;
//设置示例
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = false,
.min_interval = 0x0006, //slave connection min interval, Time = min\_interval \* 1.25 msec=7.5ms
.max_interval = 0x0010, //slave connection max interval, Time = max\_interval \* 1.25 msec=20ms
.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 = sizeof(adv_service_uuid128),
.p_service_uuid = adv_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
一个广播的有效数据是31字节,如果超过会导致超出部分被截掉
使用esp_ble_gap_config_adv_data_raw()和esp_ble_gap_config_scan_rsp_data_raw()函数可以广播自定义的空数据
广播数据设置完毕后,会自动进入ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT()或ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT状态,此时可以在gap_event_handler()中设置FSM控制程序
static void BLE\_gap\_event\_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t\* param)
{
switch (event)
{
#ifdef CONFIG\_SET\_RAW\_ADV\_DATA
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
adv_config_done &= (~adv_config_flag);
if (adv_config_done == 0)
{
esp\_ble\_gap\_start\_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done == 0)
{
esp\_ble\_gap\_start\_advertising(&adv_params);
}
break;
#else
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
adv_config_done &= (~adv_config_flag);
if (adv_config_done == 0)
{
esp\_ble\_gap\_start\_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done == 0)
{
esp\_ble\_gap\_start\_advertising(&adv_params);
}
break;
#endif
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
//advertising start complete event to indicate advertising start successfully or failed
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
{
ESP\_LOGE(TAG, "Advertising start failed\n");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS)
{
ESP\_LOGE(TAG, "Advertising stop failed\n");
}
else
{
ESP\_LOGI(TAG, "Stop adv successfully\n");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP\_LOGI(TAG, "update connection params status = %d, min\_int = %d, max\_int = %d,conn\_int = %d,latency = %d, timeout = %d",
param->update_conn_params.status,
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
default:
break;
}
}
只要使用了esp_ble_gap_start_advertising()函数,GATT Server就会开始广播,在此之前还需要用esp_ble_adv_params_t结构体配置相关的参数
//广播参数
typedef struct {
uint16_t adv_int_min;
//非定向和循环定向广播的最小时间间隔
//间隔设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N \* 0.625 ms,时间范围在20ms到10.24s
uint16_t adv_int_max;
//非定向和循环定向广播的最大时间间隔
//间隔设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N \* 0.625 ms,时间范围在20ms到10.24s
esp_ble_adv_type_t adv_type;//广播类型


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
了esp\_ble\_gap\_start\_advertising()函数,GATT Server就会开始广播,在此之前还需要用esp\_ble\_adv\_params\_t结构体配置相关的参数
//广播参数
typedef struct {
uint16_t adv_int_min;
//非定向和循环定向广播的最小时间间隔
//间隔设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N * 0.625 ms,时间范围在20ms到10.24s
uint16_t adv_int_max;
//非定向和循环定向广播的最大时间间隔
//间隔设置在0x0020到0x4000,默认0x0800(1.28s),实际时间=N \* 0.625 ms,时间范围在20ms到10.24s
esp_ble_adv_type_t adv_type;//广播类型
[外链图片转存中…(img-QhG11NSS-1715785973010)]
[外链图片转存中…(img-YKRjsiUw-1715785973010)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)