|
|
8月17日 scanf()函数的用法和实践 daizh 摘要: 本文阐述了基于ANSI,Win 95,Win NT上的 C/C++语言中scanf()函数的用法,以及在实际使用中常见错误及对策。 关键词: scanf() 一、 序言 在CSDN论坛的C/C++版块,我时常见到“对于scanf()函数的用法、及出现的各种错误而迷惑”的帖子,萌发了我写这篇文章的念头。文中结合自身在学习和编程中对它的认识和体会,以具体示例阐述问题,目的在于使得初学者能够正确使用scanf()函数,少走不必要的弯路。 二、 scanf()函数的用法 scanf()函数是格式化输入函数,它从标准输入设备(键盘) 读取输入的信息。 其调用格式为: scanf("<格式化字符串>",<地址表>); 格式化字符串包括以下三类不同的字符; 1、 格式化说明符: 格式化说明符与printf()函数中的格式说明符基本相同。但和printf()函数中格式字符串的用法有一些小区别。我们来看下面这个表。 格式字符 说明 %d 从键盘输入十进制整数 %o 从键盘输入八进制整数 %x 从键盘输入十六进制整数 %c 从键盘输入一个字符 %s 从键盘输入一个字符串 %f 从键盘输入一个实数 %e 与%f的作用相同 附加格式说明字符表 字符 说明 L 输入"长"数据 H 输入"短"数据 M 指定输入数据所占宽度 * 空读一个数据 2、 空白字符: 空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符。 3、 非空白字符: 一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。 地址表是需要读入的所有变量的地址,而不是变量本身。这与printf()函数完全不同,要特别注意。各个变量的地址之间同","分开。 例如: #include <stdio.h> void main() { int i, j; printf("i, j=?\n"); scanf("%d, %d", &i, &j); } 上例中的scanf()函数先读一个整型数,然后把接着输入的逗号剔除掉,最后读入另一个整型数。如果","这一特定字符没有找到,scanf()函数就终止。若参数之间的分隔符为空格,则参数之间必须输入一个或多个空格。 说明: (1) 对于字符串数组或字符串指针变量,由于数组名和指针变量名本身就是地址,因此使用scanf()函数时,不需要在它们前面加上"&"操作符。 例如: #include <stdio.h> void main() { char *p, str[20]; p = new char[20]; scanf("%s", p); /*从健盘输入字符串*/ scanf("%s", str); printf("%s\n", p); /*向屏幕输出字符串*/ printf("%s\n", str); } (2) 可以在格式化字符串中的"%"各格式化规定符之间加入一个整数,表示任何读操作中的最大位数。 如上例中若规定只能输入10字符给字符串指针p,则第一条scanf() 函数语句变为:scanf("%10s", p); 程序运行时一旦输入字符个数大于10,p就不再继续读入,而后面的一个读入函数即scanf("%s", str)就会从第11个字符开始读入。 (3) scanf()函数中没有精度控制。 如: scanf("%5.2f",&a); 是非法的。不能企图用此语句输入小数为2位的实数。 (4) scanf中要求给出变量地址,如给出变量名则会出错 如 scanf("%d",a);是非法的,应改为scnaf("%d",&a);才是合法的。 (5) 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔则可用空格,TAB或回车作间隔。 C编译在碰到空格,TAB,回车或非法数据(如对“%d”输入“12A”时,A即为非法数据)时即认为该数据结束。 (6) 在输入字符数据(%c)时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。 例如:scanf("%c%c%c",&a,&b,&c); 输入为: d e f 则把'd'赋予a, ' (空格)'赋予b,'e'赋予c。因为%c 只要求读入一个字符,后面不需要用空格作为两个字符的间隔,因此把' '作为下一个字符送给b。 只有当输入为:def 时,才能把'd'赋于a,'e'赋予b,'f'赋予c。 如果在格式控制中加入空格作为间隔, 如 scanf ("%c %c %c",&a,&b,&c);则输入时各数据之间可加空格。 我们用一些例子来说明一些规则: #include <stdio.h> void main() { char a,b; printf("input character a,b\n"); scanf("%c%c",&a,&b); /*注意两个%c之间没有任何符号*/ printf("%c%c\n",a,b); } 由于scanf函数"%c%c"中没有空格,输入M N,结果输出只有M。而输入改为MN时则可输出MN两字符,见下面的输入运行情况: input character a,b MN (你输入的值) MN (屏幕上显示的值) #include <stdio.h> void main() { char a,b; printf("input character a,b\n"); scanf("%c %c",&a,&b); /*注意两个%c之间有个空格*/ printf("\n%c%c\n",a,b); }本例表示scanf格式控制串"%c %c"之间有空格时, 输入的数据之间可以有空格间隔。 (7) 如果格式控制串中有非格式字符则输入时也要输入该非格式字符。 例如: scanf("%d,%d,%d",&a,&b,&c); 其中用非格式符“ , ”作间隔符,故输入时应为: 5,6,7 (与scanf 双引号之间的格式必须一样) 又如: scanf("a=%d,b=%d,c=%d",&a,&b,&c); 则输入应为 a=5,b=6,c=7 如输入的数据与输出的类型不一致时,虽然编译能够通过,但结果将不正确。 #include <stdio.h> void main() { int a; printf("input a number"); scanf("%d",&a); printf("%ld",a); } 由于输入数据类型为整型, 而输出语句的格式串中说明为长整型,因此输出结果和输入数据不符。输出并不是输入的值。 如将Scanf("%d",&a); 语句改为 scanf("%ld",&a); 输入数据为长整型,输入输出数据才相等。 三、 常见错误及对策 问题1: 我初学C程序,所以提的问题很浅,希望您不要见笑。我自己编了一个程序,但运行的结果与我预期的不一样。 #include<stdio.h> void main() { static int a[2][3]={{1,3,4},{7,9,6}}; int i,j,k; for(k=1;k<=2;k++) {printf("Please input num:"); scanf("%d %d",&i,&j); if(i<2&&j<3) printf("num=%d\n",a[i][j]); else printf("Input is error,\n"); } printf("programm is complete.\n"); } 我想将第8行改为 scanf("i=%d j=%d",&i,&j); 则程序运行结果变成 please input num:i=1 j=2 num=6 num=6(我原本希望能重复第一行再让我输入) Programm is complete. 为什么第二次不能输入? 答复: 我使用Turbo C 2.0证实存在你说的问题。象scanf("i=%d j=%d",&i,&j);这样的输入方式比较特别,TC 2.0显然在第一次输入后没有象正常情况一样清楚输入缓冲区,这样第二次执行scanf时,程序并没有让你输入而是直接读入上次输入的结果。如果你一定要这么做,应该在scanf之前加上: fflush(stdin); 这样清楚掉键盘缓冲区。 问题2: 为什么要把scanf("%c",varname)一定改成scanf(" %c")才可以正确接收字符? 答复: 类似上题,在%c的前面必须有一个空格,否则系统会将你前面输入别的值之后键入的回车符读入该变量,造成死循环。当然,如果scanf("%c",&varname)是第一条读入语句,就可以不需要空格。 问题3:(输入变量时忘记加地址运算符“&”) int a,b; scanf("%d%d",a,b); 答复: 这是不合法的。Scanf函数的作用是:按照a、b在内存的地址将a、b的值存进去。“&a”指a在内存中的地址。 问题4:(输入数据的方式与要求不符) ①scanf("%d%d",&a,&b); 输入时,不能用逗号作两个数据间的分隔符,如下面输入不合法: 3,4 输入数据时,在两个数据之间以一个或多个空格间隔,也可用回车键,跳格键tab。 ②scanf("%d,%d",&a,&b); C规定:如果在“格式控制”字符串中除了格式说明以外还有其它字符,则在输入数据时应输入与这些字符相同的字符。下面输入是合法的: 3,4 此时不用逗号而用空格或其它字符是不对的。 3 4 3:4 又如: scanf("a=%d,b=%d",&a,&b); 输入应如以下形式: a=3,b=4 问题5:(输入字符的格式与要求不一致) 在用“%c”格式输入字符时,“空格字符”和“转义字符”都作为有效字符输入。 scanf("%c%c%c",&c1,&c2,&c3); 如输入a b c 字符“a”送给c1,字符“ ”送给c2,字符“b”送给c3,因为%c只要求读入一个字符,后面不需要用空格作为两个字符的间隔。 问题6:(.输入输出的数据类型与所用格式说明符不一致) 例如,a已定义为整型,b定义为实型 a=3;b=4.5; printf("%f%d\n",a,b); 编译时不给出出错信息,但运行结果将与原意不符。这种错误尤其需要注意。 问题7:(输入数据时,企图规定精度) scanf("%7.2f",&a); 这样做是不合法的,输入数据时不能规定精度。 问题8:(在不应加地址运算符&的位置加了地址运算符) scanf("%s",&str); C语言编译系统对数组名的处理是:数组名代表该数组的起始地址,且scanf函数中的输入项是字符数组名,不必要再加地址符&。应改为:scanf("%s",str); 四、 结论 本文主要讲述了C/C++中的scanf()函数的用法,重点阐述使用scanf()函数过程中出现的常见错误及对策。当然,文中某些解决方法,均可以采用其他函数和方法来更好地解决,但本文仅限讨论scanf()函数本身。文中难免存在一些不足之处,欢迎读者批评指正。 8月15日
共三个文件:s3c2410.h, nand_flash.h, nand_flash.c。
1.s3c2410.h
#ifndef _S3C2410_H #define _S3C2410_H // watchdog register #define rWTCON (*(volatile unsigned int *)0x53000000) //interrupt register #define rSRCPND (*(volatile unsigned int *)0x4A000000) #define rSUBSRCPND (*(volatile unsigned int *)0x4A000018) #define rINTMOD (*(volatile unsigned int *)0x4A000004) #define rINTMSK (*(volatile unsigned int *)0x4A000008) #define rINTSUBMSK (*(volatile unsigned int *)0x4A00001C) #define rPRIORITY (*(volatile unsigned int *)0x4A00000C) #define rINTPND (*(volatile unsigned int *)0x4A000010) #define rINTOFFSET (*(volatile unsigned int *)0x4A000014) // bank ctrl register #define rBWSCON (*(volatile unsigned int *)0x48000000) #define rREFRESH (*(volatile unsigned int *)0x48000024) // NAND flash register #define rNFCONF (*(volatile unsigned int *)0x4e000000) //NAND Flash configuration #define rNFCMD (*(volatile unsigned char *)0x4e000004) //NADD Flash command #define rNFADDR (*(volatile unsigned char *)0x4e000008) //NAND Flash address #define rNFDATA (*(volatile unsigned char *)0x4e00000c) //NAND Flash data #define rNFSTAT (*(volatile unsigned int *)0x4e000010) //NAND Flash operation status #define rNFECC (*(volatile unsigned int *)0x4e000014) //NAND Flash ECC #define rNFECC0 (*(volatile unsigned char *)0x4e000014) #define rNFECC1 (*(volatile unsigned char *)0x4e000015) #define rNFECC2 (*(volatile unsigned char *)0x4e000016)
// I/O PORT register #define rGPACON (*(volatile unsigned int *)0x56000000) //Port A control #define rGPADAT (*(volatile unsigned int *)0x56000004) //Port A data #define rGPBCON (*(volatile unsigned int *)0x56000010) //Port B control #define rGPBDAT (*(volatile unsigned int *)0x56000014) //Port B data #define rGPBUP (*(volatile unsigned int *)0x56000018) //Pull-up control B #define rGPCCON (*(volatile unsigned int *)0x56000020) //Port C control #define rGPCDAT (*(volatile unsigned int *)0x56000024) //Port C data #define rGPCUP (*(volatile unsigned int *)0x56000028) //Pull-up control C #define rGPDCON (*(volatile unsigned int *)0x56000030) //Port D control #define rGPDDAT (*(volatile unsigned int *)0x56000034) //Port D data #define rGPDUP (*(volatile unsigned int *)0x56000038) //Pull-up control D #define rGPECON (*(volatile unsigned int *)0x56000040) //Port E control #define rGPEDAT (*(volatile unsigned int *)0x56000044) //Port E data #define rGPEUP (*(volatile unsigned int *)0x56000048) //Pull-up control E #define rGPFCON (*(volatile unsigned int *)0x56000050) //Port F control #define rGPFDAT (*(volatile unsigned int *)0x56000054) //Port F data #define rGPFUP (*(volatile unsigned int *)0x56000058) //Pull-up control F #define rGPGCON (*(volatile unsigned int *)0x56000060) //Port G control #define rGPGDAT (*(volatile unsigned int *)0x56000064) //Port G data #define rGPGUP (*(volatile unsigned int *)0x56000068) //Pull-up control G #define rGPHCON (*(volatile unsigned int *)0x56000070) //Port H control #define rGPHDAT (*(volatile unsigned int *)0x56000074) //Port H data #define rGPHUP (*(volatile unsigned int *)0x56000078) //Pull-up control H
//UART register #define rULCON0 (*(volatile unsigned int *)0x50000000) //UART 0 Line control #define rUCON0 (*(volatile unsigned int *)0x50000004) //UART 0 Control #define rUFCON0 (*(volatile unsigned int *)0x50000008) //UART 0 FIFO control #define rUMCON0 (*(volatile unsigned int *)0x5000000C) //UART 0 Modem control #define rUTRSTAT0 (*(volatile unsigned int *)0x50000010) //UART 0 Tx/Rx status #define rUERSTAT0 (*(volatile unsigned int *)0x50000014) //UART 0 Rx error status #define rUFSTAT0 (*(volatile unsigned int *)0x50000018) //UART 0 FIFO status #define rUMSTAT0 (*(volatile unsigned int *)0x5000001C) //UART 0 Modem status #define rUBRDIV0 (*(volatile unsigned int *)0x50000028) //UART 0 Baud rate divisor
#define rUTXH0 (*(volatile unsigned char *)0x50000020) //UART 0 Transmission Hold #define rURXH0 (*(volatile unsigned char *)0x50000024) //UART 0 Receive buffer #define WrUTXH0(ch) (*(volatile unsigned char *)0x50000020)=(unsigned char)(ch) #define RdURXH0() (*(volatile unsigned char *)0x50000024)
// CLOCK & POWER MANAGEMENT #define rLOCKTIME (*(volatile unsigned int *)0x4c000000) //PLL lock time counter #define rMPLLCON (*(volatile unsigned int *)0x4c000004) //MPLL Control #define rUPLLCON (*(volatile unsigned int *)0x4c000008) //UPLL Control #define rCLKCON (*(volatile unsigned int *)0x4c00000c) //Clock generator control #define rCLKSLOW (*(volatile unsigned int *)0x4c000010) //Slow clock control #define rCLKDIVN (*(volatile unsigned int *)0x4c000014) //Clock divider control
// LCD register #define rLCDCON1 (*(volatile unsigned int *)0x4D000000) #define rLCDCON2 (*(volatile unsigned int *)0x4D000004) #define rLCDCON3 (*(volatile unsigned int *)0x4D000008) #define rLCDCON4 (*(volatile unsigned int *)0x4D00000c) #define rLCDCON5 (*(volatile unsigned int *)0x4D000010) #define rLCDSADDR1 (*(volatile unsigned int *)0x4D000014) #define rLCDSADDR2 (*(volatile unsigned int *)0x4D000018) #define rLCDSADDR3 (*(volatile unsigned int *)0x4D00001c) #define rREDLUT (*(volatile unsigned int *)0x4D000020) #define rGREENLUT (*(volatile unsigned int *)0x4D000024) #define rBLUELUT (*(volatile unsigned int *)0x4D000028) #define rDITHMODE (*(volatile unsigned int *)0x4D00004c) #define rTPAL (*(volatile unsigned int *)0x4D000050) #define rLCDINTPND (*(volatile unsigned int *)0x4D000054) #define rLCDSRCPND (*(volatile unsigned int *)0x4D000058) #define rLCDINTMSK (*(volatile unsigned int *)0x4D00005c) #define rLPCSEL (*(volatile unsigned int *)0x4D000060)
// Timer register #define rTCFG0 (*(volatile unsigned int *)0x51000000) #define rTCFG1 (*(volatile unsigned int *)0x51000004) #define rTCON (*(volatile unsigned int *)0x51000008) #define rTCNTB0 (*(volatile unsigned int *)0x5100000C) #define rTCMPB0 (*(volatile unsigned int *)0x51000010) #define rTCNTO0 (*(volatile unsigned int *)0x51000014) #define rTCNTB1 (*(volatile unsigned int *)0x51000018) #define rTCMPB1 (*(volatile unsigned int *)0x5100001C) #define rTCNTO1 (*(volatile unsigned int *)0x51000020) #define rTCNTB2 (*(volatile unsigned int *)0x51000024) #define rTCMPB2 (*(volatile unsigned int *)0x51000028) #define rTCNTO2 (*(volatile unsigned int *)0x5100002C) #define rTCNTB3 (*(volatile unsigned int *)0x51000030) #define rTCMPB3 (*(volatile unsigned int *)0x51000034) #define rTCNTO3 (*(volatile unsigned int *)0x51000038) #define rTCNTB4 (*(volatile unsigned int *)0x5100003C) #define rTCNTO4 (*(volatile unsigned int *)0x51000040) #endif
2.nand_flash.h
#ifndef _NAND_FLASH_H #define _NAND_FLASH_H
#define TACLS 0 //1clk(0ns) #define TWRPH0 3 //3clk(25ns) #define TWRPH1 0 //1clk(10ns) //TACLS+TWRPH0+TWRPH1>=50ns
//send command #define NF_CMD(cmd) {rNFCMD=cmd;} //set address #define NF_ADDR(addr) {rNFADDR=addr;} //NAND Flash Memory chip enable #define NF_nFCE_L() {rNFCONF&=~(1<<11);} //NAND Flash Memory chip disable #define NF_nFCE_H() {rNFCONF|=(1<<11);} //Initialize ECC #define NF_RSTECC() {rNFCONF|=(1<<12);} //read data #define NF_RDDATA() (rNFDATA) //write data #define NF_WRDATA(data) {rNFDATA=data;} //get status #define NF_WAITRB() {while(!(rNFSTAT&(1)));}
#endif
3.nand_flash.c
/* www.another-prj.com author: caiyuqing 本代码只属于交流学习,不得用于商业开发 */ #include "s3c2410.h" #include "nand_flash.h" static unsigned char seBuf[16]={0xff}; //-------------------------------------------------------------------------------------- unsigned short nf_checkId(void) { int i; unsigned short id; NF_nFCE_L(); //chip enable NF_CMD(0x90); //Read ID NF_ADDR(0x0); for(i=0;i<10;i++); //wait tWB(100ns) id=NF_RDDATA()<<8; // Maker code(K9S1208V:0xec) id|=NF_RDDATA(); // Devide code(K9S1208V:0x76) NF_nFCE_H(); //chip enable return id; } //-------------------------------------------------------------------------------------- static void nf_reset(void) { int i; NF_nFCE_L(); //chip enable NF_CMD(0xFF); //reset command for(i=0;i<10;i++); //tWB = 100ns. NF_WAITRB(); //wait 200~500us; NF_nFCE_H(); //chip disable } //-------------------------------------------------------------------------------------- void nf_init(void) { rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0); // 1 1 1 1 1 xxx r xxx, r xxx // En r r ECCR nFCE=H tACLS tWRPH0 tWRPH1 nf_reset(); } //--------------------------------------------------------------------------------------
void nf_read(unsigned int src_addr,unsigned char *desc_addr,int size) { int i; unsigned int column_addr=src_addr%512; // column address unsigned int page_address=(src_addr>>9); // page addrress unsigned char *buf=desc_addr; while((unsigned int)buf<(unsigned int)(desc_addr)+size) { NF_nFCE_L(); // enable chip if(column_addr>255) // 2end halft { NF_CMD(0x01); // Read2 command. cmd 0x01: Read command(start from 2end half page) } else { NF_CMD(0x00); // 1st halft? } NF_ADDR(column_addr&0xff); // Column Address NF_ADDR(page_address&0xff); // Page Address NF_ADDR((page_address>>8)&0xff); // ... NF_ADDR((page_address>>16)&0xff); // .. for(i=0;i<10;i++); // wait tWB(100ns)/////?????? NF_WAITRB(); // Wait tR(max 12us) // Read from main area for(i=column_addr;i<512;i++) { *buf++=NF_RDDATA(); } NF_nFCE_H(); // disable chip column_addr=0; page_address++; } return ; } 调试的过程与心得交流
硬件平台:ARM7 44b0x 软件调试平台:ADS1.2 目的:调试一型号为:三星公司的k9f2808 容量为16M *8Bit 的NandFlash 文中:NF表示的是NandFlash
当把k9f2808 焊好后,便开始调试了,但是在其中遇到很多问题
我是第一次接触NandFlash,刚开始还是很陌生
<1>可以读出ID号(非常顺利, 这一点是我也没有想到的,认为基本上就搞定了) <2>我试着写擦除代码 写页(我试着写0X5), 读页。 结果是返回来的全部是0XFF, 这时就出现了好多的疑点了: 我分析如下: 第一种可能:没有擦除到NF,也就不可能写了,返回的是错误的(怀疑擦除的时序有问题) 第二种可能:擦除对了,写页不对,读页也不对 第三种可能:擦除对了,写页对了,读页不对,返回的全部是错误的。 第四种可能:硬件连接不对包括有:(ALE 、CLE、CS、WR、RD等等) 第五种可能:硬件的焊接有问题 第六种可能:NandFlash是坏的
那时我也不知是什么问题而且很快进入了混乱之中。朋友们,当看到这里的时候,你认为是哪里出问题? 接着我是这样做的,我现在也觉得挺有意思 不断地调试代码(可以说是这样:脑子里想到这里有可能,就调一下,那里有可能就那里调一下)(因为我太想搞出来,想一下子把它搞定) 搞了三天还是不行,这时自己又停下来思索了,究竟是哪里出问题了???,就开始去网上找代码,整个GOOGLE都给我找翻了,但是还是没有答案。很是郁闷。
因
为看到网上好多的开发板都是把控制线接到GPIO那里去的,而我的是接到地址总线上去的,于是我也就把问题归结的硬件连接的问题上去了。这时的我又是安慰
自己,本来快没有信心的了,开始又有了信心了(我想搞开发的都会有这种感觉)。很快把控制线连到GPIO上,接着是改代码。结果呢?充满信心的我一下也就
像没气的气球了,真是烦啊!
究竟是哪里出错了???很是郁闷啊!!我真的是累了,决定先放一放先,打了二天的星际争霸游戏,调节一下心情。
过了二天,我就告诉自己一定要彻底静下心来认真思考,鼓励自己要有耐心才能成功。于是我进入了第二阶段:
首先我还是把控制线接回地址总线那里去(因为我对这个比较有信心,起码可以读出ID)
我开始思考:我可以读出ID 来,就证明: 1, 写命令是正确的(否则的话就读不回ID,焊接是没问题的,线据线是没有连错的) 2, 读NF ID 的时序是没有问题的。ID =0XEC73
于是我就想:我写命令是对的了,那么我发擦除块的命令也是对的,于是我又联想到,我所有写指令(包括写地址,写命令)都有对的(这点是很重要的,也是至关重要的)。读数据的指令也是对的了(因为我能够把ID都读回来了(这点是错的,因为到最后就是卡住我了))
现在的我又很有信心了。 现在我要证明我所分析是对的,于是我开始调代码了:
我
试着擦除NF,然后往0 block
0 Page页写(0x05)(时序是不能马虎的),再来就是试着读刚才写的页,结果在串口超级终端返回的又是OXFF,我这时好像要崩溃了,但是我又
马上意识的,我分析的应是对的,我怀疑时序,本来我是不会用逻辑分析仪的,我开始进入学习逻辑分析仪了,搞了二天终于会用那玩意了。我看到时序是有的,我
脑子里突然有一种想法,读多几次看怎么样?于是我连着读好多次那页,结果有读到第三次的时候,出现不是0xff,我开心极了,因为都读回来了
(0x05),我上了次厕所(忙到忘了上厕所了),为了确认我再重新运行程序,又不行了。那是的我真是好难受啊心里面。我又试了好多次,有时能读回来,有
时又不能读回来,我又想是不是写没有写好,于我又往那页写不是同样的数据,写了0,1,2,3…….511,
就这样我再读,结果只能读回0,1,下面的全部是OXFF,这时我为了验证是不是没有写上,于是我又开始读(不是从0区的第一个字节读,我换了其它位置去
读,结果读到了8,9)这时我明白了上点,那就是:写进去的了,问题就是出在读上面(这是非常重要的一点),于是我用逻辑分析仪进行分析,结果呢?读的时
序是有的,但是写的时序也是有的;这时我又明白了一点;但是不应出现这样的现象的啊!!
最终的问题就是出现在这里了:
我用的44B0X的BANK是BANK5(PORTB10), 这个BANK的控制寄存器没有设好(我本来想都没去想这里会出错了,板子说明是BANK5没有使用)没有想到问题就是出现在这里;我换了其它的BANK试了一下,一切都是OK!!!
感想:从这次调NF 可以学到很多很多的东东。对我以后的发展以及经验的积累起到了很大的作用。我同时也感觉很幸运,为什么呢?假如一开始就用其它的BANK那我可能就少走很多的调试的路了,但是正因为这样,让我积累了更多的东东。这也是收获吧!! ECC程序中nand_ecc_precalc_table表的生成原理 作者:龙林 EMAIL:dragon_hn@sohu.com WEB:www.dragon-2008.com 在上文的《NAND FLASH
ECC校验原理与实现》中贴出了ECC算法源程序,在ECC算法源程序中有个nand_ecc_precalc_table,用于快速生成ECC校验和,
该表实际上是按照《NAND FLASH
ECC校验原理与实现》表中的ECC原理生成的,理解了ECC校验和生成原理,实际上生成该表也就不存在任何困难了。下面是生成该表的源程序:
#define BIT0(x) ((x)&0x01)
#define BIT1(x) (((x)&0x02)>>1)
#define BIT2(x) (((x)&0x04)>>2)
#define BIT3(x) (((x)&0x08)>>3)
#define BIT4(x) (((x)&0x10)>>4)
#define BIT5(x) (((x)&0x20)>>5)
#define BIT6(x) (((x)&0x40)>>6)
#define BIT7(x) (((x)&0x80)>>7)
void MakeEccTable()
{
int i,m;
BYTE xData;
m=0;
for(i=0;i<256;i++)
{
xData=0;
if(BIT0(i)^BIT2(i)^BIT4(i)^BIT6(i)) xData|=0x01;
if(BIT1(i)^BIT3(i)^BIT5(i)^BIT7(i)) xData|=0x02;
if(BIT0(i)^BIT1(i)^BIT4(i)^BIT5(i)) xData|=0x04;
if(BIT2(i)^BIT3(i)^BIT6(i)^BIT7(i)) xData|=0x08;
if(BIT0(i)^BIT1(i)^BIT2(i)^BIT3(i)) xData|=0x10;
if(BIT4(i)^BIT5(i)^BIT6(i)^BIT7(i)) xData|=0x20;
if(BIT0(i)^BIT1(i)^BIT2(i)^BIT3(i)^BIT4(i)^BIT5(i)^BIT6(i)^BIT7(i))
xData|=0x40;
if(m==15)
{
TRACE("0x%02X,\n",xData);
m=0;
}
else
{
TRACE("0x%02X,",xData);
m++;
}
}
} ECC简介
由于NAND Flash的工艺不能保证NAND的Memory
Array在其生命周期中保持性能的可靠,因此,在NAND的生产中及使用过程中会产生坏块。为了检测数据的可靠性,在应用NAND Flash的系统中一般都会采用一定的坏区管理策略,而管理坏区的前提是能比较可靠的进行坏区检测。
如果操作时序和电路稳定性不存在问题的话,NAND Flash出错的时候一般不会造成整个Block或是Page不能读取或是全部出错,而是整个Page(例如512Bytes)中只有一个或几个bit出错。
对数据的校验常用的有奇偶校验、CRC校验等,而在NAND Flash处理中,一般使用一种比较专用的校验——ECC。ECC能纠正单比特错误和检测双比特错误,而且计算速度很快,但对1比特以上的错误无法纠正,对2比特以上的错误不保证能检测。
ECC原理
ECC一般每256字节原始数据生成3字节ECC校验数据,这三字节共24比特分成两部分:6比特的列校验和16比特的行校验,多余的两个比特置1,如下图所示: 
ECC的列校验和生成规则如下图所示:
用数学表达式表示为:
P4=D7(+)D6(+)D5(+)D4 P4`=D3(+)D2(+)D1(+)D0
P2=D7(+)D6(+)D3(+)D2 P2`=D5(+)D4(+)D1(+)D0
P1=D7(+)D5(+)D3(+)D1 P1`=D6(+)D4(+)D2(+)D0
这里(+)表示“位异或”操作
ECC的行校验和生成规则如下图所示

用数学表达式表示为:
P8 = bit7(+)bit6(+)bit5(+)bit4(+)bit3(+)bit2(+)bit1(+)bit0(+)P8
……………………………………………………………………………………
这里(+)同样表示“位异或”操作
当往NAND Flash的page中写入数据的时候,每256字节我们生成一个ECC校验和,称之为原ECC校验和,保存到PAGE的OOB(out-of-band)数据区中。
当从NAND Flash中读取数据的时候,每256字节我们生成一个ECC校验和,称之为新ECC校验和。
校验的时候,根据上述ECC生成原理不难推断:将从OOB区中读出的原ECC校验和新ECC校验和按位异或,若结果为0,则表示不存在错(或是出现了
ECC无法检测的错误);若3个字节异或结果中存在11个比特位为1,表示存在一个比特错误,且可纠正;若3个字节异或结果中只存在1个比特位为1,表示
OOB区出错;其他情况均表示出现了无法纠正的错误。
ECC算法的实现
static const u_char nand_ecc_precalc_table[] =
{
0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00
};
// Creates non-inverted ECC code from line parity
static void nand_trans_result(u_char reg2, u_char reg3,u_char *ecc_code)
{
u_char a, b, i, tmp1, tmp2;
/* Initialize variables */
a = b = 0x80;
tmp1 = tmp2 = 0;
/* Calculate first ECC byte */
for (i = 0; i < 4; i++)
{
if (reg3 & a) /* LP15,13,11,9 --> ecc_code[0] */
tmp1 |= b;
b >>= 1;
if (reg2 & a) /* LP14,12,10,8 --> ecc_code[0] */
tmp1 |= b;
b >>= 1;
a >>= 1;
}
/* Calculate second ECC byte */
b = 0x80;
for (i = 0; i < 4; i++)
{
if (reg3 & a) /* LP7,5,3,1 --> ecc_code[1] */
tmp2 |= b;
b >>= 1;
if (reg2 & a) /* LP6,4,2,0 --> ecc_code[1] */
tmp2 |= b;
b >>= 1;
a >>= 1;
}
/* Store two of the ECC bytes */
ecc_code[0] = tmp1;
ecc_code[1] = tmp2;
}
// Calculate 3 byte ECC code for 256 byte block
void nand_calculate_ecc (const u_char *dat, u_char *ecc_code)
{
u_char idx, reg1, reg2, reg3;
int j;
/* Initialize variables */
reg1 = reg2 = reg3 = 0;
ecc_code[0] = ecc_code[1] = ecc_code[2] = 0;
/* Build up column parity */
for(j = 0; j < 256; j++)
{
/* Get CP0 - CP5 from table */
idx = nand_ecc_precalc_table[dat[j]];
reg1 ^= (idx & 0x3f);
/* All bit XOR = 1 ? */
if (idx & 0x40) {
reg3 ^= (u_char) j;
reg2 ^= ~((u_char) j);
}
}
/* Create non-inverted ECC code from line parity */
nand_trans_result(reg2, reg3, ecc_code);
/* Calculate final ECC code */
ecc_code[0] = ~ecc_code[0];
ecc_code[1] = ~ecc_code[1];
ecc_code[2] = ((~reg1) << 2) | 0x03;
}
// Detect and correct a 1 bit error for 256 byte block
int nand_correct_data (u_char *dat, u_char *read_ecc, u_char *calc_ecc)
{
u_char a, b, c, d1, d2, d3, add, bit, i;
/* Do error detection */
d1 = calc_ecc[0] ^ read_ecc[0];
d2 = calc_ecc[1] ^ read_ecc[1];
d3 = calc_ecc[2] ^ read_ecc[2];
if ((d1 | d2 | d3) == 0)
{
/* No errors */
return 0;
}
else
{
a = (d1 ^ (d1 >> 1)) & 0x55;
b = (d2 ^ (d2 >> 1)) & 0x55;
c = (d3 ^ (d3 >> 1)) & 0x54;
/* Found and will correct single bit error in the data */
if ((a == 0x55) && (b == 0x55) && (c == 0x54))
{
c = 0x80;
add = 0;
a = 0x80;
for (i=0; i<4; i++)
{
if (d1 & c)
add |= a;
c >>= 2;
a >>= 1;
}
c = 0x80;
for (i=0; i<4; i++)
{
if (d2 & c)
add |= a;
c >>= 2;
a >>= 1;
}
bit = 0;
b = 0x04;
c = 0x80;
for (i=0; i<3; i++)
{
if (d3 & c)
bit |= b;
c >>= 2;
b >>= 1;
}
b = 0x01;
a = dat[add];
a ^= (b << bit);
dat[add] = a;
return 1;
}
else
{
i = 0;
while (d1)
{
if (d1 & 0x01)
++i;
d1 >>= 1;
}
while (d2)
{
if (d2 & 0x01)
++i;
d2 >>= 1;
}
while (d3)
{
if (d3 & 0x01)
++i;
d3 >>= 1;
}
if (i == 1)
{
/* ECC Code Error Correction */
read_ecc[0] = calc_ecc[0];
read_ecc[1] = calc_ecc[1];
read_ecc[2] = calc_ecc[2];
return 2;
}
else
{
/* Uncorrectable Error */
return -1;
}
}
}
/* Should never happen */
return -1;
}
1.为什么会出现坏块
由于NAND Flash的工艺不能保证NAND的Memory
Array在其生命周期中保持性能的可靠,因此,在NAND的生产中及使用过程中会产生坏块。坏块的特性是:当编程/擦除这个块时,不能将某些位拉高,这
会造成Page Program和Block Erase操作时的错误,相应地反映到Status Register的相应位。
2.坏块的分类
总体上,坏块可以分为两大类
(1) 固有坏块
这是生产过程中产生的坏块,一般芯片原厂都会在出厂时都会将坏块第一个page的spare area的第6个byte标记为不等于0xff的值。
(2) 使用坏块
这是在NAND Flash使用过程中,如果Block Erase或者Page
Program错误,就可以简单地将这个块作为坏块来处理,这个时候需要把坏块标记起来。为了和固有坏块信息保持一致,将新发现的坏块的第一个page的
spare area的第6个Byte标记为非0xff的值。
3.坏块管理
根据上面的这些叙述,可以了解NAND Flash出厂时在spare
area中已经反映出了坏块信息,因此,如果在擦除一个块之前,一定要先check一下spare
area的第6个byte是否是0xff,如果是就证明这是一个好块,可以擦除;如果是非0xff,那么就不能擦除。
当然,这样处理可能会犯一个错误―――“错杀伪坏块”,因为在芯片操作过程中可能由于电压不稳定等偶然因素会造成NAND操作的错误。但是,为了数据的可
靠性及软件设计的简单化,我们就要奉行“蒋委员长”的“宁可错杀一千,也决不放过一个”的宗旨。
4.补充
(1)需要对前面由于Page Program错误发现的坏块进行一下特别说明。如果在对一个块的某个page进行编程的时候发生了错误就要把这个块标记为坏块,首先就要把其他好的page里面的内容备份到另外一个空的好块里面,然后,把这个块标记为坏块。
当然,这可能会犯“错杀”之误,一个补救的办法,就是在进行完页备份之后,再将这个块擦除一遍,如果Block Erase发生错误,那就证明这个块是个真正的坏块,那就毫不犹豫地将它打个“戳”吧!
(2)可能有人会问,为什么要使用spare area的第六个byte作为坏块标记。这是NAND Flash生产商的默认约定,你可以看到Samsung,Toshiba,STMicroelectronics都是使用这个Byte作为坏块标记的。 NAND和NOR FLASH技术设计师在使用闪存时需要慎重选择
M-Systems公司 Arie TAL
NOR和NAND是现在市场上两种主要的非易失闪存技术。Intel于1988年首先开发出NOR
flash技术,彻底改变了原先由EPROM和EEPROM一统天下的局面。紧接着,1989年,东芝公司发表了NAND
flash结构,强调降低每比特的成本,更高的性能,并且象磁盘一样可以通过接口轻松升级。但是经过了十多年之后,仍然有相当多的硬件工程师分不清NOR
和NAND闪存。相“flash存储器”经常可以与相“NOR存储器”互换使用。许多业内人士也搞不清楚NAND闪存技术相对于NOR技术的优越之处,因为大多数情况
下闪存只是用来存储少量的代码,这时NOR闪存更适合一些。而NAND则是高数据存储密度的理想解决方案。 NOR的特点是芯片内执行(XIP, eXecute In
Place),这样应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。NOR的传输效率很高,在1~4MB的小容量时具有很高的成
本效益,但是很低的写入和擦除速度大大影响了它的性能。NAND结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也很快。应用NAND的困难在于flash的管理和需要特殊的系统接口。
性能比较
flash闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何flash器件的写入操作只能在空或已擦除的单元内进行,所以大多
数情况下,在进行写入操作之前必须先执行擦除。NAND器件执行擦除操作是十分简单的,而NOR则要求在进行擦除前先要将目标块内所有的位都写为0。由于擦除NOR器件时是以64~128KB的块进行的,执行一个写入/擦除操作的时间为5s,与此相反,擦除NAND器件是以8~32KB的块进行的,执行相同的操作最多只需要4ms。执行擦除时块尺寸的不同进一步拉大了NOR和NADN之间的性能差距,统计表明,对于给定的一套写入操作(尤其是更新小文件时),更多的擦除操作必须
在基于NOR的单元中进行。这样,当选择存储解决方案时,设计师必须权衡以下的各项因素:
● NOR的读速度比NAND稍快一些。
● NAND的写入速度比NOR快很多。
● NAND的4ms擦除速度远比NOR的5s快。
● 大多数写入操作需要先进行擦除操作。
● NAND的擦除单元更小,相应的擦除电路更少。
接口差别
NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。NAND器件使用复杂的I/O口来串行地存取数据,各个产品或厂商的方法可能各不相同。8个引脚用来传送控制、地址和数据信息。NAND读和写操作采用512字节的块,这一点有点像硬盘管理此类操作,很自然地,基于NAND的存储器就可以取代硬盘或其他块设备。
容量和成本
NAND flash的单元尺寸几乎是NOR器件的一半,由于生产过程更为简单,NAND结构可以在给定的模具尺寸内提供更高的容量,也就相应地降低了价格。NOR flash占据了容量为1~16MB闪存市场的大部分,而NAND
flash只是用在8~128MB的产品当中,这也说明NOR主要应用在代码存储介质中,NAND适合于数据存储,NAND在CompactFlash、
Secure Digital、PC Cards和MMC存储卡市场上所占份额最大。
可靠性和耐用性
采用flahs介质时一个需要重点考虑的问题是可靠性。对于需要扩展MTBF的系统来说,Flash是非常合适的存储方案。可以从寿命(耐用性)、位交换和坏块处理三个方面来比较NOR和NAND的可靠性。
寿命(耐用性)
在NAND闪存中每个块的最大擦写次数是一百万次,而NOR的擦写次数是十万次。NAND存储器除了具有10比1的块擦除周期优势,典型的NAND块
尺寸要比NOR器件小8倍,每个NAND存储器块在给定的时间内的删除次数要少一些。
位交换
所有flash器件都受位交换现象的困扰。在某些情况下(很少见,NAND发生的次数要比NOR多),一个比特位会发生反转或被报告反转了。一位的变化可能不很明显,但是如果发生在一个关键文件上,这个小小的故障可能导致系统停机。如果只是报告有问题,多读几次就可能解决了。当然,如果这个位真的改变了,就必须采用错误探测/错误更正(EDC/ECC)算法。位反转的问题更多见于NAND闪存,NAND的供应商建议使用NAND闪存的时候,同时使用EDC/ECC算法。这个问题对于用NAND存储多媒体信息时倒不是致命的。当然,如果用本地存储设备来存储操作系统、配置文件或其他敏感信息时,必须使用EDC/ECC系统以确保可靠性。
坏块处理
NAND器件中的坏块是随机分布的。以前也曾有过消除坏块的努力,但发现成品率太低,代价太高,根本不划算。NAND器件需要对介质进行初始化扫描以发现坏块,并将坏块标记为不可用。在已制成的器件中,如果通过可靠的方法不能进行这项处理,将导致高故障率。
易于使用
可以非常直接地使用基于NOR的闪存,可以像其他存储器那样连接,并可以在上面直接运行代码。由于需要I/O接口,NAND要复杂得多。各种NAND器件的存取方法因厂家而异。在使用NAND器件时,必须先写入驱动程序,才能继续执行其他操作。向NAND器件写入信息需要相当的技巧,因为设计师绝不能向坏块写入,这就意味着在NAND器件上自始至终都必须进行虚拟映射。
软件支持
当讨论软件支持的时候,应该区别基本的读/写/擦操作和高一级的用于磁盘仿真和闪存管理算法的软件,包括性能优化。在NOR器件上运行代码不需要任何的软件支持,在NAND器件上进行同样操作时,通常需要驱动程序,也就是内存技术驱动程序(MTD),NAND和NOR器件在进行写入和擦除操作时都需要MTD。使用NOR器件时所需要的MTD要相对少一些,许多厂商都提供用于NOR器件的更高级软件,这其中包括M-System的TrueFFS驱动,该驱动被
Wind River System、Microsoft、QNX Software System、Symbian和Intel等厂商所采用。驱动还用于对DiskOnChip产品进行仿真和NAND闪存的管理,包括纠错、坏块处理和损耗平衡。
|