BLUENRG355MC 开发实战:从 GPIO 到 BLE 广播的完整例程解析
简介这份资源是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 例程也能帮你少走几个弯路。本文还有配套的精品资源点击获取