title: NorFlash 学习

tags: ARM

date: 2018-10-19 18:31:59

NorFlash 学习

NOR/NAND Flash的差别

NOR NAND
容量 1~32MB 16~512MB
XIP 可执行程序 Yes(因为是内存接口), 可以随机访问任意地址的数据 No
擦除 非常慢(5s) 快(3ms)
可靠性 比较高,位反转的比例小于NAND Flash 的10% 比较低:,位反转比较常见,必需有校验措施,比如“1-4 bit EDC/ECC”;必须有坏块管理措施
可擦除次数 10000~100000 100000~1000000
生命周期 低于NAND Flash的10% 是NOR Flash的10倍以上
接口 RAM接口相同 I/O接口
访问方法 随机访问 顺序访问
易用性 容易 复杂
主要用途 常用于保存代码和关键数据 用于保存数据
价格
  • Nor Flash的块大小范围为64kB、128kB:NAND Flash的块大小范围为8kB,64kB,擦/写一个Nor Flash块需4s,而擦/写一个NAND Flash块仅需2ms。
  • NAND Flash一般以512字节为单位进行读写。这使得Nor Flash适合于运行程序,而NAND Flash更适合于存储数据
  • 容量相同的情况下,NAND Flash的体积更小.Nor Flash的容量通常为IMB~4MB(也有32MB的Nor Flash),NAND Flash的容量为8MB~512MB。
  • 在Nor Flash上常用jffs2文 件系统,而在NAND Flash常用yaffs文件系统。在更底层,有MTD驱动程序实现对它们的读、写、擦除操仵,它也实现了EDC/ECC校验。
  • 这里用的nor里面有4个bank,每个bank里面有若干block(擦除大小),不同bank里面的block可能不一样的

引脚接口

更多信息参考 2440内存管理

这里NorFlash是16位的,所以地址线忽略A0

命令

所谓命令是什么意思呢?

  • Read MODE,就是先发送地址,然后就有数据得到,和正常的读内存是一样的

  • Reset Mode,往任意地址写0xF0都退出特殊模式

  • 读ID,NOR Flash 分为1字节和2字节模式,所以有两种情况的操作指令.板载是2字节的.操作如下:

    1. 发送地址555,发送数据AA, 0x555*2=0xaaa
    2. 发送地址2AA,发送数据55 0x2AA*2=0x554
    3. 发送地址555,发送数据90 0x555*2=0xaaa
    4. 发送地址00,收到数据C2

但是板载的MCU的A1接到Nor的A0,所以也就是Nor获得地址X,实际MCU需要发送地址X<<1

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
读0地址得到厂家ID(C2H) 读0地址得到厂家ID(C2H) md.w 0 1 (1:表示读一次)
读1地址得到设备ID(22DAH或225BH) 读2地址得到设备ID 2249 md.w 2 1 (1:表示读一次)
退出读ID状态:给任意地址写F0H就可以了 退出读ID状态:给任意地址写F0H就可以了 mw.w 0 f0

注意,这个uboot应该在NOR上启动,因为NAND启动的时候,看不到NORFLASH

Device ID : MX29LV160DT: 22C4; MX29LV160DB: 2249.

OpenJTAG> mw.w 0 f0
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w aaa 90
OpenJTAG> md.w 0 1
00000000: 00c2 ..
OpenJTAG> md.w 2 1
00000002: 2249 I"

参数识别(CFI)

CFI(common flash interface)探测,就是直接发各种命令来读取芯片的信息,比如 ID、容量等,芯片本身就包含了电压有多大,容量有有多少等信息.其实就是先用55地址写98,然后读参数.

一般情况下先去读取是否支持CFI,也就是

  1. 写98H到55H
  2. 查询0x10,0x11,0x12是否为QRY字符串
  3. 如果是,那么支持CFI接口
Nor Flash上操作cfi 2440上操作cfi U-BOOT上操作cfi
往55H地址写入98H 往AAH地址写入98H mw.w aa 98
读地址10H得到0051 读地址20H得到0051 md.w 20 1
读地址11H得到0052 读地址22H得到0052 md.w 22 1
读地址12H得到0059 读地址24H得到0059 md.w 24 1
读地址27H得到容量 读地址4EH得到容量 md.w 4e 1

下图中读取地址10,读取到0x0051='Q',比如读设备大小地址0x27,会读到0x15=2**15/1024/1024=2M,具体的一些参数参考标准文档CFI100

识别存储信息,其中具体的信息要搜索文档CFI100

可以看到有4个bank reggion,region里又若干sector.这个是擦除最小单位了.根据计算如下.其中2D为低位,2E为高位.block=[2E:2D]+1,

blocks sector size
bank1 1 256*0x0040/1024=16k
bank2 2 256*0x0020/1024=8k
bank3 1 256*0x0080/1024=32k
bank3 0x1E+1=31 256*0x0100/1024=64k
总计 35个block 空间大小=16+2*8+32+64*31=2048k=2M

CFI描述如下

备注手册里另一张图Table 1-1. MX29LV160DT SECTOR ARCHITECTURE好像是倒着来的扇区排序

写数据

NorFlash也是一种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

命令参考,往1M的地方写,地址需要*2,也就是0x20,0000


OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w aaa 80
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w 200000 30
OpenJTAG> mw.w 0 f0
OpenJTAG> md.w 200000 1
00200000: ffff .. //读取
OpenJTAG> mw.w aaa aa
OpenJTAG> mw.w 554 55
OpenJTAG> mw.w aaa a0
OpenJTAG> mw.w 200000 1234
OpenJTAG> md.w 200000 1
00200000: 1234 4. //接下来不能直接写了 又需要重新来执行命令
OpenJTAG> mw.w 200004 5555
OpenJTAG> md.w 200004
00200004: ffff ..

程序设计

注意 板载的是16位位宽,地址线是CPU的A1接到NOR的A0,所以MCU地址=NOR指令地址*2.

调试代码 的时候发现工具MobaXterm_Personal_10.4有时候无法接受串口输入,重启下软件串口

  • 时序设置: 参考内存管理.md

  • 基本的读写操作

     #define NOR_BASE_BANK   0
    
    /**
    * @brief 在基于Nor cmd_addr的位置写入 val
    * 比如在55H的地方写入x,也就是地址信号发出55,
    * 对应实际的地址线为55<<1 地址线 加上bank的基地址,
    * 所以cpu实际的地址是 base+(55<<1)
    * @param base
    * @param addr
    * @param val
    */
    void NorWriteU16(uint16_t base,uint16_t addr, uint16_t val)
    {
    volatile uint16_t* p= (volatile uint16_t* )((addr<<1)+base);
    *p=val;
    }
    /**
    * @brief 读取2字节
    *
    * @param base
    * @param addr
    * @param val
    */
    uint16_t NorReadU16(uint16_t base,uint16_t addr)
    {
    volatile uint16_t* p= (volatile uint16_t* )((addr<<1)+base);
    return *p;
    } /**
    * @brief CMD写入16位数据
    * @param addr cmd地址
    * @param val 写入的值
    */
    void NorFlashCMDPut(uint16_t addr,uint16_t val )
    {
    NorWriteU16(NOR_BASE_BANK,addr,val);
    }
    /**
    * @brief CMD读取Nor的16位数据
    *
    * @param addr
    * @param val
    * @return uint16_t
    */
    uint16_t NorFlashCMDGet(uint16_t addr)
    {
    return NorReadU16(NOR_BASE_BANK,addr);
    }
  • 识别CFI

    void NorFlashInfo(void)
    {
    // 进入CFI模式,INTERFACE (CFI) MODE
    NorFlashCMDPut(0x55,0x98);
    //开始读取数据
    uint8_t buf[10]={0,0,0,0,0,0,0,0,0};
    //尝试读取“QRY”
    buf[0]=(uint8_t)NorFlashCMDGet(0x10);
    buf[1]=(uint8_t)NorFlashCMDGet(0x11);
    buf[2]=(uint8_t)NorFlashCMDGet(0x12);
    buf[3]=(uint8_t)'\0';
    printf("get 0x20,0x22,0x24=%s\r\n",buf);
    printf("end\r\n");
    NorFlashCMDPut(0, 0xf0);
    }
  • NOR的擦除与写

    注意: NOR的物理地址是需要CPU地址>>1的,所以操作地址的时候之前的底层函数已经使用了cpu地址>>1

    但是,Nor是16位位宽的,也就是如下排列的

    16位宽 8位宽
    00 00,01
    01 02,03
    02 04,05
    03 06,17

    我们需要将8位位宽的地址转换为16位位宽的地址,也就是说addr>>1.

    void do_write_nor_flash(void)
    {
    unsigned int addr;
    unsigned char str[100];
    int i, j;
    unsigned int val; /* 获得地址 */
    printf("Enter the address of sector to write: ");
    addr = get_uint(); printf("Enter the string to write: ");
    gets(str); printf("writing ...\n\r"); /* 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);
    //这个是重点------------------------------------------------- /* 等待烧写完成 : 读数据, 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);
    }

    FAQ:

    在老师的视频中,写NOR的具体地址时,nor_cmd(addr>>1, val); 这里地址右移1位,老师的解释是地址线A1->A0的缘故.但是我觉得不对.

    因为 nor_cmd函数已经将地址转换为nor视角的地址了,也就是说我想写nor中的x地址,我们应该直接操作地址x即可.

    但是测试之后发现代码确实跑的没问题?那么我哪里理解错了呢?

    后来想到 Nor是16位位宽的,也就是说Nor中的0,其实对应了存储单元[0,1],

    结论: 所以这里的地址>>1,并不是因为地址线偏移(其实也是因为地址线,因为是16位,所以偏移1位),但是从函数的角度来说,实际是因为nor的地址是16位宽的,我们需要转换为8位位宽的 addr>>1 变成16位位宽的地址

    后面的测试了下往 addr=1里面写12,实际是在0,1里面写,往addr=2里面写12,实际是往2里面写,往addr=3里面写,也是在往2里面写

    参考链接

  • 判断是否忙(DQ6)

    • DQ7:Data# Polling bit,DQ7在编程时的状态变化.

      在编程过程中从正在编程的地址中读出的数据的DQ7为要写进数据的补码.比如写进的数据为0x0000,及输进的DQ7为'0',则在编程中读出的数据为'1';当编程完成时读出的数据又变回输进的数据即'0'. 在擦除过程中DQ7输出为'0';擦除完成后输出为'1';留意读取的地址必须是擦除范围内的地址.

      RY/BY#:高电平表示'停当',低电平表示'忙'.

    • DQ6:轮转位1(Toggle Bit 1).

      在编程和擦除期间,读任意地址都会导致DQ6的轮转(0,1间相互变换)当操纵完成后,DQ6停止转换.

      DQ2:轮转位2(Toggle Bit 2).当某个扇区被选中擦除时,读有效地址(地址都在擦除的扇区范围内)会导致DQ2的轮转.

      留意:DQ2只能判定一个特定的扇区是否被选中擦除.但不能区分这个快是否正在擦除中或者正处于擦除暂停状态.相比之下,DQ6可以区分NorFLASH是否处于擦除中或者擦除状态,但不能区分哪个快被选中擦除.因此需要这2个位来确定扇区和模式状态信息.

    • DQ5: 超时位(Exceeded Timing Limits),当编程或擦除操纵超过了一个特定内部脉冲计数是DQ5=1;这表明操纵失败.当编程时把'0'改为'1'就会导致DQ5=1,由于只有擦除擦做才能把'0'改为'1'.当错误发生后需要执行复位命令(见图1-1)才能返回到读数据状态.

    • DQ3: (扇区擦除计时位)Sector Erase Timer,只在扇区擦除指令时起作用.当擦除指令真正开始工作是DQ3=1,此时输进的命令(除擦除暂停命令外)都被忽略.DQ3=0,是可以添加附加的扇区用于多扇区擦除.

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

程序优化

  1. 老师的写操作并没有去判断地址是否是高低地址,也就是写0地址和写1地址都是写在0地址2个字节,实际上如果写1地址应该先读取0地址,再写1地址,这里会涉及到擦除? 如果都先擦除就没有这个问题

其他设计点

  1. 读写NORFLASH的时候需要关闭中断

    因为我们使用NOR启动的时候,cpu执行中断的时候会跳回到0地址,也就是NOR中去读取指令,这个时候如果在操作NOR,很明显取址有问题了,会死机等.之前串口有时候没输出死机可能就是这个原因了.

  2. nor是16位宽的,所以在擦除和写nor的时候,实际地址需要>>1才是nor要接受的实际地址,具体分析见程序设计章节中的写操作

  3. Makefile中加入选项-march=armv4否则在操作下面函数时,16位变量被处理为2个8位变量.网上搜索是可能gcc 考虑地址对齐的问题

    	volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
    *p = val; 30000f3c: e5c23000 strb r3, [r2]
    %.o : %.c
    arm-linux-gcc -march=armv4 -c -o $@ $< %.o : %.S
    arm-linux-gcc -march=armv4 -c -o $@ $<

    https://blog.csdn.net/klutz120215/article/details/7277130

  4. 程序应该烧录在NOR上,因为nand启动的时候,片内ram是0地址,mcu无法访问nor

最新文章

  1. C语言操作文件
  2. Scrum 项目7.0--软件工程
  3. Activiti 学习资料收集
  4. Vim折叠模式设置
  5. android 百度地图定位开发2
  6. copy和assign的使用和区别
  7. 2014搜狗前端面经【B事业部】
  8. solr使用方法 完全匹配
  9. [C#学习]在多线程中如何调用Winform[转]
  10. jQuery插件之Form
  11. HDU ACM 2845 Beans-&amp;gt;动态规划
  12. java之JAVA异常
  13. Ibatis 返回datatable数据类型案例
  14. 模型驱动 ModelDriven
  15. MVC4相关Razor语法以及Form表单(转载)
  16. equals()和==的区别
  17. 任务执行器——Executor
  18. HSSFWorkbook 导出excel java
  19. Lattice并购案和我国FPGA发展道路
  20. CC2541设置中断输入模式

热门文章

  1. docker资料---仓库搭建
  2. BZOJ 2069: [POI2004]ZAW(Dijkstra + 二进制拆分)
  3. 搭建本地yum源并定时同步
  4. 【BZOJ2817】[ZJOI2012]波浪(动态规划)
  5. 【BZOJ5308】[ZJOI2018]胖(模拟,ST表,二分)
  6. TypeError: __init__() got an unexpected keyword argument &#39;t_command&#39;
  7. [NOI2012]美食节(费用流)
  8. sublime text3 插件的安装
  9. CSS之2D转换模块
  10. 分页技术 -servlet