通信协议篇——I2C通信
通信协议篇——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通信相关推荐
- 通信协议篇——SPI通信
通信协议篇--SPI通信 1.简介 SPI(Serial Peripheral Interface)是一种高速.同步.全双工串行通信总线,采用主从机通信模式,主要应用在EEPROM,FLASH,实时时 ...
- 快速对比UART、SPI、I2C通信的区别与应用
参考:带你快速对比SPI.UART.I2C通信的区别与应用! 作者:一口Linux 网址:https://mp.weixin.qq.com/s/4_RSM2jk2W6nTboO1W8HCw 电子设备之 ...
- Linux设备驱动篇——[I2C设备驱动-1]
Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...
- 传感器i2c与arduino连接_Arduino中进行I2C通信发送数据案例分析
在之前的文章中,我们介绍了Arduino之间的SPI通信.今天我们将学习另一种串行通信协议:I2C(内部集成电路).比较I2C和SPI,I2C只有两条线,而SPI使用四条,I2C可以有多个主机和从机, ...
- 【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读操作 ...
- 看 Sugar 如何说 I2C 通信
背景介绍: Sugar 这两天玩了玩 JetBot,就是用 Nvidia Jetson Nano 做的智能小车. 其中小车的驱动模块用的是 I2C 的马达驱动板. 本篇 Sugar 就从硬件角度说一说 ...
- esp32和MPU6500 I2C通信
目录 1. 初始化I2C 2. 读取who_an_I,验证I2C设置和芯片函数正确性 3. 读取ACC,GYR数据,验证芯片正常工作 4. 用DMP算法,计算芯片欧拉角 1.初始化I2C I2C可以分 ...
- Arduino读取JY901+GPS/北斗双模定位模块信息(提高定位精度)串口和I2C通信
Arduino读取JY901+GPS/北斗双模定位模块信息 上一篇JY901模块连接ATK-1218-BD,GPS/北斗通过JY901上位机显示数据 采用JY901的上位机查看信息后,确定JY901, ...
- STC89C52单片机I2C通信以及AT24C02介绍使用代码演示
目录 AT24C02引脚介绍与使用 AT24C02介绍 I2C通信介绍 I2C通信时序 起始条件与终止条件 发送一个字节(主机发送到从机) 接受一个字节(从机发送到主机) 发送应答与接受应答 I2C ...
最新文章
- 第四范式荣获2020年度信创工委会 “卓越贡献成员单位”称号
- Jupyter Noteboot 添加kernel 环境
- TCP/IP详解卷一02
- 软件工程第二次课课堂总结
- python操作sql_Python Mysql数据库操作,sql文件操作
- 澜舟科技开源轻量级中文语言预训练模型——孟子模型
- 下载用于编译的OpenJDK源码链接
- 总结自己设计带POE的八口交换机的过程和踩坑
- 蓝桥杯—日志统计—跟外卖店优先级比较类似
- android9/android10 鼠标右键返回(已验证)
- LsDYNA 任务批量提交
- 一张图认识IPSec,区分IKE SA(ISAKMP SA)和IPSec SA
- RecyclerView使用GridLayoutManager 设置间距一致大小
- cakephp视图用php文件,CakePHP的视图
- 标准化和归一化,请勿混为一谈,透彻理解数据变换
- java将中文转换为pinyin/繁简互转
- idea创建的empty project 突然显示问题解决
- 浪姐爆火!“爬一爬”背后不为人知的数据!
- 机器学习(Machine Learning)大家
- C++虚函数表的应用