ARM学习笔记--NAND Flash控制器

本文介绍了NAND Flash的基本原理和组织结构,详细讲解了如何利用S3C2440控制器进行NAND Flash的初始化配置及读操作。通过具体实例展示了从NAND Flash读取数据并复制到SDRAM的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘自:《嵌入式Linux应用开发完全手册》——韦东山

一、NAND Flash介绍和NAND Flash控制器的使用

NAND Flash在嵌入式系统中的作用,相当于PC上的硬盘

常见的Flash有NOR Flash和NAND Flash,NOR Flash上进行读取的效率非常高,但是擦除和写操作的效率很低,容量一般比较小;NAND Flash进行擦除和写操作的效率更高,并且容量更大。一般NOR Flash用于存储程序,NAND Flash用于存储数据。

1)NAND Flash的物理结构

笔者用的开发板上NAND Flash型号是K9F1G08,大小为128M,下图为它的封装和外部引脚

* NandFlash接口信号较少

* 数据宽度只有8Bit,没有地址总线。地址和数据总线复用,串行读取


信号名称

信号描述

IO[7..0]

数据总线

CE#

片选信号(Chip Select),低电平有效

WE#

写有效(Write Enable),低电平表示当前总线操作是写操作

RE#

读有效(Read Enable),低电平表示当前总线操作是读操作

CLE

命令锁存(Command Latch Enable)信号,写操作时给出此信号表示写命令

ALE

地址/数据锁存(Address Latch Enable)信号,写操作时给出此信号表示写地址或数据

WP#

写保护(Write Protect)信号

R/B

(Read/Busy)信号

 

2)K9F1G08功能结构图如下

 

K9F1G08内部结构有下面一些功能部件

①X-Buffers Latches & Decoders:用于行地址

②X-Buffers Latches & Decoders:用于列地址

③Command Register:用于命令字

④Control Logic & High Voltage Generator:控制逻辑及产生Flash所需高压

⑤Nand Flash Array:存储部件

⑥Data Register & S/A:数据寄存器,读、写页时,数据存放此寄存器

⑦Y-Gating

⑧I/O Buffers & Latches

⑨Global Buffers

⑩Output Driver

3)NAND Flash 存储单元组织结构图如下:

 

K9F1G08容量为1056Mbit,分为65536行(页)、2112列,每一页大小为2kb,外加64字节的额外空间,这64字节的额外空间的列地址为2048-2111

命令、地址、数据都通过IO0-IO7输入/输出,写入命令、地址或数据时,需要将WE、CE信号同时拉低,数据在WE信号的上升沿被NAND FLash锁存;命令锁存信号CLE、地址锁存信号ALE用来分辨、锁存命令或地址。

K9F1G08有128MB的存储空间,需要27位地址,以字节为单位访问Flash时,需要4个地址序列

NandFlash地址结构

* NandFlash设备的存储容量是以页(Page)和块(Block)为单位的。

* Page=528Byte (512Byte用于存放数据,其余16Byte用于存放其他信息,如块好坏的标记、块的逻辑地址、页内数据的ECC校验和等)

* Block=32Page

* 容量为64MBNandFlash存储结构为:512Byte×32Page×4096Block

* NandFlash以页为单位进行读和编程(写)操作,一页为512Byte;以块为单位进行擦除操作,一块为512Byte*32page=16KB

* 对于64MBNAND设备,需要26根地址线,由于NAND设备数据总线宽度是8位的,因此必须经过4个时钟周期才能把全部地址信息接收下来


 

I/O7

I/O6

I/O5

I/O4

I/O3

I/O2

I/O1

I/O0

第一个周期

A7

A6

A5

A4

A3

A2

A1

A0

第二个周期

A15

A14

A13

A12

A11

A10

A9

A8

第三个周期

A23

A22

A21

A20

A19

A18

A17

A16

第四个周期

 

 

 

 

 

 

A25

A24

 

* 可以这么说,第一个时钟周期给出的是目标地址在一个page内的偏移量,而后三个时钟周期给出的是页地址

* 由于一个页内有512Byte,需要9bit的地址寻址,而第一个时钟周期只给出了低8bit,最高位A8由不同的读命令(Read Mode2)来区分的。

4). NandFlash的命令 

[ARM笔记]存储器扩展连接理解(S3C2410为例)—NandFlash - Fantity Wei - Footprint

NAND Flash访问方法

NAND Flash硬件连接如下图:

 

NAND Flash和S3C2440的连线包括,8个IO引脚,5个使能信号(nWE、ALE、CLE、nCE、nRE)、1个状态引脚(R/B)、1个写保护引脚(nWP)。地址、数据和命令都是在这些使能信号的配合下,通过8个IO引脚传输。写地址、数据、命令时,nCE、nWE信号必须为低电平,它们在 nWE信号的上升沿被锁存。命令锁存使能信号CLE和地址锁存使能信号ALE用来区别IO引脚上传输的是命令还是地址。

 

命令字及操作方法

        操作NAND Flash时,先传输命令,然后传输地址,最后读写数据,这个期间要检查Flash的状态。K9F1G08容量为128MB,需要一个27位的地址,发出命令后,后面要紧跟着4个地址序列。

下图为K9F1G08的命令字

下图为K9F1G08的地址序列

K9F1G08有2112列,所以必须使用A0-A11共12位来寻址,有65535行,所以必须使用A12-A27共16位来寻址。

 

3)S3C2440 NAND Flash控制器介绍

NAND Flash的读写操作次序如下:

①设置NFCONF配置NAND Flash

②向NFCMD寄存器写入命令

③向NFADDR寄存器写入地址

④读写数据:通过寄存器NFSTAT检测NAND Flash的状态,在启动某个操作后,应该检测R/nB信号以确定该操作是否完成、是否成功。

 

下面介绍这些寄存器:

①NFCONF:配置寄存器

        用来设置NAND Flash的时序参数,设置数据位宽,设置是否支持其他大小的页等。

②NFCONT:控制寄存器

        用来使能NAND Flash控制器、使能控制引脚信号nFCE、初始化ECC,锁定NAND Flash等功能

③NFCMD:命令寄存器

        用来发送Flash操作命令

④NFADDR:地址寄存器

        用来向Flash发送地址信号

⑤NFDATA:数据寄存器

        读写此寄存器启动对NAND Flash的读写数据操作

⑥NFSTAT:状态寄存器

        0:busy,1:ready

 

二、NAND Flash控制器操作实例:读Flash

1)读NAND Flash的步骤

①设置NFCONF

        在HCLK=100Mhz的情况下,TACLS=0,TWRPH0=3,TWRPH1=0,则

        NFCONF = 0x300

        使能NAND Flash控制器、禁止控制引脚信号nFCE,初始化ECC

        NFCONT = (1<<4) | (1<<1) | (1<<0)

②操作NAND Flash前,复位

        NFCONT &= ~(1<<1)        发出片选信号

        NFCMD = 0xff        reset命令

        然后循环查询NFSTAT位0,直到等于1,处于就绪态

        最后禁止片选信号,在实际使用时再使能

        NFCONT |= 0x2        禁止NAND Flash

③发出读命令

        NFCONT &= ~(1<<1)        发出片选信号

        NFCMD = 0        读命令

④发出地址信号

⑤循环查询NFSTAT,直到等于1

⑥连续读NFDATA寄存器,得到一页数据

⑦最后禁止NAND Flash片选信号

        NFCONT |= (1<<1)

 

 2)代码详解

本实例的目的是把一部分代码存放在NAND Flash地址4096之后,当程序启动后通过NAND Flash控制器读出代码,执行。

连接脚本 nand.lds

 

SECTIONS { 

  firtst   0x00000000 : { head.o init.o nand.o}

  second  0x30000000 : AT(4096) { main.o }

head.o init.o nand.o三个文件运行地址为0,生成的镜像文件偏移地址也为0

main.0的运行地址为0x30000000,生成的镜像文件偏移地址为4096

 

 

@******************************************************************************

@ File:head.s

@ 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行

@******************************************************************************       

  

.text

.global _start

_start:

                                            @函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义

            ldr     sp, =4096               @设置堆栈 

            bl      disable_watch_dog       @关WATCH DOG

            bl      memsetup                @初始化SDRAM

            bl      nand_init               @初始化NAND Flash


                                            @将NAND Flash中地址4096开始的1024字节代码(main.c编译得到)复制到SDRAM中

                                            @nand_read_ll函数需要3个参数:

            ldr     r0,     =0x30000000     @1. 目标地址=0x30000000,这是SDRAM的起始地址

            mov     r1,     #4096           @2.  源地址   = 4096,连接的时候,main.c中的代码都存在NAND Flash地址4096开始处

            mov     r2,     #2048           @3.  复制长度= 2048(bytes),对于本实验的main.c,这是足够了

            bl      nand_read               @调用C函数nand_read


            ldr     sp, =0x34000000         @设置栈

            ldr     lr, =halt_loop          @设置返回地址

            ldr     pc, =main               @b指令和bl指令只能前后跳转32M的范围,所以这里使用向pc赋值的方法进行跳转

halt_loop:

            b       halt_loop

 

init.c 用于初始化操作
/* WOTCH DOG register */
#define  WTCON (*(volatile unsigned long *)0x53000000)
 
/* SDRAM regisers */
#define  MEM_CTL_BASE 0x48000000
 
void disable_watch_dog();
void memsetup();
 
/*上电后,WATCH DOG默认是开着的,要把它关掉 */
void disable_watch_dog()
{
WTCON = 0;
}
 
/* 设置控制SDRAM的13个寄存器 */
void memsetup()
{
int  i = 0;
unsigned long *p = (unsigned long *)MEM_CTL_BASE;
 
    /* SDRAM 13个寄存器的值 */
    unsigned long  const    mem_cfg_val[]={ 0x22011110,     //BWSCON
                                            0x00000700,     //BANKCON0
                                            0x00000700,     //BANKCON1
                                            0x00000700,     //BANKCON2
                                            0x00000700,     //BANKCON3  
                                            0x00000700,     //BANKCON4
                                            0x00000700,     //BANKCON5
                                            0x00018005,     //BANKCON6
                                            0x00018005,     //BANKCON7
                                            0x008C07A3,     //REFRESH
                                            0x000000B1,     //BANKSIZE
                                            0x00000030,     //MRSRB6
                                            0x00000030,     //MRSRB7
                                    };
 
for(; i < 13; i++)
p[i] = mem_cfg_val[i];
}
 
nand.c 用于操作nand flash
 
#define BUSY            1
 
#define NAND_SECTOR_SIZE_LP    2048        //K9F1G08使用2048+64列
#define NAND_BLOCK_MASK_LP     (NAND_SECTOR_SIZE_LP - 1)
 
typedef unsigned int S3C24X0_REG32;
 
typedef struct {
    S3C24X0_REG32   NFCONF;
    S3C24X0_REG32   NFCONT;
    S3C24X0_REG32   NFCMD;
    S3C24X0_REG32   NFADDR;
    S3C24X0_REG32   NFDATA;
    S3C24X0_REG32   NFMECCD0;
    S3C24X0_REG32   NFMECCD1;
    S3C24X0_REG32   NFSECCD;
    S3C24X0_REG32   NFSTAT;
    S3C24X0_REG32   NFESTAT0;
    S3C24X0_REG32   NFESTAT1;
    S3C24X0_REG32   NFMECC0;
    S3C24X0_REG32   NFMECC1;
    S3C24X0_REG32   NFSECC;
    S3C24X0_REG32   NFSBLK;
    S3C24X0_REG32   NFEBLK;
} S3C2440_NAND;        //此结构体存储操作NAND Flash相关寄存器
 
 
typedef struct {
    void (*nand_reset)(void);
    void (*wait_idle)(void);
    void (*nand_select_chip)(void);
    void (*nand_deselect_chip)(void);
    void (*write_cmd)(int cmd);
    void (*write_addr)(unsigned int addr);
    unsigned char (*read_data)(void);
}t_nand_chip;        //存储nand相关操作的函数地址
 
static S3C2440_NAND * s3c2440nand = (S3C2440_NAND *)0x4e000000;        //s2c2440nand控制器地址
 
static t_nand_chip nand_chip;
 
/* 供外部调用的函数 */
void nand_init(void);
void nand_read(unsigned char *buf, unsigned long start_addr, int size);
 
/* NAND Flash操作的总入口, 它们将调用S3C2440的相应函数 */
static void nand_reset(void);
static void wait_idle(void);
static void nand_select_chip(void);
static void nand_deselect_chip(void);
static void write_cmd(int cmd);
static void write_addr(unsigned int addr);
static unsigned char read_data(void);
 
/* S3C2440的NAND Flash处理函数 */
static void s3c2440_nand_reset(void);
static void s3c2440_wait_idle(void);
static void s3c2440_nand_select_chip(void);
static void s3c2440_nand_deselect_chip(void);
static void s3c2440_write_cmd(int cmd);
static void s3c2440_write_addr(unsigned int addr);
static unsigned char s3c2440_read_data(void);
 
/* S3C2440的NAND Flash操作函数 */
 
/* 复位 */
static void s3c2440_nand_reset(void)
{
    s3c2440_nand_select_chip();
    s3c2440_write_cmd(0xff);  // 复位命令
    s3c2440_wait_idle();
    s3c2440_nand_deselect_chip();
}
 
/* 等待NAND Flash就绪 */
static void s3c2440_wait_idle(void)
{
    int i;
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFSTAT;
    while(!(*p & BUSY))        //*p=1表示就绪,跳出循环
        for(i=0; i<10; i++);
}
 
/* 发出片选信号 */
static void s3c2440_nand_select_chip(void)
{
    int i;
    s3c2440nand->NFCONT &= ~(1<<1);
    for(i=0; i<10; i++);    
}
 
/* 取消片选信号 */
static void s3c2440_nand_deselect_chip(void)
{
    s3c2440nand->NFCONT |= (1<<1);
}
 
/* 发出命令 */
static void s3c2440_write_cmd(int cmd)
{
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFCMD;
    *p = cmd;
}
 
/* 发出地址 */
static void s3c2440_write_addr_lp(unsigned int addr)
{
int i;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFADDR;
int col, page;
 
col = addr & NAND_BLOCK_MASK_LP;        //取得列地址
page = addr / NAND_SECTOR_SIZE_LP;        //取得行地址
*p = col & 0xff; /* 列地址 A0~A7 */
for(i=0; i<10; i++);
*p = (col >> 8) & 0x0f; /* 列地址 A8~A11 */
for(i=0; i<10; i++);
*p = page & 0xff; /* 行地址 A12~A19 */
for(i=0; i<10; i++);
*p = (page >> 8) & 0xff; /* 行地址 A20~A27 */
for(i=0; i<10; i++);
*p = (page >> 16) & 0x03; /* 行地址 A28~A29 */
for(i=0; i<10; i++);
}
 
 
/* 读取数据 */
static unsigned char s3c2440_read_data(void)
{
    volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFDATA;
    return *p;
}
 
 
/* 在第一次使用NAND Flash前,复位一下NAND Flash */
static void nand_reset(void)
{
    nand_chip.nand_reset();
}
 
static void wait_idle(void)
{
    nand_chip.wait_idle();
}
 
static void nand_select_chip(void)
{
    int i;
    nand_chip.nand_select_chip();
    for(i=0; i<10; i++);
}
 
static void nand_deselect_chip(void)
{
    nand_chip.nand_deselect_chip();
}
 
static void write_cmd(int cmd)
{
    nand_chip.write_cmd(cmd);
}
static void write_addr(unsigned int addr)
{
    nand_chip.write_addr(addr);
}
 
static unsigned char read_data(void)
{
    return nand_chip.read_data();
}
 
 
/* 初始化NAND Flash */
void nand_init(void)
{
#define TACLS   0
#define TWRPH0  3
#define TWRPH1  0
        nand_chip.nand_reset         = s3c2440_nand_reset;
        nand_chip.wait_idle          = s3c2440_wait_idle;
        nand_chip.nand_select_chip   = s3c2440_nand_select_chip;
        nand_chip.nand_deselect_chip = s3c2440_nand_deselect_chip;
        nand_chip.write_cmd          = s3c2440_write_cmd;
        nand_chip.write_addr         = s3c2440_write_addr_lp;
        nand_chip.read_data          = s3c2440_read_data;
 
/* 设置时序 */
        s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
        /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
        s3c2440nand->NFCONT = (1<<4)|(1<<1)|(1<<0);
    
    /* 复位NAND Flash */
    nand_reset();
}
 
 
/* 读函数 用于把nand flash中代码复制到sdram中*/
void nand_read(unsigned char *buf, unsigned long start_addr, int size)
{
    int i, j;
 
    if ((start_addr & NAND_BLOCK_MASK_LP) || (size & NAND_BLOCK_MASK_LP)) {
        return ;    /* 地址或长度不对齐 */
    }
 
 
    /* 选中芯片 */
    nand_select_chip();
 
    for(i=start_addr; i < (start_addr + size);) {
      /* 发出READ命令 */
      write_cmd(0);
 
      /* 写地址 */
      write_addr(i);
      write_cmd(0x30);
      wait_idle();
 
 
      for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++) {
          *buf = read_data();
          buf++;
      }
    }
 
    /* 取消片选信号 */
    nand_deselect_chip();
    
    return ;
}
 
main.c 很简单,点灯
 
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)

#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))

void  wait(unsigned long dly)
{
for(; dly > 0; dly--);
}

int main(void)
{
unsigned long i = 0;
GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out; // 将LED1-4对应的GPB5/6/7/8四个引脚设为输出

GPBDAT = ~(1<<5) | ~(1<<7) | ~(1<<8);
while(1){
wait(30000);
GPBDAT = (~(i<<5)); // 根据i的值,点亮LED1-4
if(++i == 16)
i = 0;
}

return 0;
}
 
最后是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 $@
arm-linux-objdump -D -m arm  nand_elf > nand.dis

%.o:%.c
arm-linux-gcc -Wall -c -O2 -o $@ $<

%.o:%.S
arm-linux-gcc -Wall -c -O2 -o $@ $<

clean:
rm -f  nand.dis nand.bin nand_elf *.o

移动电话的功能日益丰富,其对系统中数据存储容量的需求正在快速增长。 NAND Flash具有速度快、密度大、成本低等特点,在各种数码产品中得到了广泛 应用,在各种片上系统芯片中(SOC)集成NAND Flash控制器正成为一种趋势。 本文讨论了Flash Memory的两种主流实现技术即NAND Flash和NOR Flash 的特点和区别,分析了市场上存在的NAND Flash的典型规格及其存储结构特点, 阐述了不同NAND Flash器件一些通用的存取操作方式,近一步分析了进行这些 存取操作所必须满足的时序规范,在此基础上,结合某公司手机SOC芯片的设计 需求,提出了一种基于AMBA总线的NAND Flash控制器实现方案,对该实现方 案进行了充分的验证工作。 本文所提出的控制器的实现方式,可以支持市场上存在的两种典型规格的 NAND Flash器件,可同时外接1至4个Flash芯片,通过可配置的控制方式可灵 活的对不同存取速度的器件予以支持,具备良好的可扩展性。在控制器的主控逻 辑设计中,采取了“块读’’和“块写”方式实现对大页器件的读页和写页操作, 这种方式有效减小了控制器中用做数据缓存的buffer大小,降低了芯片面积。针 对NAND Flash在使用过程中可能出现的位反转现象,在控制器的设计中加入了错 误检测和纠错功能。论文深入分析了ECC(Error Checking and Correcting)算法,讨 论了ECC算法的硬件实现和优化方法。在不影响对存储器读写效率的前提下,实 现对数据的存取进行实时的高速检错和纠错,为提高NAND Flash的可靠性提供了 硬件上的支持。 对控制器的验证采用了模拟验证和FPGA验证两种方式。在模拟验证阶段对 控制器的所有功能点进行全面验证,结果正确后,在Xilinx公司的Vertex4开发板 上对控制器进行了FPGA验证。结果表明控制器能正确控制对于NAND Flash的各 种存取操作,工作完全正常。 本文设计验证的NAND Flash控制器即将应用于某公司的SOC手机芯片,提 出的控制器实现方案对NAND Flash控制系统的设计优化具有普遍适用性,论文研 究的工程实用价值大。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值