RAM ROM FLASH的区别和特点
RAM:随机存储器。可读可写,特点是掉电会丢失数据。分为静态RAM(SRAM)和动态RAM(DRAM)。
SRAM:速度非常快,是目前读写最快的存储设备了;但它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲;掉电数据消失,持续供电时数据一直存在,不需要动态刷新。
DRAM:保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的
ROM:只读存储器。掉电时可以保存数据。在单片机运行时,只能从中读取数据,不能向里面写数据。特点是掉电不丢失数据,在单片机中主要用来存储代码和常量等内容。分为PROM,EPROM,EEPROM。
PROM:可编程,但是是一次性的,软件灌入后无法修改。
EPROM:通过紫外光的照射擦除原先的程序,是一种通用的存储器,可多次擦除写入。
EEPROM:电子擦出,价格很高,写入时间很长,写入很慢。可多次擦除写入。
FLASH: 闪速存储器(闪存)。它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据,同时可以快速读取和写入数据。分为NOR Flash,NAND Flash。
NOR FLASH:像访问SDRAM一样,按照数据/地址总线直接访问, 可写的次数较少,速度也慢,由于其读时序类似于SRAM,读地址是线性结构,多用于程序代码的存储。NOR Flash的读取和我们常见的RAM的读取是一样,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本。
NAND FLASH:只有8位/16位/32位甚至更多位宽的总线,每次访问,都要将长地址分为几部分,一点点的分布传入才能访问NAND FLASH。其优势在于可擦写次数多,擦写速度快,但是在使用以及使用过程中会出现坏块因此需要做特殊处理才可以使用。 NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还加上了一块小的NOR Flash来运行启动代码。
为什么存入FLASH的数据断电不会丢失呢?
Flash是一种闪存存储器,它使用了一种特殊的电荷积累技术来存储数据。当数据被写入Flash时,电荷被积累在浮动栅上,这个电荷会一直保持下去,即使断电也不会丢失。
Flash内部的存储单元被称为存储单元或页。每个存储单元可以存储一个或多个位的数据。当需要写入数据时,Flash会将存储单元中的电荷进行调整,以表示所需的数据。这个过程称为擦除和编程。
擦除是将存储单元中的电荷清除为初始状态,以便重新写入新的数据。编程是将存储单元中的电荷调整为表示所需数据的状态。这两个过程都需要特定的电压和时间来完成。
由于Flash使用了特殊的电荷积累技术,即使在断电情况下,存储单元中的电荷仍然能够保持。因此,当MCU重新上电时,它可以读取Flash中的数据,并继续执行之前保存的状态或配置。
下面来讲一下FLASH的操作。
FLASH和RAM的地址是以字节(Byte)为单位的。1Byte = 8bit , 1KB = 1024B, 1MB = 1024KB。
在对FLASH进行读写之前我们需要弄清楚一些东西,我们平时使用MCU的FALSH是可以直接运行代码的,所以应该是 NOR FLASH
以下是一般情况下 NOR Flash 存储器中存储单元的层次结构:
-
位(Bit):最小的存储单元是位,它表示一个二进制的 0 或 1。
-
字节(Byte):字节由 8 个位组成,用于存储一个字节的数据。
-
页(Page):页是存储器的基本编程单位,通常包含多个字节或者千字节的数据。页的大小通常在几十字节到几千字节之间。
-
扇区(Sector):扇区是由多个页组成的逻辑单元,是 NOR Flash 存储器中的一个连续区域,它是最小的可擦除单元,擦除操作只能以扇区为单位进行,即整个扇区的数据将被清除。扇区的大小通常在几千字节到数十千字节之间。
-
块(Block):存储器中的一个更大的逻辑单元,它由多个扇区组成。块的大小是扇区大小的倍数,块是 NOR Flash 存储器进行读取和编程操作的最小单位。
写入FLASH
因为FLASH的特性,所以如果需要修改某个字节的数据,就需要把原来的字节数据擦除了,但是不能只擦除那个字节,需要擦除整个扇区,所以会把整个扇区存的数据给擦了,所以需要用该扇区大小的数组来存储整个扇区的数据,但需要修改某个字节时,在数组里面修改后再把整个数组写入FLASH里面。
例如假设存储数据的扇区大小为16K。则
#define FLASH_USER_DATA_ADDR 0x08008000
#define USER_DATA_SOCTOR_SIZE 16 * 1024 //16K
u32 FlashDataBuff[16*1024/4]; //16K ,每次修改数组,然后整个数组写入FLASH中
enum USER_DATA
{
UWB_Channel_OFFSET = 94,
UWB_Boot_VER_OFFSET = 96,
UWB_APP_VER_OFFSET = 101,
CRC_SUM_OFFSET = USER_DATA_SOCTOR_SIZE/4-1, //保存CRC开始的位置,4bytes
};
void WriteUserData(u32 *pBuffer, u32 size);
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite);
void Write_Flash(void) //修改单个字节
{
u8* pU8Buff;
pU8Buff = (u8*)&FlashDataBuff[UWB_Channel_OFFSET];
*pU8Buff = 6; //UWB 通过为6
pU8Buff+=2; //指针从94偏移到96
*pU8Buff = 13; //UWB boot版本为13
pU8Buff+=5; //指针从96偏移到101
*pU8Buff = 18; //UWB APP版本为18
WriteUserData(FlashDataBuff,USER_DATA_SOCTOR_SIZE); //写入FLASH
}
void WriteUserData(u32 *pBuffer, u32 size)
{
pBuffer[CRC_SUM_OFFSET] = SumCalBackU32((u8*)pBuffer, USER_DATA_SOCTOR_SIZE-4);
//计算并放入crc和
STMFLASH_Write(FLASH_USER_DATA_ADDR,pBuffer,USER_DATA_SOCTOR_SIZE / 4);
//用户数据写入flash,0.24s
}
void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
{
FLASH_Status status = FLASH_COMPLETE;
u32 addrx=0;
u32 endaddr=0;
if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址
FLASH_Unlock(); //解锁
FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
addrx=WriteAddr; //写入的起始地址
endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
if(addrx<0X1FFF0000) //只有主存储区,才需要执行擦除操作!!
{
while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
{
if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
{
status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间!!
if(status!=FLASH_COMPLETE)break; //发生错误了
}else addrx+=4;
}
}
if(status==FLASH_COMPLETE)
{
while(WriteAddr<endaddr)//写数据
{
if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据
{
break; //写入异常
}
WriteAddr+=4;
pBuffer++;
}
}
FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
FLASH_Lock();//上锁
}
读FLASH
从FLASH里面读数据相对于写来说要简单很多,因为不需要进行扇区擦除操作
#define FLASH_USER_DATA_ADDR 0x08008000
#define USER_DATA_SOCTOR_SIZE 16 * 1024 //16K
u32 FlashDataBuff[16*1024/4]; //16K ,每次修改数组,然后整个数组写入FLASH中
enum USER_DATA
{
UWB_Channel_OFFSET = 94,
UWB_Boot_VER_OFFSET = 96,
UWB_APP_VER_OFFSET = 101,
CRC_SUM_OFFSET = USER_DATA_SOCTOR_SIZE/4-1, //保存CRC开始的位置,4bytes
};
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead);
FLASH_OPERATE_RES ReadUserData(u32 *pBuffer);
void ReadFromFlash(u8 buff[])
{
u8 i=0;
u8 Read_Databuff[10]={0};
u8* pU8Buff;
FlashOperateRes = ReadUserData(FlashDataBuff); //读用户数据
pU8Buff = (u8*)&FlashDataBuff[UWB_Channel_OFFSET];
Read_Databuff[i++] = *pU8Buff; //读取通道
Read_Databuff[i++] = *pU8Buff+=2; //指针从94偏移到96再读取boot版本
Read_Databuff[i++] = *pU8Buff+=5; //指针从96偏移到101再读取APP版本
}
FLASH_OPERATE_RES ReadUserData(u32 *pBuffer)//0x09s
{
u32 crcSum = 0;
//读地址页
STMFLASH_Read(FLASH_USER_DATA_ADDR,(u32*)pBuffer,USER_DATA_SOCTOR_SIZE/4); //读用户数据
crcSum = SumCalBackU32((u8*)pBuffer, USER_DATA_SOCTOR_SIZE - 4);
//crc检查
if( crcSum != pBuffer[CRC_SUM_OFFSET] || IfActiveDataRight(pBuffer) == NO ) //用户数据出错
{
STMFLASH_Read(FLASH_USER_DATA_BACKUP_ADDR,(u32*)pBuffer,USER_DATA_SOCTOR_SIZE/4); //读备份数据
crcSum = SumCalBackU32((u8*)pBuffer, USER_DATA_SOCTOR_SIZE - 4);
if(crcSum != pBuffer[CRC_SUM_OFFSET] || IfActiveDataRight(pBuffer) == NO) //备份数据出错
return READ_FLASH_CRC_ERROR;
else //备份区数据正确
STMFLASH_Write(FLASH_USER_DATA_ADDR,pBuffer,USER_DATA_SOCTOR_SIZE / 4); //备份数据写入用户数据
}
return READ_FLASH_CRC_RIGHT;
}
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToRead:字(4位)数
void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)
{
u32 i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
ReadAddr+=4;//偏移4个字节.
}
}
大家在keil 里面 Target 看到 IRAM1和IRAM2,现在来看看他们的作用。
IRAM1是指内部RAM的第一个区域,通常用于存储程序的代码和数据。它的作用是提供快速的访问速度,因为它位于CPU核心附近,可以更快地读取和写入数据。IRAM1的大小通常较小,一般为几KB到几十KB。
IRAM2是指内部RAM的第二个区域,通常用于存储大量的数据。它的作用是扩展内存容量,以满足程序对内存空间的需求。IRAM2的大小通常较大,可以达到几十KB到几百KB。