STM32CubeMX系列|STM32内部FLASH

STM32内部FLASH

1. 内部FLASH简介

之前的文章中介绍过STM32F1利用SPI与外部FLASH(W25QXX芯片)通讯的例程,本例程将介绍STM32F1的内部FLASH,通过内部FLASH实现数据读写操作。
不同型号的STM32,其FLASH容量也有所不同,最小的只有16K字节,最大的则达到了1024K字节。此处我们使用的是STM32F103ZET6,其FLASH容量为512K字节,属于大容量产品,大容量产品的闪存模块组织图如下图示

在这里插入图片描述
STM32F1的闪存模块由:主存储器、信息块和闪存存储器接口寄存器3部分组成

  • 主存储器:用来存放代码和数据常量,起始地址是0x08000000,BOOT0和BOOT1都接GND时,就是从该起始地址运行代码的
  • 信息块:分为2个小部分,启动程序代码是用来存储ST自带的启动程序,用于串口下载代码,BOOT0接3.3V,BOOT1接GND时,运行的就是这部分代码;选择字节则一般用于配置写保护、读保护等功能
  • 闪存存储器接口寄存器:用于控制闪存读写等,是整个闪存模块的控制机构

对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理:编程与擦除的高电压由内部产生。在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行,即在进行写或擦除操作时,不能进行代码或数据的读取操作。

下面介绍闪存的读取、编程和擦除:
<1>. 闪存的读取
内置闪存模块可以在通用地址空间直接寻址,任何32位数据的读操作都能访问闪存模块的内容并得到相应的数据。

例如,要从地址addr,读取一个半字,可通过如下语句读取:
data = *(__IO uint16_t*)addr
将addr强制转换为vu16指针,然后取该指针所指向的地址的值,即得到了addr地址的值

<2>. 闪存的编程
STM32的闪存编程是由FPEC(闪存编程和擦除控制器)模块处理的,这个模块包含7个32位寄存器,它们分别是:

  • FPEC键寄存器(FLASH_KEYR)
  • 选择字节键寄存器(FLASH_OPTKEYR)
  • 闪存控制寄存器(FLASH_CR)
  • 闪存状态寄存器(FLASH_SR)
  • 闪存地址寄存器(FLASH_AR)
  • 选择字节寄存器(FLASH_OBR)
  • 写保护寄存器(FLASH_WRPR)

其中FPEC键寄存器共有3中键值:
PDPRT=0x000000A5; KEY1=0x45670123; KEY2=0xCDEF89AB
STM32复位后,FPEC模块是被保护的,不能写入FLASH_CR寄存器;通过写入特定的序列到FLASH_KEYR寄存器可以打开FPEC模块(即写入KEY1和KEY2),只有在写保护被解除后,才能操作相关寄存器。
闪存编程过程如下图所示:

在这里插入图片描述

<3>. 闪存的擦除
闪存编程的时候,要先判断其写入地址的FLASH是被擦除了的(也就是其值必须是0xFFFF),否则无法写入。闪存擦除分为页擦除和整片擦除。
闪存页擦除过程如下图示:

在这里插入图片描述
官方固件HAL库FLASH操作的几个常见函数:

//源文件: stm32f1xx_hal_flash.c和stm32f1xx_hal_flash_ex.c
HAL_FLASH_Unlock(void);	//解锁函数
HAL_FLASH_Lock(void);	//锁定函数
HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);	//写操作函数
HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError);	//擦除函数
HAL_FLASH_WaitForLastOperation(uint32_t Timeout);	//等待操作完成函数

2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来控制FLASH的数据写入,K_DOWN按键用来控制FLASH的数据读取,数据的写入与读取信息通过串口1打印出来

  • D1指示灯
  • K_UP和K_DOWN按键
  • USART1
  • STM32F1内部FLASH

3. 软件设计

3.1 STM32CubeMX设置
  • RCC设置外接HSE,时钟设置为72M
  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • PA0设置为GPIO输入模式、下拉模式;PE3设置为GPIO输入模式、上拉模式
3.2 MDK-ARM编程
uint16_t STMFLASH_ReadHalfWord(uint32_t faddr){
    
    		//读取指定地址的半字
	return *(__IO uint16_t*)faddr; 
}

void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite){
    
     	//不检查的写入		 		 
	uint16_t i;
	for(i=0;i<NumToWrite;i++){
    
    
		HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
	  WriteAddr+=2;		//一个地址对应8bits,写入半字,所以地址加2
	}  
} 

#define STM_SECTOR_SIZE	2048	//大容量STM32的扇区大小为2K字节
uint16_t STMFLASH_BUF[STM_SECTOR_SIZE/2];
void STMFLASH_Write(uint32_t WriteAddr,uint16_t *pBuffer,uint16_t NumToWrite){
    
    
	uint32_t secpos;	   //扇区地址
	uint16_t secoff;	   //扇区内偏移地址
	uint16_t secremain;    //扇区内剩余地址	   
 	uint16_t i;    
	uint32_t offaddr;      //去掉0x08000000后的地址
	
	if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*512)))  //STM32F103ZET6的FALSH大小是512K
	return;		//判断地址是否非法
	
	HAL_FLASH_Unlock();				//解锁
	offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址
	secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址
	secoff=(offaddr%STM_SECTOR_SIZE)/2;		//扇区内的偏移
	secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   
	if(NumToWrite<=secremain)
		secremain=NumToWrite;		//小于或等于扇区范围
	while(1){
    
    	
		STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);	//读出整个扇区的内容
		for(i=0;i<secremain;i++){
    
    
			if(STMFLASH_BUF[secoff+i]!=0XFFFF)	break;	//需要擦除  	  
		}
		if(i<secremain){
    
    	//需要擦除
			FLASH_PageErase(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);	//擦除该扇区
			FLASH_WaitForLastOperation(FLASH_WAITETIME);            	//等待上次操作完成
			CLEAR_BIT(FLASH->CR, FLASH_CR_PER);							//清除CR寄存器的PER位
			for(i=0;i<secremain;i++){
    
    					//复制
				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
			}
			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2); //写入整个扇区 
		}else{
    
    
			FLASH_WaitForLastOperation(FLASH_WAITETIME);       		//等待上次操作完成
			STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);	//写已经擦除了的,直接写入扇区剩余区间 
		}
		if(NumToWrite==secremain)
			break;		//写入结束了
		else{
    
    			//写入未结束
			secpos++;					//扇区地址加1
			secoff=0;					//偏移位置为0
		   	pBuffer+=secremain;  		//指针偏移
			WriteAddr+=secremain*2;		//写地址偏移
		   	NumToWrite-=secremain;		//字节数递减
			if(NumToWrite>(STM_SECTOR_SIZE/2))
				secremain=STM_SECTOR_SIZE/2;	//下一个扇区还是写不完
			else 
				secremain=NumToWrite;	//下一个扇区可以写完了
		}	 
	};	
	HAL_FLASH_Lock();		//上锁
}

void STMFLASH_Read(uint32_t ReadAddr,uint16_t *pBuffer,uint16_t NumToRead){
    
    
	uint16_t i;
	for(i=0;i<NumToRead;i++){
    
    
		pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);		//读取2个字节
		ReadAddr+=2;		//偏移2个字节	
	}
}
  • 在main.c文件下编写STM32 flash测试代码
/* USER CODE BEGIN PV */
const uint8_t Text_Buf[] = {
    
    "STM32F103ZET6 FLASH TEST"};
#define TEXTSIZE sizeof(Text_Buf)
#define FLASH_SAVE_ADDR 0x08070000	//设置FLASH保存地址(要大于FLASH起始地址)
/* USER CODE END PV */
void SystemClock_Config(void);
int main(void){
    
    
  /* USER CODE BEGIN 1 */
  uint8_t key;
  uint8_t Read_Buf[TEXTSIZE];
  /* USER CODE END 1 */
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("STM32 Flash Test...\r\n");
  /* USER CODE END 2 */
  while (1){
    
    
    key = KEY_Scan(0);
	if(key == KEY_UP_PRES){
    
    
		STMFLASH_Write(FLASH_SAVE_ADDR,(uint16_t *)Text_Buf,TEXTSIZE);
		printf("FLASH Write : %s\r\n",Text_Buf);
	}
		
	if(key == KEY_DOWN_PRES){
    
    
		STMFLASH_Read(FLASH_SAVE_ADDR,(uint16_t *)Read_Buf,TEXTSIZE);
		printf("FLASH Read : %s\r\n",Read_Buf);
	}
		
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
	HAL_Delay(200);
  }
}

4. 下载验证

编译无误下载到开发板后,可以看到D1指示灯不断闪烁,当按下K_UP按键后数据写入到FLASH内,当按下K_DOWN按键后将写入的数据读取出来,同时串口打印出相应信息

在这里插入图片描述

关注我的公众号,在公众号里发如下消息,即可获取相应的工程源代码:

玩转STM32CubeMX | STM32内部FLASH

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Chuangke_Andy/article/details/108848241