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位。
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处理,写平衡等。