• NorFlash 学习



    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

    mark

    命令

    mark

    所谓命令是什么意思呢?

    • 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

    mark

    mark

    识别存储信息,其中具体的信息要搜索文档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

    mark

    CFI描述如下

    mark

    mark

    备注手册里另一张图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)'';
              printf("get 0x20,0x22,0x24=%s
      ",buf);
              printf("end
      ");
          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 ...
      
      ");
      
      	/* 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

  • 相关阅读:
    Linux and the Unix Philosophy(6)
    在docker环境下部署kafka集群
    Docker学习笔记1-docker介绍及安装
    nginx安装
    centos7下NFS配置
    搭建kafka集群
    nexus开机启动
    java跨库事务Atomikos
    mongodb数据库还原
    Linux下运行jar
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10023620.html
Copyright © 2020-2023  润新知