第六站,来到大都市——nandflash程序完全解析

第六站,来到大都市——nandflash程序分析
每一次旅行,都会到一个新的地方,而对于新的环境,我们在一开始,会觉得陌生,路在何方?该往哪里走?我们心中只有一个名字而没有方向,所以我们得学着如何寻址,得需要一个地图,一种方法,才能找到我们要去的地方,取得我们想要得到的东西。
对于嵌入式,nand就好比一个大都市,里面存放着大部分东西我们需要用到,所以我们一定得要知道如何操作他。
 
 
照样,我们来先给出关于nandflash控制器的各种寄存器的宏定义
regs.h:
/*****************************nand**************************/
#define BASE_NAND 0x70200000
#define NFCONF (*(volatile unsigned int *)(BASE_NAND + 0x00))      //配置寄存器
#define NFCONT (*(volatile unsigned int *)(BASE_NAND + 0x04))      //控制寄存器
#define NFCMMD (*(volatile unsigned int *)(BASE_NAND + 0x08))     //命令寄存器
#define NFADDR (*(volatile unsigned int *)(BASE_NAND + 0x0c))     //地址寄存器
#define NFDATA (*(volatile unsigned int *)(BASE_NAND + 0x10))     //数据寄存器
#define NFSTAT (*(volatile unsigned int *)(BASE_NAND + 0x28))     //状态寄存器
#define GPOCON (*(volatile unsigned int *)0x7F008140)
#define GPPCON (*(volatile unsigned int *)0x7F008160)
/****************************lcd*****************************/

其实尽力过这么多站了,对于如何上下车,早已经不再是什么新鲜事了,新鲜的事,也就是关键的事是我们如何去初始化一个新的硬件呢,
请看nand.c文件,其中包括nandflash的初始化,以及读写nand的函数
nand.c:
#include "common.h"
#include "regs.h"

/*对于当前的NAND Flash来说是256M字节的容量
*整个NAND包括2048个块(其他NAND可能会变)
*每一个块里包括64个页(其他NAND可能会变)
*每一个页包括2048字节的存储空间和64字节
*其他NAND的页大小可能不同,但是6410只支持两种页:
*     512字节的页和2048字节的页,所以即使是用了其他的NAND
*     也要把它当作2948字节的页来操作
*/

/*定义三个宏,分别使能、禁止片选和检查NAND是否可操作*/
#define EN_NAND     (NFCONT &= ~(1 << 1))          //NFCONT寄存器的第1位就是用来使能片选,禁止片选的,取0使能,取1禁止
#define DS_NAND     (NFCONT |= 1 << 1)
#define BS_NAND     while(!(NFSTAT & 1))          //NFSTAT状态寄存器,通过位[0]判断存储器是否忙,为1,则不忙,为0,则忙,不可被操作。

void nand_init(void)
{
     /*配置GPPCON3-7为NAND专用*/
     GPPCON &= ~(0x3ff << 6);     //这里不用多说,回到第二站去吧
     GPPCON |= 0x2aa << 6;

     /*配置NANDC的控制属性,详情查看S3C6410手册P258*/
     NFCONF = (0 << 12) | (2 << 8) | (0 << 4) | (1 << 1);     //第1位表示地址周期选择,这里赋值1表示选择为5地址周期。
                                                                           //(2 << 8) | (0 << 4)是设置TACLS的设置相关了,我不多说,请看
                                                                           //贴子:http://www.100ask.net/forum/showtopic-4247.aspx
                                                                          
     NFCONT = (1 << 7) | (1 << 6) | (1 << 1) | (1 << 0);          //(1 << 0)表示控制器使能,(1 << 1) 强制nGCS[2]为高,禁用片选,
                                                                           //(1 << 6)表示锁存备用区ECC(主要是与读取ECC区有关,具体有啥用,我也没了解),(1 << 7)锁存主区ECC。

     /*重新启动NAND*/
     EN_NAND;
     NFCMMD = 0xff;     //发命令,只要把命令写到命令寄存器即可,同理,发地址和发数据也是一样写对应的寄存器,命令ffh就是用来复位即重启的,更多命令请查看nandflash手册
     BS_NAND;
     DS_NAND;
}

/*发送要写或者要读的函数
*要分五个周期发过去
*详情查看S3C6410手册P261和NAND手册P9*/
void send_addr(unsigned int addr)
{
     unsigned int col_addr = addr & 0x7ff; //看地址周期图我们知道,列地址是低11位(这里有些重点内容,请看稍后附加说明),
                                                    //即这里用我们传入的地址值与上0x7ff,得到低11位值
     unsigned int raw_addr = (addr >> 12) & 0x1ffff;     //同理,这将列地址移出,然后再与上0x1ffff,得到17位行地址。

     NFADDR = col_addr & 0xff;               //列地址,所谓列地址,通俗讲就是页内地址,以下两个连续地址周期发列地址,就是(0~2048+64-1),2048表示页大小,加上的64表示OOB区大小
     NFADDR = (col_addr >> 8) & 0x7;
     NFADDR = raw_addr & 0xff;               //行地址,也就是说哪一页地址,以下三个地址周期发行地址。
     NFADDR = (raw_addr >> 8) & 0xff;
     NFADDR = (raw_addr >> 16) & 1;
}

/*发送擦除地址,NAND擦除必须以块为单位
*擦除的时候只需要发送一个能够定位到页
*的地址即可,NAND会把那个页所在的块整
*个都擦除了*/
void send_block_addr(unsigned int addr)                    //这是发送块地址,在nand_erase()函数中被调用,
                                                                 //nand的擦除是以块为单位,因此这里只需要发送块地址,不需要列地址
{
     unsigned int raw_addr = (addr >> 12) & 0x1ffff;

     NFADDR = raw_addr & 0xff;
     NFADDR = (raw_addr >> 8) & 0xff;
     NFADDR = (raw_addr >> 16) & 1;
}

/*以下为对NAND的擦除和读写操作函数
*详情参看S3C6410手册P261和NAND手册P16
*/
void nand_read(unsigned int ddr, unsigned int nand, unsigned int len)     //读函数,其中的参数一表示读出的内容存放地址,参数二表示读的源起始地址,参数三表示读的长度
{
     unsigned int addr = nand;
     int i;
    
     EN_NAND;
     for(addr = nand; addr < nand + len; addr += 2048){  //因为读写是以页为单位,所以这里是每读一页(2048),就让add自加2048,指向下一页
          NFCMMD = 0x00;                                             //这里是命令读的第一周期命令
          send_addr(addr);                                        //发地址,5个周期地址一发,寻址
          NFCMMD = 0x30;                                             //读的第二周期命令,这样就可以正确读数据了
          BS_NAND;                                                  //为什么要关呢?
          for(i = 0; i < 2048; i += 4, ddr += 4){               //这里是将读到的数据保存在指定地址,这个是保存的地址不是nand上,所以读不是以页为单位,采用4字节的写。
               *((volatile unsigned int *)ddr) = NFDATA;
          }
     }
     DS_NAND;
}

void nand_erase(unsigned int nand, unsigned int len)
{
     unsigned int addr;

     EN_NAND;
     for(addr = nand; addr < nand + len; addr += 64 * 2048){
          NFCMMD = 0x60;     //命令60h就是 Block Erase,块擦除命令
          send_block_addr(addr);     //操作,都是先发命令再发地址的,如果是传输数据,最后发数据
          NFCMMD = 0xd0;               //这是第二周期的命令,有些命令是需要配对使用的,比如60h和d0h配对,表示擦除块。
                                        //又比如00h和30h配对表示Read,具体请看nandflash数据手册,不再赘述。
          BS_NAND;
     }
     DS_NAND;
}

void nand_write(unsigned int ddr, unsigned int nand, unsigned int len)
{
     unsigned int addr;
     int i;

     EN_NAND;
     for(addr = nand; addr < nand + len; addr += 2048){
          NFCMMD = 0x80;
          send_addr(addr);
          for(i = 0; i < 2048; i += 4, ddr += 4){
                         NFDATA = *((volatile unsigned int *)ddr);
          }
          NFCMMD = 0x10;
          BS_NAND;
     }
     DS_NAND;
}

附加说明:
     关于NAND的地址问题,在网上有各种争论,这的确也是一个理解上的难点,在此,我给出我的理解吧,也只能说是个人之见,希望对看到此分析的人有所帮助。
     通过查看nandflash数据手册,我们看到地址周期表(不方便贴图啦,自己对着去翻翻吧)在寻找nand中的一个地址时,我们需要分5次发送地址,所以我们
     传入的一个地址值,需要被拆分为5部分。
void send_addr(unsigned int addr)
{
     unsigned int col_addr = addr & 0x7ff; //看地址周期图我们知道,列地址是低11位(这里有些重点内容,请看稍后附加说明),
                                                    //即这里用我们传入的地址值与上0x7ff,得到低11位值
     unsigned int raw_addr = (addr >> 11) & 0x1ffff;     //同理,这将列地址移出,然后再与上0x1ffff,得到17位行地址。

     NFADDR = col_addr & 0xff;               //列地址,所谓列地址,通俗讲就是页内地址,以下两个连续地址周期发列地址,就是(0~2048+64-1),2048表示页大小,加上的64表示OOB区大小
     NFADDR = (col_addr >> 8) & 0x7;
     NFADDR = raw_addr & 0xff;               //行地址,也就是说哪一页地址,以下三个地址周期发行地址。
     NFADDR = (raw_addr >> 8) & 0xff;
     NFADDR = (raw_addr >> 16) & 1;
}
列地址就是页内地址,行地址就是页的首地址.
一个nand分很多页,而一页的大小为2K(2048,应该是2048+64-1的,但是我们程序中一般不会去读写OOB区,所以这里说的页,是程序可读写,真正的页不是2K),
     nand的寻址:就好比一个学校,学校分很多系部教学楼,而系部教学楼又分了很多间教室,我们要定位一个教室,那么首先得要定位是哪个
系部,然后找到了这个系部,再去在系部内寻找我们要定位的那个教室,nand寻址也就是这样一个过程。
例如:给出地址5000,一页是2048,那么5000是不是超过了2页的大小?那么这个地址落在第三页,5000-2*2048=904,904只是相当于这页首地址在本页内的偏移,最终我们找到了正确的地址。
          由此,我们在发这个地址的时候,列地址,即前两个周期发送的地址值就该是0x388(十进制为904),行地址(即后三个地址周期)发送值为0x2(十进制2,计数从0开始的)
     继续理解地址:地址可以这样计算的,继续比如寻址5000,那么5000/2048=2.44140625,那么是在第2页(从0计数),小数点后面的不够成一页,那么在页内,以该页为偏移,地址值为
                         2048*0.44140625=904,即在页内的偏移,也就是所谓的列地址。
通过看nandflash的数据手册中地址周期表,A0~A10为列地址,那么语句col_addr = addr & 0x7ff即是提取出给定地址的低11位。
raw_addr = (addr >> 11) & 0x1ffff;     //同理,这将列地址移出,然后再与上0x1ffff,得到17位行地址。

既然已经成功初始化nand,也写好了读写nand函数,那么接下来,就是在main函数中去验证他的时候了。具体请看main.c文件内容
main.c:
#include "common.h"
#include "regs.h"

int main(int argc, char **argv)
{
     nand_init();
    
     /*tftp 54000000 zImage*/
     /*nand erase 40000 300000*/

     nand_erase(0x40000, 0x300000);
     nand_write(0x54000000, 0x40000, 0x300000);
     nand_read(0x50008000, 0x40000, 0x300000);

     printf("everything OK...\n");

     return 0;
}





我记得我曾经花了蛮多功夫去理解nand,以为理解的很好了,或许当初的确是,但是过了这么久,现在去复习,发现有些地方居然理解不了了,学习啊,还是的温故而知新,今天又花了好长一段时间来各方查证,发现自己还存在诸多问题,那么继续努力吧。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值