(本页笔记的详细实验代码,可参看这里:https://github.com/elvinsys/arm_tq2440/tree/master/1_uboot/1-13.nandflash)
一、 NandFlash的原理解析
1. NandFlash一般充当的是嵌入式系统中存放数据的“硬盘”的角色。
2. 分类
SLC(Single Level Cell):单层式存储,在存储格上只存放一位数据
MLC(Multe Level Cell) :多层式存储,在存储格上则能存放两位数据
3. SLC与MLC的对比
3.1 价格:MCL低于SLC
3.2 速度:SLC是MLC的3倍
3.3 使用寿命:SLC可擦写10万余次,MLC仅为1万余次
3.4 功耗:SLC比MLC功耗低15%
4. NandFlash的编址方式:
内存为统一编址,需要大量的地址线和数据线,NandFlash为独立编址,共用8根地址线,ARM核通过NandFlash控制器对NandFlash访问,通过8个专属寄存器来传递地址、命令以及数据。
5. NandFlash的地址结构:
(例如K9F2G08X0A这么一个型号的nandflash芯片) = (2K + 64)B * 64 Page * 2048 Blocks = 2112Mbits
I/O 0 1 2 3 4 5 6 7
1st A0 A1 A2 A3 A4 A5 A6 A7
2ed A8 A9 A10 A11 L L L L Column Address
3rd A0 A13 A14 A15 A16 A17 A18 A19
4th A0 A21 A22 A23 A24 A25 A26 A27
5th A0 L L L L L L L Row Address
2kbytes : A0-A11 Column Address 列地址
128kPages: A12-A28 Row Address 行地址
L : must Low
6. 信号引脚
CLE(Command Latch Enable):命令锁存允许
ALE(Address Latch Enable):地址锁存允许
CE :芯片选择
RE :读允许
WE :写允许
WP :在读写期间写保护
R/B :读忙
二、 NandFlash驱动设计(读部分)
先touch nand.c 把nand.o加入到Makefile中
1. NandFlash的读写方式
1.1 页读:通过页地址,读取2K整页数据
1.2 随机读:通过行地址和列地址,读取某页某字节的数据
2. 页读的实现方法:
2.1 参考NandFlash的芯片手册,(搜索“operation”关键字),找出Read的时序图
建立函数 void_PageRead(unsigned long addr, unsigned char *buff);/* addr为页地址,buff为数据存放指针 */
2.2 根据 I/O 引脚编写代码如下:
2.2.1 /* 选中NandFlash芯片 */
#define NFCONF (*(volatile unsigned long*)0x4E000000)
void select_chip()
{
NFCONT &= ~(1<<1);
}
2.2.2 /* 取消选中NandFlash芯片 */
void deselect_chip()
{
NFCONT |= (1<<1);
}
2.2.3 /* 清除RnB */
#define NFSTAT (*(volatile unsigned char*)0x4E000020)
void clear_RnB()
{
NFSTAT |= (1<<2);
} //写1置0,从低电平->高电平,自动置1
2.2.4 /* 发送命令 */
#define NFCMMD (*(volatile unsigned char*)0x4E000008)
void send_cmd(unsigned cmd)
{
NFCMMD = cmd;
}
2.2.5 /* 发送地址 */
#define NFADDR (*(volatile unsigned char*)0x4E00000C)
void send_addr(unsigned addr)
{
NFADDR = addr;
}
2.2.6 /* 等待RnB */
void wait_RnB()
{
while(!(NFSTAT&(1<<2)));
}
2.3 NF_PageRead函数的实现
void NF_ReadPage(unsigned long addr, unsigned char* buff){
int i;
//选中nandflash
select_chip();
//清楚RnB
clear_RnB();
//发送指令0x00
send_cmd(0x00);
//发送两个列地址
send_addr(0x00);
send_addr(0x00);
//发送三个行地址
send_addr(addr&0xff);
send_addr((addr>>8)&0xff);
send_addr((addr>>16)&0xff);
//发送指令0x30
send_cmd(0x30);
//等待RnB
wait_RnB();
//读数据到指定地址
for(i = 0;i < 2048; i++)
{
buff[i] = NFDATA;
}
//取消选中nandflash
deselect_chip();
}
3. NandFlash的初始化
3.1 方法描述
3.1.1 设置NFCONF寄存器的三个参数TACLS [13:12],TWRPH0 [10:8],TWRPH1 [6:4]
(参考2440芯片手册6-3,对比Nandflash手册P20 command latch cycle)
TACLS->tCLS - twp
TWRPH0->twp
TWRPH1->tCLH
Duration = HCLK x TACLS > 0 ns TACLS = 1
Duration = HCLK x (TWRPH0 + 1) > 12ns TWRPH0 = 2
Duration = HCLK x (TWRPH1 + 1) > 5 ns TWRPH1 = 1
3.1.2 设置NFCONT寄存器的两个参数
MODE [0] 0:disable 1:enable //应填1
Reg_nCE [1] 0:enable select chip 1:disable //填1
3.1.3 对NandFlash进行复位(参考NandFlash手册的Reset Operation),具体代码如下:
void nand_rest()
{
//选中nandflash
select_chip();
//清楚RnB
clear_RnB();
//发送指令0xff
send_cmd(0xff);
//等待RnB
wait_RnB();
//取消选中nandflash
deselect_chip();
}
3.2 NandFlash初始化的实验代码如下:
#define NFCONF (*(volatile unsigned long*)0x4E000000)
#define TACLS (1<<12)
#define TWRPH0 (2<<8)
#define TWRPH1 (1<<4)
void nand_init()
{
NFCONF |= (TACLS | TWRPH0 | TWRPH1);
NFCONT |= (NAND_ENABLE | NAND_SELECT_DISABLE);
nand_rest();
}
4. 把NandFlash的数据复制到SDRAM的代码实现:
void NandFlash_to_SDRAM(unsigned long nand_addr, unsigned char* sdram_addr, int size)
{//size原来定义为unsigned size,虽然编译能过,但下载后就无法点灯
int i;
for(i = (nand_addr>>11); size >0;)
{
NF_ReadPage(i,sdram_addr);
sdram_addr += 2048;
size -= 2048;
i++;
}
}
5. 在start.S中修改 copy_ram标号的内容
copy_ram:
mov r0, #0x00
ldr r1, =_start
ldr r2, =bss_end
sub r2, r2, r1
mov ip, lr
bl NandFlash_to_SDRAM
mov lr, ip
mov pc, lr
6. 因为在copy_ram中需要用到堆栈,因此在start.S中,把copy_ram的标号前,加入bl init_stack 和 bl init_nand
三、 NandFlash驱动设计(写部分)
1. NandFlash写数据的代码实现:(参考手册“Page Program Operation”)
int NF_WritePage(unsigned long addr, unsigned char* buff)
{
int i, ret;
//选中nandflash
select_chip();
//清楚RnB
clear_RnB();
//发送指令0x80
send_cmd(0x80);
//发送两个列地址
send_addr(0x00);
send_addr(0x00);
//发送三个行地址
send_addr(addr&0xff);
send_addr((addr>>8)&0xff);
send_addr((addr>>16)&0xff);
//写数据到指定地址
for(i = 0;i < 2048; i++)
{
NFDATA = buff[i];
}
//发送指令0x10
send_cmd(0x10);
//等待RnB
wait_RnB();
//发送指令0x70
send_cmd(0x70);
//接收结果
ret = NFDATA;
//取消选中nandflash
deselect_chip();
return ret;
}
2. 对NandFlash进行擦除操作的实现代码:(参考NandFlash手册中的“Block Erase Operation”整块擦除的内容)
int NF_Erase(unsigned long addr)
{
int ret;
//选中nandflash
select_chip();
//清楚RnB
clear_RnB();
//发送指令0x60
send_cmd(0x60);
//发送三个行地址
send_addr(addr&0xff);
send_addr((addr>>8)&0xff);
send_addr((addr>>16)&0xff);
//发送指令0xd0
send_cmd(0xd0);
//等待RnB
wait_RnB();
//发送指令0x70
send_cmd(0x70);
//接收结果
ret = NFDATA;
//取消选中nandflash
deselect_chip();
return ret;
}
四、 最后,在main.c中加入测试代码:
NF_Erase(64*5);
led_off();
buff[0] = 100;
NF_WritePage(0xa0000, buff);
buff[0] = 10;
NF_ReadPage(0xa0000, buff);
if(buff[0] == 10)
{
led_off();
}