前言:
为什么要用内部Flash呢?当然是无需增加外围器件啦,存点信息挺方便的。我这里存储少量信息,不涉及换页操作(单次写入小于页大小),并且写入频次不高。本来以为这个文章挺快能完成的,短字符串的读操作中一个handles Hard fault interrupt给我整懵了,增加字符串长度这个问题就没有了,花了点时间处理这个问题(排查出来是内存没对齐,M4内核支持非对齐访问,但会造成代码效率下降,并且要手动改寄存器SCB->CCR,同时并不是所有指令都支持非对齐访问,比如我下文的STRD(存储双字)指令就不支持非对齐访问,像LDR(加载),STR(存储)就可以非对齐访问)。
正文:
1、如下图1,STM32L4(我使用MCU的Flash是256K的)的Flash的页大小是2K的,不是F1的1K这个要注意,并且不支持单字写入得双字写入。同时可以注意到Bank1和Bank2的Name不是连续的,但这个不连续的Name不影响我们的程序,我认为这里有点误导的,实际使用是连续的page name。如下图2是FLASH Erase structure definition,如下图3是实际上传入的page的范围。
2、计划使用Flash最后的一页存储信息,这个区域平时读写比较少,也不干涉到程序代码存放的问题。STM32L系列的Flash擦除寿命大约是10K次擦除,不是频繁写是够用了,一般来说擦除才消耗寿命,读几乎不消耗寿命,注意不能把擦除操作放在没有delay的while里面反复执行。
3、分析了一波,就开始代码编写了,代码后面再展示,这里我重点说如何排错的。
如前言所说,短字符串(我测试发现1,2,4,6个字符的字符串会卡住,长字符串不会卡住,推测长短字符串使用AC5编译的时候对齐规则不一样)的读操作中转跳进handles Hard fault interrupt。
首先我们得知道是从哪里跳进来的。先看看寄存器(如下图1),LR的值为0xFFFFFFF9,代表从异常返回到线程模式(具体看M4权威指南,下图2是个简化的表格),并使用主堆栈指针MSP。
然后读取MSP寄存器的值,依据:R0~R3、R12、LR、PC、XPRS 顺序,找到LR(和前文的LR不一样)的值,即第6个寄存器值。这个值代表发生异常后调用的下一条指令的地址。然后再追查回上一条函数是Flash_Read函数,定位问题出现在Flash_Read函数,同时调出Fault Reports注意到UNALIGNED被置位,代表内存非对齐访问。
我们再转跳PC地址(即第7个寄存器值),对应的汇编语句就是发生错误的语句(如下图3),可以看到发生错误的是STRD指令,涉及内存读写,结合UNALIGNED被置位,基本可以确信是该汇编语句对应的.c语句出现问题。下图4的方法据其他技术文章说可以,但我实际上发现不行,下图4的方法更快,可以优先尝试一下(当然,也可以通过大量断点来定位问题,只不过上面提到的方法更快一点)。
4、既然定位到问题是内存没对齐导致的,那我们在初始化的时候就把他对齐,使用__align(num)(我感觉下面代码有些对齐不是必要的)。
__align(8) uint8_t TEXT_Buffer[]={"KCer"};//要写入到STM32 FLASH的字符串数组
#define SIZE sizeof(TEXT_Buffer) //数组长度
__align(8) uint8_t datatemp[SIZE]; //Flash读取缓存数组,对齐
__align(8) uint8_t *p=datatemp; //数组指针,对齐
5、效果展示
6、主干代码展示
Use_flash.h
#ifndef _USE_FLASH_H_
#define _USE_FLASH_H_
#include "main.h"
#include "stm32l4xx_hal_flash_ex.h"
void Flash_Erase(void);
void Flash_Write(uint64_t *pBuffer,uint16_t NumToWrite);
void Flash_Read(uint64_t *pBuffer,uint16_t NumToRead);
#endif
Use_flash.c
#include "Use_flash.h"
#include "stdio.h"
#include "string.h"
#define STM32_FLASH_SIZE 256 //所选STM32的FLASH容量大小(单位为K),256K以下的产品是1K一个page,256及以上是2K一个page
#if STM32_FLASH_SIZE < 256 //设置扇区大小
#define STM_SECTOR_SIZE 1024 //1K字节
#else
#define STM_SECTOR_SIZE 2048 //2K字节
#endif
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
#define FLASH_USER_START_ADDR ( STM32_FLASH_BASE + STM_SECTOR_SIZE * 126 ) //写Flash的地址,这里从第126页开始
#define FLASH_USER_END_ADDR ( STM32_FLASH_BASE + STM_SECTOR_SIZE * 127 ) //写Flash的地址,这里以第127页结束
uint32_t PAGEError = 0;
static FLASH_EraseInitTypeDef EraseInitStruct;
/**********************************************************************************
* 函数功能: 页擦除
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Flash_Erase(void)
{
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.Page = 126;//从126页开始
EraseInitStruct.NbPages = 1;//只擦除1页
EraseInitStruct.Banks = FLASH_BANK_1;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK)
{
HAL_FLASH_Lock();
printf(" Erase error\r\n");
Error_Handler( );
}
}
/**********************************************************************************
* 函数功能: 数据写入
* 输入参数: 写入数据缓存数组指针、写入数据数
* 返 回 值: 无
* 说 明:无
*/
void Flash_Write(uint64_t *pBuffer,uint16_t NumToWrite)
{
uint16_t i=0;
uint32_t Address = FLASH_USER_START_ADDR;
HAL_FLASH_Unlock(); //解锁
Flash_Erase( ); //先擦除 再写入
printf(" 擦除完成,准备写入......\r\n");
while ( (Address < FLASH_USER_END_ADDR) && (i<NumToWrite) )
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, pBuffer[i]) == HAL_OK)
{
Address = Address + 8; //地址后移8个字节
i++;
}
else
{
printf("Write error\r\n");
Error_Handler( );
}
}
HAL_FLASH_Lock(); //上锁
}
/**********************************************************************************
* 函数功能: 数据读取
* 输入参数: 读取数据缓存数组指针、读出数据数
* 返 回 值: 无
* 说 明:无
*/
void Flash_Read(uint64_t *pBuffer,uint16_t NumToRead)
{
uint16_t i=0;
uint64_t temp;
uint32_t Address = FLASH_USER_START_ADDR;
while ( (Address < FLASH_USER_END_ADDR) && (i<NumToRead) )
{
temp = *(__IO uint64_t *)Address; // 从 FLASH 读取数据
memcpy(&pBuffer[i++], &temp, sizeof(uint64_t)); // 使用 memcpy
Address = Address + 8; //地址后移8个字节
}
}
main.c主干代码
#include "stdio.h"
#include "string.h"
#include "Use_flash.h"
.
.
.
.
.
.
Flash_Write((uint64_t*)TEXT_Buffer,(SIZE+7)/8); //强制转换后写入数组长度向上取整,双字节
printf(" 写入成功,准备读取......\r\n");
Flash_Read((uint64_t*)datatemp,(SIZE+7)/8);
printf(" 读取成功......写入内容为:\r\n");
printf("%s\r\n、",p);
结束语:
写文章还是有意思的,要整理输出文字不想误导读者,因此对程序进行鲁棒性测试,修复暴露出来的问题。回忆大学时光,如果按照个人成果产出的重要性排序,于我而言个人博客(技术文章)无疑是排第一的,远超论文和比赛获奖。
参考:
STM32L431内部flash读写参考代码_stm32l431 flash读写-CSDN博客
STM32L4系列内部FLASH双字编程示例 - STM32团队 ST意法半导体中文论坛
STM32系列(HAL库)——内部FLASH读写实验_简约版_stm32 hal flash读写-CSDN博客
解决STM32因字节对齐问题导致读写Flash失败进入HardFault的问题_flash进行搬移操作,固定位置错误-CSDN博客
Cortex-M 处理器 hardfault 定位方法和步骤(基于Keil mdk) - 薛定谔我的猫 - 博客园
stm32中字节对齐问题(__align(n),__packed用法) - King先生 - 博客园
ps:欢迎各位提建议和意见,回复均有KCB
[修改于 16时47分前 - 2025/01/08 11:29:12]