好久没有更新博文了,最近确实懒惰了,哎,不说了。开始今天的学习吧,今天让我们来学习下嵌入式开发中不可缺少的模块-NAND FLASH。
FLASH主要分为两类: NAND FLASH 和NOR FLASH
两者的主要区别和适用场合如下
参数 | NAND | NOR |
---|---|---|
容量 | 1-32MB | NAND:16-512MB 甚至更大 |
XIP | 不可以 | 可以直接在其上运行代码 |
接口 | IO接口 | RAM接口 |
访问方法 | 顺序访问 | 随机访问 |
可靠性 | 比较低,位反转比较常见 | 比较高 |
可擦写次数 | 10000-100000 | 100000-1000000 |
主要用途 | 保存数据和代码 | 保存代码 |
总结下的话就是NOR FLASH可以支持XIP,可靠性高,但是读写速度相对于NAND FLASH来说慢一些
NAND 不支持XIP,但是它的性价比比较高,但是也有一个比较严重的问题就是发生位翻转的概率比较大,因此NAND FLASH 在使用的时候都配备软件的ECC或者硬件的ECC。
在本人接触的有限的产品中,NAND FLASH用的比较多,功能类似PC的硬件,本文以三星公司生产的K9F1208U0M为例进行介绍NAND FLASH
K9F1208U0M的主要引脚如下:
对于NAND FLASH访问的命令、地址、数据都是通过I/O输入/输出,这种形式减少了
芯片的引脚个数,并使得系统很容易升级到更大的容量。
写入命令、地址或者数据时,都需要将WE#、CE#、信号同时拉低。数据在WE#信号的
上升沿被NAND FLASH锁存;命令锁存信号CLE、地址锁存信号ALE用来分辨、锁存命令命令或者地址
地址序列
K9F1208U0M一页大小为528字节,而列地址A0~A7可以寻址的范围是256字节,所以必须辅以其他手段才能完全寻址这528字节。将一页分为A、B、C、三个区;A区为0~255字节,B区为256~511字节,C区为512~527字节。访问某页,需要选定特定的区,这成为“使地址指针指向特定的区”。通过三个命令实现:命令00h让地址指针指向A区、命令01h让地址指针指向B区、命令50h让地址指针指向C区。命令00h和50h会使得访问FLASH的地址指针一直从A区或C区开始,除非发出了其他的修改地址的命令。命令01h的效果只能维持一次,当前的读、写、擦除、复位或者上电操作完成后,地址指针重新指向A区。写A区或C区的数据时,必须在发出命令80h之前发出00h或者50h;写B区的数据,发出命令01h后必须紧接就发出命令80h
下面讲下NAND FLASH的读写操作次序:
- 设置NFCONF(对于S3C2440还要设置NFCONT)寄存器,配置NAND Flash
- 向NFCMD寄存器写入命令,命令字参考具体的FLASH手册
- 向NFADDR寄存器写入地址
- 读/写数据:通过寄存器NFSTAT检测NAND Flash的状态,在启动某个操作后,应该检测R/nB信号以确定该操作是否完成、成功
举个读操作的例子:
- 设置NFCONF,NFCONT
NFCONF=0x300(TACLS=0,TWRPH0=3,TWRPH1=0)
设置NFCONT寄存器如下,表示使能NAND FlASH控制器、禁止控制引脚信号nFCE、初始化ECC
NFCONT = (1<<4)|(1<<1)|(1<<0)
2.在第一次操作NAND Flash前,通常复位一下NAND flash
NFCONT &= ~(1<<1) (发出片选信号)
NFCMD = 0xff (reset信号)
然后循环查询NFSTAT位0,直到它等于1。
最后禁止片选信号,在实际使用NAND Flash时再使能
NFCONT |= 0X2
3.发出读命令
先使能NAND Flash,然后发出读命令
NFCONT &= ~(1<<1)(发出片选信号)
NFCMD = 0 (读命令)
4 发出地址信号
在上面的表中列举出了地址的操作步骤和对应的以及对应的地址线,地址线A8不需要直接设置由A8设置,当读命令为0时,A8=0;当读命令为1时,A8=0,具体地址设置如下。
NFADDR = addr & 0xff
NFADDR = (addr>>9)&0xff
NFADDR = (addr>>17)&0xff
NFADDR = (addr>>25)&0xff
5 循环查询NFSTAT位0,直到它等于1,这时就能读取数据了
6 连续读NFDATA既存器512次,得到一页数据
7 最后,禁止 NAND Flash的片选信号
下面以实际代码讲解NAND Flash操作。nand实验的源文件为head.s、init.c和main.c。程序的功能是将点灯的程序放在NAND flash地址4096后,当程序启动后通过NAND flash控制器将它们读出来执行。
连接脚本nand.lds把所有的源程序分为两部分,nand.lds代码如下:
SECTIONS
{
firtst 0x00000000 : { head.o init.o nand.o}
second 0x30000000 : AT(4096) { main.o }
}
其中链接脚本的第二行表示head.o、init.o、nand、这3个文件的运行地址为0,它们在生成的映像文件中的偏移地址也为0(从0开始存放)
第3行表示main.o,它在生成的映像文件中的偏移地址为4096
下面开始分析源代码head.s
.text
.global _start
_start:
@函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义
ldr sp, =4096 @设置堆栈
bl disable_watch_dog @关WATCH DOG
bl memsetup @初始化SDRAM
bl nand_init @初始化NAND Flash
@将NAND Flash中地址4096开始的1024字节代码(main.c编译得到)复制到SDRAM中
@nand_read_ll函数需要3个参数:
ldr r0, =0x30000000 @1. 目标地址=0x30000000,这是SDRAM的起始地址
mov r1, #4096 @2. 源地址=4096,连接的时候,main.c中的代码在NAND Flash地址4096处
mov r2, #2048 @3. 复制长度= 2048(bytes)
bl nand_read @调用C函数nand_read
ldr sp, =0x34000000 @设置栈
ldr lr, =halt_loop @设置返回地址
ldr pc, =main @b指令和bl指令只能前后跳转32M的范围,所以这里使用向pc赋值的方法进行跳转
halt_loop:
b halt_loop
head.S中调用init.c中的函数来管WATCH DOG、初始化SDRAM;调用nand.c中函数来初始化NAND Flash,然后将main.c中的代码从NAND Flash地址4096开始处复制到SDRAM中,最后跳到main.c中的main函数继续执行。
下面再仔细分析nand_read函数
/* 读函数 */
void nand_read(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return ; /* 地址或长度不对齐 */
}
/* 选中芯片 */
nand_select_chip();
for(i=start_addr; i < (start_addr + size);)
{
/* 发出READ0命令 */
write_cmd(0);
/* Write Address */
write_addr(i);
wait_idle();
for(j=0; j < NAND_SECTOR_SIZE; j++, i++)
{
*buf = read_data();
buf++;
}
}
/* 取消片选信号 */
nand_deselect_chip();
return ;
}
函数功能为从NAND flash中将起始地址为start_addr开始的size大小的数据拷贝到buf对应的地址处。从上面的程序可以看出读Flash数据的过程如下:
- 选择芯片
- 发出读命令
- 发出地址
- 等待数据就绪
- 读取数据
- 结束后,取消片选信号
好的,本节到此为止了