ANR问题解析(二):程序等待原理分析

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

img

所以NFCONF寄存器设置如下

#define TACLS 0

#define TWRPH0 1

#define TWRPH1 0

/设置NAND FLASH的时序/

NFCONF = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

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

img

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 FLASHh的芯片手册,如下图所示:(NAND FLASH读操作时序图)

img

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

void nand_select(void)

{

/使能片选/

NFCONT &=~(1<<1);

}

有使能片选,一定有禁止片选,禁止片选的代码如下:

void nand_deselect(void)

{

/禁止片选/

NFCONT |= (1<<1);

}

读ID的操作时序图,如下所示

img

按照从左往右的时间点,来分析,片选信号像一个总开关,只有使能了片选信号,后续的操作才会有意义,我们使能片选信号之后,片选引脚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的函数,代码如下:

void 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
    1. 擦除nand flash某个扇区
    1. 编写某个地址
    1. 读某个地址

*/

switch ©

{

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;

}

四、NandFlash的数据读取

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

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

img

图的表格,来说明NAND FLASH内部结构,前面2K(02047)表示页数据,后边64字节(20482111)表示oob。

img

问: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的时序操作:

img

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

下面是程序的编写:

img

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

void wait_ready(void)

{

while (!(NFSTAT & 1));

}

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

void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)

{

int i = 0;

int page = addr / 2048;

int col = addr & (2048 - 1);

nand_select();

while (i < len)

{

/* 发出00h命令 */

nand_cmd(00);

/* 发出地址 */

/* col addr */

nand_addr_byte(col & 0xff);

nand_addr_byte((col>>8) & 0xff);

/* row/page addr */

nand_addr_byte(page & 0xff);

nand_addr_byte((page>>8) & 0xff);

nand_addr_byte((page>>16) & 0xff);

/* 发出30h命令 */

nand_cmd(0x30);

/* 等待就绪 */

wait_ready();

/* 读数据 */

for (; (col < 2048) && (i < len); col++)

{

buf[i++] = nand_data();

}

if (i == len)

break;

col = 0;

page++;

}

nand_deselect();

}

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

int isBootFromNorFlash(void)

{

volatile unsigned int *p = (volatile unsigned int *)0;

unsigned int val = *p;

*p = 0x12345678;

if (*p == 0x12345678)

{

/* 写成功, 对应nand启动 */

*p = val;

return 0;

}

else

{

return 1;

}

}

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

if (isBootFromNorFlash())

{

while (dest < end)

{

*dest++ = *src++;

}

}

else

{

nand_init();

nand_read(src, dest, len);

}

}

五、NandFlash的擦除与烧写

我们本节需要做的事情:

  • 实现nand_erase

  • 实现nand_write

  • 实现测试菜单

下面我们逐个来实现他们:

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

img

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

int nand_erase(unsigned int addr, unsigned int len)

{

int page = addr / 2048;

if (addr & (0x1FFFF))

{

printf(“nand_erase err, addr is not block align\n\r”);

return -1;

}

if (len & (0x1FFFF))

{

printf(“nand_erase err, len is not block align\n\r”);

return -1;

}

nand_select();

while (1)

{

page = addr / 2048;

nand_cmd(0x60);

/* row/page addr */

nand_addr_byte(page & 0xff);

nand_addr_byte((page>>8) & 0xff);

nand_addr_byte((page>>16) & 0xff);

nand_cmd(0xD0);

wait_ready();

len -= (128*1024);

if (len == 0)

break;

addr += (128*1024);

}

nand_deselect();

return 0;

}

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

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

void nand_w_data(unsigned char val)

{

NFDATA = val;

}

下图为烧写的时序图:

img

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

void nand_write(unsigned int addr, unsigned char *buf, unsigned int len)

{

int page = addr / 2048;

int col = addr & (2048 - 1);

int i = 0;

nand_select();

while (1)

{

nand_cmd(0x80);

/* 发出地址 */

/* col addr */

nand_addr_byte(col & 0xff);

nand_addr_byte((col>>8) & 0xff);

/* row/page addr */

nand_addr_byte(page & 0xff);

nand_addr_byte((page>>8) & 0xff);

nand_addr_byte((page>>16) & 0xff);

/* 发出数据 */

for (; (col < 2048) && (i < len); )

{

nand_w_data(buf[i++]);

}

nand_cmd(0x10);

wait_ready();

if (i == len)

break;

else

{

/* 开始下一个循环page */

col = 0;

page++;

}

}

nand_deselect();

}

我们封装擦除操作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);

}

我们封装读取操作NAND FLASH函数,我们实现NAND FLASH每次的读取,每次读取64字节数据。把从地址addr读取得到的64字节数据存放到buf缓冲区中,然后通过串口显示出来,代码如下图所示:

void do_read_nand_flash(void)

{

unsigned int addr;

volatile unsigned char *p;

int i, j;

unsigned char c;

unsigned char str[16];

unsigned char buf[64];

/* 获得地址 */

printf("Enter the address to read: ");

addr = get_uint();

nand_read(addr, buf, 64);

p = (volatile unsigned char *)buf;

printf(“Data : \n\r”);

/* 长度固定为64 */

for (i = 0; i < 4; i++)

{

/* 每行打印16个数据 */

for (j = 0; j < 16; j++)

{

/* 先打印数值 */

c = *p++;

str[j] = c;

printf("%02x ", c);

}

printf(" ; ");

for (j = 0; j < 16; j++)

{

/* 后打印字符 */

if (str[j] < 0x20 || str[j] > 0x7e) /* 不可视字符 */

putchar(‘.’);

else

putchar(str[j]);

}

printf(“\n\r”);

}

}

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

void do_write_nand_flash(void)

{

unsigned int addr;

unsigned char str[100];

int i, j;

unsigned int val;

/* 获得地址 */

printf("Enter the address of sector to write: ");

addr = get_uint();

printf("Enter the string to write: ");

gets(str);

printf(“writing …\n\r”);

nand_write(addr, str, strlen(str)+1);

}

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

void 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
    1. 擦除nand flash某个扇区
    1. 编写某个地址
    1. 读某个地址

*/

switch ©

{

case ‘q’:

case ‘Q’:

return;

break;

case ‘s’:

case ‘S’:

nand_chip_id();

break;

case ‘e’:

case ‘E’:

do_erase_nand_flash();

break;

case ‘w’:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值