本文主要介绍stm32多种的内部flash读写方式和读写长文件的功能函数怎样编写。阅读完本文可以使你能够正常的完成flash读写操作。
介绍
stm32 flash
不同型号的 stm32,其 flash 容量也有所不同,最小的只有 16k 字节,最大的则达到了1024k 字节。本次实验选用的stm32 开发板是f103zet6,其 flash 容量为 512k 字节,属于大容量产品(另外还有中容量和小容量产品),大容量产品的闪存模块组织如图 所示:
stm32 的闪存模块由:主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。
主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2k 字节。注意,小容量和中容量产品则每页只有 1k 字节。从上图可以看出主存储器的起始地址就是0x08000000, b0、b1 都接 gnd 的时候,就是从 0x08000000开始运行代码的。
信息块,该部分分为 2 个小部分,其中启动程序代码,是用来存储 st 自带的启动程序,用于串口下载代码,当 b0 接 v3.3,b1 接 gnd 的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能。
闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(fpec)管理;编程与擦除的高电压由内部产生。
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。
闪存的编程和擦除
stm32 的闪存编程是由 fpec(闪存编程和擦除控制器)模块处理的,这个模块包含 7 个
32 位寄存器,他们分别是:
fpec 键寄存器(flash_keyr)
选择字节键寄存器(flash_optkeyr)
闪存控制寄存器(flash_cr)
闪存状态寄存器(flash_sr)
闪存地址寄存器(flash_ar)
选择字节寄存器(flash_obr)
写保护寄存器(flash_wrpr)
stm32 复位后,fpec 模块是被保护的,不能写入 flash_cr 寄存器;通过写入特定的序列到 flash_keyr 寄存器可以打开 fpec 模块,只有在写保护被解除后,我们才能操作相关寄存器。
stm32 闪存的编程每次必须写入 16 位(不能单纯的写入 8 位数据哦!),当 flash_cr 寄存器的 pg 位为’1’时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据,fpec 都会产生总线错误。在编程过程中(bsy 位为’1’),任何读写闪存的操作都会使 cpu暂停,直到此次闪存编程结束。
同样,stm32 的 flash 在编程的时候,也必须要求其写入地址的 flash 是被擦除了的(也就是其值必须是 0xffff),否则无法写入,在flash_sr 寄存器的 pgerr 位将得到一个警告。
stm23 的 flash 写入过程如图所示。
stm32的flash写入顺序如下:
检查 flash_cr 的 lock 是否解锁,如果没有则先解锁
检查 flash_sr 寄存器的 bsy 位,以确认没有其他正在进行的编程操作
设置 flash_cr 寄存器的 pg 位为’1’
在指定的地址写入要编程的半字
等待 bsy 位变为’0’ - 读出写入的地址并验证数据
flash读写的标准库函数
解锁函数:void flash_unlock(void);
对 flash 进行写操作前必须先解锁,解锁操作也就是必须在 flash_keyr 寄存器写入特定的序列,固件库函数实现很简单:只需要直接调用 flash_unlock();即可。
锁定函数:void flash_lock(void);
有解锁当然就有上锁,为了保护flash,读写和擦除全部需要的flash后需要上锁,只需要调用:flash_lock();
写操作函数:
固件库提供了三个 flash 写函数:
flash_status flash_programword(uint32_t address, uint32_t data);flash_status flash_programhalfword(uint32_t address, uint16_t data);flash_status flash_programoptionbytedata(uint32_t address, uint8_t data); 顾名思义分别为:flash_programword 为 32 位字写入函数,其他分别为 16 位半字写入和用户选择字节写入函数。这里需要说明,32 位字节写入实际上是写入的两次 16 位数据,写完第一次后地址+2,这与我们前面讲解的 stm32 闪存的编程每次必须写入 16 位并不矛盾。写入 8位实际也是占用的两个地址了,跟写入 16 位基本上没啥区别。
4. 获取 flash 状态
主要是用的函数是:flash_status flash_getstatus(void);
返回值是通过枚举类型定义的,分别为:
flash_busy = 1,//忙
flash_error_pg,//编程错误
flash_error_wrp,//写保护错误
flash_complete,//操作完成
flash_timeout//操作超时
5. 等待操作完成函数
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。
所以在每次操作之前,我们都要等待上一次操作完成这次操作才能开始。使用的函数是:flash_status flash_waitforlastoperation(uint32_t timeout)入口参数为等待时间,返回值是 flash 的状态,这个很容易理解,这个函数本身我们在固件库中使用得不多,但是在固件库函数体中间可以多次看到。
6. 读 flash 特定地址数据函数
有写就必定有读,而读取 flash 指定地址的半字的函数固件库并没有给出来,这里我们自己写的一个函数。
u16 stmflash_readhalfword(u32 faddr){return *(vu16*)faddr; } 软件设计
flash的读取
直接读取某一地址的内容
因为读取flash并不需要解锁,我们可以直接用指针指向所读的地址,之后读取此地址的内容即可。
p = (uint32_t *)(0x08008000);printf(\r\n读取内部flash该地址存储的内容为:0x%x,*p); 此程序就是先将0x08008000赋给指针变量p,之后将p指向地址的内容以16进制的格式输出出来。
读取选定位置的选定大小的内容
首先我们编写一个函数,用以读取指定地址的半字(16位数据)。
u16 stmflash_readhalfword(u32 faddr){ return *(vu16*)faddr; } 从指定地址开始读出指定长度的数据
lreadaddr:起始地址
pbuffer:数据指针
numtowrite:半字(16位)数
void stmflash_read(u32 readaddr,u16 *pbuffer,u16 numtoread) { u16 i; for(i=0;i
stmflash_read(flash_addr,temporary_storage,size); 这里flash_addr是我们要读取的起始地址,temporary_storage是
16位的指针变量,存放我们读取到的内容, size是我们要读取的大小,值得注意的是,size是半字大小,也就是有多少个两个字节。比如我们要读取100个字节,size就可以填50。
flash的写入
直接使用标准库写入
首先需要先解锁
flash_unlock(); 写入前需要擦除当前页,对擦除有不理解的可以看我的另一篇文章:基于stm32的flash擦除方式
flash_erasepage(0x08000000+2*1024*5); 之后可以调用固件库函数,进行写入。例如向地址 0x08000000+210245 至 0x08000000+210246 地址写入数据
flash_programword(0x08000000+2*1024*5,0x01234567); 写入之后,不要忘了上锁。
flash_lock(); 写入选定位置的选定大小的内容
我们首先编写一个不检查的写入的函数。
writeaddr:起始地址,pbuffer:数据指针,numtowrite:半字(16位)数 。
void stmflash_write_nocheck(u32 writeaddr,u16 *pbuffer,u16 numtowrite) { u16 i; for(i=0;ivoid 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+1024*stm32_flash_size)))return;//非法地址 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;istmflash_write(flash_addr,flg_false,size);//将标志位置为更改为0x00 此语句实现从flash_addr地址写入size大小的temporary_storage数据。
4月换机潮即将来临:小米6、三星S8、LG G6你会选谁?
比特币开发的信任语言Guix介绍
中国移动未来5年投资1000亿的十大原则
直流电通过整流电路的影响
谷歌大脑提出了一种简单的方法,用于将概率编程嵌入到深度学习生态系统
基于STM32的内部Flash读写操作
原电池正负极的判断方法
商业小镇远程预付费电能管理系统的设计与应用(安科瑞 王琪 )
初步了解一下带宽拓展的结构-Bridged T-coil
使用负偏压萃取电极的方法
华为2020年Q4手机出货量同比暴降近五成
AKH-0.66Ⅰ型电流互感器产品特点
什么是区块链?区块链在食品行业有哪些应用?
智能边缘正面临多样化数据的挑战
未来摄像头将改变人机交互方式
投石科技手势识别发光圈互动装置
江西省5G发展规划:2023年全省5G产业规模达到1000亿元
LeaXield™有源漏电流滤波器 最大限度降低系统漏电流
10月23-25日IOTE国际物联网展通信模块参展商行情资讯
5G商业成功的压力可能会成为推动运营商转型进程的动力