NAND FLASH

来源:互联网 发布:java中的设计模式详解 编辑:程序博客网 时间:2024/04/30 22:13

  当OM1、OM0都是低电平——即开发板插上BOOT SEL跳线时,S3C2410从NAND Flash启动:NAND Flash的开始4k代码会被自动地复制到内部SRAM中。我们需要使用这4k代码来把更多的代码从NAND Flash中读到SDRAM中去。NAND Flash的操作通过NFCONFNFCMDNFADDRNFDATANFSTATNFECC六个寄存器来完成。在开始下面内容前,请打开S3C2410数据手册和NAND Flash K9F1208U0M的数据手册。
 在S3C2410数据手册218页,我们可以看到读写NAND Flash的操作次序
1. Set NAND flash configuration by NFCONF register.
2. Write NAND flash command onto NFCMD register.
3. Write NAND flash address onto NFADDR register.
4. Read/Write data from NFDATA while checking NAND flash status by NFSTAT
register
. R/nB signal should be checked before read operation or
after program operation.

 

下面依次介绍:
 1、NFCONF:设为0xf830——使能NAND Flash控制器(15位)初始化ECC(12位)、NAND Flash片选信号nFCE=1(inactive也就是未激活,真正使用时再让它等于0,一般在自启动后这位都会被置为1,等到真正需要使用NAND FLASH时才激活这位)设置TACLSTWRPH0TWRPH1。需要指出的是TACLS、TWRPH0和TWRPH1,请打开S3C2410数据手册218页,
可以看到这三个参数控制的是NAND Flash信号线CLE/ALE与写控制信号nWE的时序关系。我们设的值为TACLS=0,TWRPH0=3,TWRPH1=0,其含义为: TACLS=1个HCLK时钟TWRPH0=4个HCLK时钟,TWRPH1=1个HCLK时钟。请打开K9F1208U0M数据手册第13页,在表“AC Timing Characteristics for Command / Address / Data Input”中可以看到:
CLE setup Time = 0 ns,CLE Hold Time = 10 ns,
ALE setup Time = 0 ns,ALE Hold Time = 10 ns,
WE Pulse Width = 25 ns
可以计算,即使在HCLK=100MHz的情况下,TACLS+TWRPH0+TWRPH1=6/100 uS=60
ns,也是可以满足NAND Flash K9F1208U0M的时序要求的。

 

2、NFCMD
 对于不同型号的Flash,操作命令一般不一样。对于本板使用的K9F1208U0M,请打开其数据手册第8页“Table 1. Command Sets”,上面列得一清二楚。下面在分析源代码是会涉及到Flash的命令,该寄存器一般也只用后八位也就是[7:0]。

 

3、NFADDR:同样也只用到该寄存器的后八位 
4、NFDATA:只用到低8位,这个寄存器中涉及到读和写两种状态,如果NFCMD中是读命令,则可以直接从这个寄存器中读出对应地址的值,如果是写命令则是Programming data。
5、NFSTAT:只用到位0,0-busy,1-ready
6、NFECC:待补

 

 

现在来看一下如何从NAND Flash中读出数据,请打开K9F1208U0M数据手册第29页“PAGE READ”,跟本节的第2段是遥相呼应啊,提炼出来罗列如下(设读地址为addr)首先从汇编代码上来分析:

前面的对看门狗,SDRAM的初始化与上面都是一样的,主要来看看对NAND FLASH操作的这一块:

 

copy_myself:
 mov r10, lr  //前面代码中是通过bl copy_myself来进入到该程序段来执行,所以必须要保存lr,当使用bl或者blx跳转到子过程的时候,r14保存了返回地址,可以在调用过程结尾恢复

 @ inital
 mov r1, #NAND_CTL_BASE
 ldr r2, =0xf830  @ initial value
 str r2, [r1, #oNFCONF] //初始化NFCONF寄存器

 @ reset nand flash
 ldr r2, [r1, #oNFCONF]  //将NFCONF寄存器中的值赋给r2
 bic r2, r2, #0x800 @ nFCE active  在第一次操作NAND Flash前,通常复位一下,相当于NFCONF &= ~0x800 (使能NAND Flash) ,也就是将NFCONF寄存器的11位使能NAND FLASH的片选信号激活,这样后续才能对FLASH操作
 str r2, [r1, #oNFCONF]
 mov r2, #0xff  @ reset command
 strb r2, [r1, #oNFCMD]  //NFCMD = 0xff  (reset命令) 在这里就涉及到FLASH的命令行拉,这里0xff表示的是reset命令

 @ delay 
 mov r3, #0x0a  //一段延迟代码,用来等待一段时间然后再来检测NFSTAT是否准备好
1:
 subs r3, r3, #1
 bne 1b

 @ wait idle state 
2:
 ldr r2, [r1, #oNFSTAT]
 tst r2, #0x01  //tst指令:
位测试比较指令,主要来测试r1的第0位是否为1,将r2与0x01相与来查看r2的最后一位是否为1,不为1则表示NAND FLASH还没准备好,则循环等待

 beq 2b

 ldr r2, [r1, #oNFCONF]
 orr r2, r2, #0x800 @ nFCE inactive 如果FLASH已经准备好了,则将FLASH重新设置为未激活也就是NFCONF |=0x800,也就是相当于保存当前的状态,然后等到在C代码中要实际操作FLASH由重新激活
 str r2, [r1, #oNFCONF]

 ldr sp, =4096  @ nand_read.c needed 在上个例子中讲过因为涉及到C函数的调用所以需要进出栈,所以必须要设置堆栈
 ldr r0, =0x30000000 @ nand_read_ll argument 1 为C函数的调用准备好参数,r0:SDRAM的起始地址,为目的地址
 mov r1, #4096  @ nand_read_ll argument 2  因为NAND FLASH前4K的数据早就自动的读到steppingstone中,所以需要从4K之后开始读取,所以r1就是源地址
 mov r2, #1024  @ nand_read_ll argument 3  r3表示要读取数据的大小 在这里是要读取1K的数据
    @ 1024 enough for this example
 bl nand_read_ll //调用C函数nand_read_ll

 mov pc, r10  //调用C函数完毕后返回原调用处

 

继续来看看利用C语言来的FLASH进行操作:

 

#define NFCONF  (*(volatile unsigned int *)0x4e000000)
#define NFCMD  (*(volatile unsigned int *)0x4e000004)
#define NFADDR  (*(volatile unsigned char *)0x4e000008)
#define NFDATA  (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT  (*(volatile unsigned char *)0x4e000010)

 

上面是定义NAND FLASH寄存器的地址。再来看看真正的FLASH 读函数:

#define NAND_SECTOR_SIZE 512  //nand flash 每个扇区的大小为512B
#define NAND_BLOCK_MASK  (NAND_SECTOR_SIZE - 1)

/* low level nand read function */
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{    //可以看到从汇编语言中传递过来的参数buf为目的地址SDRAM的0x30000000,start_addr则是FLASH中的源地址
 int i, j;

 /*
  * K9F5608UOC asks for 512B per page, and read/write operation must
  * do with page. Therefore, first judge whether start_addr and size
  * are valid.
  */
 if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) { 检查FLASH中开始地址和药读取的大小是否是按页为单位的。
  return -1; /* invalid alignment */
 }

 /* chip Enable */因为要开始对FLASH进行操作了,所以需要将FLASH的片选激活
 NFCONF &= ~0x800;
 for (i=0; i<10; i++) {
  ;
 }

//正式开始从FLASH中读取数据到SDRAM中

 for (i=start_addr; i < (start_addr + size); i+=NAND_SECTOR_SIZE) {
  NFCMD = 0;//将FLASH命令寄存器设置为0x0表示是读命令

  /* Write Address */  这步得稍微注意一下,请打开K9F1208U0M数据手册第7页,那个表格列出了在地址操作的4个步骤对应的地址线,A8没用到:
  NFADDR = i & 0xff;
  NFADDR = (i >> 9) & 0xff;   (注意了,左移9位,不是8位) 
  NFADDR = (i >> 17) & 0xff;  (左移17位,不是16位)
  NFADDR = (i >> 25) & 0xff;  (左移25位,不是24位)

  wait_idle();在地址,命令都设置好后,我们就循环的等待NFSTAT的最后一位为1也就是准备好了,然后才开始从NFDATA读数据

   for(j=0; j < NAND_SECTOR_SIZE; j++) {   //连续读NFDATA寄存器512次,得到一页数据(512字节)
   *buf++ = (NFDATA & 0xff);
  }
 }

 /* chip Disable */
 NFCONF |= 0x800; /* chip disable */

 return 0;
}

 

 

本实验代码在NAND目录下,源代码为head.s、nand_read.c和main.c。head.s中关WATCH DOG、初始化SDRAM、初始化NAND Flash,然后调用nand_read.c中的nand_read_ll函数来从NAND Flash地址4096开始处复制到SDRAM中,最后,跳到main.c中的
main函数继续执行。代码本身没什么难度,与前面程序最大的差别就是“链接脚本”的引入:在上个实验中最后一段提到过在arm-linux-ld命令中,选项“-Ttext”可以使用选项“-Tfilexxx”来代替。在本实验中,使用“-Tnand.lds”。nand.lds
内容如下:
1 SECTIONS { 
2   firtst   0x00000000 : { head.o init.o } 也就是相当于FLASH的前4K用来存放head.o以及init.o,自启动时加载到steppingstone并且在该地址执行
3   second  0x30000000 : AT(4096) { main.o }
4 } 
 
 完整的连接脚本文件形式如下:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}
并非每个选项都是必须的,仅介绍nand.lds用到的:

1、secname:段名,对于nand.lds,段名为first和second
2、start:本段运行时的地址如果没有使用AT(xxx),则本段存储的地址也是start这就是我们所讲的运行时域和加载时域
3、AT( ldadr ):定义本段存储(加载)的地址
4、{ contents }:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
 
nand.lds的含义是:head.o放在0x00000000地址开始处,init.o放在hean.o后面,它们的运行地址是0x00000000;main.o放在地址4096(0x1000)开始处,但是它的运行地址在0x30000000,在运行前需要从4096处复制到0x30000000处。