本文最后更新于 2024年10月1日 上午
结构
| 引脚名称 |
引脚功能 |
| I/$O_0$ - I/$O_7$ |
数据输入输出 |
| CLE |
命令锁存使能 |
| ALE |
地址锁存使能 |
| $\overset{-}{CE}$ |
芯片使能 |
| $\overset{-}{RE}$ |
读使能 |
| $\overset{-}{WE}$ |
写使能 |
| $\overset{-}{WP}$ |
写保护 |
| $R/\bar{B}$ |
就绪/忙输出信号 |
| $V_{cc}$ |
电源 |
| $V_{ss}$ |
池 |
| NC |
不接 |
K9F1208U0M容量64mb,每一页的大小是512字节,并且每一页上还有额外的字节。也就是说总共有$2^{18}$个字节
读写nand的过程:
- 发出命令字
- 发出地址
- 读写数据
命令字及操作方法
| 命令 |
第一个周期 |
第二个周期 |
第三个周期 |
| Read1(读) |
00h/01h |
- |
- |
| Read2(读) |
50h |
- |
- |
| Read ID(读芯片ID) |
90h |
- |
- |
| Page Program(写页) |
80h |
10h |
- |
| Block Erase(擦除块) |
60h |
D0h |
- |
| Read Status(读状态) |
70h |
- |
- |
Read Multi-Plane Status (读多层状态) |
71h |
- |
- |
| Reset(复位) |
FF |
- |
- |
| Page Program (Dummy) |
80h |
11 |
- |
| Copy-Back Program(True) |
00 |
8a |
10 |
| Copy-Back Program(Dummy) |
03 |
8a |
11 |
| Multi-Plane Block Erase |
60 |
D0 |
- |
命令字后发送的地址序列
|
I/O 0 |
I/O 1 |
I/O 2 |
I/O 3 |
I/O 4 |
I/O 5 |
I/O 6 |
I/O 7 |
备注 |
| 第一个地址序列 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
列地址 |
| 第二个地址序列 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
行地址(页地址) |
| 第三个地址序列 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
行地址 |
| 第四个地址序列 |
25 |
- |
- |
- |
- |
- |
- |
- |
行地址 |
列地址是为了选择行内具体某个位置的,可以看到列地址可以选择256个字节,因此读命令00表示前256个字节,而01表示后256个字节。总共发出的地址数也只有25个,还有一位就是由命令决定的。
每一页共有528个字节,分为三部分,前256个称为A区,中间256个称为B区,最后16个字节称为C区。A区命令字为00h,B区01h,C区50h。
- Read1: 00或01。决定读A区还是B区,然后发送地址开始读数据
- Read2: 50h。读C区
- Reset: FF。复位NAND FLASH芯片,如果正在处于读、写、擦除撞他那么会终止这些命令。
- Page Program(True): 写数据。分为两阶段,80和10
NAND写操作一般都是以页为单位的,但是可以只写一部分,首先发送80后发送4个地址序列,然后向Flash中发送数据,然后发出命令字10h启动写操作,此时Flash内部会自动完成写、校验操作。一旦发出命令字10后,就可以使用70获知当前写操作是否完成
- Page Program(Dummy):命令字为80和11

如图所示每个为512m的flash存储器,有四个128m存储器(Plane)排列构成。因此我们可以在4个连续的块内同时进行写或者擦除操作
Copy-Back Program(True): 该命令将一页复制到同一个Plane中的另一页,它省略了独处再写入的过程,加快了速度。它的命令字分为三个阶段,00h,8ah, 10h
首先发送读命令,然后发送地址,将这528个字节存到内部存储器中,然后发出8ah, 再发出目标地址序列,最后发10h启动写操作
Block Erase: 擦除NAND Flash块。命令分为三个阶段
一块的大小为16Kb,有32页,在发出命令字60后,发出block地址,只需要发出三个行地址,并且A9-A13被忽略。然后发出Do和70
- 读状态命令有两种70和71,在Flash中有状态寄存器,启动读操作可以读入此寄存器,状态寄存器的含义如下表所示
| I/O引脚 |
所标志的状态 |
70对应含义 |
71对应含义 |
| I/O0 |
总标记,成功或失败 |
成功0,失败1 |
同70 |
| 1 |
Plane0标记 |
忽略 |
成功0,失败1 |
| 2 |
Plane1标记 |
… |
… |
| 3 |
Plane2标记 |
… |
… |
| 4 |
Plane3标记 |
… |
… |
| 5 |
保留 |
忽略 |
忽略 |
| 6 |
设备状态 |
忙: 0 , 就绪:1 |
成功0,失败1 |
| 7 |
写保护状态 |
保护0, 没有保护1 |
同70 |
寄存器介绍
NAND FLash的读写顺序如下:
- 设置NFCONF寄存器
- 向NFCMD寄存器写入命令
- 向NFADDR写入地址
- 读写数据,通过NFSTAT检测NAND Flash状态,在启动某个操作后,应检测R/nB信号判断该操作是否已经完成
寄存器介绍
- NFCONF: 在S3C2440中用来设置时序参数TACLS、TWRPH0、TWRPH1,设置数据位宽,还有一些只读位,用来指示是否支持其他大小的页
- NFCONT:用来使能/禁止NAND Flash,使能/禁止控制引脚信号nFCE、初始化ECC
- NFCMD: 写入命令字
- NFADDR: 写这个寄存器就会按序发出地址信号
- NFDATA: 只用到低8位,读写此寄存器将启动对NAND Flash的读写操作
- NFSTAT: 只用到最低位,0: busy, 1: ready
实例
示例的存储器容量为128M并且一页大小为2048
片选是由于存储器往往有很多个芯片,而片选是选择这些芯片中的某一个。例如s3c2440中将1G的地址分为8段,有3位地址线译码进行控制,片选信号决定选择哪个地址段。
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
| head.s
.equ MEM_CTL_BASE, 0x48000000 .equ SDRAM_BASE, 0x30000000 .equ PROGRAM_BASE, 0x30004000
@ 从NAND启动CPU时,CPU会通过内部硬件将NAND FLASH开始的4kb数据复制到 @ 称为Steppingstone的4kb内部RAM中(起始地址为0), 然后跳转到0开始执行 @ bl是相对跳转指令,并且返回值存在R14中, ldr是搬运数据指令,如果有=号表示将某个值给某个寄存器(伪指令),否则用该地址的数据进行跳转 .text .global _start _start: ldr sp, =4096 bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 bl memsetup @ 设置存储控制器 bl nand_init ldr r0, =0x30000000 @1. 目标地址=0x30000000,这是SDRAM的起始地址 mov r1, #8192 @2. 源地址 = 4096,连接的时候,main.c中的代码都存在NAND Flash地址4096开始处 mov r2, #2048 @3 复制长度= 2048(bytes),对于本实验的main.c,这是足够了 bl nand_read @调用C函数nand_read ldr sp, =0x34000000 @设置栈 ldr lr, =halt_loop @设置返回地址 ldr pc, =main @b指令和bl指令只能前后跳转32M的范围,所以这里使用向pc赋值的方法进行跳转
on_sdram: ldr sp, =0x34000000 @ 设置栈 bl main
halt_loop: b halt_loop
disable_watch_dog: mov r1, #0x53000000 @ 看门狗是一个硬件,一段时间没有写值会自动重启,关闭需要在该位置置零 mov r2, #0x0 str r2, [r1] mov pc, lr @ 返回, lr的值给pc
memsetup: @ 设置存储控制器以便使用sdram等外设
mov r1, #MEM_CTL_BASE @ 存储控制器的13个寄存器开始地址 adrl r2, mem_cfg_val @ 13个值的初始存储地址 add r3, r1, #52 @ 13 * 4 = 52 1: ldr r4, [r2], #4 @ 读取设置值,并让r2加4 str r4, [r1], #4 cmp r1, r3 bne 1b mov pc, lr
.align 4 mem_cfg_val: @ 存储控制器13个寄存器的设置值 .long 0x22011110 @ BWSCON, 每r位控制一个bank, 0位为启用SDRAM的数据掩码,如果为0则启用SDRAM,为1启用SRAM, 1位是否启用wait信号,通常为0,后面两位设置位宽,00 是8位,01是16位,10是32位 .long 0x00000700 @ BANKCON0 0-5是时序控制,默认的700就足以 .long 0x00000700 @ BANKCON1 .long 0x00000700 @ BANKCON2 .long 0x00000700 @ BANKCON3 .long 0x00000700 @ BANKCON4 .long 0x00000700 @ BANKCON5 @ 6-7可以接sdram或sram, 因此有所不同.[16: 15]表示是sdram(11)还是sram(00,且设置和0-5相同),如果是sdram,则[3: 2]ras to cas delay,推荐01。[1: 0]列地址位数00是8位,01是9位,10是10位 .long 0x00018005 @ BANKCON6 .long 0x00018005 @ BANKCON7 .long 0x008c07a3 @ REFRESH [23]: 是否启用刷新,1启动。[22]:刷新模式.[21: 20]设置成0, [19:18]:默认11,[10: 0]: 是R_CNT=2^11 + 1 - SDRAM时钟频率*SDRAM刷新周期 .long 0x000000b1 @ BANKSIZE [7]:如果为1核支持突发传输 [5]:1为使用scke信号进入省电模式 [4]: 1仅在访问SDRAM期间发出SCLK(推荐)[2: 0]: 设置bank6-7大小 .long 0x00000030 @ MRSRB6, 设置sdram时序 .long 0x00000030 @ MRSRB7
|
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
| nand_init.c #include "nand.h" #define BUSY 1 #define NAND_SECTOR_SIZE_LP 2048 #define NAND_BLOCK_MASK_LP (NAND_SECTOR_SIZE_LP - 1) typedef unsigned int S3C24X0_REG32; typedef struct { void (*nand_reset)(void); void (*wait_idle)(void); void (*nand_select_chip)(void); void (*nand_deselect_chip)(void); void (*write_cmd)(int cmd); void (*write_addr)(unsigned int addr); unsigned char (*read_data)(void); }t_nand_chip; static S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000; static t_nand_chip nand_chip;
void nand_init(void); void nand_read(unsigned char *buf, unsigned long start_addr, int size);
static void nand_reset(void); static void wait_idle(void); static void nand_select_chip(void); static void nand_deselect_chip(void); static void write_cmd(int cmd); static void write_addr(unsigned int addr); static unsigned char read_data(void);
static void s3c2440_nand_reset(void); static void s3c2440_wait_idle(void); static void s3c2440_nand_select_chip(void); static void s3c2440_nand_deselect_chip(void); static void s3c2440_write_cmd(int cmd); static void s3c2440_write_addr(unsigned int addr); static unsigned char s3c2440_read_data(void);
static void s3c2440_nand_reset(void) { s3c2440_nand_select_chip(); s3c2440_write_cmd(0xff); s3c2440_wait_idle(); s3c2440_nand_deselect_chip(); }
static void s3c2440_wait_idle(void) { int i; volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFSTAT; while(!(*p & BUSY)) for(i=0; i<10; i++); }
static void s3c2440_nand_select_chip(void) { int i; s3c2440nand->NFCONT &= ~(1<<1); for(i=0; i<10; i++); }
static void s3c2440_nand_deselect_chip(void) { s3c2440nand->NFCONT |= (1<<1); } /* 发出命令 */ static void s3c2440_write_cmd(int cmd) { volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFCMD; *p = cmd; } /* 发出地址 */ static void s3c2440_write_addr_lp(unsigned int addr) { int i; volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR; int col, page; col = addr & NAND_BLOCK_MASK_LP; //取得列地址 page = addr / NAND_SECTOR_SIZE_LP; //取得行地址 *p = col & 0xff; /* 列地址 A0~A7 */ for(i=0; i<10; i++); *p = (col >> 8) & 0x0f; /* 列地址 A8~A11 */ for(i=0; i<10; i++); *p = page & 0xff; /* 行地址 A12~A19 */ for(i=0; i<10; i++); *p = (page >> 8) & 0xff; /* 行地址 A20~A27 */ for(i=0; i<10; i++); *p = (page >> 16) & 0x03; /* 行地址 A28~A29 */ for(i=0; i<10; i++); } /* 读取数据 */ static unsigned char s3c2440_read_data(void) { volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFDATA; return *p; } /* 在第一次使用NAND Flash前,复位一下NAND Flash */ static void nand_reset(void) { nand_chip.nand_reset(); } static void wait_idle(void) { nand_chip.wait_idle(); } static void nand_select_chip(void) { int i; nand_chip.nand_select_chip(); for(i=0; i<10; i++); } static void nand_deselect_chip(void) { nand_chip.nand_deselect_chip(); } static void write_cmd(int cmd) { nand_chip.write_cmd(cmd); } static void write_addr(unsigned int addr) { nand_chip.write_addr(addr); } static unsigned char read_data(void) { return nand_chip.read_data(); } /* 初始化NAND Flash */ void nand_init(void) { #define TACLS 0 #define TWRPH0 3 #define TWRPH1 0 nand_chip.nand_reset = s3c2440_nand_reset; nand_chip.wait_idle = s3c2440_wait_idle; nand_chip.nand_select_chip = s3c2440_nand_select_chip; nand_chip.nand_deselect_chip = s3c2440_nand_deselect_chip; nand_chip.write_cmd = s3c2440_write_cmd; nand_chip.write_addr = s3c2440_write_addr_lp; nand_chip.read_data = s3c2440_read_data; /* 设置时序 */ s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */ s3c2440nand->NFCONT = (1<<4)|(1<<1)|(1<<0); /* 复位NAND Flash */ nand_reset(); } /* 读函数 用于把nand flash中代码复制到sdram中*/ void nand_read(unsigned char *buf, unsigned long start_addr, int size) { int i, j; if ((start_addr & NAND_BLOCK_MASK_LP) || (size & NAND_BLOCK_MASK_LP)) { return ; /* 地址或长度不对齐 */ } /* 选中芯片 */ nand_select_chip(); for(i=start_addr; i < (start_addr + size);) { /* 发出READ命令 */ write_cmd(0); /* 写地址 */ write_addr(i); write_cmd(0x30); wait_idle(); for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++) { *buf = read_data(); buf++; } } /* 取消片选信号 */ nand_deselect_chip(); return ; }
|
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
| main.c #define GPBCON (*(volatile unsigned long *)0x56000010) #define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPB5_out (1<<(5*2)) #define GPB6_out (1<<(6*2)) #define GPB7_out (1<<(7*2)) #define GPB8_out (1<<(8*2))
void wait(unsigned long dly) { for(; dly > 0; dly--); }
int main(void) { unsigned long i = 0; GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out;
GPBDAT = ~(1<<5) | ~(1<<7) | ~(1<<8); while(1) { wait(30000); GPBDAT = (~(i<<5)); if(++i == 16) i = 0; }
return 0; }
|