通信协议篇——I2C通信

1.简介

I2C(Inter-Integrated Circuit)是一种串行通信总线,总线上可以挂多个设备,可实现同步半双工通信。

2.原理

通信方式

I2C通信属于串行通信,使用串行数据线SDA和串行时钟线SCL两线实现同步半双工通信。

同步 接收端时钟频率和发送端时钟频率一致
异步 接收端时钟频率和发送端时钟频率不一致

同步通信和异步通信的区别:

  • 异步通信中的接收方并不知道数据什么时候会到达,收发双方可以有各自自己的时钟。发送方发送的时间间隔可以不均,接收方是在数据的起始位和停止位的帮助下实现信息同步的。这种传输通常是很小的分组,比如一个字符为一组,为这个组配备起始位和结束位。所以这种传输方式的效率是比较低的,毕竟额外加入了很多的辅助位作为负载,常用在低速的传输中。
  • 同步通信中双方使用频率一致的时钟 ,它的分组相比异步通信则大得多,称为一个数据帧,通过独特的bit串作为启停标识。发送方要以固定的节奏去发送数据,而接收方要时刻做好接收数据的准备,识别到前导码后马上要开始接收数据了。同步这种方式中因为分组很大,很长一段数据才会有额外的辅助位负载,所以效率更高,更加适合对速度要求高的传输,当然这种通信对时序的要求也更高。
单工 在任何时间,数据只能单向传输
半双工 能够双向通信,但通信双方不能同时进行数据收发,在同一时刻只有一方发送另一方接收
全双工 能够双向通信,且通信双方能够同时进行数据收发,两者同步进行

I2C通信中,主机通过时钟线SCL发送时钟信号,通过数据线SDA发送数据(包括从机地址、指令、数据包等),在发送完一帧数据后,需要等待从机的响应,才能继续发送下一帧数据,因此I2C属于同步通信。

I2C通信中,数据在一根数据线SDA上传输,同一时刻数据传输的方向只能是单向的,从A到B或者从B到A;通过切换传输方向从而实现双向通信,因此I2C属于半双工通信。

数据格式

I2C通信的数据包大小为8bit,主要有三类——指令、字节地址、数据。数据传输时,按照高位在前,低位在后的顺序(即MSB First,LSB Last)。

类型 格式
指令 7位从机地址+1位读写命令(写0,读1)
字节地址 8位字节地址,从这个地址开始读写数据
数据 8位数据

I2C通信通过时钟线SCL和数据线SDA确定几种通信状态——空闲状态、启动信号、停止信号、数据位传输、应答信号。

空闲状态

当I2C总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

启动信号

在时钟线SCL保持高电平期间,数据线SDA上的下降沿,定义为I2C总线的启动信号,它标志着一次数据传输的开始。启动信号是由主机建立的,在建立该信号之前,I2C总线必须处于空闲状态。

停止信号

在时钟线SCL保持高电平期间,数据线SDA上的上升沿,定义为I2C总线的停止信号,它标志着一次数据传输的终止。停止信号是由主机建立的,建立该信号之后,I2C总线将返回空闲状态。

数据位传输

在I2C通信中,时钟线SCL上的每一个时钟,同步对应着数据线SDA上的一位数据。即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL是高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。

应答信号

I2C总线上的所有数据都是以8bit字节传输的,发送器每发送一个字节,就在第9个时钟开始时释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。
如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。

操作时序

I2C设备的操作时序有四种,分别为写单个存储字节,写多个存储字节,读单个存储字节和读多个存储字节。操作时序如下图:

具体通信过程

以写单个存储字节这一操作为例,介绍I2C通信的具体流程:

​ 初始状态:SCL、SDA都为高电平,总线处于空闲状态;

→启动信号:在SCL为高电平时,SDA由高变低,产生下降沿,此时I2C通信开始启动;

→发送7位从机地址和1位读写指令:按位传输,按照高位在前、低位在后的顺序,且遵循SCL高电平时SDA上的数据保持不变,SCL低电平时SDA上的数据发生改变的原则,每个时钟脉冲发送一位地址数据;

→接收响应:I2C通信中,每发送完8bit数据,会接收1bit响应;此时,主机先把数据线SDA释放,然后在第9个时钟脉冲的高电平期间读取SDA上的应答信号,0代表ACK信号,1代表NACK信号;只有接收到ACK信号,才继续之后的操作,否则重新开始通信过程;

→发送8位字节地址:同上;

→接收响应:同上;

→写入8位数据:同上;

→接收响应:同上;

→停止信号:在SCL为高电平时,SDA由低变高,产生上升沿,此时I2C通信结束。

其他三种操作的具体流程是类似的。

标准接口

name description direction length
clk 系统时钟 input 1
rst 复位信号 input 1
scl I2C串行时钟线 output 1
sda I2C串行数据线 inout 1
rd_sig I2C读命令 input 1
wr_sig I2C写命令 input 1
rd_data I2C读取的数据 output 8
wr_data I2C写入的数据 input 8
addr I2C读/写的开始字节地址 input 8
done_sig I2C读/写操作完成信号 output 1

3.程序实现

通过EEPROM的读写操作,验证I2C通信的程序实现。

RTL视图

I2C控制模块

`timescale 1ns/1ps//Module Name :   iic_control
//Description   :   read and write eeprom using iic bus
//Editor        :   Yongxiang
//Time          :   2019-11-25module iic_control(input wire clk_50M,input wire  rst_n,output reg    wr_sig,output reg   rd_sig,output reg[7:0]  addr_sig,output reg[7:0]    wr_data,input wire  done_sig);reg[1:0] state;//eeprom先写后读
always @(posedge clk_50M)
beginif(!rst_n)beginstate <= 2'd0;addr_sig <= 8'd0;wr_data <= 8'd0;rd_sig <= 1'b0;wr_sig <= 1'b0;endelse begincase(state)2'd0:beginif(done_sig)beginwr_sig <= 1'b0;rd_sig <= 1'b0;state <= 2'd1;endelse beginwr_sig <= 1'b1;rd_sig <= 1'b0;wr_data <= 8'hff;    //写入数据0Xffaddr_sig <= 8'd0;    //在eeprom的0X00地址写入数据endend2'd1:beginif(done_sig)beginwr_sig <= 1'b0;rd_sig <= 1'b0;state <= 2'd2;endelse beginwr_sig <= 1'b0;rd_sig <= 1'b1;addr_sig <= 8'd0;    //在eeprom的0X00地址写入数据endend2'd2:beginstate <= 2'd2;endendcaseend
endendmodule

I2C通信模块

`timescale 1ns/1ps//module name :   iic
//description   :   iic communication module
//Editor        :   Yongxiang
//Time          :   2019-11-25module iic(input wire clk_50M,input wire rst_n,input wire wr_sig, //写命令,1有效input wire rd_sig,  //读命令,1有效input wire[7:0] addr_sig,   //数据地址input wire[7:0] wr_data,  //写数据output reg[7:0] rd_data,   //读数据output reg done_sig,   //读写完成标志,1有效output reg scl,inout wire sda);reg[4:0] state;
reg[4:0] state_save;
reg[8:0] cnt;
reg[7:0] data_reg;
reg is_out;
reg sda_reg;
reg is_ask_n;   //应答信号,0有效assign sda = is_out ? sda_reg : 1'bz;    //SDA输入输出方向控制//IIC读写数据
always @(posedge clk_50M)
beginif(!rst_n)begin        //系统复位state <= 5'd0;cnt <= 9'd0;sda_reg <= 1'b1; //SDA置高scl <= 1'b1;        //SCL置高is_out <= 1'b1;is_ask_n <= 1'b1;   rd_data <= 8'd0;done_sig <= 1'b0;endelse if(wr_sig)begin      //iic数据写case(state)5'd0:begin  //iic启动is_out <= 1'b1;     //SDA输出if(cnt == 9'd0)beginscl <= 1'b1;sda_reg <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd100)beginsda_reg <= 1'b0;  //启动信号:在SCL为1时,SDA的下降沿cnt <= cnt + 9'd1;endelse if(cnt == 9'd200)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd249)begincnt <= 9'd0;state <= 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd1:begin   //发送7位从机地址、1位写命令data_reg <= 8'hA0;state <= 5'd7;state_save <= 5'd2;end5'd2:begin    //发送数据写入地址data_reg <= addr_sig;state <= 5'd7;state_save <= 5'd3;end5'd3:begin    //写入数据data_reg <= wr_data;state <= 5'd7;state_save <= 5'd4;end5'd4:begin //iic停止is_out <= 1'b1;     //SDA输出if(cnt == 9'd0)beginscl <= 1'b0;sda_reg <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;       cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginsda_reg <= 1'b1;  //停止信号:在SCL为1时,SDA的上升沿cnt <= cnt + 9'd1;endelse if(cnt == 9'd249)begincnt <= 9'd0;state <= 5'd5;endelse begincnt <= cnt + 9'd1;endend5'd5:begin //写iic结束done_sig <= 1'b1;state <= 5'd6;end5'd6:begindone_sig <= 1'b0;state <= 5'd0;end5'd7,5'd8,5'd9,5'd10,5'd11,5'd12,5'd13,5'd14:begin   //发送一个字节is_out <= 1'b1;sda_reg <= data_reg[14-state];      //高位先发送if(cnt == 9'd0)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd199)begincnt <= 9'd0;state <= state + 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd15:begin //等待应答is_out <= 1'b0;  //SDA输入if(cnt == 9'd0)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd100)beginis_ask_n <= sda;cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd199)begincnt <= 9'd0;state <= state + 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd16:beginif(!is_ask_n)begin  //接收到应答信号state <= state_save;endelse beginstate <= 5'd0;endendendcaseendelse if(rd_sig)begin       //iic数据读case(state)5'd0:begin  //iic启动is_out <= 1'b1;     //SDA输出if(cnt == 9'd0)beginscl <= 1'b1;sda_reg <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd100)beginsda_reg <= 1'b0;  //启动信号:在SCL为1时,SDA的下降沿cnt <= cnt + 9'd1;endelse if(cnt == 9'd200)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd249)begincnt <= 9'd0;state <= 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd1:begin   //发送7位从机地址、1位写命令data_reg <= 8'hA0;state <= 5'd9;state_save <= 5'd2;end5'd2:begin    //发送读取数据地址data_reg <= addr_sig;state <= 5'd9;state_save <= 5'd3;end5'd3:begin    //iic再次启动is_out <= 1'b1;       //SDA输出if(cnt == 9'd0)beginscl <= 1'b1;sda_reg <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd100)beginsda_reg <= 1'b0;  //启动信号:在SCL为1时,SDA的下降沿cnt <= cnt + 9'd1;endelse if(cnt == 9'd200)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd249)begincnt <= 9'd0;state <= 5'd4;endelse begincnt <= cnt + 9'd1;endend5'd4:begin   //发送7位从机地址、1位读命令data_reg <= 8'hA1;state <= 5'd9;state_save <= 5'd5;end5'd5:begin    //读数据data_reg <= 8'd0;state <= 5'd19;state_save <= 5'd6;end5'd6:begin   //iic停止is_out <= 1'b1;     //SDA输出if(cnt == 9'd0)beginscl <= 1'b0;sda_reg <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;       cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginsda_reg <= 1'b1;  //停止信号:在SCL为1时,SDA的上升沿cnt <= cnt + 9'd1;endelse if(cnt == 9'd249)begincnt <= 9'd0;state <= 5'd7;endelse begincnt <= cnt + 9'd1;endend5'd7:begin //读iic结束done_sig <= 1'b1;state <= 5'd8;end5'd8:begindone_sig <= 1'b0;state <= 5'd0;end5'd9,5'd10,5'd11,5'd12,5'd13,5'd14,5'd15,5'd16:begin //发送一个字节is_out <= 1'b1;sda_reg <= data_reg[16-state];      //高位先发送if(cnt == 9'd0)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd199)begincnt <= 9'd0;state <= state + 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd17:begin //等待应答is_out <= 1'b0;  //SDA输入if(cnt == 9'd0)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd100)beginis_ask_n <= sda;cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd199)begincnt <= 9'd0;state <= state + 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd18:beginif(!is_ask_n)begin  //接收到应答信号state <= state_save;endelse beginstate <= 5'd0;endend5'd19,5'd20,5'd21,5'd22,5'd23,5'd24,5'd25,5'd26:begin    //接收一个字节is_out <= 1'b0;if(cnt == 9'd0)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd100)begindata_reg[26-state] <= sda;    //高位先接收cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd199)begincnt <= 9'd0;state <= state + 5'd1;endelse begincnt <= cnt + 9'd1;endend5'd27:begin  //无应答信号is_out <= 1'b1; //SDA输入rd_data <= data_reg; //接收完一个字节数据if(cnt == 9'd0)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd50)beginscl <= 1'b1;cnt <= cnt + 9'd1;endelse if(cnt == 9'd150)beginscl <= 1'b0;cnt <= cnt + 9'd1;endelse if(cnt == 9'd199)begincnt <= 9'd0;state <= state_save;endelse begincnt <= cnt + 9'd1;endendendcaseend
endendmodule

数码管显示模块

——部分具体实现代码省略(比较简单)

`timescale 1ns/1ps//module name: smg_demomodule smg_demo(input clk_50MHz,input rst,input[7:0] data,output[5:0] smg_sig,output[7:0] smg_data//output rdsig_nextdata);wire clk_1khz, clk_1hz;//clkdiv
smg_clkdiv smg_clkdiv_inst(.clk_50MHz(clk_50MHz),.rst(rst),.clk_1khz(clk_1khz),.clk_1hz(clk_1hz)//.rdsig_nextdata(rdsig_nextdata));//display
smg_display smg_display_inst(.clk_1khz(clk_1khz),.clk_1hz(clk_1hz),.rst(rst),.data(data),.smg_sig(smg_sig),.smg_data(smg_data));endmodule

顶层模块

`timescale 1ns/1ps//Module Name :   eeprom
//Description   :   top_file
//Editor        :   Yongxiang
//Time          :   2019-11-25module eeprom(input wire  clk_50M,input wire  rst_n,output wire   scl,inout wire  sda,output wire[5:0] smg_sig,output wire[7:0] smg_data);wire wr_sig;
wire rd_sig;
wire[7:0] addr_sig;
wire[7:0] wr_data;
wire[7:0] rd_data;
wire done_sig;//iic_control
iic_control iic_control_inst(.clk_50M(clk_50M),.rst_n(rst_n),.wr_sig(wr_sig),.rd_sig(rd_sig),.addr_sig(addr_sig),.wr_data(wr_data),.done_sig(done_sig));//iic
iic iic_inst(.clk_50M(clk_50M),.rst_n(rst_n),.wr_sig(wr_sig),       //写命令,1有效.rd_sig(rd_sig),        //读命令,1有效.addr_sig(addr_sig),    //数据地址.wr_data(wr_data),    //写数据.rd_data(rd_data), //读数据.done_sig(done_sig),   //读写完成标志,1有效.scl(scl),.sda(sda));//smg
smg_demo smg_demo_inst(.clk_50MHz(clk_50M),.rst(rst_n),.data(rd_data),.smg_sig(smg_sig),.smg_data(smg_data));endmodule

通信协议篇——I2C通信相关推荐

  1. 通信协议篇——SPI通信

    通信协议篇--SPI通信 1.简介 SPI(Serial Peripheral Interface)是一种高速.同步.全双工串行通信总线,采用主从机通信模式,主要应用在EEPROM,FLASH,实时时 ...

  2. 快速对比UART、SPI、I2C通信的区别与应用

    参考:带你快速对比SPI.UART.I2C通信的区别与应用! 作者:一口Linux 网址:https://mp.weixin.qq.com/s/4_RSM2jk2W6nTboO1W8HCw 电子设备之 ...

  3. Linux设备驱动篇——[I2C设备驱动-1]

    Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...

  4. 传感器i2c与arduino连接_Arduino中进行I2C通信发送数据案例分析

    在之前的文章中,我们介绍了Arduino之间的SPI通信.今天我们将学习另一种串行通信协议:I2C(内部集成电路).比较I2C和SPI,I2C只有两条线,而SPI使用四条,I2C可以有多个主机和从机, ...

  5. 【FPGA】十一、I2C通信回环

    文章目录 前言 一.I2C简介 二.I2C原理 2.1.I2C物理层 2.2.I2C协议层 2.2.1.I2C协议 2.2.2.I2C数据传输格式 2.2.3.I2C写操作 2.2.4.I2C读操作 ...

  6. 看 Sugar 如何说 I2C 通信

    背景介绍: Sugar 这两天玩了玩 JetBot,就是用 Nvidia Jetson Nano 做的智能小车. 其中小车的驱动模块用的是 I2C 的马达驱动板. 本篇 Sugar 就从硬件角度说一说 ...

  7. esp32和MPU6500 I2C通信

    目录 1. 初始化I2C 2. 读取who_an_I,验证I2C设置和芯片函数正确性 3. 读取ACC,GYR数据,验证芯片正常工作 4. 用DMP算法,计算芯片欧拉角 1.初始化I2C I2C可以分 ...

  8. Arduino读取JY901+GPS/北斗双模定位模块信息(提高定位精度)串口和I2C通信

    Arduino读取JY901+GPS/北斗双模定位模块信息 上一篇JY901模块连接ATK-1218-BD,GPS/北斗通过JY901上位机显示数据 采用JY901的上位机查看信息后,确定JY901, ...

  9. STC89C52单片机I2C通信以及AT24C02介绍使用代码演示

    目录 AT24C02引脚介绍与使用 AT24C02介绍 ​I2C通信介绍 I2C通信时序 起始条件与终止条件 发送一个字节(主机发送到从机) 接受一个字节(从机发送到主机) 发送应答与接受应答 I2C ...

最新文章

  1. 第四范式荣获2020年度信创工委会 “卓越贡献成员单位”称号
  2. Jupyter Noteboot 添加kernel 环境
  3. TCP/IP详解卷一02
  4. 软件工程第二次课课堂总结
  5. python操作sql_Python Mysql数据库操作,sql文件操作
  6. 澜舟科技开源轻量级中文语言预训练模型——孟子模型
  7. 下载用于编译的OpenJDK源码链接
  8. 总结自己设计带POE的八口交换机的过程和踩坑
  9. 蓝桥杯—日志统计—跟外卖店优先级比较类似
  10. android9/android10 鼠标右键返回(已验证)
  11. LsDYNA 任务批量提交
  12. 一张图认识IPSec,区分IKE SA(ISAKMP SA)和IPSec SA
  13. RecyclerView使用GridLayoutManager 设置间距一致大小
  14. cakephp视图用php文件,CakePHP的视图
  15. 标准化和归一化,请勿混为一谈,透彻理解数据变换
  16. java将中文转换为pinyin/繁简互转
  17. idea创建的empty project 突然显示问题解决
  18. 浪姐爆火!“爬一爬”背后不为人知的数据!
  19. 机器学习(Machine Learning)大家
  20. C++虚函数表的应用

热门文章

  1. bevfusion部署服务器超级终极版
  2. Android UniversalVideoView视频播放器
  3. 我之看法--赵丹阳与巴菲特的午餐
  4. 第二代天神:克罗诺斯
  5. 人工智能“军备竞赛”,为什么说搜狗已占据先手优势?
  6. 上大学与不上大学的区别
  7. 一行代码随意编辑网站
  8. 使用 DMV 进行监视_监视资源使用情况(1)_针对 Azure SQL 数据库和 Azure SQL 托管实例进行手动性能优化
  9. vue 组件中的setInterval方法和window的不同
  10. 谷歌任命陈俊廷为大中华区总裁 此前负责中国台湾业务