·前言:STM32 本身没有自带 EEPROM,但是 STM32 具有 IAP(在应用编程)功能,所以我们可以把它的 FLASH 当成 EEPROM 来使用。本章,我们将利用 STM32 内部的 FLASH 来实现数据的存储功能,这次我们是将数据直接存放在 STM32 内部的flash内部。
STM32 的闪存模块由:主存储器、信息块和闪存存储器接口寄存器等3部分组成。主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。注意,小容量和中容量产品则每页只有 1K 字节。从上图可以看出主存储器的起始地址就是 0X08000000, B0、B1 都接 GND 的时候,就是从 0X08000000开始运行代码的。
信息块,该部分分为2个小部分,其中启动程序代码,是用来存储ST自带的启动程序,用于串口下载代码,当 B0 接 V3.3,B1 接 GND 的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能,本章不作介绍。闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理: 编程与擦除的高电压由内部产生。
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能4进行代码或数据的读取操作。
二:闪存的软件编写流程:
1:检查 FLASH CR 的 LOCK 是否解锁,如果没有则先解锁
2:检查 FLASH SR 寄存器的 BSY 位,以确认没有其他正在进行的编程操作设置 FLASH CR 寄存器的 PG 位为’1’
3:在指定的地址写入要编程的半字
4:等待 BSY 位变为’0’
5:读出写入的地址并验证数据
三:STM32 的页擦除顺序为:
检查 FLASH CR 的 LOCK 是否解锁,如果没有则先解锁
检査 FLASH SR 寄存器的 BSY 位,以确认没有其他正在进行的闪存操作
设置 FLASH CR 寄存器的 PER 位为’1用 FLASH AR 寄存器选择要擦除的页
设置 FLASH CR 寄存器的 STRT 位为’1’等待 BSY 位变为’0’
读出被擦除的页并做验证
四:软件擦写几个常用的函数
4.1.锁定解锁函数
上面讲解到在对 FLASH 进行写操作前必须先解锁,解锁操作也就是必须在 FLASH寄存器内部;
HAL_StatusTypeDef HAL_FLASH_Unlock(void); HAL_StatusTypeDef HAL_FLASH_Lock(void);
我们在操作flash时候,操作之前需要将flash解锁,当操作完成后,需要将flash上锁。
4.2 写函数:
FLASH Status FLASH ProgramWord(uint32 tAddress, uint32 t Data); FLASH Status FLASH ProgramHalfWord(uint32 tAddress, uintl6 t Data); FLASH Status FLASH ProgramOptionByteData(uint32 t Address, uint8 t Data);
顾名思义分别为:FLASH ProgramWord 为 32 位字写入函数,其他分别为 16 位半字写入和用户选择字节写入函数。这里需要说明,32 位字节写入实际上是写入的两次 16 位数据,写完第一次后地址+2,这与我们前面讲解的 STM32 闪存的编程每次必须写入 16 位并不矛盾。写入 8位实际也是占用的两个地址了,跟写入 16 位基本上没啥区别。
4.3 擦写函数:
FLASH Status FLASH ErasePage(uint32 tPage Address); FLASH Status FLASH EraseAllPages(void); FLASH Status FLASH EraseOptionBytes(void);
这三个函数顾名思义,第一个函数是页擦除函数,根据页地址擦除特定的页数据,第二个函数是擦除所有的页数据,第三个函数是擦除用户选择字节数据。这三个函数使用非常简单。
4.4 获取flash状态功能
FLASH Status FLASH GetStatus(void);
typedef enum { FLASH BUSY = 1,//忙 FLASH ERROR PG,//编程错误 FLASH ERROR WRP,//写保护错误 FLASH COMPLETE,//操作完成 FLASH TIMEOUT//操作超时 } FLASH Status;
操作之前需要判断一下,当时flash的状态,类似于DMA发送时,判断忙是一样的道理;
4.5.等待操作完成函数
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。所以在每次操作之前,我们都要等待上一次操作完成这次操作才能开始。使用的函数是FLASH Status FLASH WaitForLastOperation(uint32 t Timeout)入口参数为等待时间,返回值是 FLASH 的状态,这个很容易理解,这个函数本身我们在固件库中使用得不多,但是在固件库函数体中间可以多次看到。
五:代码分享:
u16 STMFLASH_ReadHalfWord(u32 faddr) { return *(vu16*)faddr; } #if STM32_FLASH_WREN void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) { u16 i; for(i=0;i<NumToWrite;i++) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]); WriteAddr+=2; } } #if STM32_FLASH_SIZE<256 #define STM_SECTOR_SIZE 1024 //字节 #else #define STM_SECTOR_SIZE 2048 #endif u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节 void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) { u32 secpos; u16 secoff; u16 secremain; u16 i; u32 offaddr; if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))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); 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++; secoff=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(); //上锁 } #endif void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead) { u16 i; for(i=0;i<NumToRead;i++) { pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr); ReadAddr+=2; } } void Test_Write(u32 WriteAddr,u16 WriteData) { STMFLASH_Write(WriteAddr,&WriteData,1); }