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);
}
运行与验证方法
- 将代码编译后烧录到STM32F407,打开串口终端(115200, 8N1)。
- 复位开发板,观察串口输出:
Erase result: 0、Write result: 0、Verify: PASS。 - 若输出
FAIL,检查Flash地址是否越界(0x08040000位于第6扇区,安全可写),或确认擦除时扇区选择是否正确。 - 通过ST-Link的Memory窗口读取0x08040000地址,应看到连续256字节的0x5A。
通过标准:串口打印Verify: PASS,且Memory窗口数据与写入一致,即证明Flash驱动移植成功。
常见问题
- 擦除失败(返回-2):检查Flash是否已锁定(调用Flash_Init解锁),或目标地址是否超出芯片Flash范围。
- 写入失败(返回-2):确认写入地址未跨越扇区边界(本驱动不处理跨扇区写入),且地址为2字节对齐。
- 移植到其他MCU:仅需修改
sector_base表、扇区大小定义和底层寄存器操作(如NXP的FTFE、瑞萨的FCU),API保持不变。