NOR FLASH的一些重要信息(如内存大小、SECTOR数、运行电压和定时信息等)都是可以通过进入CFI查询模式来进行读取的;而设备ID、厂家ID等信息是需要在自动选择模式下来读取。
在这里我们通过自动选择模式来读取并显示JZ2440所用NOR FLASH的设备ID、厂家ID;通过CFI查询模式来读取并显示内存大小和每个SECTOR的起始地址。
对于16位的NOR FLASH需要依次向555H 写 AAH、2AAH 写 55H 和 555H 写 90H
来进入自动选择模式,随后的一个周期进行读取数据,操作完自动选择模式后必须要RESET,
可以向任意地址写F0H
来实现RESET;通过向55H 写 98H
来进入CFI查询模式以读取需要的数据,操作完CFI查询模式后必须RESET回之前的模式,否则不能执行除CFI查询和RESET之外的任何操作
。
以下是显示JZ2440上NOR FLASH重要信息的部分代码。
void do_scan_nor_flash(void)
{
char str[4];
unsigned int size; /* 内存容量 */
int regions, region_info_base;
int blocks,block_size, block_addr;
int i, j, cnt = 0;
int vendor, device; /* vendor:厂家ID;device:设备ID */
/* 打印厂家ID、设备ID */
nor_cmd(0x555, 0xaa); /* 解锁 */
nor_cmd(0x2aa, 0x55);
nor_cmd(0x555, 0x90); /* read id */
vendor = nor_dat(0); /* 厂家ID */
device = nor_dat(1); /* 设备ID */
nor_cmd(0, 0xf0); /* reset */
nor_cmd(0x55, 0x98); /* 进入CFI模式 */
str[0] = nor_dat(0x10);
str[1] = nor_dat(0x11);
str[2] = nor_dat(0x12);
str[3] = '\0';
printf("str = %s\n\r", str); /* Query-unique ASCII string "QRY" */
/* 打印容量 */
size = 1 << (nor_dat(0x27));
printf("vendor = 0x%x, device = 0x%x, size = 0x%x, %dMB\n\r", vendor, device, size, size/(1024*1024));
/* 打印各个扇区的起始地址
/* Erase block region information:
* 前两字节 + 1 : 表示该region中block的数目
* 后两字节 * 256 : 表示该region中每个block的大小
*/
regions = nor_dat(0x2c); /* region数 */
region_info_base = 0x2d; /* region的基址 */
block_addr = 0;
printf("Block/Sector start Address:\n\r");
for (i = 0; i < regions; i++)
{
blocks = 1 + nor_dat(region_info_base) + (nor_dat(region_info_base + 1) << 8);
block_size = 256 * (nor_dat(region_info_base + 2) + (nor_dat(region_info_base + 3) << 8));
//printf("\n\rregion %d , blocks = %d, block_size = 0x%08x, block addr = 0x%08x\n\r", i, blocks, block_size, block_addr);
region_info_base += 4;
for (j = 0; j < blocks; j++)
{
/* 打印每个block的起始地址 */
printHex(block_addr);
printf(" ");
cnt++;
block_addr += block_size;
if (cnt % 5 == 0) /* 每5个block的起始地址打印一行 */
{
printf("\n\r");
}
}
}
printf("\n\r");
nor_cmd(0, 0xf0); /* 退出CFI模式 */
}
在读NOR FLASH时我们需要先向NOR FLASH发出我们想读的地址,然后以每行打印16byte打印4行的形式来打印NOR FLASH的数据(同时打印数值和字符),具体代码如下。
void do_read_nor_flash(void)
{
unsigned int addr;
volatile unsigned char *p;
int i,j;
unsigned char str[16];
/* 获得地址 */
printf("Enter the address of sector to read: ");
addr = get_uint(); /* 得到 unsigned int 型数据 */
p = (volatile unsigned char *)addr; /* 以字节进行操作 */
printf("Data: \n\r");
/* 长度固定为64 */
for (i = 0; i < 4; i++)
{
for (j = 0; j < 16; j++)
{
/* 先打印数值 */
str[j] = *p++;
printf("%02x ", str[j]);
}
printf(" ; ");
for (j = 0; j < 16; j++)
{
/* 后打印字符 */
if (str[j] < 0x20 || str[j] > 0x7e) /* 不可视字符 */
{
putchar('.');
}
else
{
putchar(str[j]);
}
}
printf("\n\r");
}
}
NOR FLASH可以直接进行读,但是不能直接进行写,如果要向NOR FLASH中写入数据必须先对相应地址进行擦除。NOR FLASH支持Sector Erase
和Chip Erase
,这里我们对扇区进行擦除,实现代码如下。
void do_erase_nor_flash(void)
{
unsigned int addr;
/* 获得地址 */
printf("Enter the address of sector to erase: ");
addr = get_uint();
/* 擦除数据 */
puts("erasing...\n\r");
nor_cmd(0x555, 0xaa); /* 解锁 */
nor_cmd(0x2aa, 0x55);
nor_cmd(0x555, 0x80); /* erase sector */
nor_cmd(0x555, 0xaa); /* 解锁 */
nor_cmd(0x2aa, 0x55);
nor_cmd(addr >> 1, 0x30); /* 发出扇区地址 */
/* 等待擦除完成 */
wait_ready(addr);
}
其中之所以要addr >> 1
是因为我们的NOR FLASH地址线的0bit是和CPU地址线的1bit相连的(想了解为何请看我之前的博文S3C2440内存控制器与SDRAM),而我们在nor_cmd()(具体实现看下文)
中已经add << 1
了,故这里要移回来,这样NOR FLASH操作的地址才会是我们想要的。
在对NOR FLASH执行写操作时,我们要先发出想的地址,然后再发出我们要写的数据,以下是实现代码部分。
void do_write_nor_flash(void)
{
unsigned int addr;
unsigned char str[100];
unsigned int val;
int i, j;
/* 获得地址 */
printf("Enter the address of sector to write: ");
addr = get_uint();
/* 获得数据 */
printf("Enter the string to write: ");
gets(str);
printf("writing...\n\r");
/* 构造16bit数据
* str[0], str[1]==>16bit
* str[2], str[3]==>16bit
*/
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); /* program */
nor_cmd(addr>>1, val);
wait_ready(addr); /* 等待烧写完成 */
i += 2;
j += 2;
addr += 2;
}
val = str[i]; /* 当while()不满足时,str[i]中依然可能有值,也需要写入 */
/* 烧写 */
nor_cmd(0x555, 0xaa); /* 解锁 */
nor_cmd(0x2aa, 0x55);
nor_cmd(0x555, 0xa0); /* program */
nor_cmd(addr>>1, val);
wait_ready(addr); /* 等待烧写完成 */
}
【声明】
上文中部分依赖函数如下
#define NOR_FLASH_BEASE 0 /* jz2440, nor flash->nGCS0, base addr = 0 */
void nor_write_word(unsigned int base, unsigned int offset, unsigned int val) /* 写字节, base : 基址;offset : 偏移地址;val : 写的值 */
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
*p = val;
}
void nor_cmd(unsigned int offset, unsigned int cmd) /* 写命令 */
{
nor_write_word(NOR_FLASH_BEASE, offset, cmd);
}
unsigned int nor_read_word(unsigned int base, unsigned int offset) /* 读字节 */
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
return *p;
}
unsigned int nor_dat(unsigned int offset) /* 读数据 */
{
return nor_read_word(NOR_FLASH_BEASE, offset);
}
void wait_ready(unsigned int addr) /* 等待操作完成 */
{
unsigned int pre, val;
pre = nor_dat(addr>>1);
val = nor_dat(addr>>1);
while (val & (1<<6) != pre & (1<<6)) /* 当读到数据的第6bit(Q6)无变化时,操作完成 */
{
pre = val; /* 更新先前值 */
val = nor_dat(addr>>1);
}
}
注:在编译程序时需加上: -march=armv4
, 否则 volatile unsigned short *p = xxx; *p = val;
会被拆分成2个strb
操作(这会导致程序出错)。