嵌入式入门7(Nor Flash)

1、Nor Flash原理及硬件操做

Nor Flash的接口属于内存类接口,意味着它能够像内存同样读,可是,它不能像内存同样写。由于Nor Flash通常保存着重要的数据,不能轻易被修改。要想写Nor Flash,只能经过发送命令完成。编程

Nor Flash原理图如图:数组

image.png

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校验。命令行

2、u-boot操做Nor Flash

将u-boot烧写到Nor Flash,而后设置为Nor启动,进入u-boot命令行界面。

2.一、读数据

在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能够像读内存同样来读。

2.二、读Nor Flash的ID

jz2440采用MX29LV160DBTI-70G的Nor Flash,其芯片手册包含有一个命令表格,如图:

image.png

这个命令表格清晰的介绍了任何操做Nor Flash:

  • 读取芯片vendor id和device id
  • 读内存(直接读取)和写内存
  • 擦除芯片和扇区
  • 进入CFI模式
  • 退出命令模式(往任意地址写入0xf0)

如今咱们经过读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

2.三、写Nor Flash

一、测试将数据直接写入nor flash

打印当前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

写数据以前必须保证,要写的地址是擦除的。

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

3、编程操做Nor Flash

3.一、Nor Flash的两种规范

一般内核里面要识别一个 Nor Flash 有两种方法:

一种是 jedec 探测,就是在内核里面事先定义一个数组,该数组里面放有不一样厂家各个芯片的一些参数,探测的时候将 flash 的 ID 和数组里面的 ID 一一比较,若是发现相同的,就使用该数组的参数。 jedec 探测的优势就是简单,缺点是若是内核要支持的 flash 种类不少,这个数组就会很庞大。内核里面用 jedec 探测一个芯片时,是先经过发命令来获取 flash 的 ID,而后和数组比较,可是 flash.c 中连 ID 都是本身经过宏配置的。 Jedec_probe.c中,jedec_table

一种是 CFI(common flash interface)探测,就是直接发各类命令来读取芯片的信息,好比 ID、容量等,芯片自己就包含了电压有多大,容量有有多少等信息。

image.png

3.二、编程操做Nor Flash

一、编写操做菜单

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);
}
复制代码

三、Nor Flash查询

image.png

[2C]表示有多少个region。

[2E, 2D]表示erase region 1有多少个sectors(扇区),[30, 2F]表示erase region 1每一个扇区的大小。

参考CFI publication 100规范

image.png

/* 进入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不对的问题:

image.png

查看反汇编文件,发现它将一次性写入2个字节,变成每次只写一个字节。 解决方案,编译时,增长-march=armv4,表示使用armv4指令集。

image.png

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,那么读不到正确的指令,致使程序崩溃。

四、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文件中内容一致。

image.png

五、Nor Flash擦除

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");
}
复制代码

六、Nor Flash写入

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");
}
复制代码