FPGA學習之路——I2C協議詳解+Verilog源碼分析
定義
I2C Bus(Inter-Integrated Circuit Bus) 最先是由Philips半導體(現被NXP收購)開發的兩線時串行總線,經常使用於微控制器與外設之間的鏈接。I2C僅需兩根線就能夠支持一主多從或者多主鏈接,主要優勢爲簡單、便宜、可靠性高,I2C總線示意圖以下。源碼分析
SDA(Serial Data):串行數據線
SCL(Serial Clock):串行時鐘線學習
一、I2C總線共兩條雙向串行線,SDA爲串行數據線,SCL爲串行時鐘線。
二、SDA上的數據傳輸爲最大端傳輸(先發送MSB,最後發送LSB),每次傳輸1個字節。
三、支持多主控,但任什麼時候間只能有一個主控。
四、總線上每一個設備都有本身的地址,共7個bit,第8個bit存放主設備對從設備的讀/寫操做信息,廣播地址全0。
工做流程
一、I2C位傳輸
數據傳輸: SCL爲高電平時,若SDA線保持穩定,那麼SDA線上在進行數據的傳輸或是空閒態;若SDA線發生跳變,則表示一個會話的開始或者結束。
數據改變: SDA僅能在SCL爲低電平時改變傳輸的bit,不然表示會話狀態的改變。ui
二、I2C開始和結束信號
開始信號: SCL爲高電平時,SDA由高電平向低電平跳變,開始數據的傳送。
結束信號: SCL爲高電平時,SDA由低電平向高電平跳變,結束數據的傳送。.net
三、I2C應答信號
Master每發送完8bit數據後交出SDA的控制權,等待Slave的ACK。也就是在第9個Clock,若從設備發ACK,那麼SDA會被拉低。若是Master未收到從設備的ACK,那麼SDA會被拉高,這會致使Master發生RESTART或者STOP流程。設計
四、I2C寫流程
一、Master在SCL爲高電平期間,拉低SDA,發起START。
二、Master發送設備地址(7bit)和寫操做0(1bit),等待ACK。
三、對應的Slave迴應ACK。
四、Master發送寄存器地址(8bit),等待ACK。
五、對應的Slave迴應ACK。
六、Master發送數據(8bit),也就是要寫入Slave寄存器中的數據,等待ACK。
七、對應的Slave迴應ACK。
八、其中的6,7步可重複執行屢次,即按順序對多個寄存器進行寫操做。
九、Master發起STOP。blog
五、I2C讀流程
一、Master在SCL爲高電平期間,拉低SDA,發起START。
二、Master發送設備地址(7bit)和寫操做0(1bit),等待ACK。
三、Slave發送ACK。
四、Master發送寄存器地址(8bit),等待ACK。
五、Slave發ACK。
六、Master發起START。
七、Master發送I2C設備地址(7bit)和讀操做1(1bit),等待ACK。
八、Slave發送ACK。
九、Slave發送data(以字節爲單位),即對應寄存器中的值。
十、Master發送ACK。
十一、第9步和第10步可重複進行屢次,即按順序讀多個寄存器。ip
Verilog代碼分析
本博客中所示代碼片斷爲《VERILOG HDL應用程序設計實例精講》提供的例程,僅供學習用途。開發
時鐘操做是I2C設計的關鍵部分,爲更清楚的分析I2C時序關係,咱們將一個時鐘週期分爲4個部分a,b,c,d,以下圖所示。源碼
Verilog代碼以下:博客
case(startcnt)
2'b00:
begin
scl <= 1'b0; //時鐘a段
startcnt <= 2'b01;
end
2'b01:
begin
scl <= 1'b1; //時鐘b段
startcnt <= 2'b10;
end
2'b10:
begin
scl <= 1'b1; //時鐘c段
startcnt <= 2'b11;
end
2'b11:
begin
scl <= 1'b0; //時鐘d段
startcnt <= 2'b00;
end
default:
begin
scl <= 1'b0;
startcnt <= 2'b00;
end
endcase
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
I2C總線START信號的特徵:在時鐘信號SCL爲高電平時,數據信號SDA由高到低跳變,Verilog代碼以下:
assign sda=(link)? sda_buf:1'bz; //link爲是否傳輸數據的標誌,sda_buf爲sda的寄存器。
//sda爲雙向端口。
start:begin
case(startcnt)
2'b00:begin
scl<=1'b1;
sda_buf<=1'b1; //時鐘信號保持爲高,sda數據設爲高。
link<=1'b1; //表示Master此時將sda_buf中的數據送到sda線上
startcnt<=2'b01;
end
2'b01:begin
scl<=1'b1;
sda_buf<=1'b0; //時鐘信號保持爲高,sda數據設爲低,完成START。
link<=1'b1;
startcnt<=2'b10;
end
2'b10:begin
scl<=1'b0;
sda_buf<=1'b0; //時鐘信號和數據信號均爲低。
link<=1'b1;
startcnt<=2'b11;
end
2'b11:begin
scl<=1'b0;
sda_buf<=1'b0;
link<=1'b1;
startcnt<=2'b00;
inner_state<=first; //完成START操做後,進行後續操做,改變外層狀態。
end
default:begin
scl<=1'b1;
sda_buf<=1'b1;
link<=1'b1;
startcnt<=2'b00;
inner_state<=start;
end
endcase
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
I2C主設備發送從設備的地址信號和讀寫標誌,其中地址信號的發送順序爲從高位至低位。
assign sda=(link)? sda_buf:1'bz; //link爲是否傳輸數據的標誌,sda_buf爲sda的寄存器。
//sda爲雙向端口。
case(startcnt)
2'b00:begin
scl<=1'b0; //此時時鐘線爲低,沒法進行數據傳輸。
sda_buf<=chipaddr[7]; //從設備地址最高位
link<=1'b1;
startcnt<=2'b01;
end
2'b01:begin
scl<=1'b1; //此時時鐘線爲高,進行數據傳輸。
sda_buf<=chipaddr[7]; //從設備地址最高位
link<=1'b1;
startcnt<=2'b10;
end
2'b10:begin
scl<=1'b1;
sda_buf<=chipaddr[7]; //scl爲高時,sda需維持不變,不然會開始或終止當前會話。
link<=1'b1;
startcnt<=2'b11;
end
2'b11:begin
scl<=1'b0;
sda_buf<=chipaddr[7];
link<=1'b1;
startcnt<=2'b00;
inner_state<=second; //進行下一步操做,傳輸地址的次高bit。
end
default:begin
scl<=1'b1;
sda_buf<=chipaddr[7];
link<=1'b1;
startcnt<=2'b00;
inner_state<=first;
end
endcase
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
上面的代碼用於發送從設備的地址和讀寫標誌,重複7次便可完成該操做。發送完從設備的地址信號的讀寫標誌後,接着Master需檢測從設備發送的應答信號,Verilog代碼以下。
assign sda=(link)? sda_buf:1'bz; //link爲是否傳輸數據的標誌,sda_buf爲sda的寄存器。
//sda爲雙向端口。
ack:begin
case(startcnt)
2'b00:begin
scl<=1'b0;
link<=1'b0; //更改數據信號sda爲輸入。
startcnt<=2'b01;
end
2'b01:begin
scl<=1'b1;
link<=1'b0;
startcnt<=2'b10;
end
2'b10:begin
scl<=1'b1;
link<=1'b0;
sta_buf<=sda; //在時鐘線保持高電平時,採樣數據信號sda的值。
startcnt<=2'b11;
end
2'b11:begin
scl<=1'b0;
link<=1'b0;
startcnt<=2'b00;
if(sda_buf==1'b0) //若接收的sda數據爲低,則表示檢測到從設備的應答信號
begin
inner_state<=first; //返回初始狀態
i2c_state<=sendaddr; //發送從設備地址成功後,進入下一階段。
//發送寄存器地址。
link<=1'b1; //主設備從新在sda數據線上進行數據傳輸。
end
else begin
main_state<=3'b000; //回到等待讀寫請求狀態。
inner_state<=start;
end
end
endcase
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
I2C總線中止信號的特徵:在時鐘信號scl爲高電平時,sda由低到高進行跳變,Verilog代碼以下。
assign sda=(link)? sda_buf:1'bz; //link爲是否傳輸數據的標誌,sda_buf爲sda的寄存器。
//sda爲雙向端口。
stop:begin
case(startcnt)
2'b00:begin //時鐘和數據信號都爲低
scl<=1'b0;
sda_buf<=1'b0;
link<=1'b1;
startcnt<=2'b01;
end
2'b01:begin //時鐘信號變爲高,數據信號爲低
scl<=1'b1;
sda_buf<=1'b0;
link<=1'b1;
startcnt<=2'b10;
end
2'b10:begin //不在此週期內改變數據信號。
//保證時鐘信號穩定後再進行數據信號的變化.
scl<=1'b1;
sda_buf<=1'b0;
link<=1'b1;
startcnt<=2'b11;
end
2'b11:begin //時鐘信號保持高,數據信號設爲高,完成從低到高的跳變。
scl<=1'b1;
sda_buf<=1'b1;
link<=1'b1;
startcnt<=2'b00;
inner_state<=start;
i2c_state<=ini;
main_state<=2'b00;
end
default:begin
scl<=1'b1;
sda_buf<=1'b0;
link<=1'b1;
startcnt<=2'b00;
inner_state<=ack;
end
endcase
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
將以上的代碼段組合起來,基本就能夠實現I2C協議了。
———————————————— 版權聲明:本文爲CSDN博主「XDU_David」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/qq_42334072/article/details/105874692