3、第一个BLE工程:创建工程模板、理解main函数流程、初始化时钟与外设、启动协议栈、配置广播参数、开启广播、手机端使用nRF Connect扫描并连接
好,咱们正式开始写代码了。
这一章,我会带你从零搭建一个BLE工程。说白了,就是让芯片先“开口说话”——发出广播包。你想想看,一个蓝牙设备如果不广播,手机根本找不到它。所以广播是BLE通信的第一步,也是我们调试硬件和协议栈是否正常工作的“Hello World”。
3.1 创建工程模板——别从零开始,站在巨人肩膀上
我个人习惯,从来不自己从头写启动文件和链接脚本。太容易出错了。我建议你直接用芯片厂商提供的SDK示例工程,然后在此基础上“做减法”。
以Nordic nRF52832为例,我一般这么干:
- 找到SDK目录下的
examples/ble_peripheral/ble_app_beacon - 把整个文件夹复制一份,重命名为
my_first_ble - 删掉不必要的源文件(比如某些服务文件),只保留核心框架
嗯,这里要注意:不同芯片的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打印日志,方便调试。你想想看,如果连串口都没开,芯片跑没跑你都不知道。
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,这个变量告诉协议栈“用户程序从哪个地址开始用”。如果配置不对,程序会跑飞。
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)上交替发送广播包。这时候,你的手机就能看到它了。
3.7 手机端使用nRF Connect扫描并连接
代码烧录进去,怎么验证?用手机。nRF Connect是Nordic官方出的调试工具,免费又好用。
操作步骤:
- 打开nRF Connect,点击“SCAN”按钮
- 观察列表,找到你设置的设备名称(比如“My_BLE_Device”)
- 点击设备,查看广播数据——应该能看到你设置的名称和UUID
- 点击“CONNECT”,观察连接状态
如果扫描不到设备,别慌。先检查这几个地方:
- 芯片供电正常吗?我遇到过3.3V供电不稳导致射频不工作
- 天线匹配电路焊好了吗?没天线,信号出不去
- 广播参数里的设备名称设置了吗?空名称的设备会被某些手机过滤掉
我记得有一次,客户说设备扫描不到。我远程看了半天代码,最后发现是广播数据长度算错了,多了一个字节。协议栈直接拒绝发送广播包。这种问题,用nRF Connect的“RAW”模式看原始数据,一眼就能发现。
3.8 本章核心逻辑流程图
下面这张图,概括了整个BLE广播的启动流程。我建议你把它打印出来贴在工位上。
3.9 本章小结
这一章,我们走通了BLE开发的第一个里程碑——让芯片发出广播包。你学会了:
- 如何从SDK示例创建工程模板
- main函数的六步标准流程
- 时钟和外设的初始化要点
- 协议栈初始化的RAM配置陷阱
- 广播参数的配置方法
- 用nRF Connect验证广播
这些内容,是后续所有BLE应用开发的基础。你想想看,广播都搞不定,后面的连接、数据传输就更别谈了。所以这一章,我建议你亲手敲一遍代码,烧录到板子上,用手机看到设备名的那一刻,你会很有成就感。
公众号:蓝海资料掘金营,微信deep3321