BLUENRG355MC蓝牙SoC例程开发实战:编译、GATT服务与低功耗调优
2026/9/7 7:56:20 网站建设 项目流程

简介:BLUENRG355MC是ST公司面向低功耗蓝牙应用推出的系统级芯片,集成ARM Cortex-M0+内核与BLE射频收发器,适用于物联网终端、可穿戴设备及健康监测类产品。这套例程包基于官方BlueNRG-LP_LPS DK 1.4.0开发套件整理,共2000个文件,其中包含丰富的C/H源码、IAR与Keil工程文件(ewp、uvprojx)、编译链接脚本、hex/bin烧录镜像以及HTML文档等,压缩包大小264.55MB,可满足从入门评估到产品化的工程参考需求。例程内容覆盖设备初始化、BLE协议栈配置、连接管理、GATT数据传输、低功耗模式切换及中断事件驱动等关键环节,并附带库函数和示例代码,便于开发者快速理解芯片工作方式。目前已有342人学习浏览,适合正在使用BlueNRG系列芯片进行BLE产品开发的工程师对照学习。 拿到ST的BLUENRG355MC蓝牙芯片例程那几天,我差点被例程包的庞大目录劝退。后来硬着头皮把官方例程从编译、烧录到改服务全跑了一遍,才摸清楚这套代码的正确打开方式。这篇东西就是给那些和我一样,刚拿到这款芯片就想尽快出活的工程师看的。如果你之前做过CSR BC417那种经典蓝牙方案,或者一直在玩STM32F407这一类MCU裸机例程,第一次打开BLUENRG355MC例程时大概率会懵:工程初始化了一大堆东西,main函数反而“没什么事干”。

BLUENRG355MC不是一颗普通蓝牙芯片,它内部有Cortex-M0+应用核,BLE协议栈以库的形式链接进工程,不需要外部MCU通过HCI命令去控制蓝牙协议栈。这个架构决定了例程的组织方式:不是“MCU工程加蓝牙模块固件”,而是一套从启动文件、外设驱动到GATT服务、低功耗状态机完整封装好的SDK。所以我这篇不只是教你怎么编译一个例程,而是把例程包结构、跑通流程、代码拆解、改造成自己产品的方法,以及我实际踩过的坑统统讲一遍,帮你省掉自己摸索的那几天。

1. BLUENRG355MC的例程包到底装了什么

1.1 单芯片SoC决定了例程的层次

BLUENRG355MC属于ST的新一代低功耗蓝牙单芯片方案,芯片上同时集成了射频前端、BLE协议栈和应用处理器。老式方案通常是MCU通过UART或SPI向蓝牙模块发HCI命令,例程的核心在MCU侧的驱动库;而这颗芯片的例程完全是另一套玩法,应用代码和协议栈跑在同一颗芯片里,对外体现为一个完整的BLE从机或多角色设备。这就导致例程包的目录结构不是“主控工程+模块固件”,而是从CMSIS启动文件、芯片外设驱动、协议栈库、GATT服务定义到应用主循环的完整分层。

从ST官网下载的官方例程包,通常包含文档、驱动、中间件和各个示例工程。我第一次打开时习惯性去找“User”目录,结果发现整个工程被拆得很碎,光是中间层就有hci、osal、hal、apl等多个文件夹。后来我意识到,这些目录对应的是BLE协议栈的分层结构:HCI层负责和协议栈交互,OSAL层做内存管理,HAL层是硬件抽象,APL层才是你真正要写业务逻辑的地方。所以读例程千万不要从main.c第一行开始逐行啃,先看清文件夹归属,再决定改哪个文件。

1.2 先看Release Notes,再看readme,最后再编译

如果你跟我一样,是拿到例程包就急着点编译的人,大概率会在第一步翻车。ST的BLE例程包更新很快,不同版本依赖的IDE版本、芯片支持包甚至协议栈库都不一样。我看到过有人在旧版工程上硬编新版BLUENRG355MC,结果报了一堆莫名其妙的类型错误,其实只是头文件路径没配对。所以拿到例程包第一件事是打开ReleaseNotes.html和每个示例下的readme.txt,确认IDE版本、芯片型号、支持的开发板,再动手。这个习惯在调任何单片机例程时都适用,但蓝牙SoC的例程依赖链更长,不做这一步很容易被“编译错误”误导到错误的方向。

以官方包的目录为例,典型的路径是这样的:

BLUENRG355MC_DK/ ├── Documentation/ ├── Drivers/ ├── Middlewares/ │ └── ST/ │ └── STM32_BLE_Manager/ ├── Projects/ │ ├── BLE_Examples/ │ │ ├── BLE_HeartRate/ │ │ ├── BLE_Beacon/ │ │ └── BLE_SensorDemo/ │ └── Templates/ └── Utilities/

Projects下面每一个文件夹就是一个独立例程,Templates适合用来当新工程的起点。我个人建议不要从空白模板开始,而是先复制一个最接近需求的例程再删改,这样可以省掉很多芯片初始化、协议栈配置的麻烦。等你自己能完整说清楚模板里每一行是干什么的,再考虑从零建工程,效率会高很多。

2. 跑通第一个例程前,先把环境这四件事做对

2.1 开发环境选择

BLUENRG355MC官方例程一般提供STM32CubeIDE和Keil MDK两种工程。我一直在用STM32CubeIDE,免费、跨平台,对ST自家芯片支持最完整。打开IDE后直接导入Projects下的工程文件,第一次编译时它会自动下载或匹配芯片支持包,如果网络环境不稳定,这一步很容易卡住,可以先手动安装对应版本的STM32Cube MCU Package,再把例程里的工程导入。有些朋友用Keil,也没问题,关键是IDE版本和芯片支持包版本必须和Release Notes里写的保持一致,否则编译报错会让人怀疑人生。

这里要特别提醒一个坑:例程包的工程路径里尽量不要有中文和空格,也不要把文件夹放到太深的目录下。我试过把工程放在某个网盘同步目录里,路径总长超过两百个字符,编译时报了一大堆文件找不到,后来才发现是路径深度问题。换到C:\work\bluenrg355mc这种短路径后,一切正常。这种问题看起来玄学,其实和编译器的文件路径长度限制有很大关系。

2.2 ST-Link连接和驱动

BLUENRG355MC官方评估板上一般板载ST-Link调试器,用USB线连上电脑后,设备管理器里应该能看到一个ST-Link调试端口。如果没看到,先装ST-Link驱动,再确认线和板子供电正常。连好板子后在IDE里选择调试配置,目标芯片会自动识别。烧录时如果提示连接失败,最常见的是板上供电电压不对,或者调试接口被例程初始化为普通GPIO。这时候按住板上的复位键再点击烧录,等烧录进度条出现后再松开复位,基本都能救回来。这个技巧在芯片跑飞、调试接口被重新配置时特别管用。

2.3 编译和烧录

导入工程后先切到正确的编译目标。BLUENRG355MC例程一般有Release和Debug两种配置,做低功耗测量一定要用Release,因为Debug配置会关闭优化,生成的代码在时间和功耗表现上都不接近真实产品。编译前检查一下工程的C/C++预定义宏,确认包含芯片型号宏,例如BLUENRG355MC,否则编译会走进错误的条件编译分支,看起来是编译错误,实际是宏没开。编译通过后把固件烧进芯片,例程默认上电就会初始化协议栈并开始广播。此时如果手机App扫描不到设备,先不要怀疑芯片,检查是不是板子还停在调试断点上,或者广播名被手机缓存了,可以拔掉调试器重新上电让芯片独立运行再测。

2.4 手机调试工具

跑BLE例程必须准备手机App,推荐ST官方出品的ST BLE Toolbox,iOS和Android都有。第一次使用别急着点连接,先在扫描列表里看广播名称、MAC地址和RSSI,确认设备在广播。再点击连接,进去之后能看到GATT服务列表。这个过程不是“看看而已”的,后面改GATT服务、验证Notify上报、测试读写,都离不开这个工具。使用中如果发现手机扫描不到设备,可以把手机蓝牙关掉重开,或换个App对比,排除手机缓存问题。

3. 例程代码的结构拆解:别急着改,先读这三个文件

3.1 main.c:主循环和初始化流程

所有BLE例程的main.c看起来都很短,但里面藏着整套系统的生命周期。理解初始化顺序比背API重要得多。典型的初始化流程是:系统时钟和GPIO初始化,接着初始化协议栈管理器,然后调用Aci_Gap_Init建立GAP层、Aci_Gatt_Init建立GATT层,注册服务后调用Aci_Gap_Set_Discoverable开启广播,最后进入while(1)主循环处理协议栈事件和低功耗调度。我第一次打开例程时,以为主循环里会像普通MCU例程一样疯狂轮询外设,结果看到的更像一个消息循环,代码主要在等待BLE协议栈事件。这个区别很重要:蓝牙SoC的例程是事件驱动的,手机发来数据、连接建立、连接断开,都会包装成事件送进应用层。如果你把普通MCU的轮询写法套进来,蓝牙事件响应会非常别扭。

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); BLE_Init(); // 初始化协议栈 Aci_Gap_Init(role, ...); // GAP层参数 Aci_Gatt_Init(); // GATT层初始化 Service_Init(); // 注册应用服务 Aci_Gap_Set_Discoverable(...); // 开启广播 while(1) { BLE_Manager_Process(); // 处理协议栈事件 } }

上面这段不是某一个例程的完整源码,而是我在几个例程里看到的共性骨架。用它当阅读地图,你能快速在官方例程里定位到对应的初始化函数,不至于迷失在文件堆里。

3.2 ble_conf.h:协议栈裁剪的关键

另一个容易被当成“普通配置头文件”放过的文件是ble_conf.h,它是BLE协议栈的裁剪开关。例程默认配置往往开口很大,把最大连接数、GATT属性数量、缓冲区大小都调到了适合演示的值。做产品时如果照抄不裁剪,内存占用高,低功耗也做不好;反过来,盲目减小配置又会导致连接后属性读取失败。我在改一个温湿度计项目时,最开始的GATT属性数量配置偏小,手机连接后经常读到一半就断开,日志里看不到明显错误。后来把属性数、服务数、特征数逐个加大,问题才消失。所以改这个文件时,建议记录每次改动前后编译器报告的内存变化,用Map文件确认RAM和Flash的使用,做到心里有数。

3.3 GATT服务相关文件:真正的业务逻辑所在

例程里业务逻辑最集中的地方不是main.c,而是GATT服务定义和回调处理。以心率例程为例,心率服务的特征定义、心率值更新回调都在独立文件中。手机打开Notify之后,例程定时调用更新函数,芯片就会主动把心率值推给手机,不需要手机轮询。这种“服务器主动推送”的模式,和我在LabVIEW里写TCP通信时主动收发数据的思路完全不同。嵌入式工程师从串口、网络通信转到BLE时,最容易卡住的就是这个点:不知道数据是谁主动发起的。在BLE里,读写和通知都围绕GATT特征展开,特征属性里定义了可读、可写、支持通知等权限,应用层只需要在合适的时机更新特征值,协议栈会自动把数据推给订阅过的客户端。

4. 把官方例程改成自己产品功能的六个步骤

4.1 改设备名称和广播数据

第一步通常是把设备名改成自己的产品名。BLE广播数据有31字节限制,设备名如果太长,例程会把名字放在扫描响应里而不是广播数据里。我早期为了在广播里塞进一长串中文设备名,发现怎么都扫不到,后来把中文名缩短并改为UTF-8编码放到扫描响应里才解决。手机App扫描时列表显示的名称来自广播数据和扫描响应数据,如果只改了GAP层的device name,没改广播数据里的name字段,就会出现“连接后看到新名字,扫描时看到旧名字”的怪现象。改广播数据时还要注意,App扫描列表一般优先显示广播包里的Name,如果广播包里没有Name才去读扫描响应或触发连接,所以产品名最好精简到能放进广播包。

4.2 调整广播间隔和发射功率

广播间隔决定设备被发现的速度和功耗。间隔越小,被发现越快,但平均电流越大。BLE广播间隔范围从20ms到10.24s,例程默认值往往偏快,适合演示。做产品时建议从100ms开始调,再通过实际体验权衡。ST的API里通常有对应的设置命令,Tx功率也有对应命令。这里没有万能参数,只能通过实测功耗和连接速度来权衡。需要说明的是,广播里如果携带厂商自定义数据,包长增加,单个广播事件的时间也会变长,功耗随之上升,所以广播数据不是越多越好,能少就少。

4.3 增加一个自己的服务

给自己产品定义一个服务,需要先规划一个128位的自定义UUID,然后在GATT服务定义表中增加服务声明、特征声明、特征值这几条记录。以“开关控制”服务为例:特征属性设置为读、写、通知,写入回调里解析收到的数据,通知回调里判断客户端是否订阅。之后手机写对应的特征值,芯片就能收到数据并控制GPIO。这段逻辑如果没理解,建议先在官方例程上跑通一个“UUID替换”实验:把例程中某个特征的UUID改成自己的UUID,保持其他逻辑不变,手机App刷新后能看到新UUID,你就明白GATT定义表是怎么回事了。之后再加读写权限和回调,比一次堆很多功能更稳妥。

4.4 连接与断开事件处理

BLE连接后如果还一直广播,既费电又容易让手机误连。例程里通常在连接建立回调中停止广播,在断开连接回调中重新开启广播。改代码时不要只改一处,要把连接状态和广播状态当成一个状态机来维护。我曾见过有人直接注释掉停止广播的调用,结果设备连上一个手机后其他手机还能扫描到,现场演示时非常尴尬。断开重连也有讲究:如果断开后立刻重新广播,手机端刚断开又扫到同一个地址,有可能触发快速重连,体验反而奇怪;一般建议在断开回调里做一点点延时或加一个定时退避,再恢复广播。

4.5 连接参数协商

手机作为BLE主机,连接间隔最终由它决定。如果芯片希望更低的功耗,可以在连接建立后主动发送连接参数更新请求,把连接间隔拉长、从机延迟增大。但手机端不一定接受,有些手机系统会很固执,所以代码里要写好请求失败的兜底策略,不要因为一次失败就反复发请求,那反而更耗电。如果做的是数据量较大的透传产品,反而需要尽量请求短的连接间隔和大MTU,这和应用场景强相关。官方例程里一般都有连接参数更新相关的调用,直接在连接回调里找到对应函数再按需修改参数即可,不要全盘照搬默认值。

4.6 低功耗睡眠与唤醒

BLUENRG355MC的低功耗,除了靠BLE协议栈省电,还要靠应用处理器进入睡眠模式。例程主循环在空闲时会检查是否有待处理事件,没有就进入低功耗状态。实际开发时,我建议先把外设逐个停掉:关闭调试串口、把未使用的GPIO配成模拟输入或固定电平、关掉不用的定时器,再测睡眠电流。很多时候电流下不来,不是芯片的问题,而是某个GPIO悬空导致漏电。每次只改一个条件、测一次电流,记录变化,这样能快速定位到是哪个外设在捣乱。不要同时改一堆东西,不然出了问题根本不知道是哪个改动造成的。

5. 实战中踩过的坑:射频、功耗和调试

5.1 例程跑起来但手机搜不到

这个问题我遇到不下三次,每次原因都不一样。第一次是板子没从调试态完全复位,第二次是天线区域被手或金属件遮挡导致RSSI太低,第三次是广播数据里设备名编码不对,手机把它当成了非法UTF-8字符串过滤掉。排查顺序建议是:先看板载LED或串口日志确认例程是否在跑,再用官方App扫描,最后才怀疑硬件。不要一上来就动天线匹配,那是最难查的方向。另外,有些手机的BLE缓存比较顽固,设备改了广播名之后很久还显示旧名字,这时候强制关闭手机蓝牙再打开,或者换一台手机扫描,能帮你快速判断是设备问题还是手机缓存问题。

5.2 连接后反复断开

如果例程能广播,但连接后几秒钟就断开,优先检查供电和复位。BLE在广播、连接瞬间会有几十毫安的峰值电流,如果用劣质USB线供电,压降一大,芯片就会瞬间复位,看起来就像连接不上。其次检查连接事件回调里是否有非法操作,有人在回调里做长时间延时,阻塞了协议栈处理,导致协议栈看门狗超时。硬件上用示波器抓VDD波形,看到明显跌落就换供电;软件上把回调函数里的耗时操作改成置标志位,回到主循环再处理,这个习惯能避免很多隐蔽的协议栈异常。

5.3 实测功耗和手册对不上

低功耗项目最后都会卡在电流上。我拿到BLUENRG355MC后第一次测睡眠电流,数值比手册高出一个数量级,排查了两天才发现是ST-Link的复位电路还在工作,测量时没有断开调试器。所以测低功耗之前,先确认芯片处于完全独立供电状态,调试器只保留GND连接,甚至整个拔掉。另外,例程默认会周期性地翻转LED或者做串口打印,这些都会影响电流,产品代码里一定要关掉。测量时用带统计功能的万用表或电流探头,记录平均电流和最差情况电流,而不是只看瞬时值,这样才能准确评估电池寿命。

5.4 网上例程和官方版本不匹配

网络上有一些基于旧版BlueNRG系列芯片移植过来的例程,不能直接拿来往BLUENRG355MC上套。API名称看起来很像,但参数个数、结构体定义、返回错误码可能都不一样。我遇到过有人把旧版的Aci_Gap_Init参数直接搬过来,编译能过,运行时设备却起不来。建议只以官方配套例程为基底,旧代码作为参考思路,不要直接替换源文件。如果要做版本迁移,翻阅官方Release Notes里的API变更说明,比对着两个头文件逐个比对效率高得多。把官方例程跑熟之后再自己动手建工程,会比网上零散的资料靠谱得多。

最后再分享一点个人体会。做BLE产品,例程只是起点,真正花时间的永远是事件怎么进应用层、状态机怎么转移、功耗怎么压下来这三大件。BLUENRG355MC这套例程的官方代码质量还是不错的,结构分层清楚,跑通之后再自己动手建工程,遇到问题先查Release Notes,再查官方文档,最后才去搜索引擎碰运气,这是我吃了不少亏之后总结出来的顺序。如果你也是从经典蓝牙或者普通MCU转过来的,建议先接受“事件驱动”这套思维,别急着复制粘贴网上代码。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询