一、IIC總線協議特點及其工作原理
I2C(Inter-Integrated Circuit)總線是一種由PHILIPS公司開發的兩線式串行總線,用於連接微控制器及其外圍設備。
1)I2C總線特點
I2C總線最主要的優點是其簡單性和有效性。由於接口直接在組件之上,因此I2C總線占用的空間非常小,減少了電路板的空間和芯片管腳的數量,降低了互聯成本。總線的長度可高達25英尺,並且能夠以10Kbps的最大傳輸速率支持40個組件。
I2C總線的另一個優點是,它支持多主控(multimastering), 其中任何能夠進行發送和接收的設備都可以成為主總線。一個主控能夠控制信號的傳輸和時鍾頻率。當然,在任何時間點上只能有一個主控。
2)I2C總線工作原理
I2C總線是由數據線SDA和時鍾SCL構成的串行總線,可發送和接收數據。在CPU與被控IC之間、IC與IC之間進行雙向傳送,最高傳送速率 100kbps。各種被控制電路均並聯在這條總線上,但就像電話機一樣只有撥通各自的號碼才能工作,所以每個電路和模塊都有唯一的地址,在信息的傳輸過程 中,I2C總線上並接的每一模塊電路既是主控器(或被控器),又是發送器(或接收器),這取決於它所要完成的功能。CPU發出的控制信號分為地址碼和控制 量兩部分,地址碼用來選址,即接通需要控制的電路,確定控制的種類;控制量決定該調整的類別(如對比度、亮度等)及需要調整的量。這樣,各控制電路雖然掛 在同一條總線上,卻彼此獨立,互不相關。
3)總線的構成及信號類型
I2C總線在傳送數據過程中共有三種類型信號, 它們分別是:開始信號、結束信號和應答信號。
開始信號:SCL為高電平時,SDA由高電平向低電平跳變,開始傳送數據。
結束信號:SCL為高電平時,SDA由低電平向高電平跳變,結束傳送數據。
應答信號:接收數據的IC在接收到8bit數據后,向發送數據的IC發出特定的低電平脈沖,表示已收到數據。CPU向受控單元發出一個信號后,等待受控單 元發出一個應答信號,CPU接收到應答信號后,根據實際情況作出是否繼續傳遞信號的判斷。若未收到應答信號,由判斷為受控單元出現故障。
這些信號中,起始信號是必需的,結束信號和應答信號,都可以不要。
4)I2C總線操作
I2C規程運用主/從雙向通訊。器件發送數據到總線上,則定義為發送器,器件接收數據則定義為接收器。主器件和從器件都可以工作於接收和發送狀態。 總線必須由主器件(通常為微控制器)控制,主器件產生串行時鍾(SCL)控制總線的傳輸方向,並產生起始和停止條件。
SDA線上的數據狀態僅在SCL為低電平的期間才能改變,SCL為高電平的期間,SDA狀態的改變被用來表示起始和停止條件。
控制字節
在起始條件之后,必須是器件的控制字節,其中高四位為器件類型識別符(不同的芯片類型有不同的定義,EEPROM一般應為1010),接着三位為片選,最后一位為讀寫位,當為1時為讀操作,為0時為寫操作。
寫操作
寫操作分為字節寫和頁面寫兩種操作,對於頁面寫根據芯片的一次裝載的字節不同有所不同。
讀操作
讀操作有三種基本操作:當前地址讀、隨機讀和順序讀。
5)I2C總線應用
目前有很多半導體集成電路上都集成了I2C接口。帶有I2C接口的單片機有:CYGNAL的 C8051F0XX系列,三星的S3C24XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外圍器 件如存儲器、監控芯片等也提供I2C接口。
二、IIC之EEPROM
1)下面是EEPROM(24LC64)不同封裝情況。
數據手冊中關於這八個引腳的介紹:
A0, A1,A2 :片選地址輸入
SDA : 單bit數據線
SLC : 時鍾線(200KHZ)
WP : 接地或者懸空時,可讀可寫,接電源VCC時,只讀不可寫。
注意:EEPROM(24LC64)工作的最大時鍾為400KHZ,所以我們用系統50M時鍾來分頻一個400KHZ。
2)I2C總線時序圖
- 總線非忙狀態(A段)數據線SDA 和時鍾線SCL都保持高電平
- 啟動數據傳輸(B段)當時鍾線SCL為高電平時,數據線由高變低,此時認為是啟動信號,有啟動信號之后,后面的命令才有用。
- 停止數據傳輸(C段): 當時鍾SCL為高電平時,數據線SDA由低到高的上升沿認為是停止信號。
- 數據有效(D段): 出現啟動信號后,在時鍾線SCL為高電平時,數據線是穩定的,這時候數據線的狀態是穩定的,而在時鍾線SCL為低電平的時候,允許數據線發生改變,每位數據占用一個時鍾脈沖。
- 應答信號: 每個正在接收數據的從機EEPROM在接到一個字節后,通常需要發出一個應答信號ACK,用來告訴FPGA數據已經傳輸過來。此時EEPROM讀寫控制器必須產生一個與這個應答位相對應和聯系的額外時鍾脈沖。在EEPROM的讀操作中,其讀控制器對eeprom完成的最后一個字節產生一個高的應答為,這個信號叫做NOack(非應答位信號),隨后給EEPROM一個結束信號。
控制字節
在起始條件之后,必須是器件的控制字節,其中高四位為器件類型識別符(不同的芯片類型有不同的定義,EEPROM一般應為1010),接着三位為片選,最后一位為讀寫位,當為1時為讀操作,為0時為寫操作。示意圖如下:
讀控制字節: 1010_0001
寫控制字節: 1010_0000
.
(3) IIC總線讀寫時序
圖 隨機寫
圖 頁面寫
圖 隨機讀
圖 頁面讀
三 EEPROM 之隨機寫操作
寫操作的時候就要考慮幾個問題:
- 工作時鍾的問題,晶振是50MHZ時鍾,怎么確定時鍾,在數據手冊里我們發現最大時鍾為400Khz,同時又要考慮到如何去保證scl為高電平的時候,sda從高到低的問題所以考慮用到兩個時鍾,先用晶振產生一個400Khz時鍾,然后在用這個400khz時鍾產生SCL時鍾(200kHZ),在400Khz的上升沿的作用下,產生200Khz時鍾SCL,在400Khz時鍾的下降沿的作用下,記性狀態的轉換即數據的傳輸寫入。
- 認識input和output之外的另一種端口三態門inout,因為要考慮到后面的應答信號的傳輸問題,應答信號是eeprom從機給FPGA的傳輸,而數據的傳輸是FPGA到EEPROM,所以這個sda端口應該是雙向的,雙向的就要考慮三態門。
當ENB==1時,數據從FPGA到EEPROM,
當ENB==0時,數據從EEPROM到FPGA。
實現語句: assign sda = (flag==1) ? data :1’bz;
3.寫時序的問題,要對照數據手冊看到整個過程都有哪些,並捋清高低電平的問題,這里對前面幾個狀態進行詳細闡述。
State0 啟動信號,此時要搞清楚高低電平,在前邊我介紹數據手冊的時候,就說過了,當SCL為高電平的時候,sda此時也為高電平,此時應將flag三態開關打開,迎接數據的到來,其次sda線要拉低進入啟動信號,同時給temp(數據緩存總線)賦控制字節
State1 控制字節的輸入讀取,我們通過閱讀數據手冊知道當SCL為高電平的時候為數據有效,所以只有在SCL為低電平的時候,對其進行賦值,數據發生改變,因為有8個字節,所以定義一個8位的cnt,進行判斷是否數據已經輸入完畢,數據的輸入采用循環移位拼接的方法來實現,當移位完畢的時候,flag關閉,以迎接ack信號的到來,計數清零。
State2 應答信號(ack)的檢測 接受完8bit數據后,會向IIC發送低電平脈沖,表示接收到了數據,此時時鍾SCL為高電平的時候,SDA轉為只讀,表示接收到了ack信號。
State9 發送停止信號 scl為高時,讓SDA由低到高表示一個停止信號。
在stop狀態之前又加了一狀態是為了給下一個狀態讓sda從低到高的上升沿信號,這樣才能發送STOP信號。
其余的狀態就是狀態1和2的重復
module IIC_rw ( input wire sclk, input wire rst_n, output reg scl, inout wire sda ); reg [23:0] count; reg clk_400k; reg [7:0] state; reg flag; reg data; reg [7:0] temp; reg [7:0] cnt; always @(posedge sclk or negedge rst_n) begin if(!rst_n) begin count <=0; clk_400k <=0; end else begin if(count==62) begin count <=0; clk_400k <=~clk_400k; end else count <= count +1; end end always @(posedge clk_400k or negedge rst_n) begin if(!rst_n) scl <=1; else scl <=~scl; end assign sda = flag ==1 ? data :1'bz; always @(negedge clk_400k or negedge rst_n) begin if(!rst_n) begin state <= 0; flag <= 1; data <= 1; temp <= 0; cnt <= 0; end else case (state) 0: if(scl) //啟動信號 begin flag <=1; state <=1; data <=0; temp <=8'b1010_0000; end 1: if(scl==0 && cnt<8) //控制信號 begin flag <=1; data <=temp[7]; cnt <= cnt +1; temp <= {temp[6:0],temp[7]};//左移位像最低位移動 end else if(scl==0&&cnt==8) begin flag <=0; state <=2; cnt <=0; end 2: if(scl==1) //ack begin // if(sda==0) begin state<=3; temp<=8'b0000_0000; //高字節 end // else // state <=0; end 3: if(scl==0&&cnt<8)//高字節 begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=4; end 4: if(scl==1) //ack begin // if(sda==0) begin state<=5; temp<=8'b0000_0000; //低字節 end // else // state <=0; end 5: if(scl==0&&cnt<8)//低字節輸入 begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=6; end 6: if(scl==1) //ACK begin // if(sda==0) begin state<=7; temp<=8'b0010_0101; //要輸入的數據 end // else // state <=0; end 7:if(scl==0&&cnt<8) begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=8; end 8: if(scl==1) //ack begin // if(sda==0) begin state<=9; end // else // state <=0; end 9: if(scl==0)//high to low begin flag <=1; data <=0; state <=10; end 10: if(scl==1)//STOP begin flag <=1; data <=1; state <=10; end default :state<=0; endcase end endmodule
仿真波形:
四 EEPROM 之隨機讀操作
共13個狀態,所以可以通過狀態機來書寫讀操作
涉及重點和難點:
NO ACK(非應答信號):
由上面隨機讀出的過程示意圖就可以看到,NO ACK 信號是個高電平,只能由FPGA通過SDA 數據線向EEPROM 發送。
上一狀態當SCL為低電平時,且數據讀完后,SDA 開關打開(flag=1)data為高電平,在NOack狀態下,SCL為低電平的時候,數據可讀寫,此時將data拉低,以備后來的STOP信號產生上升沿而用。
我們把NO ACK 和ACK 拉到一塊 ,就能聯想出來,ACK 信號是EEPROM 收到八位數據后反饋給FPGA的信號,那么 NO ACK 信號就是 FPGA 讀到八位數據后向EEPROM 發送的反饋信號。
module IIC_read (
input wire sclk,
input wire rst_n,
output reg scl,
inout wire sda,
output reg [7:0] result
);
reg [23:0] count;
reg clk_400k;
reg [7:0] state;
reg flag;
reg data;
reg [7:0] temp;
reg [7:0] cnt;
always @(posedge sclk or negedge rst_n)
begin
if(!rst_n)
begin
count <=0;
clk_400k <=0;
end
else
begin
if(count==62)
begin
count <=0;
clk_400k <=~clk_400k;
end
else
count <= count +1;
end
end
always @(posedge clk_400k or negedge rst_n)
begin
if(!rst_n)
scl <=1;
else
scl <=~scl;
end
assign sda = flag ==1 ? data :1'bz;
always @(negedge clk_400k or negedge rst_n)
begin
if(!rst_n)
begin
state <= 0;
flag <= 1;
data <= 1;
temp <= 0;
cnt <= 0;
result <= 0;
end
else
case (state)
0: if(scl) //啟動信號
begin
flag <=1;
state <=1;
data <=0;
temp <=8'b1010_0000;
result<=0;
end
1: if(scl==0 && cnt<8) //控制信號
begin
flag <=1;
data <=temp[7];
cnt <= cnt +1;
temp <= {temp[6:0],temp[7]};
end
else if(scl==0&&cnt==8)
begin
flag <=0;
state <=2;
cnt <=0;
end
2: if(scl==1) //ack
begin
if(sda==0)
begin
state<=3;
temp<=8'b0000_0000; //高字節
end
else
state <=0;
end
3: if(scl==0&&cnt<8)//高字節
begin
flag <=1;
data <=temp[7];
cnt <= cnt+1;
temp <={temp[6:0],temp[7]};
end
else if(scl ==0&&cnt==8)
begin
flag <=0;
cnt <=0;
state<=4;
end
4: if(scl==1) //ack
begin
if(sda==0)
begin
state<=5;
temp<=8'b0000_0000; //低字節
end
else
state <=0;
end
5: if(scl==0&&cnt<8)//低字節輸入
begin
flag <=1;
data <=temp[7];
cnt <= cnt+1;
temp <={temp[6:0],temp[7]};
end
else if(scl ==0&&cnt==8)
begin
flag <=0;
cnt <=0;
state<=6;
end
6: if(scl==1) //ACK
begin
if(sda==0)
begin
state<=7;
end
else
state <=0;
end
7: if(scl==0) // 拉高sda 備啟動信號使用 啟動信號是在scl為高電平的同時, sda由高電平到低電平
begin
flag <=1;
state <=8;
data <=1;
temp <=8'b1010_0001;
result<=0;
end
8: if(scl)//啟動信號 高到低
begin
data<=0;
state<=9;
end
9: if(scl==0 && cnt<8) //控制信號
begin
flag <=1;
data <=temp[7];
cnt <= cnt +1;
temp <= {temp[6:0],temp[7]};
end
else if(scl==0&&cnt==8)
begin
flag <=0;
state <=10;
cnt <=0;
end
10: if(scl==1) //ACK
begin
if(sda==0)
begin
state<=11;
end
else
state <=0;
end
11: if(scl==1&&cnt<8)//讀 讀的時候為高電平
begin
flag <=0;
result <={result[6:0],sda};
cnt <=cnt+1;
end
else if(scl==0&&cnt==8)
begin
flag <=1;
cnt <=0;
state <=12;
data <=1;
end
12: if(scl==0) //NOack
begin
if(sda==1)
begin
state <=13;
data <=0;
end
else
state <=0;
end
13: if(scl==1)//STOP
begin
flag <=1;
data <=1;
state <=13;
end
// 14: if(scl==1)//進入非忙態
// begin
// flag <=1;
// data <=1;
// state <=14;
// end
default :state<=0;
endcase
end
endmodule
讀信號時的波形圖: