2. Flash驱动移植

任务目标

在目标MCU(以STM32F407为例)上完成Flash驱动的移植,实现扇区擦除、数据写入和读取验证三个核心操作,为后续Bootloader的跳转与APP升级提供底层支持。

环境与依赖

  • 硬件:STM32F407VET6开发板(内置Flash,1MB,扇区大小16KB/64KB不等)
  • 工具链:arm-none-eabi-gcc + STM32CubeF4标准外设库(或HAL库)
  • 调试方式:串口打印(USART2,波特率115200)

驱动接口设计

Flash驱动需要向Bootloader上层提供三个基础API,屏蔽不同MCU的寄存器差异:

void Flash_Init(void);                    // 初始化Flash控制器
int  Flash_Erase(uint32_t addr, uint32_t len);  // 按扇区擦除,返回0成功
int  Flash_Write(uint32_t addr, const uint8_t *data, uint32_t len); // 写入,返回0成功
void Flash_Read(uint32_t addr, uint8_t *buf, uint32_t len);  // 读取

关键完整代码

以下代码基于STM32标准外设库实现,核心逻辑包含解锁、等待忙、扇区擦除和半字编程。移植到其他MCU时,仅需替换底层的寄存器操作和扇区映射表。

#include "stm32f4xx.h"
#include <string.h>

/* 扇区起始地址映射表(STM32F407,1MB Flash) */
static const uint32_t sector_base[] = {
    0x08000000, 0x08004000, 0x08008000, 0x0800C000,
    0x08010000, 0x08020000, 0x08040000, 0x08060000,
    0x08080000, 0x080A0000, 0x080C0000, 0x080E0000
};
#define SECTOR_SIZE_16K  0x4000
#define SECTOR_SIZE_64K  0x10000
#define FLASH_KEY1       0x45670123
#define FLASH_KEY2       0xCDEF89AB

void Flash_Init(void) {
    /* 解锁Flash */
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
}

static int get_sector(uint32_t addr) {
    for (int i = 0; i < 12; i++) {
        if (addr >= sector_base[i] && addr < sector_base[i+1])
            return i;
    }
    return -1;
}

int Flash_Erase(uint32_t addr, uint32_t len) {
    uint32_t end = addr + len;
    while (addr < end) {
        int sector = get_sector(addr);
        if (sector < 0) return -1;
        /* 等待忙 */
        while (FLASH->SR & FLASH_SR_BSY);
        /* 选择扇区并开始擦除 */
        FLASH->CR |= FLASH_CR_SER;
        FLASH->CR |= (sector << 3) & FLASH_CR_SNB;
        FLASH->CR |= FLASH_CR_STRT;
        while (FLASH->SR & FLASH_SR_BSY);
        /* 检查错误 */
        if (FLASH->SR & (FLASH_SR_PGSERR | FLASH_SR_PGPERR)) return -2;
        FLASH->CR &= ~FLASH_CR_SER;
        /* 计算下一扇区起始地址 */
        uint32_t sector_size = (sector < 4) ? SECTOR_SIZE_16K : SECTOR_SIZE_64K;
        addr = sector_base[sector] + sector_size;
    }
    return 0;
}

int Flash_Write(uint32_t addr, const uint8_t *data, uint32_t len) {
    if (addr % 2) return -1;  /* 半字对齐 */
    FLASH->CR |= FLASH_CR_PG;
    for (uint32_t i = 0; i < len; i += 2) {
        while (FLASH->SR & FLASH_SR_BSY);
        uint16_t halfword = data[i] | (data[i+1] << 8);
        *(volatile uint16_t*)addr = halfword;
        while (FLASH->SR & FLASH_SR_BSY);
        if (FLASH->SR & FLASH_SR_PGSERR) {
            FLASH->CR &= ~FLASH_CR_PG;
            return -2;
        }
        addr += 2;
    }
    FLASH->CR &= ~FLASH_CR_PG;
    return 0;
}

void Flash_Read(uint32_t addr, uint8_t *buf, uint32_t len) {
    memcpy(buf, (uint8_t*)addr, len);
}

最小验证入口

以下代码演示擦除→写入→读取的完整闭环,通过串口打印结果。测试数据使用0x5A5A模式,便于肉眼比对。

#include <stdio.h>
#include "stm32f4xx.h"

/* USART2发送字符串(省略初始化,使用标准库printf重定向) */
extern void USART2_Init(void);

int main(void) {
    USART2_Init();
    Flash_Init();

    uint32_t test_addr = 0x08040000;  /* 第6扇区,64KB */
    uint8_t write_buf[256];
    uint8_t read_buf[256];

    /* 填充测试数据 */
    for (int i = 0; i < 256; i++) write_buf[i] = 0x5A;

    printf("Erase result: %d\r\n", Flash_Erase(test_addr, 256));
    printf("Write result: %d\r\n", Flash_Write(test_addr, write_buf, 256));
    Flash_Read(test_addr, read_buf, 256);

    int ok = (memcmp(write_buf, read_buf, 256) == 0);
    printf("Verify: %s\r\n", ok ? "PASS" : "FAIL");

    while (1);
}

运行与验证方法

  1. 将代码编译后烧录到STM32F407,打开串口终端(115200, 8N1)。
  2. 复位开发板,观察串口输出:Erase result: 0、Write result: 0、Verify: PASS。
  3. 若输出FAIL,检查Flash地址是否越界(0x08040000位于第6扇区,安全可写),或确认擦除时扇区选择是否正确。
  4. 通过ST-Link的Memory窗口读取0x08040000地址,应看到连续256字节的0x5A。

通过标准:串口打印Verify: PASS,且Memory窗口数据与写入一致,即证明Flash驱动移植成功。

常见问题

  • 擦除失败(返回-2):检查Flash是否已锁定(调用Flash_Init解锁),或目标地址是否超出芯片Flash范围。
  • 写入失败(返回-2):确认写入地址未跨越扇区边界(本驱动不处理跨扇区写入),且地址为2字节对齐。
  • 移植到其他MCU:仅需修改sector_base表、扇区大小定义和底层寄存器操作(如NXP的FTFE、瑞萨的FCU),API保持不变。