通信协议篇——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通信结束。

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

标准接口
namedescriptiondirectionlength
clk系统时钟input1
rst复位信号input1
sclI2C串行时钟线output1
sdaI2C串行数据线inout1
rd_sigI2C读命令input1
wr_sigI2C写命令input1
rd_dataI2C读取的数据output8
wr_dataI2C写入的数据input8
addrI2C读/写的开始字节地址input8
done_sigI2C读/写操作完成信号output1

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-25

module 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)
begin
	if(!rst_n)begin
		state <= 2'd0;
		addr_sig <= 8'd0;
		wr_data <= 8'd0;
		rd_sig <= 1'b0;
		wr_sig <= 1'b0;
	end
	else begin
		case(state)
			2'd0:begin
				if(done_sig)begin
					wr_sig <= 1'b0;
					rd_sig <= 1'b0;
					state <= 2'd1;
				end
				else begin
					wr_sig <= 1'b1;
					rd_sig <= 1'b0;
					wr_data <= 8'hff;	//写入数据0Xff
					addr_sig <= 8'd0;	//在eeprom的0X00地址写入数据
				end
			end
			2'd1:begin
				if(done_sig)begin
					wr_sig <= 1'b0;
					rd_sig <= 1'b0;
					state <= 2'd2;
				end
				else begin
					wr_sig <= 1'b0;
					rd_sig <= 1'b1;
					addr_sig <= 8'd0;	//在eeprom的0X00地址写入数据
				end
			end
			2'd2:begin
				state <= 2'd2;
			end
		endcase
	end
end

endmodule

I2C通信模块
`timescale 1ns/1ps

//module name	:	iic
//description	:	iic communication module
//Editor		:	Yongxiang
//Time			:	2019-11-25

module 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)
begin
	if(!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;
	end
	else if(wr_sig)begin		//iic数据写
		case(state)
			5'd0:begin	//iic启动
				is_out <= 1'b1;		//SDA输出
				if(cnt == 9'd0)begin
					scl <= 1'b1;
					sda_reg <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd100)begin
					sda_reg <= 1'b0;	//启动信号:在SCL为1时,SDA的下降沿
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd200)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd249)begin
					cnt <= 9'd0;
					state <= 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd1:begin	//发送7位从机地址、1位写命令
				data_reg <= 8'hA0;
				state <= 5'd7;
				state_save <= 5'd2;
			end
			5'd2:begin	//发送数据写入地址
				data_reg <= addr_sig;
				state <= 5'd7;
				state_save <= 5'd3;
			end
			5'd3:begin	//写入数据
				data_reg <= wr_data;
				state <= 5'd7;
				state_save <= 5'd4;
			end
			5'd4:begin	//iic停止
				is_out <= 1'b1;		//SDA输出
				if(cnt == 9'd0)begin
					scl <= 1'b0;
					sda_reg <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;		
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					sda_reg <= 1'b1;	//停止信号:在SCL为1时,SDA的上升沿
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd249)begin
					cnt <= 9'd0;
					state <= 5'd5;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd5:begin	//写iic结束
				done_sig <= 1'b1;
				state <= 5'd6;
			end
			5'd6:begin
				done_sig <= 1'b0;
				state <= 5'd0;
			end
			
			5'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)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd199)begin
					cnt <= 9'd0;
					state <= state + 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd15:begin	//等待应答
				is_out <= 1'b0;	//SDA输入
				if(cnt == 9'd0)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd100)begin
					is_ask_n <= sda;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd199)begin
					cnt <= 9'd0;
					state <= state + 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd16:begin
				if(!is_ask_n)begin	//接收到应答信号
					state <= state_save;
				end
				else begin
					state <= 5'd0;
				end
			end
		endcase
	end
	else if(rd_sig)begin		//iic数据读
		case(state)
			5'd0:begin	//iic启动
				is_out <= 1'b1;		//SDA输出
				if(cnt == 9'd0)begin
					scl <= 1'b1;
					sda_reg <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd100)begin
					sda_reg <= 1'b0;	//启动信号:在SCL为1时,SDA的下降沿
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd200)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd249)begin
					cnt <= 9'd0;
					state <= 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd1:begin	//发送7位从机地址、1位写命令
				data_reg <= 8'hA0;
				state <= 5'd9;
				state_save <= 5'd2;
			end
			5'd2:begin	//发送读取数据地址
				data_reg <= addr_sig;
				state <= 5'd9;
				state_save <= 5'd3;
			end
			5'd3:begin	//iic再次启动
				is_out <= 1'b1;		//SDA输出
				if(cnt == 9'd0)begin
					scl <= 1'b1;
					sda_reg <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd100)begin
					sda_reg <= 1'b0;	//启动信号:在SCL为1时,SDA的下降沿
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd200)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd249)begin
					cnt <= 9'd0;
					state <= 5'd4;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd4:begin	//发送7位从机地址、1位读命令
				data_reg <= 8'hA1;
				state <= 5'd9;
				state_save <= 5'd5;
			end
			5'd5:begin	//读数据
				data_reg <= 8'd0;
				state <= 5'd19;
				state_save <= 5'd6;
			end
			5'd6:begin	//iic停止
				is_out <= 1'b1;		//SDA输出
				if(cnt == 9'd0)begin
					scl <= 1'b0;
					sda_reg <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;		
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					sda_reg <= 1'b1;	//停止信号:在SCL为1时,SDA的上升沿
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd249)begin
					cnt <= 9'd0;
					state <= 5'd7;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd7:begin	//读iic结束
				done_sig <= 1'b1;
				state <= 5'd8;
			end
			5'd8:begin
				done_sig <= 1'b0;
				state <= 5'd0;
			end
			
			5'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)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd199)begin
					cnt <= 9'd0;
					state <= state + 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd17:begin	//等待应答
				is_out <= 1'b0;	//SDA输入
				if(cnt == 9'd0)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd100)begin
					is_ask_n <= sda;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd199)begin
					cnt <= 9'd0;
					state <= state + 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd18:begin
				if(!is_ask_n)begin	//接收到应答信号
					state <= state_save;
				end
				else begin
					state <= 5'd0;
				end
			end
			
			5'd19,5'd20,5'd21,5'd22,5'd23,5'd24,5'd25,5'd26:begin	//接收一个字节
				is_out <= 1'b0;
				if(cnt == 9'd0)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd100)begin
					data_reg[26-state] <= sda;	//高位先接收
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd199)begin
					cnt <= 9'd0;
					state <= state + 5'd1;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
			5'd27:begin	//无应答信号
				is_out <= 1'b1;	//SDA输入
				rd_data <= data_reg;	//接收完一个字节数据
				if(cnt == 9'd0)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd50)begin
					scl <= 1'b1;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd150)begin
					scl <= 1'b0;
					cnt <= cnt + 9'd1;
				end
				else if(cnt == 9'd199)begin
					cnt <= 9'd0;
					state <= state_save;
				end
				else begin
					cnt <= cnt + 9'd1;
				end
			end
		endcase
	end
end

endmodule

数码管显示模块

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

`timescale 1ns/1ps

//module name: smg_demo


module 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-25

module 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
	
  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值