FPGA学习笔记:I2C读写控制模块设计

一、I2C读写控制模块说明

前面的文章中设计了I2C最小单元读写模块,能够发送1byte的数据,根据I2C时序图,单次数据写操作需要发送7位器件地址、8位或16位存储单元地址、8位写数据,需要多次调用最小单元读写模块。因此本文设计I2C读写控制模块,将器件地址、存储单元地址、读写数据作为输入输出端口,能够完成一次单字节数据的读写

二、I2C读写控制模块设计

1、端口说明

端口名称方向说明
Clkinput系统时钟
Rst_ninput复位信号
wrreqinput写请求信号
rdreqinput读请求信号
device_id [7:0]input7位器件ID
addr [15:0]input16位存储器地址
wrdata [7:0]input8位写数据
rddata [7:0]output8位读数据
RW_Doneoutput读写完成标志
Ackoutput应答信号
i2c_sclkoutputI2C时钟SCLK
i2c_sdatinoutI2C数据线SDA

在这里插入图片描述
将之前设计的I2C最小单元读写模块例化到本模块中,通过多次调用实现功能。device_id为7位器件地址和读写操作位的组合。Ack为根据从机多次返回的应答信号确定,若有一次为非应答,则Ack为1,若每次都为应答,则Ack为0。

2.代码设计

(1)8位地址和16位地址确定

在代码中添加wire型信号addr_mode,若addr高八位不等于0,则addr为16位地址,addr_mode为1;addr高八位等于0,则addr为8位地址,addr_mode为0。

	wire addr_mode;//0:addr为8位地址 1:addr为16位地址
	assign addr_mode = (addr[15:8] != 8'd0) ? 1'b1:1'b0;

(2)最小单元读写任务封装

将最小单元读写操作封装为任务I2C_WR和I2C_RD,在调用时只需要将Cmd和TX_DATA传入任务即可。

	task I2C_WR;
		input [5:0] cmd;
		input [7:0] wr_DATA;
		begin
			Cmd = cmd;
			TX_DATA = wr_DATA;
			Go = 1'b1;
		end
	endtask
	
	task I2C_RD;
		input [5:0] cmd;
		begin	
			Cmd = cmd;
			Go = 1'b1;
		end
	endtask

封装成任务后,2字节地址单字节数据写操作简化为以下代码:

	I2C_WR(STA|WR, device_id & 8'hfe);
	I2C_WR(WR, addr[15:8]);
	I2C_WR(WR, addr[7:0]);
	I2C_WR(WR|STO, wrdata);

2字节地址单字节数据读操作简化为以下代码:

	I2C_WR(STA|WR, device_id & 8'hfe);
	I2C_WR(WR, addr[15:8]);
	I2C_WR(WR, addr[7:0]);
	I2C_WR(STA|WR, device_id | 8'h01);
	I2C_RD(RD|NACK|STO);

(3)状态转换

将I2C读写控制逻辑用状态机实现,可分为以下7个状态:
IDLE:空闲状态
WR_REG:写状态
WAIT_WR_DONE:等待写完成
WR_DONE:写完成
RD_REG:读状态
WAIT_RD_DONE:等待读完成
RD_DONE:读完成

状态转换图如下:
在这里插入图片描述
IDLE状态时,根据wrreq和rdreq跳入对应的读写状态。读操作需要调用4次最小单元模块,写操作需要调用5次最小单元模块,利用cnt对调用次数进行计数。在写状态WR_REG和读状态RD_REG两个状态中单次调用最小单元模块后,跳入等待读写完成状态WAIT_WR_DONE和WAIT_RD_DONE,等待单次I2C完成标志Trans_Done置1,置1后根据cnt判断是否继续读写操作。
各状态及最小单元模块Cmd参数定义:

	localparam
		STA =  6'b000001,	//起始位请求
		WR =   6'b000010,	//写请求
		RD =   6'b000100,	//读请求
		STO =  6'b001000,	//停止位请求
		ACK =  6'b010000,	//应答位请求
		NACK = 6'b100000;	//无应答请求
		
	reg [8:0] state;
	localparam
		IDLE 		 = 7'b0000001, //空闲状态
		WR_REG 		 = 7'b0000010, //写状态
		WAIT_WR_DONE = 7'b0000100, //等待写完成
		WR_DONE 	 = 7'b0001000, //写完成
		RD_REG		 = 7'b0010000, //读状态
		WAIT_RD_DONE = 7'b0100000, //等待读完成
		RD_DONE 	 = 7'b1000000; //读完成

(4)空闲状态

空闲状态根据读请求标志wrreq和写请求标志rdreq进行判断,跳入对应的读写状态。

	IDLE:begin
		cnt <= 8'd0;
		RW_Done = 1'b0;
		Ack = 1'b0;
		if(wrreq)
			state <= WR_REG;
		else if(rdreq)
			state <= RD_REG;
		else
			state <= IDLE;
	end

(5)写状态

写状态中,调用前面封装好的task任务,将一次读操作分为4步:写器件地址、写存储器高8位地址、写存储器低8位地址、写8位数据。cnt在WAIT_WR_DONE中计数

	WR_REG:begin
		case(cnt)
			0:I2C_WR(STA|WR, device_id & 8'hfe);
			1:I2C_WR(WR, addr[15:8]);
			2:I2C_WR(WR, addr[7:0]);
			3:I2C_WR(WR|STO, wrdata);
			default;
		endcase
		state <= WAIT_WR_DONE;
	end

(6)等待写完成

根据Trans_Done信号判断I2C最小单元是否发送完成,若完成,则对Ack信号进行赋值,并对cnt计数。当cnt为0时,器件地址发送完成,下一步是发送存储器地址,根据之前计算的addr_mode信号判断是否为16位地址,addr_mode为1,表示16位地址,则cnt为1,发送高8位;addr_mode为0,表示8位地址,跳过发高8位,cnt为2。

	WAIT_WR_DONE:begin
		if(Trans_Done) begin
			Ack = Ack | Ack_o;
			case(cnt)
				0:begin 
					if(addr_mode)
						cnt <= 1;
					else
						cnt <= 2;
					state <= WR_REG; 
				end
				1:begin cnt <= 2; state <= WR_REG; end
				2:begin cnt <= 3; state <= WR_REG; end
				3:begin cnt <= 0; state <= WR_DONE; end
				default:state <= IDLE;
			endcase
		end
		else
			Go = 1'b0;
	end

(7)写完成

写操作完成,将读写完成标志RW_Done置1,跳回空闲状态。

	WR_DONE:begin
		RW_Done <= 1'b1;
		state <= IDLE;
	end

(8)读状态

读状态和写状态类似,调用前面封装好的task任务,将一次读操作分为5步:写器件地址+写标志、写存储器高8位地址、写存储器低8位地址、写器件地址+读命令、写8位数据。cnt在WAIT_RD_DONE中计数。

	RD_REG:begin
		case(cnt)
			0:I2C_WR(STA|WR, device_id & 8'hfe);
			1:I2C_WR(WR, addr[15:8]);
			2:I2C_WR(WR, addr[7:0]);
			3:I2C_WR(STA|WR, device_id | 8'h01);
			4:I2C_RD(RD|NACK|STO);
			default;
		endcase
		state <= WAIT_RD_DONE;
	end

(9)等待读完成

根据Trans_Done信号判断I2C最小单元是否发送完成,并对cnt计数。当cnt为0时,器件地址发送完成,下一步是发送存储器地址,根据之前计算的addr_mode信号判断是否为16位地址,addr_mode为1,表示16位地址,则cnt为1,发送高8位;addr_mode为0,表示8位地址,跳过发高8位,cnt为2。

	WAIT_RD_DONE:begin
		if(Trans_Done) begin
			case(cnt)
				0:begin 
					if(addr_mode)
						cnt <= 1;
					else
						cnt <= 2;
					state <= RD_REG; 
				end
				1:begin cnt <= 2; state <= RD_REG; end
				2:begin cnt <= 3; state <= RD_REG; end
				3:begin cnt <= 4; state <= RD_REG; end
				4:begin cnt <= 0; state <= RD_DONE; end
				default:state <= IDLE;
			endcase
		end
		else
			Go = 1'b0;
	end

(10)读完成

读操作完成,将读写完成标志RW_Done置1,读取到的数据存入rddata,跳回空闲状态。

	RD_DONE:begin
		rddata <= RX_DATA;
		RW_Done <= 1'b1;
		state <= IDLE;
	end

I2C读写控制模块源代码

module i2c_control(
	input Clk,					//系统时钟
	input Rst_n,				//复位信号
	
	input wrreq,				//写请求信号
	input rdreq,				//读请求信号
	input [7:0] device_id,		//7位器件ID
	input [15:0] addr,			//16位存储器地址
	input [7:0] wrdata,			//8位写数据
	
	output reg [7:0] rddata,	//8位读数据
	output reg RW_Done,			//读写完成标志
	output reg Ack,				//应答信号
	output wire i2c_sclk,		//I2C时钟SCLK
	inout i2c_sdat				//I2C数据线SDA
);

	reg [5:0] Cmd;
	reg [8:0] state;
	reg Go;					//单次I2C起始信号
	reg [3:0] cnt;			//单次I2C读写操作计数器
	reg [7:0] TX_DATA;		//单次I2C接收数据
	wire Trans_Done;		//单次I2C完成标志
	wire Ack_o;				//单次I2C应答信号
	wire [7:0] RX_DATA;		//单次I2C读取数据
	
	wire addr_mode;//0:addr为8位地址 1:addr为16位地址
	//addr高八位不等于0,则addr为16位地址,addr_mode为1
	assign addr_mode = (addr[15:8] != 8'd0) ? 1'b1:1'b0;
	
	localparam
		STA =  6'b000001,	//起始位请求
		WR =   6'b000010,	//写请求
		RD =   6'b000100,	//读请求
		STO =  6'b001000,	//停止位请求
		ACK =  6'b010000,	//应答位请求
		NACK = 6'b100000;	//无应答请求
	
	localparam
		IDLE 		 = 7'b0000001, //空闲状态
		WR_REG 		 = 7'b0000010, //写状态
		WAIT_WR_DONE = 7'b0000100, //等待写完成
		WR_DONE 	 = 7'b0001000, //写完成
		RD_REG		 = 7'b0010000, //读状态
		WAIT_RD_DONE = 7'b0100000, //等待读完成
		RD_DONE 	 = 7'b1000000; //读完成

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) begin
		rddata = 8'd0;
		RW_Done = 1'b0;
		Ack = 1'b0;
		cnt = 3'd0;
		state <= IDLE;
	end
	else begin
		case(state)
			IDLE:begin
				cnt <= 8'd0;
				RW_Done = 1'b0;
				Ack = 1'b0;
				if(wrreq)
					state <= WR_REG;
				else if(rdreq)
					state <= RD_REG;
				else
					state <= IDLE;
			end
			
			WR_REG:begin
				case(cnt)
					0:I2C_WR(STA|WR, device_id & 8'hfe);
					1:I2C_WR(WR, addr[15:8]);
					2:I2C_WR(WR, addr[7:0]);
					3:I2C_WR(WR|STO, wrdata);
					default;
				endcase
				state <= WAIT_WR_DONE;
			end
			
			WAIT_WR_DONE:begin
				if(Trans_Done) begin
					Ack = Ack | Ack_o;
					case(cnt)
						0:begin 
							if(addr_mode)
								cnt <= 1;
							else
								cnt <= 2;
							state <= WR_REG; 
						end
						1:begin cnt <= 2; state <= WR_REG; end
						2:begin cnt <= 3; state <= WR_REG; end
						3:begin cnt <= 0; state <= WR_DONE; end
						default:state <= IDLE;
					endcase
				end
				else
					Go = 1'b0;
			end
			
			WR_DONE:begin
				RW_Done <= 1'b1;
				state <= IDLE;
			end
			
			RD_REG:begin
				case(cnt)
					0:I2C_WR(STA|WR, device_id & 8'hfe);
					1:I2C_WR(WR, addr[15:8]);
					2:I2C_WR(WR, addr[7:0]);
					3:I2C_WR(STA|WR, device_id | 8'h01);
					4:I2C_RD(RD|NACK|STO);
					default;
				endcase
				state <= WAIT_RD_DONE;
			end
			
			WAIT_RD_DONE:begin
				if(Trans_Done) begin
					case(cnt)
						0:begin 
							if(addr_mode)
								cnt <= 1;
							else
								cnt <= 2;
							state <= RD_REG; 
						end
						1:begin cnt <= 2; state <= RD_REG; end
						2:begin cnt <= 3; state <= RD_REG; end
						3:begin cnt <= 4; state <= RD_REG; end
						4:begin cnt <= 0; state <= RD_DONE; end
						default:state <= IDLE;
					endcase
				end
				else
					Go = 1'b0;
			end
			
			RD_DONE:begin
				rddata <= RX_DATA;
				RW_Done <= 1'b1;
				state <= IDLE;
			end
			
		endcase
	end

	task I2C_WR;
		input [5:0] cmd;
		input [7:0] wr_DATA;
		begin
			Cmd = cmd;
			TX_DATA = wr_DATA;
			Go = 1'b1;
		end
	endtask
	
	task I2C_RD;
		input [5:0] cmd;
		begin	
			Cmd = cmd;
			Go = 1'b1;
		end
	endtask

	i2c_bit_shift i2c_bit_shift(
		.Clk(Clk),					//系统时钟
		.Rst_n(Rst_n),				//复位信号
		.Cmd(Cmd),					//I2C读写控制命令
		.Go(Go),						//I2C读写开始信号
		.RX_DATA(RX_DATA),		//I2C读取数据
		.TX_DATA(TX_DATA),		//I2C发送数据
		.Trans_Done(Trans_Done),//单次读写操作完成标志
		.Ack_o(Ack_o),				//应答信号ACK
		.i2c_sclk(i2c_sclk),		//I2C时钟SCLK
		.i2c_sdat(i2c_sdat)		//I2C数据线SDA
	);
endmodule

三、I2C读写控制模块仿真

1.读写操作任务封装

将单次读写操作封装成task任务,提高代码的简洁性,仿真时只需要将器件地址device_id、存储单元地址addr和写数据wrdata传入任务即可。

	task I2C_WRITE;
		input [7:0] wr_id;
		input [15:0] wr_addr;
		input [7:0] wr_data;
		begin
			device_id = wr_id;
			addr = wr_addr;
			wrdata = wr_data;
			wrreq = 1'b1;#20;
			wrreq = 1'b0;#20;
			wait(RW_Done);#200;
		end
	endtask
	
	task I2C_READ;
		input [7:0] wr_id;
		input [15:0] wr_addr;
		begin
			device_id = wr_id;
			addr = wr_addr;
			rdreq = 1'b1;#20;
			rdreq = 1'b0;#20;
			wait(RW_Done);#200;
		end
	endtask

2.仿真文件

采用EEPROM仿真模型M24LC04B对本模块进行仿真
向器件地址A0的存储单元16’h0011写入数据8’hCD,向存储单元16’h0145写入数据8’h58,写入后读取这两个存储单元的数据。

`timescale 1ns/1ns

module i2c_control_tb();
	reg Clk,Rst_n;
	reg wrreq,rdreq;
	reg [7:0] device_id;
	reg [15:0] addr;
	reg [7:0] wrdata;
	wire [7:0] rddata;
	wire RW_Done;
	wire Ack;
	wire i2c_sclk;
	wire i2c_sdat;
	
	pullup(i2c_sdat); //模拟外部上拉电阻
	
	i2c_control i2c_control(
		.Clk(Clk),				//系统时钟
		.Rst_n(Rst_n),			//复位信号
		.wrreq(wrreq),			//写请求信号
		.rdreq(rdreq),			//读请求信号
		.device_id(device_id),	//7位器件ID
		.addr(addr),			//16位存储器地址
		.wrdata(wrdata),		//8位写数据
		.rddata(rddata),		//8位读数据
		.RW_Done(RW_Done),	//读写完成标志
		.Ack(Ack),				//应答信号
		.i2c_sclk(i2c_sclk),	//I2C时钟SCLK
		.i2c_sdat(i2c_sdat)	//I2C数据线SDA
	);

	M24LC04B M24LC04B(
		.A0(0), 
		.A1(0), 
		.A2(0), 
		.WP(0),//写保护引脚,高电平不允许写,低电平允许写
		.SDA(i2c_sdat), 
		.SCL(i2c_sclk), 
		.RESET(~Rst_n)
	);

	initial Clk = 1'b1;
	always #10 Clk = ~Clk;

	initial begin
		Rst_n = 1'b0;
		wrreq = 1'b0;
		rdreq = 1'b0;
		device_id = 8'd0;
		addr = 16'd0;
		wrdata = 8'd0;
		#201;
		
		Rst_n = 1'b1;
		#20;
		
		I2C_WRITE(8'hA0,16'h0011,8'hCD);
		I2C_WRITE(8'hA0,16'h0145,8'h58);
		#20000;
		
		I2C_READ(8'hA0,16'h0011);
		I2C_READ(8'hA0,16'h0145);
		#20000;
		$stop;	
	end

	task I2C_WRITE;
		input [7:0] wr_id;
		input [15:0] wr_addr;
		input [7:0] wr_data;
		begin
			device_id = wr_id;
			addr = wr_addr;
			wrdata = wr_data;
			wrreq = 1'b1;#20;
			wrreq = 1'b0;#20;
			wait(RW_Done);#200;
		end
	endtask
	
	task I2C_READ;
		input [7:0] wr_id;
		input [15:0] wr_addr;
		begin
			device_id = wr_id;
			addr = wr_addr;
			rdreq = 1'b1;#20;
			rdreq = 1'b0;#20;
			wait(RW_Done);#200;
		end
	endtask
endmodule

3.仿真结果

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值