1:开发环境
芯片:NUC977
控制器:FMI
flash芯片:W29N02GVS1AA
2:Nandflash原理
2.1 命名含义
2.2 引脚含义
CE :chip Enable 片选信号 低电平有效(上面带横杠表示低电平有效)
WE:Write Enable 写使能 低电平有效
RE: Read Enable 读使能 低电平有效
ALE:Address Latch Enable 地址占有使能
CLE:Command Latch Enable 命令占有使能
WP: write Project 写保护 低电平有效
RY/#BY: Read/Busy 空闲与繁忙状态
I/Ox : Input and Output 输入与输出引脚
一般工作得方式是:CE为低选中芯片,WE控制要写入数据 , RE控制读出数据。 当ALE为高时,表示可以写入地址。 当CLE为高时,表示可以协议命令。当ALE和CLE都为低可以经行数据得读写。
当操作都完成后可以通过RY/#BY得状态来判断是否真正得完成。
3:内部存储格式
3.1 page
再nand flash内部 最小操作单位时page,可以时512, 2K, 4K。这里得一个page是2K。
3.2 OOB/Spare Area / Redundant Area /
nand flash中每一页对应一块区域,用于存放校验的ECC数据和其他一些信息,比如上层文件系统放的和自己文件系统相关的数据。这个区域,在Linux MTD相关系统中,被称作oob(out of band),可以翻译为带外,也就是nand flash的一个页,可以称作一个band,band之外,对应的就是指那个多出来的,特殊的区域了。而nand flash的datasheet中,一般成为spare area,可译为空闲区域,另外,在ID的含义解释中也叫做redundant area,可译为冗余区域,归根结底,都是一个含义。不要被搞糊涂了就好
3.3 block
64个页是一个block,这里的大小是 64 * 2K = 128 K。 block是擦除的最小单元
3.4 Plane
1024个block是一个Plane ,这里的大小为 1024 * 128K = 128M
3.5 Chip
对于chip,其实任何某个型号的flash,都可以称其是一个chip,但是实际上,此处的chip,是针对内部来说的,也就是某型号的flash,内部有几个chip,比如下面会举例说到的,三星的2GB的K9WAG08U1A芯片(可以理解为外部芯片/型号)内部装了2个单片是1GB的K9K8G08U0A,此时就称 K9WAG08U1A内部有2个chip,而有些单个的chip,内部又包含多个plane,比如上面的W29N02GVS1AA内部包含2个单片是2Gb的Plane。只有搞清楚了此处的chip和plane的关系,才能明白后面提到的多页(Multi Plane / Multi Page)编程和交互(interleave)编程的含义。
这里的大小为 2 * 128M = 256M
3.6 Page Register(页寄存器)
nand flash硬件中的一块地方,名字叫做register,实际就是一个数据缓存,一个buffer,用于存放那些从flash读出来或者将要写入到flash中的。其实叫做页缓存,更合适,更容易明白其含义。此页寄存器的大小=页大小+ oob 大小,即pagesize+oob,对于常见的页是2KB的,此页寄存器就是2KB+64=2112字节
4 地址写入循环:
不同的flash,地址写入方式不同,有些把一个地址分为3次写入,有些分为5次写入
这个图表示:
有效地址为29位,地址分为5次写入
之所以这样划分起始是为了行地址和列地址,也就是页内的地址和页的地址
A0-A11:在前两次写入。这是因为一个页的大小是2048,用二进制表示就是1000 0000 0000,是12位。也是是说,要访问一个页内的0-2047处的数据,最多需要11位就可以了就可以表示(000 0000 0000 - 111 1111 1111)。
这里用了12位,是为了访问OOB区。当地址A0-A12为1000 0000 0000时,就表示访问该页后面的OOB的数据。
A11-A28:17位就是来表示哪一个页。这里有2102464 个页。 二进制表示10 0000 0000 0000 0000
所以对应的17位就可以访问所有的页
总结:地址可以分成2部分,后面3次的地址用来寻找在哪一个页。前面2次的地址用力寻找在该页的哪一个位置。
5 操作的命令
5.1 读ID READ ID(0x90)
基本过程就是:
1:先发出命令0x90
2:再发出地址0x00
3:再读取5 byte数据即可
void read_nand_id(void)
{
char id[6]={0};
int i;
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))|0x04000000); //CS enable
outpw(REG_NANDCMD, NAND_CMD_READID);
outpw(REG_NANDADDR, 0x80000000); //这里地址最高位置1 来表示时最后一笔地址
// outpw(REG_NANDADDR, 0x80000020);
for(i = 0; i< 6; i++) {
id[i] = (unsigned char)inpw(REG_NANDDATA);
}
for(i = 0; i< 6; i++) {
rt_kprintf("id[%d] = 0x%02x\n", i, id[i]);
}
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))); //CS disable
}
MSH_CMD_EXPORT(read_nand_id, read_nand_id);
5.2 读数据 PAGE READ (00h-30h)
过程:
1:先发出读命令0x00
2: 发出5次地址
3:发出命令0x30
4:等待RY/#BY变成1,表示数据由flash中的page读到page buffer这个动作结束
5:读出数据
int nand_read(int argc, char *argv[])
{
rt_uint8_t *pBuffer;;
rt_uint32_t StartAddress;
rt_uint32_t NumByteToRead;
rt_uint32_t i;
rt_uint32_t column, page_addr;
if(argc > 2) {
StartAddress = htoi(argv[1]);
NumByteToRead = htoi(argv[2]);
}else{
rt_kprintf("please input >> nand_read <addr> <length>\n");
return RT_ERROR;
}
pBuffer = rt_malloc(NumByteToRead);
column = StartAddress % 2048;
page_addr = StartAddress / 2048;
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))|0x04000000); //CS enable
outpw(REG_NANDCMD, NAND_CMD_READ0); // read0 cmd
outpw(REG_NANDADDR, column&0xff);
outpw(REG_NANDADDR, column >> 8);
outpw(REG_NANDADDR, page_addr&0xFF);
outpw(REG_NANDADDR, (page_addr >> 8)&0xFF);
outpw(REG_NANDADDR, ((page_addr >> 16)&0xFF)|ENDADDR);
outpw(REG_NANDCMD, NAND_CMD_READSTART); // cmd read start
while (!(inpw(REG_NANDINTSTS) & READYBUSY)); //wait
for(i = 0; i<NumByteToRead; i++){
pBuffer[i] = (unsigned char)inpw(REG_NANDDATA);
}
rt_kprintf("data:\n");
for(i = 0; i<NumByteToRead; i++) {
if(i%16 == 0) {
rt_kprintf("\n");
rt_kprintf("0x%08x: ", StartAddress+i);
}
rt_kprintf("0x%02x ", pBuffer[i]);
}
rt_kprintf("\n");
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))); //CS disable
return RT_EOK;
}
MSH_CMD_EXPORT(nand_read, read_nand test);
5.3 写操作 PAGE PROGRAM (80h-10h)
通常flash 用program(编程)来表示写
过程:
1:发送命令0x80
2:发送5次地址
3:发送数据
4:发送命令0x10
5:等待RY/#BY变成1,表示数据由page buffer写入到flash中的page这个动作结束
rt_uint32_t nand_write(int argc, char *argv[])
{
rt_uint8_t *pBuffer;
rt_uint8_t data;
rt_uint32_t StartAddress;
rt_uint32_t NumByteToWrite;
rt_uint32_t i;
rt_uint32_t column, page_addr;
if(argc > 3) {
StartAddress = htoi(argv[1]);
NumByteToWrite = htoi(argv[2]);
data = htoi(argv[3]);
}else{
rt_kprintf("please input >> nand_write <addr> <length> <data>\n");
return RT_ERROR;
}
column = StartAddress % 2048;
page_addr = StartAddress / 2048;
rt_kprintf("Write addr: 0x%02x, length:%d, data:%x\n\n", StartAddress, NumByteToWrite, data);
pBuffer = rt_malloc(NumByteToWrite);
for(i = 0; i<NumByteToWrite; i++){
pBuffer[i] = data;
}
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))|0x04000000); //CS enable
outpw(REG_NANDCMD, NAND_CMD_SEQIN); // pargram cmd
outpw(REG_NANDADDR, column&0xff);
outpw(REG_NANDADDR, column >> 8);
outpw(REG_NANDADDR, page_addr&0xFF);
outpw(REG_NANDADDR, (page_addr >> 8)&0xFF);
outpw(REG_NANDADDR, ((page_addr >> 16)&0xFF)|ENDADDR);
for(i = 0; i<NumByteToWrite; i++){
outpw(REG_NANDDATA, pBuffer[i]);
}
outpw(REG_NANDCMD, NAND_CMD_PAGEPROG); // data input
while (!(inpw(REG_NANDINTSTS) & READYBUSY)); //wait
outpw(REG_NANDCMD, NAND_CMD_STATUS); // write status pass: bit0 = 0 fail: bit0 = 1
if(inpw(REG_NANDDATA) & 0x1 ){
rt_kprintf("write data to address: 0x%08x failed\n", StartAddress);
rt_kprintf("status:0x%02x\n", inpw(REG_NANDDATA));
}else{
rt_kprintf("write data to address: 0x%08x success\n", StartAddress);
}
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))); //CS disable
return RT_EOK;
}
MSH_CMD_EXPORT(nand_write, read_nand test);
5.4 块擦除 BLOCK ERASE (60h-D0h)
过程:
1:写入命令0x60
2:写入3次地址,表示页的地址,会删除这个页所在的block
3:写入命令0xDO
4:等待RY/#BY变成1,表示擦删完成
5:写入命令0x70
6:读出1byte数据,查看读出数据的bit0 是不是位0
int nand_erase(int argc, char *argv[])
{
rt_uint32_t StartAddress = 0x00030000;
rt_uint32_t column, page_addr;
// column = StartAddress % 2048;
if(argc > 1) {
StartAddress = htoi(argv[1]);
}else{
rt_kprintf("please input >> nand_erase <addr> <length> <data>\n");
return RT_ERROR;
}
page_addr = StartAddress / 2048;
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))|0x04000000); //CS enable
outpw(REG_NANDCMD, NAND_CMD_ERASE1); // erase page
outpw(REG_NANDADDR, page_addr&0xFF);
outpw(REG_NANDADDR, (page_addr >> 8)&0xFF);
outpw(REG_NANDADDR, ((page_addr >> 16)&0xFF)|ENDADDR);
outpw(REG_NANDCMD, NAND_CMD_ERASE2); // erase confirm command
while (!(inpw(REG_NANDINTSTS) & READYBUSY)); //wait
outpw(REG_NANDCMD, NAND_CMD_STATUS); // write status pass: bit0 = 0 fail: bit0 = 1
if(inpw(REG_NANDDATA) & 0x1 ){
rt_kprintf("erase address: 0x%08x failed\n", page_addr);
rt_kprintf("status:0x%02x\n", inpw(REG_NANDDATA));
}else{
rt_kprintf("erase address: 0x%08x success\n", page_addr);
}
outpw(REG_NANDCTL, (inpw(REG_NANDCTL)&(~0x06000000))); //CS disable
return RT_EOK;
}
MSH_CMD_EXPORT(nand_erase, read_nand test);