分类: LINUX
2016-10-28 11:37:16
原文地址:Nand flash 基础操作 作者:xuxiyao8888
NAND Flash的数据是以bit 的方式保存在memory cell,一般来说,一个cell 中只能存储一个bit。这些cell 以8 个或者16 个为单位,连成bit line,形成所谓的byte(x8)/word(x16),这就是NAND Device 的位宽。这些Line 会再组成Page。
Nand Flash 根据的page的大小,可以分为small page和big page, 下面以samsung的K9F1G08U0B和K9F5608U0D分别说明:
K9F5608U0D:
page = 512+16 = 528 bytes (small page), 32个page组成一个block。
block = 16K +512 = 0x40000 bytes
K9F1G08U0B
page = (2K + 64)bytes (big page), 64个page组成一个block。
block = (128K + 4K)bytes
page是由两个部分组成的, 一个是main area, 是用来存储数据的, 另外一个叫Spare Area, 是用于存储ECC的校验值的。
Nand flash 以page为单位读写数据,而以block为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址: --Block Address -- Page Address --Column Address(即为页内偏移地址)
对于NAND Flash 来讲,地址和命令只能在I/O[7:0]上传递,数据宽度是8 位。
512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st half和2nd half,各自的访问由地址指针命令来选择,A[7:0]就是所谓的column address。32 个page 需要5bit 来表示,占用A[13:9],即该page 在块内的相对地址。Block的地址是由A14 以上的bit 来表示,例如512Mb 的NAND,共4096block,因此,需要12 个bit 来表示,即A[25:14],如果是1Gbit 的528byte/page的NAND Flash,则block address用A[26:14]表示。而page address就是blcok address|page address in block, NAND Flash 的地址表示为: Block Address|Page Address in block|halfpage pointer|Column Address 地址传送顺序是Column Address,Page Address,Block Address。 由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。 例如,对于512Mbit x8 的NAND flash,地址范围是0~0x3FF_FFFF,只要是这个范围内的数值表示的地址都是有效的。以NAND_ADDR 为例:第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上,而halfpage pointer 即bit8 是由操作指令决定的,即指令决定在哪个halfpage 上进行读写。而真正的bit8 的值是don\'t care 的。 第2 步就是将NAND_ADDR 右移9 位,将NAND_ADDR[16:9]传到I/O[7:0]上 第3 步将NAND_ADDR[24:17]放到I/O 上 第4 步需要将NAND_ADDR[25]放到I/O 上 因此,整个地址传递过程需要4 步才能完成,即4-step addressing。 如果NAND Flash 的容量是256Mbit 以下,那么,block adress 最高位只到bit24,因此寻址 只需要3 步。
对于big page需要两个周期才能将column address写入到flash, 两种flash的具体写address的周期,请见下表:
small page address写入到flash中的周期:
I/0 0 | I/0 1 | I/O 2 | I/0 3 | I/0 4 | I/0 5 | I/0 6 | I/0 7 | |
1st Cycle | A0 | A1 | A2 | A3 | A4 | A5 | A6 | A7 |
2st Cycle | A9 | A10 | A11 | A12 | A13 | A14 | A15 | A16 |
3st Cycle | A17 | A18 | A19 | A20 | A21 | A22 | A23 | A24 |
big page address写入到flash中的周期:
I/0 0 | I/0 1 | I/O 2 | I/0 3 | I/0 4 | I/0 5 | I/0 6 | I/0 7 | |
1st Cycle | A0 | A1 | A2 | A3 | A4 | A5 | A6 | A7 |
2st Cycle | A8 | A9 | A10 | A11 | 0 | 0 | 0 | 0 |
3st Cycle | A12 | A13 | A14 | A15 | A16 | A17 | A18 | A19 |
4st Cycle | A20 | A21 | A22 | A23 | A24 | A25 | A26 | A27 |
对应周期数,会随着flash的大小变化的, 下面会对nand flash K9f1208读写擦举例,虽然这个也是samll page的,但是有4个周期。
就x16 的NAND flash 器件稍微进行一下说明。 由于一个page 的main area 的容量为256word,仍相当于512byte。但是,这个时候没有所谓 的1st halfpage 和2nd halfpage 之分了,所以,bit8就变得没有意义了,也就是这个时候 bit8 完全不用管,地址传递仍然和x8 器件相同。除了,这一点之外,x16 的NAND使用方法和 x8 的使用方法完全相同。
每个nand flash都有一定的区别:
K9F5608U0D
function | 1st. Cycle | 2nd Cycle | Acceptable Command during Busy |
read | 00h/01h | - | |
read 2 | 50h | - | |
read ID | 90h | - | |
reset | FFh | - | 0 |
page program | 80h | 10h | |
copy back program | 00h | 8Ah | |
block erase | 60h | D0h | |
read status | 70h | - | 0 |
K9F1G08U0B
function | 1st. Cycle | 2nd Cycle | Acceptable Command during Busy |
read 1 | 00h | 30h | |
read for copy back | 00h | 35h | |
read ID | 90h | - | |
reset | FFh | - | 0 |
page program | 80h | 10h | |
copy back program | 85h | 10h | |
block erase | 60h | D0h | |
random data input | 85h | - | |
random data output | 05h | E0h | |
read status | 70h | 0 | |
read EDC status | 7Bh | 0 |
Nand Flash在出厂时会在每个block的0页面的space(512--527)空间中标记出坏块, small page和big page的地址还是不一样的, small page是在第5个字节处标明, big page是在第1个字节处标明,此页是否为坏块。如果为0xff则不是坏块,这个是nand flash出厂时的坏块检测方法。当nand flash在使用过程中出现了坏块,如何发现并且标记它呢?
答:通过某种坏块检测的软件算法对每一页的512个字节进行一定的判断,计算出一个ECC码,然后将其放入00B中,等若干时间后再用同样的算法对其进行检测,再次得到一个ECC码,将这个ECC码和之前计算出的ECC码进行比较,如果相等则不是坏块,如果不等则为坏块,然后就将其标记。
须掌握:如何遍历坏块:
ECC算法有两种:软件ECC算法,通用性好,每256个字节算出一个3byte的ECC码,故512个字节算出6byte的ECC码。
下面以small page为例,举例读写擦的过程, 对于下一节, 几乎将的都以big page为例,也有部分small page的东西。
K9f1208的寻址分为4个cycle。分别是:A[0:7]、A[9:16]、A[17:24]、A[25]。
读操作的过程为: 1、发送读取指令;2、发送第1个cycle地址;3、发送第2个cycle地址;4、发送第3个cycle地址;5、发送第4个cycle地址;6、读取数据至页末。
K9f1208提供了两个读指令,‘0x00’、‘0x01’。这两个指令区别在于‘0x00’可以将A[8]置为0,选中上半页;而‘0x01’可以将A[8]置为1,选中下半页。
虽然读写过程可以不从页边界开始,但在正式场合下还是建议从页边界开始读写至页结束。下面通过分析读取页的代码,阐述读过程。
static void ReadPage(U32 addr, U8 *buf)
//addr表示flash中的第几页,即‘flash地址>>9’
{
U16 i;
NFChipEn(); //使能NandFlash
WrNFCmd(READCMD0); //发送读指令‘0x00’,由于是整页读取,所以选用指令‘0x00’
WrNFAddr(0); //写地址的第1个cycle,即Column Address,由于是整页读取所以取0
WrNFAddr(addr); //写地址的第2个cycle,即A[9:16]
WrNFAddr(addr>>8); //写地址的第3个cycle,即A[17:24]
WrNFAddr(addr>>16); //写地址的第4个cycle,即A[25]。
WaitNFBusy(); //等待系统不忙
for(i=0; i<512; i++)
buf[i] = RdNFDat(); //循环读出1页数据
NFChipDs(); //释放NandFlash
}
写操作的过程为: 1、发送写开始指令;2、发送第1个cycle地址;3、发送第2个cycle地址;4、发送第3个cycle地址;5、发送第4个cycle地址;6、写入数据至页末;7、发送写结束指令
下面通过分析写入页的代码,阐述读写过程。
static void WritePage(U32 addr, U8 *buf)
//addr表示flash中的第几页,即‘flash地址>>9’
{
U32 i;
NFChipEn(); //使能NandFlash
WrNFCmd(PROGCMD0); //发送写开始指令’0x80’
WrNFAddr(0); //写地址的第1个cycle
WrNFAddr(addr); //写地址的第2个cycle
WrNFAddr(addr>>8); //写地址的第3个cycle
WrNFAddr(addr>>16); 写地址的第4个cycle
WaitNFBusy(); //等待系统不忙
for(i=0; i<512; i++)
WrNFDat(buf[i]); //循环写入1页数据
WrNFCmd(PROGCMD1); //发送写结束指令’0x10’
NFChipDs(); //释放NandFlash
}
static U32 EraseBlock(U32 addr)
{
UINT8T stat;
// addr &= ~0x1f;
NFChipEn(); //芯片使能,片选拉低,Nand flash使能
WrNFCmd(ERASECMD0); //写命令0x60表示块擦除操作将开始
WrNFAddr(addr); //地址分三次传送
WrNFAddr(addr>>8); //先传a9--a16,
if(NandAddr)
WrNFAddr(addr>>16); //后传a17-a24 ,a25
WrNFCmd(ERASECMD1); //传完地址后,写命令0xd0h(按时序要求)
stat = WaitNFBusy(); //写完擦除命令后,执行等待
uart_printf(".......................!\n");
NFChipDs(); //关闭片选
if(stat == 0)
{
uart_printf("erase success!\n");
}
else
{
uart_printf("erase error!\n");
}
#ifdef ER_BAD_BLK_TEST
if(!((addr+0xe0)&0xff)) stat = 1; //just for test bad block
#endif
//printf("Erase block 0x%x %s\n", addr, stat?"fail":"ok");
uart_printf (".");
return stat;
}
当往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区出错;其他情况均表示出现了无法纠正的错误。
了能检测到单个位错误,把每256字节的数据划分为一张8x256共2048位的表,使用22位校验码进行校验,16位为行校验码,进行横向校验,6位为列校验码,按进行纵向校验。下图描述了这22位校验码产生的原理:
列校验:
CP0:对所有字节的Bit 6,4,2,0进行异或操作的结果
CP1:对所有字节的Bit 7,5,3,1进行异或操作的结果
.
.
CP5:对所有字节的Bit 7,6,5,4进行异或操作的结果
行校验:
LP0:对0,2,4,6...254字节的所有位进行异或操作的结果
LP1:对1,3,5,7...255字节的所有位进行异或操作的结果
.
.
LP15:对128~255字节的所有位进行异或操作的结果
22位校验码需要三个字节保存,前两字节保存16位行校验码LP0~LP15,第三字节保存6位列校验码CP0~CP5,剩余2位置1:
把数据写入Nand Flash时,我们产生校验码并将它保存到Flash的Spare Data Area。从Flash读出数据时,产生一个新的ECC校验码,将它和Spare Data Area读出的旧校验码进行异或操作:
结果为0:数据正确
结果有11位为1:数据有1位错误(可纠正)
其他结果:数据超过1位发生错误,无法纠正
下面是ECC产生算法的c++实现代码(仅满足示意用途,为了便于理解没有经过优化,在实际使用中,大量的异或运算可转为查表操作)
1. static inline void SetBit(unsigned int& dat, int bit, int v)
2. {
3. if (v)
4. dat |= (1<5. else
6. dat &= ~(1<7. }
8.
9.
10. static inline int GetBit(unsigned int dat, int bit)
11. {
12. if (dat & (1<13. return 1;
14. else
15. return 0;
16. }
17.
18.
19. static inline int XorLP(unsigned char c)
20. {
21. return (GetBit(c, 7) ^ GetBit(c, 6) ^ GetBit(c, 5) ^ GetBit(c, 4)
^ GetBit(c, 3) ^ GetBit(c, 2) ^ GetBit(c, 1) ^ GetBit(c, 0) );
22. }
23.
24.
25. static inline int XorCP0(unsigned char c)
26. {
27. return (GetBit(c, 6) ^ GetBit(c, 4) ^ GetBit(c, 2) ^ GetBit(c, 0) );
28. }
29.
30. static inline int XorCP1(unsigned char c)
31. {
32. return (GetBit(c, 7) ^ GetBit(c, 5) ^ GetBit(c, 3) ^ GetBit(c, 1) );
33. }
34.
35. static inline int XorCP2(unsigned char c)
36. {
37. return (GetBit(c, 5) ^ GetBit(c, 4) ^ GetBit(c, 1) ^ GetBit(c, 0) );
38. }
39.
40. static inline int XorCP3(unsigned char c)
41. {
42. return (GetBit(c, 7) ^ GetBit(c, 6) ^ GetBit(c, 3) ^ GetBit(c, 2) );
43. }
44.
45. static inline int XorCP4(unsigned char c)
46. {
47. return (GetBit(c, 3) ^ GetBit(c, 2) ^ GetBit(c, 1) ^ GetBit(c, 0) );
48. }
49.
50. static inline int XorCP5(unsigned char c)
51. {
52. return (GetBit(c, 7) ^ GetBit(c, 6) ^ GetBit(c, 5) ^ GetBit(c, 4) );
53. }
54.
55.
56.
57. unsigned int ECC(const unsigned char* data)
58. {
59. unsigned int ecc = 0xFFFFFFFF;
60.
61. for(size_t i=0; i<256; i++)
62. {
63. unsigned char c=data[i];
64.
65. int lp = XorLP(c);
66.
67. // Bit 0
68. if (i & 0x01)
69. SetBit(ecc, 1, lp ^ GetBit(ecc, 1) );
70. else
71. SetBit(ecc, 0, lp ^ GetBit(ecc, 0) );
72.
73. // Bit 1
74. if (i & 0x02)
75. SetBit(ecc, 3, lp ^ GetBit(ecc, 3) );
76. else
77. SetBit(ecc, 2, lp ^ GetBit(ecc, 2) );
78.
79. // Bit 2
80. if (i & 0x04)
81. SetBit(ecc, 5, lp ^ GetBit(ecc, 5) );
82. else
83. SetBit(ecc, 4, lp ^ GetBit(ecc, 4) );
84.
85. // Bit 3
86. if (i & 0x08)
87. SetBit(ecc, 7, lp ^ GetBit(ecc, 7) );
88. else
89. SetBit(ecc, 6, lp ^ GetBit(ecc, 6) );
90.
91. // Bit 4
92. if (i & 0x10)
93. SetBit(ecc, 9, lp ^ GetBit(ecc, 8) );
94. else
95. SetBit(ecc, 8, lp ^ GetBit(ecc, 8) );
96.
97. // Bit 5
98. if (i & 0x20)
99. SetBit(ecc, 11, lp ^ GetBit(ecc, 11) );
100. else
101. SetBit(ecc, 10, lp ^ GetBit(ecc, 10) );
102.
103. // Bit 6
104. if (i & 0x40)
105. SetBit(ecc, 13, lp ^ GetBit(ecc, 13) );
106. else
107. SetBit(ecc, 12, lp ^ GetBit(ecc, 12) );
108.
109. // Bit 7
110. if (i & 0x80)
111. SetBit(ecc, 15, lp ^ GetBit(ecc, 15) );
112. else
113. SetBit(ecc, 14, lp ^ GetBit(ecc, 14) );
114.
115. SetBit(ecc, 18, XorCP0(c) ^ GetBit(ecc, 18) );
116. SetBit(ecc, 19, XorCP1(c) ^ GetBit(ecc, 19) );
117. SetBit(ecc, 20, XorCP2(c) ^ GetBit(ecc, 20) );
118. SetBit(ecc, 21, XorCP3(c) ^ GetBit(ecc, 21) );
119. SetBit(ecc, 22, XorCP4(c) ^ GetBit(ecc, 22) );
120. SetBit(ecc, 23, XorCP5(c) ^ GetBit(ecc, 23) );
121. }
122.
123. SetBit(ecc, 16, 1);
124. SetBit(ecc, 17, 1);
125.
126. return ecc;
127. }
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;
}
这里的烧写是指打开software check ecc,如果使用nand 子指令是没有问题的,会自动跳过bad block,还会计算出ecc并写入flash中,但是前提在flash中有可用的bootcode,但是如果是空的,那就必须使用烧code机烧写 flash了, 如果使用烧code机就不一样了,需要将要烧如flash的binary处理一下,将binary中包含oob信息,这里我们自己写了两个tools来处理这件事, 两个tools分别是针对small page和big page的。
命令的使用方法:big_page/small_page [source file] [dest file] [oob file]
命令的原理:就是在适当的位置,插入oob的ECC值。
用烧code机烧flash时应注意:
erase时, access mothod中Invalid Block Management要选择Do not use, 这样擦的时候不会检测是不是坏块, Spare Area Usage选择User Data with IB Info forced。
write时,需要将Invalid Block Management改为Skip IB,写时会跳过坏块,Spare Area Usage选择User Data with IB Info forced, 选择binary中的ecc信息, 即ECC_SOFTWARE。