14.Nor-Flash操作实例
目錄
1.基礎知識
2.使用u-boot體驗Nor?Flash的操作
3.編寫NOR-Flash測試程序
源代碼
數據手冊
?
1.基礎知識
1).Nor Flash?和Nand Flash?的區別?
| 結構 | NORflash采用內存的隨機讀取技術。各單元之間是并聯的,對存儲單元進行統一編址,所以可以隨機訪問任意一個字,應用程序可直接在flash內運行,而無需先拷貝到RAM。 | NANDflash數據線和地址線共用I/O線,需額外聯接一些控制的輸入輸出 |
| 讀寫速度 | NOR flash有更快的讀取速度 | NAND flash有更快的寫、擦除速度 |
| 可靠性 | NOR的擦寫次數是10萬次 | NAND的擦寫次數是100萬次 (NAND器件的壞塊是隨機分布的) |
| 成本和容量 | 在面積和工藝相同的情況下,NAND的容量比NOR大的多,成本更低 | |
| 優缺點 | 無位翻轉,無壞塊,穩定(存儲關鍵性的程序) | 位反轉,有壞塊 |
| 易用性 | NOR flash有專用的地址引腳來尋址,較容易和其他芯片聯接,還支持本地執行 | NAND flash的IO端口采用復用的數據線和地址線,必須先通過寄存器串行地進行數據存取。各廠商對信號的定義不同,增加了應用的難度 |
| 編程 | NOR flash采用統一編址(有獨立地址線),可隨機讀取每個“字”,但NOR flash不能像RAM以字節改寫數據,只能按“頁”寫,故NOR flash不能代替RAM。擦除既可整頁擦除,也可整塊擦除 注意: flash進行寫操作時,只能將相應的位由1變0,而擦除才能把塊內所有位由0變1。所有寫入數據時,如果該頁已經存在數據,必須先擦除再寫。 | ?NAND flash共用地址線和數據線,頁是讀寫數據的最小單元,塊是擦除數據的最小單元 |
NOR FLASH?的特點是:
可以像內存一樣的讀取,但是不能直接寫。
例子:?在Nor?Flash中有以下指令:
mov R0,#0? ? ? --R0賦值為0
LDR? R1 ,[R0]? ? --讀取地址R0的值到寄存器R1
STR? R1 ,[R0]? ? --把R1的值寫入R0寄存器
當執行STR? R1 ,[R0]?不會成功,被視為無效操作。
這樣子如果程序燒寫在NOR?FLASH?中會出現什么問題呢?
程序中含有需要更改的全局變量/靜態變量(變量存儲在棧中),但是如果現在bin文件燒寫到NOR?FLASH?上,那么意味著不能修改變量的值了,程序就會達不到我們預期的效果。所以如果想要的到想要的結果,就需要把這些全局變量/靜態變量,重定位放到SDRAM(可讀可寫)中。
2).以S3C2440為例,設為NOR Flash?或?Nand Flash?啟動時,啟動地址的差別
- .基本框架
S3C2440是一個SOC,在一塊芯片上面集成了有CPU、GPIO控制器、Nand控制器、Nor控制器、以及4K的SRAM。
- 啟動過程(大多數ARM芯片從0地址啟動)
1.NoR啟動,NoR Flash?基地址為0(片內RAM地址為:0x40000000),CPU讀出NOR讀出上的第一個指令(前四個字節)執行,CPU繼續讀出其他指令執行(CPU可以在Nor?Flash取指令直接執行)。
2.Nand啟動,片內4K?SRAM?基地之為0(Nor?Falsh?不可訪問),硬件2240把NAND前4K內容復制到片內內存SRAM中,然后CPU從0地址取出第一條指令(前四個字節)執行。
?3).不同位寬內存設備之間的連接
內存控制器地址線和片外內存設備的接法:
可以發現他們的接法好像都是不同的,他們的規律是什么?
這時需要去查看一下芯片手冊。
8-bit ROM
16-bit ROM
32-bit ROM
規律:那么就是說外接芯片的位寬有變化,那地址線的接法也有變化。
大致如下簡圖所示:
那為什么是這樣呢?需要了解數據內部的保存結構,簡圖如下:
假如現在執行指令:
mov? R0 ,#3? ? --把3的數值賦值給R0
LDRB R1, [R0]? --讀取地址為3的位置讀取1個字節的數據
那么CPU就會把地址3發出去,也就是:0x00000011? ,那么此時地址線?A0? =1,?A1 =1 ,其他(A2-A27)為0.
- 如果外界是8-bit ROM,因為A0接A0,A1連接A1,此時他收到的就是0x3
- 如果外界是16-bit ROM,因為A1接A0,A2連接A1,此時他收到的就是0x1
- 如果外界是32-bit ROM,因為A2接A0,A3連接A1,此時他收到的就是0x0
我們的任務是讀取第3byte的數據:
當為8-bit ROM,那我收到的正好也是0x3這個數值,沒有問題。
當為16-bit ROM,我收到的是0x1,這個數值,也就是十進制的1,讀取地址編號為1的地址的值,很巧,發現我們需要第3字節的數據剛好在地址1的位置,但是這個位置有兩個字節啊,怎么辦?CPU不用管,它只需要發出地址就行,所以這也是內存控制器做的事,內存控制器根據收到的CPU發送的?A0 = 1?,那么讀取的就是地址編號為1,右邊的那個字節(第3字節)的數據。
當為32-bit ROM,我收到的是0x0,這個數值,也就是十進制的0,讀取地址編號為0的地址的值,同樣,發現我們需要第3字節的數據剛好在地址0的位置,但是這個位置有四個字節啊,怎么辦?CPU不用管,它只需要發出地址就行,所以這也是內存控制器做的事,內存控制器根據收到的? [A1:A0]? =? 11 ,那么讀取的就是地址編號為0,最右邊的那個字節(第3字節)的數據。
這個過程可以使用一張圖來表示,就可以簡單明了。
再舉個例子:
假如現在執行指令:
mov? R0 ,#4? ? --把4的數值賦值給R0
LDR R1, [R0]? --讀取地址為43的位置讀取4個字節的數據
那他的過程可以簡化為下表:
所以CPU就是一個大BOSS,它只負責發出指令(地址)給內存控制器,而關于你數據是如何拆分和組裝的,CPU并不關心,我只需要發出指令,然后就收數據就好了,那剩下的事情就是手下(內存控制器)做的,它負責數據的組裝和拆分,最終把正確的結果返回給CPU。
2.使用u-boot體驗Nor?Flash的操作
?
例子2:讀數據
燒寫u-boot,到Nor Flash?上,開發版設為Nor Flash啟動、
使用OpenJTAG:輸入命令讀取:md.b 0?讀取Nor Flash 0地址的數據:
結果如下:
查看一下uboot.bin文件的數據和讀取出來是一樣:
說明Nor Flash可以直接讀取數據。
例子2:讀取ID
查看Nor Flash的數據手冊,在此開發版使用的芯片型號是:MX29LV160DBTI
步驟:
①往地址 0x555寫入數據0xAA
②往地址 0x2AA寫入數據0x55
③往地址 0x555寫入數據0x90
④讀取地址0x00得到廠家ID:0xC2
⑤讀取地址0x01得到設備ID:MX29LV160DT: 22C4; MX29LV160DB: 2249
注意:設備位寬連接的問題,因為NOR?Flash連接的是16位的,所以連接的簡圖如下
因此如果CPU需要往NOR?Flash的0x555的寫入數據0xAA,那么需要在2440發送地址的時候需要把0x555左移一位,再發送給Nor Flash才是正確的地址。
那么此時UBOOT怎么操作呢?
如果CPU需要左移一位,才是正確的地址,也就是是說,發送的地址需要乘上2,如下:
步驟:
①往地址 0xAAA寫入數據0xAA? ? ? (mw.w aaa aa)
②往地址 0x554寫入數據0x55? ? ? ? (mw.w 554 55)
③往地址 0xAAA寫入數據0x90? ? ? ?(mw.w aaa 90)
④讀取地址0x00得到廠家ID:0xC2? ?(md.w 0 1)
⑤讀取地址0x02得到設備ID:MX29LV160DT: 22C4; MX29LV160DB: 2249 ( md.w 2 1)
結果如下:
如何退出讀ID狀態?
往任意地址寫入:0xF0
結果如下:
例子3:讀取CFI信息
查看芯片手冊的描述:
進入CFI(common flash interface)模式:
往地址0x55?寫入 0x98?進入CFI模式
查詢芯片容量:
從0x10?讀取一個字節數據得到0x51(Q)
從0x11?讀取一個字節數據得到0x51(R)
從0x12?讀取一個字節數據得到0x51(Y)
從0x27 讀取一個字節數據得到內存大小。
由于設備位寬不同,所以在OpenJTAG的操作是:
往地址0xAA 寫入 0x98?進入CFI模式? ? ? ??(mw.w aa 98)
從0x20 讀取一個字節數據得到0x51(Q)? ? ?(md.w 20 1)
從0x22讀取一個字節數據得到0x51(R)? ? ? (md.w 22 1)
從0x24 讀取一個字節數據得到0x51(Y)? ? ?(md.w 24 1)
從0x4E 讀取一個字節數據得到內存大小。(md.w 4e 1)
結果如下:
退出CFI模式,往任意地址寫0xf0
?
例子4:寫數據
試一下在地址1M以外的地方寫入數據0x1234,然后讀出來
?
那如何寫數據呢?需要一定的格式,如下手冊所描述:
步驟:
①往地址 0x555寫入數據0xAA
②往地址 0x2AA寫入數據0x55
③往地址 0x555寫入數據0xA0
④往指定Addr寫Data
那么此時UBOOT怎么操作呢?
如果CPU需要左移一位,才是正確的地址,也就是是說,發送的地址需要乘上2,如下:
步驟:
①往地址 0xAAA寫入數據0xAA? ? ? (mw.w aaa aa)
②往地址 0x554寫入數據0x55? ? ? ? (mw.w 554 55)
③往地址 0xAAA寫入數據0xA0? ? ? ?(mw.w aaa A0)
④往地址 0x100000?寫入數據?0x1234 (mw.w 100000 1234)
⑤讀取地址0x100000的數據是否寫入成功?(md.w 100000 1)
結果如下:
?
例子5:當原來的數據不是全f的時候
解鎖后能寫入成功的前提,必須是這個位置的數據時全f,也就是:ffff
再次往0x100000這個地址 ,寫入一個新數據,是否能成功?
那如何寫呢?如果原來的數據不是全f,那么需要先對這個位置進行擦除操作?
芯片數據手冊的描述如下:
擦除扇區0x100000數據的步驟步驟:
①往地址 0x555寫入數據0xAA
②往地址 0x2AA寫入數據0x55
③往地址 0x555寫入數據0x80
④往地址 0x555寫入數據0xAA
⑤往地址 0x2AA寫入數據0x55
⑥往扇區0x100000寫0x30
那么此時UBOOT怎么操作呢?
如果CPU需要左移一位,才是正確的地址,也就是是說,發送的地址需要乘上2,如下:
步驟:
①往地址 0xAAA寫入數據0xAA? ? ? (mw.w aaa aa)
②往地址 0x554寫入數據0x55? ? ? ? (mw.w 554 55)
③往地址 0xAAA寫入數據0x80? ? ? ?(mw.w aaa 80)
④往地址 0xAAA寫入數據0xAA? ? ? (mw.w aaa aa)
⑤往地址 0x554寫入數據0x55? ? ? ? (mw.w 554 55)
④往地址 0x100000?寫入數據?0x30 (mw.w 100000 30)
如下:
擦除成功后就可以正常的寫數據了,和上面寫的步驟是一樣的。
?
3.編寫NOR-Flash測試程序
在這個測試例程中有這幾個功能如下:
??/*菜單選項
?? ? ? ? *1.識別nor_falsh,設備和容量
?? ? ? ? *2.讀取某個地址的數據
?? ? ? ? *3.擦除nor_flash扇區數據
?? ? ? ? *4.往某個地址寫數據
?? ??? ? */
1).識別nor_falsh打印出各個扇區的起始地址,讀取設備ID和廠家ID以及設備容量
由數據手冊可知,需要查詢設備容量,廠家ID和設備ID之前,需要進入CFI模式
那么進入CFI模式的方式如下所描述:往0x55的地址寫入0x98
退出CFI模式,如下所描述:往任意地址寫0xF0
進入CFI模式之后,可以查詢的信息如下描述:
讀取設備容量:讀取地址0x27可以得到設備容量
在CFI模式下,可以查詢?erase?region的有關信息:
其中region的描述需要參考:CFI?publication?100?規范,如下:
?代碼如下:
/*進入NOR_FLASH的CFI模式,讀取設備信息*/ void Scan_nor_flash(void) {char qry[4]; //存儲讀取CFI模式下的QRYint Manifacture_ID,Devide_ID;int n,size;int regions; //NOR_Flash的region個數int i,j,count=0;/*變量含義:* 1.region的起始地址 2.block的個數* 3.每個block的大小 4.block的起始扇區基地址*/int region_Bank_Area,blocks,block_size,block_base_addr;/*讀取QRY字符到數組qry*/nor_cmd(0x55,0x98);/*進入CFI模式,往0x55寫0x98*/qry[0] = nor_read(0x10);qry[1] = nor_read(0x11);qry[2] = nor_read(0x12);qry[3] = '\0';printf("******************msg = %s\n\r" ,qry); //打印出QRYnor_cmd(0,0xf0); //復位,退出CFI模式/*打印nor_flash的廠家ID,設備ID*/nor_cmd(0x555,0xaa);nor_cmd(0x2AA,0x55); //解鎖nor_cmd(0x555,0x90); //讀取廠家ID命令Manifacture_ID = nor_read(0x00); //讀取廠家IDDevide_ID = nor_read(0x01); //讀取設備IDprintf("******************The Manifacture_ID is: 0x%x\r\n",Manifacture_ID);printf("******************The Devide_ID is: 0x%x\r\n",Devide_ID);nor_cmd(0,0xf0); //復位/*打印設備容量*/nor_cmd(0x55,0x98);/*進入CFI模式,往0x55寫0x98*/n = nor_read(0x27);size = 1<<n; //表示 2^nprintf("******************Device size:0x%x = %d bytes = %d M \r\n",size,size,size/(1024*1024));/*打印各個扇區的起始地址*/regions = nor_read(0x2c); //從0x2c的地址讀出region個數region_Bank_Area =0x2d;block_base_addr=0; printf("Block/Sector start Address:\r\n");for(i=0;i<regions;i++){/*讀出這個region總共有多少個block*/blocks=nor_read(region_Bank_Area)+(nor_read(region_Bank_Area+1)<<8)+1;/*讀取每個block的大小*/block_size=(nor_read(region_Bank_Area+2)+(nor_read(region_Bank_Area+3)<<8))*256;printf("================Region %d==============\n\r",i+1);printf("number_of_blocks=%d, block_size=0x%x=%dk\n\r",blocks,block_size,block_size/1024);/*打印每個block的起始地址*/for(j=0;j<blocks;j++){puthex(block_base_addr);printf("\t");block_base_addr +=block_size;count++;if(count % 5 == 0){printf("\r\n");}}printf("\n\r");/*region地址自加4讀取下一個region的信息*/region_Bank_Area +=4; }nor_cmd(0,0xf0); //復位,退出CFI模式printf("\n\r"); }注意事項:進入CFI模式之后,退出函數時需要退出CFI模式,才能正常訪問NOR_FALSH。
程序運行結果如下:
注意事項:
編譯程序加上選項:-march=armv4 ,
否則像
? ? ?volatile unsigned short *p =xxx;?? ?
?? ?*p=data
的操作會被分成兩個strb步驟(我們需要的是strh),最后導致讀取CFI設備ID和廠家ID的時候出錯。
2).讀取nor_flash某個地址的數據
首先從鍵盤輸入要讀取的起始地址,接著輸入讀取的字節,為了方便起見,讀取的字節數是16的整數倍。
代碼如下:
unsigned short Read_nor_flash(void) {//讀取的基地址unsigned int addr;//定義一個無符號字符型變量pvolatile unsigned char *p;int i,j;unsigned char c;unsigned short amount,hex_addr=0;/*保存數據*/unsigned char str[16];/*獲取讀取地址*/printf("Enter Read Address: ");/*根據用戶輸入的地址,結果給回addr*可輸入16進制和10進制*/addr = get_uint();/*把addr強制轉換成unsigned char 類型,表明*p只讀取一個字節的數據*/p = (volatile unsigned char *)addr;printf("Enter Read amount(16 times): ");amount = get_uint();printf("Read Data:\n\r");printf(" 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n\r");/*讀取長度:64字節*/for(i=0;i<amount;i++){ printf("0x%08x ",hex_addr);/*每行打印16字節數據*/for(j=0;j<16;j++){/*先打印16進制*/c = *p++; //讀取第一個字節的數據,然后p指向下一個字節str[j] = c; //先保存數值printf("%02x ",c); //打印出當前字節的16進制}printf(" ; ");for(j=0;j<16;j++){/*打印對應的字符*/if(str[j]<0x20 || str[j]>0x7e) /*不可視字符,打印出一個點*/{putchar('.');}elseputchar(str[j]); //可視字符,直接打印}hex_addr+=16;printf("\n\r");} }加入我們現在需要讀取的是從基地址(0地址)開始的160字節的數據,那么從鍵盤輸入的地址就是0,讀取數量就是10,結果如下:
我們讀取的nor_flash上前160字節的數據,對比一下nor_flash.bin文件的數據是否一致:
可以看出來結果完全一致,說明讀取nor_flash的數據時成功的。
注意:
ascii的可打印字符:ASCII字符集由95個可打印字符(0x20-0x7E)
?
3).擦除nor_flash扇區數據
查看數據手冊中如何擦除數據:
代碼如下:
void Erase_nor_flash(void) {//擦除的地址unsigned int addr;/*從鍵盤獲取刪除地址*/printf("Enter Erase Address: ");addr = get_uint();printf("erasing ... ");nor_cmd(0x555,0xaa); /*解鎖*/nor_cmd(0x2aa,0x55);/*注意此處擦除的是一整個扇區,擦除的最小單位就是*一個扇區的大小,此處總共有35個扇區*/nor_cmd(0x555,0x80); /*擦除扇區*/nor_cmd(0x555,0xaa); /*解鎖*/nor_cmd(0x2aa,0x55);/*因為nor_Flash是16bit的,所以發送地址,需要右移一位*/nor_cmd(addr>>1,0x30);wait_ready(addr); /*等待擦除完成*/printf("Erase finished!\n\r"); }4)往某個地址寫數據
查看數據手冊如何寫數據:
需要注意一個點,寫入的數據是16位的,也就是兩個字節,需要一次性寫入一個字節,所以在寫入之前需要對數據進行整合。
如何判斷燒寫完成,以便于進行下一次的燒寫工作:
?
代碼如下:
void Write_nor_flash(void) {//寫數據的地址unsigned int addr;unsigned char write_data[100];int i=0,j=1;unsigned int data;/*從鍵盤獲取寫數據起始地址*/printf("Enter Write Address: ");addr = get_uint();printf("Enter Write data: ");gets(write_data);printf("writing... ");/*把write_data的數組構造16bit的數據*//*如果數組有數據*/while(write_data[i] && write_data[j]) {//構造一個16bit的數據data = write_data[i]+ (write_data[j]<<8);/*寫入數據*/nor_cmd(0x555,0xaa); /*解鎖*/nor_cmd(0x2aa,0x55);/*注意只有當數據為f時才能直接些,否則必須先擦除*/nor_cmd(0x555,0xa0); /*寫命令*//*往地址寫數據*/nor_cmd(addr>>1,data);/*等待燒寫完成:讀數據Q6無變化則燒寫完成*/wait_ready(addr);i +=2;j +=2;addr +=2;}/*IF write_data[i]!=0 write_data[j]=0* 說明還有數據,此時:* data = write_data[i]+ (write_data[j]<<8);*//*IF write_data[i]==0 write_data[j]!=0* 說明已經結束了,0是結束符‘\0’* data = write_data[i]+ (0<<8); 此時data =0*//*IF write_data[i]==0 write_data[j]==0* 說明已經結束了,0是結束符‘\0’* data = write_data[i]+ (0<<8); 此時data =0*//*已上三種情況都需要再寫一個數據*為了方便和保險起見,無論是三種的哪一種*都讓 data = write_data[i]; 把結束符寫進去*///構造一個16bit的數據data = write_data[i];/*寫入數據*/nor_cmd(0x555,0xaa); /*解鎖*/nor_cmd(0x2aa,0x55);/*注意只有當數據為f時才能直接些,否則必須先擦除*/nor_cmd(0x555,0xa0); /*寫命令*//*往地址寫數據*/nor_cmd(addr>>1,data);/*等待燒寫完成:讀數據Q6無變化則燒寫完成*/wait_ready(addr);printf("Write finished!\n\r"); }此處注意還需要加一個判斷數據是否寫完成的函數,當一次行寫入16bit時,等待寫入完成再寫下一個16bit,依次類推:
函數如下:
void wait_ready(unsigned int addr) {unsigned int pre_val;unsigned int current_val;pre_val = nor_read(addr>>1); //先讀取一次current_val= nor_read(addr>>1); //再次讀取/*兩次讀取的結果不一致,說明數據還在變化,繼續等待*/while((pre_val&(1<<6)) != (current_val&(1<<6))){pre_val=current_val; //當前的值等于上一次的值/*再次讀取:Q6 位*/current_val = nor_read(addr>>1);}}上面的代碼已經完成了所有功能了,接下來就是寫數據進行測試了。
通過上面的例子已經知道,這個NOR_flash的大小是2M,共有4個region,而且每一個region的block數量以及block的大小是不同的。如下所示:其中地址是每個block的起始地址
注意:在寫數據時,只有當數據為全f時,才能寫入,因為規定,數據的bit只能從1變成0,而不能從0變成1,所以當數據不為全f時,需要對擦除對應的block,而且每次擦除的最小單位是block,比如,擦除region1的block,那么一次性擦除16k的數據,擦除region2的block,一次性擦除8k的數據。
現在讀取地址:0x001D0000? 開始,160?byte的數據,如下:
直接寫入,寫入數據:hello world !?
往地址:0x001Daaaa?的位置寫入,hello yuan (也就是在這個block的末尾的位置)
現在擦除地址:0x001D0000的數據,查看數據是否還在:
這也證明了,擦除操作是針對一個扇區。
源代碼
地址:https://download.csdn.net/download/qq_36243942/10940801
數據手冊
地址:https://download.csdn.net/download/qq_36243942/10941195
總結
以上是生活随笔為你收集整理的14.Nor-Flash操作实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Rundll32.exe 如何运行dll
- 下一篇: ICPC 2015 北京 Today I