Tips:
抛砖引玉,本文记录ESP32学习过程中遇到的收获。如有不对的地方,欢迎指正。
目录
1. 前言
本文为记录ESP32蓝牙使用的心得体会,也是蓝牙系列第一篇,所以会介绍一些BLE的基础知识以及专业名词。这里博主使用的还是合宙的ESP32C3开发板:
由于本文描述的功能涉及到使用开发板上自带的两颗LED灯珠,所以为了方便讲解,博主也把该开发板的原理图上传到附件了,读者可自行下载查看。
2.实现功能
1.使用ESP32的BLE作为服务端设备,向外广播,接受其他设备连接。
2.修改BLE的特性的不同权限,展示其不同权限下写操作、读操作、开启通知的功能。
3.使用BLE的服务、特性、属性、描述符等功能实现利用手机控制两颗红色LED闪烁。(因为没有买传感器模块)
3.代码思路
1.初始化并开启蓝牙控制器,对蓝牙进行配置,为BAT GATT模块注册回调函数,为GAP事件注册回调函数。注册蓝牙应用程序ID。
2.配置GPIO,初始化IO12和IO13以控制LED1和LED2。并通过蓝牙发送LED状态到手机
3.在不同的GATTS事件中操作控制LED闪烁。
4.BLE相关基础知识
1.什么是BLE?
BLE(Bluetooth Low Energy)是蓝牙技术的一种低功耗版本,也被称为蓝牙智能(Bluetooth Smart)。它是为低功耗、低数据传输速率的应用场景设计的,特别适用于需要长时间运行而又不希望频繁充电的设备。与传统的蓝牙(Classic Bluetooth)相比,BLE主要有以下几个特点:低功耗、短距离通信、低带宽、快速连接。BLE通常使用的是蓝牙4.0及以上版本的规范,包括:
GATT(Generic Attribute Profile):定义了如何在设备之间交换数据。
广播(Advertising):设备无需配对就能发送数据,适用于传感器或设备广播信号。
2.BLE如何通信?
BLE的通信方式主要基于广播(Advertising)和连接(Connection)两种模式,结合了低功耗和高效的数据交换。BLE通信过程大体可以分为以下几个阶段:
1. 广播(Advertising)
BLE设备通常采用广播方式来发送信息,而不是像传统蓝牙一样依赖配对过程。设备通过广播信号向周围设备发送其存在信息。广播过程如下:
发送广播包:BLE设备将其信息封装成广播包发送出去,这些广播包可以包含设备的名称、服务类型、设备能力等信息。
广播间隔:BLE设备通过定时发送广播包,通常每隔一定时间(例如,100毫秒)发送一次。广播间隔的长短影响设备的功耗和响应时间。
广播模式:广播有几种模式,包括:
可连接广播(Connectable Advertising):设备向其他设备发送广播信号,其他设备可以选择是否连接。
不可连接广播(Non-connectable Advertising):设备仅发送信息,不允许连接,适用于需要传递数据但不需要建立连接的情况(如定位标签、传感器等)。
定向广播(Directed Advertising):用于与特定设备建立连接,通常由一个已知地址来识别目标设备。
设备在广播模式下不会主动维护连接,而是让其他设备主动发起连接请求。
2. 扫描(Scanning)
其他设备(例如,手机或主设备)会扫描广播信号,寻找有用的信息。当扫描到感兴趣的广播包时,它会解析其中的内容(如设备ID、服务信息等)。一般有两种扫描模式:
主动扫描(Active Scanning):设备不仅接收广播信号,还会向广播设备发送扫描响应包,进一步确认设备信息。
被动扫描(Passive Scanning):设备仅接收广播信号,不会响应,通常用于节省功耗。
3. 连接(Connection)
一旦扫描到感兴趣的设备,主设备(比如手机)可以尝试与目标设备建立连接。连接过程分为以下步骤:
建立连接:设备选择是否与某个广播设备建立连接。当主设备决定连接时,它会发起连接请求(连接请求数据包)。目标设备如果愿意接受,则会返回连接响应,建立连接。
一旦连接建立,设备之间会交换一系列的信息,包括设备的能力、连接参数等。这一过程包括设备之间的配对(Pairing),可以包括安全认证(如密码或配对码)和加密。
4. 数据传输(Data Transfer)
在连接建立后,设备可以通过GATT协议进行数据交换。GATT(Generic Attribute Profile)是BLE中的一个核心协议,用于定义如何在设备间传输数据。它通过服务(Services)和特征(Characteristics)的结构来组织数据交换:
服务(Services):服务是一组相关特征的集合,用于完成某一类功能。例如,心率监测设备可能会有一个“心率服务”。
特征(Characteristics):特征是服务中的基本数据单元,存储实际的传感器数据或控制命令。每个特征有一个值,可以是传感器数据、设备配置项等。
设备间的数据交换使用读/写操作,可以通过特征的读写来进行交互。如果数据需要定期更新,BLE还支持通知(Notification)和指示(Indication)功能,设备可以异步地将数据推送给另一设备。
5. 断开连接(Disconnection)
通信完成后,BLE设备可以选择断开连接。当一个设备请求断开连接时,连接会被安全终止,设备返回到广播状态,准备进行下一轮的通信。
3.服务、特性、属性、描述符
在 Bluetooth Low Energy(BLE) 中,服务(Services)、特性(Characteristics)、属性(Attributes) 和 描述符(Descriptors) 是核心的概念,构成了数据传输和设备间通信的基础。这些概念主要基于 GATT(Generic Attribute Profile) 协议,它定义了如何在BLE设备之间组织和交换数据。
1. 服务(Services)
服务是 GATT 协议中的一个重要概念,它是一个由一组相关联的特性(Characteristics)组成的集合,用于描述设备的功能或行为。每个服务都代表了某种功能,通常由一个唯一的标识符(UUID) 来标识。服务提供了设备的基本功能描述。例如,健康监测设备的“心率服务”可能包括测量当前心率的功能,或“环境传感器服务”可能包括温度和湿度监控功能。
2. 特性(Characteristics)
特性是服务中的基本数据单元,用于表示设备的某个状态或功能。每个特性都有一个值(Value),这个值可以是设备的传感器数据、控制命令或其他状态信息。特性通过存储设备的实际数据或提供与设备交互的接口,供其他设备读写。特性可以是只读、只写或可读可写,具体取决于设备的设计。
3. 属性(Attributes)
属性是BLE通信中最基本的单位,它是一个键值对,存储着服务和特性的信息。每个服务和特性都是由属性组成的。每个属性都包含四个基本部分:
属性句柄(Handle):每个属性都有一个唯一的句柄,用于在设备间标识该属性。句柄是一个整数,用于在数据交换时标识特定的服务或特性。
UUID:用于标识属性的类型或功能的标识符。它可以是16位(简短UUID)或128位(完整UUID)。
属性值(Value):存储在属性中的数据。对于特性,值可能是数值、字符串或二进制数据。
属性权限(Permissions):定义对该属性的访问权限(如可读、可写、可通知等)。
4. 描述符(Descriptors)
描述符(Descriptors) 是与 特性(Characteristics) 相关的附加信息,用于描述特性的额外属性或行为。描述符通常用于提供更多关于特性的信息或控制特性的访问方式。描述符的作用有:
补充特性信息:描述符可以存储特性相关的元数据,提供更详细的描述。
定义特性行为:描述符可以用来控制或改变特性行为,如指定是否允许通知、指示,或控制特性值的格式。
用于设备间的交互:某些描述符允许其他设备与特性进行交互,例如,设备是否允许某些数据的读写。
4.UUID
UUID 是一个 128 位(16 字节)的数字,用来唯一标识某个对象。在 BLE 中,UUID 用来标识以下几种主要的元素:
服务(Services)、特性(Characteristics)、描述符(Descriptors)、其他资源或操作(如设备信息、传感器数据等)
UUID 的形式通常是 32 位、128 位、16 位等多种格式。标准服务和特性的 UUID 是已定义的,而厂商自定义的服务和特性会使用 128 位的 UUID 来避免与标准的冲突。
UUID的格式有以下2种:
16 位 UUID(简短 UUID):通常用于表示标准的 BLE 服务或特性,范围从0x0001到0xFFFF。16 位 UUID 是 BLE 标准中最常见的格式,用于大多数内建的、常见的服务和特性。
128 位 UUID(完整 UUID):用于自定义服务或特性,通常由厂商定义。128 位 UUID 是一个标准的 UUID 格式,通常表示厂商自定义的特性和服务。完整 UUID 可以通过在 16 位 UUID 前加上0000xxxx-0000-1000-8000-00805f9b34fb前缀来生成,其中xxxx为特性或服务的16位标识符。
Bluetooth SIG(Special Interest Group)定义了许多标准服务、特性和描述符,每个服务和特性都有一个固定的 UUID。以下是一些常见的标准服务UUID:
心率服务(Heart Rate Service):0x180D
电池服务(Battery Service):0x180F
运动服务(Running Speed and Cadence Service):0x1814
标准特性UUID:
心率测量(Heart Rate Measurement):0x2A37
电池电量(Battery Level):0x2A19
运动步数(Step Count):0x2A5D
除了标准的服务、特性和描述符外,BLE 还允许设备厂商创建自定义的服务和特性。为了避免与标准 UUID 冲突,厂商通常使用 128 位 UUID 来标识自定义服务或特性。例如,厂商可以定义一个自定义服务(比如一个智能家居设备的特定服务)并为其指定一个独一无二的 128 位 UUID。通常,厂商会使用某些工具或算法生成 128 位 UUID,确保它是唯一的。
5.协议层GAP和GATT
GAP 是 BLE 协议栈中的一个高层协议,它定义了设备的 广播(advertising) 和 连接(connection) 方式,负责设备之间的发现、配对、连接管理等任务。GAP 主要涉及设备之间如何找到对方,如何建立和管理连接,如何处理设备间的通信权限等。GAP 主要功能:在没有建立连接的情况下,BLE 设备可以广播自己的存在信息(称为 广播),其他设备可以接收到这些信息并决定是否进行连接。通过 GAP,设备可以发起或接受连接。它负责协商连接的参数,如连接间隔、时隙等。
GATT 是 BLE 协议栈中的另一个重要协议,它定义了如何通过 GATT 服务(GATT Services) 和 特征(Characteristics) 交换数据。GATT 负责设备连接后的数据传输和管理,它基于 属性(Attributes) 的概念进行工作。GATT 主要功能:GATT 基于服务和特征来定义和组织数据,服务是由多个特征组成的,每个特征可以包含实际的数据值。通过 GATT,BLE 设备之间可以传输数据。特征用于存储实际的数据值,客户端可以读取、写入、通知等。GATT 定义了多种数据操作,如读取、写入、通知、指示等,用于不同的交互模式。
以上只是对BLE的简单叙述,详细的BLE基础知识读者可以自行查阅网上资料,这方面内容在网上由不同大咖论述的更加精彩。
5.代码讲解
1.头文件、宏定义、全局变量
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_main.h"
#include "gatts_table_creat_demo.h"
#include "esp_gatt_common_api.h"
#include "driver/gpio.h"
#define GATTS_TABLE_TAG "GATTS_TABLE_DEMO" // ESP日志打印前缀
#define PROFILE_NUM 1 //GATT服务数量
#define PROFILE_APP_IDX 0 //GATT服务索引
#define ESP_APP_ID 0x55 //GATT服务ID
#define SAMPLE_DEVICE_NAME "ESP32_C3" //GATT服务名称,也即是对外广播蓝牙设备名称
#define SVC_INST_ID 0 //GATT服务实例ID
/* 特征值最大长度. 当GATT客户端准备执行写操作或准备写操作时,特征值长度必须小于此值 */
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500
#define PREPARE_BUF_MAX_SIZE 1024 //准备缓冲区最大长度
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) //特征声明大小
#define LED_PIN12 GPIO_NUM_12 //LED控制引脚1
#define LED_PIN13 GPIO_NUM_13 //LED控制引脚2
#define ADV_CONFIG_FLAG (1 << 0) //广播配置标志
#define SCAN_RSP_CONFIG_FLAG (1 << 1) //扫描响应配置标志
static uint8_t adv_config_done = 0; //广播配置完成标志
uint16_t heart_rate_handle_table[HRS_IDX_NB]; //特征值句柄表
typedef struct {
uint8_t *prepare_buf;
int prepare_len;
} prepare_type_env_t; //准备类型环境结构体
esp_gatt_if_t gatts_if1; //GATT接口(为方便rtos定时器使用所引出)
uint16_t conn_id1; //连接ID(为方便rtos定时器使用所引出)
static prepare_type_env_t prepare_write_env; //准备写环境结构体
TimerHandle_t notify_timer; //通知定时器句柄
#define CONFIG_SET_RAW_ADV_DATA //设置原始广播数据
#ifdef CONFIG_SET_RAW_ADV_DATA //是否设置原始广播数据
static uint8_t raw_adv_data[] = { //原始广播数据,纯字节流
/* flags */
0x02, 0x01, 0x06,
/* tx power*/
0x02, 0x0a, 0xeb,
/* service uuid */
0x03, 0x03, 0x10, 0x18,
/* device name */
0x0f, 0x09, 'E', 'S', 'P','3','2', '_','C','3'
};
static uint8_t raw_scan_rsp_data[] = { //原始扫描响应数据,纯字节流
/* flags */
0x02, 0x01, 0x06,
/* tx power */
0x02, 0x0a, 0xeb,
/* service uuid */
0x03, 0x03, 0xFF,0x00
};
#else //不设置原始广播数据,使用结构体打包
static uint8_t service_uuid[16] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};
/* The length of adv data must be less than 31 bytes */
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 = 0x0010, //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,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = sizeof(service_uuid),
.p_service_uuid = service_uuid,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
// scan response data
static esp_ble_adv_data_t scan_rsp_data = {
.set_scan_rsp = true,
.include_name = true,
.include_txpower = true,
.min_interval = 0x0006,
.max_interval = 0x0010,
.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(service_uuid),
.p_service_uuid = service_uuid,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
#endif /* CONFIG_SET_RAW_ADV_DATA */
static esp_ble_adv_params_t adv_params = { //广播参数
.adv_int_min = 0x20, //广播间隔下限:32*1.25ms=40ms
.adv_int_max = 0x40, //广播间隔上限:64*1.25ms=80ms
.adv_type = ADV_TYPE_IND, //广播类型: 一般可连接广播
.own_addr_type = BLE_ADDR_TYPE_PUBLIC, //广播地址类型:公共地址
.channel_map = ADV_CHNL_ALL, //广播通道:所有通道
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, //广播过滤策略:允许所有扫描和连接
};
struct gatts_profile_inst { //gatts配置结构体
esp_gatts_cb_t gatts_cb; //回调函数
uint16_t gatts_if; //gatts_if
uint16_t app_id; //app_id
uint16_t conn_id; //连接id
uint16_t service_handle; //服务句柄
esp_gatt_srvc_id_t service_id; //服务id
uint16_t char_handle; //特征句柄
esp_bt_uuid_t char_uuid; //特征id
esp_gatt_perm_t perm; //特征属性
esp_gatt_char_prop_t property; //特征属性
uint16_t descr_handle; //描述符句柄
esp_bt_uuid_t descr_uuid; //描述符id
};
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); //gatts服务事件回调函数声明
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
/*一个配置文件基于一个app_id和一个gatts_if,这个数组将存储在ESP_GATTS_REG_EVT返回的gatts_if中*/
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = { //gatts配置表
[PROFILE_APP_IDX] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
/* Service */
static const uint16_t GATTS_SERVICE_UUID_TEST = 0x1810; //服务UUID
static const uint16_t GATTS_CHAR_UUID_TEST_A = 0x2A35; //特征1_UUID
static const uint16_t GATTS_CHAR_UUID_TEST_B = 0x2A36; //特征2_UUID
static const uint16_t GATTS_CHAR_UUID_TEST_C = 0x2A37; //特征3_UUID
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; //主服务UUID
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; //特征声明UUID
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; //特征客户端配置UUID
//static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
//static const uint8_t char_prop_write = ESP_GATT_CHAR_PROP_BIT_WRITE;
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY; //特征属性:可读、可写、可通知
static const uint8_t heart_measurement_ccc[2] = {0x80, 0x00}; //特征客户端配置值
static uint8_t char_led1[11] = {0x4f}; //用于显示LED1状态的默认特征值,随意设置
static uint8_t char_led2[11] = {0x4f}; //用于显示LED2状态的默认特征值,随意设置
/* 完整数据库描述 - 用于向数据库中添加属性 */
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{
// 服务声明
[IDX_SVC] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},
/* A:特征声明 */
[IDX_CHAR_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},
/* A:特征值 */
[IDX_CHAR_VAL_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_led1), (uint8_t *)char_led1}},
/* A:客户端特征配置描述符 */
[IDX_CHAR_CFG_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
/* B:特征声明 */
[IDX_CHAR_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},
/* B:特征值 */
[IDX_CHAR_VAL_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_led1), (uint8_t *)char_led1}},
/* B:客户端特征配置描述符 */
[IDX_CHAR_CFG_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
/* C:特征声明 */
[IDX_CHAR_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},
/* C:特征值 */
[IDX_CHAR_VAL_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_led1), (uint8_t *)char_led1}},
/* C:客户端特征配置描述符 */
[IDX_CHAR_CFG_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
};
这里使用原始广播数据,效果和使用esp_ble_adv_data_t结构体是一样的,raw_adv_data数组存放的就是原始广播数据。广播数据是用来告诉其他设备自身的一些信息,比如名称、服务UUID、制造商数据等。如果要修改蓝牙设备的名称,那么不仅需要修改宏定义SAMPLE_DEVICE_NAME,还需要在raw_adv_data数组中修改原始广播数据,否则其他设备扫描到的蓝牙设备名称会以raw_adv_data数组中数据为准。且将两者修改为同一名称。
adv_params为esp_ble_adv_params_t类型的结构体变量,它用来存储广播参数的信息。包括广播间隔下限、广播类型、广播地址类型、广播通道、广播过滤策略。这里面需要注意的是,广播上下限时间间隔的计算:BLE 广播间隔的单位是1.25 ms。换句话说,每个字段的值代表了该时间间隔的最小单位,你需要将其转换为实际的时间。比如我这里填的0x20,十进制为32,那么实际间隔时间就是32*1.25=40ms。
基于gatts_profile_inst结构体创建的结构体数组heart_rate_profile_tab包含了GATT的配置信息,其中gatts_cb为回调函数,在GATTS事件发生时会调用这个回调函数。
gatts_if是一个用来标识GATT服务器接口的标识符,代表一个GATT服务器实例,它在BLE GATT操作中用于区分不同的GATT服务器。例如,在同一个设备上可能会有多个服务(如多个 GATT 服务),每个服务都会有一个独立的gatts_if值。
app_id是一个标识应用程序(App)的标识符。它通常用于区分不同的BLE应用程序,尤其是当同一个设备上有多个独立的BLE应用时。app_id是一个整数值,在BLE框架中用于区分不同的应用程序或服务实例,确保每个BLE应用的注册、事件处理和资源管理不会发生冲突。如果一个设备上有多个BLE应用程序(例如,一个应用处理GATT服务器,另一个应用处理GATT客户端),app_id用于标识和区分这些应用。
conn_id是一个连接标识符(Connection ID),用于唯一标识一个BLE连接。每当BLE设备与另一个设备建立连接时,ESP32的BLE栈会为该连接分配一个唯一的conn_id。这个标识符是连接生命周期中的关键,通常用于区分和管理多个并发的BLE连接。对于一个BLE设备来说,在同一时间它可能与多个设备保持连接,每个连接都会有一个不同的conn_id。
service_handle是一个用于标识和管理一个 GATT 服务的句柄(handle)。它是一个由 BLE 框架在服务注册过程中分配的唯一标识符,类似于数据库中的记录指针,用于区分和操作不同的服务。
service_id是一个用于标识和管理 GATT 服务的标识符,它与service_handle类似,但用途略有不同。service_id通常与 GATT 服务的注册过程密切相关,能够在应用程序中唯一标识一个服务。
char_handle是一个特征句柄(Characteristic Handle),用于唯一标识 GATT 特征(Characteristic)。每个 GATT 服务可以包含多个特征,而每个特征又有一个唯一的句柄(char_handle),该句柄用于标识特定的特征并在 GATT 操作中引用它。
char_uuid是指Characteristic UUID(特征的 UUID),它用于唯一标识设备上的某个特征(Characteristic)。ESP32 作为一个广泛使用的蓝牙开发平台,通常会在 BLE 通信中定义各种服务和特征,char_uuid就是用来标识这些特征的标识符。
perm通常用于定义BLE 特征(Characteristic)在蓝牙通信中访问的权限。权限决定了蓝牙客户端是否能够对特征进行读取、写入、或订阅等操作。
property代表BLE 特征的属性,即该特征支持的操作类型。每个BLE 特征(Characteristic)都可以有不同的属性,用于定义该特征能够被执行的操作,如读取、写入、通知、指示等。
descr_handle通常用于标识和操作BLE 特征的描述符(Descriptor)。描述符是与BLE 特征(Characteristic)相关联的一些额外信息或元数据,它们提供了特征的更详细描述或附加功能。
descr_uuid表描述符的 UUID(Universal Unique Identifier,通用唯一标识符)。它用于唯一标识 BLE 特征(Characteristic)相关的描述符(Descriptor)。
使用esp_gatts_attr_db_t结构体定义一个完整的GATT服务数据库,他的成员有esp_attr_control_t类型的attr_control和esp_attr_desc_t类型的att_desc。attr_control表示属性控制类型,我们这里是属性自动响应。重点是esp_attr_desc_t类型,他表示主要用于表示 GATT 属性描述符(Attribute Descriptor)的相关信息。用于表示服务或特征的相关信息,他的成员有:
uuid_length表示UUID长度、*uuid_p表示uuid值、perm表示权限、max_length表示元素最大长度、length表示当前长度、*value表示元素值数组。
/**
* @brief Attribute description (used to create database)
*/
typedef struct
{
uint16_t uuid_length; /*!< UUID length */
uint8_t *uuid_p; /*!< UUID value */
uint16_t perm; /*!< Attribute permission */
uint16_t max_length; /*!< Maximum length of the element*/
uint16_t length; /*!< Current length of the element*/
uint8_t *value; /*!< Element value array*/
} esp_attr_desc_t;
/**
* @brief attribute auto response flag
*/
typedef struct
{
#define ESP_GATT_RSP_BY_APP 0
#define ESP_GATT_AUTO_RSP 1
/**
* @brief if auto_rsp set to ESP_GATT_RSP_BY_APP, means the response of Write/Read operation will by replied by application.
if auto_rsp set to ESP_GATT_AUTO_RSP, means the response of Write/Read operation will be replied by GATT stack automatically.
*/
uint8_t auto_rsp;
} esp_attr_control_t;
/**
* @brief attribute type added to the gatt server database
*/
typedef struct
{
esp_attr_control_t attr_control; /*!< The attribute control type */
esp_attr_desc_t att_desc; /*!< The attribute type */
} esp_gatts_attr_db_t;
基于以上信息创建的服务数据库,参数依次代表以上信息。
2.GATTS服务相关函数
1.GAP事件函数
static void 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(GATTS_TABLE_TAG, "advertising start failed");
}else{
ESP_LOGI(GATTS_TABLE_TAG, "advertising start successfully");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: //广播停止完成事件
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed");
}
else {
ESP_LOGI(GATTS_TABLE_TAG, "Stop adv successfully");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: //连接参数更新事件
ESP_LOGI(GATTS_TABLE_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;
}
}
GAP主要负责广播连接配对等操作,所以在他的事件当中主要是做这些工作。是一个偏底层一点的协议层
2.准备写操作处理函数
void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
{
ESP_LOGI(GATTS_TABLE_TAG, "prepare write, handle = %d, value len = %d", param->write.handle, param->write.len);
esp_gatt_status_t status = ESP_GATT_OK;
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {//如果偏移量大于缓冲区大小
status = ESP_GATT_INVALID_OFFSET; //则设置无效偏移量
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {//否则如果偏移量+写入长度大于缓冲区大小
status = ESP_GATT_INVALID_ATTR_LEN; //则设置无效属性长度
}
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) { //如果状态为OK且准备写入环境中的缓冲区为空
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t)); //则分配缓冲区
prepare_write_env->prepare_len = 0; //设置准备写入长度为0
if (prepare_write_env->prepare_buf == NULL) { //如果分配失败
ESP_LOGE(GATTS_TABLE_TAG, "%s, Gatt_server prep no mem", __func__);//则打印无内存
status = ESP_GATT_NO_RESOURCES; //则设置状态为无资源
}
}
/*当param->write.need_rsp为真时发送响应*/
if (param->write.need_rsp){
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));//分配内存
if (gatt_rsp != NULL){ //如果分配成功,则设置gatt_rsp的各属性值
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK) {//发送响应,若发送不成功则打印错误信息
ESP_LOGE(GATTS_TABLE_TAG, "Send response error");
}
free(gatt_rsp); //释放内存
}else{
ESP_LOGE(GATTS_TABLE_TAG, "%s, malloc failed", __func__);//如果分配失败则打印错误信息
status = ESP_GATT_NO_RESOURCES; //设置状态为无资源
}
}
if (status != ESP_GATT_OK){ //如果状态不为OK
return; //则返回
}
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);//将写入值复制到准备写入缓冲区中
prepare_write_env->prepare_len += param->write.len; //更新准备写入长度
}
该操作是会分多次写入,写入指的是其他设备(如手机)向BLE服务端设备写入。准备写则是防止数据过长分批次写入。由于分批写入,所以每次写入都需要继承上一次写入的地方,从这里开始继续写,所以函数第一部分检查偏移值offset,如果溢出则进行对应的错误处理。如果正常则为准备写缓冲区分配内存。随后检查是否需要回应,若需要则进行相应配置并发送回应,操作结束后收回内存。最后将单次写操作的数据存入准备写的缓冲区。在这个先后顺序中,写缓冲区在准备写缓冲区之前,分批次的数据最终依次存入准备写缓冲区中。
3.批量写事件
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf){ //如果执行写入标志为执行且准备写入缓冲区不为空
esp_log_buffer_hex(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); //打印准备写入缓冲区
}else{
ESP_LOGI(GATTS_TABLE_TAG,"ESP_GATT_PREP_WRITE_CANCEL"); //否则打印取消写入
}
if (prepare_write_env->prepare_buf) { //如果准备写入缓冲区不为空
free(prepare_write_env->prepare_buf); //则释放内存
prepare_write_env->prepare_buf = NULL; //设置准备写入缓冲区为空
}
prepare_write_env->prepare_len = 0; //设置准备写入长度为0
}
首先检查param->exec_write.exec_write_flag是否等于ESP_GATT_PREP_WRITE_EXEC,这表示是否执行准备写入的操作。如果标志为ESP_GATT_PREP_WRITE_EXEC且prepare_write_env->prepare_buf不为空,则调用esp_log_buffer_hex函数将准备写入的数据以十六进制格式打印出来。否则,打印一条日志信息,表示写操作被取消。
检查prepare_write_env->prepare_buf是否为空,如果不为空,则释放这块内存,并将指针设置为NULL。将prepare_write_env->prepare_len设置为0,表示准备写入的数据长度已经处理完毕。
4.GATT配置文件事件函数
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
static bool level = false; //控制LED状态的布尔变量
switch (event) {
case ESP_GATTS_REG_EVT:{ //注册事件
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);//设置设备名称
if (set_dev_name_ret){
ESP_LOGE(GATTS_TABLE_TAG, "set device name failed, error code = %x", set_dev_name_ret);
}
#ifdef CONFIG_SET_RAW_ADV_DATA //如果配置了原始广告数据
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));//配置原始广告数据
if (raw_adv_ret){
ESP_LOGE(GATTS_TABLE_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
}
adv_config_done |= ADV_CONFIG_FLAG;//设置广告配置完成标志
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));//配置原始扫描响应数据
if (raw_scan_ret){
ESP_LOGE(GATTS_TABLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
}
adv_config_done |= SCAN_RSP_CONFIG_FLAG;//设置扫描响应配置完成标志
#else //否则
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret);
}
adv_config_done |= ADV_CONFIG_FLAG;
//config scan response data
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "config scan response data failed, error code = %x", ret);
}
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#endif //结束条件编译
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);//创建属性表
if (create_attr_ret){
ESP_LOGE(GATTS_TABLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
}
}
break;
case ESP_GATTS_READ_EVT: //读取事件
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT");
level = !level;
gpio_set_level(LED_PIN13, level);//设置LED灯状态
//esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
//sizeof(char_led1), char_led1, false);
break;
case ESP_GATTS_WRITE_EVT: //写入事件
if (!param->write.is_prep){ //如果不是准备写
// GATT客户端写入长度必须小于GATTS_DEMO_CHAR_VAL_LEN_MAX.
ESP_LOGI(GATTS_TABLE_TAG, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len);
esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len); //打印写入的数据
if (heart_rate_handle_table[IDX_CHAR_CFG_A] == param->write.handle && param->write.len == 2){//如果写入的是配置描述符且长度为2
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];//将写入的数据转换为16位
if (descr_value == 0x0001){ //如果写入的是通知
ESP_LOGI(GATTS_TABLE_TAG, "notify enable");
// uint8_t notify_data[15];
// for (int i = 0; i < sizeof(notify_data); ++i)
// {
// notify_data[i] = i % 0xff;
// }
xTimerStart(notify_timer, 0); //开启freertos定时器
//the size of notify_data[] need less than MTU size
//esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
//sizeof(notify_data), notify_data, false);
}else if (descr_value == 0x0002){//如果写入的是指示
ESP_LOGI(GATTS_TABLE_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i % 0xff;
}
// if want to change the value in server database, call:
// esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_A], sizeof(indicate_data), indicate_data);
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
sizeof(indicate_data), indicate_data, true);//发送指示
}
else if (descr_value == 0x0000){ //如果写入的是禁用通知
ESP_LOGI(GATTS_TABLE_TAG, "notify/indicate disable ");
//gpio_set_level(LED_PIN12, 0);
xTimerStop(notify_timer, 0); //停止freertos定时器
}else{ //如果写入的是其他值
ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");
esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len);
}
}
/* send response when param->write.need_rsp is true*/
if (param->write.need_rsp){ //如果需要响应
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);//发送响应
}
}else{
/* handle prepare write */
example_prepare_write_event_env(gatts_if, &prepare_write_env, param);//处理准备写入事件
}
break;
case ESP_GATTS_EXEC_WRITE_EVT://执行写入事件
// the length of gattc prepare write data must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
example_exec_write_event_env(&prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT://当设置MTU完成时,产生此事件
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
case ESP_GATTS_CONF_EVT://当客户端确认了通知或指示时,产生此事件
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d", param->conf.status, param->conf.handle);
break;
case ESP_GATTS_START_EVT://当服务开始时,产生此事件
ESP_LOGI(GATTS_TABLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT://当连接建立时,产生此事件
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
esp_log_buffer_hex(GATTS_TABLE_TAG, param->connect.remote_bda, 6);//打印连接的设备的地址
esp_ble_conn_update_params_t conn_params = {0};//创建连接参数
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));//设置连接参数的设备地址
/* For the iOS system, please refer to Apple official documents about the BLE connection parameters restrictions. */
conn_params.latency = 0;
conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
//start sent the update connection parameters to the peer device.
esp_ble_gap_update_conn_params(&conn_params);//更新连接参数
break;
case ESP_GATTS_DISCONNECT_EVT://当连接断开时,产生此事件
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
esp_ble_gap_start_advertising(&adv_params);//断开连接后,开始广播
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{//当创建属性表完成时,产生此事件
if (param->add_attr_tab.status != ESP_GATT_OK){
ESP_LOGE(GATTS_TABLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
}
else if (param->add_attr_tab.num_handle != HRS_IDX_NB){
ESP_LOGE(GATTS_TABLE_TAG, "create attribute table abnormally, num_handle (%d) \
doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, HRS_IDX_NB);
}
else {
ESP_LOGI(GATTS_TABLE_TAG, "create attribute table successfully, the number handle = %d",param->add_attr_tab.num_handle);
memcpy(heart_rate_handle_table, param->add_attr_tab.handles, sizeof(heart_rate_handle_table));
esp_ble_gatts_start_service(heart_rate_handle_table[IDX_SVC]);//启动服务
}
break;
}
case ESP_GATTS_STOP_EVT://当停止服务时,产生此事件
case ESP_GATTS_OPEN_EVT://当连接到对端时,产生此事件
case ESP_GATTS_CANCEL_OPEN_EVT://当从对端断开连接时,产生此事件
case ESP_GATTS_CLOSE_EVT://当关闭GATT服务时,产生此事件
case ESP_GATTS_LISTEN_EVT://当监听连接时,产生此事件
case ESP_GATTS_CONGEST_EVT://当连接被拥塞时,产生此事件
case ESP_GATTS_UNREG_EVT://当取消注册时,产生此事件
case ESP_GATTS_DELETE_EVT://当删除服务时,产生此事件
default:
break;
}
}
以上涵盖了GATT的所有事件处理程序,可在不同事件下进行想要的操作,实现蓝牙控制的功能。在读事件中,ESP32作为服务端接收到其他设备的读取操作时,我们选择对LED进行翻转。在写事件中,我们在判断是否开启通知的程序内开启freertos定时器用以控制另外一颗LED自行翻转。这里的功能只是做简单演示,通知功能还可根据具体的数据变化进行自动发送。读者可以在理解BLE的机制后使用标准UUID自行开发其他功能。
5.GATT事件句柄函数
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
/* 当注册事件发生时,为每个配置文件存储gatts_if */
if (event == ESP_GATTS_REG_EVT) { //如果事件是注册事件
if (param->reg.status == ESP_GATT_OK) { //如果注册成功
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;//分配gatts_if
gatts_if1 = gatts_if; // 用于控制LED时发送通知函数需要的参数,将其引出
conn_id1 = param->write.conn_id;// 用于控制LED时发送通知函数需要的参数,将其引出
} else {
ESP_LOGE(GATTS_TABLE_TAG, "reg app failed, app_id %04x, status %d",//错误处理
param->reg.app_id,
param->reg.status);
return;
}
}
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) { //遍历所有配置文件
/* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
if (gatts_if == ESP_GATT_IF_NONE || gatts_if == heart_rate_profile_tab[idx].gatts_if) {//如果gatts_if为空或者与配置文件中的gatts_if相同
if (heart_rate_profile_tab[idx].gatts_cb) { //如果配置文件中有回调函数
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param); //调用回调函数处理相关事件
}
}
}
} while (0);
}
该函数用以处理GATT事件,配置GATT服务器编号,并查找服务中发生事件的gatt_if,去到对应的回调函数处理具体事件。具体实现原理在代码中有注释解释。
6.freertos定时器配置函数
// 定时发送通知的回调函数
void notify_timer_callback(TimerHandle_t xTimer) {
// 每次定时器触发时发送通知
ESP_LOGI("BLE", "Sending periodic notification");
static bool level1 = false;
level1 = !level1;
gpio_set_level(LED_PIN12, level1); // 设置LED状态
esp_ble_gatts_send_indicate(gatts_if1, conn_id1, heart_rate_handle_table[IDX_CHAR_VAL_A],
sizeof(char_led2), char_led2,false);
}
// 启动定时器发送通知
void start_periodic_notify() {
// 创建一个周期性定时器
notify_timer = xTimerCreate(
"notify_timer", // 定时器名称
pdMS_TO_TICKS(1000), // 定时器周期,单位是毫秒 (此例为10秒)
pdTRUE, // 设置为周期性定时器
(void*) 0, // 定时器 ID,这里可以用来区分多个定时器
notify_timer_callback // 定时器回调函数
);
if (notify_timer == NULL) {
ESP_LOGE("BLE", "Failed to create timer");
return;
}
// 启动定时器
//xTimerStart(notify_timer, 0);
}
start_periodic_notify函数在主函数中调用,配置为1s闪烁LED。在GATT事件句柄函数中引出的gatt_if1和conn_id1在这里就是为了方便传入esp_ble_gatts_send_indicate函数用以发送通知告诉设备我们在翻转LED,在使用LED之前需要正确配置连接两颗LED的GPIO口。在合宙ESP32C3开发板中,连接两颗LED的IO分别为IO12和IO13,见下图。所以我们需要对这两个IO进行配置。
由原理图可知,IO12和IO13经过1K限流电阻连接LED正极,LED负极接地,所以我们只需控制IO口为高电平时即可点亮LED,低电平时关闭LED。对GPIO的配置放在主函数中进行初始化。
3.主函数
void app_main(void)
{
esp_err_t ret;
/* Initialize NVS. */
ret = nvs_flash_init();
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_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));//释放经典蓝牙内存
char *led1statue;//创建两个字符串用于存储LED状态
char *led2statue;
led1statue = malloc(12);//分配内存
led2statue = malloc(12);
strcpy(led1statue, "LED1 Toggle");//将字符串复制到内存中
strcpy(led2statue, "LED2 Toggle");
for (size_t i = 0; i < 11; i++) {//将字符串转换为char数组
char_led1[i] = led1statue[i];
}
for (size_t i = 0; i < 11; i++) {//将字符串转换为char数组
char_led2[i] = led2statue[i];
}
free(led1statue);//释放内存
free(led2statue);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();//初始化蓝牙控制器配置
ret = esp_bt_controller_init(&bt_cfg);//初始化蓝牙控制器
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);//使能蓝牙控制器
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();//初始化蓝牙配置
ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg);//初始化蓝牙
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();//使能蓝牙
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_ble_gatts_register_callback(gatts_event_handler);//注册GATTS事件回调函数
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);//注册GAP事件回调函数
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(ESP_APP_ID);//注册GATTS应用程序
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "gatts app register error, error code = %x", ret);
return;
}
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);//设置本地MTU
if (local_mtu_ret){
ESP_LOGE(GATTS_TABLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
gpio_set_direction(LED_PIN12, GPIO_MODE_OUTPUT);//设置GPIO12为输出模式
gpio_set_direction(LED_PIN13, GPIO_MODE_OUTPUT);//设置GPIO13为输出模式
gpio_set_level(LED_PIN12, 0); //设置GPIO12初始电平为低
gpio_set_level(LED_PIN13, 0);//设置GPIO13初始电平为低
start_periodic_notify();//配置定时器设置,等待启动定时器
}
主函数中初始化蓝牙的一些配置,并对自己的功能进行初始化,包括通知LED状态使用的字符串以及对GPIO的初始化,GPIO也可使用结构体打包初始化,这里这种方法简便一点。
6.成果展示
BLE控制
7.总结
以上就是关于BLE用作服务端的内容,使用BLE的基础应该是充分了解其协议规范,再将自己的理解带入到代码中去验证。这样应该可以事半功倍。
本文是蓝牙使用系列第一篇,所以介绍了BLE的基础相关内容,篇幅冗长,还请见谅。大家如果觉得有用,还请多多点赞收藏关注,你的支持就是对我最大的肯定。谢谢大家!
如有疑问欢迎留言交流。以上观点为个人理解,只想抛砖引玉,如有不对的地方欢迎指正,不喜轻喷。
2024.11.14-11:40