硬件原理分析
引脚功能
CLE
高电平ALE
低电平时传输命令CLE
低电平ALE
高电平时传输地址ALE
和CLE
低电平时传输数据CLE
片选信号低电平有效,因为LDATA信号是多芯片共用的RnB
状态引脚,高电平表示空闲,低电平表示正忙WE
引脚上升沿锁存写入数据RE
下降沿后延迟tREA
读出数据
可以看到WE上升沿通知nand写入数据,RE下降沿后延迟tREA
输出数据
直接操作和nand控制器操作对比
命令列表
使用uboot体验nand操作
uboot安装参考上一篇文章韦东山一期视频学习笔记-Norflash
####读ID
1.选中芯片NFCONT
(0X4E000004)bit1置1选中芯片,md.l 4E000004 1
读出寄存器值(因为是32bit寄存器)
可以看到芯片没有选中,mw.l 4E000004 1
使能nand flash
2.发出命令0x90NFCMMD
(0X4E000008)写入0x90,mw.b 4E000008 90
(b
写入1字节)
3.发出地址0x00NFADDR
(0X4E00000C)写入0x00,mw.b 4E00000C 00
(b
写入1字节)
4.读数据
读取NFDATA
(0X4E000010)uboot命令md.b 4E000010 1
5.读Device Code
读取NFDATA
(0X4E000010)uboot命令md.b 4E000010 1
6.退出读ID状态Reset命令mw.b 4E000008 ff
读0地址内容
使用uboot命令nand dump 0
可以查看nand内数据
读操作时序
发出以下命令进行读0地址测试
mw.l 4E000004 1 //使能nand flash
mw.b 4E000008 00 //发出命令
mw.b 4E00000C 00 //发出地址
mw.b 4E00000C 00 //发出地址
mw.b 4E00000C 00 //发出地址
mw.b 4E00000C 00 //发出地址
md.b 4E000010 1 //读数据
mw.b 4E000008 ff //reset命令
可以看出运行结果和uboot命令直接读取nand flash结果一致
K9F1208U0M Nand flash储存结构分析(64M)
1
页是528
字节(512字节 + 16字节oob)1
块有32
页
CPU访问Nand地址不会访问到oob部分,对CPU来说是不存在的,一页只有512Byte。oob的存在是为了解决nand数据易错的问题
K9F1208U0M Nand flash时序分析
从图中可以看出从CLE/ALE
信号发出到WE
信号发出的延迟TACLS
可以设置为0
,WE
有效时间TWRPH0
最小25ns
,TWRPH1
最小10ns
nand flash控制器初始化配置
NFCONF寄存器
初始化之后HCLK配置为100M(10ns),根据上面分析的时序寄存器配置为
#define TACLS 0 // 1-clk(0ns)
#define TWRPH0 6 // 3-clk(25ns)
#define TWRPH1 0 // 1-clk(10ns) //TACLS+TWRPH0+TWRPH1>=50ns
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<0);
NFCONT寄存器
配置使能NAND flash控制器,暂时关闭片选,开ECC
NFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(0<<6)|(0<<5)|(1<<4)|(1<<1)|(1<<0);
nand flash ID读取
//读ID
void Nand_Read_ID(){
unsigned char buf[4];
Nand_Select();
Nand_CMD(0x90);
Nand_Addr_Byte(0x00);
buf[0] = Nand_Data();
buf[1] = Nand_Data();
buf[2] = Nand_Data();
buf[3] = Nand_Data();
Nand_Deselect();
printf("Byte 1 = 0x%x\n\r", buf[0]);
printf("Byte 2 = 0x%x\n\r", buf[1]);
printf("Byte 3 = 0x%x\n\r", buf[2]);
printf("Byte 4 = 0x%x\n\r", buf[3]);
}
注意头文件寄存器的定义NFDATA
需要指向unsigned char *
类型
#define __REG_BYTE(x) (*(volatile unsigned char *)(x))
#define NFDATA __REG_BYTE(0x4E000010) //NAND flash data
生成的bin
文件有11kb
所以需要烧录到norflash
运行
nand flash数据读取
这里的地址信号只针对当前64M
flash。首先发出列地址然后发出page地址,列地址分为上半区256
字节和下半区256
字节,如果列地址起始地址在0~255
使用00
命令,列地址起始地址在256~511
使用01
命令
//读flash
//参数;读取地址 读取储存数组 读取长度
void Nand_Read(unsigned int addr, unsigned char * buf, unsigned int len){
int i = 0;
int page, col;
Nand_Select();
while(i < len){//循环读
page = addr / PAGE_SIZE;//第几页
col = addr % PAGE_SIZE;//第几列
if(col >= 0 && col <= 255){
Nand_CMD(0x00);//上半区读命令
}else if(col >= 256 && col <= 511){
Nand_CMD(0x01);//下半区读命令
col = col - 256;
}
Nand_Addr_Byte(col & 0xff);
Nand_Addr_Byte(page & 0xff);
Nand_Addr_Byte((page>>8) & 0xff);
Nand_Addr_Byte((page>>16) & 0xff);
Nand_Wait_Ready();
//连续读
for(;(col < 256) && (i < len); addr++){
buf[i++] = Nand_Data();
}
if(i == len)
break;
}
Nand_Deselect();
}
nand flash擦除
nand flash擦除是以块
为单位进行擦除的,1块
等于16KB
擦除函数地址和长度只接受16K
整数倍的值,判断addr & 0X3FFF
的值0X3FFF
等于16 * 1024 - 1
二进制为0011111111111111
只有地址&
运算为0
也就是bit13~bit0为0
才运行擦除
//擦除flash 块为单位
//参数;擦除起始地址 擦除长度
int Nand_Erase(unsigned int addr, unsigned int len){
unsigned int page;
unsigned char status;
if(addr & (0x3FFF)){
printf("Nand_Erase addr is not block align\n\r");
return -1;
}
if(len & (0x3FFF)){
printf("Nand_Erase len is not block align\n\r");
return -1;
}
if(len == 0){
printf("Nand_Erase len is 0\n\r");
return -1;
}
Nand_Select();
while(1){
page = addr / PAGE_SIZE;//擦除地址所在的块
//printf("page = %d\n\r", page);
Nand_CMD(0x60);
Nand_Addr_Byte(page & 0xff);
Nand_Addr_Byte((page>>8) & 0xff);
Nand_Addr_Byte((page>>16) & 0xff);
Nand_CMD(0xD0);
Nand_Wait_Ready();
//读取状态位
Nand_CMD(0x70);
status = Nand_Data();
printf("status = 0x%x\n\r", status);
len = len - (16 * 1024); //减去1个块的长度
if(len == 0)
break;
addr = addr + (16 * 1024);//指向下一个block
}
Nand_Deselect();
return 0;
}
nand flash写入
因为列地址只能在A0~A7
之间寻址,所以写入的起始地址
只能在页的前256
字节,
//写flash
//参数;写入地址 写入数组 长度
int Nand_Write(unsigned int addr, unsigned char * buf, unsigned int len){
int i = 0;
int page, col;
printf("addr = 0x%x; buf = %s; len = %d\n\r", addr, buf, len);
col = addr % PAGE_SIZE;
if(col > 255){
printf("Starting address is greater than 255\n\r");
return -1;
}
Nand_Select();
while(1){
page = addr / PAGE_SIZE;//第几页
col = addr % PAGE_SIZE;//第几列
printf("page = %d; col = %d\n\r", page, col);
Nand_CMD(0x80);
//发出地址
Nand_Addr_Byte(col & 0xff);
Nand_Addr_Byte(page & 0xff);
Nand_Addr_Byte((page>>8) & 0xff);
Nand_Addr_Byte((page>>16) & 0xff);
//写数据 连续写
for(;(col < PAGE_SIZE) && (i < len); addr++){
//buf[i++] = Nand_Data();
Nand_WriteData(buf[i++]);
}
Nand_CMD(0x10);
Nand_Wait_Ready();
if(i == len)
break;
}
Nand_Deselect();
return 0;
}
Nand启动运行
首先需要调整start.S
从Nand中复制大于等于bin
文件的字节数到SDRAM
中,并且要调整makefile
顺序保证SDRAM
和Nand
初始化函数和拷贝函数在前4k
(通过dis文件反汇编查看)
start.S部分复制长度是20 * 1024字节,目的地址0x30000000(SDRAM起始地址)
mov r0, #0 //Nand地址
ldr r1, =0x30000000 //目的地址
ldr r2, =20480 //复制长度
bl Nand_Copy_to_SDRAM
复制函数
以字节为单位进行复制,这部分只会运行一次所以没有特别在意效率
//Nand -> SDRAM 复制函数
void Nand_Copy_to_SDRAM(unsigned int source_addr, unsigned char * dst_addr, int len){
unsigned char buf[64];
int i;
int index = 0;
while(1){
Nand_Read(source_addr, buf, 64);
for(i=0; i<64; i++){
*dst_addr = buf[i];
dst_addr++;
source_addr++;
index++;
}
if(index > len)
break;
}
}
完整代码
https://github.com/olichlika/...