简介:这份资源是ST公司BLUENRG355MC蓝牙低功耗SoC的配套例程包,面向物联网与可穿戴设备开发者,以及希望快速上手BlueNRG-LP系列芯片的嵌入式工程师。BLUENRG355MC集成ARM Cortex-M0+处理器与BLE射频收发器,支持蓝牙5.0规范,例程覆盖初始化配置、BLE协议栈交互、连接管理、GATT数据传输、低功耗模式切换及中断事件驱动等核心环节,可帮助读者理解如何配置广播与连接参数、使用HCI命令、创建自定义服务并优化电池续航。压缩包共约2000个文件,整体264.55MB,以C源码与头文件为主体,辅以hex、bin固件、工程文件(uvprojx、ewp、cproject等)、HTML文档及PNG图示,便于在STM32CubeIDE、Keil、IAR等环境中直接编译调试。目前已有344人学习下载,适合作为BLE产品原型开发的参考起点。
1. 从一块 BlueNRG-LP 开发板说起:这套例程到底能帮你省多少事
如果你手上正捏着一块基于 BLUENRG355MC 的开发板,想点亮一颗 LED、跑通一次 BLE 广播、再顺手把低功耗模式调明白,那这套例程基本就是你最该先翻的东西。BLUENRG355MC 属于 BlueNRG-LP 系列,是一颗把 Cortex-M0+ 内核和 2.4GHz 射频前端塞进同一封装里的蓝牙低功耗 SoC,官方给的这套例程覆盖了 GPIO、UART、SPI、I2C、定时器、ADC、RTC、看门狗,以及 BLE 外设从广播、扫描、连接到数据收发的一整条链路。它适合两类人:一类是刚拿到板子、连时钟树都还没摸清的新手,照着例程改改参数就能看到现象;另一类是准备做量产固件、想拿官方驱动当底座二次开发的熟手,可以直接把外设初始化和协议栈回调抠出来复用。我见过太多人一上来就自己写寄存器,结果卡在射频校准那一步,其实例程里早就把顺序和延时写好了。
2. 工程结构与技术栈:先看清目录再动手编译
2.1 这套例程的目录是怎么分的
拿到压缩包解压之后,别急着打开 IDE,先花五分钟把目录扫一遍。BLUENRG355MC 的例程通常按「外设 + 协议栈」两条线组织,根目录下一般能看到这么几类文件夹:
| 目录/文件 | 作用 | 你该关注什么 |
|---|---|---|
Drivers/ | 芯片外设驱动,含 CMSIS 和 HAL 风格封装 | 改引脚、改时钟先来这里 |
Middlewares/ | BLE 协议栈、射频驱动、OTA 相关 | 协议栈 API 和回调都在里面 |
Projects/ | 按外设或功能拆分的可编译工程 | 每个子目录一个独立例程 |
Utilities/ | 串口打印、延时、断言等公共工具 | 调试输出靠它 |
Startup/ | 启动文件与中断向量表 | 改中断服务函数别改错文件 |
Projects/下面通常还会再分Peripherals和BLE两大类,前者是纯外设验证,后者是带协议栈的完整链路。我一般建议新手先跑Peripherals里的 GPIO 翻转,确认工具链和下载器没问题,再去碰 BLE,不然一旦广播起不来,你分不清是射频问题还是编译配置问题。
2.2 技术栈与工具链
这套例程的底层是 CMSIS 标准接口,外设驱动是芯片原厂自己封的 HAL 层,BLE 部分跑的是官方协议栈库(以.a或.lib形式提供),应用层用 C 语言写回调。编译工具链常见的是 IAR EWARM 和 Keil MDK 两套工程文件,也有基于 GCC 的 Makefile 版本。调试接口走 SWD,两根线加地就能下载和单步。
提示:如果你用的是 GCC 版本,注意链接脚本里 Flash 起始地址和协议栈预留区要对齐,否则会出现「下载成功但一上电就复位」的情况。
2.3 编译前必须确认的三件事
第一,确认你的工程选的是对应封装和 Flash 容量的型号,BLUENRG355MC 不同后缀的 RAM 大小不一样,选错会在链接阶段报 region overflow。第二,确认协议栈库的路径已经加到链接器搜索目录里,否则会报一堆undefined reference。第三,确认下载器配置里的复位方式,有些板子需要「connect under reset」才能识别。
# 以 GCC Makefile 版本为例,先清理再编译 make clean make -j4 # 并行编译,加快速度 # 编译产物一般在 build/ 或 output/ 下,形如 *.elf 和 *.binmake clean清掉上次的中间文件,避免旧目标文件干扰;-j4按 CPU 核数并行,数字按你机器调整。编译完先别急着烧,用arm-none-eabi-size看一眼 Flash 和 RAM 占用,心里有个底。
3. 核心模块拆解:从 GPIO 到 BLE 广播的完整链路
3.1 GPIO 与时钟:所有外设的起点
BLUENRG355MC 的外设时钟是分域管理的,你不开对应时钟,寄存器写进去也没反应。例程里通常有一个SystemClock_Config()负责把系统时钟切到射频需要的频点,然后每个外设初始化前调用__HAL_RCC_GPIOx_CLK_ENABLE()之类的宏。
/* 使能 GPIO 时钟并配置一颗 LED 引脚 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* 不开时钟,后面全白搭 */ GPIO_InitTypeDef gpio = {0}; gpio.Pin = GPIO_PIN_5; /* 按你板子实际丝印改 */ gpio.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */ gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); /* 点亮 */ HAL_Delay(500); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); /* 熄灭 */Pin要对着原理图改,别照抄;Mode如果是按键输入就换成GPIO_MODE_INPUT并配上拉;HAL_Delay依赖 SysTick,如果例程里把 SysTick 关了,这个延时不会生效,得换成空循环。这一步跑通,说明你的下载、复位、时钟三条路都通了。
3.2 UART 打印:调试的后悔药
BLE 调试最怕「黑匣子」,广播发没发出去、连接断在哪一步,全靠日志。例程一般会重定向printf到 UART,或者提供一个APP_DEBUG宏控制输出。
/* UART 初始化片段,波特率按调试工具改 */ huart.Instance = USART1; huart.Init.BaudRate = 115200; huart.Init.WordLength = UART_WORDLENGTH_8B; huart.Init.StopBits = UART_STOPBITS_1; huart.Init.Parity = UART_PARITY_NONE; huart.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart); /* 重定向后就能直接用 printf */ printf("BLE stack init ret=%d\r\n", ret);波特率对不上就是一堆乱码,这是最常见的翻车点。Mode只发不收的话可以只留UART_MODE_TX,省一点功耗。重定向函数_write或fputc要按你的工具链实现,IAR 和 GCC 写法不一样,例程里通常已经给好了。
3.3 BLE 协议栈初始化与广播
这是整套例程的重头戏。BLE 应用的骨架是「初始化协议栈 → 配置 GAP/GATT → 启动广播 → 在回调里处理连接和读写」。
/* 1. 协议栈初始化,传入回调函数表 */ ret = BLE_STACK_Init(&BLE_Stack_Callbacks); if (ret != BLE_STATUS_SUCCESS) { printf("stack init failed: 0x%02X\r\n", ret); return; } /* 2. 配置设备名和地址 */ aci_gap_set_discoverable(ADV_IND, /* 可连接非定向广播 */ 0, 0, /* 无过滤 */ PUBLIC_ADDR, /* 公共地址 */ sizeof(local_name), local_name, 0, 0, 0, 0, 0, 0); /* 3. 启动广播 */ aci_gap_set_advertising_enable(ENABLE, 1, &adv_param);BLE_STACK_Init的回调表里最关键的是「事件回调」,连接建立、断开、数据到达都从这里进来。aci_gap_set_discoverable的参数顺序容易记混,广播类型、地址类型、设备名长度和内容要一一对应。广播起不来时,先看返回值,0x42一类通常是参数不合法,0x0C往往是射频还没校准完就调用了。
3.4 事件回调:连接与数据收发的落点
协议栈是异步的,你的业务逻辑几乎都写在回调里。
static void BLE_Event_Callback(uint16_t event, void *payload) { switch (event) { case EVT_LE_CONN_COMPLETE: { /* 连接建立,记录连接句柄,停掉广播 */ conn_handle = ((evt_le_connection_complete *)payload)->handle; printf("connected, handle=%d\r\n", conn_handle); break; } case EVT_DISCONN_COMPLETE: printf("disconnected\r\n"); /* 断开后重新开广播,否则设备就“消失”了 */ aci_gap_set_advertising_enable(ENABLE, 1, &adv_param); break; case EVT_GATT_ATTRIBUTE_MODIFIED: /* 手机写了特征值,在这里取数据 */ break; default: break; } }conn_handle是后续所有收发操作的钥匙,必须存好。断开后重开广播这一步新手最容易漏,现象就是「连一次之后再也搜不到」。EVT_GATT_ATTRIBUTE_MODIFIED里拿到的数据指针是协议栈内部缓冲,要尽快拷走,别在里面做耗时操作。
4. 避坑与排查:那些让我熬夜的现场
4.1 广播搜不到
现象:板子跑起来了,串口也打印了「advertising」,但手机搜不到。原因多半是射频校准没完成就启动广播,或者天线匹配电路没焊好。解决:在初始化后加一段延时等校准完成,用频谱仪或另一块板子做扫描确认射频真的在发。
4.2 下载成功但一上电就复位
现象:调试器里能跑,拔掉调试器重新上电就死。原因通常是链接脚本里 Flash 起始地址和实际启动区不一致,或者看门狗没喂。解决:核对链接脚本的ROM起始地址,检查main之前有没有提前开看门狗。
4.3 串口乱码
现象:日志全是问号和方块。原因九成是波特率不匹配,剩下一成是时钟源配置错了导致实际波特率偏移。解决:先用示波器量一下 TX 引脚的实际位宽,反推波特率,再回头改时钟树。
4.4 连接后频繁掉线
现象:连上几秒就断,反复重连。原因可能是连接参数(间隔、延迟、超时)设得太激进,超出了手机端的接受范围。解决:把连接间隔先设成 30ms 以上、超时设成 4s 左右,稳定后再慢慢压。
4.5 低功耗模式唤醒不了
现象:进了睡眠就再也醒不过来。原因多半是唤醒源没配对,比如用了 GPIO 唤醒却没使能对应的 EXTI 线。解决:对照例程里的低功耗 demo,逐条核对唤醒源使能位和中断优先级。
5. 二次开发:把例程改成你自己的固件
5.1 从例程到产品的改造顺序
我的习惯是先把例程的 BLE 部分原封不动跑通,确认射频和协议栈没问题,再动业务逻辑。改造顺序一般是:改设备名和广播数据 → 加自定义 GATT 服务 → 把传感器数据塞进特征值 → 调连接参数和功耗。每一步都单独验证,别一次改五个地方,不然出了问题你都不知道是哪一步引入的。
5.2 加一个自定义 GATT 服务
官方例程里通常有一个「服务定义表」,你照着加就行。关键是 UUID、权限和回调要对应上。
/* 自定义服务与特征值定义(简化示意) */ static const uint8_t service_uuid[16] = {0x11,0x22, /* ... */}; static const uint8_t char_uuid[16] = {0x33,0x44, /* ... */}; /* 注册服务,返回服务句柄 */ aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 6, &service_handle); /* 在服务下加一个可读可通知的特征值 */ aci_gatt_add_char(service_handle, UUID_TYPE_128, (Char_UUID_t *)char_uuid, 20, CHAR_PROP_READ | CHAR_PROP_NOTIFY, ATTR_PERMISSION_NONE, GATT_NOTIFY_ATTRIBUTE_OFFSET, 16, 1, &char_handle);UUID_TYPE_128表示 128 位 UUID,用 16 位的话换成UUID_TYPE_16并传短 UUID。CHAR_PROP_NOTIFY打开后,你就能用aci_gatt_characteristic_value_update主动推数据给手机。GATT_NOTIFY_ATTRIBUTE_OFFSET这个参数决定通知时带不带句柄,按你手机端解析逻辑选。
5.3 功耗优化的几个抓手
BLUENRG355MC 的低功耗是卖点,但默认配置不一定省电。我一般会做三件事:把没用到的外设时钟全关掉;把广播间隔从默认的几十毫秒拉长到几百毫秒;在连接空闲时让协议栈进低功耗模式,靠 GPIO 或定时器唤醒。改完用电流表量一下,正常能从毫安级降到微安级。
5.4 验证方法
改完别只看「能跑」,要量化验证。广播距离用固定发射功率下手机能搜到的最大距离衡量;连接稳定性用连续连接断开一百次的成功率衡量;功耗用平均电流乘电池容量估算续航。我习惯在每次改完连接参数后,强制走一遍「连接—收发—断开—重连」的完整循环,确认没有内存泄漏和句柄耗尽。
注意:协议栈的缓冲和句柄是有限的,反复连接不释放,跑几百次就会失败。断开回调里该清的资源一定要清。
从那以后我每次拿到新的蓝牙例程,都强制先跑通「广播—连接—收发—断开—重连」这一整圈,再动任何业务代码。希望这套 BLUENRG355MC 例程也能帮你少走几个弯路。
本文还有配套的精品资源,点击获取