第六站,来到大都市——nandflash程序分析
![](https://i-blog.csdnimg.cn/blog_migrate/5c36cecacd0d7008c496aa4dae939df2.png)
每一次旅行,都会到一个新的地方,而对于新的环境,我们在一开始,会觉得陌生,路在何方?该往哪里走?我们心中只有一个名字而没有方向,所以我们得学着如何寻址,得需要一个地图,一种方法,才能找到我们要去的地方,取得我们想要得到的东西。
对于嵌入式,nand就好比一个大都市,里面存放着大部分东西我们需要用到,所以我们一定得要知道如何操作他。
照样,我们来先给出关于nandflash控制器的各种寄存器的宏定义
其实尽力过这么多站了,对于如何上下车,早已经不再是什么新鲜事了,新鲜的事,也就是关键的事是我们如何去初始化一个新的硬件呢,
既然已经成功初始化nand,也写好了读写nand函数,那么接下来,就是在main函数中去验证他的时候了。具体请看main.c文件内容
我记得我曾经花了蛮多功夫去理解nand,以为理解的很好了,或许当初的确是,但是过了这么久,现在去复习,发现有些地方居然理解不了了,学习啊,还是的温故而知新,今天又花了好长一段时间来各方查证,发现自己还存在诸多问题,那么继续努力吧。
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*****************************/
#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位行地址。
#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;
}
#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,以为理解的很好了,或许当初的确是,但是过了这么久,现在去复习,发现有些地方居然理解不了了,学习啊,还是的温故而知新,今天又花了好长一段时间来各方查证,发现自己还存在诸多问题,那么继续努力吧。