1. 概述
蓝牙低功耗(BLE, Bluetooth Low Energy)是物联网设备之间通信的核心协议之一。与经典蓝牙(Classic Bluetooth)不同,BLE 专为低功耗、小数据量场景设计——你身边那些蓝牙温湿度计、智能门锁、运动手环,几乎全都跑在 BLE 上。
而"蓝牙信标"(Beacon)是 BLE 最经典的应用形态之一。它本质上是一个只发不收的广播节点,按照固定间隔向外发送固定格式的数据包。任何一个支持 BLE 的手机、平板或电脑都能扫描到它。信标本身不带任何交互能力,但它"在此"这个事实就是信息——商场里的位置推送、博物馆的展品导览、考勤打卡系统,背后都是 Beacon 在工作。
今天我们用 ESP32-S3 分别实现两种主流的蓝牙信标协议:Apple 的 iBeacon 和 Google 的 Eddystone。学完这篇文章,你将掌握 BLE 广播的核心原理,并能自主设计自己的信标应用。
本篇知识点:
- BLE 协议栈分层结构与 GAP 层作用
- 广播(Advertising)的工作机制
- iBeacon 和 Eddystone 的帧结构差异
- ESP32-S3 上使用 Arduino 框架实现 BLE 广播
- 如何用手机 App 扫描并验证信标
2. 硬件准备
需要的硬件很简单——基本上就是一块 ESP32-S3 开发板,外加一部能装 BLE 扫描 App 的手机。
- ESP32-S3 开发板:任何带 USB 口的 ESP32-S3 板子都行,比如 ESP32-S3-DevKitC-1、合宙 ESP32-S3、XIAO ESP32-S3 等
- USB 数据线:供电 + 刷固件用
- 电脑:安装好 Arduino IDE(2.x 版)或 VSCode + PlatformIO
- 手机 App:推荐 nRF Connect(Nordic 出品,iOS/Android 均有)、或 Beacon Scanner
本文全部代码基于 Arduino ESP32 核心库 v3.x 编写,如果你还在用 v2.x,记得升级一下。
3. 代码实现
3.1 安装 BLE 库
在 Arduino IDE 中,ESP32 核心包自带了 BLEDevice 和 BLEBeacon 库,无需额外安装。如果你用 PlatformIO,在 platformio.ini 中指定 framework = arduino 即可。
3.2 iBeacon 实现
iBeacon 数据帧包含四个关键字段:UUID(16字节,标识组织)、Major(2字节,标识区域)、Minor(2字节,标识具体信标)、TX Power(1字节,1米处信号强度参考值)。
下面是完整的 iBeacon 广播代码:
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEBeacon.h>
// 自定义 UUID(可以用在线 UUID 生成器生成)
#define BEACON_UUID "8f8a7e20-6d3d-4a4b-8e1c-2f3d4e5f6a7b"
#define BEACON_MAJOR 1
#define BEACON_MINOR 1
#define BEACON_TX_POWER -59 // -59 dBm @ 1m
void setup() {
Serial.begin(115200);
Serial.println("ESP32-S3 iBeacon Demo");
// 初始化 BLE 设备
BLEDevice::init("ESP32-S3 Beacon");
// 创建 BLE 服务器(不需要连接,只是为了创建广播数据)
BLEServer *pServer = BLEDevice::createServer();
// 配置 iBeacon 广播数据
BLEBeacon myBeacon = BLEBeacon();
myBeacon.setProximityUUID(BLEUUID(BEACON_UUID));
myBeacon.setMajor(BEACON_MAJOR);
myBeacon.setMinor(BEACON_MINOR);
myBeacon.setSignalPower(BEACON_TX_POWER);
// 获取广播数据
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
BLEAdvertisementData advertisementData;
advertisementData.setFlags(0x06); // LE General Discoverable + BR/EDR Not Supported
advertisementData.setManufacturerData(myBeacon.getData());
pAdvertising->setAdvertisementData(advertisementData);
// 启动广播
pAdvertising->start();
Serial.println("iBeacon 广播已启动");
Serial.print("UUID: ");
Serial.println(BEACON_UUID);
Serial.print("Major: ");
Serial.println(BEACON_MAJOR);
Serial.print("Minor: ");
Serial.println(BEACON_MINOR);
}
void loop() {
// 信标不需要做任何事,BLE 硬件会自动广播
delay(1000);
}
3.3 Eddystone 实现
Eddystone 是 Google 推出的开放信标协议,比 iBeacon 更灵活。它支持多种帧类型:UID(类似 iBeacon)、URL(广播网址,手机靠近自动弹窗)、TLM(遥测数据,如电池/温度)、EID(加密帧)。
最常用的是 Eddystone-URL。手机安装了 Google Chrome(Android)或 Physical Web 应用后,扫描到 Eddystone-URL 信标就会弹窗提示打开对应网址。来看实现:
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEEddystoneURL.h>
// 你想要广播的网址(必须缩短,建议用短链接)
#define BEACON_URL "https://goo.gl/8jFvPJ"
void setup() {
Serial.begin(115200);
Serial.println("ESP32-S3 Eddystone-URL Demo");
BLEDevice::init("ESP32-S3 Eddystone");
BLEEddystoneURL eddystoneURL = BLEEddystoneURL();
eddystoneURL.setURL(BEACON_URL);
eddystoneURL.setSignalPower(-59);
// 配置广播
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
BLEAdvertisementData advertisementData;
advertisementData.setFlags(0x06);
advertisementData.setServiceData(eddystoneURL.getData());
pAdvertising->setAdvertisementData(advertisementData);
// 设置广播间隔(单位:0.625ms,100ms 是推荐值)
pAdvertising->setMinInterval(160);
pAdvertising->setMaxInterval(160);
pAdvertising->start();
Serial.println("Eddystone-URL 广播已启动");
Serial.print("URL: ");
Serial.println(BEACON_URL);
}
void loop() {
delay(1000);
}
4. 原理分析
4.1 BLE 协议栈概览
BLE 协议栈分三层:
- Controller(控制器):物理层 + 链路层,负责无线信号的收发。ESP32-S3 内部集成了完整的 BLE 5.0 控制器
- Host(主机):包括 L2CAP、ATT、GATT、SM、GAP 等上层协议。GAP(Generic Access Profile)层负责设备的发现和连接管理,信标功能正是由 GAP 层控制
- Application(应用层):用户的业务代码,我们调用的
BLEDevice、BLEAdvertising就在这层
4.2 广播(Advertising)机制
BLE 广播使用 3 个专用的广播信道(37/38/39 号信道,分布在 2.4GHz 频段的不同位置),这 3 个信道**不在** WiFi 常用信道(1-13)上,减少了干扰。广播包格式如下:
- Preamble(1 字节):同步头
- Access Address(4 字节):固定值 0x8E89BED6
- PDU(2-39 字节):数据单元,包含广播地址和广播数据
- CRC(3 字节):校验码
广播间隔(Advertising Interval)默认在 20ms ~ 10.24s 之间。间隔越短,发现越快但功耗越高。对于室内定位等需要低延迟的场景,建议设为 100-200ms;对于电池供电设备,建议 500ms 以上。
4.3 iBeacon vs Eddystone
| 特性 | iBeacon | Eddystone |
|---|---|---|
| 开发者 | Apple | |
| 帧类型 | 1 种(固定 UUID+Major+Minor) | 4 种(UID/URL/TLM/EID) |
| 手机弹窗 | 需 App 监听(无系统级弹窗) | Android 通过 Physical Web 弹窗 |
| 开放性 | 需购买 Apple 许可 | 完全开源 |
| 适合场景 | iOS 生态内的室内定位 | 通用蓝牙信标、网页推送 |
5. 扩展与优化
5.1 温湿度 + Beacon 传感器节点
把 BLE Beacon 和温湿度传感器(如 AHT10/BME280)结合,可以实现环境监测+位置感知二合一的物联网节点。用 Eddystone-TLM 帧广播遥测数据,手机 App 收到后即可同时获得位置和环境信息。
5.2 电池供电优化
Beacon 经常部署在无插座的地方。几个省电技巧:
- 适当增大广播间隔(500ms ~ 1s),功耗可降低 30-50%
- 调低发射功率(默认 9dBm → 0dBm),覆盖范围从 50m 缩至 10m 但功耗降一半
- 使用 ESP32-S3 的 Deep Sleep 模式:唤醒 → 广播几次 → 继续睡
// Deep Sleep 版 Beacon:每 5 秒广播 1 次然后睡觉
esp_sleep_enable_timer_wakeup(5 * 1000 * 1000); // 微秒
esp_deep_sleep_start();
5.3 BLE 5.0 长距离模式
ESP32-S3 支持 BLE 5.0 的 Coded PHY(编码物理层),通过冗余传输将通信距离提升到 300+ 米。使用 pAdvertising->setMinPreferred(0x01) 和 setMaxPreferred(0x01) 即可启用。
6. 踩坑记录
🐛 坑1:广播数据长度限制
现象:自定义广播数据太长,手机扫描不到或 App 闪退。
原因:BLE 广播包 PDU 最大 39 字节,除去地址和协议头,有效数据最多 31 字节。iBeacon 帧约 25 字节,Eddystone-URL 约 20 字节,都卡在极限附近。
解法:如果需要广播更多数据(比如温湿度读数),可以启用 扫描响应(Scan Response) 包,额外提供 31 字节空间。pAdvertising->setScanResponseData(scanData)。
🐛 坑2:WiFi + BLE 同时启用冲突
现象:WiFi 和 BLE 同时运行时,BLE 广播经常丢包或异常中断。
原因:ESP32-S3 虽然内置双模蓝牙和 WiFi,但两者共享同一套射频前端(2.4GHz 天线和 PA)。时间片分时复用机制导致 BLE 广播时隙被 WiFi 抢占。
解法:
- 如果仅需信标功能,不启用 WiFi,主机通过串口/UART 把数据传给 ESP32
- 如果必须同开,使用
esp_coex_status_tAPI 调整共存优先级,或使用 ESP-Now 替代 WiFi
🐛 坑3:手机扫描不到信标
现象:代码下载正常,但 nRF Connect 就是找不到你的设备。
可能原因和对策:
- Android 定位没开:Android 8+ 要求同时开启定位才能扫描 BLE。检查「定位服务」开关
- iPhone:App 需要 BLE 权限授权
- 广播间隔太长:先用最小间隔 100ms 测试,扫到了再调整
- USB 供电不稳:部分开发板通过 USB 供电时射频性能不佳。外接 3.3V LDO 供电可改善
🐛 坑4:Arduino 库版本问题
现象:编译报错 'BLEBeacon' was not declared 或 'BLEEddystoneURL' not found。
原因:旧版 ESP32 Arduino 核心(v2.x)没有 BLEBeacon 和 BLEEddystoneURL 类。
解法:升级到 ESP32 Arduino v3.0+:
Arduino IDE → 工具 → 开发板管理器 → esp32 → 选 3.0.x