本节课的主要内容如下:
NAND Flash访问原理
- 地址空间概念
- NAND的编址
- NAND的命令
- 使用S3C2440的NAND Flash控制器访问NAND Flash
地址空间概念
NAND Flash有数据总线,没有地址总线。
内存的地址总线直接接2440发出来的地址总线上去。
网卡的地址总线也是跟2440的地址总线直接相连。
因此,SDRAM,DM9000(网卡)接到2440的地址总线上去了。
Nand Flash没有接到2440的地址总线。
结论:Nand Flash与其他两种的寻址方式是不一样的。另外两种的地址是CPU可见的或者成为CPU统一编址的。SDRAM的地址是0x30000000-0x34000000,片内RAM的内存是0-4094(4k),寄存器,DM9000,这些都有一块地址,这些是CPU统一的编址空间。
Nand Flash有256M,而Nand Flash的编址空间时独立于CPU之外的,与上面讲到的地址不同。
NAND的编址
NAND Flash的原理图如下图所示:
如果通过数据线来实现数据线和地址线的共用,这就需要用到控制信号。
我们用的是大页的空间,大页空间为2k,还有个OOB的区域,该区域有64字节,64页组成一块。故一页的大小为2048+64,64字节不参与寻址,即访问2049的地址时,访问的是第一页的第二个字节。
如何访问内存:
发出地址,传送数据
如何访问Nand:
先发出命令(命令有发送数据,读命令),再发出地址,在传输数据
命令代码如下:
控制信号的发出由寄存器开控制:
- 想发出命令时,只需要往NFCMMD寄存器写入值,2440就会自动控制命令引脚将命令发给NAND Flash。
- 想操作地址时,只需要将地址写入NFADDR寄存器就可以了。
- 读和写数据都是NFADATA寄存器来完成。
从硬件上访问Nand:
- 发出命令,涉及CLE引脚,还要将命令发送到数据总线上去
- 发出地址,发出ALE地址锁存信号,再将将地址发送到地址总线上去
- 传输数据,由引脚控制读写
2440精简了这些东西:
- 发出命令,把命令写进NFMMD寄存器就行
- 操作地址,NFADDR寄存器
- 读写数据,NFADATA寄存器
- 状态,NFSTA ,擦除之后才能写,擦除时要等一会才能写,这是就要涉及状态
下面为makefile的内容,依赖的文件是head.o init.o nand.o main.o
nand.bin : $(objs)
arm-linux-ld -Tnand.lds -o nand_elf $^
arm-linux-objcopy -O binary -S nand_elf $@
arm-linux-objdump -D -m arm nand_elf > nand.dis
%.o:%.c
arm-linux-gcc -Wall -c -O2 -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -c -O2 -o $@ $<
clean:
rm -f nand.dis nand.bin nand_elf *.o
再来看一下nand.lds这个链接脚本,该脚本的意思是从0地址开始分别写head.o init.o nand.o的程序,然后再4096的地方放main的函数。运行时,第一段的内容应该在0地址运行,而第二段的内容则应该在0x30000000的地址处执行,“应该”这字很关键,运行时应该把程序放在0x30000000的链接地址上去。
SECTIONS {
firtst 0x00000000 : { head.o init.o nand.o}
second 0x30000000 : AT(4096) { main.o }
}
该实验程序的过程以及目的(感觉和SDRAM的实验很像):
- Nand Flash里面的内容是:从0地址开始放head.o init.o nand.o,在4096地址放main.o。
- 上电时会自动把0-4k的内容,即head.o init.o nand.o的内容拷贝到片内RAM中。
- 运行片内RAM的内容,该内容有关看门狗,初始化SDRAM,其实地址为0x30000000。
- 片内RAM的代码还要将Nand Flash里面4096地址开始的main.o的内容拷贝到SDRAM里面去。
- 通过给pc赋值将运行地址转移到SDRAM里面去,main的函数是点灯函数,当灯亮是说明程序运行成功。
该实验过程与之前SDRAM实验的区别,之前没有对Nand Flash进行初始化,而且该实验中Nand Flash中4k内存之外还有程序,将4k之外的程序拷贝到SDRAM中就是其区别。
这个实验拷贝到SDRAM的内容和SDRAM实验、MMU实验拷贝内容的区别:
SDRAM拷贝过去的内容是完完全全原函数的内容,MMU拷贝的内容除了有一级页表,还有leds.o程序,而这些内容是在4k内存内的,就是说一上电就已经拷进片内RAM中,而该实验拷贝的是main.o 程序的内容,且该内容是在4k内存外。
head.s的内容如下:
.text
.global _start
_start:
@函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义
ldr sp, =4096 @设置堆栈
bl disable_watch_dog @关WATCH DOG
bl memsetup @初始化SDRAM
bl nand_init @初始化NAND Flash
@将NAND Flash中地址4096开始的1024字节代码(main.c编译得到)复制到SDRAM中
@nand_read_ll函数需要3个参数:
ldr r0, =0x30000000 @1. 目标地址=0x30000000,这是SDRAM的起始地址
mov r1, #4096 @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赋值的方法进行跳转
halt_loop:
b halt_loop
ldr sp, =4096 一开始设置栈的原因:关看门狗的功能是通过C函数实现的,因此需要设置栈。(为什么用C函数写的就要设置栈呢?)
Nand_init函数的内容挺复杂的。
初始化后接下来代码的目的将4k之外的内容读取到SDRAM里面去,那么就要知道:
- 从哪里读,4096
- 读到那里去,0x30000000
- 读多大,2k
上面的程序中r0,r1,r2分别对应函数的第一个、第二个、第三个传递参数,nand_read的函数内容如下所示。nand_read(a, b, c)
void nand_read(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
#ifdef LARGER_NAND_PAGE
if ((start_addr & NAND_BLOCK_MASK_LP) || (size & NAND_BLOCK_MASK_LP)) {
return ; /* 地址或长度不对齐 */
}
#else
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return ; /* 地址或长度不对齐 */
}
#endif
/* 选中芯片 */
nand_select_chip();
for(i=start_addr; i < (start_addr + size);) {
/* 发出READ0命令 */
write_cmd(0);
/* Write Address */
write_addr(i);
#ifdef LARGER_NAND_PAGE
write_cmd(0x30);
#endif
wait_idle();
#ifdef LARGER_NAND_PAGE
for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++) {
#else
for(j=0; j < NAND_SECTOR_SIZE; j++, i++) {
#endif
*buf = read_data();
buf++;
}
}
/* 取消片选信号 */
nand_deselect_chip();
return ;
}
nand_init函数的程序
void nand_init(void)
{
#define TACLS 0
#define TWRPH0 3
#define TWRPH1 0
/* 判断是S3C2410还是S3C2440 */
if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
{
nand_chip.nand_reset = s3c2410_nand_reset;
nand_chip.wait_idle = s3c2410_wait_idle;
nand_chip.nand_select_chip = s3c2410_nand_select_chip;
nand_chip.nand_deselect_chip = s3c2410_nand_deselect_chip;
nand_chip.write_cmd = s3c2410_write_cmd;
nand_chip.write_addr = s3c2410_write_addr;
nand_chip.read_data = s3c2410_read_data;
/* 使能NAND Flash控制器, 初始化ECC, 禁止片选, 设置时序 */
s3c2410nand->NFCONF = (1<<15)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
}
else
{
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;
#ifdef LARGER_NAND_PAGE
nand_chip.write_addr = s3c2440_write_addr_lp;
#else
nand_chip.write_addr = s3c2440_write_addr;
#endif
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();
}