Skip to content

第十章:BLE 蓝牙编程

10.1 BLE 基础概念

BLE(Bluetooth Low Energy)核心概念:

GAP(Generic Access Profile):控制设备可见性和连接
  广播(Advertising):设备周期性广播自己的存在
  扫描(Scanning):发现周围的广播设备
  连接(Connection):建立双向通信

GATT(Generic Attribute Profile):数据交换协议
  Server(服务端):提供数据的设备(如传感器)
  Client(客户端):读取数据的设备(如手机)
  Service(服务):功能集合,用 UUID 标识
  Characteristic(特征值):具体数据项
  Descriptor(描述符):特征值的元数据

UUID:
  标准 UUID:16位(如 0x180F = 电池服务)
  自定义 UUID:128位(如 12345678-1234-1234-1234-123456789ABC)

GATT 结构示意

设备
└─ 服务 (Service) UUID: 0x180F 电池服务
   └─ 特征值 (Characteristic) UUID: 0x2A19 电池电量
      ├─ 属性: READ, NOTIFY
      ├─ 值: 85 (%)
      └─ 描述符 (Descriptor) UUID: 0x2902 CCCD(通知使能)

10.2 BLE 广播(Beacon)

c
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_bt_main.h"
#include "esp_log.h"
#include "nvs_flash.h"

static const char *TAG = "BLE_ADV";

// 广播数据
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp        = false,
    .include_name        = true,   // 包含设备名
    .include_txpower     = true,   // 包含发射功率
    .min_interval        = 0x0006,
    .max_interval        = 0x0010,
    .appearance          = 0x00,
    .manufacturer_len    = 0,
    .p_manufacturer_data = NULL,
    .service_data_len    = 0,
    .p_service_data      = NULL,
    .service_uuid_len    = 0,
    .p_service_uuid      = NULL,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// 广播参数
static esp_ble_adv_params_t adv_params = {
    .adv_int_min       = 0x20,   // 20ms * 0.625 = 12.5ms
    .adv_int_max       = 0x40,
    .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,
};

static void gap_event_handler(esp_gap_ble_cb_event_t event,
                               esp_ble_gap_cb_param_t *param)
{
    switch (event) {
        case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
            esp_ble_gap_start_advertising(&adv_params);
            break;
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            if (param->adv_start_cmpl.status == ESP_BT_STATUS_SUCCESS) {
                ESP_LOGI(TAG, "广播已启动");
            }
            break;
        default:
            break;
    }
}

void ble_adv_init(void)
{
    nvs_flash_init();
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);
    esp_bt_controller_enable(ESP_BT_MODE_BLE);
    esp_bluedroid_init();
    esp_bluedroid_enable();

    esp_ble_gap_register_callback(gap_event_handler);
    esp_ble_gap_set_device_name("ESP32-S3-Demo");
    esp_ble_gap_config_adv_data(&adv_data);
}

10.3 BLE GATT 服务端(完整示例)

c
#include "esp_gatts_api.h"
#include "esp_gap_ble_api.h"

// 自定义服务 UUID
#define SERVICE_UUID        0x00FF
#define CHAR_UUID_READ      0xFF01   // 可读特征值(传感器数据)
#define CHAR_UUID_WRITE     0xFF02   // 可写特征值(控制命令)
#define CHAR_UUID_NOTIFY    0xFF03   // 通知特征值(实时数据)

static uint16_t service_handle;
static uint16_t char_read_handle;
static uint16_t char_write_handle;
static uint16_t char_notify_handle;
static uint16_t conn_id = 0xFFFF;
static esp_gatt_if_t gatts_if_global;

// 特征值数据
static uint8_t sensor_value[4] = {0};
static uint8_t notify_enabled = 0;

// GATTS 事件处理
static void gatts_event_handler(esp_gatts_cb_event_t event,
                                  esp_gatt_if_t gatts_if,
                                  esp_ble_gatts_cb_param_t *param)
{
    switch (event) {
        case ESP_GATTS_REG_EVT:
            // 注册成功,创建服务
            esp_ble_gap_set_device_name("ESP32-S3");
            esp_ble_gap_config_adv_data(&adv_data);

            esp_gatt_srvc_id_t service_id = {
                .is_primary = true,
                .id.inst_id = 0,
                .id.uuid.len = ESP_UUID_LEN_16,
                .id.uuid.uuid.uuid16 = SERVICE_UUID,
            };
            esp_ble_gatts_create_service(gatts_if, &service_id, 8);
            break;

        case ESP_GATTS_CREATE_EVT:
            service_handle = param->create.service_handle;
            esp_ble_gatts_start_service(service_handle);

            // 添加可读特征值
            esp_bt_uuid_t char_uuid = {
                .len = ESP_UUID_LEN_16,
                .uuid.uuid16 = CHAR_UUID_READ,
            };
            esp_ble_gatts_add_char(service_handle, &char_uuid,
                ESP_GATT_PERM_READ,
                ESP_GATT_CHAR_PROP_BIT_READ,
                NULL, NULL);
            break;

        case ESP_GATTS_READ_EVT:
            // 客户端读取特征值
            ESP_LOGI(TAG, "客户端读取,handle=%d", param->read.handle);
            esp_gatt_rsp_t rsp = {0};
            rsp.attr_value.handle = param->read.handle;
            rsp.attr_value.len    = sizeof(sensor_value);
            memcpy(rsp.attr_value.value, sensor_value, sizeof(sensor_value));
            esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
                                         param->read.trans_id,
                                         ESP_GATT_OK, &rsp);
            break;

        case ESP_GATTS_WRITE_EVT:
            // 客户端写入特征值
            ESP_LOGI(TAG, "客户端写入 %d 字节", param->write.len);
            // 处理写入数据...
            break;

        case ESP_GATTS_CONNECT_EVT:
            conn_id = param->connect.conn_id;
            gatts_if_global = gatts_if;
            ESP_LOGI(TAG, "客户端已连接,conn_id=%d", conn_id);
            break;

        case ESP_GATTS_DISCONNECT_EVT:
            conn_id = 0xFFFF;
            ESP_LOGI(TAG, "客户端已断开");
            esp_ble_gap_start_advertising(&adv_params);  // 重新广播
            break;

        default:
            break;
    }
}

// 发送通知(主动推送数据给客户端)
void ble_notify_send(uint8_t *data, size_t len)
{
    if (conn_id == 0xFFFF || !notify_enabled) return;

    esp_ble_gatts_send_indicate(
        gatts_if_global, conn_id, char_notify_handle,
        len, data, false);  // false = Notification(不需要确认)
}

10.4 NimBLE(推荐的轻量级 BLE 栈)

c
// NimBLE 比 Bluedroid 更轻量,API 更简洁
// menuconfig: Component config → Bluetooth → Host → NimBLE

#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"

// 自定义服务定义
static const struct ble_gatt_svc_def gatt_svcs[] = {
    {
        .type = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid = BLE_UUID16_DECLARE(0x180F),  // 电池服务
        .characteristics = (struct ble_gatt_chr_def[]) {
            {
                .uuid  = BLE_UUID16_DECLARE(0x2A19),  // 电池电量
                .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
                .access_cb = battery_level_access_cb,
            },
            { 0 }  // 结束标志
        },
    },
    { 0 }  // 结束标志
};

// 特征值访问回调
static int battery_level_access_cb(uint16_t conn_handle, uint16_t attr_handle,
                                    struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
        uint8_t battery_level = 85;  // 模拟电量 85%
        os_mbuf_append(ctxt->om, &battery_level, sizeof(battery_level));
        return 0;
    }
    return BLE_ATT_ERR_UNLIKELY;
}

📝 第十章练习题

练习 10-1:BLE 广播(基础)

目标:掌握 BLE 广播

任务

  • 配置 BLE 广播,设备名 "ESP32S3-{最后4位MAC}"
  • 在广播数据中包含自定义制造商数据(4字节:设备类型+版本号)
  • 用手机 nRF Connect App 扫描并查看广播数据
  • 每 10 秒更新广播数据中的计数值

练习 10-2:BLE 串口透传(基础)

目标:BLE GATT 通信

任务:实现 BLE 串口透传(类似 HC-05 蓝牙模块):

  • 自定义服务 UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E(Nordic UART Service)
  • TX 特征值(Notify):ESP32 → 手机
  • RX 特征值(Write):手机 → ESP32
  • 手机发送的数据通过 UART0 打印
  • UART0 收到的数据通过 BLE 发送给手机

练习 10-3:BLE 传感器(进阶)

目标:标准 BLE 服务

任务:实现标准 BLE 环境传感服务:

  • 温度服务(UUID: 0x1809)
    • 温度特征值(UUID: 0x2A1C):每 1 秒 Notify
  • 电池服务(UUID: 0x180F)
    • 电池电量(UUID: 0x2A19):可读,模拟值
  • 用 nRF Connect 连接并订阅通知

练习 10-4:BLE 配网(挑战)

目标:BLE + Wi-Fi 联动

任务:实现通过 BLE 配置 Wi-Fi 的功能:

  1. 设备启动时检查 NVS 是否有 Wi-Fi 配置
  2. 无配置:启动 BLE 广播,等待手机配网
  3. 手机通过 BLE 发送 SSID 和密码
  4. 设备保存到 NVS,连接 Wi-Fi
  5. 连接成功后关闭 BLE,打印 IP 地址

练习 10-5:思考题

  1. BLE 和经典蓝牙(BR/EDR)有什么区别?ESP32-S3 支持哪种?

  2. BLE 广播间隔越短,功耗越高,但发现速度越快。如何在功耗和响应速度之间权衡?

  3. GATT Notification 和 Indication 有什么区别?各适合什么场景?

  4. BLE 连接后为什么要协商 MTU?默认 MTU 是多少?如何发送超过 MTU 的数据?


本章小结

知识点掌握程度自评
BLE GAP/GATT 概念⬜⬜⬜⬜⬜
BLE 广播配置⬜⬜⬜⬜⬜
GATT 服务定义⬜⬜⬜⬜⬜
特征值读写⬜⬜⬜⬜⬜
Notify 通知⬜⬜⬜⬜⬜

上一章← Wi-Fi 编程下一章综合项目实战 →

个人知识库