文号
941019
阅读
8261
回复
0
收藏
0
所有教程由网友发布,仅供参考,请谨慎采纳。科创不对教程的科学性、准确性、可靠性负责。
STM32L4内部Flash双字节对齐读写短字符串
2025/01/08
原创
IP:广东
摘要:This article introduces the method of using internal Flash for data storage in STM32L4 series MCUs and the process of troubleshooting issues. The main content includes an explanation of differences in Flash page size and writing rules, selecting the last page of Flash to store small amounts of data to minimize interference with code storage, analyzing a Hard Fault caused by unaligned memory access and pinpointing the issue to the STRD instruction's inability to support unaligned access, and resolving it through alignment operations. Additionally, the core code for Flash erasure, writing, and reading is demonstrated, and the feasibility of the method is verified through examples.

前言:

为什么要用内部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' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的