第三章 Flash底层驱动开发:Flash模块初始化、擦除与编程操作
好,咱们进入第三章。这一章可以说是Bootloader开发中最核心、也最容易出问题的地方——Flash底层驱动。说白了,就是怎么让芯片老老实实地把程序写进去、擦干净、再读出来验证。我在TC3xx上折腾Flash驱动已经好几年了,踩过的坑能绕开发板三圈。今天就把这些经验掰开了揉碎了讲给你听。
3.1 Flash模块初始化
Flash初始化这事儿,看着简单,其实门道不少。很多新手上来就调用初始化函数,结果后面读写总出怪问题。我个人的习惯是:先看芯片手册里的电源时序要求,再动手写代码。
TC3xx的Flash模块初始化,主要做这几件事:
- 时钟使能:Flash需要独立的时钟源,通常是fFlash时钟。我记得第一次调试时忘了开时钟,结果读出来的全是0xFF,折腾了半天。
- 等待状态配置:根据主频设置正确的等待周期(Wait State)。频率越高,等待周期越多。这个配错了,轻则读写不稳定,重则直接HardFault。
- 电源与参考电压:TC3xx内部有电荷泵,需要稳定供电。我建议初始化时先读一下电源状态寄存器,确认电压稳定再往下走。
- 错误中断使能:ECC错误、操作完成中断等,建议一开始就配好。别等出问题了再回头查,那时候就晚了。
核心要点:初始化顺序不能乱。先时钟,再电源,然后等待状态,最后使能中断。我见过有人把顺序搞反了,结果芯片直接锁死,只能重新上电。
下面给个简化的初始化代码示例:
void Flash_Init(void)
{
/* 1. 使能Flash时钟 */
SCU_CLK->CLKCR |= SCU_CLK_CLKCR_FFLASHEN_MASK;
/* 2. 等待电源稳定 */
while(!(SCU_PWR->PWRSTAT & SCU_PWR_PWRSTAT_FLASHOK_MASK));
/* 3. 配置等待状态 - 假设主频200MHz */
FLASH0->FCON = 0x0002; /* 2个等待周期 */
/* 4. 使能ECC错误中断 */
FLASH0->ERRINT |= FLASH_ERRINT_ECCERRIE_MASK;
/* 5. 解锁Flash控制器 */
FLASH0->FCON |= FLASH_FCON_FLASHEN_MASK;
}
小技巧:初始化完成后,建议读一次Flash的ID寄存器,确认通信正常。这步不费事,但能帮你快速定位硬件问题。
3.2 擦除操作:Page / Sector / Bank
擦除是Flash操作里最耗时的环节。TC3xx支持三种粒度的擦除:Page(页)、Sector(扇区)、Bank(块)。你想想看,擦除粒度越小,灵活性越高,但耗时也越长。怎么选?看你的应用场景。
| 擦除类型 | 大小 | 典型耗时 | 适用场景 |
|---|---|---|---|
| Page擦除 | 16KB | ~20ms | 小范围更新,比如配置参数 |
| Sector擦除 | 256KB | ~100ms | 中等规模更新,比如单个应用模块 |
| Bank擦除 | 2MB~4MB | ~500ms | 整片擦除,比如Bootloader升级 |
我个人在实际项目中,Bootloader升级时一般用Sector擦除。为什么?因为Page擦除太慢,Bank擦除又太粗暴——万一升级失败,整个Bank的数据都没了,恢复起来很麻烦。
擦除操作的流程其实很固定:
- 解锁Flash控制器(写解锁序列)
- 配置擦除类型和起始地址
- 启动擦除命令
- 轮询状态寄存器,等待擦除完成
- 验证擦除结果(全读成0xFF才算成功)
我曾经踩过的坑:有一次擦除Sector后没做验证,直接开始编程。结果写到一半发现有些地址擦除不干净,数据写不进去。从那以后,我每次擦除完都会读一遍整个Sector,确认全是0xFF才继续。这步不能省。
代码示例:
uint8_t Flash_EraseSector(uint32_t addr)
{
uint32_t status;
/* 解锁Flash */
FLASH0->FCON |= FLASH_FCON_UNLOCK_MASK;
/* 配置擦除地址和类型 */
FLASH0->MAR = addr;
FLASH0->FCON = (FLASH0->FCON & ~0x07) | 0x02; /* Sector擦除 */
/* 启动擦除 */
FLASH0->FCON |= FLASH_FCON_START_MASK;
/* 等待完成 */
do {
status = FLASH0->FSTAT;
} while(status & FLASH_FSTAT_BUSY_MASK);
/* 验证擦除结果 */
for(uint32_t i = 0; i < SECTOR_SIZE; i += 4) {
if(*(uint32_t*)(addr + i) != 0xFFFFFFFF) {
return 1; /* 擦除失败 */
}
}
return 0; /* 成功 */
}
3.3 编程操作:Write / Verify
编程操作,说白了就是把数据写进Flash。TC3xx支持多种编程方式:单字编程(32位)、双字编程(64位)、以及突发编程(Burst)。我建议在Bootloader里用双字编程,效率高,而且能配合ECC校验。
编程的流程比擦除稍微复杂一点:
- 地址对齐:TC3xx要求编程地址必须按双字(8字节)对齐。不对齐?直接报错。我刚开始做的时候就被这个坑过,写进去的数据总是错位。
- 数据缓冲:先把要写的数据放到一个临时缓冲区,凑够8字节再一次性写入。别一个一个字节写,效率太低。
- 启动编程:写命令寄存器,触发编程操作。
- 等待完成:轮询状态位,或者等中断。
- 读回验证:把刚写进去的数据读出来,跟原始数据比对。这步不能省!
我的经验:验证的时候,别只比对一次。我习惯读两遍:第一遍快速比对,第二遍带ECC校验读。为什么?因为有些错误是偶发的,读一次可能发现不了。
代码示例:
uint8_t Flash_ProgramDoubleWord(uint32_t addr, uint64_t data)
{
uint32_t status;
uint64_t verify_data;
/* 检查地址对齐 */
if(addr & 0x07) return 1; /* 未对齐 */
/* 解锁并配置编程模式 */
FLASH0->FCON |= FLASH_FCON_UNLOCK_MASK;
FLASH0->MAR = addr;
/* 写入数据 */
*(uint64_t*)addr = data;
/* 启动编程 */
FLASH0->FCON |= FLASH_FCON_START_MASK;
/* 等待完成 */
do {
status = FLASH0->FSTAT;
} while(status & FLASH_FSTAT_BUSY_MASK);
/* 读回验证 */
verify_data = *(uint64_t*)addr;
if(verify_data != data) {
return 2; /* 数据不一致 */
}
return 0;
}
3.4 ECC与纠错机制
ECC(Error Correction Code)是TC3xx Flash的一大特色。说白了,就是每写一段数据,硬件自动算出一段校验码存起来。读的时候再算一遍,比对一下。如果发现有一位错了,硬件能自动纠正;两位错了,就报错给你。
TC3xx的ECC机制是这样的:
- 每64位数据配8位ECC码:能纠正1位错误,检测2位错误。
- ECC错误分两种:可纠正错误(Correctable Error)和不可纠正错误(Uncorrectable Error)。
- 错误处理策略:可纠正的错误,我建议记录日志,但不中断操作;不可纠正的错误,必须立即停止,并触发安全机制。
注意:ECC校验是硬件自动完成的,但你需要配置错误处理的中断服务函数。我见过有人没配中断,结果ECC错误一直累积,最后Flash彻底锁死。
ECC错误处理的中断示例:
void Flash_ECCErrorHandler(void)
{
uint32_t err_type;
uint32_t err_addr;
/* 读取错误类型和地址 */
err_type = FLASH0->ERRSTAT;
err_addr = FLASH0->ERRADDR;
if(err_type & FLASH_ERRSTAT_CORRERR_MASK) {
/* 可纠正错误 - 记录日志 */
Log_Write("ECC correctable error at 0x%08X", err_addr);
/* 清除错误标志 */
FLASH0->ERRSTAT |= FLASH_ERRSTAT_CORRERR_MASK;
}
if(err_type & FLASH_ERRSTAT_UNCORRERR_MASK) {
/* 不可纠正错误 - 紧急处理 */
Log_Write("FATAL: ECC uncorrectable error at 0x%08X", err_addr);
/* 触发安全复位 */
SCU_RST->RSTCON = 0xDEADBEEF;
}
}
说到ECC,我想起一个真实案例。有一次客户反馈说设备运行一段时间后程序就跑飞了。我查了很久,最后发现是Flash里有个bit翻转了,但ECC中断没配好,系统没发现这个错误。从那以后,我每个项目的ECC中断配置都会反复检查三遍。
建议:在Bootloader里加一个ECC自检功能。每次启动时,随机读几个Flash地址,检查ECC状态。这样能提前发现潜在的硬件问题,避免运行时出故障。
好了,这一章的内容就到这儿。Flash驱动开发,说白了就是跟时序和状态机打交道。你只要把初始化、擦除、编程、ECC这几个环节搞清楚了,后面写Bootloader就顺了。下一章咱们讲Flash分区管理和数据保护,到时候会用到今天讲的这些基础操作。