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");
}

运行与验证

  1. 在main函数中调用 Flash_Test(),编译烧录后打开串口助手(115200-8-N-1)。
  2. 预期串口输出顺序:扇区擦除成功 → 页写入成功 → 数据校验通过 → 回读数据: A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4。
  3. 判定标准:串口完整打印上述4行信息且无错误码,说明Flash驱动工作正常。

注意事项

  • 操作地址必须落在参数区(0x0800F800~0x0800FC00),误操作应用区会导致程序跑飞。
  • 写入前必须先擦除,Flash只能将1写为0,不能反向操作。
  • 若使用RTOS,Flash操作期间需关中断或加互斥锁,防止任务切换导致死锁。