1. 基本知识介绍
本文介绍了GD32F4开发板NAND FLASH的初始化配置过程,使用GD32450Z芯片对NAND FLASH(HY27US08281A)进行配置,实现读写操作。
1.1 NAND FLASH介绍
NAND FLASH 存储器具有容量较大,改写速度快等优点,适用于大量数据的存储,在业界得到了广泛应用,如: SD 卡、TF 卡、U盘等,一般都是采用 NAND FLASH 作为存储的。
1.1.1 NAND FLASH信号线
信号线 | 说明 |
CLE | 命令锁存使能,高电平有效,表示写入的是命令 |
ALE | 地址锁存使能,高电平有效,表示写入的是地址 |
CE# | 芯片使能,低电平有效,用于选中 NAND 芯片 |
RE# | 读使能,低电平有效,用于读取数据 |
WE# | 写使能,低电平有效,用于写入数据 |
WP# | 写保护,低电平有效 |
R/P | 就绪/忙,注意用于判断编程/擦除操作是否完成 |
I/00-7 | 地址/数据 输入/输出口 |
NAND FLASH 信号线如上,因为地址/数据是共用数据线的,所以必须有 CLE/ALE 信号,告诉 NAND FLASH,发送的数据是命令还是地址。
1.1.2 NAND FLASH储存结构
通过查阅HY27US08281A的数据数据手册,可知一块该芯片由1024个Blocks构成,每个Blocks由32个Page构成,每个Page有512+16字节(528字节)的存储容量。所以,HY27US08281A的总容量为:1024*32*528=17301504字节( 16MB)。具体 block、 page 等的个数根据 NAND FLASH 型号的不同,会有所区别,应查看对应 NAND FLASH 芯片的数据手册。
NAND FLASH 的最小擦除单位是 block,对应HY27US08281A来说是(16+0.5)K字节。NAND FLASH 的写操作具有只可以写 0,不能写 1 的特性,所以,在写数据的时候,必须先擦除 block(擦除后, block 数据全部为 1),才可以写入。
NandFlash是以页(Page)为最小单位进行读写的,以块(Block)为最小单位进行擦除的,也就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本Page的最后一个 Byte为止。
NAND FLASH 的地址分为三类:块地址( Block Address)、页地址( Page Address)和列地址( Column Address)。以HY27US08281A为例,通过查阅数据手册,这三个地址,通过3个周期发送,通过查看时序图第一个循环发送列地址( Column Address),第二三个循环发送页地址( Page Address)。(不同的芯片地址发送的方式不一样,具体查看数据手册)
1.1.3 HY27US08281A地址计算方式
NAND FLASH读写操作时需要我们传送行地址和列地址,以HY27US08281A为例,一共有1024个块,每个块32页,每个页是512+16 Byte。假设访问第 500个块中的第 16 页中的 400字节处的信息,具体的物理地址为:
物理地址 = 块大小×块号+页大小×页号+页内地址 = 16K × 500 + 512B × 16 + 400B = 0X007D2190。
由图所示,HY27US08281A寻址分为三个循环发送,分为1个列(Column),2个行(Row)周期。由此其发送的过程为:
0X007D2190 = 0111 1101 0010 0001 1001 0000
1st周期,A7~A0: 1001 0000 = 0x90
2nd周期,A9~A16: 0010 0001 = 0x21
3rd周期,A17~A23: 0111 1101 = 0x7D
第一个周期发送的为页内偏移地址,512B理论上需要9位数据来表示,HY27US08281A将地址信号A8的高低电平通过发送PAGE PROGRAM控制命令(介绍见1.1.4小节)0X00 和 0X01 来代表。第二第三个循环发送页地址,其中第三个循环的最高位必须置0。
1.1.4 控制命令
NAND FLASH 的驱动需要用到一系列命令,下表我们列出常用的一些命令,方便大家了解 NAND FLASH 的操作,具体的操作下文会说明。(这些指令对于大部分NAND是通用的,具体可以查看数据手册)
命令 (HEX) | 名称 | 说明 | |
1# | 2# | ||
0X90 | READID | 读取NAND 的ID和相关特性,可以此判断NAND的容量等信息 | |
0XEF | SET FEATURE | 设置 NAND 的相关参数,比如时序模式 | |
0XFF | RESET | 复位NAND | |
0X70 | READ STATUS | 读取 NAND 的状态,比如可以判断编程/擦除操作是否完成 | |
0X00/01 | 0X30 | READ PAGE | 该指令由 2 部分组成(分 2 次发),用于读取一个Page 里面的数据(不能跨页读) |
0X80 | 0X10 | WRITE PAGE | 该指令由 2 部分组成(分 2 次发),用于写入一个Page 的数据(不能跨页写) |
0X60 | 0XD0 | ERASE BLOCK | 该指令由 2 部分组成(分 2 次发),用于擦除一个Block |
0X00 | 0X35 | READ FOR INTERNAL DATA MOVE | 这两个指令(分四次发),组成 NAND 的内部数据移动操作,该操作可以实现拷贝一个 Page 到另外一个 Page(仅限同一 plane 内),且支持拷贝时写入数据,该操作可以极大的方便数据写入 |
0X80 | 0X10 | PROGRAM FOR INTERNAL DATA MOVE |
1.2 外部存储器控制器(EXMC)
GD EXMC可以把AMBA协议转换为专用的片外存储器通信协议, 包括SRAM, ROM, NOR Flash, NAND Flash, PC Card和SDRAM。EXMC模块划分为许多个子Bank(如下图所示),每个Bank支持特定的存储器类型,用户可以通过对Bank的寄存器配置来控制外部存储器。
EXMC将外部存储器分成多个Bank,每个Bank占256M字节,其中Bank1和Bank2用于连接NAND Flash,且每个Bank连接一个NAND。同时,每个bank可以分为通用存储空间和属性存储空间,通用存储空间又可以划分为数据区域,指令区域和地址区域。
接下来讲解一下NAND FLASH的初始化配置。
2. NAND FLASH初始化配置
(1)首先定义两个NAND初始化结构体:
exmc_nand_parameter_struct nand_init_struct;
exmc_nand_pccard_timing_parameter_struct nand_timing_init_struct;
(2)引脚初始化设置
具体的引脚配置需要查看原理图,相对应的进行修改。注意其中的 NWAIT 为输入引脚,其余的信号线、控制线设置为复用输出,AF_12。
/* -----------引脚初始化--------------*/
rcu_periph_clock_enable(RCU_EXMC);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE);
/* common GPIO configuration */
/* D2(PD0),D3(PD1),D0(PD14),D1(PD15) pin configuration */
gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_14 | GPIO_PIN_15);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_14 | GPIO_PIN_15);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_14 | GPIO_PIN_15);
/* D4(PE7),D5(PE8),D6(PE9),D7(PE10) pin configuration */
gpio_af_set(GPIOE, GPIO_AF_12, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);
/* CLE(PD11),ALE(PD12) pin configuration */
gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_11 | GPIO_PIN_12);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_11 | GPIO_PIN_12);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_11 | GPIO_PIN_12);
/* NOE(PD4),NWE(PD5) pin configuration */
gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_4 | GPIO_PIN_5 );
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_4 | GPIO_PIN_5);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4 | GPIO_PIN_5);
/* NWAIT(PD6) pin configuration */
gpio_mode_set(GPIOD, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN_6);
/* NCE1(PD7) pin configuration */
gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_7);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_7);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
(3)NAND 控制时序设置
EXMC可配置的时序参数如下表所示,用户可以根据需求和外部存储器的特性来进行相应的配置。
nand_timing_init_struct.setuptime = 10; //存储器建立时间
nand_timing_init_struct.waittime = 10; //存储器等待时间
nand_timing_init_struct.holdtime = 10; //存储器保持时间
nand_timing_init_struct.databus_hiztime = 10; //存储器数据总线高阻时间
(4)NAND 初始化参数设置
nand_init_struct.nand_bank = EXMC_BANK1_NAND; // 选择NAND bank1
nand_init_struct.ecc_size = EXMC_ECC_SIZE_512BYTES; // ECC页大小为512字节
nand_init_struct.atr_latency = EXMC_ALE_RE_DELAY_2_HCLK;
nand_init_struct.ctr_latency = EXMC_CLE_RE_DELAY_2_HCLK;
nand_init_struct.ecc_logic = DISABLE; //禁止ECC
nand_init_struct.databus_width = EXMC_NAND_DATABUS_WIDTH_8B; //8位数据宽度
nand_init_struct.wait_feature = DISABLE; //关闭等待特性
nand_init_struct.common_space_timing = &nand_timing_init_struct; //时序设置
nand_init_struct.attribute_space_timing = &nand_timing_init_struct; //时序设置
exmc_nand_init(&nand_init_struct);
(5)使能 NAND
/* enable EXMC NAND bank1 */
exmc_nand_enable(EXMC_BANK1_NAND);
以上便完成了NAND FLASH的初始化配置,接下来介绍NAND FLASH读写操作。
3. NAND FLASH读写操作
不同NAND FLASH寻址方式不一样,移植时要对照数据手册对寻址相关的代码进行修改。
3.1 读取芯片ID
这里先从读取芯片ID简单操作说明起,以便理解NAND的工作过程。下面定义了NAND FLASH的控制、地址、数据存储的地址。我们可以通过 NAND_CMD_AREA 发送操作指令、NAND_ADDR_AREA 发送地址数据、NAND_DATA_AREA 写入或读取数据。
#define BANK_NAND_ADDR ((uint32_t)0x70000000)
/* NAND area definition */
/* 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)0x0000000)
/* define operating nand flash macro */
#define NAND_CMD_AREA *(__IO uint8_t *)(BANK_NAND_ADDR | EXMC_CMD_AREA)
#define NAND_ADDR_AREA *(__IO uint8_t *)(BANK_NAND_ADDR | EXMC_ADDR_AREA)
#define NAND_DATA_AREA *(__IO uint8_t *)(BANK_NAND_ADDR | EXMC_DATA_AREA)
通过查询HY27US08281A的数据手册,向指令操作区写入读取芯片ID的控制命令为 0X90,在地址写入0X00,之后在两个循环中,将会输出芯片的ID。
读取芯片ID代码如下:
void nand_read_id(nand_id_struct *nand_id)
{
uint32_t data = 0;
/* send command to the command area */
NAND_CMD_AREA = NAND_CMD_READID;
/* send address to the address area */
NAND_ADDR_AREA = 0x00;
/* read id from NAND flash */
data = *(__IO uint32_t *)(BANK_NAND_ADDR | EXMC_DATA_AREA);
nand_id->maker_id = ADDR_1ST_CYCLE(data);
nand_id->device_id = ADDR_2ND_CYCLE(data);
}
3.2 写操作
NAND FLASH 的写操作时序图如下所示:首先向指令操作区写入控制命令0X80,之后的三个循环中发送写入地址,之后传输需要写入的数据,最后入控制命令0X10结束写操作。
NAND FLASH(HY27US08281A)写入数据代码如下,其中PageNum为页地址; ColNum为列地址,数值为0~511(地址的计算方式详见1.1.3小节);*pBuffer 为要写入的数据;NumByteToWrite 为要写入数据的个数。
unsigned char Nand_WritePage(unsigned long PageNum,unsigned short ColNum,unsigned char *pBuffer,unsigned short NumByteToWrite)
{
volatile unsigned short i=0;
if(ColNum<256)
NAND_CMD_AREA = 0x00;
else
NAND_CMD_AREA = 0x01;
/* send 1st cycle page programming command to the command area */
NAND_CMD_AREA = 0x80;
/* send address to the address area
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
first byte: A7 A6 A5 A4 A3 A2 A1 A0 (column address)
second byte: A16 A15 A14 A13 A12 A11 A10 A9 (row address)
third byte: 0 A23 A22 A21 A20 A19 A18 A17 (row address)
*/
if(ColNum<256)
NAND_ADDR_AREA = ColNum;
else
NAND_ADDR_AREA = ColNum-255;
NAND_ADDR_AREA = (uint8_t)((PageNum)& 0xFF); //页地址
NAND_ADDR_AREA = (uint8_t)(((PageNum)& 0xFF00) >> 8);
delay_ms(30);
/* write data to data area */
for(i=0;i<NumByteToWrite;i++) //写入数据
{
*(volatile unsigned char*)BANK_NAND_ADDR = *(volatile unsigned char*)pBuffer++;
}
/* send 2nd cycle page programming command to the command area */
NAND_CMD_AREA = 0x10;
/* check operation status */
if(NAND_READY == exmc_nand_getstatus()) {
return NAND_OK;
}
return NAND_FAIL;
}
3.3 读操作
NAND FLASH 的写操作时序图如下所示:首先向指令操作区写入控制命令 0X00 或是 0X01(由列地址大小判断),之后的三个循环中发送写入地址,之后输入控制命令0X30,之后就可以依次读取出数据。
NAND FLASH(HY27US08281A)读取数据代码如下,其中PageNum为页地址; ColNum为列地址,数值为0~511(地址的计算方式详见1.1.3小节);*pBuffer 为存放读取数据的数组;NumByteToWrite 为要读出数据的个数。
unsigned char PhyNand_ReadPage(unsigned long PageNum,unsigned short ColNum,unsigned char *pBuffer,unsigned short NumByteToRead)
{
volatile unsigned short i=0;
/* send 1st cycle read command to the command area */
if(ColNum>255){
NAND_CMD_AREA = 0x01;
NAND_ADDR_AREA = ColNum-255;
}
else {
NAND_CMD_AREA = 0x00;
NAND_ADDR_AREA = ColNum;
}
/* send address to the address area
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
first byte: A7 A6 A5 A4 A3 A2 A1 A0 (column address)
second byte: A16 A15 A14 A13 A12 A11 A10 A9 (row address)
third byte: 0 A23 A22 A21 A20 A19 A18 A17 (row address)
*/
NAND_ADDR_AREA = (uint8_t)((PageNum)& 0xFF);
NAND_ADDR_AREA = (uint8_t)(((PageNum)& 0xFF00) >> 8);
/* send 2nd cycle read command to the command area */
NAND_CMD_AREA = NAND_CMD_READ1_2ND;
delay_ms(30);
/* read data to pbuffer */
for(i=0;i<NumByteToRead;i++)
{
*(volatile unsigned char*)pBuffer++ = *(volatile unsigned char*)BANK_NAND_ADDR;
}
/* check operation status */
if(NAND_READY == exmc_nand_getstatus()) {
return NAND_OK;
}
return NAND_FAIL;
}
参考资料:
《STM32H7开发指南-HAL库版本_V1.0.pdf》、《GD32F450xx_Datasheet_Rev2.2.pdf》、《HY27US08281A_Datasheet.pdf》、《GD32F4xx_用户手册_Rev2.5.pdf》