一、内部 Flash 简介
CW32F030 是武汉芯源半导体推出的一款基于 ARM Cortex-M0+ 内核的 32 位微控制器,其中 CW32F030C8T6 型号内置了 64KB 主 Flash 存储器,同时配有 8KB SRAM 和 128 字节一次性可编程存储器(OTP)。内部 Flash 是程序存储的核心区域,CPU 直接从 Flash 取指执行,数据手册标注其数据保持时间可达 25 年(@85℃),擦写寿命通常在万次级别的量级,足以满足绝大多数工业和消费类应用的需求。
从存储结构来看,CW32F030 的内部 Flash 以页(Page)为最小擦除单位,每页 512 字节。这意味着无论你只想改写其中几个字节,都必须先对整个 512 字节的页执行擦除操作,然后再重新写入全部数据。这种"先擦后写、按页擦除"的特性是所有 NOR Flash 的共同特点,也是做 Flash 参数存储设计时必须牢记的基本约束。
除了主存储区之外,CW32F030 还提供了选项字节(Option Bytes)区域,用于配置写保护、读保护、硬件启动模式等系统级参数。通过选项字节中的 Lock 位,可以将 Flash 的特定扇区设置为只读保护,防止程序跑飞或误操作导致关键固件被意外擦除。此外,芯片出厂时还内置了一段 BootLoader 程序,支持通过 UART 串口进入 ISP(在系统编程)模式进行固件烧写。
二、Flash 的典型应用场景
内部 Flash 在 CW32F030 上的用途远不止存储程序代码,合理利用 Flash 的剩余空间可以实现很多实用的功能。
第一,存储用户程序代码。这是 Flash 最基本的功能,64KB 的容量对于大多数控制类应用——如小家电控制板、传感器采集模块、LED 调光驱动、简单的电机控制器——来说完全足够。代码从 Flash 直接执行,无需额外的外部存储芯片,降低了 BOM 成本和 PCB 布线复杂度。
第二,存储运行参数和校准数据。产品在出厂校准或用户使用过程中产生的参数,比如 ADC 校准值、PID 控制参数、设备地址、亮度偏好等,都可以存放在 Flash 的最后几页。掉电后数据不会丢失,下次上电时程序再从 Flash 读回这些参数。需要注意的是,由于 Flash 是按页擦除的,设计参数存储结构时应该让每个参数页独立完整,避免两个无关参数挤在同一页导致互相影响。
第三,实现固件升级(IAP)。利用芯片内置的 BootLoader 或者自己在 Flash 中划分一段升级区,可以实现通过串口、CAN 或其他通信接口在产品现场升级固件。典型做法是将 Flash 划分为 BootLoader 区、App 区和备份区,升级时把新固件先写到备份区,校验通过后再跳转覆盖 App 区,升级失败还能回滚。
第四,利用 OTP 存储一次性信息。那 128 字节的 OTP 区域适合存放出厂序列号、唯一设备 ID、密钥等一旦写入就不希望再被修改的数据,写死之后永久生效,安全性和可靠性比普通 Flash 页更高。
三、Flash 操作的基本流程
对 CW32F030 内部 Flash 进行编程或擦除,必须严格按照手册规定的时序操作。Flash 控制器默认处于锁定状态,任何直接写寄存器的操作都会被忽略,目的是防止 CPU 跑飞时误改 Flash。
解锁的第一步是向 Flash 密钥寄存器依次写入两个特定的密钥值,解锁成功后才能操作控制寄存器。解锁之后,根据需要执行页擦除或半字编程操作。写入数据时,CW32F030 的 Flash 编程粒度通常是半字(16 位),不能像 RAM 那样随意按字节或字写入。写完之后必须等待 busy 标志清零,确认编程完成后再进行下一步操作。
每次操作完成后,建议重新锁定 Flash 控制器,防止后续代码异常时意外擦写 Flash。这个"解锁—操作—等待—锁定"的流程要养成习惯,不能图省事长期保持解锁状态。
四、调试心得与踩坑记录4.1 Flash 等待周期必须随主频同步配置
这是 CW32F030 开发中最容易踩的坑之一。CW32F030 的 Flash 在最高 24MHz 的系统时钟下可以零等待访问,但一旦 HCLK 超过 24MHz,就必须通过 Flash 控制寄存器插入等待周期,否则 CPU 从 Flash 读取的指令或数据会出错,轻则程序跑飞,重则连调试器都连不上。
具体的配置规则是:HCLK 不超过 24MHz 时等待周期设为 0;HCLK 在 24MHz 到 48MHz 之间需要设为 2 个等待周期;HCLK 超过 48MHz 则需要 3 个等待周期。很多人配置系统时钟时先切到了高速外部晶振或 PLL,结果忘了改 Flash 等待周期,程序一运行就死机,还以为是时钟配置有问题,其实根源在 Flash 这边。正确的顺序应该是:先降速或先插等待周期,再切换时钟源,保证任意时刻 Flash 访问都在其允许的速度范围内。
4.2 擦除单位是页,不是字节
另一个常见误区是想当然地按字节改写 Flash。Flash 的物理特性决定了它只能把位从 1 写成 0,不能反过来把 0 写成 1;要恢复成 1 必须整页擦除。所以如果只想更新 Flash 中的几个字节,正确做法是:先读出整页 512 字节到 RAM,修改需要改的那几个字节,然后擦除该页,最后把整个缓冲写回。如果直接跳过读旧值这一步,写回去的数据除了你修改的部分,其他区域全变成 0xFF,等于把同页的其他数据全冲掉了。
4.3 VDDA 悬空会导致烧录失败
调试烧录阶段还有一个隐蔽问题:CW32F030 的 VDDA 引脚是模拟电源,负责给 ADC、内部 RC 振荡器、PLL 和电源监控电路供电。即使你的产品完全不用 ADC,VDDA 也必须接到 VDD 上。如果 VDDA 悬空,芯片内部的上电复位和电源监控电路工作不正常,表现出来的现象就是烧录器完全识别不到芯片,SWD 连接失败。这个问题在自己画的最小系统板上特别常见,很多人看到 VDDA 旁边没接电容就觉得可以悬空,其实不行,飞一根线到 VDD 立刻就能烧录。
4.4 SWD 引脚复用要注意
CW32F030 的 SWD 调试接口是 PA13(SWDIO)和 PA14(SWCLK),这两个引脚默认就是调试功能。但如果在应用代码里把它们配置成了普通 GPIO 使用,调试器就会断开连接。遇到"烧录一次之后再也连不上"的情况,大概率是代码里把 PA13/PA14 当普通 IO 用了。解决办法是按住复位键的同时让调试器连接,在芯片释放复位的瞬间抢在用户代码运行之前接管调试接口,然后把引脚功能改回来。做产品设计时,如果确实需要用这两个引脚做普通 IO,建议预留一个独立的复位按键,方便后续恢复调试。
4.5 参数存储的磨损均衡
如果产品需要频繁保存运行参数,比如每几秒就往 Flash 写一次累计电量或运行时间,就必须考虑 Flash 的擦写寿命。单个页擦写次数有限,长期频繁写同一页会导致该页提前失效。实用的做法是在 Flash 中划出多个备份页,每次写入时轮询不同的页,或者采用"日志式"追加写入——每次新参数写到下一个空位置,写满一页后再擦除重写。这样可以把擦写次数分散到多个页上,显著延长 Flash 的使用寿命。4.6 选项字节写保护的使用
关键代码区可以通过选项字节设置写保护,一旦锁定,即使程序跑飞也无法擦除受保护区域的内容。这对于量产产品来说是一道重要的安全屏障。但要注意,选项字节的修改次数同样有限,而且某些保护一旦设置(比如读保护 RDP),解除时可能会触发全片擦除。开发调试阶段不建议过早开启读保护,等产品功能完全验证通过、准备量产时再根据需要配置。
4.7 内部 Flash 读写函数实现
下面给出一组可直接在 CW32F030 工程中使用的内部 Flash 读写函数示例,基于武汉芯源官方标准外设库(Firmware Library)的接口风格编写,涵盖解锁、页擦除、半字写入和整页读取四个基本操作,并在此基础上封装了一个实用的参数读写函数。
实际使用中经常需要把一段数据整页写入。由于 Flash 只能半字编程,这里把任意长度的数据按半字对齐写入,不足一个半字的尾部自动补 0xFF:
// 把 buf 中的 len 字节数据写入指定地址(自动按半字对齐)
// 返回值:0 = 成功,非 0 = 失败
uint32_t Flash_WritePage(uint32_t addr, const uint8_t *buf, uint32_t len)
{
uint32_t i;
uint32_t status = 0;
if (len > FLASH_PAGE_SIZE) return 1; // 超过页大小,拒绝写入
FLASH_Unlock();
// 按半字(2 字节)为单位循环写入
for (i = 0; i + 1 < len; i += 2)
{
uint16_t halfWord = (uint16_t)buf[i] | ((uint16_t)buf[i + 1] << 8);
status = FLASH_ProgramHalfWord(addr + i, halfWord);
if (status != 0) break;
}
// 如果 len 是奇数,最后单独写一个字节(高字节补 0xFF)
if (status == 0 && (len % 2) != 0)
{
uint16_t lastWord = (uint16_t)buf[len - 1] | 0xFF00;
status = FLASH_ProgramHalfWord(addr + len - 1, lastWord);
}
FLASH_Lock();
return status;
}Flash 的读取非常简单,它映射在 CPU 的统一地址空间上,直接用指针按地址读即可,不需要任何解锁或特殊操作:
// 从指定地址读取 len 字节数据到 buf
void Flash_ReadData(uint32_t addr, uint8_t *buf, uint32_t len)
{
for (uint32_t i = 0; i < len; i++)
{
buf[i] = *(__IO uint8_t *)(addr + i);
}
}使用注意事项:调用 Flash 擦除和写入函数时,程序可以在 Flash 中正常执行(CW32 的 Flash 控制器支持"运行时自编程"),但要确保擦写过程中不要中断——关闭总中断或至少把中断服务函数搬到 RAM 中执行,否则擦写 Flash 期间如果 CPU 再从 Flash 取中断向量,会导致总线错误。另外,写入完成后建议回读一遍数据做校验,确认写进去的内容和预期一致,避免因为 Flash 老化或电压不稳导致写入失败而不知情。
五、小结
CW32F030 的内部 Flash 虽然容量不算大,但功能完整,操作流程清晰。掌握它的关键在于:理解"按页擦除、先擦后写"的物理限制,牢记高速运行时必须配置 Flash 等待周期,烧录前确保 VDDA 供电正常,以及设计参数存储时做好磨损均衡。把这几点处理好,Flash 部分基本不会出大问题。开发过程中遇到异常,可以先用调试器读一下 Flash 状态寄存器,看看 busy 位、错误标志位的状态,往往能快速定位问题所在。
我要赚赏金
