Appearance
第十章: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 的功能:
- 设备启动时检查 NVS 是否有 Wi-Fi 配置
- 无配置:启动 BLE 广播,等待手机配网
- 手机通过 BLE 发送 SSID 和密码
- 设备保存到 NVS,连接 Wi-Fi
- 连接成功后关闭 BLE,打印 IP 地址
练习 10-5:思考题
BLE 和经典蓝牙(BR/EDR)有什么区别?ESP32-S3 支持哪种?
BLE 广播间隔越短,功耗越高,但发现速度越快。如何在功耗和响应速度之间权衡?
GATT Notification 和 Indication 有什么区别?各适合什么场景?
BLE 连接后为什么要协商 MTU?默认 MTU 是多少?如何发送超过 MTU 的数据?
本章小结
| 知识点 | 掌握程度自评 |
|---|---|
| BLE GAP/GATT 概念 | ⬜⬜⬜⬜⬜ |
| BLE 广播配置 | ⬜⬜⬜⬜⬜ |
| GATT 服务定义 | ⬜⬜⬜⬜⬜ |
| 特征值读写 | ⬜⬜⬜⬜⬜ |
| Notify 通知 | ⬜⬜⬜⬜⬜ |
上一章:← Wi-Fi 编程下一章:综合项目实战 →