天嵌 ARM开发社区

 找回密码
 注册
查看: 2319|回复: 7

裸奔程序7(下)-NAND芯片的读写及ECC检验软硬件实现

[复制链接]
吕氏春秋8266 发表于 2011-1-13 21:05:19 | 显示全部楼层 |阅读模式
本帖最后由 吕氏春秋8266 于 2011-1-13 21:12 编辑

行检验码的生成过程:
根据异或规则,当一个数的ECC码第bit6位(En=1)时,将改变11位行检验码,显然,问题的关键是如何判断这11位行检验码。
当我们用i来表示字节在2048个字节中的位置时,由于2^11=2048,即i只取11位。

考虑如下:
   2048个字节中第i个字节,其值ECC码第6位为1
再让我们考虑一下行检验码的生成规则
将其进行2平分后异或
P8912_1=E1024+E1025+……+E2047(显然,i[10]=1)
P8912_0=E0  +  E1……   +E1023(显然,i[10]=0)
从以上规则可知:当i[10]=1时,即i在1024~2047之间,将改变p8912_1的值
                当i[10]=0时,即i在0~1024   之间,将改变p8192_0的值
将其进行4平分后交差取值异或
P4196_1=E512+…+E1023+E1536+…+E2047
P4196_0= E0+…+ E511+E1024+…+E1535
显然,对512~1023而言,i[10,9]=01 1536~2047而言 i[10,9]=11
      对0~511   而言, i[10,9]=00 1024~1535而言i[10,9]=10
从以上分析可以得知:我们只管i[9]而不用管i[10](用以判断P8192_0/1的值)
     当i[9]=1时,即i在512~1023、1536~2047之间,将改变p4196_1的值
     当i[9]=0时,即i在0~511、   1024~1535之间,将改变p4196_0的值

同理,我们根据位置i每一位的值为0/1来判断要改变的行检验码。
通过以上分析,我们知道,i取值范围为0~2047共2048个字节,i为11位。正好对应需要改变的11位行检验码。程序描述如下:

for(i=0;i<2048;i++)
        {
                j=pData;                             //得到要检验的数值
                byte=Ecc_Table[j];                      //查表得到ECC结果
                list^=(byte&0x3f);                      //得到列检验码bit6位清零
if(byte&0x40)                           //判断第6位行检验码是否为1
{
   If(bit0(i)==1)    p8_1^=1;
       Else    p8_0^=1;

   ……………………………
   If(bit10(i)==1)    p8192_1^=1;
       Else    p8192_0^=1;
        }
        }
显然,通过一次循环及查表,我们就得到了6位列检验码和22位行检验码,我们将其按S3C2440内部定义的格式排列好就行了。
算法优化:
仔细观察第1109字节行校验码生成表示如下:

显然,我们可以定义两个标志Pn_0/1分别表示如上图:
Pn_0[0]=p8_0, Pn_0[1]=p16_0……pn_0[10]=p8192_0
Pn_1[0]=p8_1, Pn_0[1]=p16_1……pn_0[10]=p8192_1
根据异或规则,对0异或不改变其值,因此,要改变p8_1\p32_1\p128_1\p512_1\p8192_1
我们可以用i和Pn_1进行异或即可,同理,要改变其它行检验码的值,我们将i取反后与pn_0异或即可,故程度优化如下:
for(i=0;i<2048;i++)
{
      j=pData;//得到要检验的数值
     byte=Ecc_Table[j];//
查表得到ECC结果

     list^=(byte&0x3f);//
得到列检验码

     if(byte&0x40)//
判断第6位为行检验码

     {  

        row0^=((~i)&0x7ff);//取值11位pn_0

        row1^=(i&0x7ff);//pn_1
     }
  }

好了,我们已经彻底了解了ECC校验算法的原理及实现,下面,我们再进行一个关键问题的研究,即ECC纠码错误位置确定。
ECC纠码错误
nandflash的每一页有两区:main区和spare区,main区用于存储正常的数据,spare区用于存储其他附加信息,其中就包括ECC校验码。当我们在写入数据的时候,我们就计算这一页数据的ECC校验码,然后把校验码存储到spare区的特定位置中,在下次读取这一页数据的时候,同样我们也计算ECC校验码,然后与spare区中的ECC校验码进行异或,结果全为0则说明读取的数据正确,如果为14个1位则表示有一位数据错误

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主| 吕氏春秋8266 发表于 2011-1-13 21:17:57 | 显示全部楼层
设定:
由于异或与顺序无关,我们设定除发生数据错误的第i个字节外的另2047个字节的ECC行校验码结果为P2047_1,P2047_0。
我们考虑2048个字节中第i个字节中一位发生错误(只考虑行检验码)。显然,一位发生错误,其En=bit(0)+bit(1)+bit(2)+ bit(3)+bit(4)+bit(5)+ bit(6)+bit(7)必将发生改变。分两种情况考虑:
1.En由0->1
未发生错误前,由于En=0,故不影响其它ECC行检验码,故其原始ECC行校验码
          P2047_1,P2047_0
发生错误后,由于En=1,根据行检验算法可知,新的ECC行检验码为:
          P2047_1^i,P2047_0^(~i)
新旧ECC行检验码异或得其结果为:
           P2047_1^(P2047_1^i)= (P2047_1^P2047_1)^i=0^I=i
        P2047_0^(P2047_0^(~i))= (P2047_1^P2047_1)^(~i)=0^(~i)=~i
2.En由1->0
未发生错误前,由于En=1,将影响其它ECC行检验码,故其原始ECC行校验码
          P2047_1^i,P2047_0^i
发生错误后,由于En=0,根据行检验算法可知,故不影响ECC行检验码:
          P2047_1,P2047_0
新旧ECC行检验码异或得其结果为:
          P2047_1^(P2047_1^i)= (P2047_1^P2047_1)^i=0^I=i
               P2047_0^(P2047_0^(~i))= (P2047_1^P2047_1)^(~i)=0^(~i)=~i
可见,新旧ECC校检验码异或结果已经包括了错误的字节的位置i。
if(byte&0x40)       //判断第6位为行检验码
{
   row0^=((~i)&0x7ff);
  row1^=(i&0x7ff);
}
根据算法分析,新旧row1异或的结果其值就是i,其值为p8192_1/p4096_1等
同理,新旧ECC校检验码异或结果已经包括了错误的字节中哪一位错误的位置
总结如下:我们将异或结果中P8192_1/P4096_1/P2048_1……P8_1这些位取出组成一个11位的数,其值就是错误字节数在2048个字节中的位置。
同理,将p1_1/p2_1/p4_1取出,组成一个3位的数,其值就是字节中错误的列数。
好了,到此为止,我们对ECC校验算法进行了彻底的研究。搞清了它的生成原理和编程算法,以及确定错误位置的原理及算法。

三、S3C2440中硬件ECC检验算法的编程实现
在我们彻底研究了ECC检验算法的基础上,我们自然可以采取软件编程实现的方法来实现,但同时,s3c2440内部实现了ECC的硬检验。由于K9F2G08为8位NAND,因此,主要对以下几个寄存器进行操作。
NFMECC0:用于保存自动生成的[8~0]位ECC码
NFMECC1:用于保存自动生成的[15~8]位ECC码,不用。
NFMECCD0/1:将space区域ECC码写入,和NFMECC0寄存器内的ECC码异或
NFESTAT0:保存新旧ECC码异或结果。
根据资料,要启用ECC,
1.        NFCONT寄存器位4置1用以初始化ECC(在初始化代码中实现)
2.        NFCONT寄存器位5置0用以解开MAIN区域的ECC        
3.        读取2048个字节,以生成ECC码
4.        NFCONT寄存器位5置1用以锁定MAIN区域的ECC,即NFMECC0寄存器不再改变
当我们读取NAND第0页第1页和重读第0页数据时,其生成的ECC码如图

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主| 吕氏春秋8266 发表于 2011-1-13 21:20:43 | 显示全部楼层
从结果可以看出,三次读取页数据,其检验码只有在第一次读取0页时正确,第二次读取1页时错误,第三次重读0页时生成的竞是第1页的ECC码,当时差点让人崩溃。

为了找到问题的症结所在,决定在读取页数据前先查看一下NFMECC0寄存器的值,重启系统调试发现,三次读取页数据前,其值如下:

NFMECC0=0xf0ffffff

NFMECC0=0x50969569

NFMECC0=0xf0c3fc33

经过分析发现当读取页数据结束并锁定ECC值时,NFMECC0的值为

NFMECC0=NFMECC0^(~页ECC码)

第一次读取为0XF0FFFFFF^(~0X50969569[页0ECC码])=0X50969569

第二次读取为0X50969569 ^(~0X50AA96A5[页1ECC码])=0XF0C3FC33

第三次赢取为0XF0C3FC33^(~0X50969569[页0ECC码])=0x50aa96a5

通过分析,我们得知,要想得到正确的页ECC码,显然,NFMECC0寄存器其值必须保持为0XF0FFFFFF

找到了问题的症结所在,并在对NFMECC0赋值无效的情况下,查找资料发现。

好了,在解开ECC寄存器之后,重新对NFCONT[4]位置1,NFMECC0寄存恢复原值0XF0FFFFFF,这样读取任意页均可得到正确的ECC码。

       在得到页ECC码的情况下,我们读取[2048~2051]得到写页数据时生成的ECC码

并将该值放入NFMECCD0/1两个寄存器的相关位置,这样S3C2440将自动对两个ECC码进行异或,并根据结果设置NFESTAT0寄存器。我们可以根据该寄存器的值来判断我们读取页数据是否成功。

       经程序调试发现,对NFMECCD0/1寄存器赋值必须一次完成,否则检验结果不正确。

NFESTAT0[1~0]位表示如下:

0 没有错误 1 一位错误可纠下 2不可纠下错误,3,ECC码错误

NFESTAT0[6~4]位表示错误字节错误位的列数

NFESTAT0[17~7]位表示错误字节的位置。

经过调试发现,其保存值均为反码,也就是说我们得到该位的值必须将其取反。
读取页数据其主要核心代码如下:
UN_MAINECC();
NF_RESTECC();                 //使NFMECC0寄存器值复原

       for(i=0;i<512;i++)             //pData为32位,一次读取4字节

       {

           pData=NF_DATA();

       }

       LOCK_MAINECC();

       Ecc_Data=NF_DATA();        //得到写生成时的ECC码        

       NF_nFCE_H();

       //对寄存器赋值必须一次写完,否则检验结果将不正确

       NF_MECCD0()=((Ecc_Data)&0xff)|((Ecc_Data<<8)&0xff0000);                  

       NF_MECCD1() =((Ecc_Data>>16)&0xff)|((Ecc_Data>>8)xff0000);      

       Ecc_Stat=NF_STAT0();

       if((Ecc_Stat&0x3)==0) return true;         //数据没有错误

       if((Ecc_Stat&0x3)==1)                   //一位数据错误,纠正该错误位

       {

              row=~((unsigned int)(Ecc_Stat>>7));  //取反

              row&=0x7ff;

              arr=~((unsigned int)(Ecc_Stat>>4));

              arr&=0x7;

              

              temp=((unsigned char *)pData)[row];

              if(((temp>>arr)&0x1)==1)

              {

                     temp&=(unsigned char)(~(1<<arr));

              }

              else {

                     temp|=(unsigned char)(1<<arr);

              }

              ((unsigned char *)pData)[row]=temp;            

              return true;

       }

体会:在对处围设置相关寄存器不同位赋值时,最好一次性完成,否则可能导致无法预测的结果。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
 楼主| 吕氏春秋8266 发表于 2011-1-13 21:23:25 | 显示全部楼层
最后,附上源代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
xiaoqiang08wang 发表于 2011-1-14 19:26:37 | 显示全部楼层
看不懂呢咋
pzxsoso 发表于 2011-3-23 15:03:06 | 显示全部楼层
谢谢楼主分享!
txgc_wm 发表于 2011-4-1 21:38:41 | 显示全部楼层
感谢楼主!
txgc_wm 发表于 2011-4-1 21:44:45 | 显示全部楼层
请教一下,为什么地址是右移11位,而不是12位。我看列地址是12位呀!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

i.MX8系列ARM cortex A53 M4 工控板上一条 /1 下一条

Archiver|手机版|小黑屋|天嵌 嵌入式开发社区 ( 粤ICP备11094220号 )

GMT+8, 2024-5-24 03:06 , Processed in 1.031250 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表