GD32450i-EVAL学习笔记 12 - EXMC&NandFlash

21 篇文章 36 订阅

NandFlash也是连接到EXMC上,EXMC中对应NandFlash的空间地址:

对于NAND FLASH,通用和属性空间又可以细划分为3个区域

 指令区:指定NAND FLASH将要执行的指令,软件在命令区写入指令。在指令传输过程中,
EXMC会使能命令锁存信号(CLE),CLE映射到EXMC_A[16]。
地址区:指定操作NAND FLASH的地址,软件在地址区写入地址。在地址传输过程中,EXMC
会使能地址锁存信号(ALE),ALE映射到EXMC_A[17]。
数据区:NAND FLASH读写数据,软件在数据区读出或写入数据。当EXMC在数据发送模式,
软件需要在数据区写入数据,当EXMC在数据接收模式,软件需要在数据区读取数据。由于
NAND FLASH会自动累加其内部操作地址,故在读写时不需要软件修改操作地址。

#define EXMC_NAND_COMMON_DAT(x)             (uint8_t *)(0x70000000 + 0x10000000 * x)    //64KB
#define EXMC_NAND_COMMON_CMD(x)             (uint8_t *)(0x70010000 + 0x10000000 * x)    //64KB
#define EXMC_NAND_COMMON_ADDR(x)            (uint8_t *)(0x70020000 + 0x10000000 * x)    //128KB

1 硬件接法

开发板上的NandFlash信号是GD9FU1G8F2AMG

用到的IO口如下:

EXMC_D0-D7接Nand Flash的地址和数据总线 

EXMC_A16-A17接CL和AL(SDRAM用了A0-A15),也正好对应了内部指令区域和地址区域的地址。

/* A16 = CLE high command area */
#define EXMC_CMD_AREA              (uint32_t)(1<<16)
/* A17 = ALE high address area */
#define EXMC_ADDR_AREA             (uint32_t)(1<<17)
/* data area */
#define EXMC_DATA_AREA             ((uint32_t)0x00000000)

而选择EXMC_NCE1意味着EXMC用的是Bank1.

注意:NandFlash对用的EXMC的IO口都没有复用。EXMC_NCE1对应Bank1,而EXMC_NCE2(GPIOG9)对应Bank2。

2 IO初始化 

和SDRAM一样,所有的IO都配置为AF12

//GPIOD: Pin0-1, Pin4-7, Pin11-12, Pin14-15
//GPIOE: Pin7, Pin8-10

3 EXMC初始化

3.1 RCU时钟使能

RCU_AHB3EN |= 0x01;

AHB的最大时钟为200MHz。

3.2 控制器寄存器(EXMC_NPCTLx) (x=1, 2)

控制器寄存器有3个,不过NandFlash对应的是1和2。

3.2.1 NDTP(外部存储器的类型)

3.2.2 NDWTEN(NWAIT信号使能位)

使能这个位后,EXMC会通过EXMC_NWAIT脚等待NandFlash Ready。一般是使能的。

3.2.3 NDW[1:0](外部存储器数据宽度)

3.2.4 ECCEN(ECC使能)

3.2.5 CTR[3:0](CLE至RE的延迟)

对应NandFlash Datasheet中的Tclr

最小10ns,即1个HCLK。

3.2.6 ATR[3:0](ALE至RE的延迟)

 最小10ns,即1个HCLK。

3.2.7 ECCSZ[2:0](ECC块大小)

对应Nand Flash Datasheet的Page大小

所以这里要取值0b011。

3.3 通用空间时序寄存器 (NPCTCFGx) (x=1, 2)

和控制器寄存器一样,NandFlash对应x=1,2。通用空间的时序应该是对所有指令的时序都有效。

3.3.1 COMSET[7:0](建立时间)

EXMC写入命令和地址后,Nand Flash需要一定的时间才能反馈命令和地址的内容。

3.3.2 COMWAIT[7:0](等待时间)

等待时间应该表示的是EXMC等待NandFlash的时间,似乎可以理解为当EXMC打算从NandFlash中读数据,然后等待NandFlash将数据准备好的时间,所以这里会有写加上NWAIT时钟周期。而下面的保持时间应该是EXMC输出时参考的时序。

3.3.3 COMHLD[7:0](保持时间)

3.3.4 COMHIZ[7:0](高阻时间)

3.4 属性空间时序寄存器 (EXMC_NPATCFGx) (x=1, 2)

各个位的含义与通用空间时序寄存器相同,针对的是写入属性空间的命令与数据。

4. 读Nand Flash的ID

命令为0x90,地址为0x00,返回5个字节的ID,具体意义:

而GD9FU1G8F2AMG的ID为:

 读取ID的代码如下:

void nandReadID(uint8_t port, nandID_t *pstNandID)
{
    if(port >= HW_NAND_MAX)
        return;
    
    *EXMC_NAND_COMMON_CMD(port) = NAND_CMD_READID;
    *EXMC_NAND_COMMON_ADDR(port) = 0x00;
    pstNandID->maker = *EXMC_NAND_COMMON_DAT(port);
    pstNandID->device = ((uint32_t)(*((uint8_t *)(EXMC_NAND_COMMON_DAT(port) + 1))) << 24);
    pstNandID->device |= ((uint32_t)(*((uint8_t *)(EXMC_NAND_COMMON_DAT(port) + 2))) << 16);
    pstNandID->device |= ((uint32_t)(*((uint8_t *)(EXMC_NAND_COMMON_DAT(port) + 3))) << 8);
    pstNandID->device |= ((uint32_t)(*((uint8_t *)(EXMC_NAND_COMMON_DAT(port) + 4))) << 0);
}

打印结果:

NandFlash Maker ID:c8
NandFlash Device ID:f1801d42

5 等待Nand空闲

通过命令Read Status(0x70)读回NandFlash的空闲状态,然后判断I/O 6是否为空闲,当I/O 6为1时表示Nand Flash空闲。

 

void nandWaitFree(uint8_t port)
{
    uint8_t value;
    uint16_t timeOut = 30000;
    do
    {
        *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_STATUS;
        value = *(uint8_t *)EXMC_NAND_COMMON_DAT(port);
        if((value & (1 << 6)) > 0)
            break;
    }while(--timeOut > 0);
}

6 块擦除

Nand Flash的擦除单元为Block,一个Block大小为n个Page,GD9FU1G8F2AMG的Datasheet上有对应的说明

即GD9FU1G8F2AMG的Page大小为2048,而Block大小为128K,64个Page。

写入ADDR的地址需要左移6位,可以参考下面的图片,对于Block来说,不需要Column Address, 只需要Block + Page Address地址(即下图中4字节地址只需要后面2个字节地址),Block指定哪个Block,Page Address选择Block内的哪个Page,所以需要左移6位。

[NAND] <wbr>NAND <wbr>Flash的基本操作——读、写、擦除

uint8_t nandBlockErase(uint8_t port, uint16_t blocknum)
{
    uint8_t status;
    blocknum <<= stNandflash.blockAddrBits;
    EXMCNAND_INFO(Printf("Erase Block:%x\n", blocknum));
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_ERASE_1ST;
    *(uint8_t *)EXMC_NAND_COMMON_ADDR(port) = (uint8_t)(blocknum & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 1) = (uint8_t)((blocknum >> 8)& 0xFF);
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_ERASE_2ND;
    nandWaitFree(port);
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_STATUS;
    status = (*(uint8_t *)EXMC_NAND_COMMON_DAT(port));
    EXMCNAND_INFO(Printf("Erase Block Status:%x\n", status));
    return (*(uint8_t *)EXMC_NAND_COMMON_DAT(port)) & 0x01; //0: Pass, 1: Fail
}

 注意这里写地址时第二个字节写入位置和官方例程 不同,原因是用MDK编译的没问题,但是如果用GCC编译的发现有问题,但是如果在2个写地址中间加打印又能恢复正常,估计GCC编译的速度更快导致时序有问题。

7 页编程

NandFlash也是以页为单位写入,并且要求必须先擦除为0xFF。不过页编程是可以允许页内任意位置和大小的编程。

uint8_t nandPageProgram(uint8_t port, uint32_t colAddr, uint32_t rowAddr, uint8_t* buf, uint16_t len)
{
    uint16_t i;
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_WRITE_1ST;
    *(uint8_t *)EXMC_NAND_COMMON_ADDR(port) = (uint8_t)(colAddr & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 1) = (uint8_t)((colAddr >> 8) & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 2) = (uint8_t)((rowAddr >> 0) & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 3) = (uint8_t)((rowAddr >> 8) & 0xFF);
    if(stNandflash.rowAddrsLen > 2)
        *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 4) = (uint8_t)((rowAddr >> 16)& 0xFF);
    /* write data to data area */
    for(i = 0; i < len; i++)
    {
        (*(uint8_t *)EXMC_NAND_COMMON_DAT(port)) = buf[i];
    }
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_WRITE_2ND;
    nandWaitFree(port);
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_STATUS;
    return (*(uint8_t *)EXMC_NAND_COMMON_DAT(port)) & 0x01;
}

 注意,实际上Nand Flash的页的大小一般是分2部分的,Valid Area + Spare Area

GD9FU1G8F2AMG的页是2048+128字节,所以页内地址需要12个位,参数flashAddr的含义如下:

    /* send address to the address area, for GD9FU1G8F2AMG
                    bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
       first byte:  A7   A6   A5   A4   A3   A2   A1   A0    (bit7 - bit0 of page address)
       second byte: 0    0    0    0    A11  A10  A9   A8    (bit11 - bit8 of page address, high 4bit must be zero)
       third byte:  A19  A18  A17  A16  A15  A14  A13  A12
       fourth byte: A27  A26  A25  A24  A23  A22  A21  A20
    */

 对应Datasheet中的地址说明:

8 页读

页读和页编程类似

uint8_t nandPageRead(uint8_t port, uint32_t colAddr, uint32_t rowAddr, uint8_t* buf, uint16_t len)
{
    uint16_t i;
    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_READ1_1ST;
    *(uint8_t *)EXMC_NAND_COMMON_ADDR(port) = (uint8_t)(colAddr & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 1) = (uint8_t)((colAddr >> 8) & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 2) = (uint8_t)((rowAddr >> 0) & 0xFF);
    *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 3) = (uint8_t)((rowAddr >> 8) & 0xFF);
    if(stNandflash.rowAddrsLen > 2)
        *(uint8_t *)(EXMC_NAND_COMMON_ADDR(port) + 4) = (uint8_t)((rowAddr >> 16)& 0xFF);

    *(uint8_t *)EXMC_NAND_COMMON_CMD(port) = NAND_CMD_READ1_2ND;
    Printf("%x\n", NAND_CMD_READ1_2ND);
        /* write data to data area */
    for(i = 0; i < len; i++)
    {
        buf[i] = *((uint8_t *)EXMC_NAND_COMMON_DAT(port));
    }
    nandWaitFree(port);
    return 0;
}

Nand Flash的实际应用远没有这么简单,这涉及到Spare空间的操作,ECC处理,写平衡等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值