1. NOR FLASH 的简单介绍
NOR FLASH 是很常见的一种存储芯片,数据掉电不会丢失.NOR FLASH支持Execute On Chip,即程序可以直接在FLASH片内执行(这意味着存储在NOR FLASH上的程序不需要复制到RAM就可以直接运行).这点和NAND FLASH不一样.因此,在嵌入式系统中,NOR FLASH很适合作为启动程序的存储介质.NOR FLASH的读取和RAM很类似(只要能够提供数据的地址,数据总线就能够正确的给出数据),但不可以直接进行写操作.对NOR FLASH的写操作需要遵循特定的命令序列,最终由芯片内部的控制单元完成写操作.
从支持的最小访问单元来看,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.这样,写操作就可以正确完成了.
由于NOR FLASH没有本地坏区管理,所以一旦存储区块发生毁损,软件或驱动程序必须接手这个问题,否则可能会导致设备发生异常. 在解锁、抹除或写入NOR FLASH区块时,特殊的指令会先写入已绘测的记忆区的第一页(Page).接着快闪记忆芯片会提供可用的指令清单给实体驱动程序,而这些指令是由一般性闪存接口(CommON FLASH memory Interface, CFI)所界定的. 与用于随机存取的ROM不同,NOR FLASH也可以用在存储设备上;不过与NAND FLASH相比,NOR FLASH的写入速度一般来说会慢很多.
2. NOR Flash的烧写方式
以下内容,如无特别说明,处理器指的是 ARM 处理器,FLASH 指的都是 NOR FLASH.另外,BYTE指的是8-BIT的数据单元,HALF-WORD代表的是16-BIT的数据单元,而WORD 则代表了32-BIT的数据单元.
2.1 处理器寻址
ARM 可以说是目前最流行的32位嵌进式处理器.在这里只提一下ARM处理器的寻址,为后面做个展垫.从处理器的角度来看,系统中每个地址对应的是一个BYTE的数据单元.这和很多别的处理器都是一样的.
2.2 处理器和NOR FLASH的硬件连接
从前面的先容,我们知道从处理器的角度来看,每个地址对应的是一个 BYTE 的数据单元.而,NOR FLASH 的每个地址有可能对应的是一个BYTE的数据单元,也有可能对应的是一个HALF-WORD的数据单元.所以在硬件设计中,连接ARM处理器和 NOR FLASH时,必须根据实际情况对地址信号做特别的处理.
假如ARM处理器外部扩展的是8-BIT的NOR FLASH, 数据线和地址线的连接应该如图1所示. 从图中我们可以看到,处理器的数据信号D0-D7和 FLASH的数据信号D0-D7是逐一对应连接的,处理器的地址信号A0-An和NOR FLASH的地址信号A0-An 也是逐一对应连接的.
假如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来说就被屏蔽掉了.
补充说明:
- 一般来说,ARM处理器内部要设置相应的寄存器,告诉处理器外部扩展的FLASH的位宽(8-BIT/16-BIT/32-BIT) .这样,处理器才知道在访问的时候如何从FLASH正确的读取数据;
- 有些ARM处理器内部可以设置地址的错位.对于支持软件选择地址错位的处理器,在连接16-BIT FLASH的时候,硬件上可以不需要把地址线错位.读者设计的时候,请参考MCU的数据手册,以手册为准,以免造成不必要的麻烦;
- 假如处理器支持内部设置地址错位,在实际访问的时候,送出的地址实际上是在MCU内部做了错位处理,其作用是等效于硬件连接上的错位的.
上面的描述可能比较抽象,下面让我们来看2个ARM处理器访问16-BIT FLASH的例子:
例子 1:ARM处理器需要从地址0x0读取一个BYTE
- ARM处理器在地址线An-A0上送出信号0x0;
- 16-BIT FLASH在自己的地址信号An-A0上看到的地址是0x0,然后将地址0x0对应的16-BIT数据单元输出到D15-D0上;
- ARM处理器知道访问的是16-BIT的FLASH,从D7-D0上读取所需要的一个BYTE的数据.
例子 2:ARM处理器需要从地址0x1读取一个BYTE
- ARM处理器在地址线An-A0上送出信号0x1;
- 16-BIT FLASH在自己的地址信号An-A0上看到的地址依然是0x0, 然后将地址0x0对应的16-BIT数据单元输出到D15-D0上;
- ARM处理器知道访问的是16-BIT的FLASH,从D15-D8 上读取所需要的一个BYTE 的数据.
2.3 从软件角度来看 ARM 处理器和 NOR FLASH 的连接
从软件的角度来理解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点:
- ARM处理器访问8-BIT FLASH的时候,地址是逐一对应的;
- ARM处理器访问16-BIT FLASH的时候,地址肯定是错位的.
2.4 8-BIT FLASH 烧写驱动实例 - HY29F040
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上看到的地址.
a. 整片擦除操作
整片擦除操纵共需要6个周期的总线写操作:
- 将 0xAA写到 FLASH 地址 0x5555;
- 将 0x55 写到 FLASH 地址 0x2AAA;
- 将 0x80 写到 FLASH 地址 0x5555;
- 将 0xAA写到 FLASH 地址 0x5555;
- 将 0x55 写到 FLASH 地址 0x2AAA;
- 将 0x10 写到 FLASH 地址 0x5555.
对应的代码:
*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
b. SECTOR擦除操作
SECTOR的擦除操纵共需要6个周期的总线写操作:
- 将 0xAA写到 FLASH 地址 0x5555;
- 将 0x55 写到 FLASH 地址 0x2AAA;
- 将 0x80 写到 FLASH 地址 0x5555;
- 将 0xAA写到 FLASH 地址 0x5555;
- 将 0x55 写到 FLASH 地址 0x2AAA;
- 将 0x30 写到要擦除的 SECTOR 对应的地址.
对应的代码:
*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 对应的地址
c. BYTE擦除操作
写一个BYTE 的数据到FLASH中往,需要 4个周期的总线写操作:
- 将 0xAA写到 FLASH 地址 0x5555;
- 将 0x55 写到 FLASH 地址 0x2AAA;
- 将 0xA0 写到 FLASH 地址 0x5555;
- 将编程数据(BYTE)写到对应的编程地址上.
对应的代码:
*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的数据写到期看的地址
2.5 16-BIT FLASH 烧写驱动实例 - SST39VF160
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偏移量或偏移地址)
注意:
- 在本节后面的描述中,SysAddr16中的 SYSBASE代表的是FLASH的起始地址,而SysAddr16中的 OFFSET则代表了相对于FLASH起始地址的 HALF-WORD 偏移量或偏移地址.OFFSET 的值也是16-BIT FLASH在自己的地址信号An-A0上看到的值;
- 在SST39VF160的命令定义中,所有的地址都是针对FLASH的HALF-WORD地址,指的是在FLASH自己的地址信号An-A0上看到的地址.
整片擦除操作
整片擦除操纵共需要6个周期的总线写操作:
- 将 0x00AA写到 FLASH HALF-WORD 地址 0x5555;
- 将 0x0055 写到 FLASH HALF-WORD地址 0x2AAA;
- 将 0x0080 写到 FLASH HALF-WORD地址 0x5555;
- 将 0x00AA写到 FLASH HALF-WORD 地址 0x5555;
- 将 0x0055 写到 FLASH HALF-WORD地址 0x2AAA;
- 将 0x0010 写到 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) = 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个周期的总线写操作:
- 将 0x00AA写到 FLASH HALF-WORD 地址 0x5555;
- 将 0x0055 写到 FLASH HALF-WORD地址 0x2AAA;
- 将 0x0080 写到 FLASH HALF-WORD地址 0x5555;
- 将 0x00AA写到 FLASH HALF-WORD 地址 0x5555;
- 将 0x0055 写到 FLASH HALF-WORD地址 0x2AAA;
- 将 0x0030 写到要擦除的 SECTOR 对应的 HALF-WORD地址.
对应的代码:
*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个周期的总线写操作:
- 将 0x00AA写到 FLASH HALF-WORD 地址 0x5555;
- 将 0x0055 写到 FLASH HALF-WORD地址 0x2AAA;
- 将 0x00A0 写到 FLASH HALF-WORD 地址 0x5555;
- 将编程数据(HALF-WORD)写到对应的 HALF-WORD地址.
对应的代码:
*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;
本实验是基于TQ2440的实验平台,采用S3C2440(ARM9)的CPU,以ADS/MDK的开发环境,使用的是AM29LV160DB的NORFLASH,进行开发学习的一个NORFLASH的编写和读取.
(1) Nor FLASH 工作模式
Nor FLASH上电后处于数据读取状态(Reading Array Data).此状态可以进行正常的读.这和读取SDRAM/SRAM/ROM一样.(要是不一样的话,芯片上电后如何从NorFLASH中读取启动代码.~)
一般再对FLASH进行操作前都要读取芯片信息比如设备ID号.这样做的主要目的是为了判断自己写的程序是否支持该设备. NorFLASH支持2种方式获取ID号.一种是编程器所用的方法需要高电压(11.5V-12.5V).另一种方法就是所谓的in-system方法,就是在系统中通过NorFLASH的命令寄存器来完成.本文中只对in-system方法进行说明.此时需要切换到自动选择(Autoselect Command),这要通过发送命令来完成.命令类型见下图.注意:
进入自动选择(Autoselect Command)模式后需要发送复位命令才能回到数据读取状态(Reading Array Data).
在完成信息获取后一般就要擦除数据. NorFLASH支持扇区擦(Sector Erase)除和整片擦除(Chip Erase).这2种模式都有对应的命令序列.在完成擦除命令后会自动返回到数据读取(Reading Array Data)状态.在返回前可查询编程的状态.
完成擦除后就需要对芯片进行写入操作也就是编程.这就需要进入编程(Program)状态.在完成编程命令后会自动返回到数据读取(Reading Array Data)状态.在返回前可查询编程的状态.注意:编程前一定要先擦除.因为编程只能将'1'改写为'0',通过擦写可以将数据全部擦写为'1'.
以上是主要的操作其他操作还有写保护等,请参考芯片数据手册.
图1-1 命令与模式
(2) Nor FLASH 硬件连接
图2-1 Nor FLASH 引脚
图2-2 TQ2440中实际连接
上图中要注意的几个地方:
1.引脚47 为BYTE# :当其为高电平时数据输出为16bit模式(此时地址线为A19:A0).低电平为8bit模式. (此时地址线为A19:A1)上图中Pin47加VCC选用的是16bit模式有效地址线为A19:A0.
2.对于16bit模式要16bit对齐因此S3C2440A的LADDR1要与A0连接.此时要注意的是NorFLASH片内地址0x555对应S3C2440A的地址为baseaddr+0x555*2;其中baseaddr与NorFLASH映射的地址有关.一般NorFLASH放在Bank0.所以baseaddr=0,但是开启mmu后baseaddr=地址0映射到的新地址.0x555*2的原因是LADDR1与A0连接.也就是0x555表示片内第0x555个word(16bit).
3.引脚15为RYBY#输出引脚.用于输出Ready与Busy信号.实际用时可以不接.可用命令查询NorFLASH状态代替.
(3) Nor FLASH 模式编程
1. 读ID
上图中表明 读id共有4个指令周期,钱3个为写周期,最后一个为读周期;
#define FLASH_base 0x00000000 //FLASH接到bank0上
#define CMD_ADDR0 *((volatile U16 *)(0x555*2+FLASH_base))
#define CMD_ADDR1 *((volatile U16 *)(0x2aa*2+FLASH_base))
#define CMD_ADDR2 *((volatile U16 *)(0xaaa*2+FLASH_base))
/*** 读取 Am29LV800D 的ID ***/
U32 Get_Am29LV800D_ID(void)
{
U32 i="0";
CMD_ADDR0 = 0xaa; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0x90;
i = (*(U16 *)(0*2+FLASH_base))<<16;//Manufacturer ID = 01
CMD_ADDR0 = 0xaa; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0x90;
i |= *(U16 *)(1*2+FLASH_base);//device ID = 2249
return i;
}
2. 扇区擦除(Sector Erase)
扇区擦除命令序列的每个周期均为写周期.
void Am29LV800D_SectorErase(U32 SA)
{
CMD_ADDR0 = 0xAA; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0x80;
CMD_ADDR0 = 0xAA; CMD_ADDR1 = 0x55;//Word 模式命令序列
*((volatile U16 *)(SA)) = 0x30;
Waitfor_endofprg();//状态查询
}
3. 编程(Program)
编程 命令序列的每个周期均为写周期.
int Am29LV800D_WordProg (U32 PA,U16 PD)
{
CMD_ADDR0 = 0xAA; CMD_ADDR1 = 0x55; CMD_ADDR0 = 0xA0;
*((volatile U16 *)(PA)) = PD;// word模式,以上为4个命令周期
return(Waitfor_endofprg());//状态查询
}
4.写操作状态(WRITE OPERATION STATUS)
NorFLASH 提供几个数据位来确定一个写操作的状态,它们分别是: DQ2, DQ3, DQ5, DQ6,DQ7, and RY/BY#. 如上图所示.其中DQ7, RY/BY#引脚, 和 DQ6 中的每一个都提供了一种方法来判断一个编程或者擦除操作是否已经完成或正在进行中.实际编程中只需要使用其中的一种.
DQ7:Data# Polling bit,DQ7在编程时的状态变化.
在编程过程中从正在编程的地址中读出的数据的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可以区分NorFLASH是否处于擦除中或者擦除状态,但不能区分哪个快被选中擦除.因此需要这2个位来确定扇区和模式状态信息.
DQ5: 超时位(Exceeded Timing Limits),当编程或擦除操作超过了一个特定内部脉冲计数是DQ5=1;这表明操作失败.当编程时把'0'改为'1'就会导致DQ5=1,因为只有擦除擦做才能把'0'改为'1'.当错误发生后需要执行复位命令(见图1-1)才能返回到读数据状态.
DQ3: (扇区擦除计时位)Sector Erase Timer,只在扇区擦除指令时起作用.当擦除指令真正开始工作是DQ3=1,此时输入的命令(除擦除暂停命令外)都被忽略.DQ3=0,是可以添加附加的扇区用于多扇区擦除.
以上讲了这些状态为,实际只需要使用几个就行,比较简单的就是选择DQ5,DQ6/DQ2.如下例.
/****** Am29LV800D 的检测 ******/
int Waitfor_endofprg(void)
{
volatile U16 FLASHStatus,old;
old=*((volatile U16 *)0x0);//先读一次状态
while(1)
{
FLASHStatus=*((volatile U16 *)0x0);//再读一次状态
if( (old&0x40) == (FLASHStatus&0x40) ) break;//比较DQ6.相同说明设备已经就绪
if( FLASHStatus&0x20 ) //判断DQ5,为'1'则超时
{
old=*((volatile U16 *)0x0);
FLASHStatus=*((volatile U16 *)0x0);//在读取比较一次.因为可能在超时前刚好玩
//成操作.
if( (old&0x40) == (FLASHStatus&0x40) )
return 1;
else return 0;
}
old=FLASHStatus;
}
return 1;
}
以上文段转自:http://wiki.dzsc.com/info/5353.html#top
http://blog.163.com/stm32f103vct6@126/blog/static/1667434792010614690538/