Nor Flash的接口属于内存类接口,意味着它能够像内存同样读,可是,它不能像内存同样写。由于Nor Flash通常保存着重要的数据,不能轻易被修改。要想写Nor Flash,只能经过发送命令完成。编程
Nor Flash原理图如图:数组
Nor Flash与Nand Flash有什么不一样?markdown
Nor Flash有21条数据线,有地址线,有片选信号和读写信号。
Nand Flash只有8条数据线,既传输命令,又传输地址,还传输数据。函数
- | Nor | NAND |
---|---|---|
接口 | 与RAM接口相同 | I/O接口 |
容量 | 小 | 大 |
读 | 简单 | 复杂 |
写 | 发出特定命令 | 复杂 |
XIP(代码能够直接运行) | Yes | no |
性能(擦除) | 很是慢(5s) | 快(3ms) |
性能 | 写慢、读快 | 写快、读快 |
可靠性 | 较高,位反转的比例小于NAND Flash的10% | 容易发生位反转,须要有校验措施(如TNR)和坏块管理措施 |
可擦除次数 | 10000 ~ 100000 | 100000 ~ 1000000 |
生命周期 | 低于NAND Flash的10% | 是Nor Flash的10倍以上 |
易用性 | 容易 | 复杂 |
主要用途 | 经常使用于保存关键代码和数据 | 用于保存数据 |
价格 | 高 | 低 |
Nor Flash支持XIP(Execute in place),即代码能够直接在Nor Flash上执行,无需复制到内存中。这是因为NorF lash的接口与RAM彻底相同,能够随机访问任意地址的数据。Nor Flash进行读操做的效率很是高,可是擦除和写操做的效率很低,另外,Nor Flash的容量通常比较小。NAND Flash进行擦除和写操做的效率更高,而且容量更大。通常而言,Nor Flash用于存储程序,NAND Flash用于存储数据。基于NAND Flash的设备一般也要搭配Nor Flash以存储程字。oop
Flash存储器件由擦除单元(也称为块)组成,当要写某个块时,须要确保这个块己经被擦除。Nor Flash的块大小范围为64kB、128kB:NAND Flash的块大小范围为8kB,64kB,擦/写一个Nor Flash块需4s,而擦/写一个NAND Flash块仅需2ms。Nor Flash的块太大,不只增长了擦写时间,对于给定的写操做,Nor Flash也须要更多的擦除操做——特别是小文件,好比一个文件只有IkB,可是为了保存它却须要擦除大小为64kB—128kB的Nor Flash块。性能
Nor Flash的接口与RAM彻底相同,能够随意访问任意地址的数据。而NAND Flash的 接口仅仅包含几个I/O引脚,须要串行地访问。NAND Flash通常以512字节为单位进行读写。这使得Nor Flash适合于运行程序,而NAND Flash更适合于存储数据。测试
容量相同的状况下,NAND Flash的体积更小,对于空间有严格要求的系统,NAND Flash能够节省更多空间。市场上Nor Flash的容量一般为1MB~4MB(也有32MB的Nor Flash),NAND Flash的容量为8MB~512MB。容量的差异也使得Nor Flash多用于存储程序,NAND Flash多用于存储数据。ui
对于Flash存储器件的可靠性须要考虑3点:位反转、坏块和可擦除次数。全部Flash器件都遭遇位反转的问题:因为Flash固有的电器特性,在读写数据过程当中,偶然会产生一位或几位数据错误(这种几率很低),而NAND Flash出现的几率远大于Nor Flash,当位反转发生在关键的代码、数据上时,有可能致使系统崩溃。当仅仅是报告位反转,从新读取便可;若是确实发生了位反转,则必须有相应的错误检测/恢复措施。在NAND Flash上发生位反转的几率高,推荐使用EDC/ECC进行错误检测和恢复。NAND Flash上面会有坏块随机分布在使用前须要将坏块扫描出来,确保再也不使用它们,不然会使产品含有严重的故障。NAND Flash每块的可擦除次数一般在100000次左右,是Nor Flash的10倍。另外,由于NAND Flash的块大小一般是NorF lash的1/8,因此NAND Flash的寿命远远超过Nor Flash。spa
嵌入式Linux对Nor、NAND Flash的软件支持都很成熟。在Nor Flash上经常使用jffs2文件系统,而在NAND Flash经常使用yaffs文件系统。在更底层,有MTD驱动程序实现对它们的读、写、擦除操仵,它也实现了EDC/ECC校验。命令行
将u-boot烧写到Nor Flash,而后设置为Nor启动,进入u-boot命令行界面。
在u-boot上执行:
md.b 0
复制代码
返回的结果和咱们烧入的bin文件彻底同样:
OpenJTAG> md.b 0
00000000: 17 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 ................
00000010: 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 ................
00000020: 60 01 f8 33 c0 01 f8 33 20 02 f8 33 80 02 f8 33 `..3...3 ..3...3
00000030: e0 02 f8 33 00 04 f8 33 20 04 f8 33 ef be ad de ...3...3 ..3....
复制代码
因而可知,u-boot能够像读内存同样来读。
jz2440采用MX29LV160DBTI-70G
的Nor Flash,其芯片手册包含有一个命令表格,如图:
这个命令表格清晰的介绍了任何操做Nor Flash:
如今咱们经过读id操做来说解如何经过u-boot操做Nor Flash。
这里须要注意的是:因为JZ2440使用的是16位Nor Flash芯片,因此JZ2440的A1接在Nor Flash的A0上。也就意味着:JZ2440发出的地址右移一位才是Nor Flash收到的地址
。好比:2440发出(555H<<1)
地址,Nor Flash才能收到555H
这个地址。
因此,整个读id操做以下:
操做 | Nor Flash的操做 | 2440的操做 | u-boot发出的命令 | 结果 |
---|---|---|---|---|
解锁 | 往地址555H写入AAH | 往地址AAAH写入AAH | mw.w aaa aa | |
解锁 | 往地址2AAH写入55H | 往地址554H写入55H | mw.w 554 55 | |
命令 | 往地址555H写入90H | 往地址AAAH写入90H | mw.w aaa 90 | |
读厂家id | 读0地址获得厂家ID | 读0地址获得厂家ID | md.w 0 1 | 00000000: 00c2 . |
读设备id | 读1地址获得设备ID | 读2地址获得设备ID | md.w 2 1 | 00000002: 2249 I" |
退出读id状态 | 任意地址写F0H | 任意地址写F0H | mw.w 0 f0 |
打印当前100000地址上的数据:
OpenJTAG> md.w 100000 1
00100000:ffff..
复制代码
在100000地址上直接写入1234:
OpenJTAG> mw.w 100000 1234
复制代码
再从新打印:
OpenJTAG> md.w 100000 1
00100000:ffff
复制代码
能够发现,u-boot直接写入数据到nor flash是无效的,若是将地址改成0x30000000,即SDRAM中,发现写入数据是有效的。
写数据以前必须保证,要写的地址是擦除的。
Nor Flash上操做写操做 | 2440上操做写操做 | U-BOOT上操做写操做 |
---|---|---|
往地址555H写AAH(解锁) | 往地址AAAH写AAH(解锁) | mw.w aaa aa |
往地址2AAH写55H(解锁) | 往地址554H写55H(解锁) | mw.w 554 55 |
往地址555H写A0H | 往地址AAAH写A0H | mw.w aaa a0 |
往地址PA写PD | 往地址0x100000写1234h | mw.w 100000 1234 |
若是地址上的数据不是全0xffff,写操做会失败,此时须要先擦除扇区。
Nor Flash操做 | u-boot操做 |
---|---|
555H AAH | mw.w aaa aa |
2AAH 55H | mw.w 554 55 |
555H 80H | mw.w aaa 80 |
555H AAH | mw.w aaa aa |
2AAH 55H | mw.w 554 55 |
SA 30H //往扇区地址写入30 | mw.w 100000 30 |
一般内核里面要识别一个 Nor Flash 有两种方法:
一种是 jedec 探测,就是在内核里面事先定义一个数组,该数组里面放有不一样厂家各个芯片的一些参数,探测的时候将 flash 的 ID 和数组里面的 ID 一一比较,若是发现相同的,就使用该数组的参数。 jedec 探测的优势就是简单,缺点是若是内核要支持的 flash 种类不少,这个数组就会很庞大。内核里面用 jedec 探测一个芯片时,是先经过发命令来获取 flash 的 ID,而后和数组比较,可是 flash.c 中连 ID 都是本身经过宏配置的。 Jedec_probe.c中,jedec_table
一种是 CFI(common flash interface)探测,就是直接发各类命令来读取芯片的信息,好比 ID、容量等,芯片自己就包含了电压有多大,容量有有多少等信息。
nor_flash.c
#include "my_printf.h"
#include "string_utils.h"
void nor_flash_test() {
while(1)
{
// 打印菜单,供咱们选择测试内容
printf("[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:");
char c = getchar();
printf("%c\n\r\n\r",c);
switch(c)
{
case 'q':
case 'Q':
return;
break;
case 's':
case 'S':
do_scan_nor_flash();
break;
case 'e':
case 'E':
do_erase_nor_flash();
break;
case 'w':
case 'W':
do_write_nor_flash();
break;
case 'r':
case 'R':
do_read_nor_flash();
break;
default:
break;
}
}
}
复制代码
#define NOR_FLASH_BASE 0
void nor_write_word(unsigned int base, unsigned int offset, unsigned int 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_BASE, 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_data(unsigned int offset)
{
return nor_read_word(NOR_FLASH_BASE, offset);
}
复制代码
[2C]表示有多少个region。
[2E, 2D]表示erase region 1有多少个sectors(扇区),[30, 2F]表示erase region 1每一个扇区的大小。
/* 进入Nor Flash的CFI模式 * 读取各种信息 */
void do_scan_nor_flash() {
char str[4];
//打印厂家ID、设备ID
nor_cmd(0x555, 0xaa); //解锁
nor_cmd(0x2aa, 0x55);
nor_cmd(0x555, 0x90); //read id
int vendor = nor_data(0);
int device = nor_data(1);
nor_cmd(0, 0xf0); //reset
nor_cmd(0x55, 0x98); //进入CFI模式
str[0] = nor_data(0x10);
str[1] = nor_data(0x11);
str[2] = nor_data(0x12);
str[3] = '\0';
printf("str = %s\n\r", str);
//打印容量
unsigned int size = 1 << (nor_data(0x27));
printf("vendor=0x%x, device=0x%x, nor size = 0x%x, %dM\n\r", vendor, device, size, size / (1024 * 1024));
//打印各扇区起始位置
/** * 名词解释: * erase block region:里面含义1个或多个block,它们的大小同样 * 一个nor flash含义1个或多个region * 一个region含有1个或多个block(扇区) * * Erase block region information: * 前2字节+1 表示该region有多少个block * 后2字节*256 表示block的大小 */
unsigned int regions = nor_data(0x2c);
int i, j;
int region_info_base = 0x2d;
int block_addr = 0;
int count = 0;
printf("Block/Sector start address:\n\r");
for (i = 0;i < regions;i++) {
int blocks = (nor_data(region_info_base) + (nor_data(region_info_base + 1) << 8)) + 1;
int block_size = (nor_data(region_info_base + 2) + (nor_data(region_info_base + 3) << 8)) * 256;
region_info_base += 4;
for (j = 0;j < blocks; j++) {
//打印每一个block的起始地址
printHex(block_addr);
printf(" ");
block_addr += block_size;
count++;
if (count % 5 == 0) {
printf("\n\r");
}
}
}
//退出CFI模式
nor_cmd(0x0, 0xf0);
}
复制代码
解决ID不对的问题:
查看反汇编文件,发现它将一次性写入2个字节,变成每次只写一个字节。 解决方案,编译时,增长-march=armv4
,表示使用armv4指令集。
str = QRY
vendor=0xc2, device=0x2249, nor size = 0x200000, 2M
Block/Sector start address:
0x00000000 0x00004000 0x00006000 0x00008000 0x00010000
0x00020000 0x00030000 0x00040000 0x00050000 0x00060000
0x00070000 0x00080000 0x00090000 0x000A0000 0x000B0000
0x000C0000 0x000D0000 0x000E0000 0x000F0000 0x00100000
0x00110000 0x00120000 0x00130000 0x00140000 0x00150000
0x00160000 0x00170000 0x00180000 0x00190000 0x001A0000
0x001B0000 0x001C0000 0x001D0000 0x001E0000 0x001F0000
复制代码
main函数代码以下所示。把timer中断去掉,不然: 测试NOR Flash时进入CFI等模式时, 若是发生了中断,cpu一定读NOR Flash,那么读不到正确的指令,致使程序崩溃。
void do_read_nor_flash() {
int i, j;
unsigned char c;
unsigned char str[16];
printf("Enter the address to read: ");
unsigned char* addr = (unsigned char*)get_uint();
printf("Data: \n\r");
for (i = 0; i < 4; i++)
{
//打印数值
for (j = 0; j < 16; j++)
{
c = *addr++;
printf("%02x ", c);
str[j] = c;
}
printf(" ;");
//打印字符
for (j = 0; j < 16; j++)
{
if (str[j] < 0x20 || str[j]>0x7e) //不可见字符
putchar('.');
else
putchar(str[j]);
}
printf("\n\r");
}
}
复制代码
运行,输入地址0
Data:
27 0 0 ea 14 f0 9f e5 14 f0 9f e5 4d 0 0 ea ;'...........M...
4c 0 0 ea 4b 0 0 ea 8 f0 9f e5 49 0 0 ea ;L...K.......I...
2c 0 0 30 70 0 0 30 90 0 0 30 d d3 a0 e3 ;,..0p..0...0....
ff 5f 2d e9 0 0 f e1 c 11 9f e5 aa 1 0 eb ;._-.............
复制代码
与bin文件中内容一致。
void wait_ready(unsigned int addr) {
unsigned int val;
unsigned int pre;
pre = nor_dat(addr >> 1);
val = nor_dat(addr >> 1);
while ((val & (1 << 6)) != (pre & (1 << 6)))
{
pre = val;
val = nor_dat(addr >> 1);
}
}
void do_erase_nor_flash() {
unsigned int addr;
printf("Enter the address of sector to erase: ");
addr = get_uint();
printf("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);
printf("erase completed\n\r");
}
复制代码
void do_write_nor_flash() {
printf("Enter the address of sector to write: ");
unsigned int addr = get_uint();
unsigned char str[100];
printf("Enter the string to write: ");
gets(str);
printf("writing ...\n\r");
int i = 0, j = 1;
unsigned int val;
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); /* 烧写 */
//等待烧写完成:读数据Q6无变化时表示结束
wait_ready(addr);
i += 2;
j += 2;
addr += 2;
}
val = str[i];
//烧写
nor_cmd(0x555, 0xaa); /* 解锁 */
nor_cmd(0x2aa, 0x55);
nor_cmd(0x555, 0xa0); /* program */
nor_cmd(addr >> 1, val); /* 烧写 */
//等待烧写完成:读数据Q6无变化时表示结束
wait_ready(addr);
printf("write completed\n\r");
}
复制代码