[STM32L4] STM32L431读写内部FLASH源码分享

[复制链接]
1517|28
Haizangwang 发表于 2025-9-3 16:58 | 显示全部楼层 |阅读模式
一、源码分享
flash.h

/*************************************************
* @copyright:
* @author:Xupeng
* @date:2022-11-03
* @description:
**************************************************/  
#ifndef _FLASH_H_
#define _FLASH_H_

#include "main_value.h"


int flash_read(uint32_t addr, uint8_t *buf, size_t size);
int flash_write(uint32_t addr, const uint8_t *buf, size_t size);
int flash_erase(uint32_t addr, size_t size);
#endif



flash.c

/*************************************************
* @copyright:
* @author:Xupeng
* @date:2022-11-03
* @description:
**************************************************/  
#include "flash.h"

/*************************************************
* @function:static uint32_t get_page(uint32_t Addr)
* @description:获取页面
* @calls:         
* @input:                    
* @return:      
* @others:      
*************************************************/
static uint32_t get_page(uint32_t Addr)
{
    uint32_t page = 0;

    if (Addr < (FLASH_BASE + FLASH_BANK_SIZE))
    {
        /* Bank 1 */
        page = (Addr - FLASH_BASE) / FLASH_PAGE_SIZE;
    }
    else
    {
        /* Bank 2 */
        page = (Addr - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE;
    }

    return page;
}

/*************************************************
* @function:static uint32_t get_bank(uint32_t Addr)
* @description:获取bank
* @calls:         
* @input:                    
* @return:      
* @others:      
*************************************************/
static uint32_t get_bank(uint32_t Addr)
{
    uint32_t bank = 0;
#ifndef FLASH_BANK_2
    bank = FLASH_BANK_1;
#else
    if (READ_BIT(SYSCFG->MEMRMP, SYSCFG_MEMRMP_FB_MODE) == 0)
    {
        /* No Bank swap */
        if (Addr < (FLASH_BASE + FLASH_BANK_SIZE))
        {
            bank = FLASH_BANK_1;
        }
        else
        {
            bank = FLASH_BANK_2;
        }
    }
    else
    {
        /* Bank swap */
        if (Addr < (FLASH_BASE + FLASH_BANK_SIZE))
        {
            bank = FLASH_BANK_2;
        }
        else
        {
            bank = FLASH_BANK_1;
        }
    }
#endif
    return bank;
}

/*************************************************
* @function:int flash_read(uint32_t addr, uint8_t *buf, size_t size)
* @description:读
* @calls:         
* @input:                    
* @return:      
* @others:      
*************************************************/
int flash_read(uint32_t addr, uint8_t *buf, size_t size)
{
    size_t i;

    if ((addr + size) > STM32_FLASH_END_ADDRESS)
    {
        printf("read outrange flash size! addr is (0x%p)\r\n", (void*)(addr + size));
        return -1;
    }

    for (i = 0; i < size; i++, buf++, addr++)
    {
        *buf = *(uint8_t *) addr;
    }

    return size;
}
/*************************************************
* @function:int flash_write(uint32_t addr, const uint8_t *buf, size_t size)
* @description:写
* @calls:         
* @input:                    
* @return:      
* @others:      
*************************************************/
int flash_write(uint32_t addr, const uint8_t *buf, size_t size)
{
    size_t i, j;
    int result = 0;
    uint64_t write_data = 0, temp_data = 0;

    if ((addr + size) > STM32_FLASH_END_ADDRESS)
    {
        printf("ERROR: write outrange flash size! addr is (0x%p)\r\n", (void*)(addr + size));
        return -1;
    }

    if(addr % 8 != 0)
    {
        printf("write addr must be 8-byte alignment\r\n");
        return -1;
    }

    HAL_FLASH_Unlock();

    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGSERR | FLASH_FLAG_BSY | FLASH_FLAG_RDERR);

    if (size < 1)
    {
        return -2;
    }

    for (i = 0; i < size;)
    {
        if ((size - i) < 8)
        {
            for (j = 0; (size - i) > 0; i++, j++)
            {
                temp_data = *buf;
                write_data = (write_data) | (temp_data << 8 * j);
                buf ++;
            }
        }
        else
        {
            for (j = 0; j < 8; j++, i++)
            {
                temp_data = *buf;
                write_data = (write_data) | (temp_data << 8 * j);
                buf ++;
            }
        }
        /* write data */
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, write_data) == HAL_OK)
        {
            /* Check the written value */
            if (*(uint64_t*)addr != write_data)
            {
                printf("ERROR: write data != read data\r\n");
                result = -2;
                goto __exit;
            }
        }
        else
        {
            result = -2;
            goto __exit;
        }

        temp_data = 0;
        write_data = 0;

        addr += 8;
    }

__exit:
    HAL_FLASH_Lock();
    if (result != 0)
    {
        return result;
    }

    return size;
}

/*************************************************
* @function:int flash_erase(uint32_t addr, size_t size)
* @description:擦除
* @calls:         
* @input:                    
* @return:      
* @others:      
*************************************************/
int flash_erase(uint32_t addr, size_t size)
{
    int result = 0;
    uint32_t FirstPage = 0, NbOfPages = 0, BankNumber = 0;
    uint32_t PAGEError = 0;

    if ((addr + size) > STM32_FLASH_END_ADDRESS)
    {
        printf("ERROR: erase outrange flash size! addr is (0x%p)\r\n", (void*)(addr + size));
        return -1;
    }

    /*Variable used for Erase procedure*/
    FLASH_EraseInitTypeDef EraseInitStruct;
    /* Unlock the Flash to enable the flash control register access *************/
    HAL_FLASH_Unlock();

    /* Clear OPTVERR bit set on virgin samples */
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR | FLASH_FLAG_PGSERR | FLASH_FLAG_BSY);
    /* Get the 1st page to erase */
    FirstPage = get_page(addr);
    /* Get the number of pages to erase from 1st page */
    NbOfPages = get_page(addr + size - 1) - FirstPage + 1;
    /* Get the bank */
    BankNumber = get_bank(addr);
    /* Fill EraseInit structure*/
    EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
    EraseInitStruct.Banks       = BankNumber;
    EraseInitStruct.Page        = FirstPage;
    EraseInitStruct.NbPages     = NbOfPages;

    if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK)
    {
        result = -2;
        goto __exit;
    }

__exit:
    HAL_FLASH_Lock();

    if (result != 0)
    {
        return result;
    }

    printf("erase done: addr (0x%p), size %d\r\n", (void*)addr, size);
    return size;
}



擦除流程图:

2381068b7fe8ea5b0e.png


写入流程图:

4621468b7fe97ecb0b.png



二、注意事项
1、Flash解锁与锁定
必须先解锁Flash: 在写或擦除操作前,必须解锁Flash控制寄存器(FLASH_CR)。默认状态下,Flash是锁定的,以防止意外修改。使用HAL库时,调用HAL_FLASH_Unlock()函数;直接寄存器操作时,向FLASH_KEYR寄存器写入特定密钥(例如:0x456701230x456701230x45670123 和 0xCDEF89AB0xCDEF89AB0xCDEF89AB)。
操作后重新锁定: 完成所有写/擦除操作后,务必调用HAL_FLASH_Lock()或设置FLASH_CR寄存器锁定位,以恢复保护。如果不锁定,可能导致后续意外写入。
注意事项: 解锁操作必须在代码中显式执行,且只能在内核运行模式下进行(不能处于低功耗睡眠状态)。
2、擦除操作注意事项
擦除是必须的前置步骤: Flash存储器必须先擦除才能写入新数据,因为Flash单元只能从1变为0,擦除会将整个扇区恢复为1。STM32L431的Flash组织为2KB页(具体页大小见芯片手册),擦除以页为单位。
使用正确擦除函数: 在HAL库中,使用HAL_FLASHEx_Erase()并指定页地址。擦除时间较长(典型值约20-40ms),程序必须等待完成,通过检查FLASH_SR寄存器的忙标志(例如:$ \text{FLASH_SR_BSY} $)或使用HAL_FLASH_WaitForLastOperation()。
避免部分擦除: 不能只擦除单个字节或字,必须擦除整页。如果只需要修改少量数据,考虑先读取整页数据到RAM,修改后再擦除并写回。
地址对齐: 起始地址必须页对齐(例如:地址需满足 $ \text{address} \mod 2048 = 0 $,因为页大小为2KB)。不对齐会导致擦除失败。
3、写入操作注意事项
写入单位限制: Flash写入必须以双字(64位)为单位进行。不支持字节写入。使用HAL库的HAL_FLASH_Program()函数,指定数据类型FLASH_TYPEPROGRAM_DOUBLEWORD。
地址对齐要求: 写入地址必须对齐到写入单位(例如:32位写入时,地址需满足
address /mod8=0
不对齐会触发硬件错误。
写入顺序: 在同一个页内,多次写入时数据会累积(从1变0),但只能单向修改。写入后检查FLASH_SR寄存器的错误标志(如
FLASH_SR_PROGERR
确保无编程错误。
写入时间: 每个写入操作耗时短(约10-50μs),但仍需等待完成。避免在写入过程中断电,否则数据可能损坏。
4、中断与系统管理
禁用全局中断: 在Flash擦除或写入操作期间,必须禁用全局中断(使用__disable_irq()或HAL库的临界区保护),因为操作过程可能被中断打断,导致超时或错误。
时钟配置: 确保系统时钟(如MSI或HSI)稳定运行。Flash操作需要最低时钟频率(例如:>1MHz),在低功耗模式(如Sleep或Stop模式)下可能无法访问Flash,需先切换到运行模式。
避免Flash访问冲突: 如果代码从Flash执行,不能修改当前正在执行的扇区。否则,会导致程序崩溃(常见错误:HardFault)。建议将读写代码放入RAM中运行(使用__attribute__((section(".ramfunc")))在GCC中)。
5、错误处理与安全
检查状态寄存器: 每次操作后,读取FLASH_SR寄存器检测错误标志(如编程错误、写保护错误或擦除错误)。使用HAL_FLASH_GetError()获取错误码,并实现重试或恢复机制。
写保护配置: STM32L431支持硬件写保护(通过Option Bytes)。如果启用了写保护,无法修改受保护区域。读写前检查并配置Option Bytes(使用HAL_FLASH_OB_Unlock()和HAL_FLASHEx_OBProgram())。
数据完整性: 写入后读取回数据验证(例如:if (*(uint64_t*)addr != write_data))。考虑添加ECC(错误校正码)或CRC校验,尤其在关键数据存储中。
6、功耗与性能优化
低功耗影响: STM32L431设计为低功耗设备,Flash操作会增加功耗。在电池供电应用中,尽量减少Flash访问频率(例如:批量写入而非单次写入)。
延迟处理: 擦除和写入操作有延迟,使用非阻塞方式时(如中断驱动),确保超时机制(例如:设置超时计数器 $ \text{timeout} = 1000 $ 并轮询状态)。
温度考虑: 高温环境下Flash寿命可能降低(典型耐久性:约10,000次擦写循环)。避免频繁擦写同一页,使用磨损均衡算法(如果实现数据存储)。
7、操作前记得清除相关标记位
擦除

__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGSERR | FLASH_FLAG_BSY | FLASH_FLAG_RDERR);



__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGSERR | FLASH_FLAG_BSY | FLASH_FLAG_RDERR);



————————————————
版权声明:本文为CSDN博主「小灰灰搞电子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_15181569/article/details/150423643

maqianqu 发表于 2025-9-4 21:59 | 显示全部楼层
在对FLASH进行写操作前,必须先解锁。
uytyu 发表于 2025-9-6 09:39 | 显示全部楼层
FLASH存储单元只能从1变为0,擦除操作会将整个页(2KB)复位为全1状态。
uptown 发表于 2025-9-6 12:04 | 显示全部楼层
先擦除目标FLASH页。              
uytyu 发表于 2025-9-6 14:52 | 显示全部楼层
内部FLASH写入必须遵循“先擦除,后写入”的原则。
ingramward 发表于 2025-9-6 16:24 | 显示全部楼层
高频中断可能干扰 FLASH 写入时序,建议在写入 / 擦除期间关闭全局中断
i1mcu 发表于 2025-9-6 18:17 | 显示全部楼层
FLASH操作对电压敏感,需确保电源稳定
hilahope 发表于 2025-9-6 20:01 | 显示全部楼层
在进行任何FLASH写入操作之前,必须先解锁FLASH访问。
ulystronglll 发表于 2025-9-6 22:26 | 显示全部楼层
最小擦除单位是 Page              
uptown 发表于 2025-9-8 10:50 | 显示全部楼层
严格遵循 “解锁→擦除→写入→锁定” 的操作流程
claretttt 发表于 2025-9-8 14:48 | 显示全部楼层
可直接通过指针访问目标地址。              
timfordlare 发表于 2025-9-8 15:46 | 显示全部楼层
内部FLASH起始地址为0x0800 0000,结束地址由芯片实际FLASH大小决定。
uptown 发表于 2025-9-9 12:31 | 显示全部楼层
在完成FLASH操作后,应锁定FLASH访问以防止意外写入。
burgessmaggie 发表于 2025-9-9 17:40 | 显示全部楼层
复位后FLASH控制寄存器默认上锁,防止误操作。可通过调用FLASH_Unlock()函数解锁,操作完成后调用FLASH_Lock()加锁以保护数据
sesefadou 发表于 2025-9-10 09:46 | 显示全部楼层
STM32 的 Flash 控制器默认是​​写保护的​​,在进行​​擦除或写入操作之前,必须先解锁 Flash 控制器​​。
hilahope 发表于 2025-9-10 16:57 | 显示全部楼层
在设计程序时应尽量减少对FLASH的频繁写入操作,以延长FLASH的使用寿命。
eefas 发表于 2025-9-10 17:47 | 显示全部楼层
在进行FLASH操作时,建议关闭中断,以防止操作过程中出现干扰。
robincotton 发表于 2025-9-10 18:59 | 显示全部楼层
支持 ​​字节 / 半字 / 字写操作,以及整页擦除​​
wangdezhi 发表于 2025-9-13 15:26 | 显示全部楼层
​​Flash 必须先解锁,才能进行擦除和写入​
updownq 发表于 2025-9-13 20:25 | 显示全部楼层
注意地址对齐、写入频率限制              
您需要登录后才可以回帖 登录 | 注册

本版积分规则

75

主题

240

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部