赞
踩
Flash 是一种非易失性存储器,其结构由主存储器块、系统存储器、OTP 区域和选项字节组成。 主存储器块被分为不同大小的扇区,包括 4 个 16 KB 的扇区、1 个 64 KB 的扇区和 7 个 128 KB 的扇区。这些扇区可被擦除和编程,用于存储应用程序和数据。 系统存储器可以在系统启动时自主加载,用于执行初始化和配置操作。这个存储器通常包含 bootloader、初始化代码和配置数据。 OTP 区域是一次性可编程的区域,可以用来存储用户数据。该区域还有 16 个额外字节,用于锁定对应的 OTP 数据块,以保护其不被意外擦除或编程。 选项字节包含配置数据,用于设置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。这些选项可以被编程,并在器件复位时加载。Flash 存储器是许多嵌入式系统中重要的组成部分,可以存储数据、程序和配置信息,并支持快速读写操作。
以STM32F4系列为例,FLASH分布如下图。
对于FLASH_CR的LOCK位,需要先判断是否已经锁定。如果锁定的话,需要先解锁才能进行擦除操作。
FLASH_SR寄存器的BSY位为‘0’时表示闪存空闲,可以进行操作。如果BSY位为‘1’,表示当前有其他操作正在进行,需要等待其完成后再进行擦除操作。
设置FLASH_CR寄存器中的PER位为‘1’可以启用擦除模式。
选择要擦除的页需要使用FLASH_AR寄存器进行设置。
设置FLASH_CR寄存器中的STRT位为‘1’,开始执行擦除操作。
等待BSY位变为‘0’,表示擦除操作已经完成。
最后,需要读出被擦除的页并进行验证,以确保擦除操作成功。
需要注意的是,在进行实际硬件擦除操作时,需要具备相应的硬件技术和安全措施,以避免对设备造成损坏或安全风险。
在进行STM32内置Flash的操作时,必须遵循一些重要的操作规则。其一是擦除操作必须先于写操作。其二是内置Flash以页为单位进行擦除,每页的大小以具体芯片型号而定,其起始地址为0x08000000。 同时,写操作必须以16位宽度为单位,且可以跨页进行写入。在进行内置Flash的擦写操作时,必须打开外部/内部高速振荡器,以确保操作的准确性和稳定性。但是需要注意的是,内置Flash最多只能进行10万次的擦写操作,因此不能进行死循环的擦写操作,否则会损坏内置Flash。 此外,在进行擦写操作时,需要注意避开用户程序可能存储的区域,以免意外擦写导致错误。虽然每一页的擦除时间为10ms,比较慢,但是不能单个字节的擦写,需要以页为单位进行擦除操作。总之,要正确而有效地进行STM32内置Flash操作,需要遵循一系列操作规则,包括正确的操作顺序、正确的宽度单位、必要的振荡器打开、避开用户程序存储区域等等。
当STM32芯片复位后,Flash控制寄存器(FLASH_CR)不允许执行写操作,以避免因电气干扰等原因而出现对Flash的意外操作。因此,在进行Flash控制寄存器的写操作之前,必须先执行解锁操作。解锁的顺序为:
在Flash密钥寄存器(FLASH_KEYR)中写入KEY1=0x45670123;
在Flash密钥寄存器(FLASH_KEYR)中写入KEY2=0xCDEF89AB。
如果解锁操作的顺序出错,将会返回总线错误并锁定FLASHCR寄存器,直到下一次复位。同时,也可以通过软件将FLASHCR寄存器中的LOCK位置为1来锁定FLASH_CR寄存器。
需要特别注意的是,当FLASHSR寄存器中的BSY位为1时,将不能在写模式下访问FLASHCR寄存器。在这种情况下,对该寄存器的任何写操作尝试都会导致AHB总线阻塞,直到BSY位清零。
执行任何 Flash 编程操作(擦除或编程)时,CPU 时钟频率 (HCLK) 不能低于 1 MHz。如果 在 Flash 操作期间发生器件复位,无法保证 Flash 中的内容。 在对 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线 阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能 从 Flash 中执行代码或数据获取操作
Flash 擦除操作可针对扇区或整个 Flash(批量擦除)执行。执行批量擦除时,不会影响 OTP 扇区或配置扇区
在进行STM32芯片的Flash扇区擦除操作时,需要遵循以下具体步骤:
检查FLASH_SR寄存器中的BSY位,以确认当前未执行任何Flash操作;
在FLASH_CR寄存器中,将SER位置1,并从主存储块的12个或24个和扇区中选择要擦除的扇区(SNB);
将FLASH_CR寄存器中的STRT位置1;
等待BSY位清零。
如果要执行批量擦除,采用以下步骤:
检查FLASH_SR寄存器中的BSY位,以确认当前未执行任何Flash操作;
将FLASH_CR寄存器中的MER位置1;
将FLASH_CR寄存器中的MER和MER1位置1;
将FLASH_CR寄存器中的STRT位置1;
等待BSY位清零。
在进行扇区擦除或批量擦除操作时,都必须先确认BSY位为0,以确保当前未执行任何Flash操作。
#include "stmflash.h" //读取指定地址的半字(16位数据) //faddr:读地址 //返回值:对应数据. u16 STMFLASH_ReadHalfWord(u32 faddr) { return *(vu16*)faddr; } #if STM32_FLASH_WREN //如果使能了写 //不检查的写入 //WriteAddr:起始地址 //pBuffer:数据指针 //NumToWrite:半字(16位)数 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;//地址增加2. } } //从指定地址开始写入指定长度的数据 //WriteAddr:起始地址(此地址必须为2的倍数!!) //pBuffer:数据指针 //NumToWrite:半字(16位)数(就是要写入的16位数据的个数.) #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; //扇区内偏移地址(16位字计算) u16 secremain; //扇区内剩余地址(16位字计算) u16 i; u32 offaddr; //去掉0X08000000后的地址 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; //扇区地址 0~127 for STM32F103RBT6 secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(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位,此操作应该在FLASH_PageErase()中完成!((((FLASH_TypeDef *)((0x40000000UL + 0x00020000UL) + 0x00002000UL))->CR) &= ~((0x1UL << (1U)))) //但是HAL库里面并没有做,应该是HAL库bug! 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; //写地址偏移(16位数据地址,需要*2) NumToWrite-=secremain; //字节(16位)数递减 if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完 else secremain=NumToWrite;//下一个扇区可以写完了 } }; HAL_FLASH_Lock(); //上锁 } #endif //从指定地址开始读出指定长度的数据 //ReadAddr:起始地址 //pBuffer:数据指针 //NumToWrite:半字(16位)数 void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead) { u16 i; for(i=0;i<NumToRead;i++) { pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节. ReadAddr+=2;//偏移2个字节. } } //测试用/// //WriteAddr:起始地址 //WriteData:要写入的数据 void Test_Write(u32 WriteAddr,u16 WriteData) { STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 }
#ifndef __STMFLASH_H__ #define __STMFLASH_H__ #include "user_includes.h" #include "config.h" #include "stm32f1xx_hal.h" #include "stm32f1xx.h" // //用户根据自己的需要设置 #define STM32_FLASH_SIZE 512 //所选STM32的FLASH容量大小(单位为K) #define STM32_FLASH_WREN 1 //使能FLASH写入(0,不是能;1,使能) #define FLASH_WAITETIME 50000 //FLASH等待超时时间 //FLASH起始地址 #define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址 #define FLASH_SAVE_ADDR 0X0801E000 u8 STMFLASH_GetStatus(void); //获得状态 u8 STMFLASH_WaitDone(u16 time); //等待操作结束 u8 STMFLASH_ErasePage(u32 paddr); //擦除页 u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat);//写入半字 u16 STMFLASH_ReadHalfWord(u32 faddr); //读出半字 void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len); //指定地址开始写入指定长度的数据 u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len); //指定地址开始读取指定长度数据 void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite); //从指定地址开始写入指定长度的数据 void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead); //从指定地址开始读出指定长度的数据 //测试写入 void Test_Write(u32 WriteAddr,u16 WriteData); #endif
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。