开始前适用开发板:ESP32-S3 Korvo · 开发框架:ESP-IDF
适用开发板:Korvo S3 · 工程
02.beginner.ble_spp_server· targetesp32s3
学习时长:约 25 分钟 · 难度:★★★☆☆
学习目标
完成本节后,你将能够:
- 理解 BLE GATT 服务的基本架构(Service → Characteristic → Descriptor)
- 掌握用 GATT 模拟串口透传(SPP)的实现方式
- 了解 BLE 广播、连接、通知(Notify)的完整流程
- 学会用手机 App(nRF Connect)与开发板双向收发数据
1. 这个例子在干嘛
基于 Bluedroid + GATT 实现 BLE SPP 服务端(广播名 ESP_SPP_SERVER),手机连接后可以双向收发数据。数据同时桥接到 UART0 串口——从串口发的数据也能到手机。屏幕用 LVGL 显示连接状态和收发数据。
生活类比:和蓝牙版本的对讲机一样——手机说什么,板子串口就收到什么;板子串口发什么,手机就显示什么。
┌───────────┐ BLE GATT ┌──────────────┐ UART ┌──────────┐
│ 手机 App │←────────────→│ ESP32-S3 │←────────→│ 串口助手 │
│ nRF Connect│ Notify/Write│ SPP Server │ 桥接 │ 115200 │
└───────────┘ └──────────────┘ └──────────┘
↓
LVGL 状态显示
连接/数据/断开
BLE SPP 与经典蓝牙 SPP 的区别
| 对比 | 经典蓝牙 SPP | BLE SPP(本例) |
|---|---|---|
| 功耗 | 较高 | 极低(Low Energy) |
| 配对 | 需要配对 | 通常不需配对 |
| 速率 | 最大 3Mbps | 通常几百 Kbps |
| 实现 | RFCOMM 协议 | GATT Characteristic 模拟 |
| 适用 | 音频/大量数据 | 传感器/小数据包/遥控 |
2. 编译烧录
cd Korvo_Firmware\02.beginner.ble_spp_server
idf.py set-target esp32s3
idf.py build flash monitor
无 Kconfig.projbuild,主要配置在 sdkconfig.defaults(已预设 BLE 开启)。
3. 使用步骤(详细版)
3.1 手机准备
安装 nRF Connect(推荐)或 LightBlue App。
3.2 连接流程
- 烧录后屏幕显示
BLE SPP Ready - 手机打开 nRF Connect → SCAN
- 找到
ESP_SPP_SERVER→ CONNECT - 展开 Unknown Service (0xABF0)
- 找到 Characteristic 0xABF2 → 点击 ↓ 箭头开启 Notify
- 找到 Characteristic 0xABF1 → 点击 ↑ 箭头发送数据
3.3 双向测试
- 手机→板子:在 0xABF1 写数据 → 串口输出收到的内容
- 板子→手机:串口助手发数据 → 手机 0xABF2 的 Notify 收到
4. GATT 服务结构
ESP_SPP_SERVER (广播名, App ID=0x56)
└── SPP Service (UUID: 0xABF0)
├── Data Receive 0xABF1 [Read + Write No Response] ← 手机写入
├── Data Notify 0xABF2 [Read + Notify] → 推给手机
├── Command Receive 0xABF3 [Read + Write No Response] ← 命令写入
├── Command Notify 0xABF4 [Read + Notify] → 命令回复
└── Heartbeat 0xABF5 [可选,默认注释] ↔ 心跳保活
5. 关键文件与代码解析
| 文件 | 作用 | 重点 |
|---|---|---|
main/app_main.c | 核心:BLE 初始化 + GATT 事件 + UART 桥接 | ~800 行 |
main/include/ble_spp_server_demo.h | 属性索引枚举 + 编译宏 | 常量定义 |
main/ksdiy_lvgl_port.c | SPI LCD + 触摸 + LVGL 移植 | 显示层 |
核心初始化流程
app_main()
├── ksdiy_lvgl_port_init() // 屏幕
├── 创建 LVGL 标签(title/status/detail)
├── nvs_flash_init()
├── esp_bt_controller_init/enable(BLE)
├── esp_bluedroid_init/enable()
├── esp_ble_gatts_register_callback() // GATT 事件
├── esp_ble_gap_register_callback() // 广播事件
├── esp_ble_gatts_app_register(0x56) // 注册 App
└── spp_task_init()
├── spp_uart_init() // UART0 桥接
└── spp_cmd_task() // 命令处理
数据路径详解
下行(手机→板子):
手机写 0xABF1 → GATTS WRITE事件 → uart_write_bytes(UART_NUM_0)
上行(板子→手机):
UART 事件 → uart_read_bytes() → esp_ble_gatts_send_indicate(0xABF2)
大包分片:
超过 MTU-3 时,加 "##" + 总包数 + 当前包号 前缀分片发送
6. 运行现象
| 阶段 | 屏幕 | 串口 |
|---|---|---|
| 启动 | BLE SPP Ready | GATTS: create attr table OK |
| 广播中 | Advertising... | GAP: adv start complete |
| 手机连接 | Connected! + MAC | GATTS: connect, conn_id=0 |
| 开 Notify | Notify enabled | CCCD write, enable notify |
| 收到数据 | RX: <数据> | Data recv: <hex> |
| 手机断开 | Disconnected | GATTS: disconnect → restart adv |
7. 常见问题与排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 搜不到设备 | 手机蓝牙未开 | 开启蓝牙 |
| 搜不到设备 | 已被其他手机连接 | BLE 一对一,断开再连 |
| 连上无数据 | 未开启 Notify | 必须点击 0xABF2 的 Notify 开关 |
| 数据乱码 | 编码不一致 | 确认 UTF-8;检查 MTU |
| 距离近才行 | BLE 功率/天线 | 室内典型 10~30 米 |
| 连接后立即断开 | MTU 协商失败 | 检查手机 BLE 版本 |
| UART 无桥接 | UART0 被串口监视占用 | 换 UART1 或关闭 monitor |
8. 动手改造建议
- 手机发 "LED ON":在 GATTS WRITE 事件中解析命令控制 GPIO
- 定时推送传感器数据:lv_timer 定时读 ADC → Notify 推送到手机
- BLE + Wi-Fi 共存:先 BLE 收到 SSID/密码 → 切 Wi-Fi 连接(BluFi 原理)
- 修改广播名:搜索
ESP_SPP_SERVER替换为自定义名称 - 添加心跳:取消
SUPPORT_HEARTBEAT的注释,启用心跳保活
9. 知识延伸
- BLE 4.2 vs 5.0:BLE 5.0 支持更大广播包和更远距离,ESP32-S3 支持 BLE 5.0
- MTU 协商:默认 MTU=23(有效数据 20 字节),可协商到 512,提高吞吐量
- GATT vs GAP:GAP 管理广播/扫描/连接,GATT 管理数据读写
- Bluedroid vs NimBLE:本例用 Bluedroid(ESP-IDF 默认),NimBLE 更轻量但 API 不同
- 安全性:本例无加密配对,生产环境应启用 LE Secure Connections
10. 下一步
| 方向 | 推荐例程 | 说明 |
|---|---|---|
| BLE 配网 | BluFi | BLE 收 Wi-Fi 信息 |
| USB 串口 | USB CDC | 另一种数据通道 |
| Wi-Fi 基础 | Wi-Fi Station | 联网 |
| P4C5 对比 | P4C5 BLE SPP | 通过 C5 做 BLE |
酷世DIY · Kevincoooool