目录
1.Nand-Flash的原理图及其引脚功能
问题1:Nand-Flash和2440之间只有数据线,如何传输地址呢?
答:在DATA0-DATA7 即传输数据,又传输地址,
当ALE为高电平时传输的是地址.
问题2:从Nand Flash芯片数据手册可知,要操作Nand Flash需要先发出命令,如何分辨是命令还是数据呢?
答: 在DATA0-DATA7 即传输数据,又传输地址,又传输命令,
当ALE为高电平时传输的是地址。
当CLE为高电平时传输的是命令。
当ALE和CLE都为低电平时传输的是数据。
问题3:假设烧写Nand Flash,把命令、地址、数据发送给它之后,Nand Flash如何判断烧写完成?
答:通过判断引脚Rnb:它为高电平表示就绪,低电平表示正忙状态。
2.如何操作Nand Flash
2.1读ID操作
查看时序图:
读ID对于Nand Flash做法和对比S3C2440的做法:
关于不同位宽的设备如何连接及操作方式:点我查看
操作方式 | Nand Flash | S3C2440 |
发送命令 | 1.芯片片选拉低 2.CLE设为高电平 3.在DATA0-DATA7上写入命令 4.发出一个写脉冲WE拉低 |
把命令的值写到寄存器:NFCMMD 例子: NFCMD = 命令值 |
发送地址 | 1.芯片片选拉低 2.ALE设为高电平 3.在DATA0-DATA7上写入地址 4.发出一个写脉冲WE |
把地址的值写到寄存器:NFADDR 例子: NFADDR = 地址值 |
发送数据 | 1.芯片片选拉低 2.ALE和CLE设为低电平 3.在DATA0-DATA7上写入数据 4.发出一个写脉冲WE |
把数据的值写到寄存器:NFDATA 例子:NFDATA = 数据值 |
读取数据 | 1.芯片片选拉低 2.发出读脉冲RE拉低 3.读取在DATA0-DATA7上的数据 |
读取寄存器NFDATA的值 例子: val = NFDATA |
备注:
在S3C2440中集成了一个Nand Flash控制器,它帮我们简化了这些读写的步骤:
我们需要做的就是对应寄存器写地址或数据,或读取数据就可以,时序是Nand Controller帮我们自动完成的。
读取ID的操作:
根据时序图可以知道:
Nand Flash | S3C2440 | |
操作步骤 | 1.发出片选信号 2.发出命令:0x90 3.发出地址:0x00 4.第一次读取第一个字节数据得到:0xEC 5.第二次读取得到设备码:devide code 6.第三次读取得到:Internal Chip Number, Cell Type, Number of Simultaneously Programmed Pages, Etc 7.第四次读取得到: Page Size, Block Size,Redundant Area Size, Organization, Serial Access Minimum 8.第五次读取得到:Plane Number, Plane Size |
1.设置NFCONT寄存器bit1=0 2.NFCMMD=0x90 3.NFADDR=0x00 4.val=NFDATA 接着读取下一个信息 |
芯片手册描述:
S3C2440的Nand Flash Controller
1).设置片选信号:配置NFCONT寄存器
2).发出命令,配置NFCMMD寄存器
3).发送地址:配置NFADDR寄存器
4).读数据,读取寄存器NFDATA寄存器
3.Nand Flash 编程
步骤:
1) .初始化Nand Flash控制器
在S3C2440内部有一个Nand Flash控制器 ,因此当我们外部接有Nand Flash芯片的时候,很多命令发送控制器自动帮我们完成了,但是为了兼容不同类型的Nand Flash芯片,所以2440的时序是可以编程的,外部的时序是确定的,当我们知道了外部芯片的时序,就可以通过编程使Nand Flash控制器的时序能够满足外部Nand Flash芯片的时序,这样子2440就能够控制Nand Flash芯片了。
1).查看外部Nand Flash芯片数据手册的时序图。使用的芯片是:K9F2G08U0C
得到几条关键的信息:
1.CLE、CLE、WE的信号可以同时发出
2.WE的信号持续时间(tWP)最少为12ns
3.WE信号释放后,ALE和CLE信号至少再维持多久才可以变化时间最少为5ns
2).从上边已经知道了外部nand Flash的控制时序了,接下来就是让2440的nand Flash控制器的时序满足这个要求,打开2440数据手册,nand Flash控制器时序控制这一块:
从上边可知,可设置TACLS = 0, TWRPH0 = 12ns,TWRPH1=5ns,注意这个数值是最小值,设置的数值只可大不可小。
可以通过寄存器NFCONF设置这几个位的参数:
因此可以设置寄存器NFCONF的[13:12] =0b00,[10:8]=0b001,[6:4]=0b000.
注意:因为这里的Nand Falsh的位宽是8位的,因此发送地址,命令、数据都只能以一个字节的大小发送,因此需要重新设置一下一次性读写的字节数:
3).设置NFCONT寄存器,初始化ECC,使能Nand Flash控制器
编程如下:
2).读取芯片ID
查看Nand Flash的读ID时序:
可以把读取ID的时序抽象成这几个时刻:
分析:
1).片选信号CS是一个总开关,只有当使能了片选信号,别的操作才有意义,所以一旦片选拉低了以后就可以开始传输读写命令了。
2).把上图的时间轴从t1往t2移动的过程中,可知需要先发出CLE命令信号,然后发送WE写信号(在上面分析时序可知外接的芯片允许CLE和WE同时发送),当CLE和WE有效时,代表的就是写命令,写的命令就是根据 I/Ox(数据总线来决定),其数值就是0x90(这个命令的含义就是读取ID)。到时间t2写的命令就结束了。
备注:
那么关于WE信号需要维持多久才可以释放,数据总线的数据需要维持多久,才能释放CLE信号等,都是通过查看外部所接的芯片手册来决定的,在上面的时序图也提到过,这里再贴一次:
所以结论就是:CLE信号和WE的信号可以同时发出(至少维持12ns),发出这个信号之后就代表是写命令信号,此时数据总线接受的就是命令的数据,tDS的含义是数据的建立时间,至少12ns(在发出CLE信号和WE信号维持的时间内,这个时间已经满足要求了,所以可以不用设置),也就是说至少经过这个时间,WE的信号才能够释放,然后至少再经过tCLH或tALH(5ns),CLE或ALE的信号才能释放。
如果说没有nand Flash控制器的话,我们就需要自己去实现如上所述时序的控制,而且必须掌控每个信号的时间,操作难度就会加大,性能也就随着降低,但是好在S3C2440有一个Nnad Flash控制器,通过前面对Nand Flash 控制器时序的初始化之后,我们写一个命令的时候,只需往寄存器:NFCMMD寄存器写一个命令的数值,也就是: NFCMMD = 命令值; nand Flash控制器就会自动帮我们发送如上的时序,完成写命令操作。
3).同理t2-t3时刻就是一个发送地址的时序步骤,当有nand Flash 控制器,只需往,NFADDR寄存器写地址值,nand flash控制器自动帮我们完成上边的时序
4)t3-t4时刻就是读取一个字节的数据,当有nand flash控制器,只需读取NFDATA寄存器的值就可以得到数据总线传回来的数据,接着再读取一个字节的数据也是一样的步骤。
编写函数:
1).抽象出写地址,写命令,写数据,读数据,片选使能,片选失能的函数如下:
/*使能片选 CS*/
void nand_enable_cs(void)
{
NFCONT &= ~(1<<1);
}
/*失能片选 CS*/
void nand_disable_cs(void)
{
NFCONT |= (1<<1);
}
/*函数:nand_write_cmd
*功能:往Nand Flash写一个字节的命令
*/
void nand_write_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for(i=0;i<10;i++); //适当延时,保证稳定性
}
/*函数:nand_write_addr
*功能:往Nand Flash写一个字节的地址
*/
void nand_write_addr(unsigned char addr)
{
volatile int i;
NFADDR = addr;
for(i=0;i<10;i++); //适当延时,保证稳定性
}
/*函数:nand_writr_data_byte
*功能:往Nand Flash写一个字节的数据
*/
void nand_writr_data_byte(unsigned char data)
{
volatile int i;
NFDATA = data;
for(i=0;i<10;i++);
}
/*函数:nand_read_data_byte
*功能:读取往Nand Flash一个字节的数据
*/
unsigned char nand_read_data_byte(void)
{
return NFDATA;
}
2).根据读取ID的时序,进行读取操作,读取5个字节的数据,如下:
void nand_read_ID(void)
{
int i;
/*保存读取ID信息的数组*/
unsigned char ID_info[5]={0};
/*片选选中*/
nand_enable_cs();
/*发送命令:0x90*/
nand_write_cmd(0x90);
/*发送地址:0x00*/
nand_write_addr(0x00);
/*连续读取5个字节的数据,存储到数组中*/
for(i=0;i<5;i++)
{
ID_info[i]=nand_read_data();
}
/*打印出数组信息:*/
printf("Maker Code : 0x%x\n\r",ID_info[0]);
printf("Devide Code : 0x%x\n\r",ID_info[1]);
printf("3rd cycle : 0x%x\n\r",ID_info[2]);
printf("4th cycle : 0x%x\n\r",ID_info[3]);
printf("5th cycle : 0x%x\n\r",ID_info[4]);
/*片选失能*/
nand_disable_cs();
}
3).编写一个菜单选项功能函数,先只写读取ID的函数:
void nand_flash_test()
{
while(1)
{
char c;
printf("[s] Scan nand flash\n\r");
printf("[e] Erase nand flash\n\r");
printf("[w] Write nand flash\n\r");
printf("[r] Read nand flash\n\r");
printf("[q] Quit \n\r");
printf("Enter after slection: ");
c = getchar();
printf("%c\r\n",c);
/*打印菜单*/
/*菜单选项
*1.识别nand_falsh,设备和容量
*2.读取某个地址的数据
*3.擦除nand_flash扇区数据
*4.往某个地址写数据
*/
switch(c)
{
case 'q':
case 'Q':
return;
break;
case 's':
case 'S':
nand_read_ID();
break;
case 'e':
case 'E':
break;
case 'w':
case 'W':
break;
case 'r':
case 'R':
break;
}
}
}
4).在函数的入口,调用nand_flash初始化函数和测试函数:
int main()
{
/*初始化nand_flash*/
nand_flash_init();
/*调用nand_Flash操作菜单函数*/
nand_flash_test();
return 0;
}
5).修改Makefile内容,增加nand_flash.c进行编译链接:
all: start.o led.o uart.o sdram_init.o main.o execption.o interrupt.o timer.o lib1funcs.o my_printf.o nand_flash.o string_utils.o
arm-linux-ld -T nand_flash.lds $^ -o nand_falsh.elf
arm-linux-objcopy -O binary -S nand_falsh.elf nand_falsh.bin
arm-linux-objdump -D nand_falsh.elf > nand_falsh.dis
%.o : %.c
arm-linux-gcc -march=armv4 -c -o $@ $<
%.o : %.S
arm-linux-gcc -march=armv4 -c -o $@ $<
clean:
rm *.bin *.o *.elf *.dis
6).烧写到NOR_Flash,并从nor_Flash启动:
注意:如果此时烧写到Nand Flash,并从Nand Flash启动程序是不会成功的,因为这个bin文件大小已经超过了4K,且现在还没有实现nand flash的读函数。
打开串口:选择打印打印到的信息:
第一个字节的函数是Maker Code,第二个字节是设备ID,在数据手册,如下描述:
其中我们比较关系的是第4个字节的数据,有关于nand_flash块的大小,页的大小:
在上面我们读取到的数据是:0x95 = 0b10010101 ,因此,Page Size = 2KB,Block Size = 128KB。
我们也可以把这些关键信息打印出来,修改nand_read_ID函数如下:
void nand_read_ID(void)
{
int i;
/*保存读取ID信息的数组*/
unsigned char ID_info[5]={0};
/*片选选中*/
nand_enable_cs();
/*发送命令:0x90*/
nand_write_cmd(0x90);
/*发送地址:0x00*/
nand_write_addr(0x00);
/*连续读取5个字节的数据,存储到数组中*/
for(i=0;i<5;i++)
{
ID_info[i]=nand_read_data();
}
/*打印出数组信息:*/
printf("Maker Code : 0x%x\n\r",ID_info[0]);
printf("Devide Code : 0x%x\n\r",ID_info[1]);
printf("3rd cycle : 0x%x\n\r",ID_info[2]);
printf("4th cycle : 0x%x\n\r",ID_info[3]);
printf("Page Size = %d KB\r\n",1<<(ID_info[3]&0x03));
printf("Block Size = %d KB\r\n",1<<((ID_info[3]>>4)&0x03)+6);
printf("5th cycle : 0x%x\n\r",ID_info[4]);
/*片选失能*/
nand_disable_cs();
}
再次烧写运行程序,就可以打印出页和块的大小了:
备注:此处代码命令为:nand_Flash读取ID
3).Nand_FLash读取数据
- 预备知识:
①nand_flash 的结构示意图如下:
从上面的结构图提取处如下的简图。
可以得到的信息有:
1.这个nand_flash的数据线是8bit的,数据区总共有 2048(2K)列
2.总共有2048个block,每一个block的大小是 128K ,而每个block有64页(page),每个page的大小是2K,
3.这样算来,这个nand_flash的大小是: 2048 * 128 K = 256M
②读取数据时如何发送地址呢?
从数据手册中可以看出,需要发送5个周期的地址,才算发送一个完整的地址,有一些位是没有用到的,其目的也是以后兼容更大芯片的nand falsh.从结构图中计算出这个nand flash的大小是 256 Mbits,这个是如何得到:
分析:
首先对列进行寻址,从看表看出,需要发送两个字节的列地址,共有2048+64 列,即为(2K + 64B),使用二进制描述的话至少需要12位的二进制数(A0-A11),也就是2^12= 4096,方可描述所有的列数。因此一个page = (2k + 64B)bytes
规定在一个block中有64个page,因此一个block的大小是:(128K+4K)bytes
接着对行进行寻址,从看表看出,需要发送三个字节的行地址,但是只用到(A12 - A28),共用到17位,它可描述的最大数组是:2^17 = 131072个数字,如果一个block有64个page,那么17位地址一共可以描述 :131072 /64 = 2048个block,如果使用简图把上面的结构展开的话,大致如下图所示:
- 读操作时序:
步骤:
①发出片选信号
②发出0x00命令.
③发送5个周期的地址(两个列地址,三个行地址(page))
④再发送0x30命令,然后就是接收所发送地址的数据了。
⑤禁止片选
- 判断nand Flash读写忙标志位
通过判断NFSTAT的第0位来判断是否处于忙状态
使程序能从Nand Flash启动
nand_Flash读取ID 代码是没有办法从nand Flash启动的,因为上一个代码的长度大于4K了,当把程序烧写到nand Flash,2240硬件自动把nand Falsh前4K的代码复制到片内内存运行:
为什么从Nand Flash启动会失败?
如果从NOR启动,那么CPU的0地址就是NOR Flash的0地址。片内SRAM的起始地址就是0x40000000
如果从Nand启动,那么CPU的0地址就是SRAM的0地址。此时NOR FLASH(大小为2M)为不可见状态
然后再看一下,代码重定位函数:
void copy_to_sdram(void)
{
/*从sdran.lds链接脚本的到代码的运行地址
*从外部得到_code_start,_bss_start
*接着从0地址复制数据到运行时地址
**/
//定义两个变量,_code_start 用于获取链接脚本的代码起始位置,_bss_star
//用于获取代码结束位置,也是bss段的起始位置
extern int _code_start,_bss_star;
//定义三个指针变量,分别是重定位代码的位置(运行时地址)
//代码段结束位置
//需要重定位的代码
volatile unsigned int *des =(volatile unsigned int *)&_code_start;
volatile unsigned int *end =(volatile unsigned int *)&_bss_star;
volatile unsigned int *src =(volatile unsigned int *)0;
//进行复制,把原地址代码复制到目的地址,代码重定位
while(des<end)
{
*des++ = *src++;
}
}
重定位代码函数是不区分是Nand Flash 启动还是NOR Flash启动的,而直接从0地址开始的代码复制到运行时地址,此处时SDRAM(0x30000000):
情况一:从NOR Flash启动,此时NOR Flash的0地址,对应的就是CPU的0地址,而NOR Flash是支持读取的,因此可以把所有的代码全部重定位到SDRAM.
情况二:从Nand Flash启动,此时片内SRAM的地址对应的就是CPU的0地址,如果从Nand Flash启动,2440硬件会把nand Flash前4K的数据复制到片内SRAM,如果Nand Flash上的程序大于4K,那也不理睬,接着还是重定位,就相当于只重定位了前4K的代码,这样子从Nand Flash就得不到想要的结果。
如何解决这个问题:
现在我们可以写读取Nand Flash数据的函数了,可接着以这样子做:
1.前提:已经能实现Nand Flash的读函数
2.代码烧写到Nand Flash,并从Nand Flash启动。
3.程序运行到重定位代码的位置判断一下,是从Nand Flash启动还是NOR Flash启动(通过往0地址写数据,因为Nand是支持读取的,所以读出的结果和写的结果一样,而NOR Flash不能像内存一样读写,因此读写的内容是不一致的)
4.如果从NOR Flash启动,直接使用简单的重定位代码就行,如果是Nand Flash启动,那就是用Nnad Flash的读函数进行代码的重定位。
核心代码如下:
nand Flash读取数据的函数:
/*函数功能:读取nand_Falsh 数据
*参数1:地址,参数2:读取到的数据,参数3:读取的字节数
*
*/
void nand_read_data(unsigned int addr,unsigned char *data,unsigned int len)
{
int i=0;
/*因为读取数据的时候是一次性读出一页,因此当给出
*地址addr之后,每一页的数据大小是2K,因此我们可以
*根据地址知道我们读取的数据是哪一个页
*/
int page = addr/2048;
/*page是定位到哪一个页,col变量定位的就是在这个
*页的偏移量
*/
int col = addr &(2048-1); //addr%2048;
/*打开片选*/
nand_enable_cs();
/*注意每次读取数据一次性只能读取一页,
*当读取的数据大于一页时就需要循环操作,因此
*加上死循环,当不满足条件再退出
*/
while(i<len)
{
/*发出0x00命令*/
nand_write_cmd(0x00);
/*发送5个周期的地址*/
/*行地址,col addr*/
/*发出列地址的低8位*/
nand_write_addr(col & 0xff);
/*发送高8位,其实只用到4位*/
nand_write_addr((col>>8) & 0xff);
/*列地址。row/page addr*/
nand_write_addr(page & 0xff);
nand_write_addr((page>>8) & 0xff);
nand_write_addr((page>>16) & 0xff);
/*发送0x30命令*/
nand_write_cmd(0x30);
/*等待就绪*/
nand_wait_ready();
/*读取数据*/
/*我们可以从地址分析出所需要读取的行和列,而列就是
*col,列地址最大为2047,因此如果读取的字节数大于(2047-col)
*那么就需要去读取下一个page的数据,依次类推
*for循环里面有两个条件:
*1.当读取到页尾,但是还没有读取完,说明需要读取下一页
*2.已经读取到指定的字节数了,已经读完
*/
for(;(col<2048)&&(i<len);col++)
{
data[i++] = nand_read_data_byte();
}
if(i==len) //说明已经读取完成指定的字节数了
{
break; //跳出循环
}
/*读取到一页中最后一个数据之后
*到下一页的第0个地址继续读取
*/
col = 0;
page++;
}
/*禁止片选*/
nand_disable_cs();
}
判断是从NOR Flash启动还是Nand Falsh启动:
int isBootFromNorFlash()
{
volatile unsigned int *p=(volatile unsigned int *)0;
int val = *p; //读取0地址的值
*p= 0x66666666; //改变0地址的值
if(*p== 0x66666666)
{
/*写入数据成功,说明从Nand Flash启动*/
/*还原刚才改动的数据*/
*p = val;
return 0;
}
else /*NOR Flash启动*/
{
return 1;
}
}
根据启动方式不同,使用不同的代码进行代码重定位:
/*把代码复制到SDRAM运行*/
void copy_to_sdram(void)
{
/*从sdran.lds链接脚本的到代码的运行地址
*从外部得到_code_start,_bss_start
*接着从0地址复制数据到运行时地址
**/
//定义两个变量,_code_start 用于获取链接脚本的代码起始位置,_bss_star
//用于获取代码结束位置,也是bss段的起始位置
extern int _code_start,_bss_star;
//定义三个指针变量,分别是重定位代码的位置(运行时地址)
//代码段结束位置
//需要重定位的代码
volatile unsigned int *des =(volatile unsigned int *)&_code_start;
volatile unsigned int *end =(volatile unsigned int *)&_bss_star;
volatile unsigned int *src =(volatile unsigned int *)0;
int len;
len = ((int)&_bss_star) - ((int)&_code_start); //重定位代码的长度
//进行复制,把原地址代码复制到目的地址,代码重定位
if(isBootFromNorFlash()) //如果从NorFlash启动
{
while(des<end)
{
*des++ = *src++;
}
}
else //否则从Nand Flash启动
{
/*需要使用到Nand Flash,先初始化Nand Flash*/
nand_flash_init();
/*调用nand flash读取数据函数
*从 src 复制到 des ,总共复制len字节,也就是重定位的代码
*/
nand_read_data(src,des,len);
}
}
这里还需要注意一点,需要修改Makefile使编译生成的bin文件,使代码重定位的代码放在前4K,如果在后面的话,nand Flash拷贝到SRAM数据丢失会导致程序重定位失败。
把这几个放在前面编译:
编译,下载到Nand Flash,从Nand Flash启动,如果说这个程序能正常运行,就说明,刚才那么Nand Flash数据读取函数是成功的:
下载复位,程序正常运行,说明数据读取成功:
现在代码可以烧写到nand flash并从nand flash启动,并已经写出了nand_read_dat()函数,现在抽象出一个函数,来读取指定地址的数据并打印出来:
代码如下:
/*函数功能:测试数据擦除函数,调用上面的nand_read_data 函数
默认一次性读取160个字节数据
*/
unsigned short Read_nand_flash(void)
{
//读取的基地址
unsigned int addr;
//定义一个无符号字符型变量p
volatile unsigned char *p;
int i,j;
static unsigned int hex_addr=0;
unsigned char c;
unsigned short amount;
/*保存数据*/
unsigned char str[16];
unsigned char data[160];
/*获取读取地址*/
printf("=================Enter Read Address: ");
/*根据用户输入的地址,结果给回addr
*可输入16进制和10进制
*/
addr = get_uint();
hex_addr = addr;
/*从nand flash读取64个字节的数据*/
nand_read_data(addr,data,160);
/*p指向data,打印data数组的数据*/
p = (volatile unsigned char *)data;
//printf("Enter Read amount(16 times): ");
//amount = get_uint();
printf("Read Data:\n\r");
printf(" 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n\r");
/*读取长度:160字节*/
for(i=0;i<10;i++)
{
printf("0x%08x ",hex_addr);
/*每行打印16字节数据*/
for(j=0;j<16;j++)
{
/*先打印16进制*/
c = *p++; //读取第一个字节的数据,然后p指向下一个字节
str[j] = c; //先保存数值
printf("%02x ",c); //打印出当前字节的16进制
}
printf(" ; ");
for(j=0;j<16;j++)
{
/*打印对应的字符*/
if(str[j]<0x20 || str[j]>0x7e) /*不可视字符,打印出一个点*/
{
putchar('.');
}
else
putchar(str[j]); //可视字符,直接打印
}
hex_addr+=16;
printf("\n\r");
}
}
现在下载到开发版,打印从0地址开始的160字节的数据,如果和烧写的bin文件数据一致,说明读取是成功的,否则读取失败:
1.bin文件内容如下:
2.调用Read_nand_flash()函数,打印0地址开始的内容如下:
对比可以看出数据时一模一样的,说明数据读取成功。
4).Nand Flash块擦除
块擦除时序:
1.选中nand flash片选
2.发送命令:0x60
3.发送行地址:三个字节
4.发送命令:0xD0,等待擦除完成
5.禁止片选
代码如下:
/*函数功能:擦除nand_Falsh 数据
*参数1:地址,参数2:长度
*
*/
int nand_erase(unsigned int addr,unsigned int len)
{
/*注意:nand flash是以块位单位擦除的:一个block=128k
*比如len = 1,虽然len =1,但是此时扔擦除一个block
*/
int page = addr / 2048;
/*擦除的起始地址不是128的整数倍*/
if(addr & (0x1FFFF))
{
printf("ERR addr!Please enter an integer multiple of 128K\n\r");
return -1;
}
/*擦除的字节数不是128K的整数倍*/
if(len & (0x1FFFF))
{
printf("ERR len!Please enter an integer multiple of 128K\n\r");
return -1;
}
/*片选选中*/
nand_enable_cs(void)
while(1)
{
/*发送命令:0x60*/
nand_write_cmd(0x60);
/*发送行地址:page/row addr*/
nand_write_addr(page & 0xff);
nand_write_addr((page>>8) & 0xff);
nand_write_addr((page>>16) & 0xff);
/*发送0xD0命令*/
nand_write_cmd(0xD0);
/*等待擦除完成*/
nand_wait_ready();
/*此时已经完成擦除一个块的数据了,len减去128K*/
len -= (128*1024);
if(len <= 0) //说明擦除的数据小于一个块,擦除结束
{
break; //退出
}
addr+= (128*1024); //指向下一个block,继续擦除
}
/*取消片选*/
nand_disable_cs();
return 0;
}
接着抽象出一个函数,来擦除指定的块,因为擦除是以块为单位的,所以在程序中干脆直接指定擦除哪一个块,防止擦除出错。
程序如下:
/*函数功能:测试数据擦除函数,调用上面的nand_erase 函数
一次性最小擦除一个块。
*/
void Erase_nand_flash(void)
{
int err=0;
//擦除的地址
unsigned int addr;
unsigned int whichblock;
/*从键盘获取删除地址*/
printf("=================remarks:The size of each block is 128K======================\n\r");
printf("=================Enter Erase Block number[0-2047]: ");
/*输入需要删除的block*/
whichblock = get_uint();
while(whichblock > 2047 || whichblock < 0)
{
printf("!!!!!!!!!!!ERROR!!!!!!!!!!\n\r");
printf("=================Please enter a correct number: ");
whichblock = get_uint();
}
/*计算这个block对应的地址*/
addr = whichblock *(128*1024);
/*提示擦除数据的范围*/
printf("=================Erase range : 0x%08x - 0x%08x\n\r",addr,(addr+(128*1024)));
printf("=================erasing ... ");
/*擦除一个块(block)*/
if(nand_erase(addr,128*1024)<0)
{
printf("!!!!!!!!!!!!!!!!Erase fail!!!!!!!!!!!!!\n\r");
}
printf("\n\r=================Erase finished!\n\r");
}
里面注释的很清楚,不做过多解释,直接调用函数Erase_nand_flash();指定擦除:0x500000 即:5M位置的数据(第40个block,计算方式:0x500000 /(1024*128)),擦除的范围是一个block,程序也会有提示:
擦除之前,先读取0x500000位置的数据不为空:
开始擦除:擦除第40个block的数据:
5).Nand_Flash写数据
- 查看芯片手册,nand Flash 写时序:
1.选中nand flash片选
2.发送命令:0x80
3.发送地址:5个字节
4.发送命令:0x10,等待写入完成
5.禁止片选
代码如下:
/*函数功能:往nand_Falsh 写数据
*参数1:写地址,参数2:需要写的数据,参数3:写的字节数
*
*/
void nand_write(unsigned int addr,unsigned char *data,unsigned int len)
{
int i=0;
/*page是定位到哪一个页,col变量定位的就是在这个
*页的偏移量
*/
int page = addr / 2048;
int col = addr % 2048; //addr%2048;
/*片选选中*/
nand_enable_cs();
while(1)
{
/*发出0x00命令*/
nand_write_cmd(0x80);
/*发送5个周期的地址*/
/*行地址,col addr*/
/*发出列地址的低8位*/
nand_write_addr(col & 0xff);
/*发送高8位,其实只用到4位*/
nand_write_addr((col>>8) & 0xff);
/*列地址。row/page addr*/
nand_write_addr(page & 0xff);
nand_write_addr((page>>8) & 0xff);
nand_write_addr((page>>16) & 0xff);
/*发出数据*/
/*for循环里面有两个条件:
*1.当写到页尾,但是还没有写完,说明需要写下一页
*2.已经写到指定的字节数了,已经写完*/
for(;(col<2048) && (i<len);col++)
{
/*一次写一个字节的数据*/
nand_write_data_byte(data[i++]);
}
/*发出0x10命令*/
nand_write_cmd(0x10);
/*等待烧写完成*/
nand_wait_ready();
if(i==len)
{
break; //退出
}
else /*还没有写完,继续写下一页*/
{
col = 0;
page ++;
}
}
/*取消片选*/
nand_disable_cs();
}
抽象出一个函数,用于指定输入写入的地址,以及写入的数据,为了擦除的方便,程序中会提示你所写入的数据的地址是属于哪一页和哪一个block的。
代码如下:
/*函数功能:测试数据擦除函数,调用上面的nand_write 函数
*/
void Write_nand_flash(void)
{
//写数据的地址
unsigned int addr;
unsigned char write_data[100];
unsigned int whichpage,whichblock;
/*从键盘获取写数据起始地址*/
printf("=================Enter Write Address: ");
addr = get_uint();
/*提示你所输入的地址是否正确
*如果正确,则告知你所写的是属于哪个block以及哪个page
*/
if(addr >= 0x10000000 || addr < 0)
{
printf("!!!!!!!!!!!ERROR!!!!!!!!!!\n\r");
printf("=================Please enter a correct addr: ");
addr = get_uint();
}
whichpage = addr/2048;
whichblock= whichpage/64;
printf("=================This address belongs to: page-->%d, block--> %d\n\r",whichpage,whichblock);
printf("=================Enter Write data: ");
gets(write_data);
printf("=================writing........ ");
nand_write(addr,write_data,strlen(write_data)+1);
printf("\n\r=================writie Finished! \n\r");
}
此处注意:
一般在烧写数据之前需要对数据进行擦除操作,除非原本的数据全f,否则都需要进行擦除。
调用函数:Write_nand_flash()函数,写入指定地址:0x500000 数据 hello world!
然后再读取数据:
写入成功,读取0x500000的数据,如下:
如果想擦除这个数据的话,通过上面的提示,知道需要擦除的是第40个block,擦除后再读取数据:
备注:此处代码命名为:nand_Flash读写擦除数据。
源代码
nand_Flash读取ID :https://download.csdn.net/download/qq_36243942/10943840
nand_Flash读写擦除数据:https://download.csdn.net/download/qq_36243942/10947506