4、第一个BLE广播程序:初始化协议栈、配置广播包、启动广播

好,咱们今天正式动手写代码。

前面几章聊了不少理论,什么GAP、GATT、连接参数……说实话,光看协议栈文档容易犯困。我个人习惯是,先让设备“出声”——也就是把广播发出来。你想想看,一个蓝牙设备连广播都发不出来,后面所有事情都无从谈起。

这一章的目标很简单:让你的开发板变成一个能被手机扫描到的BLE设备。我们会走完三个步骤——初始化协议栈、配置广播包、启动广播。嗯,每一步都有坑,我会把我在项目中踩过的雷都告诉你。

4.1 协议栈初始化——先让底层跑起来

BLE协议栈不是个简单的库,它是一整套协议分层实现。初始化顺序错了,后面全崩。我记得第一次做nRF52832项目时,上来就调广播启动函数,结果芯片直接HardFault……后来查了三天,才发现是协议栈没初始化完。

标准流程大致如下:

  1. 配置系统时钟与硬件外设
  2. 调用协议栈初始化接口
  3. 注册GAP回调函数
  4. 设置设备地址类型(公开地址/随机地址)

以Nordic nRF5 SDK为例,核心代码长这样:

// 1. 使能协议栈
nrf_clock_lf_cfg_t clock_cfg = {
    .source = NRF_CLOCK_LF_SRC_XTAL,
    .rc_ctiv = 0,
    .rc_temp_ctiv = 0,
    .accuracy = NRF_CLOCK_LF_ACCURACY_20_PPM
};

uint32_t err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);

// 2. 初始化BLE协议栈
nrf_ble_cfg_t ble_cfg;
memset(&ble_cfg, 0, sizeof(ble_cfg));
ble_cfg.gap_cfg.role_count_cfg.periph_role_count = 1;
ble_cfg.gap_cfg.role_count_cfg.central_role_count = 0;
ble_cfg.gap_cfg.role_count_cfg.central_sec_count  = 0;

err_code = nrf_sdh_ble_enable(&ram_start, &ble_cfg);
APP_ERROR_CHECK(err_code);

// 3. 注册GAP事件回调
err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_GENERIC_TAG);
APP_ERROR_CHECK(err_code);

// 4. 设置设备地址(使用静态随机地址)
ble_gap_addr_t addr;
addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;
memcpy(addr.addr, m_adv_handle.adv_addr, BLE_GAP_ADDR_LEN);
err_code = sd_ble_gap_addr_set(&addr);
APP_ERROR_CHECK(err_code);

注意: 不同芯片厂商的初始化顺序有差异。TI的CC26xx需要先初始化RF驱动,再初始化协议栈;而Nordic的nRF52系列则要求先使能SoftDevice。一定要看对应SDK的初始化序列图,别凭经验硬套。

4.2 配置广播包——你的设备名片

广播包说白了就是设备在喊:“我在这儿,我叫XX,我能干这些事。”手机扫描时看到的就是这些数据。

广播包分两种:

  • 广播数据(Advertising Data):31字节,每次广播必带
  • 扫描响应数据(Scan Response Data):31字节,手机主动扫描时才回复

我曾经犯过一个低级错误——把设备名称塞进广播数据里,结果名称太长(超过31字节),广播包直接发不出来。后来改成把完整名称放扫描响应里,广播数据只放缩写和UUID,问题就解决了。

配置广播包的步骤:

  1. 准备一个 ble_advdata_t 结构体
  2. 填充需要的字段(名称、UUID、外观等)
  3. 调用 ble_advdata_set 写入协议栈

看代码:

// 广播数据配置
ble_advdata_t advdata;
memset(&advdata, 0, sizeof(advdata));

advdata.name_type          = BLE_ADVDATA_FULL_NAME;
advdata.include_appearance = true;
advdata.flags              = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

// 添加16位服务UUID
uint16_t uuid = 0x180F;  // 电池服务
advdata.uuids_complete.uuid_cnt = 1;
advdata.uuids_complete.p_uuids  = &uuid;

// 扫描响应数据(放完整名称)
ble_advdata_t srdata;
memset(&srdata, 0, sizeof(srdata));
srdata.name_type = BLE_ADVDATA_NO_NAME;  // 广播里已经放了名称

// 写入协议栈
uint32_t err_code = ble_advdata_set(&advdata, &srdata);
APP_ERROR_CHECK(err_code);

小技巧: 广播数据里尽量放短名称(8字节以内),把完整名称(比如“MyDevice-001”)放到扫描响应里。这样既能被手机识别,又不浪费宝贵的31字节空间。

4.3 启动广播——让世界听到你

广播包配好了,协议栈也初始化了,最后一步就是喊出来。

启动广播需要配置一个 ble_gap_adv_params_t 结构体,里面最关键的是:

参数 说明 常见取值
type 广播类型 BLE_GAP_ADV_TYPE_ADV_IND(可连接)
interval 广播间隔(单位0.625ms) 160(100ms)、320(200ms)
timeout 超时时间(单位秒) 0(永不超时)
channel_mask 使用的信道 37/38/39三个信道全开

启动代码:

// 配置广播参数
ble_gap_adv_params_t adv_params;
memset(&adv_params, 0, sizeof(adv_params));

adv_params.type        = BLE_GAP_ADV_TYPE_ADV_IND;   // 可连接广播
adv_params.interval    = 160;                         // 100ms
adv_params.timeout     = 0;                           // 永不超时
adv_params.channel_mask.ch_37_off = false;
adv_params.channel_mask.ch_38_off = false;
adv_params.channel_mask.ch_39_off = false;

// 启动广播
uint32_t err_code = sd_ble_gap_adv_start(m_adv_handle, &adv_params);
APP_ERROR_CHECK(err_code);

启动成功后,用手机上的nRF Connect或LightBlue扫描,应该能看到你的设备了。

核心要点: 广播间隔直接影响功耗和被发现速度。100ms间隔下,手机几乎秒发现;500ms间隔则可能等3-5秒。量产产品建议用200-300ms,兼顾功耗和体验。

4.4 完整流程与常见问题

把上面三步串起来,整个广播初始化的流程图是这样的:

BLE广播初始化流程 系统时钟配置 协议栈使能 GAP回调注册 配置广播包数据 启动广播 常见错误 • 协议栈未使能就调广播 • 广播数据超过31字节 • 地址类型不匹配 调试建议 • 用nRF Connect抓包 • 检查返回值err_code

这张图把整个流程串起来了。你按这个顺序走,基本不会出大问题。

4.5 避坑指南——我踩过的雷

做广播程序,有几个坑特别容易踩:

  • 广播数据超31字节:协议栈不会报错,但广播发不出来。用 ble_advdata_set 的返回值检查一下。
  • 地址类型选错:公开地址需要向IEEE申请,个人开发建议用静态随机地址(BLE_GAP_ADDR_TYPE_RANDOM_STATIC)。
  • 广播间隔太短:低于20ms会导致协议栈报错,因为BLE规范不允许。我见过有人设成10ms,结果广播根本起不来。
  • 忘记使能LF时钟:很多芯片需要外部32.768kHz晶振,没配好时钟,协议栈初始化直接失败。

特别提醒: 如果你用的是nRF52832或nRF52840,一定要先调用 nrf_sdh_enable_request() 再调 nrf_sdh_ble_enable()。顺序反了,协议栈会卡死在初始化里。我当年在这个问题上浪费了整整一个下午。

4.6 验证与调试

广播启动后,怎么确认它正常工作?

  1. 用手机扫描:nRF Connect或LightBlue,看能不能搜到设备名
  2. 抓空口包:有条件的用Ellisys或nRF Sniffer,看广播包内容对不对
  3. 看日志:串口打印 err_code,确保每一步都返回 NRF_SUCCESS

我个人习惯是先在开发板上跑通,再用逻辑分析仪抓一下广播间隔,确认实际间隔和配置的一致。有一次我发现配置的100ms,实际测出来只有80ms,后来查手册才知道——广播间隔的精度受LF时钟影响,晶振不准会导致偏差。

好了,到这里你已经掌握了BLE广播的完整流程。把代码烧进去,打开手机,看到设备名出现的那一刻——嗯,那种感觉还是挺爽的。