FPGA學習之路—接口(2)—I2C協議詳解+Verilog源碼分析

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

相關文章
相關標籤/搜索