第一期_Nand flash

NAND_FLASH操作原理

NAND FLASH原理图

NAND FLASH是一个存储芯片

那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A"

问1. 原理图上NAND FLASH和S3C2440之间只有数据线,怎么传输地址?

答1.在DATA0~DATA7上既传输数据,又传输地址当ALE为高电平时传输的是地址,

那么在数据线上是不是只传输数据和只传输地址呢?

我们参考NAND FLASH的芯片手册可以知道,对NAND FLASH的操作还需要发出命令,下面有个NAND FLASH的命令表格 

问2. 从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令怎么传入命令?

答2.在DATA0~DATA7上既传输数据,又传输地址,也传输命令:

1. 当ALE为高电平时传输的是地址。

2. 当CLE为高电平时传输的是命令。

3. 当ALE和CLE都为低电平时传输的是数据。

问3. 数据线既接到NAND FLASH,也接到NOR FLASH,还接到SDRAM、DM9000等等,怎么避免干扰?

答3. 这些设备,要访问必须"选中",没有选中的芯片不会工作,相当于没接一样。

问4. 假设烧写NAND FLASH,把命令、地址、数据发给它之后,NAND FLASH肯定不可能瞬间完成烧写的,怎么判断烧写完成?

答4. 通过状态引脚RnB来判断:它为高电平表示就绪,它为低电平表示正忙

问5. 怎么操作NAND FLASH呢?

答5. 根据NAND FLASH的芯片手册,一般的过程是:

发出命令

发出地址

发出数据/读数据

 

每个NAND FLASH都内嵌一些ID(譬如:厂家ID,设备ID),时序图从左往右看,纵向放是一列一列的看。

对于我们s3c2440来说,内部集成了一个NAND FLASH控制器,2440和外设连接的简易图,如下图所示 

 

NAND FLASH控制器,帮我们简化了对NAND FLASH的操作,下面来分析一下不使用NAND FLASH控制器和使用NAND FLASH控制器对外设NAND FLASH的操作。

发命令:

Nand flash s3c2440
选中芯片 NFCMMD=命令值
CLE设为高电平  
在DATA0~DATA7上输出命令值  
发出一个写脉冲  

发地址:

NAND FLASH S3C2440
选中芯片 NFADDR=地址值
ALE设为高电平  
在DATA0~DATA7上输出地址值  
发出一个写脉冲  

 

发数据:

NAND FLASH S3C2440
选中芯片 NFDATA=数据值
ALE,CLE设为低电平  
在DATA0~DATA7上输出数据值  
发出一个写脉冲  

读数据 :

NAND FLASH
S3C2440
选中芯片

val=NFDATA
发出读脉冲  
读DATA0~DATA7的数据  

用UBOOT来体验NAND FLASH的操作:

1. 读ID

  S3C2440 u-boot
选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1
发出命令0x90 NFCMMD=0x90 mw.b 0x4E000008 0x90
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
读数据得到0xEC val=NFDATA md.b 0x4E000010 1
读数据得到device code val=NFDATA md.b 0x4E000010 1
退出读ID的状态 NFCMMD=0xff mw.b 0x4E000008 0xff

对于存储为256M的NAND FLASH,需要28条地址线,来表示这个地址值,根据原理图可以,只用8根地址线,所以需要4个周期的地址,为了兼容更大容量的NAND FLASH,要发出5个周期的地址:(如下图所示) 

2,读数据

  S3C2440 u-boot
选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1
发出命令0x00
NFCMMD=0x00

mw.b 0x4E000008 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出命令0x30 NFCMMD=0x30 mw.b 0x4E000008 0x30
读数据得到0x17 val=NFDATA md.b 0x4E000010 1
读数据得到0x00 val=NFDATA md.b 0x4E000010 1
读数据得到0x00 val=NFDATA md.b 0x4E000010 1
读数据得到0xea val=NFDATA md.b 0x4E000010 1
退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff

NandFlash时序及初始化

存储芯片的编程NAND FLASH存储芯片编程
初始化 主控芯片的NAND FLASH控制器的初始化
识别 读取ID
读操作 一次读一个页(page)
写操作 一次写一个页(page)
擦除 一次擦除一个块(block)

NAND FLASH控制器的时序,是为了让NAND FLASH外设工作起来,假如外接不同的 NAND FLASH外设,那么它的操作时序可能就会不同,所以NAND FLASH控制器发出 的时序图,就是不一样的,所以我们根据NAND FLASH外设来设置NAND FLASH控制器, 

NAND FLASH时序图,如下所示:

我们在汇编语言中已经设置HCLK为100MHZ,一个周期T = 1000/100 = 10s,通过上面三个图可以知道:TACLS的值可以为0;TWRPH0的值可以为1;TWRPH1的值可以为0。

所以NFCONF寄存器设置如下:

#define  TACLS   0
#define  TWRPH0  1
#define  TWRPH1  0
/*设置NAND FLASH的时序*/
NFCONF = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

到此设置NAND FLASH的时序已经设置完了,我们接着来使能,使能实在NFCONT。

MODE [0]: 设置为1,使能NAND FLASH。

Reg_nCE [1]: 设置为1,禁止片选。因为我们现在还没有使用。为例错误的操作。

InitECC [4]: 初始化ECC的编码器,后边要使用,我们设置为1,来初始化。

所以NFCONF寄存器设置如下:

/*使能NAND FLASH控制器,初始化ECC,禁止片选*/
NFCONT = (1<<4) | (1<<1) | (1<<0);

NandFlash的芯片id读取

上节课我们讲解了NAND FLASH的初始化,这节课我们来讲解读取NAND FLASH的ID, 我们可以参考NAND FLASHh的芯片手册,如下图所示:(NAND FLASH读操作时序图) 

我们一般先操作片选使能,只有片选使能之后才能进行后边的操作,片选是能代码如下:

void nand_select(void)
{
	/*使能片选*/
	NFCONT &=~(1<<1);
}
有使能片选,一定有禁止片选,禁止片选的代码如下:
void nand_deselect(void) { /*禁止片选*/ NFCONT |= (1<<1); }

我们按照从左往右的时间点,来分析,片选信号像一个总开关,只有使能了片选信号,后续的操作才会有意义,我们使能片选信号之后,片选引脚nCE后续一直为低电平,在前面的命令时序图中知道tCLS和tWP最小的时间参数都是12us,就表明CLE和nWE这两个信号可以同时发出,就表示要命令了,对于写什么命令,就要看数据总线上要发送的命令了,当CLE从高电平变为低电平后,表示上次的写操作已经结束了。

对于上面复杂的时序,我们可以使用2440上的NAND FLASH控制器简化操作,只需要往NFCMMD寄存器写入要传输的命令就可以了,NAND FLASH控制器默认把上面复杂的时序发出来。

发命令后,后面就需要发送地址了,当nWE和ALE有效的时候,表示写地址,上图中,要写入的地址是0x00,当ALE从高电平变为低电平的时候,表示写地址结束,我们可以简化为:往NFADDR寄存器中写值就可以了,比如:NFADDR=0x00。

下面我们写代码:发命令的函数,和发地址的函数代码如下:

void nand_cmd(unsigned char cmd)
{
	volatile int i;
	NFCCMD = cmd;
	for(i=0; i<10; i++);
}

void nand_addr_byte(unsigned char addr)
{
	volatile int i;
	NFADDR = addr;
	for(i=0; i<10; i++);
}

接下来就可以读取数据了,数据可以直接通过读取NFDATA寄存器里面数据来获得数据,根据时序图,是读5个字节的数据,代码如下:

unsigned char nand_data(void)
{
	return	NFDATA;
}

读芯片ID之前先打开片选, 读取芯片ID函数,代码如下:

 1 void nand_chip_id(void)
 2 { 
 3     unsigned char buf[5]={0};
 4     
 5     nand_select(); 
 6     nand_cmd(0x90);
 7     nand_addr_byte(0x00);
 8 
 9     buf[0] = nand_data();
10     buf[1] = nand_data();    
11     buf[2] = nand_data();
12     buf[3] = nand_data();
13     buf[4] = nand_data();    
14     nand_deselect();     
15 
16     printf("maker   id  = 0x%x\n\r",buf[0]);
17     printf("device  id  = 0x%x\n\r",buf[1]);    
18     printf("3rd byte    = 0x%x\n\r",buf[2]);        
19     printf("4th byte    = 0x%x\n\r",buf[3]);            
20     printf("page  size  = %d kb\n\r",1  <<  (buf[3] & 0x03));    
21     printf("block size  = %d kb\n\r",64 << ((buf[3] >> 4) & 0x03));    
22     printf("5th byte    = 0x%x\n\r",buf[4]);
23 }
 1 void nand_flash_test(void)
 2 {
 3     char c;
 4 
 5     while (1)
 6     {
 7         /* 打印菜单, 供我们选择测试内容 */
 8         printf("[s] Scan nand flash\n\r");
 9         printf("[e] Erase nand flash\n\r");
10         printf("[w] Write nand flash\n\r");
11         printf("[r] Read nand flash\n\r");
12         printf("[q] quit\n\r");
13         printf("Enter selection: ");
14 
15         c = getchar();
16         printf("%c\n\r", c);
17 
18         /* 测试内容:
19          * 1. 识别nand flash
20          * 2. 擦除nand flash某个扇区
21          * 3. 编写某个地址
22          * 4. 读某个地址
23          */
24         switch (c)         
25         {
26             case 'q':
27             case 'Q':
28                 return;
29                 break;
30                 
31             case 's':
32             case 'S':
33                 nand_chip_id();
34                 break;
35 
36             case 'e':
37             case 'E':
38                 break;
39 
40             case 'w':
41             case 'W':
42                 break;
43 
44             case 'r':
45             case 'R':
46                 break;
47             default:
48                 break;
49         }
50     }
51 }

在主函数中调用nand flash的初始化函数,和nand flash的测试函数。

 1 int main(void)
 2 {
 3     led_init();
 4     //interrupt_init();  /* 初始化中断控制器 */
 5     key_eint_init();   /* 初始化按键, 设为中断源 */
 6     //timer_init();
 7     
 8     puts("\n\rg_A = ");
 9     printHex(g_A);
10     puts("\n\r");
11 
12     //nor_flash_test();
13     nand_init();
14     nand_flash_test();
15     
16     return 0;
17 }

NandFlash的数据读取

在上节 我们实现了芯片ID的读取,可是那个程序已经超过了4k,我们想把它烧到开发板的话,必需把它烧写到NOR FLASH上去,这节我们来讲解NAND FLASH数据的读取,并且实现超过4k的程序从NAND FLASH启动。

下图为NAND FLASH内部结构图,从图中可以可以知道,一个page含有2k 字节的页数据,和64字节的oob区,后面会介绍页数据和oob区有什么关系。

下图的表格,来说明NAND FLASH内部结构,前面2K(0~2047)表示页数据,后边64字节(2048~2111)表示oob。

问:CPU想读取,第2048个数据,它是哪以一个?

 答:是Page1的第0个字节。CPU使用某个地址访问数据的时候,是在页数据空间来寻址的,根本就看不到oob区。

 我们知道NAND FLASH 和 NOR FLASH相比有个缺点,NAND FLASH读或写一页数据的时候,可能会发生位反转,里面可能有一位是错误的,为了解决这个问题,引入oob区, 它写页数据的时候,把数据写进页数据的同时会生成一个校验码,把这个校验码写进oob区里面,当读数据的时候,读出1页数据,读取1数据里面有可能有某一位发生错误,它继续读出原来的校验码,使用oob区里面的校验码,来修正页数据里面的数据。从这里我们可以得出一个结论,oob区的存在是为了解决NAND FLASH的缺陷而存在的。

CPU: 只关心数据,不需要看到oob区的校验码(把数据读出来,然后进行校验再把正确的数据返回,就可以了)。CPU想使用某个addr来访问数据的时候,addr是在页数据区间来寻址的,addr根本不会在oob区里面寻址。 

为了形象在下面说一个幽默的对话来说明一下CPU和NAND FLASH的功能:

CPU大爷: 小nand啊,你的性能比不上小nor啊,听说你有位反转的毛病

Nand  : 是的,大爷,位反转是我天生的毛病,时有时无

CPU大爷: 靠,你说你价格便宜容量大,这不是害我嘛

Nand  : 没事,我有偏方,用OOB就可以解决这问题

CPU大爷: 得得得,你那偏方是什么也别告诉我,我只管能读写正确的数据

Nand  : 是的,大爷,我这OOB偏方也就我自个私下使用。您就像使用nor一样使唤我就可以了

下面我们开始写程序,想去读NAND FLASH应该怎样操作,下面是nand flash的地址周期。 

下图为读NAND FLASH的时序操作: 

读NAND FLASH步骤:(从程序的角度来说),我们需要先发出00命令再发出5个周期的地址,再发出30命令,然后就可以读数据了。比如:我想访问某个地址的数据,需要确定在哪一行page(row),在哪一列col(0~2047)。从NAND FLASH的地址周期中可以看出来,先发出2个col(列地址),再发出3个(Row)行地址。 下面是程序的编写:

wait_ready函数等待NAND FLASHh空闲,从上图可以看出当NFSTAT寄存器[0]的值为1时NAND FLASH是空闲的,我们可以通过该位来判断NAND FLASH是否繁忙。代码如下:

1 void wait_ready(void)
2 {
3     while (!(NFSTAT & 1));
4 }

nand_read函数为NAND FLASH的读函数,代码如下:

 1 void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
 2 {
 3     int i = 0;
 4     int page = addr / 2048;
 5     int col  = addr & (2048 - 1);
 6     
 7     nand_select(); 
 8 
 9     while (i < len)
10     {
11         /* 发出00h命令 */
12         nand_cmd(00);
13 
14         /* 发出地址 */
15         /* col addr */
16         nand_addr_byte(col & 0xff);
17         nand_addr_byte((col>>8) & 0xff);
18 
19         /* row/page addr */
20         nand_addr_byte(page & 0xff);
21         nand_addr_byte((page>>8) & 0xff);
22         nand_addr_byte((page>>16) & 0xff);
23 
24         /* 发出30h命令 */
25         nand_cmd(0x30);
26 
27         /* 等待就绪 */
28         wait_ready();
29 
30         /* 读数据 */
31         for (; (col < 2048) && (i < len); col++)
32         {
33             buf[i++] = nand_data();            
34         }
35         if (i == len)
36             break;
37 
38         col = 0;
39         page++;
40     }
41     
42     nand_deselect();     
43 }

在init.c文件中,加上如下代码,用来判断所使用的FLASH是NOR FLASH还是NAND FLASH。代码如下:

 1 int isBootFromNorFlash(void)
 2 {
 3     volatile unsigned int *p = (volatile unsigned int *)0;
 4     unsigned int val = *p;//把原来的p中的内容保存起来
 5 
 6     *p = 0x12345678;
 7     if (*p == 0x12345678)
 8     {
 9         /* 写成功, 对应nand启动 */
10         *p = val;
11         return 0;
12     }
13     else
14     {
15         return 1;
16     }
17 }

在init.c文件中的copy2sdram函数里面加上如下代码,用来支持NAND FLASH启动,当isBootFromNorFlash函数的返回值为1时,是从NOR FLASH启动,当isBootFromNorFlash函数的返回值为0是,是从NAND FLASH启动。

 1 if (isBootFromNorFlash())
 2     {
 3         while (dest < end)
 4         {
 5             *dest++ = *src++;
 6         }
 7     }
 8     else
 9     {
10         nand_init();
11         nand_read(src, dest, len);
12     }
13 }

NandFlash的擦除与烧写

我们本节需要做的事情:

1,实现nand_erase

2, 实现nand_write

3, 实现测试菜单

本节讲的NAND FLASH的烧写和擦除还是比较简单的,它只涉及到页数据区,不涉及到oob区,擦出的时候是以块为单位。下图为擦除的时序图: 

我们就根据擦除的时序图发出对应的命令和地址,NAND FLASH是以块为单位进行擦除的,假如我们传入len的值为1,但是它仍然会擦出一个块(128k字节),我们根据芯片手册,来操作NAND FLASH的擦除操作,函数功能:从addr地址开始,擦除len长度的数据。代码如下:

 1 int nand_erase(unsigned int addr, unsigned int len)
 2 {
 3     int page = addr / 2048;//确定在第几页
 4 
 5     if (addr & (0x1FFFF))//
 6     {
 7         printf("nand_erase err, addr is not block align\n\r");
 8         return -1;
 9     }
10     
11     if (len & (0x1FFFF))//
12     {
13         printf("nand_erase err, len is not block align\n\r");
14         return -1;
15     }
16     
17     nand_select(); //片选
18 
19     while (1)
20     {
21         page = addr / 2048;//在第几页
22         
23         nand_cmd(0x60);
24         
25         /* row/page addr */
26         nand_addr_byte(page & 0xff);
27         nand_addr_byte((page>>8) & 0xff);
28         nand_addr_byte((page>>16) & 0xff);
29 
30         nand_cmd(0xD0);
31 
32         wait_ready();
33 
34         len -= (128*1024);
35         if (len == 0)
36             break;
37         addr += (128*1024);
38     }
39     
40     nand_deselect(); //取消片选    
41     return 0;
42 }

操作NAND FLASH之前要,选中芯片,然后就可以根据芯片手册来操作NAND FLASH的擦除操作了,操作完之后,要取消片选。

往NAND FLASH写数据时,只需要把要写的数据复制给NFDATA寄存器即可。代码如下:

1 void nand_w_data(unsigned char val)
2 {
3     NFDATA = val;
4 }

下图为烧写的时序图: 

从上图中的NAND FLASH烧写时序图可以知道对于NAND FLASH的烧写,先发出0x80命令,再发出地址周期,然后发出要烧写的数据,最后发出0x10,就开始内部烧写,然后等待烧写成功。(我们写数据的时候是逐页写的,开始要烧写的数据地址可能不是该页的起始地址)。操作之前需要选中片选,操作完之后取消片选

 1 void nand_write(unsigned int addr, unsigned char *buf, unsigned int len)
 2 {
 3     int page = addr / 2048;//在第几行
 4     int col  = addr & (2048 - 1);//在第几列
 5     int i = 0;
 6 
 7     nand_select(); 
 8 
 9     while (1)
10     {
11         nand_cmd(0x80);
12 
13         /* 发出地址 */
14         /* col addr */
15         nand_addr_byte(col & 0xff);
16         nand_addr_byte((col>>8) & 0xff);
17         
18         /* row/page addr */
19         nand_addr_byte(page & 0xff);
20         nand_addr_byte((page>>8) & 0xff);
21         nand_addr_byte((page>>16) & 0xff);
22 
23         /* 发出数据 */
24         for (; (col < 2048) && (i < len); )
25         {
26             nand_w_data(buf[i++]);
27         }
28         nand_cmd(0x10);
29         wait_ready();
30 
31         if (i == len)
32             break;
33         else
34         {
35             /* 开始下一个循环page */
36             col = 0;
37             page++;
38         }
39         
40     }
41     
42     nand_deselect();     
43 }

我们封装擦除操作NAND FLASH函数的时候,每一次擦除的大小是一个块(128*1024)代码如下:

void do_erase_nand_flash(void)
{
    unsigned int addr;
    
    /* 获得地址 */
    printf("Enter the address of sector to erase: ");
    addr = get_uint();

    printf("erasing ...\n\r");
    nand_erase(addr, 128*1024);//从哪开始,擦除多大
}
 1 void do_read_nand_flash(void)
 2 {
 3     unsigned int addr;
 4     volatile unsigned char *p;
 5     int i, j;
 6     unsigned char c;
 7     unsigned char str[16];
 8     unsigned char buf[64];
 9     
10     /* 获得地址 */
11     printf("Enter the address to read: ");
12     addr = get_uint();
13 
14     nand_read(addr, buf, 64);
15     p = (volatile unsigned char *)buf;
16 
17     printf("Data : \n\r");
18     /* 长度固定为64 */
19     for (i = 0; i < 4; i++)
20     {
21         /* 每行打印16个数据 */
22         for (j = 0; j < 16; j++)
23         {
24             /* 先打印数值 */
25             c = *p++;
26             str[j] = c;
27             printf("%02x ", c);
28         }
29 
30         printf("   ; ");
31 
32         for (j = 0; j < 16; j++)
33         {
34             /* 后打印字符 */
35             if (str[j] < 0x20 || str[j] > 0x7e)  /* 不可视字符 */
36                 putchar('.');
37             else
38                 putchar(str[j]);
39         }
40         printf("\n\r");
41     }
42 }

NAND FLASH的烧写封装函数代码如下:

 1 void do_write_nand_flash(void)
 2 {
 3     unsigned int addr;
 4     unsigned char str[100];
 5     int i, j;
 6     unsigned int val;
 7     
 8     /* 获得地址 */
 9     printf("Enter the address of sector to write: ");
10     addr = get_uint();
11 
12     printf("Enter the string to write: ");
13     gets(str);
14 
15     printf("writing ...\n\r");
16     nand_write(addr, str, strlen(str)+1);
17 
18 }

NAND FLASH的测试菜单函数代码如下:

 1 void nand_flash_test(void)
 2 {
 3     char c;
 4 
 5     while (1)
 6     {
 7         /* 打印菜单, 供我们选择测试内容 */
 8         printf("[s] Scan nand flash\n\r");
 9         printf("[e] Erase nand flash\n\r");
10         printf("[w] Write nand flash\n\r");
11         printf("[r] Read nand flash\n\r");
12         printf("[q] quit\n\r");
13         printf("Enter selection: ");
14 
15         c = getchar();
16         printf("%c\n\r", c);
17 
18         /* 测试内容:
19          * 1. 识别nand flash
20          * 2. 擦除nand flash某个扇区
21          * 3. 编写某个地址
22          * 4. 读某个地址
23          */
24         switch (c)         
25         {
26             case 'q':
27             case 'Q':
28                 return;
29                 break;
30                 
31             case 's':
32             case 'S':
33                 nand_chip_id();
34                 break;
35 
36             case 'e':
37             case 'E':
38                 do_erase_nand_flash();
39                 break;
40 
41             case 'w':
42             case 'W':
43                 do_write_nand_flash();
44                 break;
45 
46             case 'r':
47             case 'R':
48                 do_read_nand_flash();
49                 break;
50             default:
51                 break;
52         }
53     }
54 }

 

posted @ 2019-03-11 10:09  夜空的北极星  阅读(294)  评论(0编辑  收藏  举报