结构

引脚名称 引脚功能
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位地址线译码进行控制,片选信号决定选择哪个地址段。

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

 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 ;
}
 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;
}