2. Flash扇区擦除与写入驱动
任务目标
编写STM32F103系列内部Flash驱动,实现按扇区擦除(1KB/扇区)与按页写入(256B/页),并通过回读校验验证数据正确性。驱动直接操作Flash寄存器,不依赖HAL库。
硬件与依赖
- MCU:STM32F103C8T6(主频72MHz,Flash 64KB)
- 寄存器定义:使用CMSIS头文件
stm32f10x.h - 调试输出:UART1(PA9/PA10,115200-8-N-1),用于打印验证结果
关键实现
Flash操作必须遵循解锁→擦除/写入→加锁的顺序。写入前需确认目标地址已擦除(值为0xFF)。本驱动将Flash划分为两个区域:应用区(0x08000000~0x0800F800,共62KB)和Bootloader参数区(0x0800F800~0x0800FC00,1KB),本案例操作参数区,避免破坏应用代码。
完整代码
// flash_drv.c - STM32F103内部Flash扇区擦除与页写入驱动
#include "stm32f10x.h"
#include <string.h>
#define FLASH_PARAM_BASE 0x0800F800 // 参数区起始地址(第62扇区)
#define FLASH_PAGE_SIZE 256 // 页大小(半页编程)
#define FLASH_SECTOR_SIZE 1024 // 扇区大小
// Flash解锁
static void Flash_Unlock(void) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
// Flash加锁
static void Flash_Lock(void) {
FLASH->CR |= FLASH_CR_LOCK;
}
// 等待操作完成
static void Flash_WaitBusy(void) {
while (FLASH->SR & FLASH_SR_BSY);
}
// 擦除整个扇区(1KB)
uint8_t Flash_EraseSector(uint32_t sector_addr) {
if (sector_addr % FLASH_SECTOR_SIZE != 0) return 1; // 地址未对齐
Flash_Unlock();
Flash_WaitBusy();
// 设置擦除位和起始地址
FLASH->CR |= FLASH_CR_PER;
FLASH->AR = sector_addr;
FLASH->CR |= FLASH_CR_STRT;
Flash_WaitBusy();
FLASH->CR &= ~FLASH_CR_PER; // 清除擦除位
// 验证擦除结果
uint32_t *p = (uint32_t*)sector_addr;
for (int i = 0; i < FLASH_SECTOR_SIZE/4; i++) {
if (p[i] != 0xFFFFFFFF) {
Flash_Lock();
return 2; // 擦除失败
}
}
Flash_Lock();
return 0;
}
// 写入一页(256字节),addr必须256字节对齐
uint8_t Flash_WritePage(uint32_t addr, uint8_t *data, uint16_t len) {
if (addr % FLASH_PAGE_SIZE != 0) return 1;
if (len > FLASH_PAGE_SIZE) return 2;
Flash_Unlock();
Flash_WaitBusy();
// 半页编程模式
FLASH->CR |= FLASH_CR_PG;
uint16_t half_word_count = (len + 1) / 2; // 向上取整到半字
uint16_t *src = (uint16_t*)data;
uint16_t *dst = (uint16_t*)addr;
for (int i = 0; i < half_word_count; i++) {
*dst++ = *src++;
Flash_WaitBusy();
if (FLASH->SR & FLASH_SR_PGERR) {
FLASH->SR = FLASH_SR_PGERR; // 清除错误
FLASH->CR &= ~FLASH_CR_PG;
Flash_Lock();
return 3; // 编程错误
}
}
FLASH->CR &= ~FLASH_CR_PG;
Flash_Lock();
return 0;
}
// 回读校验
uint8_t Flash_Verify(uint32_t addr, uint8_t *expected, uint16_t len) {
return memcmp((void*)addr, expected, len) == 0 ? 0 : 1;
}
// 测试主函数
void Flash_Test(void) {
uint8_t write_buf[256];
uint8_t test_pattern = 0xA5;
// 填充测试数据
for (int i = 0; i < 256; i++) {
write_buf[i] = test_pattern + i;
}
// 1. 擦除参数区扇区
uint8_t err = Flash_EraseSector(FLASH_PARAM_BASE);
if (err) {
printf("擦除失败, err=%d\r\n", err);
return;
}
printf("扇区擦除成功\r\n");
// 2. 写入第一页
err = Flash_WritePage(FLASH_PARAM_BASE, write_buf, 256);
if (err) {
printf("写入失败, err=%d\r\n", err);
return;
}
printf("页写入成功\r\n");
// 3. 回读校验
err = Flash_Verify(FLASH_PARAM_BASE, write_buf, 256);
if (err == 0) {
printf("数据校验通过\r\n");
} else {
printf("数据校验失败\r\n");
}
// 4. 打印前16字节确认
uint8_t *read_ptr = (uint8_t*)FLASH_PARAM_BASE;
printf("回读数据: ");
for (int i = 0; i < 16; i++) {
printf("%02X ", read_ptr[i]);
}
printf("\r\n");
}
运行与验证
- 在main函数中调用
Flash_Test(),编译烧录后打开串口助手(115200-8-N-1)。 - 预期串口输出顺序:
扇区擦除成功→页写入成功→数据校验通过→回读数据: A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4。 - 判定标准:串口完整打印上述4行信息且无错误码,说明Flash驱动工作正常。
注意事项
- 操作地址必须落在参数区(0x0800F800~0x0800FC00),误操作应用区会导致程序跑飞。
- 写入前必须先擦除,Flash只能将1写为0,不能反向操作。
- 若使用RTOS,Flash操作期间需关中断或加互斥锁,防止任务切换导致死锁。