NOR FLASH 是很常见的一种存储芯片,数据掉电不会丢失.NOR FLASH支持Execute On Chip,即程序能够直接在FLASH片内执行(这意味着存储在NOR FLASH上的程序不须要复制到RAM就能够直接运行).这点和NAND FLASH不同.所以,在嵌入式系统中,NOR FLASH很适合做为启动程序的存储介质.NOR FLASH的读取和RAM很相似(只要可以提供数据的地址,数据总线就可以正确的给出数据),但不能够直接进行写操做.对NOR FLASH的写操做须要遵循特定的命令序列,最终由芯片内部的控制单元完成写操做.html
从支持的最小访问单元来看,NOR FLASH通常分为 8 位的和16位的(固然,也有不少NOR FLASH芯片同时支持8位模式和是16 位模式,具体的工做模式经过特定的管脚进行选择).编程
对8位的 NOR FLASH芯片,或是工做在8-BIT模式的芯片来讲,一个地址对应一个BYTE(8-BIT)的数据.例如一块8-BIT的NOR FLASH,假设容量为4个BYTE.那芯片应该有8个数据信号D7-D0 和2个地址信号,A1-A0.地址0x0对应第0个 BYTE,地址0x1对应于1BYTE,地址0x2对应于第2个BYTE,而地址0x3则对应于第3 个BYTE.对16位的 NOR FLASH芯片,或是工做在16-BIT模式的芯片来讲,一个地址对应于一个HALF-WORD(16-BIT)的数据.例如,一块16-BIT的 NOR FLASH,假设其容量为4个BYTE.那芯片应该有16 个数据信号线D15-D0 和1个地址信号A0.地址 0x0对应于芯片内部的第0个HALF-WORD,地址0x1对应于芯片内部的第1个 HALF-WORD设计
FLASH通常都分为不少个SECTOR,每一个SECTOR包括必定数量的存储单元.对有些大容量的FLASH,还分为不一样的BANK,每一个BANK包括必定数目的SECTOR.FLASH的擦除操做通常都是以SECTOR,BANK或是整片FLASH为单位的.指针
在对FLASH进行写操做的时候,每一个BIT能够经过编程由1变为0,但不能够有0修改成1.为了保证写操做的正确性,在执行写操做前,都要执行擦除操做.擦除操做会把FLASH的一个SECTOR,一个BANK或是整片FLASH的值全修改成0xFF.这样,写操做就能够正确完成了.code
因为NOR FLASH没有本地坏区管理,因此一旦存储区块发生毁损,软件或驱动程序必须接手这个问题,不然可能会致使设备发生异常. 在解锁、抹除或写入NOR FLASH区块时,特殊的指令会先写入已绘测的记忆区的第一页(Page).接着快闪记忆芯片会提供可用的指令清单给实体驱动程序,而这些指令是由通常性闪存接口(CommON FLASH memory Interface, CFI)所界定的. 与用于随机存取的ROM不一样,NOR FLASH也能够用在存储设备上;不过与NAND FLASH相比,NOR FLASH的写入速度通常来讲会慢不少.htm
如下内容,如无特别说明,处理器指的是 ARM 处理器,FLASH 指的都是 NOR FLASH.另外,BYTE指的是8-BIT的数据单元,HALF-WORD表明的是16-BIT的数据单元,而WORD 则表明了32-BIT的数据单元.blog
ARM 能够说是目前最流行的32位嵌进式处理器.在这里只提一下ARM处理器的寻址,为后面作个展垫.从处理器的角度来看,系统中每一个地址对应的是一个BYTE的数据单元.这和不少别的处理器都是同样的.接口
从前面的先容,咱们知道从处理器的角度来看,每一个地址对应的是一个 BYTE 的数据单元.而,NOR FLASH 的每一个地址有可能对应的是一个BYTE的数据单元,也有可能对应的是一个HALF-WORD的数据单元.因此在硬件设计中,链接ARM处理器和 NOR FLASH时,必须根据实际状况对地址信号作特别的处理.ip
假如ARM处理器外部扩展的是8-BIT的NOR FLASH, 数据线和地址线的链接应该如图1所示. 从图中咱们能够看到,处理器的数据信号D0-D7和 FLASH的数据信号D0-D7是逐一对应链接的,处理器的地址信号A0-An和NOR FLASH的地址信号A0-An 也是逐一对应链接的.get
假如ARM处理器外部扩展的是16-BIT的NOR FLASH, 地址线必需要错位链接. 图2给了一个ARM处理器和16-BIT NOR FLASH的链接示意图.如图2所示,ARM处理器的数据信号D0-D15和FLASH 的数据信号D0-D15是逐一对应的.而ARM处理器的地址信号和NOR FLASH 的地址信号是错位链接的,ARM的A0悬空,ARM 的A1 链接FLASH 的A0,ARM 的A2链接FLASH的A1,依次类推.须要错位链接的缘由是:ARM处理器的每一个地址对应的是一个BYTE 的数据单元,而 16-BIT 的 FLASH 的每一个地址对应的是一个HALF-WORD(16-BIT)的数据单元.为了保持匹配,因此必须错位链接.这样,从ARM处理器发送出来的地址信号的最低位A0对16-BIT FLASH来讲就被屏蔽掉了.
补充说明:
上面的描述可能比较抽象,下面让咱们来看2个ARM处理器访问16-BIT FLASH的例子:
例子 1:ARM处理器须要从地址0x0读取一个BYTE
例子 2:ARM处理器须要从地址0x1读取一个BYTE
从软件的角度来理解ARM处理器和 FLASH的链接.对于8-BIT的FLASH的链接,很好理解,因为ARM处理器和8-BIT FLASH的每一个地址对应的都是一个 BYTE 的数据单元.因此地址链接毫无疑问是逐一对应的.假如 ARM 处理器链接的是 16-BIT 的处理器,因为 ARM 处理器的每一个地址对应的是一个 BYTE 的数据单元,而 16-BIT FLASH 的每一个地址对应的是一个 HALF-WORD 的16-BIT的数据单元.因此,也毫无疑问,ARM处理器访问16-BIT处理器的时候,地址确定是要错开一位的.在写FLASH驱动的时候,咱们不须要知道地址错位是由硬件实现的,仍是是经过设置ARM处理器内部的寄存器来实现的,只须要记住2点:
HY29F040是现代公司的一款8-BIT的NOR FLASH.在这个小节里,咱们以这个芯片为例子,讲述如何对8-BIT NOR FLASH进行操做.
HY29F040的容量为512K-BYTE,总共包括8 个SECTOR,每一个SECTOR 的容量是64K-BYTE.该芯片支持SECTOR擦除,整片擦除和以BYTE 为基本单位的写操纵.HY29F040的命令定义如表-1所示.
下面,咱们来看看如何实现基本的擦除和编程操做.在本节后面的描述中,咱们使用了下面的2 个定义:
U32 sysbase; //该变量用来表示 FLASH 的起始地址 #define SysAddr8(sysbase, offset) ((volatile U8*)(sysbase)+(offset)) //用来方便对指定的 FALSH 地址进行操做
宏SysAddr8定义了一个 BYTE(8-BIT)指针,其地址为(sysbase + offset).假设FLASH的起始地址为0x10000000,假如要将0xAB写到FLASH的第一个BYTE中往,能够用下面的代码:
*SysAddr8(0x10000000, 0x1) = 0xAB;
注意:
在本节后面的描述中,sysbase表明的是FLASH的起始地址,而SysAddr8中的offset则表明了相对于FLASH起始地址的BYTE偏移量.offset也是8-BIT FLASH在本身的地址信号An-A0上看到的地址.
整片擦除操纵共须要6个周期的总线写操做:
对应的代码:
*SysAddr8(sysbase, 0x5555) = 0xAA; //将值 0xAA写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x2AAA) = 0x55; //将值 0x55 写到 FLASH 地址 0x2AAA *SysAddr8(sysbase, 0x5555) = 0x80; //将值 0x80 写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x5555) = 0xAA; //将值 0xAA写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x2AAA) = 0x55; //将值 0x55 写到 FLASH 地址 0x2AAA *SysAddr8(sysbase, 0x5555) = 0x10; //将值 0x10 写到 FLASH 地址 0x5555
SECTOR的擦除操纵共须要6个周期的总线写操做:
对应的代码:
*SysAddr8(sysbase, 0x5555) = 0xAA; //将值 0xAA写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x2AAA) = 0x55; //将值 0x55 写到 FLASH 地址 0x2AAA *SysAddr8(sysbase, 0x5555) = 0x80; //将值 0x80 写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x5555) = 0xAA; //将值 0xAA写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x2AAA) = 0x55; //将值 0x55 写到 FLASH 地址 0x2AAA *SysAddr8(sysbase, addr) = 0x30; //将值 0x30 写到要擦除的 SECTOR 对应的地址
写一个BYTE 的数据到FLASH中往,须要 4个周期的总线写操做:
对应的代码:
*SysAddr8(sysbase, 0x5555) = 0xAA; //将值 0xAA写到 FLASH 地址 0x5555 *SysAddr8(sysbase, 0x2AAA) = 0x55; //将值 0x55 写到 FLASH 地址 0x2AAA *SysAddr8(sysbase, 0x5555) = 0xA0; //将值 0xA0 写到 FLASH 地址 0x5555 *SysAddr8(sysbase, addr) = data; //将一个 BYTE的数据写到期看的地址
SST39VF160是SST公司的一款16-BIT的NOR FLASH. 在这个小节里, 咱们以SST39VF160为例子, 讲述如何对16-BIT NOR FLASH进行操做.对8-BIT FLASH的操做很好理解,但对16-BIT FLASH的操做理解起来要晦涩不少.我尽力描述得清楚些.
SST39VF160的容量为2M-BYTE , 总共包括512个SECTOR, 每一个SECTOR 的容量是4K-BYTE. 该芯片支持SECTOR擦除,整片擦除和以 HALF-WORD 为基本单位的写操纵.SST39VF160 的命令定义如表-2 所示.在表 2 中,因为全部命令都是从FLASH的角度来定义的. 因此, 全部的地址都是HALF-WORD地址, 指的是16-BIT FLASH在本身的地址信号An-A0上看到的地址.
在本节后面的描述中,咱们使用了下面的2个定义:
U32 sysbase; //该变量用来表示 FLASH 的起始地址 #define SysAddr16(sysbase, offset) ((volatile U16*)(sysbase)+(offset)) //用来方便对指定的 FALSH 地址进行操做
SysAddr16(sysbase, offset)首先定义了一个16-BIT HALF-WORD的指针,指针的地址为sysbase,而后根据offset作个偏移操纵. 因为HALF-WORD指针的地址是2个BYTE对齐的, 因此每一个偏移操纵会使得地址加2. 终极, SysAddr16 (sysbase, offset)至关于定义了一个HALF-WORD的指针,其终极地址为(sysbase + 2offset) .在使用SysAddr16的时候,将sysbase设置成 FLASH 的起始地址,offset 则能够理解为相对于 FLASH 起始地址的HALF-WORD 偏移量或是偏移地址.假设FLASH 的起始地址为 0x10000000,SysAddr16(0x10000000, 0)指向 16-BIT FLASH 的第 0 个HALF-WORD, SysAddr16(0x10000000, 1指向16-BIT FLASH的第1个HALF-WORD.依次类推.假如要将0xABCD分别写到FLASH 的第0个和第 1个HALF-WORD 中往,能够用下面的代码:
*SysAddr16(0x10000000, 0x0) = 0xABCD; *SysAddr16(0x10000000, 0x1) = 0xABCD;
接下来,咱们分别从ARM处理器的角度和FLASH的角度来具体分析一下.
从 ARM 的角度来看:
假设 FLASH 的起始地址为 0x10000000,因为 ARM 处理器知道 FLASH 的地址空间为 0x10000000 ~ (0x10000000 +FLASH容量 – 1),因此在对这个地址空间进行访问的时候,会设置好FLASH的片选信号,并将低位的地址输出到 地址信号上.以*SysAddr16(0x10000000, 0x1) = 0xABCD 为例.从ARM 处理器的角度来看,该操纵是把0xABCD写到地址0x10000002上往.因此ARM处理器终极会在它的地址信号An-A0输出地址0x2,同时会在D15-D0 上输出0xABCD.
从 FLASH 的角度来看:
仍是以 *SysAddr16(0x10000000, 0x1) = 0xABCD 为例,FLASH看到的地址是多少呢?接着分析.ARM 处理器在执行操纵的时候,会设置好相应的FLASH片选使能信号,并在ARM的地址信号An-A0上输出 0x2.因为 ARM和 16-BIT FLASH的地址信号的链接是错开一位的, 因此, FLASH终极在本身的地址An-A0上看到的信号是0x1, 至关于将ARM处理器输出的地址往右作了一个移位操纵,刚好对应的是FLASH的第1 个HALF-WORD.同时,FLASH会在本身的D15-D0上看到数据0xABCD.
经过上面的分析,咱们知道 SysAddr16 中指定的 offset 的值就是 16-BIT FLASH 在本身的地址 An-A0 上看到的值.因此,咱们能够很方便的经过 SysAddr16(sysbase, offset) 对 FLASH 进行操纵,其中 sysbase 表明 FLASH 起始地址,offset 则表明了FLASH 的第几个HALF-WORD(HALF-WORD偏移量或偏移地址)
注意:
整片擦除操做
整片擦除操纵共须要6个周期的总线写操做:
对应的代码:
*SysAddr16(sysbase, 0x5555) = 0x00AA; //将值 0x00AA 写到 FLASH HALF-WORD地址 0x5555 *SysAddr16(sysbase, 0x2AAA) = 0x0055; //将值 0x0055 写到 FLASH HALF-WORD地址 0x2AAA *SysAddr16(sysbase, 0x5555) = 0x0080; //将值 0x0080 写到 FLASH HALF-WORD地址 0x5555 *SysAddr16(sysbase, 0x5555) = 0x00AA; //将值 0x00AA 写到 FLASH HALF-WORD地址 0x5555 *SysAddr16(sysbase, 0x2AAA) = 0x0055; //将值 0x0055 写到 FLASH HALF-WORD地址 0x2AAA *SysAddr16(sysbase, 0x5555) = 0x0010; //将值 0x0010 写到 FLASH HALF-WORD地址 0x5555
SECTOR擦除操做
SECTOR的擦除操纵共须要6个周期的总线写操做:
对应的代码:
*SysAddr16(sysbase, 0x5555) = 0x00AA; //将值 0x00AA 写到 FLASH HALF-WORD地址 0x5555 *SysAddr16(sysbase, 0x2AAA) = 0x0055; //将值 0x0055 写到 FLASH HALF-WORD地址 0x2AAA *SysAddr16(sysbase, 0x5555) = 0x0080; //将值 0x0080 写到 FLASH HALF-WORD地址 0x5555 *SysAddr16(sysbase, 0x5555) = 0x00AA; //将值 0x00AA 写到 FLASH HALF-WORD地址 0x5555 *SysAddr16(sysbase, 0x2AAA) = 0x0055; //将值 0x0055 写到 FLASH HALF-WORD地址 0x2AAA *SysAddr16(sysbase, addr >> 1) = 0x0030; //将值 0x0030 写到要擦除的 SECTOR 对应的HALF-WORD地址
注意:
上面的代码中第6个操纵周期中的ADDR 是从ARM处理器的角度来看的BYTE地址,因为在擦除的时候,用户希看指定的是从 ARM 的角度看到的地址,这样更方便和更直观.而在 SysAddr16 的宏定义中,OFFSET 表示的是相对于FLASH起始地址的 HALF-WORD 偏移量,或是FLASH在本身的地址信号An-A0上看到的地址.因此须要执行一个右移操做,把ADDR转换成 HALF-WORD 地址.
举例说明,SST39VF160 每一个 SECTOR 的大小是 4K-BYTE.从 ARM 处器的角度和用户的角度来看,SECTOR-0 相对于FLASH起始地址的BYTE地址是0x0;从FLASH来看SECTOR-0 的HALF-WORD地址是0x0.从ARM处理器的角度和用户的角度来看, FLASH SECTOR-1相对于FLASH起始地址的BYTE地址0x1000; 从FLASH来看, SECTOR-1的HALF-WORD地址应该是(0x1000 >> 1) = 0x800.
假如要擦除SECTOR-0,上面代码的第6条指令应该是:
*SysAddr16(sysbase, 0x0 >> 1) = 0x0030;
假如要擦除SECTOR-1,上面代码的第6条指令应该是:
*SysAddr16(sysbase, 0x1000 >> 1) = 0x0030;
HALF-WORD 编程操做
写一个HALF-WORD的数据到FLASH中往,须要4个周期的总线写操做:
对应的代码:
*SysAddr16(sysbase, 0x5555) = 0x00AA; //将值 0x00AA 写到 FLASH 地址 0x5555 *SysAddr16(sysbase, 0x2AAA) = 0x0055; //将值 0x0055 写到 FLASH 地址 0x2AAA *SysAddr16(sysbase, 0x5555) = 0x00A0; //将值 0x00A0 写到 FLASH 地址 0x5555 *SysAddr16(sysbase, addr >> 1) = data; //将数据写到对应的 HALF-WORD 地址
注意:
上面的代码中第4个操做周期中的ADDR是从ARM处理器的角度来看的BYTE地址, 因为在执行写操做的时候,用户希看指定的是从 ARM 的角度看到的地址,这样会更方便和更直观.而在 SysAddr16 的宏定义中,OFFSET表示的是相对于FLASH起始地址的HALF-WORD偏移量. 因此须要执行一个右移操纵, 把它转换成HALF-WORD地址.
例如要将数据 0x0123 写到地址 0x0处,对应的是 FLASH 的第 0 个 HAFL-WORD,对应的 HALF-WORD 地址应该是0x0,上面代码的第4条指令应该是:
*SysAddr16(sysbase, 0x0 >> 1) = 0x0123;
又如要将数据0x4567写到地址0x2处, 对应的是FLASH的第1个 HALF-WORD, 对应的HALF-WORD地址应该是0x1, 上面代码的第4条指令应该是:
*SysAddr16(sysbase, 0x2 >> 1) = 0x4567;
再如要将数据0x89AB写到地址0x4处, 对应的是FLASH的第2个HALF-WORD, 对应的HALF-WORD地址应该是0x2,上面代码的第4条指令应该是:
*SysAddr16(sysbase, 0x4 >> 1) = 0x89AB;
还如要将数据0xCDEF 写到地址 0x6处,对应的是 FLASH 的第 3 个 HALF-WORD,对应的 HALF-WORD 地址应该是0x3,上面代码的第4条指令应该是:
*SysAddr16(sysbase, 0x6 >> 1) = 0xCDEF;
以上简单介绍了NOR FLASH原理,以及如何对NOR FLASH进行操做, 但没有包括状态查询, 保护等其余操纵. 对于更复杂的多片FLASH并联的状况也没有讨论,若有须要者,可自行分析.