無論任何電子產品都會涉及到數據的產生與數據的保存,這個數據可能並不是用來長久保存,只是在運行程序才會用到,有些數據體量較大對於獲取時效性並不太強,各種各樣的數據也就有不同的存儲載體,這次在EEPROM讀寫中,順道把看到的關於存儲的一些東西整理一下,有些話來自於網友,所以還是那句話,看到的人要帶着自己的思考去看,記住盡信書不如無書,fighting!!!
一、基本概念
最熟悉的兩個詞語應該是RAM與ROM,RAM(Random Access Memory)的全名為隨機存取記憶體,它相當於PC機上的移動存儲,用來存儲和保存數據的。它在任何時候都可以讀寫,RAM通常是作為操作系統或其他正在運行程序的臨時存儲介質,它的一切都是最好的,唯一缺點斷電一切東西都沒有了。一般情況下,現在移動設備也多了,我們叫它內存,更通常的叫運行內存。還有一個熟悉的詞DDR2或DDR3,后面還會學習到的。
ROM(Read Only Memory)的全名為唯讀記憶體,它相當於PC機上的硬盤,用來存儲和保存數據。ROM數據不能隨意更新,但是在任何時候都可以讀取。即使是斷電,ROM也能夠保留數據。但是資料一但寫入后只能用特殊方法或根本無法更改,但這么久了ROM已經有了很大的發展,不再是最初的摸樣了。rom最初不能編程,出廠什么內容就永遠什么內容,不靈活。后來出現了prom,可以自己寫入一次,要是寫錯了,只能換一片,自認倒霉。人類文明不斷進步,終於出現了可多次擦除寫入的EPROM,每次擦除要把芯片拿到紫外線上照一下,想一下你往單片機上下了一個程序之后發現有個地方需要加一句話,為此你要把單片機放紫外燈下照半小時,然后才能再下一次,這么折騰一天也改不了幾次。歷史的車輪不斷前進,偉大的EEPROM出現了,拯救了一大批程序員,終於可以隨意的修改rom中的內容了,這一段話就說出了ROM的發展歷程。
狹義的EEPROM:這種rom的特點是可以隨機訪問和修改任何一個字節,可以往每個bit中寫入0或者1。這是最傳統的一種EEPROM,掉電后數據不丟失,可以保存100年,可以擦寫100w次。具有較高的可靠性,但是電路復雜/成本也高。它的改寫是由高電壓或者由控制端的邏輯電平來完成的。因此目前的EEPROM都是幾十千字節到幾百千字節的,絕少有超過512K的。我們也就發現了EEPROM的確可以實現隨意讀寫,EEPROM的全稱是“電可擦除可編程只讀存儲器”,即Electrically Erasable Programmable Read-Only Memory。可介紹的這兩種都不存在大容量並且也十分昂貴,那我們平時見到的幾十G的存儲設備是什么?flash就應運而生了。flash屬於廣義的EEPROM,因為它也是電擦除的rom。但是為了區別於一般的按字節為單位的擦寫的EEPROM,我們都叫它flash。flash做的改進就是擦除時不再以字節為單位,而是以塊為單位,一次簡化了電路,數據密度更高,降低了成本。上M的rom一般都是flash。
ROM的應用
對數指數、三角函數等常規計算通過寫出真值表,將自變量以地址碼的形式輸至ROM,用ROM表來實現。還有碼制轉換,例如二進制碼轉格雷碼。脈沖序列發生器,偽彩色處理電路,也就是將一幅黑白圖像變成彩色圖像顯示,將灰度圖像對應到red , green , blue三個通道上,最后將三個通道的顏色值合成為需要顯示的RGB顏色值即可。具體可參考 https://blog.csdn.net/huixingshao/article/details/42706699 。
接下來說一下flash的分類,flash分為nor flash和nand flash。nor flash數據線和地址線分開,可以實現ram一樣的隨機尋址功能,可以讀取任何一個字節。但是擦除仍要按塊來擦。nand flash同樣是按塊擦除,但是數據線和地址線復用,不能利用地址線隨機尋址。讀取只能按頁來讀取。NOR Flash的讀取,用戶可以直接運行裝載在NOR FLASH里面的代碼。NAND Flash沒有采取內存RAM的隨機讀取技術,它的讀取是以一次讀取一塊的形式來進行的,通常是一次讀取512個字節,采用這種技術的Flash比較廉價。用戶不能直接運行NAND Flash上的代碼,因此好多使用NAND Flash的開發板除了使用NAND Flah以外,還作上了一塊小的NOR Flash來運行啟動代碼。nandflash引腳上復用,因此讀取速度比nor flash慢一點,但是擦除和寫入速度比nor flash快很多。nand flash內部電路更簡單,因此數據密度大,體積小,成本也低。因此大容量的flash都是nand型的。小容量的2~12M的flash多是nor型的。nor flash可以進行字節尋址,所以程序可以在nor flash中運行。嵌入式系統多用一個小容量的nor flash存儲引導代碼,用一個大容量的nand flash存放文件系統和內核。
二、I2C總線
這個在我轉載的一篇文章里面有很詳細的描述,就不在提及了。有一個問題是無論UART還是I2C都是串行按位傳輸數據,區別在哪?還有SPI傳輸,下面分別總結一下三者的特點。
UART:兩線,一根發送一根接收,可以全雙工通信,數據異步傳輸,對雙方的時序要求比較嚴格,在多機通信上面用的最多。按照標准波特率完成雙向通訊,速度慢,之前提到采集一位數據就需要16個時鍾周期,適合遠距離傳輸,比如IEEE488定義並行通行狀態時,規定設備線總常不得超過20米,並且任意兩個設備間的長度不得超過2米;而對於串口而言,長度可達1200米。UART需要固定的波特率,就是說兩位數據的間隔要相等,
I2C:能用於替代標准的並行總線,能連接的各種集成電路和功能模塊。I2C是多主控總線,所以任何一個設備都能像主控器一樣工作,並控制總線。總線上每一個設備都有一個獨一無二的地址,根據設備它們自己的能力,它們可以作為發射器或接收器工作。多路微控制器能在同一個I2C總線上共存,當然在任何時間點上只能有一個主控。一般用於同一板卡上芯片之間的通信,較少用於遠距離通信。
SPI:SPI接口和UART相比,多了一條同步時鍾線,對通信雙方的時序要求不嚴格不同設備之間可以很容易結合,而且通信速度非常快。一般用在產品內部元件之間的高速數據通信上面,如大容量存儲器flash等。高速同步串行口,3~4線接口,收發獨立、可同步進行。
三、EEPROM通信舉例
同樣通過一個程序來學習里面內容。自加內容用紅筆標出。
1)IIC_WR模塊
寫步驟:
a. 實現開始信號
b. 發送24LC04B設備地址,從機發送應答信號
c. 發送待存儲數據的地址,接受應答信號
d. 發送待寫入數據,接受應答信號
e. 實現結束信號
讀步驟:
a. 實現開始信號
b. 發送24LC04B設備地址,從機發送應答信號
c. 發送待讀取數據的地址,接受應答信號
d. 實現開始信號
e. 發送24LC04B設備地址,從機發送應答信號
f. 讀取8位數據
g. 實現非應答信號
h. 實現結束信號
通過狀態機 i 來切換 IIC 的不同狀態,譬 如接收到寫命令,狀態機i=0 轉入 Start 狀態,SDA 先變低,再 SCL 變低;狀態機i=1 開 始 轉 入 寫 設 備 地 址 0xA0; 之 后 狀 態 機 轉 到 7 開 始 發 送 8 位 的 數 據 , 其 中 狀 態 機 i=7,8,9,10,11,12,13,14 是 IIC 發送8位的數據,然后狀態機進入 i=15 等待 IIC 從設備的應答 信號。狀態機 i=16 為判斷是否有應答,如果有的話狀態機轉到 i=2 寫 IIC 的地址,然后狀態機 又是重復i=7,8,9,10,11,12,13,14 發送地址和 i=15 等待應答,i=16 判斷應答。最后狀態機 i=3 開始發送 IIC 寫數據。發送完數據 i=4 發送 Stop 信號。
1 module iic_com 2 ( 3 input CLK, 4 input RSTn, 5 6 input [1:0] Start_Sig, //read or write command 7 input [7:0] Addr_Sig, //eeprom words address 8 input [7:0] WrData, //eeprom write data 9 output [7:0] RdData, //eeprom read data 10 output Done_Sig, //eeprom read/write finish 11 12 output SCL, 13 inout SDA 14 15 ); 16 17 parameter F250K = 9'd200; //250Khz的時鍾分頻系數 //200分頻系數不必要用到9位,可改為8 18 19 reg [4:0]i; //用來只是狀態機 20 reg [4:0]Go; 21 reg [8:0]C1; 22 reg [7:0]rData; //讀信號 23 reg rSCL; 24 reg rSDA; 25 reg isAck; 26 reg isDone; //結束信號 27 reg isOut; 28 29 assign Done_Sig = isDone; 30 assign RdData = rData; 31 assign SCL = rSCL; 32 assign SDA = isOut ? rSDA : 1'bz; //SDA數據輸出選擇 //SDA的數據輸出受到SCL的控制,SCL為高時SDA保持不變,在接收應答位期間SDA也受控制 33 34 //****************************************// 35 //* I2C讀寫處理程序 *// 36 //****************************************// 37 always @ ( posedge CLK or negedge RSTn ) 38 if( !RSTn ) begin 39 i <= 5'd0; //狀態機初始為0 40 Go <= 5'd0; 41 C1 <= 9'd0; 42 rData <= 8'd0; 43 rSCL <= 1'b1; //數據線和時鍾線保持高電平初始值 44 rSDA <= 1'b1; 45 isAck <= 1'b1; //信號為低電平時規定為有效應答位,高電平為非有效應答位。 46 isDone <= 1'b0; 47 isOut <= 1'b1; 48 end 49 else if( Start_Sig[0] ) //I2C 數據寫 //start_sig用來判斷數據為讀或寫 50 case( i ) 51 52 0: //發送IIC開始信號 53 begin 54 isOut <= 1; //SDA端口輸出 55 56 if( C1 == 0 ) rSCL <= 1'b1; 57 else if( C1 == 200 ) rSCL <= 1'b0; //SCL由高變低 //分頻系數為200,計數到200表明經過一個新的時鍾周期時鍾線變為低電平。 58 59 if( C1 == 0 ) rSDA <= 1'b1; 60 else if( C1 == 100 ) rSDA <= 1'b0; //SDA先由高變低 //計數到100時,rSDA變為低電平,符合信號開始發送條件。 61 62 if( C1 == 250 -1) begin C1 <= 9'd0; i <= i + 1'b1; end 63 else C1 <= C1 + 1'b1; //i=0用來表示開始信號發送,根據SDA與SCL變化可得,此處C1到達249,表示又過了四分之一個新時鍾周期,i+1,運行下一步 64 end 65 66 1: // Write Device Addr 67 begin rData <= {4'b1010, 3'b000, 1'b0}; i <= 5'd7; Go <= i + 1'b1; end //非阻塞賦值,Go為2 68 69 2: // Wirte Word Addr 70 begin rData <= Addr_Sig; i <= 5'd7; Go <= i + 1'b1; end //addr_sig為word address,Go為3 71 72 3: // Write Data 73 begin rData <= WrData; i <= 5'd7; Go <= i + 1'b1; end //寫入數據,Go為4,WrData數值賦給rData。 74 75 4: //發送IIC停止信號 76 begin 77 isOut <= 1'b1; 78 79 if( C1 == 0 ) rSCL <= 1'b0; 80 else if( C1 == 50 ) rSCL <= 1'b1; //SCL先由低變高 81 82 if( C1 == 0 ) rSDA <= 1'b0; 83 else if( C1 == 150 ) rSDA <= 1'b1; //SDA由低變高 //SCL處於高電位時,SDA由低到高變化,處於結束位 84 85 if( C1 == 250 -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end 86 else C1 <= C1 + 1'b1; 87 end 88 89 5: 90 begin isDone <= 1'b1; i <= i + 1'b1; end //寫I2C 結束 91 92 6: 93 begin isDone <= 1'b0; i <= 5'd0; end 94 95 7,8,9,10,11,12,13,14: //發送Device Addr/Word Addr/Write Data 96 begin 97 isOut <= 1'b1; //isout=1, SDA <= rSDA 98 rSDA <= rData[14-i]; //高位先發送 99 100 if( C1 == 0 ) rSCL <= 1'b0; 101 else if( C1 == 50 ) rSCL <= 1'b1; //SCL高電平100個時鍾周期,低電平100個時鍾周期 102 else if( C1 == 150 ) rSCL <= 1'b0; 103 104 if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end //產生250Khz的IIC時鍾 //i=14運行之后,狀態機i=15 105 else C1 <= C1 + 1'b1; 106 end 107 108 15: // waiting for acknowledge 109 begin 110 isOut <= 1'b0; //SDA端口改為輸入 111 if( C1 == 100 ) isAck <= SDA; //讀取IIC 從設備的應答信號 112 113 if( C1 == 0 ) rSCL <= 1'b0; 114 else if( C1 == 50 ) rSCL <= 1'b1; //SCL高電平100個時鍾周期,低電平100個時鍾周期 115 else if( C1 == 150 ) rSCL <= 1'b0; 116 117 if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end //產生250Khz的IIC時鍾 118 else C1 <= C1 + 1'b1; 119 end 120 121 16: 122 if( isAck != 0 ) i <= 5'd0; //判斷是否接收到應答信號 123 else i <= Go; //狀態機i=1時,計算出i=2 124 125 endcase 126 127 else if( Start_Sig[1] ) //I2C 數據讀 128 case( i ) 129 130 0: // Start 131 begin 132 isOut <= 1; //SDA端口輸出 133 134 if( C1 == 0 ) rSCL <= 1'b1; 135 else if( C1 == 200 ) rSCL <= 1'b0; //SCL由高變低 136 137 if( C1 == 0 ) rSDA <= 1'b1; 138 else if( C1 == 100 ) rSDA <= 1'b0; //SDA先由高變低 139 140 if( C1 == 250 -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end 141 else C1 <= C1 + 1'b1; 142 end 143 144 1: // Write Device Addr(設備地址) 145 begin rData <= {4'b1010, 3'b000, 1'b0}; i <= 5'd9; Go <= i + 1'b1; end //先進行一個偽寫操作 146 147 2: // Wirte Word Addr(EEPROM的寫地址) 148 begin rData <= Addr_Sig; i <= 5'd9; Go <= i + 1'b1; end 149 150 3: // Start again 151 begin 152 isOut <= 1'b1; //開始進行讀操作 153 154 if( C1 == 0 ) rSCL <= 1'b0; 155 else if( C1 == 50 ) rSCL <= 1'b1; 156 else if( C1 == 250 ) rSCL <= 1'b0; 157 158 if( C1 == 0 ) rSDA <= 1'b0; 159 else if( C1 == 50 ) rSDA <= 1'b1; 160 else if( C1 == 150 ) rSDA <= 1'b0; 161 162 if( C1 == 300 -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end 163 else C1 <= C1 + 1'b1; 164 end 165 166 4: // Write Device Addr ( Read ) 167 begin rData <= {4'b1010, 3'b000, 1'b1}; i <= 5'd9; Go <= i + 1'b1; end 168 169 5: // Read Data 170 begin rData <= 8'd0; i <= 5'd19; Go <= i + 1'b1; end 171 172 6: // Stop 173 begin 174 isOut <= 1'b1; 175 if( C1 == 0 ) rSCL <= 1'b0; 176 else if( C1 == 50 ) rSCL <= 1'b1; 177 178 if( C1 == 0 ) rSDA <= 1'b0; 179 else if( C1 == 150 ) rSDA <= 1'b1; 180 181 if( C1 == 250 -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end 182 else C1 <= C1 + 1'b1; 183 end 184 185 7: //寫I2C 結束 186 begin isDone <= 1'b1; i <= i + 1'b1; end 187 188 8: 189 begin isDone <= 1'b0; i <= 5'd0; end 190 191 192 9,10,11,12,13,14,15,16: //發送Device Addr(write)/Word Addr/Device Addr(read) 193 begin 194 isOut <= 1'b1; 195 rSDA <= rData[16-i]; //高位先發送 //將rData數據賦值給數據線,偽寫操作 196 197 if( C1 == 0 ) rSCL <= 1'b0; 198 else if( C1 == 50 ) rSCL <= 1'b1; //SCL高電平100個時鍾周期,低電平100個時鍾周期 199 else if( C1 == 150 ) rSCL <= 1'b0; 200 201 if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end //產生250Khz的IIC時鍾 202 else C1 <= C1 + 1'b1; 203 end 204 205 17: // waiting for acknowledge 206 begin 207 isOut <= 1'b0; //SDA端口改為輸入 208 209 if( C1 == 100 ) isAck <= SDA; //讀取IIC 的應答信號 210 211 if( C1 == 0 ) rSCL <= 1'b0; 212 else if( C1 == 50 ) rSCL <= 1'b1; //SCL高電平100個時鍾周期,低電平100個時鍾周期 213 else if( C1 == 150 ) rSCL <= 1'b0; 214 215 if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end //產生250Khz的IIC時鍾 216 else C1 <= C1 + 1'b1; 217 end 218 219 18: 220 if( isAck != 0 ) i <= 5'd0; 221 else i <= Go; 222 223 224 19,20,21,22,23,24,25,26: // Read data 225 begin 226 isOut <= 1'b0; 227 if( C1 == 100 ) rData[26-i] <= SDA; //高位先接收 228 229 if( C1 == 0 ) rSCL <= 1'b0; 230 else if( C1 == 50 ) rSCL <= 1'b1; //SCL高電平100個時鍾周期,低電平100個時鍾周期 231 else if( C1 == 150 ) rSCL <= 1'b0; 232 233 if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end //產生250Khz的IIC時鍾 234 else C1 <= C1 + 1'b1; 235 end 236 237 27: // no acknowledge 238 begin 239 isOut <= 1'b1; 240 241 if( C1 == 0 ) rSCL <= 1'b0; 242 else if( C1 == 50 ) rSCL <= 1'b1; 243 else if( C1 == 150 ) rSCL <= 1'b0; 244 245 if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= Go; end 246 else C1 <= C1 + 1'b1; 247 end 248 249 endcase 250 251 252 253 254 endmodule