“第016课 Nand Flash”的版本间的差异
第240行: | 第240行: | ||
上节课我们讲解了NAND FLASH的初始化,这节课我们来讲解读取NAND FLASH的ID, | 上节课我们讲解了NAND FLASH的初始化,这节课我们来讲解读取NAND FLASH的ID, | ||
我们可以参考NAND FLASHh的芯片手册,如下图所示:(NAND FLASH读操作时序图) | 我们可以参考NAND FLASHh的芯片手册,如下图所示:(NAND FLASH读操作时序图) | ||
+ | [[File:chapter16_lesson3_001.png|800px]] | ||
+ | 我们一般先操作片选使能,只有片选使能之后才能进行后边的操作,片选是能代码如下: | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | void nand_select(void) | ||
+ | { | ||
+ | /*使能片选*/ | ||
+ | NFCONT &=~(1<<1); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 有使能片选,一定有禁止片选,禁止片选的代码如下: | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | void nand_deselect(void) | ||
+ | { | ||
+ | /*禁止片选*/ | ||
+ | NFCONT |= (1<<1); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 读ID的操作时序图,如下所示 | ||
+ | [[File:chapter16_lesson3_002.png|800px]] | ||
+ | 我们按照从左往右的时间点,来分析,片选信号像一个总开关,只有使能了片选信号,后续的操作才会有意义,我们使能片选信号之后,片选引脚nCE后续一直为低电平,在前面的命令时序图中知道tCLS和tWP最小的时间参数都是12us,就表明CLE和nWE这两个信号可以同时发出,就表示要命令了,对于写什么命令,就要看数据总线上要发送的命令了,当CLE从高电平变为低电平后,表示上次的写操作已经结束了。 | ||
+ | |||
+ | 对于上面复杂的时序,我们可以使用2440上的NAND FLASH控制器简化操作,只需要往NFCMMD寄存器写入要传输的命令就可以了,NAND FLASH控制器默认把上面复杂的时序发出来。 | ||
+ | |||
+ | 发命令后,后面就需要发送地址了,当nWE和ALE有效的时候,表示写地址,上图中,要写入的地址是0x00,当ALE从高电平变为低电平的时候,表示写地址结束,我们可以简化为:往NFADDR寄存器中写值,NFADDR=0x00,就可以了。 | ||
+ | |||
+ | 下面我们写代码:发命令的函数,和发地址的函数代码如下: | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | 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++); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 接下来就可以读取数据了,数据可以直接通过读取NFDATA寄存器里面数据来获得数据,根据时序图,是读5个字节的数据,代码如下: | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | unsigned char nand_data(void) | ||
+ | { | ||
+ | return NFDATA; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 读芯片ID之前先打开片选, 读取芯片ID函数,代码如下: | ||
+ | <syntaxhighlight lang="c" > | ||
+ | void nand_chip_id(void) | ||
+ | { | ||
+ | unsigned char buf[5]={0}; | ||
+ | |||
+ | nand_select(); | ||
+ | nand_cmd(0x90); | ||
+ | nand_addr_byte(0x00); | ||
+ | |||
+ | buf[0] = nand_data(); | ||
+ | buf[1] = nand_data(); | ||
+ | buf[2] = nand_data(); | ||
+ | buf[3] = nand_data(); | ||
+ | buf[4] = nand_data(); | ||
+ | nand_deselect(); | ||
+ | |||
+ | printf("maker id = 0x%x\n\r",buf[0]); | ||
+ | printf("device id = 0x%x\n\r",buf[1]); | ||
+ | printf("3rd byte = 0x%x\n\r",buf[2]); | ||
+ | printf("4th byte = 0x%x\n\r",buf[3]); | ||
+ | printf("page size = %d kb\n\r",1 << (buf[3] & 0x03)); | ||
+ | printf("block size = %d kb\n\r",64 << ((buf[3] >> 4) & 0x03)); | ||
+ | printf("5th byte = 0x%x\n\r",buf[4]); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 下面我们再写一个打印菜单的函数,在菜单中调用读取芯片ID的函数,代码如下 | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | oid nand_flash_test(void) | ||
+ | { | ||
+ | char c; | ||
+ | |||
+ | while (1) | ||
+ | { | ||
+ | /* 打印菜单, 供我们选择测试内容 */ | ||
+ | printf("[s] Scan nand flash\n\r"); | ||
+ | printf("[e] Erase nand flash\n\r"); | ||
+ | printf("[w] Write nand flash\n\r"); | ||
+ | printf("[r] Read nand flash\n\r"); | ||
+ | printf("[q] quit\n\r"); | ||
+ | printf("Enter selection: "); | ||
+ | |||
+ | c = getchar(); | ||
+ | printf("%c\n\r", c); | ||
+ | |||
+ | /* 测试内容: | ||
+ | * 1. 识别nand flash | ||
+ | * 2. 擦除nand flash某个扇区 | ||
+ | * 3. 编写某个地址 | ||
+ | * 4. 读某个地址 | ||
+ | */ | ||
+ | switch (c) | ||
+ | { | ||
+ | case 'q': | ||
+ | case 'Q': | ||
+ | return; | ||
+ | break; | ||
+ | |||
+ | case 's': | ||
+ | case 'S': | ||
+ | nand_chip_id(); | ||
+ | break; | ||
+ | |||
+ | case 'e': | ||
+ | case 'E': | ||
+ | break; | ||
+ | |||
+ | case 'w': | ||
+ | case 'W': | ||
+ | break; | ||
+ | |||
+ | case 'r': | ||
+ | case 'R': | ||
+ | break; | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 在主函数中调用nand flash的初始化函数,和nand flash的测试函数。 | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | int main(void) | ||
+ | { | ||
+ | led_init(); | ||
+ | //interrupt_init(); /* 初始化中断控制器 */ | ||
+ | key_eint_init(); /* 初始化按键, 设为中断源 */ | ||
+ | //timer_init(); | ||
+ | |||
+ | puts("\n\rg_A = "); | ||
+ | printHex(g_A); | ||
+ | puts("\n\r"); | ||
+ | |||
+ | //nor_flash_test(); | ||
+ | nand_init(); | ||
+ | nand_flash_test(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </syntaxhighlight> |
2018年2月2日 (五) 17:52的版本
第001节_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 |
第002节_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);
第003节_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);
}
读ID的操作时序图,如下所示 我们按照从左往右的时间点,来分析,片选信号像一个总开关,只有使能了片选信号,后续的操作才会有意义,我们使能片选信号之后,片选引脚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函数,代码如下:
void nand_chip_id(void)
{
unsigned char buf[5]={0};
nand_select();
nand_cmd(0x90);
nand_addr_byte(0x00);
buf[0] = nand_data();
buf[1] = nand_data();
buf[2] = nand_data();
buf[3] = nand_data();
buf[4] = nand_data();
nand_deselect();
printf("maker id = 0x%x\n\r",buf[0]);
printf("device id = 0x%x\n\r",buf[1]);
printf("3rd byte = 0x%x\n\r",buf[2]);
printf("4th byte = 0x%x\n\r",buf[3]);
printf("page size = %d kb\n\r",1 << (buf[3] & 0x03));
printf("block size = %d kb\n\r",64 << ((buf[3] >> 4) & 0x03));
printf("5th byte = 0x%x\n\r",buf[4]);
}
下面我们再写一个打印菜单的函数,在菜单中调用读取芯片ID的函数,代码如下
oid nand_flash_test(void)
{
char c;
while (1)
{
/* 打印菜单, 供我们选择测试内容 */
printf("[s] Scan nand flash\n\r");
printf("[e] Erase nand flash\n\r");
printf("[w] Write nand flash\n\r");
printf("[r] Read nand flash\n\r");
printf("[q] quit\n\r");
printf("Enter selection: ");
c = getchar();
printf("%c\n\r", c);
/* 测试内容:
* 1. 识别nand flash
* 2. 擦除nand flash某个扇区
* 3. 编写某个地址
* 4. 读某个地址
*/
switch (c)
{
case 'q':
case 'Q':
return;
break;
case 's':
case 'S':
nand_chip_id();
break;
case 'e':
case 'E':
break;
case 'w':
case 'W':
break;
case 'r':
case 'R':
break;
default:
break;
}
}
}
在主函数中调用nand flash的初始化函数,和nand flash的测试函数。
int main(void)
{
led_init();
//interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
//timer_init();
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
//nor_flash_test();
nand_init();
nand_flash_test();
return 0;
}