【保姆级IDF】ESP32蓝牙之BLE服务端通信

Tips:

抛砖引玉,本文记录ESP32学习过程中遇到的收获。如有不对的地方,欢迎指正。

目录

1. 前言

2.实现功能

3.代码思路

4.BLE相关基础知识

1.什么是BLE?

2.BLE如何通信?

3.服务、特性、属性、描述符

4.UUID

5.协议层GAP和GATT

5.代码讲解

1.头文件、宏定义、全局变量

2.GATTS服务相关函数

        1.GAP事件函数

        2.准备写操作处理函数

        3.批量写事件

        4.GATT配置文件事件函数

        5.GATT事件句柄函数

        6.freertos定时器配置函数

3.主函数

6.成果展示

7.总结


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?

        BLEBluetooth 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

        GAPBLE 协议栈中的一个高层协议,它定义了设备的 广播(advertising)连接(connection) 方式,负责设备之间的发现、配对、连接管理等任务。GAP 主要涉及设备之间如何找到对方,如何建立和管理连接,如何处理设备间的通信权限等。GAP 主要功能:在没有建立连接的情况下,BLE 设备可以广播自己的存在信息(称为 广播),其他设备可以接收到这些信息并决定是否进行连接。通过 GAP,设备可以发起或接受连接。它负责协商连接的参数,如连接间隔、时隙等。

        GATTBLE 协议栈中的另一个重要协议,它定义了如何通过 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_if1conn_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

### starRC、LEF 和 DEF 文件的 EDA 工具使用教程 #### 关于 starRC 的使用说明 starRC 是由 Synopsys 开发的一款用于寄生参数提取 (PEX) 的工具,在 detail routing 完成之后被调用,以提供精确的电阻电容延迟分析数据[^2]。该工具能够处理复杂的多层互连结构并支持多种工艺节点。 对于 starRC 的具体操作指南,通常可以从官方文档获取最权威的信息。访问 Synopsys 官方网站的技术资源页面,可以找到最新的产品手册以及应用笔记等资料。此外,还可以通过在线帮助系统获得交互式的指导和支持服务。 #### LEF 和 DEF 文件格式解析及其在 Cadence 中的应用 LEF(Library Exchange Format)和 DEF(Design Exchange Format)是两种广泛应用于集成电路布局布线阶段的标准文件格式之一[^3]。前者主要用于描述标准单元库中的元件几何形状;后者则记录了整个芯片版图的设计信息,包括但不限于各个模块的位置关系、网络连接情况等重要细节。 当涉及到这些文件类型的编辑或读取时,Cadence 提供了一系列强大的平台解决方案,比如 Virtuoso Layout Editor 就可以直接打开并修改 LEF/DEF 格式的项目工程。为了更好地理解和运用这两种文件格式,建议参阅 Cadence 发布的相关培训材料或是参加其举办的专项课程学习活动。 ```bash # 示例命令:查看 LEF 或 DEF 文件内容 cat my_design.lef cat my_design.def ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_山岚_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值