第三章 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的数据都没了,恢复起来很麻烦。

擦除操作的流程其实很固定:

  1. 解锁Flash控制器(写解锁序列)
  2. 配置擦除类型和起始地址
  3. 启动擦除命令
  4. 轮询状态寄存器,等待擦除完成
  5. 验证擦除结果(全读成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分区管理和数据保护,到时候会用到今天讲的这些基础操作。