3、BLE广播基础:广播类型与模式、广播数据格式、广播参数配置、广播启动与停止

好,咱们进入BLE开发的第一道门槛——广播。说实话,很多新手一上来就急着写连接代码,结果设备死活搜不到。为什么?广播没配好。广播就像你在人群中举着个牌子,牌子上的信息决定了别人要不要来找你搭话。

3.1 广播类型与模式

BLE广播分两种类型:可连接广播不可连接广播。我刚开始做项目时,总以为广播就是广播,后来发现这里头门道不少。

  • 可连接广播(Connectable):这是最常见的。设备广播自己,等待中心设备来连接。比如你的手环、心率带,都属于这种。
  • 不可连接广播(Non-connectable):只发不收。适合信标、温度传感器这类只上报数据的场景。说白了,就是“我只说,不听”。
  • 可扫描广播(Scannable):允许扫描请求,但不允许连接。嗯,这个模式我用的不多,但在某些私有协议里见过。

另外还有定向广播,专门针对某个已知设备。我记得有一次调试智能锁,门锁只对特定手机广播,其他设备根本搜不到——用的就是定向广播。

核心要点:广播类型决定了你的设备“可被发现”的方式。选错了,要么连不上,要么功耗爆炸。

3.2 广播数据格式

广播数据包最大31字节。别小看这31字节,怎么塞东西进去,是有讲究的。广播数据由多个AD Structure组成,每个结构包含三部分:

字段 长度 说明
Length 1字节 表示后面Data的长度(不包括Length本身)
AD Type 1字节 数据类型标识,比如0x01表示Flags
AD Data N字节 具体数据内容

举个例子,最常见的Flags字段:

// 广播数据:Flags = 0x02 0x01 0x06
// 0x02: Length(后面有2字节)
// 0x01: AD Type = Flags
// 0x06: LE General Discoverable Mode + BR/EDR Not Supported

我个人习惯把广播数据分成两部分:Scan Response DataAdvertising Data。前者是扫描响应时额外返回的,后者是广播包本身带的。你想想看,31字节不够用怎么办?把不紧急的信息塞到Scan Response里。

小技巧:我在项目中遇到过设备名称太长塞不进广播包的情况。解决办法是把完整名称放Scan Response,广播包里只放一个缩短的Local Name。这样既保证了可发现性,又省了空间。

3.3 广播参数配置

广播参数配置,说白了就是告诉蓝牙芯片“怎么广播、多快广播、用多大功率广播”。核心参数有这几个:

  • 广播间隔(Advertising Interval):单位是0.625ms。范围从20ms到10.24s。间隔越短,被发现越快,但功耗越高。我一般做穿戴设备时用100ms~200ms,信标类用1s以上。
  • 广播通道(Advertising Channel):BLE在37/38/39三个通道上广播。默认全开,但有些场景可以只开一个通道来省电。
  • 发射功率(Tx Power):从-20dBm到+10dBm不等。功率越大,距离越远,但电池也掉得快。
  • 广播超时(Advertising Timeout):有些芯片支持设置广播持续多久后自动停止。比如只广播30秒,之后进入休眠。

来看一段鸿蒙的广播参数配置代码:

// 鸿蒙BLE广播参数配置示例
#include "ohos_bt_def.h"
#include "ohos_bt_gatt.h"

void start_ble_advertising() {
    BLEAdvertisingParams params = {0};
    
    // 设置广播间隔为100ms(160 * 0.625ms)
    params.interval = 160;
    
    // 设置广播类型为可连接可扫描
    params.adv_type = BLE_ADV_TYPE_CONNECTABLE_UNDIRECTED;
    
    // 设置广播通道(37/38/39全开)
    params.channel_map = BLE_ADV_CHNL_37 | BLE_ADV_CHNL_38 | BLE_ADV_CHNL_39;
    
    // 设置发射功率为0dBm
    params.tx_power = 0;
    
    // 设置广播超时(0表示持续广播)
    params.timeout = 0;
    
    // 启动广播
    int ret = BleGattStartAdvertising(¶ms);
    if (ret != 0) {
        printf("广播启动失败,错误码:%d\n", ret);
    }
}

避坑指南:我曾经把广播间隔设成了10ms,结果设备发热严重,电池两小时就耗光了。后来查手册才发现,广播间隔太短会导致芯片持续发射,功耗飙升。记住,广播间隔不是越小越好

3.4 广播启动与停止

广播的启动和停止,看似简单,但时机很重要。我见过不少项目,广播启动后忘了停止,导致设备一直处于可发现状态,白白浪费电。

广播启动的典型流程:

  1. 初始化BLE协议栈
  2. 注册GATT服务(如果需要)
  3. 配置广播参数
  4. 设置广播数据
  5. 调用启动接口

广播停止的时机:

  • 设备已连接成功——这时候广播就没必要了
  • 用户主动关闭蓝牙
  • 设备进入低功耗模式
  • 广播超时自动停止

来看鸿蒙的广播启停代码:

// 启动广播
void start_advertising() {
    // 设置广播数据
    uint8_t adv_data[] = {
        0x02, 0x01, 0x06,           // Flags
        0x03, 0x03, 0x00, 0x18,     // Complete List of 16-bit Service UUIDs
        0x05, 0x09, 0x48, 0x65, 0x6C, 0x6C, 0x6F  // Local Name: "Hello"
    };
    
    BleGattSetAdvData(adv_data, sizeof(adv_data));
    BleGattStartAdvertising(¶ms);
    printf("广播已启动\n");
}

// 停止广播
void stop_advertising() {
    BleGattStopAdvertising();
    printf("广播已停止\n");
}

// 连接回调中自动停止广播
void on_connect() {
    stop_advertising();
    printf("设备已连接,停止广播\n");
}

个人经验:我习惯在连接成功后立即停止广播。为什么?因为广播会占用射频资源,影响连接后的数据传输。另外,有些手机在连接后还会继续扫描,如果广播不停止,可能会引起连接不稳定。

3.5 广播数据的高级玩法

除了基础数据,广播里还能塞很多东西。比如:

  • Service UUID:告诉别人你支持什么服务
  • Manufacturer Specific Data:厂商自定义数据,可以放版本号、状态信息等
  • Tx Power Level:发射功率,接收端可以根据这个估算距离
  • Appearance:设备外观类型,比如心率带、血压计

举个例子,我曾经做一个智能灯项目,把灯的当前颜色和亮度编码到Manufacturer Specific Data里。手机扫描到广播包,不用连接就能显示灯的状态。用户体验非常好。

// 厂商自定义数据示例
// AD Type = 0xFF (Manufacturer Specific Data)
// Company ID = 0x0059 (Nordic Semiconductor)
// Data = 颜色值 + 亮度值

uint8_t adv_data[] = {
    0x02, 0x01, 0x06,           // Flags
    0x05, 0xFF,                 // Manufacturer Specific Data
    0x59, 0x00,                 // Company ID (Nordic)
    0xFF, 0x00, 0x64            // 颜色=红色(0xFF), 亮度=100(0x64)
};

总结一下:广播是BLE通信的起点。配好广播类型、塞对数据格式、调好参数、控制好启停时机,你的设备才能被正确发现和连接。下一章我们会讲扫描,到时候你就知道广播端和扫描端是怎么配合的了。