欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

STM32内部Flash读写问题

程序员文章站 2022-04-08 22:46:05
STM32Flash读写之Flash调试技巧文章目录先熟悉所用MCU的Flash存储大小以及扇区地址Flsah写之前为什么要先擦除Flash擦除长时间占用CPU实测Flash擦写占用的时间Flash读写要注意几点keil的.map文件中包含了什么操作不当导致Flash损坏会怎样Flash上锁与解锁Keil编译器如何查看MCU寄存器的值Flash读、写、擦除、擦除写代码下一篇:Flash擦除长时间占用CPU时间,影响代码正常运行解决方案。概述:  MCU-STM32H743,编程环境-Keil,Flas...

STM32Flash读写之Flash调试技巧


概述:
  MCU-STM32H743,编程环境-Keil,Flash容量为2MB(2048K)。

1.先熟悉所用MCU的Flash存储大小以及扇区地址

  2MB分成 2个块:Bank1和 Bank2,每个块有8个128K的用户扇区和1个128K的系统扇区,所以用户可用的Flash大小为28128=2048KB,也就是2MB。
闪存模块组织表:
STM32内部Flash读写问题

2.Flsah写之前为什么要先擦除

  要明白Flash的编程原理都是只能将1写为0,而不能将0写为1,所以在进行Flash编程前,必须将对应的块擦除,即将该块的每一位都变为1,块内所有字节变为0xFF。如下图所示将BANK1的扇区2(0x08040000)擦除的结果:
STM32内部Flash读写问题
Keil中查看某个地址的数据
STM32内部Flash读写问题
在Memory中输入地址查看改地址的内容。
STM32内部Flash读写问题

3.Flash擦除长时间占用CPU

  片内flash擦除及写入的时序由芯片内自动控制,当发出擦除或写入指令时,CPU暂时停止工作,外围设备(串行口、ADC、Timer等仍处于活动状态),外围设备产生的中断此时被挂起,中断在擦除或写入完成后按优先级顺序执行,所以片内Flash的擦除占用了CPU的时间,这段时间还不少呢,查看STM32H743芯片手册:
STM32内部Flash读写问题
写只需要us级的时间,但是扇区擦除和块擦除都是s级的,这时间是真的不短呢,因为Flash机制而且H7的Flash有2M字节,擦除时间长也在所难免。

4.实测Flash擦写占用的时间

实测扇区擦除需要时间
  首先要明白STM32H7只能以扇区(128K)或块(1M)为单位擦除,利用GPIO拉高拉低的方式测试Flash扇区擦除的时间,擦除之前拉高GPIO4擦除完毕后拉低,代码如下

	GPIO4_L;
	GPIO4_H;
	STMFLASH_OnlyErase(FLASH_Erase_ADDR,1);//后面会给出该函数
	GPIO4_L;

执行结果如下,一个扇区的擦除消耗了950ms
STM32内部Flash读写问题
实测不擦除写入时间
  不擦除写入是指:要写入的区域没有被写过,或者提前被擦除过。执行以下代码

	GPIO4_L;
	GPIO4_H;
	STMFLASH_OnlyWrite(FLASH_Write_ADDR,(uint32_t*)Flash_WData,8);//flash测试(写) 写完一次就屏蔽(写只需要一次)
	GPIO4_L;

执行结果如下,消耗约190us
STM32内部Flash读写问题

5.Flash读写要注意几点

●<1>.STM32H7每次写入数据必须为8个字(32字节)
STM32内部Flash读写问题
  STM32H743内存的编程位数固定为256位,也就是每次写入数据必须为8个字(32字节),如果不够8个字,可以在后面进行补0写入,否则内容将不可预知。而且,写入首地址必须是32的整数倍,否则会影响前后的数据。
32字节写入至BANK1的扇区2(0x08040000)
执行代码:

uint32_t Flash_WData[8] ={0xAAAAAAAA,0xAAAAAAAA,0xAAAAAAAA,0xAAAAAAAA,0xAAAAAAAA,0xAAAAAAAA,0xAAAAAAAA,0xAAAAAAAA};//测试数组
STMFLASH_Write(0x08040000,(uint32_t*)Flash_WData,8);//Flash擦除后写入

写入结果
STM32内部Flash读写问题
小于32字节写入至BANK1的扇区2(0x08040000)
执行代码:

uint32_t Flash_WData[1] ={0xAAAAAAAA};//测试数组
STMFLASH_Write(0x08040000,(uint32_t*)Flash_WData,1);//写入读取的安装位置放置在0x08020060(扇区2)flash地址中(转存上一次写入的ID值)	

写入结果,出现了不可预知的内容:
STM32内部Flash读写问题

●<2>Flash写入的首地址要大于代码占用的地址
  stm32默认就是从flash中取指令执行的,所以我们用户操作Flash时要避开代码占用区,闪存的起始地址为 0x08000000。我们在Keil的工程下就可以查看代码的起始地址和结束地址。方式如下:在Keil工程目录下搜索.map文件,用notepad++打开,里面有各个变量存放的地址代码的起始地址等等,从中看到代码起始地址为0x08000000:
STM32内部Flash读写问题
结束地址为:
STM32内部Flash读写问题
所以我们所要操作的Flash起始地址要大于代码占用的结束地址,并且为32的整数倍。

6.keil的.map文件中包含了什么

  编译后的.map文件中包含了很多有用的信息,关于涉及内存调试必看。
1.Section Cross References:模块、段(入口)交叉引用
2.Removing Unused input sections from the image:移除未调用模块
3.Image Symbol Table:映射符号表
4.Memory Map of the image:内存(映射)分布
5.Image component sizes:存储组成大小

比如在程序的编译后,我们会注意到以下信息:
STM32内部Flash读写问题
其中
Code: 指代码的大小
RO-data :const变量或字符串常量
RW-data :指可读写(RW)、已初始化的变量数据
ZI-data:指未初始化(ZI)的变量数据

其实在工程目录下的.map文件中有更详细的描述,动手查看一下。
.map文件中:
Code:代码段
RO :const变量或字符串常量
RW :指可读写(RW)的数据
data:赋值了的全局变量或static变量、全局数组
bss:未赋值的全局变量或static变量

★注意:
Code、Ro-data:位于FLASH中;
RW-data、ZI-data:位于RAM中;
RW-data已初始化的数据会存储在Flash中,上电会从FLASH搬移至RAM。

观察下图.map文件中,RW数据事先保存在Flash中,但是.bss(未赋值的全局变量)就不会为其在Flash中分配内存。
STM32内部Flash读写问题
所以程序占用的Flash大小为Code + RO Data + RW Data(中已初始化的数据)。

7.操作不当导致Flash损坏会怎样

调试一模一样的三块电路板,同样的程序两块好使,另一块一运行就进错误中断,最后查看了我操作的Flash部分:
STM32内部Flash读写问题
成了这样…
换了扇区就好了。只能怀疑不小心损坏了它?

8.Flash上锁与解锁

  在Flash的读、写、擦除、操作的时候都会对Flash进行上锁解锁操作。
解锁: 在FLASH_KEYR寄存器写入特定的序列(KEY1和KEY2)。
STM32内部Flash读写问题
上锁: 在Flash操作完成后要对Flash上锁,将FLASH_CR寄存D0位置1。
STM32内部Flash读写问题
STM32内部Flash读写问题

9.Keil编译器如何查看MCU寄存器的值

执行以下步骤:
STM32内部Flash读写问题
就可以查看当前某个寄存器的值,调试起来非常方便了。
STM32内部Flash读写问题

10.Flash读、写、擦除、擦除写代码

●Flash只擦除不写

/*只擦除不写*/
void STMFLASH_OnlyErase(uint32_t EraseAddr,uint32_t NumToErase)
{
	FLASH_EraseInitTypeDef FlashEraseInit;
    HAL_StatusTypeDef FlashStatus=HAL_OK;
    uint32_t SectorError=0;
	uint32_t addrx=0;
	uint32_t endaddr=0;	
    if(EraseAddr<STM32_FLASH_BASE||EraseAddr%4)return;	//非法地址
    
 	HAL_FLASH_Unlock();             //解锁	
	addrx=EraseAddr;				//写入的起始地址
	endaddr=EraseAddr+NumToErase*4;	//写入的结束地址
    
    if(addrx<0X1FF00000)
    {
        while(addrx<endaddr)		//扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
		{
			if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
			{   
				FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS;       //擦除类型,扇区擦除 
				FlashEraseInit.Sector=STMFLASH_GetFlashSector(addrx);   //要擦除的扇区STMFLASH_GetFlashSector(addrx)
				FlashEraseInit.Banks=FLASH_BANK_1;						//操作BANK1
                FlashEraseInit.NbSectors=1;                             //一次只擦除一个扇区
                FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3;      //电压范围,VCC=2.7~3.6V之间!!
                if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK) 
                {
                    break;//发生错误了	
                }
               SCB_CleanInvalidateDCache();                            //清除无效的D-Cache
			}else addrx+=4;
            FLASH_WaitForLastOperation(FLASH_WAITETIME,FLASH_BANK_1);    //等待上次操作完成
        }
    }
    FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME,FLASH_BANK_1);       //等待上次操作完成
	if(FlashStatus==HAL_OK)
	{

	}
	HAL_FLASH_Lock();           //上锁
}

●Flash只写不擦

/*只写不擦除*/
void STMFLASH_OnlyWrite(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite)
{
	HAL_StatusTypeDef FlashStatus=HAL_OK;
	uint32_t endaddr=0;	
	
	HAL_FLASH_Unlock();             //解锁	
	endaddr=WriteAddr+NumToWrite*4;	//写入的结束地址
		while(WriteAddr<endaddr)//写数据
		{
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD,WriteAddr,(uint64_t)pBuffer)!=HAL_OK)//写入数据
			{ 
				break;	//写入异常
			}
			WriteAddr+=32;
			pBuffer+=8;
		} 
	HAL_FLASH_Lock();           //上锁
}

●Flash先擦除后写

/*先擦出后写*/
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite)	
{ 
    FLASH_EraseInitTypeDef FlashEraseInit;
    HAL_StatusTypeDef FlashStatus=HAL_OK;
    uint32_t SectorError=0;
	uint32_t addrx=0;
	uint32_t endaddr=0;	
    if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return;	//非法地址
    
 	HAL_FLASH_Unlock();             //解锁	
	addrx=WriteAddr;				//写入的起始地址
	endaddr=WriteAddr+NumToWrite*4;	//写入的结束地址
    
    if(addrx<0X1FF00000)
    {
        while(addrx<endaddr)		//扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
		{
			if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
			{   
				FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS;       //擦除类型,扇区擦除 
				FlashEraseInit.Sector=STMFLASH_GetFlashSector(addrx);   //要擦除的扇区STMFLASH_GetFlashSector(addrx)
				FlashEraseInit.Banks=FLASH_BANK_1;						//操作BANK1
                FlashEraseInit.NbSectors=1;                             //一次只擦除一个扇区
                FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3;      //电压范围,VCC=2.7~3.6V之间!!
                if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK) 
                {
                    break;//发生错误了	
                }
               SCB_CleanInvalidateDCache();                            //清除无效的D-Cache
			}else addrx+=4;
            FLASH_WaitForLastOperation(FLASH_WAITETIME,FLASH_BANK_1);    //等待上次操作完成
        }
    }
    FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME,FLASH_BANK_1);       //等待上次操作完成
	if(FlashStatus==HAL_OK)
	{
		while(WriteAddr<endaddr)//写数据
		{
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD,WriteAddr,(uint64_t)pBuffer)!=HAL_OK)//写入数据
			{ 
				break;	//写入异常
			}
			WriteAddr+=32;
			pBuffer+=8;
		} 
	}
	HAL_FLASH_Lock();           //上锁
} 

●Flash读

//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToRead:字(32位)数
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint32_t NumToRead)   	
{
	uint32_t i;
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
		ReadAddr+=4;//偏移4个字节.	
	}
}

下一篇:Flash擦除长时间占用CPU时间,影响代码正常运行解决方案。

待更新…
待更新…
待更新…
待更新…
待更新…
待更新…
待更新…

★★★如有错误,欢迎指导!!!

本文地址:https://blog.csdn.net/qq_40147893/article/details/107423621