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 实际项目中的分区策略

最后说说我在实际项目中怎么分区的。不同的蓝牙芯片,分区策略略有不同,但核心思路是一致的:

  1. Bootloader区:固定大小,一般32KB或64KB。包含启动代码、OTA升级逻辑、Flash驱动。
  2. Application区:剩余Flash空间的大部分。包含蓝牙协议栈、应用代码。
  3. 参数存储区:在Flash末尾留一块区域,存放配对信息、设备配置等。这个区域在OTA升级时不能擦除。
  4. OTA临时区:如果支持双区OTA,还需要一个备份区来存放新固件。

我个人的建议:分区时一定要考虑未来固件大小的增长。我见过一个产品,刚开始Application只用了80KB,分区时给了100KB。结果两年后功能增加了,固件膨胀到120KB,不得不重新设计分区。所以,我一般会预留30%-50%的余量。

好了,关于蓝牙芯片的存储架构,今天就聊这么多。记住一句话:Flash是存代码的,RAM是跑代码的;Bootloader是保命的,Application是干活的;地址映射是连接物理和逻辑的桥梁。搞清楚了这些,后面做OTA升级就顺理成章了。