EEPROM (Electrically Erasable Programmable read only memory)是指帶電可擦可編程只讀存儲器。是一種掉電后數據不丟失的存儲芯片。EEPROM可以在電腦上或專用設備上擦除已有信息,重新編程。一般用在即插即用。
EEPROM(帶電可擦可編程只讀存儲器)是用戶可更改的只讀存儲器(ROM),其可通過高于普通電壓的作用來擦除和重編程(重寫)。不像EPROM芯片,EEPROM不需從計算機中取出即可修改。在一個EEPROM中,當計算機在使用的時候可頻繁地反復編程,因此EEPROM的壽命是一個很重要的設計考慮參數。
EEPROM是一種特殊形式的閃存,其應用通常是個人電腦中的電壓來擦寫和重編程。我們以SANXIN-B04板卡上的EEPROM為例,向大家介紹一下其驅動方式。EEPROM型號為24LC64。
從特征中我們可以看出一些信息:
2、有自擦除/寫循環時間。
3、有32字節頁寫和單字節寫模式。
4、最大5ms的寫循環時間。
知道了以上信息之后,那么我們繼續往下看手冊。
在這段描述中,我們可以看出,24LC64是一個8K*8的一個存儲器,總共64K bit的容量。因為存儲深度為8K,也就意味著芯片的地址有需要有8K個,也就是有8192個,從0開始到8191,那么地址線的位寬就是13位。這也就是我們在寫時序圖中展示的高字節地址和低字節地址。此外,芯片的供電范圍是1.8V到5.5V。芯片有頁寫功能,最多可寫32字節。允許最多有8個芯片連接到相同的總線上。
管腳及功能介紹:
A0、A1、A2三個端口為片選輸入,當這三個端口的電平與從機地址相同時,該芯片被選中,這些管腳必須接電源或者地。
SDA是一個雙向總線用于傳輸地址和數據,在SCL為低電平的時候,正常的數據傳輸SDA是允許被改變的,如果在SCL為高電平期間改變就會被認為是開始和停止條件。
SCL為串行時鐘,用于同步數據的傳輸,由主機發出。
寫保護管腳,這個管腳可以連接地、電源或懸空。如果懸空,這個管腳上的下拉電阻會讓芯片保持不被保護的狀態。如果連接到了地或者懸空,正常的存儲器操作是有效地,如果連接到了電源,寫操作是禁止的,讀操作不受影響。
在SCL為高電平時,SDA由高變低被視為開始條件,所有的命令必須在開始條件之后。
在SCL為高電平時,SDA由低變高被視為停止條件,所有的操作必須以停止條件結束。
端口介紹完,這里要著重說明一下應答信號。每次接收完8bit數據,從機需要產生一個應答信號,主機必須產生一個額外的時鐘周期伴隨著應答信號。注釋:芯片如果有內部循環在進程中的話,是不會產生應答信號的。在時鐘為高電平的時候,芯片的應答信號必須拉低SDA,并且在SCL低電平期間保持穩定。
上圖為IIC總線的示意圖,其中開始條件與停止條件在上文已經描述過。其次是數據的讀寫,在圖中,SCL為高電平時,地址或應答有效,那么我們就在數據有效的時候進行采數;在SCL為低電平時,數據允許被改變,那么我們發送數據就在此時間段。
在了解了端口的作用及功能之后,我們做一個總結:
1、起始條件:SCL為高電平,SDA由高變低。
2、停止條件:SCL為低電平,SDA由低變高。
3、數據接收在SCL高電平期間。
4、數據發送在SCL低電平期間。
5、A0、A1、A2、WP在電路圖中已經全部接地,在代碼中我們不需要關注。
電路圖中可以看出,我們的EEPROM的控制管腳只有SCL和SDA。
設計框架
按照上面的框架,完成各部分代碼,最后在此框架的基礎上,再加上數碼管模塊,用來顯示從EEPROM里面讀出的數據。
新建工程:
新建好工程后,新建文件,開始寫代碼,首先是寫模塊。
寫時序如下圖。
我們以單字節寫為例,因為在寫的工程中,沒寫8bit,就要讀一次ACK,所以在此我得做法是,讀模塊只寫8bit的線性序列機,代碼如下:
?
1 module iic_wr( 2 3 input wire clk, 4 input wire rst_n, 5 6 input wire wr_en, 7 input wire [7:0] wr_data, 8 9 output reg wr_scl, 10 output reg wr_sda, 11 output wire wr_done 12 ); 13 14 parameter f_clk = 50_000_000; 15 parameter f = 100_000; 16 parameter t = f_clk / f / 4; 17 18 reg [11:0] cnt; 19 20 always @ (posedge clk, negedge rst_n) 21 begin 22 if(rst_n == 1'b0) 23 cnt <= 12'd0; 24 else if(wr_en) 25 begin 26 if(cnt == 32 * t - 1) 27 cnt <= 12'd0; 28 else 29 cnt <= cnt + 1'b1; 30 end 31 else 32 cnt <= 12'd0; 33 end 34 35 always @ (posedge clk, negedge rst_n) 36 begin 37 if(rst_n == 1'b0) 38 begin 39 wr_scl <= 1'b0; 40 wr_sda <= 1'b0; 41 end 42 else if(wr_en) 43 case(cnt) 44 0 : begin wr_scl <= 1'b0; wr_sda <= wr_data[7]; end 45 1 * t - 1 : begin wr_scl <= 1'b1; end 46 3 * t - 1 : begin wr_scl <= 1'b0; end 47 4 * t - 1 : begin wr_sda <= wr_data[6]; end 48 5 * t - 1 : begin wr_scl <= 1'b1; end 49 7 * t - 1 : begin wr_scl <= 1'b0; end 50 8 * t - 1 : begin wr_sda <= wr_data[5]; end 51 9 * t - 1 : begin wr_scl <= 1'b1; end 52 11* t - 1 : begin wr_scl <= 1'b0; end 53 12* t - 1 : begin wr_sda <= wr_data[4]; end 54 13* t - 1 : begin wr_scl <= 1'b1; end 55 15* t - 1 : begin wr_scl <= 1'b0; end 56 16* t - 1 : begin wr_sda <= wr_data[3]; end 57 17* t - 1 : begin wr_scl <= 1'b1; end 58 19* t - 1 : begin wr_scl <= 1'b0; end 59 20* t - 1 : begin wr_sda <= wr_data[2]; end 60 21* t - 1 : begin wr_scl <= 1'b1; end 61 23* t - 1 : begin wr_scl <= 1'b0; end 62 24* t - 1 : begin wr_sda <= wr_data[1]; end 63 25* t - 1 : begin wr_scl <= 1'b1; end 64 27* t - 1 : begin wr_scl <= 1'b0; end 65 28* t - 1 : begin wr_sda <= wr_data[0]; end 66 29* t - 1 : begin wr_scl <= 1'b1; end 67 31* t - 1 : begin wr_scl <= 1'b0; end 68 default : ; 69 endcase 70 end 71 72 assign wr_done = (cnt == 32 * t - 1) ? 1'b1 : 1'b0; 73 74 endmodule
?
在寫模塊中,我們選擇在SCL低電平中心位置發送數據,那么為了方便我們寫線性序列機,我們將四分之一SCL周期看做是一個單位時間T,總共8bit時間,所以計數器需要計數32個T。
讀模塊代碼如下:
?
1 module iic_rd( 2 3 input wire clk, 4 input wire rst_n, 5 6 input wire rd_sda, 7 input wire rd_en, 8 9 output reg rd_scl, 10 output reg [7:0] rd_data, 11 output wire rd_done 12 ); 13 14 parameter f_clk = 50_000_000; 15 parameter f = 100_000; 16 parameter t = f_clk / f / 4; 17 18 reg [11:0] cnt; 19 20 always @ (posedge clk, negedge rst_n) 21 begin 22 if(rst_n == 1'b0) 23 cnt <= 12'd0; 24 else if(rd_en) 25 begin 26 if(cnt == 32 * t - 1) 27 cnt <= 12'd0; 28 else 29 cnt <= cnt + 1'b1; 30 end 31 else 32 cnt <= 12'd0; 33 end 34 35 always @ (posedge clk, negedge rst_n) 36 begin 37 if(rst_n == 1'b0) 38 begin 39 rd_scl <= 1'b0; 40 rd_data <= 8'd0; 41 end 42 else if(rd_en) 43 case(cnt) 44 0 : begin rd_scl <= 1'b0; end 45 1 * t - 1 : begin rd_scl <= 1'b1; end 46 2 * t - 1 : begin rd_data[7] <= rd_sda; end 47 3 * t - 1 : begin rd_scl <= 1'b0; end 48 5 * t - 1 : begin rd_scl <= 1'b1; end 49 6 * t - 1 : begin rd_data[6] <= rd_sda; end 50 7 * t - 1 : begin rd_scl <= 1'b0; end 51 9 * t - 1 : begin rd_scl <= 1'b1; end 52 10* t - 1 : begin rd_data[5] <= rd_sda; end 53 11* t - 1 : begin rd_scl <= 1'b0; end 54 13* t - 1 : begin rd_scl <= 1'b1; end 55 14* t - 1 : begin rd_data[4] <= rd_sda; end 56 15* t - 1 : begin rd_scl <= 1'b0; end 57 17* t - 1 : begin rd_scl <= 1'b1; end 58 18* t - 1 : begin rd_data[3] <= rd_sda; end 59 19* t - 1 : begin rd_scl <= 1'b0; end 60 21* t - 1 : begin rd_scl <= 1'b1; end 61 22* t - 1 : begin rd_data[2] <= rd_sda; end 62 23* t - 1 : begin rd_scl <= 1'b0; end 63 25* t - 1 : begin rd_scl <= 1'b1; end 64 26* t - 1 : begin rd_data[1] <= rd_sda; end 65 27* t - 1 : begin rd_scl <= 1'b0; end 66 29* t - 1 : begin rd_scl <= 1'b1; end 67 30* t - 1 : begin rd_data[0] <= rd_sda; end 68 31* t - 1 : begin rd_scl <= 1'b0; end 69 default : ; 70 endcase 71 end 72 73 assign rd_done = (cnt == 32 * t - 1) ? 1'b1 : 1'b0; 74 75 endmodule
?
在控制模塊中,我們可以將每個bit分解開進行寫,比如起始條件:
類比以上寫法,停止條件也是如此。
在控制模塊中我們使用狀態機來完成數據的寫入以及ACK的判斷。我們在第一個狀態中,可以加入一個按鍵來啟動整個過程的開始。
在ACK讀取的狀態,我們在SCL高電平中心讀取ACK的值,在此狀態結束時判斷ACK的值是否為0,如果為0說明響應正確,狀態繼續往下,否則返回起始條件狀態。
在頂層代碼中,我們需要加入三態門來控制雙端口。
在寫好代碼之后,出現了一個問題,那就是我們無法仿真,因為單純的看波形,會發現我們得不到正確的ACK,導致狀態一直無法繼續。因此我們將使用仿真模型來進行仿真,在仿真模型中,端口為芯片的控制端口。
在此模型中需要我們注意的一點就是,寫循環時間定義的為5ms時間,如果大家覺得時間太長,可以自行修改。
仿真代碼如下:
?
1 `timescale 1ns/1ps 2 3 module iic_tb; 4 5 reg clk; 6 reg rst_n; 7 reg key; 8 9 wire [2:0] sel; 10 wire [7:0] seg; 11 wire SCL; 12 wire SDA; 13 14 defparam iic_inst.jitter_inst.t = 10; 15 16 pullup(SDA); 17 18 initial begin 19 clk = 0; 20 rst_n = 0; 21 key = 1; 22 #100; 23 rst_n = 1; 24 #1000; 25 key = 0; 26 #1000; 27 key = 1; 28 #1000000; 29 $stop; 30 end 31 32 always #10 clk = ~clk; 33 34 iic iic_inst( 35 36 .clk (clk), 37 .rst_n (rst_n), 38 .key (key), 39 40 .sel (sel), 41 .seg (seg), 42 .SCL (SCL), 43 .SDA (SDA) 44 ); 45 46 M24LC64 M24LC64_inst( 47 .A0 (1'b0), 48 .A1 (1'b0), 49 .A2 (1'b0), 50 .WP (1'b0), 51 .SDA (SDA), 52 .SCL (SCL) 53 ); 54 55 endmodule
?
寫好代碼之后,我們打開仿真波形進行觀察。
在仿真波形中可以看出,我們讀出來的數據跟寫入的數據時一致的,即表明驅動正確。需要注意的是,在寫循環時間內,我們發送的命令,芯片是不接收的,所以會出現起始條件、寫控制字、ACK三個狀態一直循環,直到數據寫入完成,仿真模型才會返回ACK。此時,狀態繼續往下進行,直至結束。
我們從仿真模型中讀出的數據,高電平是為高阻的,模型無法拉高,所以我們在仿真中可以自行上拉,這樣才能看到完整且正確的數據。
至此,我們實驗正確。
審核編輯:劉清
評論
查看更多