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

相关文章
相关标签/搜索