采用的器件是:29lv160dbt1-70g
1. 简介:
norflash 的特点是:
- NOR Flash 的特点是芯片内执行(XIP ,eXecute In Place),这样应用程序可以直接在Flash闪存内运行,不必再把代码读到系统RAM中。
- NOR 的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的写入和擦除速度大大影响到它的性能。
- 由于擦除NOR器件时是以64~128KB的块进行的,执行一个写入/擦除操作的时间为5s
- NOR Flash 能够像内存一样读操作,不能像内存写入和擦除
采用的器件:
- 大小是:2M Bytes
- 从 norflash 启动,norflash 的起始地址是 0
- 从 nandflash 启动,norflash 的起始地址是 0x0800 0000
2. 硬件:
给的原理图还是有点瞎的:
这个原理图上型号没有跟实际硬件对应上,其中的几个引脚也是标注“错误”,当然作为批量的东西,找一个通用的元件来代替,本质是没有错误的。
板子上芯片的名字:29lv160dbt1-70g
引脚15:RY/BY# (
Ready/Busy output
)
引脚47:BYTE# (
Selects 8-bit or 16-bit mode
)
高电平:16位模式,有效的输入输出引脚是 DQ15-DQ0
低电平:8位模式,有效的输入输出引脚是 DQ7-DQ0,DQ8-DQ14处于三态,DQ15(
the DQ15 pin is used as
an input for the LSB (A-1) address function.
)
从原理图上看到,引脚47是高电平,这个开发板采用的是16位模式。
下图是扇区的地址表:
3. 编程:
3.1 读:系统上电,直接能读
U16 read_en29lv160ab(U32 addr)
{
return *((volatile U16 *)(addr));
}
3.2 软件复位:
norflash不仅能硬件复位,也能软件复位,思路是向任一地址写入复位命令 0xf0:
void reset_en29lv160ab(void)
{
*((volatile U16 *)0x0) = 0xf0;
}
norflash 的写和擦除:需要 4 到 6 个周期来完成,每一个周期都要把相应的命令写入 norflash 中的某一个寄存器:
3.3 写操作的过程:
- 第一个周期是把命令 0xaa 写到地址 0x555 的命令寄存器中
- 第二个周期是把命令 0x55 写到地址 0x2aa 的命令寄存器中
- 第三个周期是把命令 0xa0 写到地址 0x555 的命令寄存器中
- 第四个周期是把数据 写到目的地址中
需要知道的几点:
- norflash 接到的是s3c2440 的bank0 上,norflash 的基地址为 0x00000000
- 之所以把norflash 的地址向左移一位,是因为 s3c2440 的addr1 链接到了 norflash 的 a0 上
check_toggle函数的作用是:用于判断这次操作是否正确
它的原理是连续两次读取数据总线上的数据,判断数据总线上的第6位数值(DQ6)是否翻转,如果没有翻转则正确,否则还要判断第5位(DQ5),以确定是否是因为超时而引起的翻转。
#define flash_base 0x00000000
#define CMD_ADDR0 *((volatile U16 *)(0x555<<1+flash_base))
#define CMD_ADDR1 *((volatile U16 *)(0x2aa<<1+flash_base))
U8 en29lv160ab_program(U32 addr, U16 dat)
{
CMD_ADDR0 = 0xaa;
CMD_ADDR1 = 0x55;
CMD_ADDR0 = 0xa0;
*((volatile U16 *)(addr)) = dat;
return check_toggle();
}
U8 check_toggle()
{
volatile U16 newtoggle,oldtoggle;
oldtoggle = *((volatile U16 *)0x0);
while(1)
{
newtoggle = *((volatile U16 *)0x0);
if((oldtoggle & 0x40)==(newtoggle & 0x40))
break;
if(newtoggle & 0x20) //DQ5
{
oldtoggle = *((volatile U16 *)0x0);
newtoggle = *((volatile U16 *)0x0);
if((oldtoggle & 0x40)==(newtoggle & 0x40))
break;
else
return 0; //错误
}
oldtoggle = newtoggle;
}
return 1; //正确
}
3.4 擦除操作的过程:
写操作只能使“1”变为“0”,而只有擦除才能使“0”变为“1”。因此在写之前一定要先擦除。
- 第一个周期是把命令 0xaa 写到地址 0x555 的命令寄存器中
- 第二个周期是把命令 0x55 写到地址 0x2aa 的命令寄存器中
- 第三个周期是把命令 0x80 写到地址 0x555 的命令寄存器中
- 第四个周期是把命令 0xaa 写到地址 0x555 的命令寄存器中
- 第五个周期是把命令 0x55 写到地址 0x2aa 的命令寄存器中
- 第六个周期是把命令 0x30 写到要擦除块的首地址
// 输入参数为擦除块的首地址
U8 en29lv160ab_sector_erase(U32 section_addr)
{
CMD_ADDR0 = 0xaa;
CMD_ADDR1 = 0x55;
CMD_ADDR0 = 0x80;
CMD_ADDR0 = 0xaa;
CMD_ADDR1 = 0x55;
*((volatile U16 *)(section_addr)) = 0x30;
return check_toggle();
}
3.5 读取芯片的 ID:
- 第一个周期是把命令 0xaa 写到地址 0x555 的命令寄存器中
- 第二个周期是把命令 0x55 写到地址 0x2aa 的命令寄存器中
- 第三个周期是把命令 0x90 写到地址 0x555 的命令寄存器中
- 第四个周期是读地址 0x100 中的内容 得到厂商 ID(0x1c)
- 第四个周期是读地址 0x01 中的内容 得到设备 ID(0x2249)
//读厂商 ID
U32 get_en29lv160ab_id(void)
{
U32 temp=0;
CMD_ADDR0 = 0xaa;
CMD_ADDR1 = 0x55;
CMD_ADDR0 = 0x90;
temp = (*(volatile unsigned short *)(flash_base+ (0x100<<1)))<<16;
temp |= *(volatile unsigned short *)(flash_base + (1<<1));
return temp;
}
4. 例子:
下面的程序实现了对一块区域进行擦除,写入,并读出的操作,判断写入的数据是否与读出的数据相同:
CFI:是一个记录芯片信息的接口,可以通过特定的命令来访问这些数据
…… ……
U16 buffer[1024];
char cmd;
…… ……
void test_en29lv160ab(void)
{
U32 temp;
U8 sta;
int i;
for(i=0;i<1024;i++)
buffer[i]=2*i+1;
//读ID
temp = get_en29lv160ab_id();
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0xff000000)>>24);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0x00ff0000)>>16);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0x0000ff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0x000000ff));
reset_en29lv160ab(); //这里一定要复位
delay(100);
//擦除块33
sta=en29lv160ab_sector_erase(0xf0000);
if(sta == 0)
{
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=0xaf; //擦除出错
}
else
{
for(i=0;i<1024;i++)
{
sta = en29lv160ab_program(0xf0000+(i<<1),buffer[i]); //写
if(sta == 0) //写出错
{
while(!(rUTRSTAT0 & 0x2));
rUTXH0=0xbf;
break;
}
delay(200);
}
if(sta == 1)
{
for(i=0;i<1024;i++)
{
if(read_en29lv160ab(0xf0000+(i<<1))!=buffer[i]) //读出错
{
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=0xcf;
sta = 3;
break;
}
}
if(sta !=3) //全部操作都正确
{
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=0x66;
}
}
}
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=0x88; //结束
}
//简单测试CFI
void test_en29lv160ab_CFI(void)
{
U16 temp;
*((volatile U16 *)(0x55<<1+flash_base))=0x98; //CFI命令
temp = (*(volatile unsigned short *)(flash_base+ (0x10<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x11<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x12<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x13<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x14<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x15<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x16<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x17<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x18<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x19<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
temp = (*(volatile unsigned short *)(flash_base+ (0x1a<<1)));
//while(!(rUTRSTAT0 & 0x2)) ;
//rUTXH0=(U8)((temp&0xff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)(temp&0x00ff);
}
void __irq uartISR(void)
{
char ch;
rSUBSRCPND |= 0x1;
rSRCPND |= 0x1<<28;
rINTPND |= 0x1<<28;
ch=rURXH0;
switch(ch)
{
case 0x11: //get ID
cmd = 1;
break;
case 0x66: //test CFI
cmd = 6;
break;
case 0x77: //test norflash
cmd = 7;
break;
}
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=ch;
}
void Main(void)
{
U32 temp;
int i;
//uart0 port
rGPHCON = 0x00faaa;
rGPHUP = 0x7ff;
//init uart0
rULCON0 = 0x3;
rUCON0 = 0x5;
rUFCON0 = 0;
rUMCON0 = 0;
rUBRDIV0 = 26;
rSRCPND = (0x1<<19)|(0x1<<28);
rSUBSRCPND = 0x1;
rINTPND = (0x1<<19)|(0x1<<28);
rINTSUBMSK = ~(0x1);
rINTMSK = ~((0x1<<19)|(0x1<<28));
pISR_UART0 = (U32)uartISR;
cmd = 0;
while(1)
{
switch(cmd)
{
case 1: //读ID
cmd = 0;
temp = get_en29lv160ab_id();
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0xff000000)>>24);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0x00ff0000)>>16);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0x0000ff00)>>8);
while(!(rUTRSTAT0 & 0x2)) ;
rUTXH0=(U8)((temp&0x000000ff));
reset_en29lv160ab();
break;
case 0x7:
cmd = 0;
test_en29lv160ab();
break;
case 0x6:
cmd = 0;
test_en29lv160ab_CFI();
reset_en29lv160ab();
break;
}
}
}
博客参考: s3c2440对norflash的操作