这一节来了解在jz2440上的nor flash的操作,jz2440上有nor flash和nand flash两种flash,这两种flash的基本的差别如下
nor | nand | |
---|---|---|
接口 | 像内存一样,引脚较多 | 引脚少,可以复用 |
容量 | 小 | 大 |
读操作 | 简单,像内存一样直接读 | 复杂,需要使用指令 |
写操作 | 使用命令来写(慢) | 使用命令来写(快) |
特性 | 无位反转 | 存在位反转,坏块 |
jz2440上的Nor flash的接口如下图所示
jz2440中的nor flash配置为16位的位宽,从上图来看,CPU发出的地址LADDR1接到了nor flash的地址线A0上,所以nor flash看到的地址是CPU右移一位得到的
1、nor flash指令
nor flash是像内存一样的接口,所以它的读操作像内存一样直接去读就行了,对于nor flash的写操作就稍微麻烦一些了,需要使用固定的指令去对nor flash进行写操作,但是在写之前需要对nor flash进行擦除
在获取nor flash设备信息的时候,先进入nor flash的CFI模式,CFI(通用的flash接口),进入CFI模式之后就可以获取到nor flash的基本设备信息了
要获取的基本信息也就是上面这些了,下面通过一张表格来对这些内容进行整理
地址 | 指令 | 地址 | 指令 | |
---|---|---|---|---|
解锁 | 0x555 | 0xaa | 0x2aa | 0x55 |
复位 | 任意地址 | 0xf0 | ||
进入CFI模式 | 0x55 | 0x98 |
读ID | 解锁,往0x555写入0x90,读0地址是厂家ID,读1地址是设备ID,复位退出读ID模式 |
---|---|
读容量 | 进入CFI模式,读取0x27地址的值,可以计算出容量 |
读取regions容量 | 进入CFI模式,读取0x2c地址处的值 |
擦除块 | 先解锁,往0x555地址写入0x80,再解锁,往擦除地址处写入0x30 |
写操作 | 先解锁,往0x555地址写入0xa0,然后往写入地址处发送要写的值 |
对于nor flash的基本操作指令就了解了,在这里要对上面的regions进行说明,1个nor flash中含有1个或者多个regions,而一个regions含有一个或者多个block,读出regions之后还需要读出该regions含有多少个block,每一个block的容量是多少,从[2D,30]的地址就存在这些内容,[2D,2E]的值+1就表示该region含有多少block,[2F,30]*256表示一个block的容量是多少,然后往后移动4个字节获取下一个regions的信息
2、测试程序
封装对nor flash的一些指令函数
#define NOR_FLASH_BASE 0 /* jz2440 nor -> cs0 , base addr = 0 */
/* 在地址偏移0ff的地方发出数据value */
void nor_write_word(unsigned int base,unsigned int off,unsigned int value)
{
/* 发出指令,因为CPU的地址线A1接nor flash的地址线A0,所以这里发出的地址要向左偏移一位
nor flash接收的时候再向右偏移一位,就保证了地址不变
*/
volatile unsigned short *p = (volatile unsigned short *)(base + (off << 1));
*p = value;
}
/* 调用上面的函数 */
void nor_cmd(unsigned int off,unsigned int cmd)
{
nor_write_word(NOR_FLASH_BASE,off,cmd);
}
/* 读取两个字节的数据,地址同样要偏移 */
unsigned int nor_read_word(unsigned int base,unsigned int off)
{
volatile unsigned short *p = (volatile unsigned short *)(base + (off << 1));
return *p;
}
unsigned int nor_data(unsigned int off)
{
return nor_read_word(NOR_FLASH_BASE,off);
}
测试主程序
void nor_flash_test()
{
char c;
printf("\n\r");
while (1) {
printf("\n\r[s] scan nor flash\n\r");
printf("[e] erase nor flash\n\r");
printf("[w] write nor flash\n\r");
printf("[r] read nor flash\n\r");
printf("[q] quit\n\r");
printf("Enter selection: ");
c = getchar();
printf("%c\n\r",c);
switch(c) {
case 'q':
case 'Q':
return;
case 's':
case 'S':
do_scan_nor();
break;
case 'e':
case 'E':
do_erase_nor();
break;
case 'w':
case 'W':
do_write_nor();
break;
case 'r':
case 'R':
do_read_nor();
break;
default:
break;
}
printf("continue......");
getchar();
}
}
扫描设备函数
void do_scan_nor(void)
{
char str[4];
unsigned int size;
int regions,blocks,block_size;
int block_addr,region_info_base;
int i,j,cnt;
int vender,device;
/* 打印厂家ID、设备ID */
nor_cmd(0x555,0xaa); // 解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0x90); // 读ID
vender = nor_data(0); //厂家ID
device = nor_data(1); //设备ID
nor_cmd(0,0xf0); // 复位
nor_cmd(0x55, 0x98); // 进入CFI模式
str[0] = nor_data(0x10);
str[1] = nor_data(0x11);
str[2] = nor_data(0x12);
str[3] = '\0';
size = 1 << nor_data(0x27); // 计算容量
regions = nor_data(0x2c); // 计算regions个数
/* 打印容量 */
printf("nor size = 0x%x,%dM\n\r",size,size/(1024*1024));
printf("venderID = 0x%x,deviceID = 0x%x\n\r",vender,device);
/*
打印设备扇区起始地址
1个nor flash中含有1个或者多个region
1个region含有1个或者多个block(扇区)
*/
region_info_base = 0x2d;
block_addr = 0;
printf("nor blocks:\n\r");
cnt = 0;
for (i = 0; i < regions; i++) {
/* 组装2D和2E地址处的值,拼装成一个十六位的数据,这里为什么要左移呢,因为我们获取到的2个字节的数据高8位是0,
我们只要每两个字节的低字节就行了,所以将2E地址处的数据左移8位获得低字节处的数据和2D地址处的数据低字节进行拼装
*/
blocks = 1 + nor_data(region_info_base) + (nor_data(region_info_base+1)<<8);
block_size=256*(nor_data(region_info_base+2)+(nor_data(region_info_base+3)<<8));
/* 地址往后偏移4个字节,读取下一个region的信息 */
region_info_base += 4;
for (j = 0; j < blocks; j++) {
printHex(block_addr); /* 打印每个block的起始地址 */
printf(" ");
block_addr += block_size;
cnt++;
if (cnt % 5 == 0)
printf("\n\r");
}
}
/* 退回CFI模式 */
nor_cmd(0x0, 0xf0);
}
擦除块操作
void do_erase_nor()
{
unsigned int addr;
printf("please enter the address to earse: ");
addr = get_uint();
nor_cmd(0x555,0xaa); // 解锁
nor_cmd(0x2aa,0x55); // 擦除指令
nor_cmd(0x555,0x80); // earse
nor_cmd(0x555,0xaa); // 解锁
nor_cmd(0x2aa,0x55);
//nor_cmd(addr>>1,0x30); // 发出扇区地址
nor_cmd(addr,0x30);
wait_ready(addr); //等待完成
}
写操作
void do_write_nor()
{
unsigned int addr;
unsigned char str[100];
int i,j;
unsigned int val;
printf("please enter the address to write: ");
addr = get_uint();
printf("please enter the string to write: ");
gets(str);
/* str[0] str[1]构造16位的数据,写 */
i = 0;
j = 1;
while (str[i] && str[j]) {
val = str[i] + (str[j] << 8);
nor_cmd(0x555,0xaa); // 解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0xa0); //写指令
//nor_cmd(addr>>1, val);
nor_cmd(addr,val);
/* 等待烧写完成 */
wait_ready(addr);
i += 2;
j += 2;
addr += 2;
}
val = str[i];
nor_cmd(0x555,0xaa); // 解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0xa0);
//nor_cmd(addr>>1, val); //写入最后的数据
nor_cmd(addr,val);
/* 等待烧写完成 */
wait_ready(addr);
}
读函数
void do_read_nor(void)
{
unsigned int addr;
volatile unsigned char *p;
unsigned char c,str[16];
int i,j = 0;
/* 地址,长度为64字节 */
printf("please enter the address : ");
addr = get_uint();
p = (volatile unsigned char *)(addr<<1);
for (i = 0; i < 4; i++) {
for (j = 0; j < 16; j++) {
c = *p++;
str[j] = c;
printf("%02x ",c);
}
printf(" ");
for (j = 0; j < 16; j++) {
if (str[j] < 0x20 || str[j] > 0x7e)
putchar('.');
else
putchar(str[j]);
}
}
}
等待函数
void wait_ready(unsigned int addr)
{
unsigned int val;
unsigned int pre;
val = nor_data(addr >> 1);
pre = nor_data(addr >> 1);
while ((val & (1 << 6)) != (pre & (1 << 6))) {
pre = val;
val = nor_data(addr >> 1);
}
}
测试程序均已写完,最底层的函数就是根据CPU和nor flash之间的地址差来发出地址和数据,上面一层的函数就是根据上面总结的操作指令对nor flash发出
固定的指令而已,在写程序的过程中遇到了一个让我不能想通的问题,在这里记录一下:
在上面我的擦除和读写函数里往地址处写值得时候都采用了两种写法,这两种写法都是可以的
nor_cmd(0x555,0xa0); //写指令
//nor_cmd(addr>>1, val);
比如说要执行写操作,我要往0x0010 0000的地址处写入一串字符,那么我要写的这个地址可以是CPU发出的实际地址,也可以理解成是nor flash看到的实际地址
1、如果要写入的地址是CPU发出的实际地址,那么在发出写指令的时候就要把地址就行右移nor_cmd(addr>>1, val);为什么呢?
因为这个函数实际上调用的是nor_write_word函数,在nor_write_word函数里又会把CPU发出的地址进行左移,这样一来CPU发出的地址就和我们开始输入的地址是一样的,那么nor flash接收到的数据可就不是这个地址了,nor flash接收到的数据是CPU发出的地址右移一位得到的,所以在nor flash上实际写的地址并不是我们发出的地址,同理,在擦除地址的时候也要把地址就行右移一位,但是这个时候读数据操作是不需要进行移动的,因为进行读操作时是没有调用我们的更底层的发送指令的,直接将我们输入的地址发出去的,那么我们输入和写地址一样的地址进行读数据的话,读取到的是Nor flash上进行右移一位的地址处的数据,和之间写的地址就匹配上了。
2、如果要写入的地址是nor flash上的实际地址,那么在执行写操作时不需要把地址进行右移,这样的话调用nor_write_word函数把我们
发出的地址就行了左移,那么nor flash在接收的时候会把地址进行右移,看到的地址就是我们实际想要写入的地址,同理,在进行擦除操作的时候也是不需要将地址进行右移的,但是,这个时候要进行读操作的话,需要手动将要读取的地址进行左移一位,nor flash接收的右移一位的地址就和之前写和擦除的地址一致了,这样的话就和CPU实际发出的地址匹配上了,这样才能将读写的地址进行统一。
总结:这个问题在我写程序的时候困扰了我很久,一直没有想到这是怎么回事,后来慢慢的就想到了这一点,然后进行试验,结果正确。