1. 程式人生 > >Nor flash操作

Nor flash操作

cpu 讀寫 per 獲得 thead png href adf 視頻


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有時候無法接受串口輸入,重啟下軟件串口

  • 基本的讀寫操作

     #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要接受的實際地址,具體分析見程序設計章節中的寫操作

Nor flash操作