在基于 BlueNRG‑1/2 做蓝牙项目时,几乎都会遇到同一个坑:一擦 Flash、一写 Flash,BLE 就断连、射频停摆甚至整机死机。
ST 官方 LAT1216 应用笔记把这个问题的根源、互斥规则、以及可直接工程化的解决方案讲得非常清楚。这篇文章就用最落地、去 AI 化的方式,把这套Flash 安全操作方案完整讲明白,让你以后再也不会因为存参数把蓝牙跑挂。
资料获取:【应用笔记】LAT1216 BlueNRG系列芯片Flash操作与BLE事件的互斥处理
1. 问题到底出在哪
BlueNRG‑1/2 里有两个硬件事实,决定了 Flash 和 BLE 不能同时干活:
- Flash 擦除会关中断 20ms 以上,擦一页 Flash 期间,芯片会关掉全局中断来保证写入稳定。而 BLE 射频、连接事件、广播、空中包收发全都靠高精度中断,中断一堵,BLE 状态机直接乱掉。
- Flash 与射频总线互斥,Flash 操作和 RF 收发会抢同一条硬件总线,不能同时进行,否则直接硬件异常、死机。
所以:
- 读 Flash:不用互斥,随时可以读;
- 写 / 擦 Flash:必须等 BLE 射频完全空闲才能做。
2. 官方标准解法:链表调度 + Radio 空闲窗口
LAT1216 给出的方案不是粗暴关蓝牙,而是:
- 把擦除、写入封装成任务,挂到双向链表里排队;
- 监听 BlueNRG 的 radio 活动事件,精准抓到射频空闲时间段;
- 在空闲窗口里安全执行一次 Flash 操作,做完立刻释放给 BLE。
整套机制完全不堵 BLE 协议栈,多连接、主从一体都能稳跑。
3. 4 步直接移植到你的工程
1)打开 Radio 活动事件上报
初始化 BLE 之后,调用这一句,让芯片告诉你射频什么时候忙 / 闲:
aci_hal_set_radio_activity_mask(0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020);
2)实现射频空闲回调
重写事件 aci_hal_end_of_radio_activity_event,系统每次射频结束都会进来,告诉你下一次什么时候开始忙。
void aci_hal_end_of_radio_activity_event(uint8_t last_state, uint32_t next_state_sys_time)
{
// 更新空闲窗口,交给 Flash 调度器
free_list_update(last_state, 0, next_state_sys_time);
}
3)主循环里跑调度
在 APP_Tick() 或主 while 里调用 Flash 调度:
void APP_Tick(void)
{
BTLE_StackTick();
// Flash 任务调度(核心)
flash_operate_tick();
}
4)应用层只调链表 API,不直接操作 Flash
把写 / 擦变成 “任务”,丢进链表排队,由调度器自动找空闲执行:
// 擦页
flash_list_erase_page(page_addr, NULL, NULL);
// 写入
flash_list_program(write_addr, data_buf, len, NULL, NULL);
4. 调度器干了什么(看懂就不会乱改)
flash_operate_tick 里面只做三件事:
- 用
next_state_sys_time算出当前空闲多久; - 如果空闲够执行一次 Flash,就从链表拿一个任务执行;
- 做完移除节点,超时任务直接扔掉,不拖 BLE 后腿。
5. 没有 BLE 活动时怎么写 Flash
如果设备没广播、没连接,Radio 不会出空闲事件,调度器不会跑。
这时可以强制跑一遍所有 Flash 任务:
flash_list_tick(0xFFFFFFFF);
适合上电初始化、离线配置等场景。
6. 最重要的 5 条避坑规则(LAT1216 核心)
- 写 / 擦必须走链表,绝对不能直接调用;
- 中断里、BLE 回调里绝对不能操作 Flash;
- 只有擦除和编程需要互斥,读 Flash 随便读;
- 多连接场景一定要用这套方案,不要关 BLE 等 Flash;
- 空闲窗口不够时,调度器会自动跳过,不会堵死蓝牙。
BlueNRG‑1/2 里 Flash 写 / 擦 ≠ 能和 BLE 同时跑,这是硬件总线与中断机制决定的。ST LAT1216 给出的双向链表 + Radio 空闲调度是官方标准、最稳定的解法:
- 不修改协议栈
- 不影响连接、广播、扫描
- 多连接 / 主从一体都稳
- 代码可直接跨工程移植
你只要按本文 4 步把链表调度加上,以后存参数、存日志、存配对信息,再也不会把 BLE 跑挂。
93