1.相关原理图如下所示:
注:
以上接法在s3c2440手册里有说明:
我的nand flash : 2048Bytes/page, 大小128M,由上篇对nandflash的分析可以得知需要4个地址周期 , 8位的总线宽度.也就是NCON=1,GPG13=1,GPG14=0,GPG15=0.
所以上一张接线图应该把NR5跟NR4去掉,但是mini2440的GPG14管脚接上了高电平?也就变成了NCON=1,GPG13=1,GPG14=0,GPG15=0….估计是为了往后兼容更大容量的nand flash吧...在nand手册里对于写地址周朝那一部分注释说明如果有超过的地址周期就会被忽略,所以不管怎样,写代码的时候CPU给的第五个周期我们不给数据就好了,没影响.反正对于K9F1G08U0B都会被忽略.
2.相关寄存器如下:
1)与LED灯相关:
略…
2)与NAND FLASH 控制器相关的:
①NFCONF
相关Pin介绍:
NFCONF[13:12](TACLS)=01B;NFCONF[10:8](TWRPH0)=000B;NFCONF[6:4](TWRPH1)=000B;NFCONF[0]=0B;其它是只读的.
TACLS跟TWRPH0/1,在S3C2440手册里有下图示:
关于CLE/ALE持续时间在nandflash手册里有如下图示:
在nand flash手册里还规定了,tCLS/ALS+ tCLH/ALS>=17ns,如下图所示:(这也就要求了S3C2440里面nand flash控制器里TACLS>=17ns(其中TACLS值对应HCLK时钟个数,而TERPH0/1对应的HCLK时钟个数要本值加一))
关于nWE/nRE周期持续时间在nandflash手册里由如下图示:
在nand flash手册里也规定了,tWP>=12ns, tWH>=10ns , 如下图所示:(这也就要求了S3C2440里nand flash控制器里的TWRPH0>=12ns ,TWRPH1>=10ns )
没有进行PLL倍频时,HCLK=12M,也就是一个HCLK时钟周期为83ns,这样CFCONF寄存器里的TWRPH0/1都设置为000B,TACLS设置为01B就可以满足时序要求了,以下是对NFCONF的配置情况:
NFCONF[13:12](TACLS)=01B;NFCONF[10:8](TWRPH0)=000B;NFCONF[6:4](TWRPH1)=000B;NFCONF[0]=0B;其它是只读的.
②NFCONT
相关Pin:
Lock-tight : 这个pin在第一次置位后就不能在清零了.只有复位或者从睡眠模式唤醒才可以对它清零(不能实现软件清零);
当这个pin置位时,NFSBLK(地址区域起始块)到NFEBLK(区域结束块地址)-1区间不上锁,当NFSBLK和NFEBLK一样,所有区域被锁上.配置为0,不要上锁.
Soft Lock : 跟Lock-tight相似,差别在于可以通过软件来更改这个bit.配置为0,不上锁.
EnbillegalAdccINT : 设置在CPU写或擦除被上锁的区域时是否产生中断,这里选择不要中断就可以了
EnbRnBINT : RnB状态输入信号传输中断控制;选择0,不产生RnB中断
RnB_TransMode : 选择0,选择检测上升沿,也就是从Busy状态变到ready状态.
SpareECCLock : 选择1,对spare area ECC产生进行上锁(等到在读写数据之前再解锁,让它产生ECC值,读写完数据之后重新上锁,就把ECC存放到相应寄存器了)
MainECCLock : 选择1,对Main area ECC产生进行加锁(同上)
InitECC : 选择1,初始化ECC
Reg_nCE : 片选信号,刚开始时我们禁止掉,以后在需要对nand flash操作时再打开.
MODE : 使能NAND flash控制器,选择1
综上所述:选择配置如下:,
NFCONT[13:0]=0 0 0 00 0 0 1 11 00 1 1B
③NFCMMD
④NFADDR
⑤NFDATA(因为使用的nand flash是位宽为8位,所以只有前8位有效)
以下有几个寄存器是关于ECC校验的,下面对S3C2440对ECC的处理:
1.ECC模块为每次读写数据产生奇偶校验码 , 所以必须在读写数据之前复位ECC并且Unlock ECC产生.
2.不管数据是读还是写 , ECC模块产生的ECC奇偶校验码都存在NFMECC0/1.
3.在你完全读写完一页数据之后(不包括spare areadata) ,设置MainECCLock bit为'1'(Lock).ECC奇偶校验码就会被锁存起来了,ECC 状态寄存器值没有被改变.
4.通过Unlock spareECCLock(NFCONT[6]) bit可以产生spare area ECC奇偶校验码.
5.不管数据是被读还是写,spare area ECC模块产生的ECC奇偶校验码都放在NFSECC.
6.当完全读写好了spare area,通过设置spareECCLock为1(Lock).ECC奇偶校验码就会被锁存住,并且ECC状态寄存器不会被改变.
7.一旦完成了我们就可以使用这些值来报告给spare area 或者检测错误位.
注意:
NFSECCD,NFMECCD用于(通常我们都把main dataarea 的ECC写到spare area里)之前从写入到main data area产生出来的放在spare area里的 ECC.CPU会自动比较NFSECCD与NFSECC里面的内容,NFMECCD与NFMECC里的内容是否一致,然后设置NFESTAT0/1状态寄存器,对于8位接口的,NFESTAT1无效.
⑥NFMECCD0/1(从spare area读出来的main ECC要手动放入这个寄存器,让CPU进行判断.我的是8位接口,所以NFMECCD0/1[15:8]的数据是无效的)
⑦NFSECCD(从spare area里读取的spare ECC要手动放入这个寄存器)
⑧NFSTAT
⑨NFESTAT0/1
⑩NFMECC0/1 MFSECC (读写数据产生的ECC奇偶校验码存入这里)
NFSBLK,NFEBLK
3.程序流程图设计:
①主程序流程图:
②内存拷贝子程序
4.程序设计:
①nand.lds
SECTIONS {
first 0x00000000 : { head.o }
second 0x30000000 : AT(2048) { nand.o }
third 0x30008000 : AT(4096) { main.o }
}
②Makefile
nand.bin : head.o nand.o main.o
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 : %.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o : %.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f nand.dis nand.bin nand_elf *.o *.bak
③head.S
#define WTCON 0x53000000
#define BWSCON 0x48000000
#define BANKCON6 0x4800001C
#define REFRESH 0x48000024
#define BANKSIZE 0x48000028
#define MRSRB6 0x4800002C
#define GPBCON 0x56000010
#define GPBDAT 0x56000014
.text
.global _start
_start:
/******关闭看门狗**********/
ldr r0 , =WTCON
mov r1 , #0x0
str r1 , [r0]
/*****END关闭看门狗********/
/***调用初始化内存子程序***/
bl memory_init
/**********END*************/
/*拷贝0x800~0x1000 to 0x30000000*/
bl copy_bootsram_to_sdram
/**************END***************/
/***********设置栈顶指针*********/
ldr sp , =0x34000000 @设置栈
/***************END**************/
ldr lr , =D1 @调用c函数前设置好返回地址
ldr pc , =fnand_copyFromNand @调用C函数nand_read
D1:
/*********判断是否正确拷贝********/
cmp r0 , #0x1
beq halt_loop
/*****如果是,执行下面指令********/
ldr lr , =halt_loop
/*******跳转到main测试子程序******/
ldr pc , = main
/****************END**************/
halt_loop:
b halt_loop
/******拷贝前4K代码到sdram*******/
copy_bootsram_to_sdram:
mov r0 , #2048
ldr r1 , =0x30000000
ldr r2 , =4*1024
copy:
ldr r3 , [r0] , #4
str r3 , [r1] , #4
cmp r0 , r2
bne copy
mov pc , lr
/*****END拷贝前4K代码到sdram*****/
memory_init:
/*******内存初始化子程序*********/
@BWSCON[27:24] = 0 0 10B
ldr r0 , =BWSCON
ldr r1 , [r0]
ldr r2 , =(0x0F<<24)
bic r1 , r1 , r2
ldr r2 , =(0x02<<24)
orr r1 , r1 , r2
str r1 , [r0]
@BANKCON6[16:15]=11B;BANKCON6[3:0]=00 01B
ldr r0 , =BANKCON6
ldr r1 , [r0]
ldr r2 , =(0x03<<15)
bic r1 , r1 , r2
orr r1 , r1 , r2
ldr r2 , =0x0F
bic r1 , r1 , r2
ldr r2 , = 0x01
orr r1 , r1 , r2
str r1 , [r0]
@REFRESH[23:18] = 1 0 00 00B;REFRESH[10:0] = 0x7A3
ldr r0 , =REFRESH
ldr r1 , [r0]
ldr r2 , =(0x3F<<18)
bic r1 , r1 , r2
ldr r2 , =(0x20<<18)
orr r1 , r1 , r2
ldr r2 , =0x7FF
bic r1 , r1 , r2
ldr r2 , = 0x7A3
orr r1 , r1 , r2
str r1 , [r0]
@BANKSIZE[7:0] = 1 0 1 1 0 001 B
ldr r0 , =BANKSIZE
ldr r1 , [r0]
ldr r2 , =0xFF
bic r1 , r1 , r2
ldr r2 , =0xB1
orr r1 , r1 , r2
str r1 , [r0]
@MRSRB6[11:0] = 0 00 011 0 000 B
ldr r0 , =MRSRB6
ldr r1 , [r0]
ldr r2 , =0x3FF
bic r1 , r1 , r2
ldr r2 , =0x030
orr r1 , r1 , r2
str r1 , [r0]
mov pc , lr @函数返回
/******END内存初始化子程序*******/
/************************************************END************************************************************/
④nand.c
/* NAND FLASH */
#define NFCONF (*(volatile unsigned long *)0x4E000000)
#define NFCONT (*(volatile unsigned long *)0x4E000004)
#define NFCMMD (*(volatile unsigned long *)0x4E000008)
#define NFADDR (*(volatile unsigned long *)0x4E00000C)
#define NFDATA (*(volatile unsigned long *)0x4E000010)
#define NFSTAT (*(volatile unsigned long *)0x4E000020)
/**
*提取出页内列号,块内页号,块号
*/
#define dnand_addr2ColAddr(addr) (addr & 0x7FF)
#define dnand_addr2RowAddr(addr) (( addr & 0xFC00 ) >> 11)
#define dnand_addr2BlockAddr(addr) (addr >> 17)
/**
*初始化NAND Flash
*/
#define dnand_init() {NFCONT = 0x73;}
/**
*等待NAND Flash就绪
*/
#define dnand_waitReady() {while(!(NFSTAT & 0x1));}
/**
*发出片选信号
*/
#define dnand_enable() {NFCONT &= ~(1<<1);}
/**
*取消片选信号
*/
#define dnand_disable() {NFCONT |= (1<<1);}
/**
*发出命令
*/
#define dnand_writeCmd(cmd) {NFCMMD = cmd;}
/**
*读数据
*/
#define dnand_readData() (*(volatile unsigned char *)&NFDATA)
//为何不能用下面代替,反汇编后结果是一模一样的???????????????????????
//#define dnand_readData() ((unsigned char )NFDATA)
/**
*写数据
*/
#define dnand_writeData(data) {*(volatile unsigned char *)&NFDATA = data;}
/**
*写地址
*/
#define dnand_writeAddr(addr) { NFADDR = addr & 0xff; \
NFADDR = (addr >> 8) & 0x0f; \
NFADDR = (addr >> 11) & 0xff;\
NFADDR = (addr >> 19) & 0xff;\
}
/**
* 复位
*/
static void fnand_reset(void)
{
dnand_enable();
dnand_writeCmd(0xff); // 复位命令
dnand_waitReady();
dnand_disable();
}
/**
*读取ID
*/
static unsigned int fnand_readID()
{
unsigned int Device=0;
//选中芯片
dnand_enable();
//发出READ0命令
dnand_writeCmd(0x90);
dnand_writeAddr(0x00);
dnand_waitReady();
dnand_readData(); //去掉第一个数据(制造厂商)
Device |= dnand_readData()<<24;
Device |= dnand_readData()<<16;
Device |= dnand_readData()<<8;
Device |= dnand_readData();
dnand_disable();
return Device;
}
/**
*块擦除函数
*/
static unsigned char fnand_eraseBlocks(unsigned int startBlockNum , unsigned int blockCount)
{
unsigned int i;
dnand_enable();
for( i = 0 ; i < blockCount ; i++ ){
//发送擦除命令
dnand_writeCmd( 0x60 );
dnand_writeAddr( startBlockNum << 6 );
dnand_writeCmd(0xD0);
dnand_waitReady();
//读取状态
dnand_writeCmd( 0x70 );
if ( ( dnand_readData() & 0x01 ) ){
return 1;
}
startBlockNum += 1;
}
dnand_disable();
return 0;
}
/**
*从nand flash的sourAddr处读取pageCount页数据到内存的destAddr地址处
*/
void fnand_readOfPage(unsigned char *destAddr , unsigned int sourAddr , unsigned int pageCount)
{
unsigned int i , j;
//使源地址对齐在2048
sourAddr &= ( ~0x7FF );
dnand_enable();
for( j = 0 ; j < pageCount ; j++ ){
//发出read命令
dnand_writeCmd( 0x00 );
dnand_writeAddr( sourAddr );
dnand_writeCmd( 0x30 );
dnand_waitReady( );
//开始读一页数据到destAddr里
for( i = 0 ; i < 2048 ; i++ ){
*destAddr++ = dnand_readData();
}
//源地址跳到下一页
sourAddr += 0x800;
}
dnand_disable();
}
/**
*从内存的sourAddr处写入pageCount页数据到nand flash的destAddr地址处
*/
unsigned char fnand_writeOfPage(unsigned char *sourAddr , unsigned int destAddr , unsigned int pageCount)
{
unsigned int i , j;
unsigned int startBlockNum , blockCount ;
//使源地址对齐在2048,因为页大小为2K/page
destAddr &= ( ~0x7FF );
//为块起始地址 , 读取的页地址范围所占的块数赋值
startBlockNum = dnand_addr2BlockAddr( destAddr ) ;
blockCount = ( dnand_addr2RowAddr( destAddr ) + pageCount ) >> 6 ;
if ( ( dnand_addr2RowAddr( destAddr ) + pageCount ) & 0x3F ){
blockCount++;
}
//写之前先把当前所在这块数据擦除掉,
//因为nand flash硬件上的cell只能从'1'变为'0',不能从'0'变为'1'
//清除动作能使整块的cell都变成'1'
if ( fnand_eraseBlocks( startBlockNum , blockCount ) ){
return 1;
}
dnand_enable();
for( j = 0 ; j < pageCount ; j++ ){
//发出read命令
dnand_writeCmd( 0x80 );
dnand_writeAddr( destAddr );
//开始写一页数据到destAddr里
for( i = 0 ; i < 2048 ; i++ ){
dnand_writeData( *sourAddr++ );
}
dnand_writeCmd( 0x10 );
dnand_waitReady();
//发送读取状态命令
dnand_writeCmd( 0x70 );
if ( ( dnand_readData() & 0x01 ) ){
return 1;
}
//目标地址跳到下一页
destAddr += 0x800;
}
dnand_disable();
return 0;
}
unsigned char fnand_copyFromNand()
{
dnand_init();
fnand_reset();
GPBCON &= ~( 0xFF << 10 );
GPBCON |= ( 0x55 << 10 ); //把GPB5~8都置为输出功能
GPBDAT = ( 0x0F << 5 );
//判断
if(fnand_readID() != 0xF1009540){
return 1;
}
//起始地址4096,目的地址0x30005000,读出one page
fnand_readOfPage( (unsigned char *)0x30005000 , 4096 , 1 );
//起始地址0x30005000,目的地址131072,写入one page
//这里需要注意的是131072是第一个block的起始地址,
//不要写到第0个块里,不然里面的擦除动作会把整一块内容清除掉
//那样会把head.S的内容也清除的
if( fnand_writeOfPage((unsigned char *)0x30005000 , 131072 , 1 ) ){
return 1;
}
//起始地址6144,目的地址0x30008000,写入one page
fnand_readOfPage( (unsigned char *)0x30008000 , 131072 , 1 );
return 0;
}
⑤main.c