ESP32-S3 BLE 七个实验学习笔记
环境:ESP-IDF v5.4 · ESP32-S3 · iPhone 13
协议栈:NimBLE(实验 ①②④⑤)/ Bluedroid(实验 ⑥⑦)/ ESP-BLE-MESH(实验 ⑦)
总览:七个实验在学什么
| 序号 |
例程 |
ESP32 角色 |
核心原理 |
手机怎么用 |
状态 |
| ① |
NimBLE_GATT_Server |
从机 Peripheral |
GATT 服务 + 读写/订阅 |
nRF Connect |
✅ |
| ② |
NimBLE_Connection |
从机 Peripheral |
广播 + 连接生命周期 |
nRF Connect |
✅ |
| ③ |
blecent |
主机 Central |
扫描 + 连接 + 读远端 GATT |
需第二块 ESP32 |
⏭ 跳过 |
| ④ |
NimBLE_Beacon |
信标 Beacon |
只广播、不可连接 |
nRF Connect 扫描 |
✅ |
| ⑤ |
NimBLE_Security |
从机 + 加密 |
配对 / Passkey / Bonding |
nRF Connect |
✅ |
| ⑥ |
ble_hid_device_demo |
HID 设备 |
模拟蓝牙键盘/遥控器 |
系统蓝牙设置 |
✅ |
| ⑦ |
onoff_server |
Mesh 节点 |
网状网 + 配网 + 组控 |
nRF Mesh |
✅ |
第 0 章:BLE 基础概念(所有实验共用)
1.1 BLE 协议栈分层
┌─────────────────────────────────────┐
│ 应用层(你的代码:LED、心率、Mesh Model) │
├─────────────────────────────────────┤
│ GATT / GAP / Mesh Models │ ← 主机层(Host)
├─────────────────────────────────────┤
│ HCI │
├─────────────────────────────────────┤
│ 蓝牙控制器(Controller) │ ← 芯片硬件
└─────────────────────────────────────┘
- GAP(Generic Access Profile):广播、扫描、连接、配对
- GATT(Generic Attribute Profile):服务 Service、特征 Characteristic、读写 Notify
- Mesh:在 GAP/GATT 之上,多设备网状通信
1.2 两种经典角色
| 角色 |
英文 |
做什么 |
类比 |
| 从机 |
Peripheral |
广播等人连,提供数据 |
蓝牙音箱 |
| 主机 |
Central |
扫描并主动连接 |
手机、电脑 |
实验 ①②④⑤⑥⑦ 中 ESP32 都是从机/节点;实验 ③ 角色反转,ESP32 当主机。
1.3 NimBLE vs Bluedroid
| 协议栈 |
特点 |
本笔记用在 |
| NimBLE |
轻量、省 RAM |
实验 ①②④⑤ |
| Bluedroid |
功能全、HID/Mesh 官方例程多 |
实验 ⑥⑦ |
实验 ①:NimBLE_GATT_Server --- GATT 从机与数据交换
例程路径
examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server
原理
ESP32 作为 GATT Server(从机),定义两个标准服务,等待手机连接后读写数据。
上电 → 初始化 LED / NVS / NimBLE
→ 注册 GATT 服务表
→ 开始广播(设备名:NimBLE_GATT)
→ 手机连接
→ 手机读写 Characteristic → ESP32 回调响应
文件分工
| 文件 |
职责 |
main.c |
总入口,启动 NimBLE Host 任务 + 心率模拟任务 |
gap.c |
广播参数、连接/断开/订阅事件 |
gatt_svc.c |
核心:GATT 服务表 + 读写回调 |
led.c |
控制板载 LED |
heart_rate_mock.c |
模拟心率 60--80,每秒更新 |
提供的两个服务
| 服务 |
UUID |
特征 |
权限 |
作用 |
| Heart Rate |
0x180D |
Heart Rate Measurement 0x2A37 |
读 + Indicate |
模拟心率,可订阅推送 |
| Automation IO |
0x1815 |
LED(自定义 128-bit UUID) |
写 |
手机写 0x01/0x00 开关灯 |
与 Arduino 对照
| Arduino |
ESP-IDF NimBLE |
BLEDevice::init() |
nimble_port_init() |
BLEServer |
gatt_svc.c 服务表 gatt_svr_svcs[] |
BLECharacteristic + onWrite |
access_cb(如 led_chr_access) |
setValue() + notify() |
ble_gatts_indicate() |
startAdvertising() |
gap.c → start_advertising() |
手机测试
- App:nRF Connect
- 连接 NimBLE_GATT → 写 Automation IO 控灯 → 订阅 Heart Rate 收数据
实验 ②:NimBLE_Connection --- 连接与广播
例程路径
examples/bluetooth/ble_get_started/nimble/NimBLE_Connection
原理
比 ① 更简单:没有自定义 GATT 服务,只练三件事:
-
广播 --- 让手机能搜到
-
连接 / 断开 --- 处理连上和断开事件
-
连接参数 --- 连上后协商间隔、延迟等
广播中(灯灭)
↓ 手机 CONNECT
连接成功 → LED 亮 + 打印连接信息
↓ 手机 DISCONNECT
断开 → LED 灭 → 自动重新广播
与 ① 的对比
|
GATT Server(①) |
Connection(②) |
| 设备名 |
NimBLE_GATT |
NimBLE_CONN |
| 自定义服务 |
有 |
无 |
| 连上后能干什么 |
读写特征值 |
仅连/断,看 LED |
| 核心学习点 |
GATT 数据交换 |
GAP 连接生命周期 |
gap.c 三个关键事件
| 事件 |
行为 |
BLE_GAP_EVENT_CONNECT |
连上 → 亮灯 → 更新连接参数 |
BLE_GAP_EVENT_DISCONNECT |
断开 → 灭灯 → 重新广播 |
BLE_GAP_EVENT_CONN_UPDATE |
连接参数协商完成 |
Beacon vs Connection 广播区别
|
Connection |
Beacon |
conn_mode |
可连接(UND) |
不可连接(NON) |
| nRF Connect |
有 CONNECT 按钮 |
只能看广播数据 |
实验 ③:blecent --- GATT 主机(已跳过)
例程路径
examples/bluetooth/nimble/blecent
原理
角色反转:ESP32 扮演手机在实验 ① 里的角色 ------ 扫描、连接、发现服务、读写远端 Characteristic。
ESP32(Central)扫描 → 找到带 0x1811 服务的设备
→ 连接 → 发现服务 → 读/写/订阅 Notify
为何跳过
需要 两块 ESP32 :一块烧 bleprph(从机),一块烧 blecent(主机)。iPhone 无法充当所需的对端设备。
与 ① 的关系
| ① GATT Server |
③ blecent |
| 你建服务,等别人读写 |
你主动找服务、读写别人 |
| 手机 = Central |
ESP32 = Central |
实验 ④:NimBLE_Beacon --- 信标广播
例程路径
examples/bluetooth/ble_get_started/nimble/NimBLE_Beacon
原理
Beacon = 只喊「我在哪、我是谁」,不接待连接。
┌─────────────────────────────────────┐
│ Advertising Data(主广播包,≤31B) │ ← 设备名、Flags
├─────────────────────────────────────┤
│ Scan Response(扫描响应包) │ ← URI、MAC 等
└─────────────────────────────────────┘
conn_mode = NON → 不可连接
- 设备名:NimBLE_Beacon
- Scan Response 含 URI:
https://espressif.com
与前面实验对比
|
GATT Server / Connection |
Beacon |
| 能否 CONNECT |
✅ |
❌ |
| 目的 |
连上后传数据 |
只被发现、传递少量信息 |
| 典型场景 |
智能设备 |
商场定位、资产标签 |
注意
- 例程名是
NimBLE_Beacon ,不是 ble_ibeacon(后者是 Apple iBeacon 格式,Bluedroid)
- nRF Connect 里搜
NimBLE_Beacon ,不应出现 NIMBLE_CONN
实验 ⑤:NimBLE_Security --- 配对与加密
例程路径
examples/bluetooth/ble_get_started/nimble/NimBLE_Security
原理
在 ① GATT Server 基础上增加 安全机制:
| 概念 |
说明 |
| 配对 Pairing |
手机和 ESP32「认亲」 |
| Passkey |
ESP32 串口打印 6 位密码,手机输入相同数字 |
| 连接加密 |
配对后数据加密传输 |
| Bonding |
记住配对,下次可自动加密 |
| 随机 MAC |
每次启动可能用随机私有地址广播 |
与 ① 的对比
|
GATT Server(①) |
Security(⑤) |
| 设备名 |
NimBLE_GATT |
NimBLE_SEC |
| 连上后 |
直接读写 |
读写前弹出配对 |
| 特征权限 |
普通 |
带 READ_ENC / WRITE_ENC |
配对流
连接 → 尝试读写特征值
→ 触发配对(Passkey)
→ 串口:enter passkey 123456 on the peer side
→ 手机输入相同 6 位
→ connection encrypted!
→ 读写生效
实验 ⑥:ble_hid_device_demo --- BLE HID 模拟遥控器
例程路径
examples/bluetooth/bluedroid/ble/ble_hid_device_demo
原理
ESP32 模拟 BLE HID 设备 (蓝牙人体学输入设备),走 系统级蓝牙,不是 GATT 自定义服务。
按键/定时任务 → 发 HID 报告 → 手机系统接收 → 调音量/媒体键等
| 项目 |
内容 |
| 协议栈 |
Bluedroid(不是 NimBLE) |
| 设备名 |
HID |
| 模拟类型 |
Consumer 多媒体遥控器 |
| 连上后 |
每约 2 秒自动 音量+ / 音量- |
与 GATT Server 的本质区别
|
GATT Server(①) |
HID(⑥) |
| 连接方式 |
nRF Connect 等 App |
系统蓝牙设置 |
| 谁识别 |
只有装了对应 App 的 |
系统自动识别 |
| 两个 GPIO 按键 |
需写 Characteristic + App 配合 |
直接控制音量/媒体 (改 hid_demo_task 读 GPIO 即可) |
两个按键控手机
// 伪代码
if (button_A) esp_hidd_send_consumer_value(conn_id, HID_CONSUMER_VOLUME_UP, true/false);
if (button_B) esp_hidd_send_consumer_value(conn_id, HID_CONSUMER_VOLUME_DOWN, true/false);
HID 报告需 先 true(按下)再 false(松开)。
手机测试
- 设置 → 蓝牙 → 连接 HID(不用 nRF Connect)
- 配对成功后音量应自动变化
实验 ⑦:onoff_server --- BLE Mesh 组网
例程路径
examples/bluetooth/esp_ble_mesh/onoff_models/onoff_server
原理:Mesh 与点对点 BLE 的本质区别
|
GATT(①--⑥) |
BLE Mesh(⑦) |
| 拓扑 |
1 对 1 |
多对多网状 |
| 连接 |
通常需 CONNECT |
多数不建传统连接,靠广播 + 中继 |
| 通信 |
读写 Characteristic |
Model + 发布/订阅 |
| 距离 |
约 10--30 m |
多跳 Relay,可覆盖整栋楼 |
| 入网 |
扫到就连 |
必须先配网 Provisioning |
五个核心概念
1. 配网 Provisioning(办户口)
未配网设备 ──配网──→ 节点(有 NetKey、单播地址、AppKey)
↑
Provisioner(手机 nRF Mesh / 另一块 ESP32)
- NetKey:整网共用,网络身份
- AppKey:绑定到 Model,控制哪类应用
- 单播地址 :每个节点唯一(如
0x0001)
2. 节点 · 元素 · 模型
节点(一块 ESP32)
└── 元素 Element 0
├── Configuration Server(标配)
└── Generic OnOff Server(开关灯,UUID 相关 0x1000)
Model = 具体能力(开关、传感器、自定义协议)。
3. 发布 / 订阅(像 MQTT,但本地无 Broker)
| 概念 |
MQTT |
BLE Mesh |
| 发布 |
往 Topic 发 |
往发布地址 / 组地址发 Model 消息 |
| 订阅 |
订阅 Topic |
Model 订阅组地址(如 0xC000 = 一组灯) |
[开关 Client] ──→ [中继 Relay] ──→ [灯 Server]
4. 节点特性
| 特性 |
作用 |
| Relay |
帮别人转发(组网必备) |
| Proxy |
让 手机 通过 GATT 接入 Mesh(iPhone 必需) |
| Friend + LPN |
低功耗节点 + 好友存消息 |
5. 与 MQTT 的关系
- Mesh:本地多设备、无 WiFi、省电、多跳 ------ 「局域网广播网」
- MQTT :上云、远程、需 Broker ------ 常通过 Mesh 网关 + WiFi 再转 MQTT
配网流程(nRF Mesh · iPhone)
- 安装 nRF Mesh(不是 nRF Connect)
- 创建 Network
- 底部 Scanner → 扫未配网设备
- Identify → Provision(贴手机、等 30 秒)
- 节点页 → Add AppKey → Bind Generic OnOff Server
- 发 On/Off → RGB 亮灭
常见问题与处理
| 现象 |
处理 |
| 扫不到 |
确认 onoff_server + erase-flash;非 GATT/HID 固件 |
| device not supported |
关 Mesh 1.1 EPA;用 Mesh 1.0;单元素节点 |
| 配网成功灯不控 |
Bind AppKey 到 OnOff Server |
rsn 0x13 断开 |
nRF Mesh iOS 兼容性 → 关 V11/EPA 或换 Silicon Labs App |
推荐 sdkconfig 兼容项(nRF Mesh iOS)
# CONFIG_BLE_MESH_V11_SUPPORT is not set
# CONFIG_BLE_MESH_HCI_5_0 is not set
# CONFIG_BLE_MESH_PROV_EPA is not set
CONFIG_BLE_MESH_PB_GATT=y
CONFIG_BLE_MESH_GATT_PROXY_SERVER=y
附录 A:七个实验的学习路线
① GATT Server 手机连 ESP32,读写 LED / 心率
↓
② Connection 连上亮灯、断开灭灯,理解 GAP
↓
③ blecent (需两块板)ESP32 当主机 --- 可跳过
↓
④ Beacon 只广播不可连,理解广播包结构
↓
⑤ Security Passkey 配对后才能读写
↓
⑥ HID 系统蓝牙遥控器,两个键可控手机
↓
⑦ Mesh 配网 + Model + 组控,多设备网状网
附录 B:手机 App 对照
| App |
用于实验 |
用途 |
| nRF Connect |
①②④⑤ |
GATT 连接、读写 Characteristic |
| nRF Mesh |
⑦ |
Mesh 配网、Bind AppKey、OnOff 控灯 |
| 系统蓝牙设置 |
⑥ |
配对 HID 设备 |
| Silicon Labs Mesh |
⑦ 备选 |
配网兼容备选 |
| EspBleMesh |
⑦ 备选 |
仅 Android,对 ESP 最友好 |
附录 C:编译常见问题
| 现象 |
处理 |
ninja log v6 AssertionError |
Full Clean / 删 build;或忽略(扩展解析日志问题) |
Mesh 扫到 NIMBLE_CONN |
烧错固件,应烧 NimBLE_Beacon 或 onoff_server |
| HID 在 nRF Connect 无效果 |
必须在 系统设置 → 蓝牙 连 HID |
附录 D:官方文档
文档生成自 ESP32-S3 BLE 七个实验实践记录 · ESP-IDF 5.4