一、NorFlash概述
1、NorFlash
Intel于1988年首先开发出NOR Flash 技术,彻底改变了原先由EPROM(Erasable Programmable Read-Only-Memory电可编程序只读存储器)和EEPROM(电可擦只读存储器Electrically Erasable Programmable Read - Only Memory)一统天下的局面。
NOR的优越之处是芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。NOR的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的写入和擦除速度大大影响了它的性能。
2、NandFlash和NorFlash对比
Nor | Nand | |
容量 | 1MB~32MB | 16M~512MB |
XIP | Yes | No |
擦除 | 非常慢(5S) | 快(3ms) |
写 | 慢 | 快 |
读 | 快 | 快 |
可靠性 | 比较高,位反转的比例小于NandFlash的10% | 比较低,位反转比较常见,必须有校验措施,比如TNR必须有坏块管理措施 |
可擦除次数 | 10000~100000 | 100000~1000000 |
生命周期 | 低于NandFlash的10% | 是NorFlash的10倍以上 |
接口 | 与RAM接口相同 | I/O接口 |
访问方法 | 随机访问 | 顺序访问 |
易用性 | 容易 | 复杂 |
主要用途 | 常用于保存代码和关键数据 | 用于保存数据 |
价格 | 贵 | 便宜 |
3、CFI(Common Flash Interface)
不同公司产的NOR Flash在 erase,program,lock,unlock等操作上有差别,即command set不一样。
NorFlash要是支持CFI就好办多了,就不用改代码。通过CFI可以读出片子的manufacturer id,vendorid等等,在程序中就可以通过以上信息来选择正确的erase,program等操作方式。
NorFlash的数据线和地址线都可能为32/16/8条.为了统一起见,通过CFI接口查询时, CFI接口描述的地址均为Flash芯片的地址,CFI接口查询到的数据,以低字节D7-D0上为准,高字节数据线无视就好了。
CFI的作用是把NorFlash 的信息通过统一的方法读出来。
二、NorFlash物理结构
以下内容皆以EN29LV160AB(word模式)为例进行说明。
1、引脚
2、NorFlash块图
3、扇区结构
Size: MB in Sectors
Sector Start Addresses: 000A0000 000B0000
000C0000 000D0000 000E0000 000F0000 001A0000
001B0000 001C0000 001D0000 001E0000 001F0000
EN29LV160AB大小为2M,总共35个扇区,上图列出了每个扇区的起始地址。
三、NorFlash与ARM接线图
左图为EN29LV160AB与ARM的接线图,右图是SRAM与ARM的接线图,可以看到两者没多大的差别。
四、NorFlash相对于RAM使用的特殊性
RAM | NorFlash | |
读取/写入的叫法 | 读取/写入 | 读取/编程(Program) |
读取/写入的最小单位 | 字节(Byte) | 字(Word) |
擦除(Erase)操作的最小单位 | 字节 | Sector/扇区 |
擦除操作的含义 | 将数据删除/全部写入0 | 将整个块都擦除成全是1,也就是里面的数据都是0xFF |
对于写操作 | 直接写即可 | 在写数据之前,要先擦除,然后再写 |
五、NorFlash操作
1、CFI操作
EN29LV160AB遵循CFI接口协议,所以可以进行CFI操作方式。下表仅仅是CFI查询信息的命令表,还有其他表格,这里不再列出。下边将要阐述的操作方式不是通用性的CFI操作,而是EN29LV160AB特有命令集操作。
2、NorFlash命令表
不利用CFI方式也可以操作,EN29LV160AB有自己的命令集,利用这个命令集可以完成需要的操作。
3、状态标志位
DQ7
Data# Polling bit,在编程过程从正在编程的地址中读出的数据的 DQ7 为要写入数据位的补码。比如写入的数据为 0x0000 ,即输入的 DQ7 为 0 ,则在编程中读出的数据为 1 ;当编程完成时读出的数据又变回输入的数据 0 。在擦除过程中 DQ7 输出为 0 ;擦除完成后输出为 1 ;注意读取的地址必须是擦除范围内的地址。RY/BY#:高电平表示‘就绪’,低电平表示‘忙’。
DQ6
轮转位 1(Toggle Bit 1 ),在编程和擦除期间,读任意地址都会导致 DQ6 的轮转(0 ,1间相互变换)。当操作完成后,DQ6停止转换。
DQ2
轮转位 2 (Toggle Bit 2 ),当某个扇区被选中擦除时,读有效地址(地址都在擦除的扇区范围内)会导致 DQ2 的轮转。 注意: DQ2 只能判断一个特定的扇区是否被选中擦除,但不能区分这个扇区是否正在擦除中或者
正处于擦除暂停状态。相比之下,DQ6 可以区分 Nor Flash 是否处于擦除中或者擦除暂停状态,但不能区分哪个扇区被选中擦除,因此需要这 2 个位来确定扇区和模式状态信息。
DQ5
超时位(Exceeded Timing Limits) ,当编程或擦除操作超过了一个特定内部脉冲计数时 DQ5=1,表明操作失败。当编程时把 0 改为 1 就会导致 DQ5=1,因为只有擦除擦做才能把 0 改为 1 。当错误发生后需要执行复位命令才能返回到读数据状态。
DQ3
(扇区擦除计时位)Sector Erase Timer ,只在扇区擦除指令时起作用。当擦除指令真正开始工作时 DQ3=1 ,此时输入的命令(除擦除暂停命令外)都被忽略,DQ3=0 时,可以添加附加的扇区用于多扇区擦除。
六、实现代码
1、读ID
/*******************************************************************
函数功能:显示NorFlash的“Manufacturer ID”和“Device ID”
输入参数:addr
输出参数:无
********************************************************************/
void NOR_Read_ID(void)
{
unsigned short ManuID,DevID; NOR_Rest();
NOR_WR(0x555,0xaa);
NOR_WR(0x2aa,0x55);
NOR_WR(0x555,0x90);
ManuID = NOR_RD(0x0);
DevID = NOR_RD(0x1);
printf("Manufacturer ID: %x\n",ManuID);
printf("Device ID: %x\n",DevID);
}
2、读内容并显示
/*******************************************************************
函数功能:从NorFlash给定的地址处显示256B的内容
输入参数:addr
输出参数:无
*********************************************************************/
void NOR_2K_Content(unsigned char *addr)
{ unsigned int i; printf("Show 256 Bytes content of NorFlash:\n");
for(i=; i<; i++) {
if((i%)==) printf("\n%4x: ", i);
printf("%02x ", *addr++);
}
printf("\n");
}
3、擦除
/*******************************************************************
函数功能:擦除整个芯片
输入参数:无
输出参数:无
********************************************************************/
void NOR_EraseChip(void)
{
printf("chip erasing is started!\n");
NOR_Rest();
NOR_WR(0x555,0xaa);
NOR_WR(0x2aa,0x55);
NOR_WR(0x555,0x80);
NOR_WR(0x555,0xaa);
NOR_WR(0x2aa,0x55);
NOR_WR(0x555,0x10);
printf("it may takes some seconds,plese wait......\n");
if(NOR_Wait())
{
printf("Erasing done!\n");
}
else
{
printf("Erasing error!\n");
}
} /*******************************************************************
函数功能:擦除给定的扇区
输入参数:sect 扇区号
输出参数:无
*********************************************************************/
void NOR_EraseSector(unsigned char sect)
{
unsigned int sectaddr; if(sect > )
{
printf("sect number error!\n");
return ;
} switch(sect)
{
case :
sectaddr = 0x0;
break;
case :
sectaddr = 0x2000;
break;
case :
sectaddr = 0x3000;
break;
case :
sectaddr = 0x4000;
break;
default:
sectaddr = 0x8000*(sect - );
}
printf("Sector %d erasing is started!\n",sect);
NOR_Rest();
NOR_WR(0x555,0xaa);
NOR_WR(0x2aa,0x55);
NOR_WR(0x555,0x80);
NOR_WR(0x555,0xaa);
NOR_WR(0x2aa,0x55);
NOR_WR(sectaddr,0x30);
printf("it may takes some seconds,plese wait......\n");
if(NOR_Wait())
{
printf("Erasing done!\n");
}
else
{
printf("Erasing error!\n");
}
}
4、编程
/*******************************************************************
函数功能:写一个字(2Byte)
输入参数:addr 要写入一个字的物理地址,注意第0位肯定是‘0’
data 要写入的数据
输出参数:1 成功
0 失败
********************************************************************/
static unsigned int NOR_Program(unsigned int addr,unsigned short data)
{
NOR_Rest();
NOR_WR(0x555,0xaa);
NOR_WR(0x2aa,0x55);
NOR_WR(0x555,0xa0);
NOR_WR_DAT(addr,data);
return NOR_Wait();
} /*******************************************************************
函数功能:写入多个字节
输入参数:srcaddr 源地址
desaddr 目的地址
size 大小(以Byte为单位)
输出参数:1 成功
0 失败
********************************************************************/
unsigned int NOR_Write(unsigned short *srcaddr,unsigned int desaddr,unsigned int size)
{
unsigned int i;
printf(" Write NorFlash start!\n");
for(i = ;i < size;i += ){
if( NOR_Program(desaddr,*srcaddr) != ){
printf(" Write NorFlash error!\n");
return ;
}
desaddr += ;
srcaddr++;
}
printf(" Write done.\n");
return ;
}
5、等待擦除或者编程结束
判断当前状态,从而决定是否等待以及判断是否出错是根据状态标志位来操作的。“等待擦除或者编程结束”有不同的方法实现,这里使用的是DQ6轮转判断。
/*******************************************************************
函数功能:等待擦除或者编程结束
输入参数:无
输出参数: 1 成功
0 失败
*********************************************************************/
static unsigned int NOR_Wait(void)
{
unsigned short oldstatus,newstatus;
while()
{ oldstatus = NOR_RD(0x0);
newstatus = NOR_RD(0x0);
if( (oldstatus&0x40) == (newstatus&0x40) )
return ;
if( newstatus&0x20 )
{
oldstatus = NOR_RD(0x0);
newstatus = NOR_RD(0x0);
if( (oldstatus&0x40) == (newstatus&0x40) )
return ;
else
return ;
}
}
}
参考资料:CFI接口
测试代码及文档资料: