nand flash控制

本文最后更新于 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的过程:

  1. 发出命令字
  2. 发出地址
  3. 读写数据

命令字及操作方法

命令 第一个周期 第二个周期 第三个周期
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的读写顺序如下:

  1. 设置NFCONF寄存器
  2. 向NFCMD寄存器写入命令
  3. 向NFADDR写入地址
  4. 读写数据,通过NFSTAT检测NAND Flash状态,在启动某个操作后,应检测R/nB信号判断该操作是否已经完成

寄存器介绍

  1. NFCONF: 在S3C2440中用来设置时序参数TACLS、TWRPH0、TWRPH1,设置数据位宽,还有一些只读位,用来指示是否支持其他大小的页
  2. NFCONT:用来使能/禁止NAND Flash,使能/禁止控制引脚信号nFCE、初始化ECC
  3. NFCMD: 写入命令字
  4. NFADDR: 写这个寄存器就会按序发出地址信号
  5. NFDATA: 只用到低8位,读写此寄存器将启动对NAND Flash的读写操作
  6. 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 @ 读取设置值,并让r24
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 //K9F1G08使用2048+64列
#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; //存储nand相关操作的函数地址

static S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000; //s2c2440nand控制器地址

static t_nand_chip nand_chip;

/* 供外部调用的函数 */
void nand_init(void);
void nand_read(unsigned char *buf, unsigned long start_addr, int size);

/* NAND Flash操作的总入口, 它们将调用S3C2440的相应函数 */
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);

/* S3C2440的NAND Flash处理函数 */
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);

/* S3C2440的NAND Flash操作函数 */

/* 复位 */
static void s3c2440_nand_reset(void)
{
s3c2440_nand_select_chip();
s3c2440_write_cmd(0xff); // 复位命令
s3c2440_wait_idle();
s3c2440_nand_deselect_chip();
}

/* 等待NAND Flash就绪 */
static void s3c2440_wait_idle(void)
{
int i;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFSTAT;
while(!(*p & BUSY)) //*p=1表示就绪,跳出循环
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; // 将LED1-4对应的GPB5/6/7/8四个引脚设为输出

GPBDAT = ~(1<<5) | ~(1<<7) | ~(1<<8);
while(1)
{
wait(30000);
GPBDAT = (~(i<<5)); // 根据i的值,点亮LED1-4
if(++i == 16)
i = 0;
}

return 0;
}

nand flash控制
https://www.xinhecuican.tech/post/5da26719.html
作者
星河璀璨
发布于
2022年1月26日
许可协议