一、源码分享
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;
}
擦除流程图:
写入流程图:
二、注意事项
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
|
|