本篇博客芯片基于三星s3c2440,nand flash基于三星的K9F2GXXU0M
首先来看下电路图上相关的内容
看到这个电路图会有一个疑问,其他的存储设备一般会有地址总线和数据总线相连,而在nand flash中只有数据总线相连,所以传递数据和地址以及命令都只能依靠这8根数据线,当ALE为高电平的时候传输的是地址,当CLE为高电平时传输的是命令,当ALE和CLE都为低电平时传输的是数据,后面我们将根据详细讲述为什么是这样?
由于nand flash连接上了数据总线,所以需要通过片选信号来控制设备是否要处于工作状态,承担这一功能的是CE。
nand flash和其他所有存储设备一样,在读取数据或者写数据的过程不是一瞬间就完成,会有一段延迟,我们可以根据RnB管脚来判断读写数据是否完成
对于各个管脚的功能我们也可以从nand flash手册上得到
如下介绍了各种mode下,对应的各个引脚的状态。如果想要更详细分析的话,需要阅读相关的时序图
我们来对nand flash做一个简单的测试(基于u-boot完成)
CE电平拉低,选择当前设备
CLE发出一个信号,WE发出一个信号,设置当前是向nand flash写入命令
同时写入 read ID Command
ALE发出一个脉冲,WE发出一个信号,设置当前是向nand flash写入地址
同时写入 Address. 1cycle(为什么是1个周期,因为只有8根线,显然一次是无法完成地址传输的)
当ALE信号结束,会有一个等待时间,接着RE发出信号,分两次读出信息
来看nand flash controller
COMMAND REGISTER NFCMMD 0x4E000008 直接将命令发送给NFCMMD寄存器,就可以实现对nand flash写命令
ADDRESS REGISTER NFADDR 0x4E00000C 直接将命令发送给NFADDR寄存器,就可以实现对nand flash写地址
还有一个数据寄存器NFDATA ,可以通过这个寄存器读数据,也可以通过这个寄存器写数据。
按照时序图上的操作,确实读出了ID
测试成功;开始正式地编程
nand flash的初始化
如下是nand flash controller的时序图
如下是nand flash手册上的时序图,虽然这个是command latch cycle,还有address latch cycle,但是参数其实没有太大区别
TACLS起始于CLE电平拉高, 终止于WE电平开始拉低,所以TACLS = tcls - twp,可以查询tcls 和 twp的值都为0
TWRPH0 = tWP > 15ns TWRPH0 = tALH > 5ns
void nand_init(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
/* 设置时序 */
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
/* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
}
因为nand flash的地址写入方式有点特别,对它的结构有个基本了解会对nand flash的读写编程有帮助
可以看到每一页就像是一个切片,页可以视作是它的基本组成单元,每一页除了有1024字节的page register还有64字节(32words)的obb(out of bank)区域。所以对nand flash的寻址只能在page register区域(有的前辈称为main area),同时寻址的方式是需要确定在哪一页的哪一行,可以从上图来看,1、2周期发送的是行地址(在哪一行),3、4、5发送的是列地址(在哪一页)
为什么是这样?一页包含了2Kbyte + 64byte,所以至少要用12位数据来传输才能确定是在某一个page中的哪一个byte中,十二位数据需要传输两次;nand flash中总共有128K的page,所以需要17位数来传输才能确定是在哪一页里,17位数需要三次传输;
那如何从数据寄存器中接受数据呢?我们采用的方式是传入一个数组,也可以理解成是指针,把数据寄存器中获得的数据传递给数组
那每一次操作可以获得多少个数据呢?其实原则上我们是可以一直获取数据,但是这存在一个问题,如果一直获取数据的话,那么页地址就会发生了改变,相当于换了页,需要重新输入地址。(其实如果画一个表格的话或许更好理解了,无奈买不起excel)
所以每传输一次地址,可以一直读,除非到了源地址的末尾,或者到了当前的页末尾需要换页
#include "s3c2440_soc.h"
void nand_select(void)
{
/*使能片选*/
NFCONT &=~(1<<1);
}
void nand_deselect(void)
{
/*禁止片选*/
NFCONT |= (1<<1);
}
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCCMD = cmd;
for(i=0; i<10; i++);
}
void nand_addr_byte(unsigned char addr)
{
volatile int i;
NFADDR = addr;
for(i=0; i<10; i++);
}
unsigned char nand_data(void)
{
return NFDATA;
}
void wait_ready(void)
{
while (!(NFSTAT & 1));
}
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
int i = 0;
unsigned int page = addr / 2048;
unsigned int col = addr % 2048;
nand_select();
while(i < len)
{
nand_cmd(0x00);
/*地址分为5个周期传给nand flash*/
/*前两个周期传递列地址*/
nand_addr_byte(col & 0xff);
nand_addr_byte(col >> 8 & 0xff);
/*后三个周期传递page地址*/
nand_addr_byte(page & 0xff);
nand_addr_byte(page >> 8 & 0xff);
nand_addr_byte(page >> 16 & 0xff);
nand_cmd(0x30);
wait_ready();
/*其实一次并不是只能读出一个数据,而是可以一直读,直到这一页结束*/
for(; (col < 2048) && (i < len); col++)
{
buf[i] = nand_data();
i++;
}
/*这个小细节其实只是为了让代码更加优美,如果源地址的最后一位在一页的末尾,说明到此就应该结束了,不应该在执行后面的col = 0和page++的操作*/
if(i == len)
break;
/*读完当前页以后,列地址就变成了0,同时页地址也需要加一*/
col = 0;
page++;
}
nand_deselect();
}