嵌入式学习-uboot-lesson12-NandFlash相关

一、NandFlash原理解析

1.NandFlash简介

根据物理结构上的区别,NandFlash主要分为如下两类:

?SLC (Single Level Cell): 单层式存储

?MLC (Multi Level Cell): 多层式存储

SLC在存储格上只存一位数据,而MLC则存放两位数据。

SLC的访问速度比MLC快3倍,SLC能进行10万次的擦写,MLC能进行1万次,MLC功耗比SLC高15%左右

2.访问方式

NANDFlash在地址空间以外,采用独立编址的方式,不同于内存的非独立编址。

根据其原理图可以得知,

一共有8个数据端口DATA0 ~ DATA7,这8个数据端口,需要传递地址、命令、数据。通过寄存器控制这8个端口,实现不同的功能。

3.构成

从上图可以看到,一共有4096个block,每个block又划分为128pages,每一页的4K存放数据,218B存放校验码。

存储单元的构成:

从上图可以看到,存储单元可以被划分为行地址和列地址。

通过行地址可以找到某个页,通过列地址则是找到偏移,扎到存储单元。

4.信号引脚

1. CLE(Command Latch Enable): 命令锁存允许

2. ALE(Address Lactch Enable): 地址锁存允许

3. CE:芯片选择

4. RE:读允许

5. WE:写允许

6. WP:在写或擦除期间,提供写保护

7. R/B:读/忙

二、NandFlash读操作

1.读取的方式

页读:把整个页都读出来,需要提供页地址

随机读:读取页中某一个单元格的地址,需要提供页地址和列地址

本次是使用页读的方式

其流程如下:

2.页读操作

2.1流程

根据上面的图,看I/Ox的执行顺序,便得到页读的主要流程:

1.发送命令0x00

2.address 发送行地址

3.address 发送列地址

4.发送0x30命令

当发送0x30之后,发现RB信号处于忙的状态,因此需要等待一段时间,使其处于等待的状态。

5.等待RB信号

6读取数据

在等待RB信号之前,需要对RB信号做一个初始化的操作,即清除工作,因此需要补充一个,即

0.清除RB信号

然后在操作nandflash之前,需要选中nandflash芯片

0.选中nandflash芯片

综合以上流程,得到页读的总体流程:

  • 1.选中nandflash芯片
  • 2.清除RB信号
  • 3.发送命令0x00
  • 4.address 发送行地址
  • 5.address 发送列地址
  • 6.发送0x30命令
  • 7.等待RB信号
  • 8.读取数据
  • 9.取消选中nandflash芯片

2.2编程实现

  • 1.选中nandflash芯片

    使能nandflash片选信号,根据下面的图可知,将NFCONT 的第一位置0即可

#define NFCONT             (*((volatile unsigned long*)0x70200004))
 NFCONT &= ~(1<<1);    
  • 2.清除RB信号

在等待RB信号之前,需要对RB信号做一个初始化的操作,即清除工作

#define NFSTAT             (*((volatile unsigned char*)0x70200028))
NFSTAT |= (1<<4);
  • 3.发送命令0x00

#define NFCMMD             (*((volatile unsigned char*)0x70200008))
nand_cmd(0x00);
void nand_cmd(unsigned char cmd)
{
    NFCMMD = cmd;
}
  • 4.address 发送列地址

没有偏移,因此两个列地址都为0

从上图可以看到,两个列地址,三个行地址

#define NFADDR             (*((volatile unsigned char*)0x7020000c))  //地址寄存器
nand_addr(0x00);
nand_addr(0x00);
void nand_addr(unsigned char addr)
{
    NFADDR = addr;
}
  • 5.address 发送行地址

一共8个数据IO,因此8位发送一次,

#define NFADDR             (*((volatile unsigned char*)0x7020000c))
    nand_addr(addr&0xff);
    nand_addr((addr >>8 ) & (0xff));
    nand_addr((addr >>16 ) & (0xff));   
  • 6.发送0x30命令
nand_cmd(0x30);
  • 7.等待RB信号

如果这一位没有为1,说明没有处于空闲状态,所以要等其为1

while(!(NFSTAT & 0x1));
  • 8.读取数据

读一个页的大小,为4KB

//读取数据

for(i = 0; i<1024*4; i++) //4KB

{

buff[i] = NFDATA;

}

  • 9.取消选中nandflash芯片
#define NFCONT             (*((volatile unsigned long*)0x70200004))
NFCONT |= (1<<1);

3.初始化操作

初始化就是 对上面两个寄存器进行初始化。

因此其流程为:

  • 1.初始化NFCONF
  • 2.初始化NFCONT
  • 3.复位
  • 1.初始化NFCONF

初始化NFCONF 主要是对上面的三个寄存器进行设置。

根据上面两幅图,可以算出三个寄存器的值

#define TACLS  1
#define TWRPH0 2
#define TWRPH1 1
/*
HCLK的频率为100MHZ,周期就为10ns
TACLS > 0 ns
TWRPH0  > 15ns
TWRPH1 > 5ns

TACLS的值 = HCLK x TACLS > 0ns
TWRPH0的值 = HCLK x (TWRPH0 + 1) > 15ns
TWRPH1的值 = HCLK x (TWRPH1 +1) > 5ns
*/

 NFCONF |= (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
  • 2.初始化NFCONT

 NFCONT = 1 | (1<<1); 
  • 3.复位

如上图,等待RB信号从低到高,便是reset成功。

其流程为:

1.选中

select_ship();

2.清除RnB

clean_RnB();

3.发出复位信号

nand_cmd(0xff);

4.等待就绪

wait_RnB();

5.取消选中

diselect_ship();

4.测试

对上面写的代码进行测试,主要 是通过内存拷贝来验证。

void nand_to_ram(unsigned long start_addr,unsigned char* sdram_addr,int size)
{
    /* i为页号、sdram_addr为内存中的位置、size拷贝数据的大小 */
    int i;

/*
   S3C6410启动时拷贝的8K代码不是存储在Nand flash的第一页上,
   而是存储在Nand flash的前4页上,每页2K,总共8K,

*/
    for (i = 0; i < 4; i++, sdram_addr+=2048)
        {
            NF_PageRead(i,sdram_addr);
        }

        size -= 1024*8;

        for( i=4; size>0;)
    {
        NF_PageRead(i,sdram_addr);
        size -= 4096;  //每拷贝一次就减少4KB
        sdram_addr += 4096;
        i++;
    }      

}

贴上nand.c全部代码:

/********************************************
*file name: nand.c
*author   : stone
*date     : 2016.7.3
*function : nandflash相关操作
*********************************************/
#define NFCONT             (*((volatile unsigned long*)0x70200004))  //控制寄存器
#define NFSTAT             (*((volatile unsigned char*)0x70200028))  //NAND 状态寄存器
#define NFCMMD             (*((volatile unsigned char*)0x70200008))  //命令寄存器
#define NFADDR             (*((volatile unsigned char*)0x7020000c))  //地址寄存器
#define NFDATA             (*((volatile unsigned char*)0x70200010))  //数据寄存器
#define NFCONF             (*((volatile unsigned long*)0x70200000))  //配置寄存器

    /* 设置时间参数 */
#define TACLS  1
#define TWRPH0 2
#define TWRPH1 1

void select_ship(void)
{
    NFCONT &= ~(1<<1);
}

void diselect_ship(void)
{
    NFCONT |= (1<<1);
}

void clean_RnB()
{
    NFSTAT |= (1<<4);
}

void nand_cmd(unsigned char cmd)
{
    NFCMMD = cmd;
}

void nand_addr(unsigned char addr)
{
    NFADDR = addr;
}

void wait_RnB(void)
{
    while(!(NFSTAT & (0x1<4)));
}

void nand_reset(void)
{
    /* 选中 */
    select_ship();

    /* 清除RnB */
    clean_RnB();

    /* 发出复位信号 */
    nand_cmd(0xff);

    /* 等待就绪 */
    wait_RnB();

    /* 取消选中 */
    diselect_ship();
}

void nand_init(void)
{
 //初始化NFCONF

/*
HCLK的频率为100MHZ,周期就为10ns
TACLS > 0 ns
TWRPH0  > 15ns
TWRPH1 > 5ns

TACLS的值 = HCLK x TACLS > 0ns
TWRPH0的值 = HCLK x (TWRPH0 + 1) > 15ns
TWRPH1的值 = HCLK x (TWRPH1 +1) > 5ns
*/

    //NFCONF &= ~((7<<12)|(7<<8)|(7<<4));
    NFCONF |= (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);

 //初始化NFCONT
    NFCONT = 1 | (1<<1); 

 //复位
    nand_reset(); 

}

void NF_PageRead(unsigned long addr,unsigned char* buff)
{
    int i;

    //选中nandflash芯片
    select_ship();

    //清除RNB信号
    clean_RnB();

    //发送命令0x00
    nand_cmd(0x00);

    //发送列地址
    nand_addr(0x00);
    nand_addr(0x00);    

    //发送行地址
    nand_addr(addr&0xff);
        nand_addr((addr >>8 ) & (0xff));
    nand_addr((addr >>16 ) & (0xff));   

    //发送命令0x30
     nand_cmd(0x30);    

    //等待R/B信号
        wait_RnB(); 

    //读取数据
     for(i = 0; i<1024*4; i++)  //4KB
     {
       buff[i] = NFDATA;
     }  

    //取消选中nandflash芯片
    diselect_ship();

}

void nand_to_ram(unsigned long start_addr,unsigned char* sdram_addr,int size)
{
    /* i为页号、sdram_addr为内存中的位置、size拷贝数据的大小 */
    int i;

/*
   S3C6410启动时拷贝的8K代码不是存储在Nand flash的第一页上,
   而是存储在Nand flash的前4页上,每页2K,总共8K,

*/
    for (i = 0; i < 4; i++, sdram_addr+=2048)
    {
        NF_PageRead(i,sdram_addr);
    }

    size -= 1024*8;

    for( i=4; size>0;)
    {
        NF_PageRead(i,sdram_addr);
        size -= 4096;  //每拷贝一次就减少4KB
        sdram_addr += 4096;
        i++;
    }      

}

三、NandFlash写操作

NandFlah写操作和读操作一样,也分为两种按页写,随机写

本次使用页写的方式

流程如下:

1).使用按页写方式。

看上图,可得出流程:

1.发送命令0x80

2.发送列地址(2个周期)

3.发送行地址(3个周期)

4.写入数据

5.发送命令10

6.等待RnB

7.发送命令70

8.读取写入结果

和读操作一样,也需要选中flash芯片和清除RB,因此其最后流程为:

  • 1.选中flash芯片
  • 2.清除RnB
  • 3.发送命令0x80
  • 4.发送列地址(2个周期)
  • 5.发送行地址(3个周期)
  • 6.写入数据
  • 7.发送命令10
  • 8.等待RnB
  • 9.发送命令70
  • 10.读取写入结果
  • 11.取消选中flash芯片

编程实现:

1.选中flash芯片

select_ship();

2.清除RnB

clean_RnB();

3.发送命令0x80

nand_cmd(0x80);

4.发送列地址(2个周期)

nand_addr(0x00);
nand_addr(0x00);

5.发送行地址(3个周期)

    nand_addr(addr&0xff);
    nand_addr((addr >>8 ) & (0xff));
    nand_addr((addr >>16 ) & (0xff));

6.写入数据

for(i=0;i<1024*4;i++)

{

NFDATA = buff[i];

}

7.发送命令10

nand_cmd(0x10);

8.等待RnB

    wait_RnB();

9.发送命令70

nand_cmd(0x70);

10.读取写入结果

ret = NFDATA;

11.取消选中flash芯片

diselect_ship();

2).擦除工作

在写入数据之前,需要对其进行擦除工作,擦除的时候会把这个页所在的块全部擦除。

1.选中flash芯片

    select_ship();

2.清除RnB

    clean_RnB();

3.发送命令60

    nand_cmd(0x60);

4.发送行地址(3个周期)

    nand_addr(addr&0xff);
    nand_addr((addr >>8 ) & (0xff));
    nand_addr((addr >>16 ) & (0xff));

5.发送命令D0

    nand_cmd(0xD0);

6.等待RnB

    wait_RnB();

7.发送命令70

nand_cmd(0x70);

8.读取擦除结果

    ret = NFDATA;

9.取消选中flash芯片

    diselect_ship();

3)测试

测试时采用LED亮灭的方式进行验证。

    NF_Erase(128*1+1); //第128+1页
    buf[0] = 100;
    NF_WritePage(128*1+1,buf);

    buf[0] = 10;
    NF_PageRead(128*1+1,buf);

    if( buf[0] == 100 )
        led_off();

贴上代码,仅供参考:

nand.c

/********************************************
*file name: nand.c
*author   : stone
*date     : 2016.7.3
*function : nandflash相关操作
*********************************************/
#define NFCONT             (*((volatile unsigned long*)0x70200004))  //控制寄存器
#define NFSTAT             (*((volatile unsigned char*)0x70200028))  //NAND 状态寄存器
#define NFCMMD             (*((volatile unsigned char*)0x70200008))  //命令寄存器
#define NFADDR             (*((volatile unsigned char*)0x7020000c))  //地址寄存器
#define NFDATA             (*((volatile unsigned char*)0x70200010))  //数据寄存器
#define NFCONF             (*((volatile unsigned long*)0x70200000))  //配置寄存器

    /* 设置时间参数 */
#define TACLS  1
#define TWRPH0 2
#define TWRPH1 1

void select_ship(void)
{
    NFCONT &= ~(1<<1);
}

void diselect_ship(void)
{
    NFCONT |= (1<<1);
}

void clean_RnB()
{
    NFSTAT |= (1<<4);
}

void nand_cmd(unsigned char cmd)
{
    NFCMMD = cmd;
}

void nand_addr(unsigned char addr)
{
    NFADDR = addr;
}

void wait_RnB(void)
{
    while(!(NFSTAT & (0x1<4)));
}

void nand_reset(void)
{
    /* 选中 */
    select_ship();

    /* 清除RnB */
    clean_RnB();

    /* 发出复位信号 */
    nand_cmd(0xff);

    /* 等待就绪 */
    wait_RnB();

    /* 取消选中 */
    diselect_ship();
}

void nand_init(void)
{
 //初始化NFCONF

/*
HCLK的频率为100MHZ,周期就为10ns
TACLS > 0 ns
TWRPH0  > 15ns
TWRPH1 > 5ns

TACLS的值 = HCLK x TACLS > 0ns
TWRPH0的值 = HCLK x (TWRPH0 + 1) > 15ns
TWRPH1的值 = HCLK x (TWRPH1 +1) > 5ns
*/

    //NFCONF &= ~((7<<12)|(7<<8)|(7<<4));
    NFCONF |= (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);

 //初始化NFCONT
    NFCONT = 1 | (1<<1); 

 //复位
    nand_reset();
}

void NF_PageRead(unsigned long addr,unsigned char* buff)
{
    int i;

    //选中nandflash芯片
    select_ship();

    //清除RNB信号
    clean_RnB();

    //发送命令0x00
    nand_cmd(0x00);

    //发送列地址
    nand_addr(0x00);
    nand_addr(0x00);    

    //发送行地址
    nand_addr(addr&0xff);
        nand_addr((addr >>8 ) & (0xff));
    nand_addr((addr >>16 ) & (0xff));   

    //发送命令0x30
     nand_cmd(0x30);    

    //等待R/B信号
        wait_RnB(); 

    //读取数据
     for(i = 0; i<1024*4; i++)  //4KB
     {
       buff[i] = NFDATA;
     }  

    //取消选中nandflash芯片
    diselect_ship();

}

void nand_to_ram(unsigned long start_addr,unsigned char* sdram_addr,int size)
{
    /* i为页号、sdram_addr为内存中的位置、size拷贝数据的大小 */
    int i;

/*
   S3C6410启动时拷贝的8K代码不是存储在Nand flash的第一页上,
   而是存储在Nand flash的前4页上,每页2K,总共8K,

*/
    for (i = 0; i < 4; i++, sdram_addr+=2048)
    {
        NF_PageRead(i,sdram_addr);
    }

    size -= 1024*8;

    for( i=4; size>0;)
    {
        NF_PageRead(i,sdram_addr);
        size -= 4096;  //每拷贝一次就减少4KB
        sdram_addr += 4096;
        i++;
    }      

}

//在写之前必须要擦除
int NF_Erase(unsigned long addr)
{
    int ret;

    //选中flash芯片
    select_ship();

    //清除RnB
    clean_RnB();

    //发送命令60
    nand_cmd(0x60);

    //发送行地址(3个周期)
    nand_addr(addr&0xff);
        nand_addr((addr >>8 ) & (0xff));
    nand_addr((addr >>16 ) & (0xff));

    //发送命令D0
    nand_cmd(0xD0);

    //等待RnB
    wait_RnB();

    //发送命令70
    nand_cmd(0x70);

    //读取擦除结果
    ret = NFDATA;

    //取消选中flash芯片
    diselect_ship();

    return ret;
}

int NF_WritePage(unsigned long addr,unsigned char* buff)
{
    int ret,i;

    //选中flash芯片
    select_ship();

    //清除RnB
    clean_RnB();

    //发送命令80
    nand_cmd(0x80);

    //发送列地址(2个周期)
    nand_addr(0x00);
        nand_addr(0x00);

    //发送行地址(3个周期)
    nand_addr(addr&0xff);
        nand_addr((addr >>8 ) & (0xff));
        nand_addr((addr >>16 ) & (0xff));

    //写入数据
    for(i=0;i<1024*4;i++)
    {
        NFDATA = buff[i];
    }

    //发送命令10
    nand_cmd(0x10);

    //等待RnB
    wait_RnB();

    //发送命令70
    nand_cmd(0x70);

    //读取写入结果
    ret = NFDATA;

    //取消选中flash芯片
    diselect_ship();

    return ret;
}

main.c

/********************************************
*file name: main.c
*author   : stone
*date     : 2016.7.3
*function : 总程序
*********************************************/

int gboot_main()
{

    /*mmu 初始化,暂时不用*/
    #ifdef MMU_ON
      mmu_init();
    #endif

    /*led初始化*/
        led_init();

    /*中断初始化*/
    init_irq();

    /*按键初始化*/
    button_init();

    led_on();

    NF_Erase(128*1+1); //第128+1页
    buf[0] = 100;
    NF_WritePage(128*1+1,buf);

    buf[0] = 10;
    NF_PageRead(128*1+1,buf);

    if( buf[0] == 100 )
        led_off();

    while(1);

    return 0;
}

菜鸟一枚,如有错误,多多指教。。。

时间: 2024-11-08 20:03:02

嵌入式学习-uboot-lesson12-NandFlash相关的相关文章

嵌入式学习汇总

[转载]嵌入式学习网学习资料下载 (2010-07-23 15:50:15) 转载▼ 标签: 转载   网络资源 原文地址:嵌入式学习网学习资料下载作者:潇楚一生 刚才发现一个论坛上贴的一个帖子,有很多学习资料可以下载,特转发. 学习经验 1.嵌入式Linux操作系统学习规划 http://www.embedstudy.com/?action-viewnews-itemid-342 2.学习ARM嵌入式Linux开发的学习过程 http://www.embedstudy.com/?action-

以软件推动工业进步 -嵌入式学习网站

http://www.cnblogs.com/cubean/archive/2010/04/26/1721035.html 以下内容转自:http://bbs.msembed.com/showtopic-1238.aspx 嵌入式入门篇:什么是嵌入式系统                        http://www.helloarm.com/Embedded-Learn/58.htm嵌 入式资深工程师白话说“嵌入式”        http://www.helloarm.com/Embed

嵌入式学习笔记103-uboot_1.1.6移植(3)

经过之前对uboot的整体flow分析,现在开始针对2440移植,需要注意的是移植的code可能包含支持部分的2410code 不过并没有在s3c2410板子实测过. 主要概括:第一阶段的汇编code尽量短小,能用C实现的就用C,由于2440的board和头文件是从2410 copy过来的 里面会有很多信息或者宏关于2410,并且很多.c文件的头文件由于include的是2410,所以新增的一些关于2440的结构体也会一并放在2410.h,移植的思想与前文类似, 根据code的执行流程来移植.

嵌入式学习笔记101-uboot_1.1.6移植(1)

根据前篇博文(嵌入式学习笔记100-uboot1.1.6初体验)最后的结论,现在开始将其实现: a. 修改makefile的CROSS_COMPILE指定编译器 arm-linux-gcc -v –> gcc version 3.4.5 CROSS_COMPILE = /opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/bin/arm-linux- chmod -R 777 u-boot-1.1.6/

嵌入式学习(转)

嵌入式系统无疑是当前最热门最有发展前途的IT应用领域之一.嵌入式系统用在一些特定专用设备上,通常这些设备的硬件资源(如处理器.存储器等)非常有限,并且对成本很敏感,有时对实时响应要求很高等.特别是随着消费家电的智能化,嵌入式更显重要.像我们平常常见到的手机.PDA.电子字典.可视电话.VCD/DVD/MP3 Player.数字相机(DC).数字摄像机(DV).U-Disk.机顶盒(Set Top Box).高清电视(HDTV).游戏机.智能玩具.交换机.路由器.数控设备或仪表.汽车电子.家电控制

嵌入式学习笔记201-Linux kernel动起来

在前篇博文<嵌入式学习笔记200-Linux kernel初体验>在已经确保环境编译是ok的,接下来让kernel能够最基本的动起来,起码可以看到基本的启动打印! 修改外部输入时钟频率, 修改 linux-2.6.30.4\arch\arm\mach-s3c2440\mach-smdk2440.c 在大概163行将16934400改成12000000. static void __init smdk2440_map_io(void) { s3c24xx_init_io(smdk2440_iod

嵌入式学习笔记104-uboot_1.1.6移植(4)

前面的4篇uboot博文基本概括了uboot的整体flow,现在使能支持启动linux,至此之前请先阅读<嵌入式学习笔记200-Linux kernel初体验>和<嵌入式学习笔记201-Linux kernel动起来>.准备kernel的镜像文件才可以立马检测uboot是否能够启动kernel.在u-boot-1.1.6\include\configs\tq2440.h 最后添加如下几行code: /****************** boot kernel setup ****

嵌入式学习笔记202-Linux kernel跑起来

在<嵌入式学习笔记104-uboot_1.1.6移植(4)>.<嵌入式学习笔记300-linux根文件系统搭建>uboot和根文件系统都准备好的前提下,此时的kernel已基本可以跑起来,不过还差正确的挂起文件系统,其中uboot传给kernel的挂载处是root=/dev/mtdblock2 ,而在<嵌入式学习笔记201-Linux kernel动起来>的打印分区信息是: mtd: partition "Boot Agent" doesn't en

嵌入式学习步骤

对于不太了解嵌入式的人来说,很关心的一个问题就是:嵌入式学习步骤.有一个正确的嵌入式学习步骤,可以使我们事半功倍的学习. 个人总结的嵌入式学习步骤: 嵌入式学习步骤分为三个阶段 1.:基础与理论阶段 主要包括一些理论知识,你至少了解这行业吧,基本的Linux系统使用;其次就是嵌入式核心开发语言C语言(必须精通);了解c语言数据结构及经典算法编程;最后就是要了解嵌入式产品的一个基本的开发流程,这对后续的开发有很大的帮助,不至于是那么的迷茫. 2.:嵌入式系统核心开发 整理了下,至少这些是你要学会的