当前位置:   article > 正文

基于Verilog的IIC接口通信设计(支持多字节地址读写)_verilog i2c

verilog i2c


一、IIC协议简介

IIC(Inter-Integrated Circuit)协议是一种同步串行通信接口,它采用半双工工作模式,即在同一时间只能进行单向的数据传输。总线由两条信号线组成:一条是数据线SDA(Serial Data Line),用于传输数据;另一条是时钟线SCL(Serial Clock Line),由主设备提供时钟信号,以确保所有连接到总线的设备同步进行数据交换。
在IIC总线上,每个从设备都有一个唯一的地址,主设备通过发送这个地址来选择与其通信的目标设备。由于支持多主控功能,多个具备主控能力的设备可以在同一总线上竞争控制权,并通过硬件仲裁机制避免冲突。在这里插入图片描述
IIC总线四种特点:
① 由时钟线SCL和数据线SDA组成,并且都接上拉电阻,确保总线空闲状态为高电平;
② 总线支持多设备连接,允许多主机存在,每个设备都有一个唯一的地址;
③ 连接到总线上的数目受总线的最大电容400pf限制;
数据传输速率:标准模式100k bit/s、快速模式400k bit/s 、高速模式3.4Mbit/s。

由于其物理接口简单且占用线路少,I2C被广泛应用于嵌入式系统和电子设备中,方便连接各种低速外设,例如传感器、存储器、时钟芯片等。同时,I2C支持多种速度等级,能满足不同应用环境对数据传输速率的需求。

二、时序分析

1、协议时序

IIC通信协议的时序分为开始信号、地址字节、数据字节和停止信号四个阶段。
在这里插入图片描述

  1. 开始信号(Start Signal):
    主设备通过拉低数据线(SDA)时钟线(SCL)仍为高电平来发送开始信号。此时,从设备需准备好接收数据,并等待地址字节的到来。
  2. 地址字节(Address Byte):
    主设备发送一个地址字节到从设备以确定通信对象。地址字节的高七位是设备的地址,最低一位是读写控制位,通常为0表示写操作,1表示读操作。此时,从设备会检查其地址是否与发送的地址字节匹配。
  3. 数据字节(Data Byte):
    主设备和从设备之间的数据传输是通过数据字节来完成的。主设备发送数据字节,从设备接收数据字节。数据的传输是以字节为单位的,每个数据字节传输后都会有一个应答信号。
  4. 停止信号(Stop Signal):
    主设备发送停止信号作为传输的结束标志。停止信号通过将数据线从低电平拉升至高电平实现,此时时钟线仍然保持高电平。

2、实例分析

以下时序案例,在工程实例中比较常见。包括单地址、多地址的读写,以及读写数据的突发时序,本设计代码都可以扩展实现。
1、单字节地址写时序
在这里插入图片描述
2、双字节地址写时序
在这里插入图片描述
3、单字节地址读时序
在这里插入图片描述
4、双字节地址读时序
在这里插入图片描述

三、设计实现

该模块的设计具有以下两个特点:①读写地址可扩展,支持多字节地址实现;②读写数据可扩展,支持多字节地址实现;只需要修改相关代码参数就可以实现。源码下载链接:FPGA-Verilog语言-IIC接口驱动代码

参考代码如下所示:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/03/29 09:43:38
// Design Name: 
// Module Name: iic_comm
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module iic_comm#(
	parameter  	SYS_CLOCK = 60_000_000,	//系统时钟频率
	parameter  	SCL_CLOCK = 100_000		//iic时钟频率
	)(
	input                  i_clk			,	//系统时钟
	input                  i_rst_n			,	//系统复位
	
	input                  i_wr_en			,	//写使能
	input                  i_rd_en			,	//读使能
	input          [6:0]   i_dev_addr		,	//器件地址
	input          [15:0]  i_reg_addr		,	//寄存器地址,单字节时从低字节有效
	input          [1:0]   i_reg_addr_num	,	//寄存器地址字节数 1: 1字节,其他:2字节

    input          [7:0]   i_wr_data		,	//写数据
	input          [1:0]   i_wr_data_num	,  	//写数据字节数,默认为1
	
	output  reg            o_rd_data_vaild	,	//读数据有效	
	output  reg    [7:0]   o_rd_data		,	//读数据	
	input          [1:0]   i_rd_data_num	,  	//读数据字节数,默认为1

	output  reg            o_cfg_done 		,	//一次读写操作完成标志
	
	inout 		           io_SDA			,   //IIC--SDA
	output  reg            o_SCL				//IIC--SCL
);

//================================================================================
localparam  SCL_CNT_M = SYS_CLOCK / SCL_CLOCK;

localparam 	IDLE 		= 9'b0_0000_0001;  //独热编码
localparam  WR_START 	= 9'b0_0000_0010;
localparam  WR_CTRL 	= 9'b0_0000_0100;
localparam  WR_REG_ADDR = 9'b0_0000_1000;
localparam  WR_DATA 	= 9'b0_0001_0000;
localparam  RD_START 	= 9'b0_0010_0000;
localparam  RD_CTRL 	= 9'b0_0100_0000;
localparam  RD_DATA 	= 9'b0_1000_0000;
localparam  STOP 		= 9'b1_0000_0000;

//-------------------------------------------------
// 变量声明
//-------------------------------------------------
reg  [15:0]	scl_cnt;		//clk计数器,用于产生scl时钟
reg 		scl_high;		//scl高电平中部标志
reg 		scl_low;		//scl低电平中部标志
reg 		scl_vaild;		//scl有效标志

reg  [8:0]  main_state;	   	//状态寄存器
reg         sda_reg;		//sda输出寄存器
reg 		sda_en;			//sda三态使能
reg 		sda_task_flag;	//串行输出输入任务执行标志位
reg 		w_flag;			//写标志
reg 		r_flag;			//读标志
reg  [7:0]	scl_level_cnt;	//scl高低电平计数器
reg 		ack;			//应答信号
reg  [1:0]	wdata_cnt;		//写数据字节数计数器
reg  [1:0]	rdata_cnt;		//读数据字节数计数器
reg  [1:0]	reg_addr_cnt;	//地址字节数计数器
reg  [7:0]	sda_data_out;	//数据输出buffer
reg  [7:0]	sda_data_in;	//数据输入buffer
wire [7:0]	wr_ctrl_word;	//写控制字
wire [7:0]	rd_ctrl_word;	//读控制字
wire 		rdata_vaild; 	//读数据有效前寄存器

assign wr_ctrl_word = {i_dev_addr,1'b0};
assign rd_ctrl_word = {i_dev_addr,1'b1};

//----------------------------------------------------
// iic时钟信号生成
//----------------------------------------------------
/* iic 非空闲状态产生 scl_vaild */
always @(posedge i_clk) begin
	if(!i_rst_n)
		scl_vaild <= 1'b0;
	else begin
		if(i_wr_en | i_rd_en)
			scl_vaild <= 1'b1;
		else if(o_cfg_done)
			scl_vaild <= 1'b0;
	end
end
	
/* o_SCL 计数器*/
always @(posedge i_clk) begin
	if(!i_rst_n)
		scl_cnt <= 16'd0;
	else begin
		if(scl_vaild) begin
			if(scl_cnt == SCL_CNT_M-1'b1)
				scl_cnt <= 16'd0;
			else
				scl_cnt <= scl_cnt + 16'd1;
		end
		else
			scl_cnt <= 16'd0;
	end
end
	
/* o_SCL 时钟产生*/
always @(posedge i_clk) begin
	if(!i_rst_n)
		o_SCL <= 1'b1;
	else begin
		if(scl_cnt == SCL_CNT_M >> 1) 
			o_SCL <= 1'b0;
		else if(scl_cnt == 16'd0)
			o_SCL <= 1'b1;
	end
end

/*o_SCL 高低电平中部标志*/
always @(posedge i_clk) begin
	if(!i_rst_n) begin
		scl_high <= 1'b0;
		scl_low  <= 1'b0;
	end
	else begin
		if(scl_cnt == (SCL_CNT_M >> 2))
			scl_high <= 1'b1;
		else
			scl_high <= 1'b0;
			
		if(scl_cnt == ((SCL_CNT_M >> 1) + (SCL_CNT_M >> 2)))
			scl_low <= 1'b1;
		else
			scl_low <= 1'b0;			
	end
end

//----------------------------------------------------
// iic主程序状态机
//----------------------------------------------------
always @(posedge i_clk) begin
	if(!i_rst_n) begin
		main_state 		<= IDLE;
		sda_reg 	  	<= 1'b1;	
		w_flag 			<= 1'b0;
		r_flag 			<= 1'b0;
		o_cfg_done 		<= 1'b0;
		reg_addr_cnt 	<= 2'd1;
		wdata_cnt 		<= 2'd1;
		rdata_cnt 		<= 2'd1;
	end
	else begin		
		case(main_state)
			IDLE: begin
				sda_reg   	 <= 1'b1;	
				w_flag    	 <= 1'b0;
				r_flag 	 	 <= 1'b0;
				o_cfg_done 	 <= 1'b0;
				reg_addr_cnt <= 2'd1;
				wdata_cnt 	 <= 2'd1;
				rdata_cnt 	 <= 2'd1;
				
				if(i_wr_en) begin 
					main_state <= WR_START;
					w_flag     <= 1'b1;
				end	
				else if(i_rd_en) begin
					main_state <= WR_START; 
					r_flag     <= 1'b1;
				end
			end
			//------------------- iic起始信号 ---------------------------			
			WR_START: begin
				if(scl_high) begin
					main_state <= WR_START;
					sda_reg    <= 1'b0;
				end
				else if(scl_low) begin
					main_state    <= WR_CTRL;
					sda_data_out  <= wr_ctrl_word;	// 准备要发送的控制字
					sda_task_flag <= 1'b0; 			// 开始串行传输任务
				end
			end
			//---------------- 写设备地址、寄存器地址 -------------------
			WR_CTRL: begin
				if(sda_task_flag == 1'b0) // 发送数据
					send_8bit_data;
				else begin	              // 等待响应
					if(ack == 1'b1) begin // 收到响应
						if(scl_low) begin // 准备发送的寄存器地址数据
							main_state 	  <= WR_REG_ADDR;// 转换到寄存器地址
							sda_task_flag <= 1'b0;
							if(i_reg_addr_num == 2'b1)
								sda_data_out <= i_reg_addr[7:0];
							else
								sda_data_out <= i_reg_addr[15:8];//如果寄存器地址为2个字节,要保证先发的最高位
						end
					end
					else // 未收到响应
						main_state <= IDLE;
				end
			end			
			WR_REG_ADDR: begin
				if(sda_task_flag == 1'b0)
					send_8bit_data;
				else begin
					if(ack == 1'b1) begin //收到响应
						if(reg_addr_cnt == i_reg_addr_num) begin // 寄存器地址数据发送完成
							if(w_flag && scl_low) begin
								main_state    <= WR_DATA;       //状态转移
								sda_data_out  <= i_wr_data[7:0];//数据准备
								sda_task_flag <= 1'b0;
								reg_addr_cnt  <= 2'd1;
							end
							else if(r_flag && scl_low) begin
								main_state    <= RD_START; 
								sda_reg       <= 1'b1; //sda拉高
							end
						end
						else begin					// 寄存器地址数据没有发送完成
							if(scl_low) begin
								main_state    <= WR_REG_ADDR;
								reg_addr_cnt  <= reg_addr_cnt + 2'd1;
								sda_data_out  <= i_reg_addr[7:0]; // 准备低8位寄存器地址
								sda_task_flag <= 1'b0;
							end
						end		
					end
					else	// 未收到响应
						main_state <= IDLE;					
				end
			end		
			//----------------- 写iic数据 ---------------------------------
			WR_DATA: begin
				if(sda_task_flag == 1'b0)
					send_8bit_data;
				else begin
					if(ack == 1'b1) begin // 收到响应
						if(wdata_cnt == i_wr_data_num) begin //发送完成
							if(scl_low) begin
								main_state <= STOP;
								sda_reg    <= 1'b0;
								wdata_cnt  <= 2'd1;
							end
						end
						else begin //未发送完成
							if(scl_low) begin
								main_state    <= WR_DATA;
								sda_data_out  <= i_wr_data[7:0];
								wdata_cnt     <= wdata_cnt + 2'd1;
								sda_task_flag <= 1'b0;
							end
						end
					end
					else // 未收到响应
						main_state <= IDLE;
				end
			end
			//----------------- 读iic数据 ---------------------------------
			RD_START: begin
				if(scl_high) begin
					main_state    <= RD_START;
					sda_reg       <= 1'b0;
				end
				else if(scl_low) begin
					main_state    <= RD_CTRL;
					sda_data_out  <= rd_ctrl_word;	// 准备要发送的控制字
					sda_task_flag <= 1'b0; 			// 开始串行传输任务
				end
			end			
			RD_CTRL: begin
				if(sda_task_flag == 1'b0)  // 发送数据
					send_8bit_data;
				else begin	// 等待响应
					if(ack == 1'b1) begin  // 收到响应
						if(scl_low) begin  // 准备发送的寄存器地址数据
							main_state    <= RD_DATA; // 转换到寄存器地址
							sda_task_flag <= 1'b0;
						end
					end
					else // 未收到响应
						main_state <= IDLE;
				end
			end			
			RD_DATA: begin
				if(sda_task_flag == 1'b0)
					receive_8bit_data;
				else begin
					if(rdata_cnt == i_rd_data_num) begin  // 接收完成
						sda_reg <= 1'b1;  //发送 ACK,不读了
						if(scl_low) begin
							main_state <= STOP;
							sda_reg    <= 1'b0;
						end
					end
					else begin
						sda_reg <= 1'b0; // 发送NACK,继续读下一个字节
						if(scl_low) begin
							rdata_cnt     <= rdata_cnt + 2'd1;
							sda_task_flag <= 1'b0;
						end
					end
				end
			end
			//------------------- iic停止信号 ---------------------------
			STOP: begin
				if(scl_high) begin
					sda_reg    <= 1'b1;
					main_state <= IDLE;
					o_cfg_done <= 1'b1;
				end
			end
			
			default: main_state <= IDLE;
		endcase
	end
	
end

//----------------------------------------------------
// 串行数据任务,收/发8bit数据
//----------------------------------------------------
/*发送接收数据时 o_SCL 时钟计数*/
always @(posedge i_clk) begin
	if(!i_rst_n)
		scl_level_cnt <= 8'd0;
    else begin
		//这几个状态需要执行数据发送接收任务
		if(main_state == WR_CTRL || main_state == WR_REG_ADDR || main_state == WR_DATA ||
		   main_state == RD_CTRL || main_state == RD_DATA) begin  
			if(scl_low | scl_high) begin
				if(scl_level_cnt == 8'd17)
					scl_level_cnt <= 8'd0;
				else
					scl_level_cnt <= scl_level_cnt + 8'd1;
			end
		end
		else
			scl_level_cnt <= 8'd0;
	end
end

/*数据接收对发送的响应标志位*/
always @(posedge i_clk) begin
	if(!i_rst_n)
		ack <= 1'b0;
	else begin
		if((scl_level_cnt == 8'd16) && scl_high && (io_SDA === 1'd0))
			ack <= 1'b1;
		else if((scl_level_cnt == 8'd17) && scl_low)
			ack <= 1'b0;
	end
end

/* 输出串行数据任务 */
task send_8bit_data;
	if(scl_high && (scl_level_cnt == 8'd16)) //8bit data send o_cfg_done
		sda_task_flag <= 1'b1;
	else if(scl_level_cnt < 8'd17) begin
		sda_reg <= sda_data_out[7];
		if(scl_low)
			sda_data_out <= {sda_data_out[6:0],1'b0};
	end
endtask
	
/* 接收串行数据任务 */ 
task receive_8bit_data;
	if(scl_low && (scl_level_cnt == 8'd15))
		sda_task_flag <= 1'b1;
	else if(scl_level_cnt < 8'd15) begin
		if(scl_high)
			sda_data_in <= {sda_data_in[6:0],io_SDA};
	end
endtask

//----------------------------------------------------
// SDA三态门控制输出
//----------------------------------------------------						
/*io_SDA 三态门输出*/
assign io_SDA = sda_en ? sda_reg : 1'bz; 

always @(*) begin
	case(main_state)
		IDLE: sda_en <= 1'b0;  //输入
			
		WR_START,RD_START,STOP: sda_en <= 1'b1;  //输出
			
		WR_CTRL,WR_REG_ADDR,WR_DATA,RD_CTRL: begin
			if(scl_level_cnt < 8'd16)
				sda_en <= 1'b1;
			else
				sda_en <= 1'b0;
		end		
		RD_DATA: begin
			if(scl_level_cnt < 8'd16)
				sda_en <= 1'b0;
			else	
				sda_en <= 1'b1;
		end
		default: sda_en <= 1'b0;
	endcase
end

//----------------------------------------------------
// 读有效数据
//----------------------------------------------------						
/*读出数据有效标志位*/
assign rdata_vaild = (main_state == RD_DATA) && (scl_level_cnt == 8'd15) && scl_low;

/*读出的有效数据*/
always @(posedge i_clk) begin
	if(!i_rst_n) begin
		o_rd_data_vaild <= 1'b0;
		o_rd_data       <= 8'd0;
	end
	else begin
		if(rdata_vaild) begin
			o_rd_data_vaild <= 1'b1;
			o_rd_data       <= sda_data_in;
		end
		else
			o_rd_data_vaild <= 1'b0;
	end
end



endmodule

  • 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
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/599763
推荐阅读
相关标签
  

闽ICP备14008679号