【嵌入式学习03】STM32的“固态硬盘”——flash(闪存)
一、存储器
程序正常编译生成的二进制文件,需要下载烧录到单片机里面去,这个文件保存在单片机的ROM中,简称只读存储器。
1、分类
ROM一般可以分为四大类:
1)PROM(Programmable Read-only Memory),可编程只读存储器,也叫one-Time Programmabe(OTP)ROM“一次可编程只读存储器”,是一种可以用程,只可以擦写一次,例如芯片的全球唯一ID码。
2)EPROM(Erasable Programmable Read-onlyMemory),可擦除可编程只读存储器,一旦编程完成后,EPROM只能用强紫外线照射来擦除。
3)EEPROM(Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
4)FLASH,可以擦写多次,按区/块擦除。
我们一般使用的单片机里面使用的是EEPROM(E2PROM),STM32则使用FLASH。
2、EPROM
EPROM是一种具有可擦除功能,擦除后即可进行再编程(写入数据)的ROM内存,写入前必须先把里面的内容用紫外线照射它的IC卡上的透明视窗的方式来清除掉。这一类芯片比较容易识别,其封装中包含有“石英玻璃窗”,一个编程后的EPROM芯片的“石英玻璃窗”一般使用黑色不干胶纸盖住, 以防止遭到阳光直射。EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12~24V,随不同的芯片型号而定)。
3、EEPROM
EEPROM拯救了这一切?
EEPROM的全称是「电可擦除可编程只读仔储器」,即Electrically Erasable Programmable Read-onlyMemory。EEPROM的出现可以说是跨时代的因为ROM可以多次编程了,对于程序员来说,终于可以多次烧写单片机了,更让我们兴奋的是,我们可以使用电擦除,而不是紫外线擦除了。从擦除次数上,EEPROM可以擦除100万次,而且EEPROM可以针对每一个区块,也就是每一个位置写[0]或者写[1],如果大家知道FLASH特性的话,就会觉得EEPROM是多么优秀。而且数据的保存时间可以达到100年。当然了,特点就是电路复杂,成本高,因为成本高就导致了EEPROM的大小不是非常大,一般在512KB以下。
4、FLASH
如果从电擦除这个特性上说的话,FLASH也是EEPROM的一种,不同的是,FLASH的擦除区块不是一个字节,而是扇区来擦除,也是因为这样的特性,才导致FLASH价格比EEPROM便宜。
闪存(flash Memory)是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器。用途SD卡、固态硬盘、芯片内存存储单元存储代码。
Flash 具有以下主要特性:
- 对于 STM32F40x和 STM32F41x,容量高达1MB;对于 STM32F42x和 STM32F43x,容量高达 2 MB
- 128 位宽数据读取
- 字节(8bit)、半字(16bit)、字(32bit)和双字(64bit)数据写入
- 扇区擦除与全部擦除
- 存储器组织结
Flash 结构如下:
——主存储器块,分为4个 16 KB 扇区、1 个 64 KB 扇区和 7个 128 KB 扇区
——系统存储器,器件在系统存储器自举模式下从该存储器启动
——512 字节 OTP(一次性可编程),用于存储用户数据
OTP 区域还有 16 个额外字节,用于锁定对应的 OTP 数据块。
——选项字节,用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。
- 低功耗模式
STM32F4系列芯片大部分是对扇区4进行操作,其大小为64KB,足够日常程序的使用,扇区3的16KB不满足日常的容量使用。
Flash 的作用:
①存储程序代码
②存储小图片、小视频
③存储自定义的数据,如:密码、账户信息
如果要在某个扇区写入数据,其前提条件是:当前写入数据存储的地址一定要被擦除过,才能被写入。(类似于黑板的使用,在写之前得先将黑板擦干净)。
在项目开发中如何对某个扇区的字节进行修改?
①先保存整个扇区的数据
②针对保存中的数据进行修改
③擦除该扇区
④将之前保存的所有数据写入该扇区
擦除就像咱们电脑硬盘的格式化
扇区擦除=分区格式化、全部擦除=硬盘格式化
二、FLASH代码编写
1、FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange)
a.功能:擦除指定的闪存扇区。(如果同时请求擦除和编程操作,擦除操作在编程操作之前执行。)
b.传参:①FLASH_Sector:要擦除的扇区号。②VoltageRange:定义擦除并行性的设备电压范围。
此参数可以是以下值之一:VoltageRange_1(当器件电压范围为1.8V至2.1V时,操作将按字节(8位)完成);VoltageRange_2(当器件电压范围为2.1V至2.7V时,操作将通过半字(16位)完成);VoltageRange_3(当设备电压范围为2.7V至3.6V时,操作将由字(32位)完成);VoltageRange_4(当器件电压范围为2.7V至3.6V+外部Vpp时,操作将通过双字(64位)完成)。
我们一般是对字进行操作,这里第二参数选择VoltageRange_3。
2、FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
a.功能:在指定地址编程一个字(32位)。当设备电压范围为2.7V至3.6V时,必须使用此功能。
b.传参:①Address:指定要编程的地址。②Data:指定要编程的数据。
3、往扇区写入数据的步骤:
①解锁FLASH
②擦除扇区
③扇区写入
④锁定扇区
实例:将0x12345678写入扇区4。
uint32_t d;
/* 解锁FLASH(闪存)*/
FLASH_Unlock();
/* 擦除扇区4 */
if(FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3) != FLASH_COMPLETE)
{
printf("FLASH_EraseSector error\r\n");
while(1);
}
/* 向扇区4首地址写入0x12345678 */
if(FLASH_ProgramWord(0x8010000,0xAABBCCDD)!= FLASH_COMPLETE)
{
printf("FLASH_ProgramWord error\r\n");
while(1);
}
/* 不再需要修改数据,则锁定闪存 */
FLASH_Lock();
d = *(__IO uint32_t*)0x8010000;
/* 打印写入的数据 */
printf("read addr at 0x8010000 is 0x%08X\r\n",d);
三、思考题
1、对某一扇区连续写入64字的数据,并读取验证数据是否正确。(64字=64*4字节)
__IO uint32_t *d;
/* 解锁FLASH(闪存)*/
FLASH_Unlock();
/* 擦除扇区4 */
if(FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3) != FLASH_COMPLETE)
{
printf("FLASH_EraseSector error\r\n");
while(1);
}
/* 向扇区4首地址写入0xAABBCCDD */
for(i=0;i<64;i++)
{ /* 这里地址偏移是对字节操作,一个字是4个字节,所以这里是*4 */
if(FLASH_ProgramWord(0x8010000+i*4,0xAABBCCDD)!= FLASH_COMPLETE)
{
printf("FLASH_ProgramWord error\r\n");
while(1);
}
}
/* 不再需要修改数据,则锁定闪存 */
FLASH_Lock();
d = (__IO uint32_t*)0x8010000;
for(i=0;i<64;i++)
{
printf("read addr at 0x8010000 is 0x%08X\r\n",*d++);
}
2、温湿度数据记录仪,实现步骤如下:按下KEY1或甲口1接收到”start”,居动温湿度数据记录模式,将读取到温湿度模块教据保存到STM32内部PLASH当中,最大记录数目100条。
按下KEY2或串口1接收到”stop”,停止温湿度数据记录。
按下KEY3或串口1接收到”show”,显示当前所有温湿度所有记录,显示格式如下:
[001]2020/03/25 Week 6 9:40:12 Temp=30.0 Humi=92.0
[002]2020/03/25 Week 6 9:42:32 Temp=31.0 Humi=93.0
按下KEY4或串口1接收到”clear”,清空所有温湿度数据记录
如何界定存储的数据达到100条?如果超过100条记录,请打印“存储已满”
应用场景:例如智能手表存储用户的身体健康的数据;指纹锁与密码锁,指纹数据与密码数据都可以存储到FLASH。
扇区写入每条记录
/* 扇区写入记录 */
uint32_t flash_write_record(char *pbuf,uint32_t record_count)
{
uint32_t addr_start=ADDR_FLASH_SECTOR_4+record_count*64;
uint32_t addr_end =addr_start+64;
uint32_t i=0;
while (addr_start < addr_end)
{
//每次写入数据是4个字节
if (FLASH_ProgramWord(addr_start, *(uint32_t *)&pbuf[i]) == FLASH_COMPLETE)
{
//地址每次偏移4个字节
addr_start +=4;
i+=4;
}
else
{
printf("flash write record fail,now goto erase sector!\r\n");
//重新擦除扇区
flash_erase_record();
return 1;
}
}
return 0;
}
扇区读取每条记录
/* 扇区读取记录 */
void flash_read_record(char *pbuf,uint32_t record_count)
{
/* 每条记录是64个字节 */
uint32_t addr_start=ADDR_FLASH_SECTOR_4+record_count*64;
uint32_t addr_end =addr_start+64;
uint32_t i=0;
while (addr_start < addr_end)
{
*(uint32_t *)&pbuf[i] = *(__IO uint32_t*)addr_start;
addr_start+=4; /* 每次写入4个字节 */
i = i + 4;
}
}
扇区擦除所有记录
/* 扇区擦除所有记录 */
void flash_erase_record(void)
{
/* 如果不擦除,写入会失败,读取的数据都为0 */
printf("FLASH_EraseSector start\r\n");
if (FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3) != FLASH_COMPLETE)
{
printf("Erase error\r\n");
return;
}
printf("FLASH_EraseSector ends\r\n");
}
主要代码
/*解锁FLASH,允许操作FLASH*/
FLASH_Unlock();
/* 清空相应的标志位*/
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
//尝试获取100条记录
for(i=0;i<100;i++)
{
//获取存储的记录
flash_read_record(buf,i);
//检查记录是否存在换行符号,不存在则不打印输出
if(strstr((const char *)buf,"\n")==0)
break;
}
dht11_rec_cnt=i;
printf("data records count=%d\r\n",dht11_rec_cnt);
while(1)
{
//rtc唤醒事件
if(g_rtc_wakeup_event)
{
g_rtc_wakeup_event=0;
//开始采集温湿度数据
if(g_dht11_start)
{
//读取温湿度数据
if(dht11_read_data(dht11_data) == 0)
{
//检查当前的数据记录是否小于100条
if(dht11_rec_cnt<100)
{
// 获取日期
RTC_GetDate(RTC_Format_BCD,&RTC_DateStructure);
// 获取时间
RTC_GetTime(RTC_Format_BCD, &RTC_TimeStructure);
//格式化字符串,末尾添加\r\n作为一个结束标记,方便我们读取的时候进行判断
//格式如下:[001]2020/03/25 Week 6 9:40:12 Temp=30.0 Humi=92.0
sprintf((char *)buf,"[%03d]20%02x/%02x/%02x Week:%x %02x:%02x:%02x Temp=%d.%d Humi=%d.%d\r\n", \
dht11_rec_cnt,\
RTC_DateStructure.RTC_Year, RTC_DateStructure.RTC_Month, RTC_DateStructure.RTC_Date,RTC_DateStructure.RTC_WeekDay,\
RTC_TimeStructure.RTC_Hours, RTC_TimeStructure.RTC_Minutes, RTC_TimeStructure.RTC_Seconds,\
dht11_data[2],dht11_data[3],dht11_data[0],dht11_data[1]);
//写入温湿度记录
if(0==flash_write_record(buf,dht11_rec_cnt))
{
//显示
printf(buf);
//记录自加1
dht11_rec_cnt++;
}
else
{
//数据记录清零,重头开始存储数据
dht11_rec_cnt=0;
}
}
else
{
//超过100条记录则打印
printf("The record has reached 100 and cannot continue writing\r\n");
}
}
}
}
//判断串口接收到的字符串
//执行串口1事件
if(g_usart1_event)
{
//判断接收到的字符串为start
if(strstr((char *)g_usart1_recv_buf,"start"))
{
//开启温湿度数据采集
g_dht11_start =1;
printf("Open temperature and humidity data acquisition\r\n");
}
//判断接收到的字符串为stop
if(strstr((char *)g_usart1_recv_buf,"stop"))
{
//停止温湿度数据采集
g_dht11_start =0;
printf("Stop temperature and humidity data acquisition\r\n");
}
//判断接收到的字符串为clear
if(strstr((char *)g_usart1_recv_buf,"clear"))
{
//清空所有记录
printf("Emptying all data records is being executed......\r\n");
//扇区擦除
flash_erase_record();
printf("Empty all data records successfully\r\n");
//清零记录计数值
dht11_rec_cnt=0;
}
//判断接收到的字符串为show
if(strstr((char *)g_usart1_recv_buf,"show"))
{
printf("Execution temperature data record display:\r\n");
//尝试获取100条记录
for(i=0;i<100;i++)
{
//获取存储的记录
flash_read_record(buf,i);
//检查记录是否存在换行符号,不存在则不打印输出
if(strstr(buf,"\n")==0)
break;
//打印记录
printf(buf);
}
//如果i等于0,代表没有一条记录
if(i==0)
{
printf("There is no record\r\n");
}
}
//清空串口1事件
g_usart1_event=0;
//清空串口1接收数据缓冲区
memset((uint8_t *)g_usart1_recv_buf,0,sizeof g_usart1_recv_buf);
//清空串口1接收计数值
g_usart1_recv_cnt=0;
}
}
作者:insanity_7