STM32内部Flash读写问题

STM32Flash读写之Flash调试技巧


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

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

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

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

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

3.Flash擦除长时间占用CPU

  片内flash擦除及写入的时序由芯片内自动控制,当发出擦除或写入指令时,CPU暂时停止工作,外围设备(串行口、ADC、Timer等仍处于活动状态),外围设备产生的中断此时被挂起,中断在擦除或写入完成后按优先级顺序执行,所以片内Flash的擦除占用了CPU的时间,这段时间还不少呢,查看STM32H743芯片手册:
在这里插入图片描述
写只需要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
在这里插入图片描述
实测不擦除写入时间
  不擦除写入是指:要写入的区域没有被写过,或者提前被擦除过。执行以下代码

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

执行结果如下,消耗约190us
在这里插入图片描述

5.Flash读写要注意几点

●<1>.STM32H7每次写入数据必须为8个字(32字节)
在这里插入图片描述
  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擦除后写入

写入结果
在这里插入图片描述
小于32字节写入至BANK1的扇区2(0x08040000)
执行代码:

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

写入结果,出现了不可预知的内容:
在这里插入图片描述

●<2>Flash写入的首地址要大于代码占用的地址
  stm32默认就是从flash中取指令执行的,所以我们用户操作Flash时要避开代码占用区,闪存的起始地址为 0x08000000。我们在Keil的工程下就可以查看代码的起始地址和结束地址。方式如下:在Keil工程目录下搜索.map文件,用notepad++打开,里面有各个变量存放的地址代码的起始地址等等,从中看到代码起始地址为0x08000000:
在这里插入图片描述
结束地址为:
在这里插入图片描述
所以我们所要操作的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:存储组成大小

比如在程序的编译后,我们会注意到以下信息:
在这里插入图片描述
其中
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中分配内存。
在这里插入图片描述
所以程序占用的Flash大小为Code + RO Data + RW Data(中已初始化的数据)。

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

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

8.Flash上锁与解锁

  在Flash的读、写、擦除、操作的时候都会对Flash进行上锁解锁操作。
解锁: 在FLASH_KEYR寄存器写入特定的序列(KEY1和KEY2)。
在这里插入图片描述
上锁: 在Flash操作完成后要对Flash上锁,将FLASH_CR寄存D0位置1。
在这里插入图片描述
在这里插入图片描述

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

执行以下步骤:
在这里插入图片描述
就可以查看当前某个寄存器的值,调试起来非常方便了。
在这里插入图片描述

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时间,影响代码正常运行解决方案。

软件优化Flash擦写长时间占用CPU
已更新…
已更新…
已更新…
待更新…
已更新…
已更新…
已更新…
软件优化Flash擦写长时间占用CPU

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

猜你喜欢

转载自blog.csdn.net/qq_40147893/article/details/107423621