北屋教程网

专注编程知识分享,从入门到精通的编程学习平台

「STM32 Flash 操作全解析」擦除、写入、读取一网打尽!附完整源码

在嵌入式开发中,MCU 内部的 Flash 常用于存储配置信息、日志数据或用于 OTA 升级。STM32F4 系列 MCU 提供了对 Flash 的灵活操作能力,包括按扇区擦除、字节或半字写入等。本文将围绕一段实际使用的 Flash 操作代码进行讲解,主要涉及 Flash 的擦除、写入与读取功能。

一、Flash 结构及操作基本原理

STM32F4 MCU 的 Flash 存储器按照扇区(Sector)划分,每个扇区大小不一,例如在 STM32F407 中,前四个扇区大小为 16KB,第五个为 64KB,之后为若干个 128KB 的大扇区。片上 Flash 支持:

  • 扇区级擦除(Sector Erase)
  • 多种对齐方式的编程(如 Byte、Halfword、Word、Double Word)
  • 擦写需先解锁并清除相关标志位

操作前需解锁 Flash 控制器,完成后应及时锁定以防意外写入。

二、Flash 扇区映射及擦除操作

代码中的扇区映射表 sec_map[] 采用结构体 sec_info_t 维护每个扇区的起始地址、大小及编号:

typedef struct {
    unsigned int start;
    unsigned int size;
    unsigned int secnum;
} sec_info_t;

这是一个结构体类型,表示每个扇区的起始地址、扇区大小和扇区编号。接着通过一个常量数组 sec_map[] 列出 Flash 不同扇区的信息:

const sec_info_t sec_map[] = 
{
    {0x08000000, 16*1024, FLASH_Sector_0},
    {0x08004000, 16*1024, FLASH_Sector_1},
    {0x08008000, 16*1024, FLASH_Sector_2},
    {0x0800C000, 16*1024, FLASH_Sector_3},
    {0x08010000, 64*1024, FLASH_Sector_4},
    {0x08020000, 128*1024, FLASH_Sector_5},
    {0x08040000, 128*1024, FLASH_Sector_6},
    {0x08040000, 128*1024, FLASH_Sector_7}
};

该映射表根据 STM32F4 的 Flash 布局列出了常用的 8 个扇区。


二、Flash 擦除函数讲解

函数 mcu_flash_erase() 实现对 Flash 指定地址范围的擦除。

int mcu_flash_erase(unsigned int addr, size_t size)
  • addr: 要擦除的起始地址
  • size: 要擦除的范围(单位为字节)

函数先计算扇区数量:

int len = sizeof(sec_map) / sizeof(sec_info_t);

然后依次遍历扇区,找出与 addr 和 size 匹配的扇区范围,并执行擦除:

status = FLASH_EraseSector(sec->secnum, VoltageRange_2);

在执行擦除之前必须解锁 Flash:

FLASH_Unlock();

擦除完成后锁定 Flash:

FLASH_Lock();

最后返回 1 表示成功,返回 0 表示擦除失败。

三、Flash 写入函数讲解

写入函数为:

int mcu_flash_write(unsigned int addr ,const void *buf, size_t size)
  • addr: 写入的起始地址
  • buf: 待写入的数据缓冲区
  • size: 写入数据的字节数

写入之前,同样要进行 Flash 解锁,并清除标志位:

FLASH_Unlock();              
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_OPERR | 
                FLASH_FLAG_PGAERR);     

然后进入一个循环,按字节或半字方式逐步写入 Flash:

if ((addr & 1) == 0 && size > 2) {
    status = FLASH_ProgramHalfWord(addr, *((uint16_t *)p));
    wrlen = 2;
} else {
    status = FLASH_ProgramByte(addr, *((uint8_t *)p));
    wrlen = 1;
}

写入完成后更新地址和缓冲区指针:

size -= wrlen;
addr += wrlen;
p    += wrlen;

如果写入中某次操作返回错误,则提前跳出,最后执行锁定操作并返回是否写入成功:

FLASH_Lock();
return ret;

四、Flash 读取函数讲解

读取函数的接口为:

int mcu_flash_read(unsigned int addr ,void *buf, size_t size)

该函数直接通过内存拷贝读取 Flash 数据:

memcpy(buf, (void *)addr, size);  
return 0;

其中 addr 是 Flash 的起始地址,buf 是目标缓存区,size 是读取字节数。由于 STM32 的 Flash 可以直接映射为内存读取,因此可以像访问普通内存一样操作。

五、总结

本篇文章介绍了 STM32F4 MCU 内部 Flash 的基础操作实现,包括:

  • 使用结构体映射 Flash 扇区信息
  • 实现 Flash 擦除函数,通过匹配地址范围擦除对应扇区
  • 实现 Flash 写入函数,按字节或半字逐步写入 Flash
  • 实现 Flash 读取函数,通过 memcpy 方式直接读取 Flash 内容

以上代码适用于裸机开发,也可作为 STM32 Flash 操作的基础模板,配合上层协议或文件系统进行扩展应用,如参数存储、数据记录、Bootloader 固件升级等功能。

开源源码供参考:

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

typedef struct {
    unsigned int start;
    unsigned int size;
    unsigned int secnum;
}sec_info_t;

/*扇区地址映射 ---------------------------------------------------------------*/
const sec_info_t sec_map[] = 
{
    {0x08000000, 16*1024, FLASH_Sector_0},
    {0x08004000, 16*1024, FLASH_Sector_1},
    {0x08008000, 16*1024, FLASH_Sector_2},
    {0x0800C000, 16*1024, FLASH_Sector_3},
    {0x08010000, 64*1024, FLASH_Sector_4},
    {0x08020000, 128*1024, FLASH_Sector_5},
    {0x08040000, 128*1024, FLASH_Sector_6},
    {0x08040000, 128*1024, FLASH_Sector_7}
};

/*
 * @brief       stm32 mcu 内部flash擦除操作
 * @param[in]   addr        - 地址
 * @param[in]   探险大小    - size
 * @return      0 - 失败, 非0 - 成功
 */
int mcu_flash_erase(unsigned int addr, size_t size)
{ 
    int i;
    int len = sizeof(sec_map) / sizeof(sec_info_t);
    const sec_info_t *sec = &sec_map[len - 1];
    
    FLASH_Status status;
    
    /*越界处理*/
    if (addr > sec->start + sec->size)
        return 0;
    
    FLASH_Unlock();
    for (i = 0; i < len; i++)
    {
        sec = &sec_map[i];
        if ( (sec->start >= addr && sec->start < addr + size) || 
             (sec->start + sec->size > addr && sec->start + sec->size <= addr + size))
        {
            //FLASH_OB_WRPConfig();
            status = FLASH_EraseSector(sec->secnum, VoltageRange_2);
            if (status != FLASH_COMPLETE)
            {
                FLASH_Lock(); 
                return 0;  
            }
                          
        }
    }
    FLASH_Lock(); 
    return 1;
}

/*
 * @brief       stm32 mcu 内部flash写操作
 * @param[in]   addr        - 地址
 * @param[in]   buf         - 数据缓冲区
 * @param[in]   写入大小    - size
 * @return      0 - 失败, 非0 - 成功
 */
int mcu_flash_write(unsigned int addr ,const void *buf, size_t size)
{
    unsigned char *p = (uint8_t *)buf;
//    unsigned int base = addr;
//    size_t tlen = size;
    int wrlen;
    FLASH_Status status = FLASH_COMPLETE;
    int ret = 0;
    FLASH_Unlock();              
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_OPERR | 
                    FLASH_FLAG_PGAERR);     
    while (size) {
#if 0
        /*根据对齐方式优化写入长度*/
        if ((addr & 7) == 0 && size > 8)             /*8字节对齐,按双字写入*/  
        {
            status = FLASH_ProgramDoubleWord(addr, *((uint64_t *)p));
            if (status != FLASH_COMPLETE)
                goto _quit;
            wrlen = 8;
        }
        else if ((addr & 3) == 0 && size > 4)        /*4字节对齐,按字写入*/
        {
            status = FLASH_ProgramWord(addr, *((uint32_t *)p));
            if (status != FLASH_COMPLETE)
                goto _quit;
            wrlen = 4;
        }
        else if ((addr & 1) == 0 && size > 2)        /*2字节对齐,按半字写入*/
        {
            status = FLASH_ProgramHalfWord(addr, *((uint16_t *)p));
            if (status != FLASH_COMPLETE)
                goto _quit;
            wrlen = 2;
        }
        else                                         /*按字节写入 --------*/
        {
            status = FLASH_ProgramByte(addr, *((uint8_t *)p));
            if (status != FLASH_COMPLETE)
                goto _quit;
            wrlen = 1;
        }
#endif
        if ((addr & 1) == 0 && size > 2)        /*2字节对齐,按半字写入*/
        {
            status = FLASH_ProgramHalfWord(addr, *((uint16_t *)p));
            if (status != FLASH_COMPLETE)
                goto _quit;
            wrlen = 2;
        }
        else                                         /*按字节写入 --------*/
        {
            status = FLASH_ProgramByte(addr, *((uint8_t *)p));
            if (status != FLASH_COMPLETE)
                goto _quit;
            wrlen = 1;
        }        
        /*地址偏移 -------------------------------------------------------*/
        size -= wrlen;
        addr += wrlen;
        p    += wrlen;        
    }
_quit:

     ret = status == FLASH_COMPLETE;// && memcmp(buf, (void *)base, tlen) ? 1 : 0;     
	 FLASH_Lock();
     return ret;
}

/*
 * @brief       stm32 mcu 内部flash读操作
 * @param[in]   addr        - 地址
 * @param[in]   buf         - 数据缓冲区
 * @param[in]   读出长度    - size
 * @return      0 - 失败, 非0 - 成功
 */
int mcu_flash_read(unsigned int addr ,void *buf, size_t size)
{
    memcpy(buf, (void *)addr, size);  
    return 0;
}
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言