2. Flash驱动移植与验证

任务目标

在目标MCU(以STM32F407为例)上完成Flash驱动的移植,实现扇区擦除、双字编程与读取验证三个核心操作,并通过写入已知数据后回读比对的方式验证驱动正确性。本案例聚焦驱动层实现,不涉及Bootloader的上层跳转逻辑。

环境依赖

  • 硬件:STM32F407开发板(板载1MB Flash,扇区大小16KB/128KB不等)
  • 软件:STM32CubeIDE 或 Keil MDK-ARM(本文以寄存器操作实现,不依赖HAL库)
  • 调试方式:串口打印或JTAG/SWD调试器观察内存

关键完整代码

#include "stm32f4xx.h"

/* Flash寄存器基地址 */
#define FLASH_KEY1         0x45670123U
#define FLASH_KEY2         0xCDEF89ABU
#define FLASH_OPTKEY1      0x08192A3BU
#define FLASH_OPTKEY2      0x4C5D6E7FU

/* 等待Flash操作完成 */
static void Flash_WaitBusy(void) {
    while ((FLASH->SR & FLASH_SR_BSY) != 0) {
        /* 等待BSY位清零 */
    }
}

/* 解锁Flash控制寄存器 */
static void Flash_Unlock(void) {
    if ((FLASH->CR & FLASH_CR_LOCK) != 0) {
        FLASH->KEYR = FLASH_KEY1;
        FLASH->KEYR = FLASH_KEY2;
    }
}

/* 锁定Flash控制寄存器 */
static void Flash_Lock(void) {
    FLASH->CR |= FLASH_CR_LOCK;
}

/* 擦除指定扇区(扇区编号0~11) */
void Flash_EraseSector(uint8_t sector_num) {
    Flash_Unlock();
    Flash_WaitBusy();
    
    /* 设置扇区号与擦除使能 */
    FLASH->CR &= ~FLASH_CR_SNB_Msk;
    FLASH->CR |= ((uint32_t)sector_num << FLASH_CR_SNB_Pos) | FLASH_CR_SER;
    FLASH->CR |= FLASH_CR_STRT;
    
    Flash_WaitBusy();
    
    /* 清除擦除标志位 */
    FLASH->CR &= ~(FLASH_CR_SER | FLASH_CR_SNB_Msk);
    Flash_Lock();
}

/* 编程64位双字数据到指定地址(地址必须8字节对齐) */
void Flash_ProgramDoubleWord(uint32_t addr, uint64_t data) {
    Flash_Unlock();
    Flash_WaitBusy();
    
    /* 设置编程模式 */
    FLASH->CR |= FLASH_CR_PG;
    
    /* 写入低32位,再写高32位(触发编程) */
    *(volatile uint32_t*)addr = (uint32_t)(data & 0xFFFFFFFF);
    *(volatile uint32_t*)(addr + 4) = (uint32_t)(data >> 32);
    
    Flash_WaitBusy();
    
    /* 关闭编程模式 */
    FLASH->CR &= ~FLASH_CR_PG;
    Flash_Lock();
}

/* 从指定地址读取64位数据 */
uint64_t Flash_ReadDoubleWord(uint32_t addr) {
    uint64_t val;
    val = *(volatile uint32_t*)addr;
    val |= ((uint64_t)(*(volatile uint32_t*)(addr + 4)) << 32);
    return val;
}

/* 验证函数:擦除-写入-回读比对 */
int Flash_Verify(void) {
    uint32_t test_addr = 0x08020000;  /* 扇区4起始地址(16KB扇区) */
    uint64_t write_data = 0x0123456789ABCDEFULL;
    uint64_t read_data;
    
    /* 1. 擦除扇区 */
    Flash_EraseSector(4);
    
    /* 2. 验证擦除后内容为0xFF */
    read_data = Flash_ReadDoubleWord(test_addr);
    if (read_data != 0xFFFFFFFFFFFFFFFFULL) {
        return -1;  /* 擦除失败 */
    }
    
    /* 3. 写入测试数据 */
    Flash_ProgramDoubleWord(test_addr, write_data);
    
    /* 4. 回读比对 */
    read_data = Flash_ReadDoubleWord(test_addr);
    if (read_data != write_data) {
        return -2;  /* 编程或读取失败 */
    }
    
    return 0;  /* 全部通过 */
}

/* 主函数调用示例 */
int main(void) {
    int result;
    
    /* 初始化系统时钟(略,使用默认配置) */
    result = Flash_Verify();
    
    if (result == 0) {
        /* 验证通过:此处可设置断点或串口输出 */
        while (1);
    } else {
        /* 验证失败:错误码为result */
        while (1);
    }
}

运行与验证方法

  1. 将上述代码加入STM32F407工程,编译并下载到开发板。
  2. 在Flash_Verify()返回后设置断点,或通过调试器观察result变量值。
  3. 判定标准:
    • result == 0:擦除后全0xFF、写入数据回读一致,驱动功能正确。
    • result == -1:擦除操作未生效,检查扇区号映射或电源电压。
    • result == -2:编程或读取路径有误,检查地址对齐与编程时序。

常见问题

  • 编程后数据全为0xFF:确认目标地址在擦除后未被写保护,且编程前已执行扇区擦除。
  • HardFault异常:检查编程地址是否越界(F407用户Flash区为0x08000000~0x080FFFFF),以及是否误操作了OTP或选项字节区域。
  • 扇区编号混淆:F407的扇区0~3为16KB,扇区4为64KB,扇区5~11为128KB。擦除前务必确认所选扇区与地址范围匹配。