一、STM32F103的FLASH简介
1、如图所示,STM32F103内部FLASH存储区分为三个区域:主存储区、信信息块和闪存存储器接口寄存器。
储存储区是我们读写FLASH的主要的存储区,MCU程序以及一些需要掉电保存的数据都是存储在这个区域的。
信息快:程序启动代码被存储在这部分。
最后的寄存器则是FLASH读写需要配置的一些寄存器位置。
主存储器的起始位置0x08000000,除去程序占用的空间,剩余部分就可以作为数据保存的区域了,所以在利用内部FLASH存储数据的时候,一定不要占用程序本身所占用的空间,否则会导致死机。
主存储器一共256页,每页2K字节长度。
二、FLASH存储寄存器的配置
1、FPEC键寄存器:
在读写FLASH之前必须进行解锁FPEC,通过该寄存器,向该寄存器写入键值。当要操作FLASH时,需要将相应键值KEY1和KEY2写入寄存器中,同理,操作完成后,还要锁定该FLASH。
2、控制寄存器FLASH_CR
LOCK位:解锁或锁住位,解锁后该位清零,写1可锁住。
STRT位:该位写1,进行一次擦除。
PER位:该位写1,进行页擦除
PG位:读写选择位,写数据时该位需要置1,读数据则该位为0.
3、状态寄存器FLASH_SR
第5位EOP位:操作结束标志
第4位WRPRTERR位:写保护错误
第3位:保留
第2位:PGERR:编程错误
第1位BSY位:当FLASH正在被操作时,该位置1,当操作完成或发生错误时,清零。
4、地址寄存器FLASH_AR
选择要操作的FLASH地址,当BSY位为1时,即FLASH正在被操作,则该寄存器不能进行操作。
三、FLASH读写操作流程/1、STM32复位后,FPEC模块是被保护的,不能去操作各个寄存器,因此需要先解锁,向模块中写入
特定的序列到FLASH_KEYR寄存器,才能打开FPEC模块。如图所示。
SMT32内部FLASH每次必须写入16位的数据,不能写8位,否则会出错。在对FLASH读写操作的时候,CPU会暂停(好像和SD卡很像,操作SD卡的时候,也是停止了MCU的其他事情,不能对MCU进行操作了)
2、写操作如图所示:
首先判断FLASH是否解锁,如果没有解锁,将序列写入FLASH_KEYR寄存器解锁,然后将PG置1,选择写操作,然后向指定地址写入数据,检测BSY=1?如果忙碌状态,则=1,否则为0.
3、读操作
读操作比较简单,直接读取一个地址的数据即可,例如:
data = *(*uint16_t *)addr,首先将地址强制转换成uint6_t类型指针, 然后取该地址内容即可。
4、FLASH擦除
页擦除流程:首先判断FLASH是否是锁定状态,如果是则进行解锁,然后判断是否是BSY状态,若不是忙碌状态,则设置CR寄存器位为擦除操作,且为页擦除操作。
四、FLASH读写函数解析
1、解锁函数
void STMFLASH_Unlock(void)
{
FLASH->KEYR=FLASH_KEY1;
FLASH->KEYR=FLASH_KEY2;
}可以看出该函数是将解锁序列依次写入FLASH->KEYR寄存器。
2、上锁函数
void STMFLASH_Lock(void)
{
FLASH->CR|=1<<7;//该函数就是将LOCK位置1
}
3、读取FLASH状态寄存器,查看状态,返回值为状态
u8 STMFLASH_GetStatus(void)
{
u32 res;
res=FLASH->SR;
if(res&(1<<0))return 1; //忙碌则返回1
else if(res&(1<<2))return 2; //编程错误返回2
else if(res&(1<<4))return 3; //写保护错误返回3
return 0; //非忙碌状态且无任何错误时,返回0,或者说可以读写操作的话返回0
}
4、等待操作完成函数
u8 STMFLASH_WaitDone(u16 time)
{
u8 res;
do
{
res=STMFLASH_GetStatus();//读取FLASH状态是否处于忙碌状态
if(res!=1)break;//非忙碌状态则break
delay_us(1);
time--;
}while(time);
if(time==0)res=0xff;//TIMEOUT超时了,res等于0xff
return res;//操作完成返回0
}
5、擦除页
u8 STMFLASH_ErasePage(u32 paddr)
{
u8 res=0;
res=STMFLASH_WaitDone(0X5FFF);// 首先查看是否是忙碌状态,
if(res==0)//非忙碌状态可以操作
{
FLASH->CR|=1<<1;//选择页擦除
FLASH->AR=paddr;//擦除地址
FLASH->CR|=1<<6;// 擦除操作
res=STMFLASH_WaitDone(0X5FFF);//是否操作完成
if(res!=1)//
{
FLASH->CR&=~(1<<1);//完成后,页擦除选择位清零
}
}
return res;
}
6、在指定位置写入半字
u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat)
{
u8 res;
res=STMFLASH_WaitDone(0XFF);
if(res==0)//OK,非忙碌状态下进入
{
FLASH->CR|=1<<0;//设置写操作
*(vu16*)faddr=dat;//向地址中写入数据
res=STMFLASH_WaitDone(0XFF);//等待操作完成
if(res!=1)//
{
FLASH->CR&=~(1<<0);//清零写操作位
}
}
return res;
}
7、读函数
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
return *(vu16*)faddr; //返回指向该地址的内容
}
8、直接写入数据,不检查是否非法
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i=0;i
{
STMFLASH_WriteHalfWord(WriteAddr,pBuffer[i]);//将pBuffer的内容写入到WriteAddr地址中
WriteAddr+=2;//因为写入了两个字节,相应的地址+2
}
}
9、写数据到FLASH中,会检查地址等信息是否合法,以及长度等
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; //
u16 secoff; //
u16 secremain; //
u16 i;
u32 offaddr; //
if(WriteAddr=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//要写入的地址是否大于FLASH起始地址,并且不超过FLASH地址的总大小的地址,若果任何一个非法,那么就不执行下面的操作,直接return
STMFLASH_Unlock(); //解锁
offaddr=WriteAddr-STM32_FLASH_BASE; //计算地址偏移量,地址减去FLASH起始地址
secpos=offaddr/STM_SECTOR_SIZE; //将地址偏移量除以扇区大小,计算从第几个扇区开始写,每个扇区大小2K
secoff=(offaddr%STM_SECTOR_SIZE)/2; //在该扇区内,地址的偏移量,由于地址是+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
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//查看扇区是否被擦除
}
if(i
{
STMFLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除该地址开始的一个页的空间
for(i=0;i
{
STMFLASH_BUF[i+secoff]=pBuffer[i]; //将要写入的数据写入到缓冲中
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//将缓冲中的数据写入该地址中
}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//如果没必要擦除操作,那么直接写数据就好了
if(NumToWrite==secremain)break;//完成了要写入的数据长度,则结束
else//如果要写入的数据比剩余空间大,那么执行以下操作
{
secpos++; //扇区地址+1,开始下一个扇区的写数据
secoff=0; //下个扇区的偏移量肯定是0
pBuffer+=secremain; //数据指向+secremain的数据地址,从这个地址开始读数据
WriteAddr+=secremain*2;//地址也相应的+数据大小*2,因为每个数据两个字节,对应两个地址
NumToWrite-=secremain; //要写入的长度减去之前的已经写入的secremain长度
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//还是判断接下来要写入的长度,是否是大于整个扇区的大小,如果还是一个扇区还是不够,那么先写入一个扇区的长度
else secremain=NumToWrite;//如果不大于,那么直接将剩余长度写入即可
}
};
STMFLASH_Lock();//操作结束后,锁定
}
10、从指定的地址读指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0;i
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取改地址数据
ReadAddr+=2;// 一次读取两字节的数据,因此地址要相应的+2
}
}
以上是根据正点原子迷你STM32开发板的历程做的解析,其实懂得了操作原理后,完全可以自己去写了。
上一篇:STM32ADC采样时间、采样周期、采样频率计算方法
下一篇:STM32F407之TF卡HAL库的使用
推荐阅读最新更新时间:2024-03-16 16:15