Nor flash操作
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字節的.操作如下:- 發送地址555,發送數據AA, 0x555*2=0xaaa
- 發送地址2AA,發送數據55 0x2AA*2=0x554
- 發送地址555,發送數據90 0x555*2=0xaaa
- 發送地址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
,也就是
- 寫98H到55H
- 查詢
0x10,0x11,0x12
是否為QRY
字符串 - 如果是,那麽支持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); } }
程序優化
- 老師的寫操作並沒有去判斷地址是否是高低地址,也就是寫0地址和寫1地址都是寫在0地址2個字節,實際上如果寫1地址應該先讀取0地址,再寫1地址,這裏會涉及到擦除? 如果都先擦除就沒有這個問題
其他設計點
讀寫NORFLASH的時候需要關閉中斷
因為我們使用NOR啟動的時候,cpu執行中斷的時候會跳回到0地址,也就是NOR中去讀取指令,這個時候如果在操作NOR,很明顯取址有問題了,會死機等.之前串口有時候沒輸出死機可能就是這個原因了.
nor是16位寬的,所以在擦除和寫nor的時候,實際地址需要
>>1
才是nor要接受的實際地址,具體分析見程序設計章節中的寫操作
Nor flash操作