一、I2C總線介紹
??I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線,它只需要兩根線即可在連接于總線上的器件之間傳送資訊,
??主器件用于啟動總線傳送資料,并產生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發和收的關系不是恒定的,而取決于此時資料傳送方向,如果主機要發送資料給從器件,則主機首先尋址從器件,然后主動發送資料至從器件,最后由主機終止資料傳送;如果主機要接收從器件的資料,首先由主器件尋址從器件.然后主機接收從器件發送的資料,最后由主機終止接收程序,在這種情況下.主機負責產生定時時鐘和終止資料傳送,
??I2C總線是公認的世界標準,由50多家公司生產超過1000個不同的地方實施的集成電路,此外,通用的i2c總線用于各種控制體系結構,如系統管理總線(SMBus),電源管理總線(PMBus),智能平臺管理介面(IPMI),顯示幕資料通道(DDC)和高級電信計算架構(ATCA),
??I2C總線的原理和細節參考這篇文章,感謝作者的整理,受益匪淺,
二、I2C總線協議
-
串行資料線(SDA)和串行時鐘線(SCL)
??SDA和SCL都是雙向的線路,通過電流源或上拉電阻連接到一個正的供應電壓,連接到總線的設備的輸出級必須有一個開路漏極或開路集電極來執行線和功能,I2C-bus上的資料可以在標準模式下以高達100kbit /s的速率傳輸,在快速模式下以高達400kbit /s的速率傳輸,在快速模式+下為1 Mbit/s,在高速模式下最高為3.4 Mbit/s,總線電容限制連接到總線的介面數量, -
資料有效性
??SDA線上的資料必須在SCL時鐘線時鐘高電平期間保持穩定,在時鐘低電平期間改變(見下圖),因此我們在設計信號時,最佳情況就是在時鐘線SCL為低電平中間時SDA資料線上的資料改變,在時鐘高電平中間時獲取SDA資料線上的資料,

-
起始和終止位
??所有資料的傳輸都以START ( S )開始,以STOP ( P )結束(見下圖),
??I2C總線空閑:SDA和SCL均為高電平;
??I2C協議起始位:SCL為高電平時,SDA出現下降沿;
??I2C協議終止位:SCL為高電平時,SDA出現上升沿,
??啟動和停止條件總是由主設備生成,在啟動條件后,總線被認為是忙碌的,該總線在停止條件后的某一段時間內再次空閑,如果生成了重復啟動(Sr)而不是停止條件,則總線將保持忙碌狀態,在這方面,啟動(S)和重復啟動(Sr)條件在功能上是相同的, -
傳輸1位元組格式
??在SDA資料線上傳輸的每個位元組長度必須是8位,每次傳輸可以傳輸的位元組數是不受限制的,每個位元組后面必須跟著一個應答位(ACK),資料從位元組最高位(MSB)開始傳輸(見下圖),如果一個從設備無法接識訓發送另一個完整的位元組的資料,直到執行一些其他功能,例如服務內部中斷,它可以維持時鐘線scl為低強迫主設備進入等待狀態,當從設備準備好另一個位元組的資料后繼續傳輸資料并釋放時鐘線SCL,

-
應答(ACK)與非應答(NACK)
??應答發生在每個位元組之后,應答位回應,表明該位元組已成功接收,并且可以發送另一個位元組,主設備產生所有時鐘脈沖,包括確認位第九個時鐘脈沖,應答信號定義如下:在回應時鐘脈沖期間,發射機釋放SDA線,接識訓可以把SDA線拉低,,并且在時鐘高電平期間穩定保持低電平,
??當SDA在第9個時鐘脈沖期間保持高時,這被定義為非應答信號,然后,主設備可以生成終止傳輸 的停止條件,或者生成啟動新傳輸的重復啟動條件,NACK的產生有五個條件:
??- 總線上沒有帶有所傳輸地址的接收者,因此沒有設備以應答,
??- 接收器無法接識訓發送,因為它正在執行一些實時功能,并沒有準備好開始與主控通信,
??- 在傳輸程序中,接收方獲取它不理解的資料或命令,
??- 在傳輸程序中,接收器無法接收到更多的資料位元組,
??- 主控接識訓必須將傳輸結束的信號發送給從發射機,
- 從設備器件地址和讀寫位
??從設備地址是I2C協議在傳輸資料時對總線上設備尋址的依據,資料傳輸遵循下圖所示的格式,在啟動條件之后,發送一個從設備器件地址,這個地址有7位長,后面跟著一個資料方向位(R/W)——“0”表示傳輸(寫),“1”表示對資料的請求(讀)(參見下2圖),資料傳輸總是以主設備產生的停止條件§結束,然而,如果一個主人仍然希望在總線上通信,它可以產生一個重復的開始條件(Sr)和地址而不用再產生一個停止條件,

??從設備器件地址通常是由固定為和可變位組合而成的,所謂固定位就是器件本就確定無法更改的,認為不能讓控制的的,而可變位通常是器件的硬體,可供用戶進行硬體連接,按照用戶的硬體連接確定,例如下圖中7位器件地址中,前四位1010是出廠時就已經固定了的,而后三位是器件硬體引腳可供用戶改變的,
??對不同的器件,I2C傳輸格式略有不同,對于存盤設備,還具有存盤器的地址號,在主設備發送器件地址,從設備存盤器回應后,主設備要再發8或16位存盤器地址資料,來選擇存盤器的地址,等待從設備回應后,主設備在發送資料到存盤器地址或從存盤器地址讀取資料,
三、代碼實作
??I2C介面具有嚴格的讀寫時序,包括寫資料時序、讀資料時序、連續寫資料以及連續讀時序,本次實驗僅實作寫資料時序和讀資料時序,I2C資料傳輸都是高位優先,


??根據I2C的讀寫時序圖,我們可以發現,讀寫時序傳輸順序:
- 首先,主設備在SCL為高電平時拉低SDA,產生一個起始信號;
- 主設備發送從設備地址位(R/W位為0,寫入),在之后的一個時鐘周期,主設備釋放SDA總線的控制,從設備將SDA拉低,產生一個應答位(ACK);
- 主設備發送要寫入或讀取的暫存器地址位,在之后的一個時鐘周期,主設備釋放SDA總線的控制,從設備將SDA拉低,產生一個應答位(ACK);
- (寫入資料時) 主設備發送要寫入的資料,在之后的一個時鐘周期,主設備釋放SDA總線的控制,從設備將SDA拉低,產生一個應答位(ACK);
- (讀取資料時) 主設備再次產生一個起始信號(這里非常重要!),然后再次發送從設備地址位(R/W位為1,讀取),主設備釋放SDA總線控制,從設備產生應答位,然后從設備發送資料,主設備不產生應答位;
- 最后,主設備在SCL高電平期間,拉高SDA,產生一個結束信號,
??所以可以據此畫出狀態機圖:

廢話不多說,上代碼:
module IICModule(
input CLK, // 50MHz時鐘頻率
input reset,
input start, // 啟動信號,注意!!!啟動信號高電平持續時間最好在一個scl高/低周期左右,不可以很長
input WR_OR_RE, // 讀/寫控制,0=寫,1=讀
input [6:0]DeviecAddr, // 從設備器件地址,7bit
input [7:0] RegisterAddr, // 暫存器地址
input [7:0] WriteData, // 要寫入的資料
output reg scl,
inout sda,
output reg done,
output reg [7:0] readData // 從從設備讀取的8bit資料
);
parameter CLK_FREQ = 50_000_000 , SCL_FREQ = 100_000; // SCL的頻率設定為100kHz
localparam SCL_CNT = CLK_FREQ/(2*SCL_FREQ); // 為使signalTap能夠觀察足夠長的視窗時間,我提高了頻率,實際不需要除2
// 狀態機的狀態標識
parameter IDLE =4'd0 ,START =4'd1 ,WR_ADDR =4'd2 ,WR_REGISTER =4'd3 , WR_DATA =4'd4 ,RE_START =4'd5 ,RE_ADDR =4'd6 ,RE_DATA =4'd7 ,STOP =4'd8 ;
reg [7:0] state = IDLE , next_state = IDLE;
// 設備地址
// parameter DeviecAddr = 7'b0011101;
reg [7:0] DeviecAddr_wr;
reg [7:0] DeviecAddr_re;
always@(*)begin
DeviecAddr_wr = {DeviecAddr , 1'b0};
DeviecAddr_re = {DeviecAddr , 1'b1};
end
// scl & scl_cnt ---- 50MHz時鐘生成100kHzscl時鐘
reg [7:0] scl_cnt;
always@(posedge CLK)begin
if(reset)begin
scl <= 1'b1;
scl_cnt <= 8'd0;
end
else begin
if(scl_cnt == (SCL_CNT/2-1))begin
scl <= ~scl;
scl_cnt <= 8'd0;
end
else begin
scl_cnt <= scl_cnt + 1'b1;
end
end
end
// scl_midHigh ---- 當處于scl高電平段的中間時,置1
reg scl_midHigh;
always@(posedge CLK)begin
if(reset)begin
scl_midHigh <= 1'b0;
end
else begin
if((scl_cnt == (SCL_CNT/4-1)) & (scl == 1'b1))begin
scl_midHigh <= 1'b1;
end
else begin
scl_midHigh <= 1'b0;
end
end
end
// scl_midLow ---- 當處于scl低電平段的中間時,置1
reg scl_midLow;
always@(posedge CLK)begin
if(reset)begin
scl_midLow <= 1'b0;
end
else begin
if((scl_cnt == (SCL_CNT/4-1)) & (scl == 1'b0))begin
scl_midLow <= 1'b1;
end
else begin
scl_midLow <= 1'b0;
end
end
end
// bit_cnt ---- 寫器件地址、暫存器、資料等都是一位元組(8bit)
// 在scl_midLow和scl_midHigh時均計數(8bit資料位 + 1bit應答位,所以計數范圍為0~17)
reg [4:0] bit_cnt;
always@(posedge CLK)begin
if(reset)begin
bit_cnt <= 5'd0;
end
else begin
case(state)
IDLE: bit_cnt <= 5'd0;
START: bit_cnt <= 5'd0;
WR_ADDR: begin
if(scl_midHigh | scl_midLow)begin
bit_cnt <= (bit_cnt == 5'd17 & scl_midLow)?5'd0:(bit_cnt + 1'b1);
end
else begin
bit_cnt <= bit_cnt + 1'b0;
end
end
WR_REGISTER: begin
if(scl_midHigh | scl_midLow)begin
bit_cnt <= (bit_cnt == 5'd17 & scl_midLow)?5'd0:(bit_cnt + 1'b1);
end
else begin
bit_cnt <= bit_cnt + 1'b0;
end
end
WR_DATA: begin
if(scl_midHigh | scl_midLow)begin
bit_cnt <= (bit_cnt == 5'd17 & scl_midLow)?5'd0:(bit_cnt + 1'b1);
end
else begin
bit_cnt <= bit_cnt + 1'b0;
end
end
RE_START: bit_cnt <= 5'd0;
RE_ADDR: begin
if(scl_midHigh | scl_midLow)begin
bit_cnt <= (bit_cnt == 5'd17 & scl_midLow)?5'd0:(bit_cnt + 1'b1);
end
else begin
bit_cnt <= bit_cnt + 1'b0;
end
end
RE_DATA: begin
if(scl_midHigh | scl_midLow)begin
bit_cnt <= (bit_cnt == 5'd17 & scl_midLow)?5'd0:(bit_cnt + 1'b1);
end
else begin
bit_cnt <= bit_cnt + 1'b0;
end
end
STOP: bit_cnt <= 5'd0;
default: bit_cnt <= 5'd0;
endcase
end
end
// link ---- 控制sda(inout信號)的控制權,link == 0 時從設備控制, link == 1 時主設備控制
reg link;
assign sda = link?sda_reg:1'bz; // 1'bz表示由從設備控制資料總線
always@(posedge CLK)begin
if(reset)begin
link <= 1'b1;
end
else begin
case(state)
IDLE: link <= 1'b1;
START: link <= 1'b1;
WR_ADDR: begin
if(bit_cnt < 5'd16)begin
link <= 1'b1;
end
else begin
link <= 1'b0;
end
end
WR_REGISTER: begin
if(bit_cnt < 5'd16)begin
link <= 1'b1;
end
else begin
link <= 1'b0;
end
end
WR_DATA: begin
if(bit_cnt < 5'd16)begin
link <= 1'b1;
end
else begin
link <= 1'b0;
end
end
RE_START: link <= 1'b1;
RE_ADDR: begin
if(bit_cnt < 5'd16)begin
link <= 1'b1;
end
else begin
link <= 1'b0;
end
end
RE_DATA: begin
if(bit_cnt < 5'd16)begin
link <= 1'b0;
end
else begin
link <= 1'b1; // 此時主設備不會產生應答信號,但是也要將控制權交給主設備
end
end
STOP: link <= 1'b1;
default: link <= 1'b1;
endcase
end
end
// ack ---- 當主設備發送8bit資料后,將sda控制權釋放,從設備將sda拉低產生應答位
reg ack;
always@(posedge CLK)begin
if(reset)begin
ack <= 1'd0;
end
else begin
case(state)
IDLE: ack <= 1'd0;
START: ack <= 1'd0;
WR_ADDR: begin
if(bit_cnt < 5'd16)begin
ack <= 1'b0;
end
else if(bit_cnt == 5'd16 & scl_midHigh & ~sda)begin
ack <= 1'b1;
end
else
ack <= ack + 1'b0;
end
WR_REGISTER: begin
if(bit_cnt < 5'd16)begin
ack <= 1'b0;
end
else if(bit_cnt == 5'd16 & scl_midHigh & ~sda)begin
ack <= 1'b1;
end
else
ack <= ack + 1'b0;
end
WR_DATA: begin
if(bit_cnt < 5'd16)begin
ack <= 1'b0;
end
else if(bit_cnt == 5'd16 & scl_midHigh & ~sda)begin
ack <= 1'b1;
end
else
ack <= ack + 1'b0;
end
RE_START: ack <= 1'b0;
RE_ADDR: begin
if(bit_cnt < 5'd16)begin
ack <= 1'b0;
end
else if(bit_cnt == 5'd16 & scl_midHigh & ~sda)begin
ack <= 1'b1;
end
else
ack <= ack + 1'b0;
end
RE_DATA: begin
if(bit_cnt < 5'd16)begin
ack <= 1'b0;
end
else if(bit_cnt == 5'd16 & scl_midHigh & ~sda)begin
ack <= 1'b1;
end
else
ack <= ack + 1'b0;
end
STOP: ack <= 1'b0;
default: ack <= 1'b0;
endcase
end
end
// sda_reg_cnt ---- 8bit資料計數,用作資料陣列索引
reg [3:0] sda_reg_cnt;
always@(posedge CLK)begin
if(reset)begin
sda_reg_cnt <= 4'd7;
end
else begin
case(state)
IDLE: sda_reg_cnt <= 4'd7;
START: sda_reg_cnt <= 4'd7;
WR_ADDR: begin
if(bit_cnt < 5'd16 & scl_midLow )begin
sda_reg_cnt <= (sda_reg_cnt == 4'd0)?4'd7:(sda_reg_cnt - 1'b1);
end
else begin
sda_reg_cnt <= sda_reg_cnt + 1'b0;
end
end
WR_REGISTER: begin
if(bit_cnt < 5'd16 & scl_midLow )begin
sda_reg_cnt <= (sda_reg_cnt == 4'd0)?4'd7:(sda_reg_cnt - 1'b1);
end
else begin
sda_reg_cnt <= sda_reg_cnt + 1'b0;
end
end
WR_DATA: begin
if(bit_cnt < 5'd16 & scl_midLow )begin
sda_reg_cnt <= (sda_reg_cnt == 4'd0)?4'd7:(sda_reg_cnt - 1'b1);
end
else begin
sda_reg_cnt <= sda_reg_cnt + 1'b0;
end
end
RE_START: sda_reg_cnt <= 4'd7;
RE_ADDR: begin
if(bit_cnt < 5'd16 & scl_midLow )begin
sda_reg_cnt <= (sda_reg_cnt == 4'd0)?4'd7:(sda_reg_cnt - 1'b1);
end
else begin
sda_reg_cnt <= sda_reg_cnt + 1'b0;
end
end
RE_DATA: sda_reg_cnt <= 4'd7;
STOP: sda_reg_cnt <= 4'd7;
default: sda_reg_cnt <= 4'd7;
endcase
end
end
// sda_reg ---- 在不同state時sda輸出不同的值,使用sda_reg來控制
reg sda_reg;
always@(posedge CLK)begin
if(reset)begin
sda_reg <= 1'b1;
end
else begin
case(state)
IDLE: sda_reg <= 1'b1;
START: sda_reg <= 1'b0;
WR_ADDR: begin
if(bit_cnt < 5'd16)begin
sda_reg <= DeviecAddr_wr[sda_reg_cnt];
end
else begin
sda_reg <= 1'b1;
end
end
WR_REGISTER: begin
if(bit_cnt < 5'd16)begin
sda_reg <= RegisterAddr[sda_reg_cnt];
end
else begin
sda_reg <= 1'b1; // 這里sda_reg <= 1'b1是為了state==RE_START時方便產生start信號
end
end
WR_DATA: begin
if(bit_cnt < 5'd16)begin
sda_reg <= WriteData[sda_reg_cnt];
end
else begin
sda_reg <= 1'b0; // 這里sda_reg <= 1'b0是為了state==STOP時方便產生stop信號
end
end
RE_START: begin
if(scl_midHigh & scl)begin
sda_reg <= 1'b0;
end
else begin
sda_reg <= sda_reg;
end
end
RE_ADDR: sda_reg <= DeviecAddr_re[sda_reg_cnt];
RE_DATA: sda_reg <= 1'b0; // 這里sda_reg <= 1'b0是為了state==STOP時方便產生stop信號
STOP: begin // 產生stop信號
if(scl_midHigh & scl)
sda_reg <= 1'b1;
else
sda_reg <= sda_reg + 1'b0;
end
default: sda_reg <= 1'b1;
endcase
end
end
// readData ---- 存盤從設備發回的資料
always@(posedge CLK)begin
if(reset)begin
readData <= 8'd0;
end
else begin
case(state)
RE_DATA: begin
if(bit_cnt < 5'd16 & scl_midHigh)begin
readData <= {readData[6:0],sda};
//readData[sda_reg_cnt] <= sda;
end
else begin
readData <= readData + 1'b0;
end
end
default: readData <= readData + 1'b0;
endcase
end
end
// done ---- 一次讀/寫資料完成后置1
always@(posedge CLK)begin
if(reset)begin
done <= 1'b0;
end
else begin
if(state == STOP)
done <= 1'b1;
else
done <= 1'b0;
end
end
// 狀態機第一段
always@(posedge CLK)begin
if(reset)begin
state <= IDLE;
end
else begin
state <= next_state;
end
end
// 狀態機第二段
always@(*)begin
case(state)
IDLE: next_state = (start&scl)?START:IDLE;
START: next_state = (scl_midLow&~scl)?WR_ADDR:START;
WR_ADDR: begin
if(bit_cnt == 5'd17 && scl_midLow && ack)
next_state = WR_REGISTER;
else if(bit_cnt == 5'd17 && scl_midLow && ~ack)
next_state = IDLE;
else
next_state = WR_ADDR;
end
WR_REGISTER: begin
if(bit_cnt == 5'd17 && scl_midLow && ack)begin
next_state = WR_OR_RE?RE_START:WR_DATA;
end
else if(bit_cnt == 5'd17 && scl_midLow && ~ack)
next_state = IDLE;
else
next_state = WR_REGISTER;
end
WR_DATA: begin
if(bit_cnt == 5'd17 && scl_midLow && ack)
next_state = STOP;
else if(bit_cnt == 5'd17 && scl_midLow && ~ack)
next_state = IDLE;
else
next_state = WR_DATA;
end
RE_START: begin
if(scl_midLow & ~scl)begin
next_state = RE_ADDR;
end
else begin
next_state = RE_START;
end
end
RE_ADDR: begin
if(bit_cnt == 5'd17 && scl_midLow && ack)
next_state = RE_DATA;
else if(bit_cnt == 5'd17 && scl_midLow && ~ack)
next_state = IDLE;
else
next_state = RE_ADDR;
end
RE_DATA: begin
if(bit_cnt == 5'd17 && scl_midLow )
next_state = STOP;
else
next_state = RE_DATA;
end
STOP: begin
if(scl_midLow & ~scl)
next_state = IDLE;
else
next_state = STOP;
end
default: next_state = IDLE;
endcase
end
endmodule
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/355402.html
標籤:其他
