nand flash按照我的理解,在开发板上就类似我们所用的电脑中的硬盘,用来保存系统运行的操作系统,应用程序,数据等,掉电之后还可以永久得保存数据(不包括临时数据)。通过控制或配置NAND Flash的控制器寄存器,即可完成对nand的操作:包括读、写、擦除等。
而控制、配置这些寄存器是根据板子的原理图、用户手册以及nand flash芯片手册上的说明来配置的。
通过原理图,可见nand flash与cpu之间的数据传输主要是通过LDDATA0~7这8根引脚线,其中传输的“数据”可以是地址、数据,也可以是命令,这就要靠CLE、ALE引脚的状态进行选择。
对于命令的传输,主要是对NFCMD寄存器写以上相对应的命令值,但这个命令是分两个周期进行的。
而对于数据的传输,在数据传输模式下,传输数据也是要根据以上的格式去进行传输,分5个周期,每一个周期传输的数据都十分讲究,这就要求要有一个符合这种格式的巧妙算法(在接下来的代码中体现)。
了解了这些命令、数据的传输格式之后,就是要对具体的寄存器进行相应的配置了。首先要介绍这些寄存器的作用(数据手册上对各寄存器都有说明):
(1)NFCONF:用来设置时序参数,设置位宽。
(2)NFCMD:命令寄存器。
(3)NFADDR:地址寄存器。
(4)NFDATA:数据寄存器,用于读写数据。
(5)NFSTAT:状态寄存器,只用到最低1位,表示是否忙碌。
编程举例:
实现从nand flash中拷贝程序到sdram中运行,其中涉及到启动代码的编程,初始化内存控制器,读取并拷贝nand flash上的数据。(只展示部分核心代码)
head.S
.text .global _start _start: ldr sp,=4096 @设置堆栈 bl disable_watch_dog @关闭看门狗 bl set_mem @设置内存控制器 bl nand_init @nand初始化 ldr r0,=0x30000000 ldr r1,=4096 ldr r2,=4096 @传递参数 bl nand_read @从nand中拷贝 ldr sp,=0x38000000 ldr lr, =halt @设置返回地址 ldr pc, =main halt: b halt
nand.c
typedef struct s3c2440_nand { unsigned int NFCONF; unsigned int NFCONT; unsigned int NFCMMD; unsigned int NFADDR; unsigned int NFDATA; unsigned int NFMECCD0; unsigned int NFMECCD1; unsigned int NFSECCD; unsigned int NFSTAT; unsigned int NFESTAT0; unsigned int NFESTAT1; unsigned int NFMECC0; unsigned int NFMECC1; unsigned int NFSECC; unsigned int NFSBLK; unsigned int NFEBLK; }s3c2440_nand; //定义nandflash控制器的起始地址 static s3c2440_nand* nand_base = (s3c2440_nand*)0x4E000000; //片选 void select_chip_or_not(int flag)//0不片选,1片选 { if(flag == 1) { nand_base->NFCONT |= (0x1<<1); } if(flag == 0) { nand_base->NFCONT &= ~(0x1<<1); } } void write_command(unsigned char cmd) { volatile unsigned char *p = (volatile unsigned char *)&nand_base->NFCMMD; *p = cmd; } void write_addr(unsigned int addr) { int i; volatile unsigned char* p = (volatile unsigned char *)nand_base->NFADDR; *p = addr & 0xff; for(i=0; i<10; i++); *p = (addr >> 9) & 0xff; for(i=0; i<10; i++); *p = (addr >> 17) & 0xff; for(i=0; i<10; i++); *p = (addr >> 25) & 0xff; for(i=0; i<10; i++); } //等待nand flash就绪 void wait_ldle(void) { volatile unsigned char* p = (volatile unsigned char*)nand_base->NFSTAT; int flag = *p & 1; while(!flag) { int i; for(i=0;i<20;i++); } } unsigned char read_data(void) { volatile unsigned char *p= (volatile unsigned char*)nand_base->NFDATA; return *p; } void nand_read(unsigned char* buf,unsigned char base_addr,unsigned int size) { int i,j; //片选 select_chip_or_not(1); //复制数据 for(i=base_addr;i<base_addr+size;) { write_command(0);//发送读命令 write_addr(i); //发送地址 write_command(0x30); wait_ldle(); for(j=0;j<512;j++,i++) { *buf = read_data(); //读取数据,一次读取一页(512个字节) buf++; } } select_chip_or_not(0);//取消片选 } void nand_reset(void) { select_chip_or_not(1);//片选 write_command(0xff); wait_ldle(); select_chip_or_not(0); } void nand_init(void) { nand_base->NFCONF = (0<<12)|(3<<8)|(0<<4); nand_base->NFCONT = (1<<4)|(1<<1)|(1<<0); nand_reset(); }
还要说下Makefile
objs := 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 [email protected] %.o : %.S arm-linux-gcc -c $< -o [email protected] %.o : %.c arm-linux-gcc -c $< -o [email protected] clean : rm -f nand.bin *.o nand_elf
平时写的Makefile不一样,这里使用了一个名为nand.lds的链接脚本,这样做主要是为了试验让编译好的程序在链接时存放的地址和理论运行地址都不同且不在同一个存储设备上,方便看试验效果。
nand.lds
SECTIONS { first 0x00000000 : {head.o init.o nand.o} second 0x30000000 : AT(4096) {main.o} }
链接脚本上的第一段是放head.S init.c nand.c编译出的内容,从0地址开始存放和执行,而main.o则是需要从nand flash手动复制到sdram中去执行。
实现的效果如图: