嵌入式入门7(Nor Flash)

786 阅读8分钟

一、Nor Flash原理及硬件操作

Nor Flash支持XIP(Execute in place),可以直接执行代码,而无需复制到内存中。这是由于Nor Flash的接口与RAM完全相同,可以随机访问任意地址的数据。但是,它无法像内存一样写,因为Nor Flash一般保存着重要的数据,不能轻易被修改,写操作能通过发送命令完成。

JZ2440中Nor Flash的电路图如下:

image.png

Nor Flash有21条数据线,16条地址线,有片选信号和读写信号。
而Nand Flash只有8条线,同时用于传输命令、地址和数据。

除了接口、读写操作不一样之外,Nor Flash和Nand Flash还有以下区别:

  • 1、价格:Nor Flash比Nand Flash要贵一些。
  • 2、容量:Nand Flash一般来说容量更大。
  • 3、用途:Nor Flash用于存储程序,NAND Flash用于存储数据。
  • 4、性能:对于写和擦除操作,Nand Flash性能更好。对于读操作,两者差不多。
  • 5、可靠性:Nand Flash容易发生位翻转,需要有校验措施(如TNR)和坏块管理措施。
  • 6、可擦除次数:Nor Flash(10000 ~ 100000),Nand Flash(100000 ~ 1000000)。

Flash存储器件由擦除单元(也称为块)组成,当要写某个块时,需要确保这个块已经被擦除。Nor Flash的块大小范围为64kB、128kB,NAND Flash的块大小范围为8kB,64kB。擦/写一个Nor Flash块需4s,而擦/写一个NAND Flash块仅需2ms。Nor Flash的块太大,不仅增加了擦写时间,对于给定的写操作,Nor Flash也需要更多的擦除操作,特别是小文件,比如一个文件只有1kB,但是为了保存它却需要擦除大小为64kB—128kB的Nor Flash块。

容量相同的情况下,NAND Flash的体积更小,对于空间有严格要求的系统,NAND Flash可以节省更多空间。市场上Nor Flash的容量通常为1MB~4MB(也有32MB的Nor Flash),NAND Flash的容量为8MB~512MB。

对于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。

嵌入式Linux对Nor、NAND Flash的软件支持都很成熟。在Nor Flash上常用jffs2文件系统,而在NAND Flash常用yaffs文件系统。在更底层,用MTD驱动程序实现对它们的读、写、擦除操作,它也实现了EDC/ECC校验。

二、u-boot操作Nor Flash

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

2.1、读数据

在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.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写入AAHmw.w aaa aa
解锁往地址2AAH写入55H往地址554H写入55Hmw.w 554 55
命令往地址555H写入90H往地址AAAH写入90Hmw.w aaa 90
读厂家id读0地址得到厂家ID读0地址得到厂家IDmd.w 0 100000000: 00c2 .
读设备id读1地址得到设备ID读2地址得到设备IDmd.w 2 100000002: 2249 I"
退出读id状态任意地址写F0H任意地址写F0Hmw.w 0 f0

2.3、写Nor Flash

1、测试将数据直接写入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中,发现写入数据是有效的。

2、通过命令将数据写入到nor flash

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

操作Nor Flash上操作2440上操作U-BOOT上操作
解锁往地址555H写AAH往地址AAAH写AAHmw.w aaa aa
解锁往地址2AAH写55H往地址554H写55Hmw.w 554 55
写命令往地址555H写A0H往地址AAAH写A0Hmw.w aaa a0
写操作往地址PA写PD往地址0x100000写1234hmw.w 100000 1234

如果地址上的数据不是全0xffff,写操作会失败,此时需要先擦除扇区。

Nor Flash操作u-boot操作
555H AAHmw.w aaa aa
2AAH 55Hmw.w 554 55
555H 80Hmw.w aaa 80
555H AAHmw.w aaa aa
2AAH 55Hmw.w 554 55
SA 30H //往扇区地址写入30mw.w 100000 30

三、编程操作Nor Flash

3.1、Nor Flash的两种规范

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

  • 1、jedec探测:在内核里事先定义一个数组(Jedec_probe.c中的jedec_table),用于存放有不同厂家各个芯片的一些参数,探测的时候将 flash 的 ID 和数组里面的 ID 一一比较,如果发现相同的,就使用该数组的参数。

jedec 探测的优点就是简单,缺点是如果内核要支持的 flash 种类很多,这个数组就会很庞大。内核里面用 jedec 探测一个芯片时,是先通过发命令来获取 flash 的 ID,然后和数组比较,但是 flash.c 中连 ID 都是自己通过宏配置的。

  • 2、CFI(common flash interface)探测:通过发各种命令来读取芯片的信息,比如 ID、容量、电压等信息。

image.png

3.2、编程操作Nor Flash

1、编写操作菜单

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;
        }
    }
}

2、编写读写函数

#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);
}

3、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

运行时,注意把timer中断去掉,否则: 测试NOR Flash时进入CFI等模式时, 如果发生了中断,cpu必定读NOR Flash,那么读不到正确的指令,导致程序崩溃。

4、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

5、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");
}

6、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");
}