平台: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();
}
这里应用到了nand_send_addr()函数,我们来分析一下这个函数
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;
}
}
写操作的完成是为了读操作准备的,读操作和写操作的流程及其相似,主要原因是两者的模式都是基于sdram与nand flash的交互下完成的。
可以注意到,写操作命令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;
}
}
最终通过调用这两个函数,可以实现大数据的读写,sdram与nand flash的交互,一定程度体现了嵌入式设备RAM与ROM之间的关系,可编程的静态存储控制器与软件编程为动态存储与静态存储的沟通建立了桥梁。