文号
941019
阅读
8262
回复
0
收藏
0
所有教程由网友发布,仅供参考,请谨慎采纳。科创不对教程的科学性、准确性、可靠性负责。
STM32L4内部Flash双字节对齐读写短字符串
2025/01/08
原创
IP:广东
摘要:本文介绍了在STM32L4系列MCU中使用内部Flash存储数据的方法及排查问题的过程。主要内容包括:Flash页面大小与写入规则的差异说明,选择Flash最后一页存储少量数据以降低对代码存放的干扰,分析了因内存未对齐导致Hard Fault的问题并定位到STRD指令无法支持非对齐访问,通过对齐操作解决问题。同时,展示了Flash擦除、写入和读取的核心代码,并通过示例验证了方法的可行性。

前言:

为什么要用内部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的范围。

image.png

image.png

image.png


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的方法更快,可以优先尝试一下(当然,也可以通过大量断点来定位问题,只不过上面提到的方法更快一点)。

image.png

image.png

image.png

image.png


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、效果展示

image.png


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"

__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;	    //数组指针,对齐
.
.
.
.
.
.
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);


结束语:

写文章还是有意思的,要整理输出文字不想误导读者,因此对程序进行鲁棒性测试,修复暴露出来的问题。回忆大学时光,如果按照个人成果产出的重要性排序,于我而言个人博客(技术文章)无疑是排第一的,远超论文和比赛获奖。


参考:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


ps:欢迎各位提建议和意见,回复均有KCB

[ 修改于1年9个月前 - 2025/01/31 20:00:17 ]
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的