[smart210] Nand Flash K9F4G08U0B 的配置与读写控制(二)
平台:smart210
CPU:s5pv210
目标:控制核心板上的Nand Flash,对其进行读写操作,本文为上文续篇,主要实现的是对nand flash进行读/写与块擦除操作
void nand_init(void) { // 1. config nandflash controller NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<3)|(0<<2)|(1<<1)|(0<<0); NFCONT = (0<<18)|(0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(0<<7)|(0<<6)|(0x3<<1)|(1<<0); // 2. config memory port link to nand flash MP0_1CON = 0x22333322; MP0_2CON = 0x00002222; MP0_3CON = 0x22222222; // 3. reset nand_reset(); }
上文我们研究了NFCONF与NFCONT两个寄存器的设置,以及配置MP0_1,MP0_2,MP0_3的必要性,针对nand flash的初始化进行到了最后阶段,就是reset了。
可以得知Reset命令为FFh,这里#define NAND_CMD_RES 0xff
static void nand_reset(void) { nand_select_chip(); //NFCONT bit1=0 nand_send_cmd(NAND_CMD_RES);//NFCMMD=NAND_CMD_RES nand_wait_idle();//wait until NFSTAT bit4=1 means no busy nand_deselect_chip();//NFCONT bit1=1 }
对于nand_reset()函数,顺序依次是片选,命令,等待,解除片选。
接下来就是获取nand flash的id,这次的思路是片选,命令,地址,等待,读取,解除片选。
void nand_read_id(void) { nand_id_info nand_id; nand_select_chip(); nand_send_cmd(NAND_CMD_READ_ID);//发送读ID命令 nand_send_addr(0x00); nand_wait_idle(); nand_id.IDm = nand_read(); nand_id.IDd = nand_read(); nand_id.ID3rd = nand_read(); nand_id.ID4th = nand_read(); nand_id.ID5th = nand_read(); printf("nandflash: makercode = %x,devicecode = %x\r\n",nand_id.IDm,nand_id.IDd); nand_deselect_chip(); }
static void nand_send_addr(unsigned long addr)//send addr as column address and row address to send { unsigned long i; unsigned long col, row; col = addr % NAND_PAGE_SIZE; //column :the address in a page 页内地址 row = addr / NAND_PAGE_SIZE; //row: the page address 页地址 NFADDR = col & 0xff; // Column Address A0~A7 for(i=0; i<10; i++); NFADDR = (col >> 8) & 0x0f; // Column Address A8~A11 for(i=0; i<10; i++); NFADDR = row & 0xff; // Row Address A12~A19 for(i=0; i<10; i++); NFADDR = (row >> 8) & 0xff; // Row Address A20~A27 for(i=0; i<10; i++); NFADDR = (row >> 16) & 0xff; // Row Address A28~A30 for(i=0; i<10; i++); }
获取到的id,可以根据下图判断是否正确,这块K9F1G08U0B所获取的Maker Code为0xec,Device Code为0xf1。而我们的芯片型号是K9F4G08U0B,具体细节会有所不同,我获取到的是Maker Code=0xec,Device Code=0x54。
获取id之后,证明nand flash处于正常工作状态,我们就能开始对其进行读/写以及块擦除工作了。首先是块擦除的流程图
根据流程图,操作顺序应该是 片选,块擦除命令1,块地址,块擦除命令2,等待,发读取状态命令确认块擦除完成。
之前说过了,row是用来描述页地址的,由于一个块有64页,所以特定的row值如0、64、128、192、256、320等就是块地址了。
unsigned char nand_erase(unsigned long block_num) { unsigned long i = 0; unsigned long row = block_num * NAND_BLOCK_SIZE; nand_select_chip(); nand_send_cmd(NAND_CMD_BLOCK_ERASE_1st); for(i=0; i<10; i++); NFADDR = row & 0xff; // Row Address A12~A19 for(i=0; i<10; i++); NFADDR = (row >> 8) & 0xff;// Row Address A20~A27 for(i=0; i<10; i++); NFADDR = (row >> 16) & 0xff; // Row Address A28~A30 NFSTAT = (NFSTAT)|(1<<4); nand_send_cmd(NAND_CMD_BLOCK_ERASE_2st); for(i=0; i<10; i++); nand_wait_idle(); unsigned char status = read_nand_status(); if (status & 1 )//一旦读取到状态异常,则认为该处为坏块,需要对坏块进行一些处理。 { nand_deselect_chip(); printf("masking bad block %d\r\n", block_num); return -1; } else { nand_deselect_chip(); return 0; } }
完成了块擦除,接下来就是读写了,读的前提是写,没有写入数据的nand flash 自然就没有数据给我们读,所以我们首先来进行写操作(又叫页面编程 page program),下面是写操作流程图
写操作的一开始要先明确给出页地址和页内地址作为写的起始地址,由于nand flash采取的是整页读写的策略,所以我们只需要给定一次地址,就能源源不断地往该页写数据,实际上就是把一个缓冲数组的每个元素逐个逐个地给搬移到nand flash的特定页内,由于一开始给定的地址是寻址地址而非行列地址,所以遇到字节数大于2048的缓冲数组还得做特殊处理,下面这段程序用了巧妙的方式,以检查剩余缓冲区大小为主线,同时兼顾页面的剩余大小,哪个条件先达到就处理哪一种情况。
int copy_sdram_to_nand(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long length) { unsigned long i = 0; nand_select_chip(); while(length) { nand_send_cmd(NAND_CMD_WRITE_PAGE_1st); nand_send_addr(nand_addr); unsigned long col = nand_addr % NAND_PAGE_SIZE; i = col; for(; i<NAND_PAGE_SIZE && length!=0; i++,length--) { nand_write(*sdram_addr); sdram_addr++; nand_addr++; } NFSTAT = (NFSTAT)|(1<<4); nand_send_cmd(NAND_CMD_WRITE_PAGE_2st); nand_wait_idle(); } unsigned char status = read_nand_status(); if (status & 1 ) { nand_deselect_chip(); printf("copy sdram to nand fail\r\n"); return -1; } else { nand_deselect_chip(); return 0; } }
可以注意到,写操作命令1st和2nd携带了地址和数据,而读操作命令1st与2nd只携带了地址,接着就能顺着页面内的列地址读下去了。不管读写操作是从页面的哪个列地址开始的,最终都只能持续读写直到当前页面的最后一个字节,所以必要的时候还是需要重新计算行列地址,在新的页内内做读写操作。
int copy_nand_to_sdram(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long length) { unsigned long i = 0; nand_select_chip(); while(length) { nand_send_cmd(NAND_CMD_READ_1st); nand_send_addr(nand_addr); NFSTAT = (NFSTAT)|(1<<4); nand_send_cmd(NAND_CMD_READ_2st); nand_wait_idle(); unsigned long col = nand_addr % NAND_PAGE_SIZE; i = col; for(; i<NAND_PAGE_SIZE && length!=0; i++,length--) { *sdram_addr = nand_read(); sdram_addr++; nand_addr++; } } unsigned char status = read_nand_status(); if (status & 1 ) { nand_deselect_chip(); printf("copy nand to sdram fail\r\n"); return -1; } else { nand_deselect_chip(); return 0; } }