FPGA学习之路—接口(2)—I2C协议详解+Verilog源码分析
FPGA学习之路——I2C协议详解+Verilog源码分析
定义
I2C Bus(Inter-Integrated Circuit Bus) 最早是由Philips半导体(现被NXP收购)开发的两线时串行总线,常用于微控制器与外设之间的连接。I2C仅需两根线就可以支持一主多从或者多主连接,主要优点为简单、便宜、可靠性高,I2C总线示意图如下。
- SDA(Serial Data):串行数据线
- SCL(Serial Clock):串行时钟线
1、I2C总线共两条双向串行线,SDA为串行数据线,SCL为串行时钟线。
2、SDA上的数据传输为最大端传输(先发送MSB,最后发送LSB),每次传输1个字节。
3、支持多主控,但任何时间只能有一个主控。
4、总线上每个设备都有自己的地址,共7个bit,第8个bit存放主设备对从设备的读/写操作信息,广播地址全0。
工作流程
1、I2C位传输
数据传输: SCL为高电平时,若SDA线保持稳定,那么SDA线上在进行数据的传输或是空闲态;若SDA线发生跳变,则表示一个会话的开始或者结束。
数据改变: SDA仅能在SCL为低电平时改变传输的bit,否则表示会话状态的改变。
2、I2C开始和结束信号
开始信号: SCL为高电平时,SDA由高电平向低电平跳变,开始数据的传送。
结束信号: SCL为高电平时,SDA由低电平向高电平跳变,结束数据的传送。
3、I2C应答信号
Master每发送完8bit数据后交出SDA的控制权,等待Slave的ACK。也就是在第9个Clock,若从设备发ACK,那么SDA会被拉低。如果Master未收到从设备的ACK,那么SDA会被拉高,这会导致Master发生RESTART或者STOP流程。
4、I2C写流程
1、Master在SCL为高电平期间,拉低SDA,发起START。
2、Master发送设备地址(7bit)和写操作0(1bit),等待ACK。
3、对应的Slave回应ACK。
4、Master发送寄存器地址(8bit),等待ACK。
5、对应的Slave回应ACK。
6、Master发送数据(8bit),也就是要写入Slave寄存器中的数据,等待ACK。
7、对应的Slave回应ACK。
8、其中的6,7步可重复执行多次,即按顺序对多个寄存器进行写操作。
9、Master发起STOP。
5、I2C读流程
1、Master在SCL为高电平期间,拉低SDA,发起START。
2、Master发送设备地址(7bit)和写操作0(1bit),等待ACK。
3、Slave发送ACK。
4、Master发送寄存器地址(8bit),等待ACK。
5、Slave发ACK。
6、Master发起START。
7、Master发送I2C设备地址(7bit)和读操作1(1bit),等待ACK。
8、Slave发送ACK。
9、Slave发送data(以字节为单位),即对应寄存器中的值。
10、Master发送ACK。
11、第9步和第10步可重复进行多次,即按顺序读多个寄存器。
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
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
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
上面的代码用于发送从设备的地址和读写标志,重复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
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
将以上的代码段组合起来,基本就可以实现I2C协议了。
- FPGA学习之路—接口(3)—SPI详解及Verilog源码分析
- FPGA学习之路—应用程序—原码二位乘法器及Verilog代码分析
- LwIP 协议栈源码详解 ——TCP/IP 协议的实现(六:网络接口结构)
- Android开发学习之路-LruCache使用和源码分析
- MINA源码分析---协议解码输出接口ProtocolDecoderOutput及其实现
- Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析
- leveldb 源码分析---接口详解之include文件
- 微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—下篇
- SSH学习(十)Hibernate常用API详解及源码分析
- Netty学习:ChannelHandler执行顺序详解,附源码分析
- [EntLib]微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—上篇
- spring源码学习之路---深度分析IOC容器初始化过程(四)
- [EntLib]微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—下篇
- JavaSE学习随笔(一) Cloneable接口源码分析与技术细节
- spring源码学习之路---深度分析IOC容器初始化过程(四)
- (转)Bootstrap 之 Metronic 模板的学习之路 - (2)源码分析之 head 部分
- mybatis源码学习之执行过程分析(3)——mapper接口的获取
- Cloudera Impala源码分析: SimpleScheduler调度策略详解包括作用、接口及实现等
- [原创]java WEB学习笔记70:Struts2 学习之路-- struts2拦截器源码分析,运行流程
- 详解关于Lua源码分析学习教程