这个操作真他妈蛋疼!
Nand Flash型号:K9F2G08U0A。三星产的,TSOP1封装的有48个引脚,很多都没有用,8个I/O口,地址数据命令复用这个端口。
引脚名称: 描述 引脚名称: 描述
I/O0-I/O7 数据输入输出WP#写保护
CLE 命令锁存使能 R/B# 准备好/忙碌
ALE 地址锁存使能 vcc 电源2.7-3.6v
CE# 片选 vss 地
WE# 写使能 其余:没用
根据芯片手册第九页可以知道,
A0-A11用于列地址,A12-A29是行地址
command register 命令寄存器,不同的Nand Flash 有不同的命令
大小是2048+64Mbit 就是256M内存大小,加上8M OOB区,用来存放一些校验信息之类的信息。
8个I/O口
存储结构也在第九页
1page=(2k+64)byte
1Block=(2k+64)*64pages=128k+4k byte
1 Device = (2K+64)B x 64Pages x 2,048 Blocks=2112Mbit=256M+8M
记住的是1block=64page,1device=64block,最后换成M,要除以8。
根据芯片手册18页,进一步知道,每一页都是2048B的main域+64B的spare域。main域分为4个小扇区,每个扇区512B,spare分为4个每个16B。每一页之所以分为更小的
空间是为了提高查询效率,OOB 区在这里也称为Spare 域,主要用来存放文件系统的信息、ECC 校验信息、数据的格式等等。
访问方法
对于 NAND FLASH 的访问方法, 我们需要了解其命令字和操作方法。 操作 NAND FLASH 的时候,先传输命令,接着传输地址,然后才读写数据,中间需要根据 R/nB
引脚来判断 FLASH 的忙闲。根据命令字位于第10页,需要什么操作,先写相应命令,写入到NFCMMD寄存器就可以了。
写完命令后该写地址了,地址位于芯片手册9页最下端,在5个周期内发5个地址,前两个是列,后三个是行,因为不能一次发完,所以分多次发。
时序设置:
时序设置非常重要,芯片手册的后面有不少都是时序,设计到时序必然设计到时间长短和时钟。
设置 CLE(命令控制使能)和 ALE(地址控制使能)时序,设置关键就是上升沿和下降沿高低电平持续时间等等。见芯片手册20页,后面会提到的。
常用的寄存器:
寄存器是多去了,复杂的很,只找几个用到的
NFCONF:NAND配置寄存器
NFCONF[15:12],TACLS 参数的设置, 范围是 0~15.
NFCONF[11:8], TWRPH0 参数的设置,范围是 0~15.
NFCONF[7:4], TWRPH1 参数的设置,范围是 0~15.
上面这三个参数表示
TACLS:CLE/ALE在写使能出发之前和建立成功后持续的时间
TWRPH0:表示使能位持续的时间。
TWRPH1:使能位无效后,CLE/ALE状态保持的时间。
就是上面提到的时序相关的。
NFCONF[3], NAND 类型的选择,0 为 SLC 型,1 为 MLC 型 NAND FLASH. 单层和多层设置
NFCONF[2], NAND 页大小的选择.
NFCONF[1], 读写 NAND FLASH 的地址周期的选择.
NFCONT:NAND控制寄存器
NFCONT[1], 是否使能片选 NAND FLASH,0 使能,1 禁止.
NFCONT[0], NAND FLASH 控制器使能位,0 禁止,1 使能.
NFCMMD:NAND命令寄存器 ,传说中的放命令的地方,开头以提及。
NFCMMD[31:8], 保留,默认为 0 即可.
NFCMMD[7:0], 8 位的命令值,要往 NAND FLASH 发命令,只需要将命令写到 NFCMMD 寄存器的低八位,NAND FLASH 控制器就会自动将命令写入 NAND FLASH 芯片了。
NFADDR:NAND地址寄存器,和上边没什么区别,只是这个是放地址的。
NFDATA:NAND数据寄存器,和前面差不多,只是这个是32位可用的,上面两个是8位可用的
NFSTAT:状态寄存器,第四位可以判断是否处于忙的状态。
在S5PV210的693页的 NAND FLASH MEMORY TIMING这个图
对比这NAND芯片手册的13页 AC Timing Characteristics for Command / Address / Data Input这个表格,规定电平持续时间长短。
得知TACLS=tALS=12ns TWRPH0=tWP=12ns TWRPH1=tWH=10ns ,这些都是取最小的值。
引脚的连接:
MP03_0 --->CLE 命令锁存使能
MP03_1 ---->ALE地址锁存使能
MP03_2------>WE写使能
MP03_3------>RE读使能
MP01_2------>CE片选使能
MP03_4------>R/B 准备好/忙碌
MP06 ------->i/o 0--8
#ifndef _NAND_H_
#define _NAND_H_
/*NAND Pins*/
#define NFCONF ( *(volatile unsigned int *)0xB0E00000 ) //nand配置寄存器
#define NFCONT ( *(volatile unsigned int *)0xB0E00004 ) //nand控制寄存器
#define NFCMMD ( *(volatile unsigned char *)0xB0E00008 ) //命令寄存器
#define NFADDR ( *(volatile unsigned char *)0xB0E0000C )//地址寄存器
#define NFDATA ( *(volatile unsigned char *)0xB0E00010 )//数据寄存器
#define NFSTAT ( *(volatile unsigned int *)0xB0E00028 )//状态寄存器
/* NAND GPIO Pins */
#define MP0_1CON ( *((volatile unsigned long *)0xE02002E0) )
#define MP0_2CON ( *((volatile unsigned long *)0xE0200300) )
#define MP0_3CON ( *((volatile unsigned long *)0xE0200320) )
/* 时序相关的设置 */
#define TACLS 2
#define TWRPH0 1
#define TWRPH1 1
/* K9F4G08U0B参数 */
/*K9F2G08U0A*/
#define PAGE_SIZE 2048
#define NAND_SECTOR_SIZE_LP 2048
#define NAND_BLOCK_SIZE 64
typedef struct nand_id_msg
{
unsigned char maker_id;
unsigned char device_id;
unsigned char id_3th;
unsigned char id_4th;
unsigned char id_5th;
}nand_id_msg;
void wait_idle();//等待忙碌函数
unsigned char read_nand_status();//读状态
void nand_select_chip();//片选
void nand_deselect_chip();//释放片选
void write_cmd(int cmd);//写命令
void write_addr(unsigned int addr);//写地址
void nand_reset();//复位
unsigned char read_data();//读数据
void nand_init();//初始化
void nand_read_id();//读ID
int nand_read(unsigned int *buf, unsigned long start_addr, int size);//完整的读
int nand_write(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long size);//完整的写
unsigned char nand_erase(unsigned long block_num);//擦除去
#endif //_NAND_H_
#include "nand.h"
#include "led.h" //delay()
//等待忙碌
void wait_idle()
{
int i;
/* NFSTAT[4]为1时表示可以操作NAND FLASH,为0是表示忙 */
while(!(NFSTAT&(1<<4)));
for(i=0; i<10; i++);
}
unsigned char read_nand_status()//芯片手册41 页
{
unsigned char ret;
int i;
// 1. 发出片选信号
nand_select_chip();
// 2. 读状态
write_cmd(0x70);
for(i=0; i<10; i++);
ret = read_data();
// 3. 取消片选
nand_deselect_chip();
return ret;
}
void nand_select_chip()
{
int i;
NFCONT &= ~(1<<1);//210手册710页,NFCONT的[1]位
/*NAND Flash Memory nRCS[0] signal control
0 = Force nRCS[0] to low (Enable chip select)
1 = Force nRCS[0] to High (Disable chip select)
Note: The setting all nCE[3:0] zero can not be allowed. Only
one nCE can be asserted to enable external NAND flash
memory. The lower bit has more priority when user set all
nCE[3:0] zeros.
除了[1]是和使能有关的,[22][23]也是,用来片选不同的NANDflash,0的时候使能,
不能把这三个都设置为0.有多个设置为0的时候,相当于片选的是低位,这个操作片选了
最低位
*/
for(i=0; i<10; i++);
}
void nand_deselect_chip()
{
int i;
NFCONT |= (1<<1);
//这是释放,芯片不被选择
for(i=0; i<10; i++);
}
void write_cmd(int cmd)
{
NFCMMD = cmd;//直接把命令写到这个NFCMMD寄存器就可以了,就是一些数
}
void write_addr(unsigned int addr)
{
//参考芯片手册9页,行列地址写入顺序,前两个是列,后三个是行int是个32位数,I/O口有8个,每次最多写8位
NFADDR = (addr>>0) & 0xFF;/*A0--A7,写了8位*/
wait_idle();
NFADDR = (addr>>8) & 0x7;/*A8--A11,这写了三位*/
wait_idle();
NFADDR = (addr>>11) & 0xFF;/*A12--A19,写入八位*/
wait_idle();
NFADDR = (addr>>19) & 0xFF;/*A20--A27,写入八位*/
wait_idle();
NFADDR = (addr>>27) & 0x1;/*A28,写入1位*/
wait_idle();//判断处理器忙不忙
}
unsigned char read_data()
{
return NFDATA;//从这个地方直接取数据就可以了
//这个寄存器是个32位的,在210的芯片手册695页,可以看到,数据存储格式是小端法,大于一个字节
//数据返回时候,先返回的是低位
}
void write_data(unsigned char data)
{
NFDATA = data;//同read_data,这里参数是unsigned char ,也可以为其他类型。
}
void nand_reset()//复位操作,NAND芯片手册42页,可以知道在写入0XFF命令后,R/B引脚会出现tRST时间长的低电平
{
nand_select_chip();
write_cmd(0xff); // 复位命令
wait_idle();//这一句前面严禁来说应该根据条件判断是否出现了低电平跳变到高电平
nand_deselect_chip();
}
void nand_init()
{
/*
* 设置时间参数(HCLK_PSYS = 667MHz/5 = 133MHz)
* NFCONF[15:12]: TACLS = 2 2/133Mhz = 15ns > 12ns
* NFCONF[11:8]: TWRPH0 = 1 15ns * 2 = 30ns > 12ns
* NFCONF[7:4]: TWRPH1 = 1 15ns * 2 = 30ns > 10ns
* NFCONF[1]: 当页大小为2KB或4KB时,1表示5分地址周期
*/
NFCONF |= (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4)|(1<<1);
/*
* NFCONT[1]:为0时表示禁止片选,为1时表示使能片选
* NFCONT[0]:为0时表示禁止NAND FLASH控制器,反之使能
*/
NFCONT |= (1<<1)|(1<<0);
/* 设置相应管脚用于Nand Flash控制器*/
MP0_1CON = 0x22333322;//210手册185页,这里面为3的原来是5,设置为3后为片选使能信号控制
MP0_2CON = 0x00002222;
MP0_3CON = 0x22222222;
/* 复位NAND Flash */
nand_reset();
return;
}
/* 读ID */
void nand_read_id()
{
nand_id_msg nand_id;
/* 片选 */
nand_select_chip();
/* 发90命令 */
write_cmd(0x90);//依据NAND芯片手册10页,读ID命令和31页时序图,确定写0x90命令和地址0x00
/* 写00地址 */
write_addr(0x00);
//31页的图,在发送后的5个周期,分别返回5个数据,对应下面内容
nand_id.maker_id = read_data();//make code
nand_id.device_id = read_data();//device code
nand_id.id_3th = read_data();//Internal Chip Number, Cell Type, Number of Simultaneously Programmed Pages, Etc
nand_id.id_4th = read_data();//Page Size, Block Size,Redundant Area Size, Organization, Serial Access Minimum
nand_id.id_5th = read_data();//Plane Number, Plane Size
//具体的信息可以查看NAND芯片手册32页,有具体描述
printf("\nmaker_id = %x,device_id = %x\r\n",nand_id.maker_id,nand_id.device_id);
nand_deselect_chip();
}
/* 从NAND FLASH里读一页到buf指定的地址 */
int nand_read(unsigned int *buf, unsigned long start_addr, int size)
{
//读取是以页为单位,具体时序在芯片手册35页,具体流程图在17页
int i, j;
/* 选中芯片 */
nand_select_chip();
for(i=start_addr; i < (start_addr + size);)
{
/* 发出READ0命令 */
write_cmd(0x00);//参考芯片手册35页
/* Write Address */
write_addr(i);//5个周期,每个周期写一个,和芯片完全符合
write_cmd(0x30);//参考芯片手册35页
wait_idle();
for(j=0; j < NAND_SECTOR_SIZE_LP; j++, i++)
{
*buf = read_data();
buf++;
}
}
if(read_nand_status()&0x01)//分析R/B判断是否完成
/*The system controller can detect the completion of
this data transfer(tR) by analyzing the output of R/Bpin*/
{
/* 取消片选信号 */
nand_deselect_chip();
printf("\nNand read fail\r\n");
return -1;
}
else
{
/* 取消片选信号 */
nand_deselect_chip();
printf("\nNand read success\r\n");
return 0;
}
}
/* 从buf指定的地址写一页数据到NAND FLASH */
int nand_write(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long size)
{
//和读类似,参考芯片手册25页 Page Program Operation
int i, j;
/* 选中芯片 */
nand_select_chip();
for(i=nand_addr; i < (nand_addr + size);)
{
/* 发出wirte0命令 */
write_cmd(0x80);
/* Write Address */
write_addr(i);
write_cmd(0x10);
wait_idle();
for(j=0; j < NAND_SECTOR_SIZE_LP&&size!=0; j++,size--,i++)
{
delay(5); //一定要有延时,否则会失败
write_data(*sdram_addr);
sdram_addr++;
nand_addr++;
}
}
if(read_nand_status()&0x01)
{
/* 取消片选信号 */
nand_deselect_chip();
printf("\nNand write fail\r\n");
return -1;
}
else
{
/* 取消片选信号 */
nand_deselect_chip();
printf("\nNand write success\r\n");
return 0;
}
}
unsigned char nand_erase(unsigned long block_num)
{
unsigned long i = 0;
// 获得row地址,即页地址r
unsigned long row = block_num * NAND_BLOCK_SIZE;
/*根据芯片手册的第9页的图,128k需要17个二进制位,所以那个应该是行地址,
2K,需要11位就可以了,所以这个是列地址,1block=64page,所以要用64*号码*/
1. 发出片选信号
nand_select_chip();
/* 2. 擦除:第一个周期发命令0x60,
<span style="white-space:pre"> </span> * 第二个周期发块地址,
* 第三个周期发命令0xd0
*/
write_cmd(0x60);
for(i=0; i<10; i++);
// Row Address A12~A19,对比原来写入地址的5个环节,把row低8位取出后作为A12-A19
NFADDR = row & 0xff;
for(i=0; i<10; i++);
// Row Address A20~A27
NFADDR = (row >> 8) & 0xff;
for(i=0; i<10; i++);
// Row Address A28~A30
NFADDR = (row >> 16) & 0xff;
NFSTAT = (NFSTAT)|(1<<4);
write_cmd(0xd0);
for(i=0; i<10; i++);
wait_idle();
if(read_nand_status()&0x01)
{
/* 取消片选信号 */
nand_deselect_chip();
printf("\nFail to erase block %d\r\n", block_num);
return -1;
}
else
{
/* 取消片选信号 */
nand_deselect_chip();
printf("\nSuccess to erase block %d\r\n", block_num);
return 0;
}
}
#include "lib.h"
#include "clock.h"
#include "led.h"
#include "uart.h"
#include "nand.h"
void *memcpy(void *dest, const void *src, int count)
{
char *tmp = dest;
const char *s = src;
while (count--)
*tmp++ = *s++;
return dest;
}
int main()
{
unsigned int rbuf[2048];
unsigned char wbuf[] = "hello";
unsigned char c;
sys_clock_init(); //初始化时钟
uart_init(); //初始化串口
nand_init(); //初始化Nand Flash
/* 提醒用户,如何测试本实验 */
printf("[i] Read K9F4G08U0B NAND FALSH ID\r\n");
printf("[e] Erase K9F4G08U0B Nand Flash block\r\n");
printf("[r] Read K9F4G08U0B Nand Flash\r\n");
printf("[w] Write K9F4G08U0B Nand Flash\r\n");
while (1)
{
printf("Please enter your choice: ");
c = uart0_getbyte(); //等待输入
printf("%c",c);
switch(c)
{
case 'I':
case 'i':
nand_read_id(); //读K9F4G08U0B的ID号
break;
case 'E':
case 'e':
nand_erase(1); //擦除第1块,从0开始算起。
break;
case 'R':
case 'r':
nand_read(rbuf, (unsigned long)0, 9); //页读操作
printf("Read string ok:%s\r\n",rbuf);
break;
case 'W':
case 'w':
nand_write(wbuf, (unsigned long)0, 9); //页写操作
break;
default:
break;
}
}
return 0;
}