3、第一个BLE工程:创建工程模板、理解main函数流程、初始化时钟与外设、启动协议栈、配置广播参数、开启广播、手机端使用nRF Connect扫描并连接

好,咱们正式开始写代码了。

这一章,我会带你从零搭建一个BLE工程。说白了,就是让芯片先“开口说话”——发出广播包。你想想看,一个蓝牙设备如果不广播,手机根本找不到它。所以广播是BLE通信的第一步,也是我们调试硬件和协议栈是否正常工作的“Hello World”。

3.1 创建工程模板——别从零开始,站在巨人肩膀上

我个人习惯,从来不自己从头写启动文件和链接脚本。太容易出错了。我建议你直接用芯片厂商提供的SDK示例工程,然后在此基础上“做减法”。

以Nordic nRF52832为例,我一般这么干:

  1. 找到SDK目录下的 examples/ble_peripheral/ble_app_beacon
  2. 把整个文件夹复制一份,重命名为 my_first_ble
  3. 删掉不必要的源文件(比如某些服务文件),只保留核心框架

嗯,这里要注意:不同芯片的SDK结构差异很大。但核心思想是一样的——先跑通,再裁剪。

我的小技巧: 第一次接触某款芯片时,先编译原版示例,确保工具链没问题。然后再改代码。这样出了问题,你知道是硬件环境问题还是代码问题。

3.2 main函数流程——BLE应用的“骨架”

一个典型的BLE工程,main函数长这样:

int main(void)
{
    // 1. 初始化时钟
    clock_init();
    
    // 2. 初始化外设(GPIO、UART等)
    peripherals_init();
    
    // 3. 初始化BLE协议栈
    ble_stack_init();
    
    // 4. 配置广播参数
    advertising_init();
    
    // 5. 启动广播
    advertising_start();
    
    // 6. 进入主循环
    while(1)
    {
        // 处理协议栈事件
        sd_app_evt_wait();
    }
}

这个流程,我在项目中遇到过无数次。不管你是做心率计、温湿度传感器还是智能灯,main函数的骨架基本不变。区别只在于外设初始化和服务配置。

为什么会这样?因为BLE协议栈本身就是一个事件驱动的系统。你初始化完,它就等着手机来连接。连接之后,手机发指令,芯片响应。就这么简单。

3.3 初始化时钟与外设——芯片的“心跳”和“手脚”

时钟是芯片的心脏。没有时钟,CPU不跑,外设不动。我见过不少新手,代码逻辑完全正确,但就是跑不起来——最后发现是时钟配置错了。

以nRF52为例,时钟初始化一般这样:

static void clock_init(void)
{
    // 启动外部高频晶振(16MHz)
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART = 1;
    while(NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
    
    // 启动外部低频晶振(32.768KHz)
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_LFCLKSTART = 1;
    while(NRF_CLOCK->EVENTS_LFCLKSTARTED == 0);
}

外设初始化呢?我一般先初始化调试串口。这样可以用printf打印日志,方便调试。你想想看,如果连串口都没开,芯片跑没跑你都不知道。

重要: 协议栈初始化之前,必须确保低频时钟(LFCLK)已经稳定。否则协议栈会卡死在初始化函数里。我曾经在这个坑里浪费了半天。

3.4 启动协议栈——让芯片“听懂”蓝牙语言

协议栈初始化,说白了就是加载蓝牙协议栈的固件到芯片内部。这个过程由SDK封装好了,我们只需要调用一个函数:

static void ble_stack_init(void)
{
    nrf_ble_lib_t lib_init = {0};
    
    // 配置协议栈参数
    lib_init.clock_source = NRF_CLOCK_LFCLKSRC_XTAL_32768;
    lib_init.sdh_enable = true;
    
    // 初始化协议栈
    err_code = nrf_ble_lib_init(&lib_init, NULL);
    APP_ERROR_CHECK(err_code);
    
    // 使能协议栈
    err_code = sd_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);
}

这里有个关键点:ram_start 变量。协议栈会占用一部分RAM,这个变量告诉协议栈“用户程序从哪个地址开始用”。如果配置不对,程序会跑飞。

避坑指南: 我曾经在更换芯片型号时,忘了更新链接脚本中的RAM起始地址。结果协议栈初始化成功,但一调用蓝牙API就死机。折腾了两天才发现是RAM冲突。

3.5 配置广播参数——告诉手机“我是谁”

广播参数配置,是BLE开发中最灵活也最容易出错的地方。我们来看一个典型的配置:

static void advertising_init(void)
{
    ble_gap_adv_params_t adv_params = {0};
    ble_adv_data_t adv_data = {0};
    
    // 广播模式:可连接、可扫描
    adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND;
    
    // 广播间隔:100ms(单位0.625ms)
    adv_params.interval = 160;  // 100ms / 0.625ms = 160
    
    // 广播通道:全部三个通道
    adv_params.channel_mask = BLE_GAP_ADV_CHANNEL_MASK_ALL;
    
    // 设置广播数据(设备名称、UUID等)
    adv_data.name = "My_BLE_Device";
    adv_data.name_len = strlen(adv_data.name);
    adv_data.appearance = BLE_APPEARANCE_GENERIC_TAG;
    
    // 应用配置
    sd_ble_gap_adv_set_configure(&adv_handle, &adv_data, &adv_params);
}

广播间隔怎么选?我一般这样:

  • 快速广播(20-50ms): 手机扫描响应快,但功耗高。适合配对阶段。
  • 慢速广播(100-500ms): 平衡功耗和响应速度。日常使用推荐。
  • 超慢广播(1s以上): 省电,但手机要等很久才能发现。适合电池供电设备。

嗯,这里要注意:广播数据不能超过31字节。如果你要广播的数据多,可以用扫描响应数据(Scan Response Data)来补充。

3.6 开启广播——让信号“飞”出去

配置完广播参数,最后一步就是开启广播:

static void advertising_start(void)
{
    ret_code_t err_code;
    
    err_code = sd_ble_gap_adv_start(adv_handle, APP_BLE_CONN_CFG_TAG);
    APP_ERROR_CHECK(err_code);
    
    NRF_LOG_INFO("广播已开启,等待连接...");
}

如果一切顺利,芯片就会在三个广播通道(37、38、39)上交替发送广播包。这时候,你的手机就能看到它了。

调试技巧: 如果广播开启失败,先检查协议栈是否初始化成功。我习惯在协议栈初始化后加一个GPIO翻转,用示波器看波形。如果GPIO没翻转,说明协议栈卡死了。

3.7 手机端使用nRF Connect扫描并连接

代码烧录进去,怎么验证?用手机。nRF Connect是Nordic官方出的调试工具,免费又好用。

操作步骤:

  1. 打开nRF Connect,点击“SCAN”按钮
  2. 观察列表,找到你设置的设备名称(比如“My_BLE_Device”)
  3. 点击设备,查看广播数据——应该能看到你设置的名称和UUID
  4. 点击“CONNECT”,观察连接状态

如果扫描不到设备,别慌。先检查这几个地方:

  • 芯片供电正常吗?我遇到过3.3V供电不稳导致射频不工作
  • 天线匹配电路焊好了吗?没天线,信号出不去
  • 广播参数里的设备名称设置了吗?空名称的设备会被某些手机过滤掉

我记得有一次,客户说设备扫描不到。我远程看了半天代码,最后发现是广播数据长度算错了,多了一个字节。协议栈直接拒绝发送广播包。这种问题,用nRF Connect的“RAW”模式看原始数据,一眼就能发现。

3.8 本章核心逻辑流程图

下面这张图,概括了整个BLE广播的启动流程。我建议你把它打印出来贴在工位上。

BLE广播启动流程 系统上电 初始化时钟 初始化外设(串口等) 初始化BLE协议栈 配置广播参数 开启广播 关键注意事项 1. 低频时钟必须稳定 后再初始化协议栈 2. 广播数据不超过31字节 3. 广播间隔影响功耗和 响应速度 4. 检查RAM地址是否冲突 5. 天线匹配电路必须焊接 6. 用nRF Connect验证 广播数据是否正确 7. 调试时先开串口打印 8. 失败时检查错误码

3.9 本章小结

这一章,我们走通了BLE开发的第一个里程碑——让芯片发出广播包。你学会了:

  • 如何从SDK示例创建工程模板
  • main函数的六步标准流程
  • 时钟和外设的初始化要点
  • 协议栈初始化的RAM配置陷阱
  • 广播参数的配置方法
  • 用nRF Connect验证广播

这些内容,是后续所有BLE应用开发的基础。你想想看,广播都搞不定,后面的连接、数据传输就更别谈了。所以这一章,我建议你亲手敲一遍代码,烧录到板子上,用手机看到设备名的那一刻,你会很有成就感。

核心一句话: BLE开发,先让芯片“喊出来”,再让它“听进去”。广播就是它“喊”的方式。

公众号:蓝海资料掘金营,微信deep3321