×
单片机 > 单片机程序设计 > 详情

STM32F103RCT6之FLASH读写操作

发布时间:2020-06-09 发布时间:
|

一、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开发板的历程做的解析,其实懂得了操作原理后,完全可以自己去写了。


关键字:STM32F103RCT6  FLASH  读写操作 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STM32中断向量表的位置.重定向