历史上的今天
返回首页

历史上的今天

今天是:2025年01月09日(星期四)

正在发生

2019年01月09日 | STM32学习笔记一一FLASH 模拟 EEPROM

2019-01-09 来源:eefocus

1. 简述

STM32 本身没有自带 EEPROM,但是 STM32 具有在应用编程(IAP:In Application Programming)功能,可以把它的 FLASH 当成 EEPROM 来使用。


不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了1024K 字节。MiniSTM32 开发板选择的 STM32F103RCT6 的 FLASH 容量为 256K 字节,属于大容量产品,闪存模块组织如下图:


在这里插入图片描述


1.1 主存储器:

该部分用来存放代码和数据常数(如 const 类型的数据)。对于大容量产品,其被划分为 256 页,每页 2K 字节。**注意:**小容量和中容量产品则每页只有 1K 字节。从上图可以看出主存储器的起始地址就是 0X08000000, B0、 B1 (BOOT0、BOOT1)都接 GND 的时候,就是从 0X08000000开始运行代码的。


在这里插入图片描述


1.2 信息块:

该部分分为 2 个小部分,其中启动程序代码(2K),用来存储 ST 自带的启动程序,用于串口下载代码,当 B0 接 V3.3, B1 接 GND 的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能.


1.3 闪存存储器接口寄存器:

该部分用于控制闪存读写等,是整个闪存模块的控制机构。


2. 闪存读取

内置闪存模块可以在通用地址空间直接寻址,任何 32 位数据的读操作都能访问闪存模块的内容并得到相应的数据。读接口在闪存端包含一个读控制器,还包含一个 AHB 接口与 CPU 衔接,这个接口的主要工作是产生读闪存的控制信号并预取 CPU 要求的指令块,预取指令块仅用于在 I-Code 总线上的取指操作,数据常量是通过 D-Code 总线访问的。这两条总线的访问目标是相同的闪存模块,访问 D-Code 将比预取指令优先级高。


闪存等待时间:


因为 CPU 运行速度比 FLASH 快得多, STM32F103的 FLASH 最快访问速度≤24Mhz,如果 CPU 频率超过这个速度,那么必须加入等待时间,比如我们一般使用 72Mhz的主频,那么 FLASH等待周期就必须设置为 2,该设置通过 FLASH_ACR寄存器设置。


等待周期体现了系统时钟(SYSCLK)频率与闪存访问时间的关系:

等待周期 系统时钟

0等待周期 0 < SYSCLK < 24MHz

1等待周期 24MHz < SYSCLK ≤ 48MHz

2等待周期 48MHz < SYSCLK ≤ 72MHz

读取地址设置:

要从地址 addr,读取一个半字(半字为 16 为,字为 32 位),可以通过如下的语句读取:

data=(vu16)addr;

将 addr 强制转换为 vu16 指针,然后取该指针所指向的地址的值,即得到了 addr 地址的值。类似的,将上面的 vu16 改为 vu8,即可读取指定地址的一个字节。

对于指针的解释,可学习求求你,不要再纠结指针了,通过与别人的交流讨论,对于提高对知识的理解是大有裨益的!


3. 闪存的编程和擦除

STM32 的闪存编程是由 FPEC(闪存编程和擦除控制器) 模块处理的,这个模块包含 7 个32 位寄存器。


3.1 FPEC 键寄存器(FLASH_KEYR)

FPEC 键寄存器总共有 3 个键值:RDPRT 键=0X000000A5;KEY1=0X45670123;KEY2=0XCDEF89AB。


STM32 复位后, FPEC 模块是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列到 FLASH_KEYR 寄存器可以打开 FPEC 模块(即写入 KEY1 和 KEY2),只有在写保护被解除后,我们才能操作相关寄存器。


在这里插入图片描述


STM32 闪存的编程每次必须写入 16 位(不能单纯的写入 8 位数据!), 当 FLASH_CR 寄存器的 PG 位为’ 1’时,在一个闪存地址写入一个半字将启动一次编程;写入任何非半字的数据, FPEC 都会产生总线错误。在编程过程中(BSY 位为’ 1’ ),任何读写闪存的操作都会使 CPU暂停,直到此次闪存编程结束。同样, STM32 的 FLASH 在编程的时候,也必须要求其写入地址的 FLASH 是被擦除了的(也就是其值必须是 0XFFFF),否则无法写入, 在 FLASH_SR 寄存器的 PGERR 位将得到一个警告。


3.2 STM32 闪存编程过程

(1)检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁;

(2)检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的编程操作;

(3)设置 FLASH_CR 寄存器的 PG 位为’ 1’;

(3)在指定的地址写入要编程的半字;

(4) 等待 BSY 位变为’ 0’;

(5)读出写入的地址并验证数据。


在这里插入图片描述


3.3 STM32 闪存页擦除过程

在 STM32 的 FLASH 编程的时候,要先判断缩写地址是否被擦除了。 STM32 的闪存擦除分为两种:页擦除和整片擦除。


(1)检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁;

(2)检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的闪存操作;

(3)设置 FLASH_CR 寄存器的 PER 位为’ 1’;

(4) 用 FLASH_AR 寄存器选择要擦除的页;

(5) 设置 FLASH_CR 寄存器的 STRT 位为’ 1’;

(6) 等待 BSY 位变为’ 0’;

(7)读出被擦除的页并做验证。


在这里插入图片描述


3.4 FLASH相关寄存器介绍

(1)PEC 键寄存器: FLASH_KEYR


在这里插入图片描述


寄存器主要用来解锁 FPEC,必须在该寄存器写入特定的序列(KEY1 和 KEY2)解锁后,才能对 FLASH_CR 寄存器进行写操作。


(2)闪存控制寄存器: FLASH_CR


在这里插入图片描述


LOCK 位:用于指示 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。

STRT 位:用于开始一次擦除操作。在该位写入 1 , 将执行一次擦除操作。

PER 位:用于选择页擦除操作,在页擦除的时候,需要将该位置 1。

PG 位:用于选择编程操作,在往 FLASH 写数据的时候,该位需要置 1。


(3)闪存状态寄存器: FLASH_SR


在这里插入图片描述


该寄存器主要用来指示当前 FPEC 的操作编程状态。


(4)存地址寄存器: FLASH_AR


在这里插入图片描述


4. 软件实现

4.1 FLASH操作

#ifndef __STMFLASH_H__

#define __STMFLASH_H__

#include "sys.h"

#define STM32_FLASH_SIZE 256 //所选STM32的FLASH容量大小(单位为K)

#define STM32_FLASH_WREN 1              //使能FLASH写入(0,不使能;1,使能)


//FLASH起始地址

#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址


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


//读取指定地址的半字(16位数据)

//faddr:读地址(此地址必须为2的倍数!!)

//返回值:对应数据.

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

{

FLASH_ProgramHalfWord(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

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;i

{

if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除    

}

if(i

{

FLASH_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;  //指针偏移

WriteAddr += secremain; //写地址偏移    

    NumToWrite -= secremain; //字节(16位)数递减

if(NumToWrite>(STM_SECTOR_SIZE/2))

secremain = STM_SECTOR_SIZE/2;//下一个扇区还是写不完

else 

secremain=NumToWrite;//下一个扇区可以写完了

}  

};

FLASH_Lock();//上锁

}



#endif


//从指定地址开始读出指定长度的数据

//ReadAddr:起始地址

//pBuffer:数据指针

//NumToWrite:半字(16位)数

void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)   

{

u16 i;

for(i=0;i

{

pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.

ReadAddr+=2;//偏移2个字节.

}

}


//////////////////////////////////////////////////////////////////////////////////////////////////////

//WriteAddr:起始地址

//WriteData:要写入的数据

void Test_Write(u32 WriteAddr,u16 WriteData)   

{

STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字 

}


4.2 测试


在这里插入图片描述


测试实现就是想 FLASH 特定的地址写入一定长度的数据,再读出来,类似 SPI 读取外部 FLASH 的操作。


现象:


在这里插入图片描述


读取的数据为:u8 TEXT_Buffer[]={“STM32 FLASH TEST”};



推荐阅读

史海拾趣

EPT公司的发展小趣事

随着品牌影响力的不断提升,EPT开始积极拓展OEM/ODM业务。公司凭借先进的生产技术和严格的质量管理体系,成功与多家国内外知名企业建立了合作关系。通过为客户提供定制化的产品和服务,EPT不仅实现了业务的快速增长,还为其积累了丰富的行业经验。

Display Engineering Services公司的发展小趣事

面对日益严峻的环境问题,EPT积极响应国家绿色发展的号召,开始探索和实践绿色生产。公司引进了一系列环保设备和工艺,实现了生产过程中的节能减排和资源循环利用。同时,EPT还加强了对废旧电池的回收和处理工作,以减少对环境的污染。这一举措不仅展现了EPT对社会责任的担当,也为其在电子行业中树立了绿色发展的典范。

以上故事均基于EPT公司在电子行业发展的背景信息虚构而成,旨在展现其发展历程中的关键节点和亮点。请注意,这些故事并非真实事件,仅供参考。

ANADIGICS公司的发展小趣事

EPT公司在其发展历程中,始终重视与科研机构的合作。早年间,EPT与哈尔滨工业大学共同研发了一种新型电池材料,这种材料具有更高的能量密度和更长的循环寿命。通过双方的技术交流和深度合作,EPT成功将这种材料应用到其产品线中,极大地提升了产品的竞争力。这一合作不仅为EPT带来了技术上的突破,也为其在电子行业树立了良好的口碑。

Esterline Power Systems公司的发展小趣事

Esterline Power Systems深知人才是企业发展的核心动力。因此,公司高度重视人才培养和团队建设。公司建立了完善的培训体系,为员工提供多元化的培训和发展机会。同时,公司还注重营造良好的企业文化氛围,鼓励员工积极参与团队合作和创新实践。这些举措不仅提高了员工的专业技能和综合素质,也增强了公司的凝聚力和竞争力。

Cooper Industries公司的发展小趣事

为了进一步拓展全球业务,Cooper Industries在2007年收购了韩国防爆电力设备制造商Hyundai Explosion-Proof Electric Co.。这一收购使公司能够更好地进入韩国市场,并充分利用韩国在造船业以及国内能源与石化基础设施方面的优势。通过这次收购,Cooper Industries不仅提升了在韩国市场的竞争力,也为其在全球防爆电力设备市场的地位打下了坚实基础。这一举措再次展现了Cooper Industries在全球化战略中的远见卓识和果断行动。

这五个故事只是Cooper Industries发展历程中的一部分,但它们足以展现出公司在电子行业中的卓越地位和不断创新的精神。通过全球扩张、收购整合、技术创新和市场拓展等方式,Cooper Industries不断壮大自身实力,为行业的发展做出了积极贡献。

CYANLITE公司的发展小趣事

在XXXX年,CYANLITE公司迎来了一次重要的技术突破。他们成功研发出了一种新型的LED芯片,这种芯片具有更高的发光效率和更长的使用寿命。这一技术的突破使得CYANLITE公司的产品在市场上更具竞争力,公司也借此机会进一步扩大了市场份额。随着品牌知名度的提升,CYANLITE公司开始拓展国际市场,其产品逐渐出口到欧洲、北美等地区,公司的业绩也实现了快速增长。

问答坊 | AI 解惑

请教quartus ii 测试模块仿真问题!!

quartus II 中的#display在哪显示结果,新手,运行了个测试模块 不知道哪看显示结果! 没用第三方工具..modelsim。新手上路,望多指教!! 代码如下: `timescale 100ns/10ns module alu(a,b,add_en,result); input wire     & ...…

查看全部问答>

GPS开发求助(40分在线等,解决速度散分)

小弟现在正在做一个GPS导航的项目,用的是GR-89的模块。自己先在PC机上写了一个接受程序,发现能正常接受GPS发过来的信息。四条语句都能就收到(GLL语句出厂默认被关闭)。现在想写一个控制程序,通过写控制报文来对GPS的工作模式进行控制。可是不 ...…

查看全部问答>

WinCE下如何读取SD卡信息?包括SD卡大小,剩余容量等?

WinCE下如何读取SD卡信息?包括SD卡大小,剩余容量等? 是否得用IO口去读? 得用哪个函数? 另外,同时问一下如何读取RAM内存信息,包括总大小以及已经使用的大小? …

查看全部问答>

eCGUI-微型嵌入式GUI-图形界面系统(支持DOS 16/32bit,Linux,uC/OS等等)

eCGUI-微型嵌入式GUI-图形界面系统(支持DOS 16/32bit,Linux,uC/OS等等)                                 微型16/32位嵌入式GUI,自主开发,历时四年左右 ...…

查看全部问答>

MSP430 ADC10中断进不去,帮帮忙看看程序

我的程序如下 void main() {     ADC10CTL0 &= ~ ENC;                                //在改变设置前停止转换 & ...…

查看全部问答>

杭州 家和智能控制公司 怎么样 啊

这家公司说是搞什么传感网的,不知道待遇如何?以及公司内部氛围如何,麻烦相关人士解答啊…

查看全部问答>

wince 几个基本问题(在线等)谢谢

a在activesync 已经连接的情况下,pc上的程序怎么样能直接访问wince的文件下的文件,就像pc访问文件一样? bactivesync怎么同步指定文件,比如,我要把wince上的\\windows\\xxx.txt文件同步到pc的d:\\xxx\\文件夹下怎么设置 啊? cwince5.0怎么实现开机 ...…

查看全部问答>

光电鼠标传感器测应变

求助,能不能用光电鼠标传感器测平面应变,求相关资料及设计指导…

查看全部问答>

易电源学习心得

易电源最本质的特点就是容易。易电源的游戏设计的非常好,通过看电源管理模块的原理图,已经知道器件的放置位置了。游戏过程中只要分析一下芯片的工作原理,其实就可以很轻易分析到器件使用场合。这个比原来单独设计,计算参数方便很多了…

查看全部问答>

【求助】初学FPGA,有几个与verilog相关的问题请教各位高人:

1.verilog里的$display系统函数是否能被quartusII9.0支持,在哪里能看到运行的结果? 2.一个半加器的verilog文件如下 `timescale 1ns/100ps module abc(a,b,sum,c);         input a,b;         outp ...…

查看全部问答>