NAND flash在嵌入式设备中作为ROM存储程序和数据。NAND Flash的显著特点是不支持XIP (Excute In Place)、存在坏块、数据不可靠,需要ECC等。本文介绍一款NAND Flash和控制器,并给出实战代码。
nand flash支持的操作包括读、写、擦除等。因为写的时候只能把1变成0。要想把0变成1,只能通过擦除。这也是nand flash在每次写之前需要擦除的原因。nand flash的读写单位是页(page),擦除单位是块(block)。每个页还包括 spare数据区,通常用来存储ECC等。
页大小是(2K+64)Byte,每个块包含64页 (128K+4K)Byte,共有2048个块(256M+8M) x 8bit。页读写时间250us,块擦除时间2ms。在ECC的支持下可以达到100K次擦写周期。
引脚描述
Pin name | description |
I/O0-I/O7 | Data Inputs/Ouputs 数据输入输出引脚。用于输入命令、地址和数据;读时,输出数据。 |
CE | Chip Enable 芯片使能引脚。 |
CLE | Command Latch Enable 指令锁存使能引脚。高电平时,在WE引脚的上升沿将指令锁存到指令寄存器。 |
ALE | Address Latch Enable 地址锁存使能引脚。高电平时,在WE的上升沿将地址锁存到地址寄存器。 |
RE | Read Enable 读使能引脚,用于串行数据输出控制,数据在RE的下降沿有效,同时内部列地址加1。 |
WE | Write Enable 写使能引脚,指令、地址和数据在WE引脚的上升沿锁存。 |
WP | Write Protect 写保护引脚,在电压过度阶段提供误擦写保护。 |
R/B | Ready/Busy Ouput 状态指示引脚,低电压表示正在program/erase or random read,高电平表示完成。 |
Vcc | Power |
Vss | Ground |
N.C | No Connection |
命令列表如下:
Function | 1st cycle | 2nd cycle | Acceptable command during busy |
Read | 00h | 30h | |
Read for Copy back | 00h | 35h | |
Read ID | 90h | - | |
Reset | FFh | - | |
Page Program | 80h | 10h | |
Copy-Back Program | 85h | 10h | |
Two-Plane Page Program | 80h--11h | 81h-10h | |
Block Erase | 60h | D0h | |
Random Data Input | 85h | - | |
Random Data Output | 05h | E0h | |
Read Status | 70h | - | |
Read Status 2 | F1h | - |
出厂坏块 Initial invalid blocks
正常的块在出厂时被擦除为FFh(包括spare区)。出厂坏块不被擦除,但是第1页或第二页的spare数据区的第一个字节被置为非FFh。
读写操作错误 Error In Write Or Read Operation
在NAND的整个生命周期中,可能产生额外的坏块。在写或擦除时,必须检查状态,如果失败,执行必要的块替换。在读取时,必须校验ECC,并执行ECC correction。
ID表
读ID命令,读取5个字节,如下:
1st Byte | Maker Code, DAh |
2nd Byte | Device Code, 10h |
3nd Byte | 0:1 Internal Chip Number 00:1, 01:2, 10:4, 11:8 2:3 Cell Type, 00:2 Level cell, 01:4, 10:8, 11:16 4:5 Number of simulataneously programmed pages, 1,2,4,8 6 Interleave program between multiple chips, 0: no suport, 1: support 7 Cache program, 0: no support, 1: support |
4th Byte | 0:1 Page size, 1/2/4/8KB 2 Redundant area size, 0: 8bytes/512byte, 1: 16bytes/512bytes 4:5 Block size, 64/128/256/512KB 6 Organization, 0:x8, 1:x16 3,7 Serial access minimum, 00:50ns/30ns, 10:25ns, 01/11 reserved |
5th Byte | 2:3 Plane Number, 1/2/4/8 4:6 Plane size, 64Mb/128Mb/256Mb/512Mb/1Gb/2Gb/4Gb/8Gb |
Page read
首先发出00h指令,然后发送5个周期的地址,然后发送30h指令,所选page 2112字节的数据将在40us内拷贝到数据寄存器,可以通过R/B引脚检测拷贝是否完成。之后数据就可以顺序访问,同时也支持随机访问该页内的数据。
Page program
通常一次写一个整页,但是也允许multiple partial page programming,页内的每个局部数据可以是4个字节到2112个字节,同一个页连续的局部页写不能超过4次,否则需要擦除才能再次写。
首先发出80h指令,然后发送地址和数据,然后发送10h开始执行写操作,读取R/B引脚检测写完成,然后发出70h指令,在读取状态数据,IO0为0,表示成功,1表示失败。
地址周期
读操作有5个地址周期,2个列地址周期,3个行地址周期。
Col Addr1 | Col. Addr2 | Row Addr1 | Row Addr2 | Row Add3 |
A0-A7 | A8-A11 | A12-A19 | A20-A27 | A28 |
列地址就是页内地址,12位列地址,则有4096个地址,0-2047是页内数据,2048-2111是oob数据。
行地址是页地址和块地址,一个块有64页,需要6位,所以A12-A17是块内的页地址。共有2048个块,需要11位,所以A18-A28是块地址。
要使用NAND Flash就需要按照其电路时序要求来输出电信号,这通常使用控制器来实现。下面是s3c2440的NAND Flash Controller。
只支持Software Moode,具有下列操作:
1. Writing to the command register = NAND flash command cycle
2. Writing to the address register = NAND flash address cycle
3. Writing to the data register = write cycle
4. Reading from the data register = read cycle
5. Reading main ECC register and Spare ECC registers = read cycle
Data register configuration
8-bit NAND Flash Memory Interface
NFDATA bit[7:0] 1st I/O[7:0]
ECC (ERROR CORRECTION CODE)
包含4个ECC模块,其中两个(一个用于data[7:0],一个用于data[15:8])用于最多2048字节的ECC Parity code generation。另外两个(一个用于data[7:0],一个用于data[15:8])用于最多16字节的ECC Parity code generation。
接口
下面是寄存器资料
1 NFCONF
地址:0x4E000000, R/W, nand flash configuration register, 复位:0x0000100x
控制BusWidth、AddrCycle、PageSize、AdvFlash、TWRPH1、TWRPH0、TACLS
2 NFCONT
地址:0x4E000004, R/W, nand flash control register, 复位:0x0384,这是个16位寄存器。
控制是否激活控制器、控制nFCE的值(接到CE),初始化ECC、main ECC lock、spare ECC lock、RB检测模式、RB是否产生中断、锁区域控制。
3 NFCMMD
地址:0x4E000008, R/W, nand flash command set register, 这是个8位寄存器。
写入命令。
4 NFADDR
地址:0x4E00000C, R/W, nand flash address set register, 这是个8位寄存器。
写入地址
5 NFDATA
地址:0x4E000010, R/W nand flash data register
6 NFMECCD0/1
地址:0x4e000014/8, main data erea ecc register
NFMECCD0: 1st and 2nd register for main data read
NFMECCD1: 3nd and 4th register for main data read
7 NFSECCD
地址: 0x4e00001c, spare area ecc register
8 NFSTAT
地址:0x4e000020, R/W, nand flash operation status register
RB状态
9 NFESTAT0/1
地址:0x4e000024/8 R/W nand flash ecc status register ofr I/O[7:0] I/O[15:8]
10 NFMECC0/1
地址:0x4e00002C/30 R, nand flash main data area ecc register for data[7:0] / data[15:8]
11 NFSECC
地址:0x4e000034 R nand flash spare area ecc register for I/O[15:0]
12 NFSBLK/NFEBLK
地址:0x4e000038/c R/W nand flash programmable start/end block address
惯例,先展示代码:
init.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | // watchdog #define WTCON (*(volatile unsigned long *)0x53000000) void disable_watchdog() { WTCON = 0; } void init_sdram() { *( volatile unsigned long *)0x48000000 = 0x22011110; // BWSCON *( volatile unsigned long *)0x48000004 = 0x00000700; // BANKCON0 *( volatile unsigned long *)0x48000008 = 0x00000700; // BANKCON1 *( volatile unsigned long *)0x4800000c = 0x00000700; // BANKCON2 *( volatile unsigned long *)0x48000010 = 0x00000700; // BANKCON3 *( volatile unsigned long *)0x48000014 = 0x00000700; // BANKCON4 *( volatile unsigned long *)0x48000018 = 0x00000700; // BANKCON5 *( volatile unsigned long *)0x4800001c = 0x00018005; // BANKCON6 *( volatile unsigned long *)0x48000020 = 0x00018005; // BANKCON7 *( volatile unsigned long *)0x48000024 = 0x008C07A3; // REFRESH *( volatile unsigned long *)0x48000028 = 0x000000B1; // BANKSIZE *( volatile unsigned long *)0x4800002c = 0x00000030; // MRSRB6 *( volatile unsigned long *)0x48000030 = 0x00000030; // MRSRB7 } typedef unsigned long uint32_t; typedef unsigned char uint8_t; struct NAND_REG { uint32_t NFCONF; uint32_t NFCONT; uint8_t NFCMMD; uint8_t unused0[3]; uint8_t NFADDR; uint8_t unused1[3]; uint8_t NFDATA; uint8_t unused2[3]; uint32_t NFMECCD0; uint32_t NFMECCD1; uint32_t NFSECCD; uint8_t NFSTAT; uint8_t unused3[3]; uint32_t NFESTAT0; uint32_t NFESTAT1; uint32_t NFMECC0; uint32_t NFMECC1; uint32_t NFSECC; uint32_t NFSBLK; uint32_t NFEBLK; }; volatile struct NAND_REG *s3c2440_nand = ( struct NAND_REG *)0x4E000000; void nand_init() { #define TACLS 0 #define TWRPH0 3 #define TWRPH1 0 s3c2440_nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); s3c2440_nand->NFCONT = (1<<4)|(1<<1)|(1<<0); } static inline void nand_wait_idle() { while ( !(s3c2440_nand->NFSTAT & 1)) asm volatile ( "nop" ); } static inline void nand_select_chip() { s3c2440_nand->NFCONT &= ~(1<<1); } static inline void nand_deselect_chip() { s3c2440_nand->NFCONT |= (1<<1); } static inline void nand_write_cmd(uint8_t cmd) { s3c2440_nand->NFCMMD = cmd; } static inline void nand_write_addr(uint8_t addr) { s3c2440_nand->NFADDR = addr; } static inline uint8_t nand_read_data() { return s3c2440_nand->NFDATA; } // here is the api for K9F2G08U0C // Page 2K + 64 // 64 pages per block // total 2048 blocks #define PAGES_PER_BLOCK 64 #define PAGE_SIZE 2048 #define PAGE_SPARE_SIZE 64 #define TOTAL_BLOCKS 2048 void nand_reset() { nand_select_chip(); nand_write_cmd(0xff); nand_wait_idle(); nand_deselect_chip(); } int nand_read_page(unsigned int block, unsigned int page, unsigned char *buf, unsigned int off, unsigned int len) { if (block >= TOTAL_BLOCKS || page > PAGES_PER_BLOCK) return -1; if (off >= PAGE_SIZE + PAGE_SPARE_SIZE || len > PAGE_SIZE + PAGE_SPARE_SIZE - off) return -1; nand_select_chip(); nand_write_cmd(0x0); nand_write_addr((uint8_t)off); nand_write_addr((uint8_t)(off >> 8)); nand_write_addr((uint8_t)(page|((block&3)<<6))); nand_write_addr((uint8_t)(block >> 2)); nand_write_addr((uint8_t)(block >> 10)); nand_write_cmd(0x30); nand_wait_idle(); while ( len-- > 0) *buf++ = nand_read_data(); return 1; } int nand_write_page() { return 0; } int nand_erase_block() { return 0; } void init() { disable_watchdog(); init_sdram(); nand_init(); nand_reset(); nand_read_page(0, 2, (unsigned char *)0x30000000, 0, 2048); } |
nand_init()里面几个时间值是参考韦东山同学的。
start.S
1 2 3 4 5 6 7 8 9 | .text .global _start _start: ldr sp, =4096 bl init ldr lr,=loop ldr pc,=main loop: b loop |
这里跳转到main没有用bl,因为bl是相对pc跳转,只能跳转32M的范围,这里远远超过了。
bare.lds
1 2 3 4 5 6 7 8 9 10 11 12 | SECTIONS { rom 0x00000000 : { start.o init.o } ram 0x30000000 : AT(4096) { main.o } } |
main.c和第一节的led亮灯代码一样,做跑马灯操作。
编译后,烧录到样机,可以看到跑马灯效果,说明我们的NAND读取是成功的。
[1] https://en.wikipedia.org/wiki/Execute_in_place
[2] Samsung K9F2G08U0C Data sheet.
[3] Samsung s3c2440 Data sheet.
[4] S3C2440对Nand Flash操作和电路原理(基于K9F2G08U0A).
http://www.cnblogs.com/idle_man/archive/2010/12/23/1915303.html
[5] 深度分析NandFlash—控制器参数TACLS、TWRPH0和TWRPH1的确定.
http://blog.csdn.net/huangmc198907/article/details/10531937