2、蓝牙芯片存储架构:Flash与RAM的区别、Bootloader与Application分区、存储映射与地址空间
好,咱们来聊聊蓝牙芯片的存储架构。说实话,这部分内容我刚开始做蓝牙开发时也绕了不少弯路。你想想看,芯片就那么点资源,Flash和RAM怎么分配,Bootloader和Application怎么共存,这些搞不清楚,后面做OTA升级简直就是噩梦。
2.1 Flash与RAM:一个存代码,一个跑代码
先说说Flash和RAM的区别。这个其实挺直观的——Flash就像你的硬盘,断电后数据还在;RAM就像你的工作台,一断电就啥都没了。
Flash的特点:
- 非易失性存储,掉电不丢数据
- 读取速度快,写入速度慢(尤其擦除操作)
- 有擦写寿命限制,一般10万次左右
- 按页/扇区擦除,不能像RAM那样按字节改写
RAM的特点:
- 易失性存储,掉电数据全丢
- 读写速度都很快,几乎无延迟
- 无擦写寿命限制
- 按字节随机访问
我个人的经验:在项目中,我习惯把代码、常量、初始化数据放在Flash里。而堆栈、全局变量、动态数据则放在RAM里。有一次我为了省Flash空间,把一个大数组定义成const放在了Flash里,结果程序跑起来发现访问速度慢了不少——嗯,这就是没搞清楚Flash和RAM的读写特性。
说白了,Flash是「存」的地方,RAM是「跑」的地方。程序在Flash里存着,但运行时需要加载到RAM里执行(或者直接从Flash执行,取决于芯片架构)。
2.2 Bootloader与Application分区
接下来聊聊分区。蓝牙芯片的固件通常分成两个区域:Bootloader区和Application区。
Bootloader区:
- 负责芯片启动、硬件初始化
- 负责固件升级(OTA的核心)
- 一般很小,几KB到几十KB
- 通常写保护,防止意外擦除
Application区:
- 存放用户业务代码
- 蓝牙协议栈、应用逻辑都在这里
- 占大部分Flash空间
- OTA升级时会被擦除并重写
避坑指南:我曾经在一个项目中,Bootloader和Application的链接脚本没配置好,导致Bootloader的堆栈和Application的堆栈重叠了。结果每次OTA升级到一半就死机。后来花了整整两天才定位到问题——其实就是分区地址没对齐。所以,分区地址一定要留够安全间距,我一般会在两个分区之间留至少4KB的空白区域。
你可能会问:为什么要分两个区?直接一个区不行吗?
其实不行。你想想看,如果只有一个区,升级时把当前固件擦除了,结果新固件下载失败,芯片就变砖了。Bootloader就是那个「救生员」——它独立于Application,即使Application坏了,Bootloader还能工作,保证你能重新刷固件。
2.3 存储映射与地址空间
这部分稍微有点抽象,但很重要。存储映射说白了就是芯片怎么把Flash和RAM的物理地址映射到CPU的寻址空间里。
以我常用的Nordic nRF52832为例,它的地址空间大概是这样的:
| 地址范围 | 用途 | 大小 |
|---|---|---|
| 0x00000000 - 0x0003FFFF | Flash(代码区) | 256KB |
| 0x10000000 - 0x10000FFF | FICR(工厂信息配置寄存器) | 4KB |
| 0x20000000 - 0x20007FFF | RAM(数据区) | 32KB |
| 0x40000000 - 0x4FFFFFFF | 外设寄存器 | 256MB |
你看,Flash从0x00000000开始,RAM从0x20000000开始。CPU访问0x00000000就是访问Flash的第一个字节,访问0x20000000就是访问RAM的第一个字节。
那Bootloader和Application怎么映射呢?我举个例子:
// 典型的nRF52832分区映射
// Bootloader: 0x00000000 - 0x00007FFF (32KB)
// Application: 0x00008000 - 0x0003FFFF (224KB)
// 注意:0x00008000就是Application的起始地址
注意:芯片上电后,CPU会从复位向量地址(通常是0x00000000)开始执行。所以Bootloader必须放在Flash的起始位置。Application的起始地址则需要在链接脚本中明确指定,并且Bootloader在跳转到Application之前,需要重新设置中断向量表偏移。
我记得有一次调试,发现Application跑起来后中断不响应。查了半天,原来是中断向量表偏移没设置。代码里加一行就解决了:
// 设置中断向量表偏移到Application起始地址
SCB->VTOR = APPLICATION_START_ADDRESS;
嗯,这种小细节,不注意就会浪费大把时间。
2.4 实际项目中的分区策略
最后说说我在实际项目中怎么分区的。不同的蓝牙芯片,分区策略略有不同,但核心思路是一致的:
- Bootloader区:固定大小,一般32KB或64KB。包含启动代码、OTA升级逻辑、Flash驱动。
- Application区:剩余Flash空间的大部分。包含蓝牙协议栈、应用代码。
- 参数存储区:在Flash末尾留一块区域,存放配对信息、设备配置等。这个区域在OTA升级时不能擦除。
- OTA临时区:如果支持双区OTA,还需要一个备份区来存放新固件。
我个人的建议:分区时一定要考虑未来固件大小的增长。我见过一个产品,刚开始Application只用了80KB,分区时给了100KB。结果两年后功能增加了,固件膨胀到120KB,不得不重新设计分区。所以,我一般会预留30%-50%的余量。
好了,关于蓝牙芯片的存储架构,今天就聊这么多。记住一句话:Flash是存代码的,RAM是跑代码的;Bootloader是保命的,Application是干活的;地址映射是连接物理和逻辑的桥梁。搞清楚了这些,后面做OTA升级就顺理成章了。