FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(3)

单字节写时序

EEPROM器件或其他不同器件,I2C 器件地址字节不同,这样对于 I2C 单字节写时序就会有所差别,如下图分别为 1 字节地址段器件和 2 字节地址段器件单字节写时序图。
在这里插入图片描述
根据时序图,从主机角度来描述一次写入单字节数据过程如下:
主机设置 SDA 为输出;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,对于两字节地址段器件,传输地址数据低字节,对于 1字节地址段器件,主机设置 SDA 为输出,传输待写入的数据;
设置 SDA 为三态门输入,读取从机应答信号,对于两字节地址段器件,接着步骤 i;对于 1 字节地址段器件,直接跳转到步骤 k;
读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据(对于两字节地址段器件);
设置 SDA 为三态门输入,读取从机应答信号(两字节地址段器件);
读取应答信号成功,主机产生 STOP 位,终止传输。

单字节读时序

在这里插入图片描述
根据时序图,从主机角度描述一次读数据过程,如下:
主机设置 SDA 为输出;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 输出,传输 1 字节地址数据;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 输出,对于两字节地址段器件,传输低字节地址数据;对于 1 字节地址段器件,无此步骤,直接跳转到步骤 h;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 1,表明为读操作;
设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
主机产生 STOP 位,终止传输。

I2C 控制器设计

模块框图

在这里插入图片描述
状态机里面线性序列机思路

在这里插入图片描述

scl_high 和 scl_low 产生的时序图

通过上述的讲述,对 I2C 读写器件数据时序有了一定的了解,下面将开始进行控制程序的设计。根据上面 I2C 的基本概念中有关读写时SDA 与 SCL 时序,不管对于从机还是主机,SDA 上的每一位数据在 SCL 的高电平期间保持不变,而数据的改变总是在 SCL 的低电平期间发生。因此,我们可以选用 2 个标志位对时钟 SCL 的高电平和低电平进行标记,如下图所示:scl_high 对 SCL 高电平期间进行标志,scl_low 对 SCL 低电平期间进行标志。这样就可以在 scl_high 有效时读 SDA 数据,在 scl_low 有效时改变数据

如下图所示
在这里插入图片描述

在状态机中,从主机角度来看,SDA 数据线上在写控制、写数据、读控制状态过程是需要串行输出数据,而在读数据状态过程是需要串行输入数据。根据数据在时钟高电平期间保持不变,改变数据在低电平时期的规则,本设计对时钟信号的高低电平进行计数,从而在指定的计数值进行输出或读取数据实现数据的串行输出和串行输入。串行输出和串行输入数据采用任务的形式进行表示,便于在主状态机中多次的调用。图下为计数的过程以及特定状态变化的时序图,这里的特定状态主要是指读/写控制、读/写地址和读/写数据状态。
在这里插入图片描述

状态转移图

定义
在这里插入图片描述

在这里插入图片描述

独热码
在这里插入图片描述

为了节省资源,可以通过上一个状态一定会执行下一个状态进而连贯在一起弄一个组合拳法

创建任务的思想
对于计数器 halfbit_cnt 只在写控制、写数据、读控制、读数据状态下才进行计数,其他状态为零。代码中 FF 是进行串行输出或输入任务的标志位,当 FF 为1 时表示退出任务,FF 为 0 时表示进入任务。这样便于在状态机中对任务的调用,以及在指定的时间退出任务。

状态机里面线性序列机思路

Verilog代码

module I2C(
	Clk,
	Rst_n,
	
	Rddata_num,
	Wrdata_num,
	Wdaddr_num,
	
	Device_addr,
	Word_addr,
	
	Wr,
	Wr_data,
	Wr_data_vaild,
	Rd,
	Rd_data,
	Rd_data_vaild,
	
	Scl,
	Sda,
	Done
);

//系统时钟采用50MHz
parameter SYS_CLOCK = 50_000_000;
//SCL总线时钟采用400kHz
parameter SCL_CLOCK = 400_000;
//产生时钟SCL计数器最大值
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK;

	input             Clk;          //系统时钟
	input             Rst_n;        //系统复位信号
	
	input     [5:0]   Rddata_num;   //I2C总线连续读取数据字节数
	input     [5:0]   Wrdata_num;   //I2C总线连续读取数据字节数
	input     [1:0]   Wdaddr_num;   //I2C器件数据地址字节数
	
	input     [2:0]   Device_addr;  //I2C器件地址
	input     [15:0]  Word_addr;    //I2C寄存器地址
	
	input             Wr;           //I2C器件写使能
	input     [7:0]   Wr_data;      //I2C器件写数据
	output            Wr_data_vaild;//I2C器件写数据有效标志位
	input             Rd;           //I2C器件读使能
	output reg[7:0]   Rd_data;      //I2C器件读数据
	output reg        Rd_data_vaild;//I2C器件读数据有效标志位
	
	output  reg       Scl;          //I2C时钟线
	inout             Sda;          //I2C数据线
	output  reg       Done;         //对I2C器件读写完成标识位
	
  
	
	//主状态机状态
	localparam  IDLE     = 9'b0_0000_0001,//空闲状态
               WR_START = 9'b0_0000_0010,//写开始状态
               WR_CTRL  = 9'b0_0000_0100,//写控制状态
               WR_WADDR = 9'b0_0000_1000,//写地址状态
               WR_DATA  = 9'b0_0001_0000,//写数据状态
               RD_START = 9'b0_0010_0000,//读开始状态
               RD_CTRL  = 9'b0_0100_0000,//读控制状态
               RD_DATA  = 9'b0_1000_0000,//读数据状态
               STOP     = 9'b1_0000_0000;//停止状态
				
	reg [8:0] main_state;	   //主状态机状态寄存器
	reg       sda_en;          //sda数据总线控制位
	reg       sda_reg;         //sda数据输出寄存器
	reg       W_flag;          //IIC写操作标志位
	reg       R_flag;          //IIC读操作标志位
	reg       FF;              //串行输出输入任务执行标志位
	wire[7:0] wr_ctrl_word;    //写控制数据寄存器
	wire[7:0] rd_ctrl_word;    //读控制数据寄存器
	reg [15:0]scl_cnt;         //SCL时钟计数器
	reg       scl_vaild;       //IIC非空闲时期
	reg       scl_high;        //SCL时钟高电平中部标志位
	reg       scl_low;         //SCL时钟低电平中部标志位	
	reg [7:0] halfbit_cnt;     //串行数据传输计数器
	reg       ack;             //串行输出输入高低电平计数完成标志位
	reg [1:0] waddr_cnt;       //地址字节数计数器
	reg [7:0] wdata_cnt;       //写数据字节数计数器
	reg [7:0] rdata_cnt;       //读数据字节数计数器
	reg [7:0] sda_data_out;    //待输出SDA串行数据
	reg [7:0] sda_data_in;     //SDA串行输入后数据
	wire      rdata_vaild_r; 
	
	//读写控制字
	assign wr_ctrl_word = {4'b1010, Device_addr,1'b0};
	assign rd_ctrl_word = {4'b1010, Device_addr,1'b1};
	
	//I2C数据线采用三态门传输
	assign Sda = sda_en ? sda_reg : 1'bz;
	
	//I2C非空闲时期scl_vaild的产生
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			scl_vaild <= 1'b0;
		else if(Wr | Rd)
			scl_vaild <= 1'b1;
		else if(Done)
			scl_vaild <= 1'b0;
		else
			scl_vaild <= scl_vaild;
	end

	//scl时钟计数器
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			scl_cnt <= 16'd0;
		else if(scl_vaild)begin
			if(scl_cnt == SCL_CNT_M - 1)
				scl_cnt <= 16'd0;
			else
				scl_cnt <= scl_cnt + 16'd1;
		end
		else
			scl_cnt <= 16'd0;
	end

	//scl时钟,在计数器值到达最大值一半和0时翻转
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			Scl <= 1'b1;
		else if(scl_cnt == SCL_CNT_M >>1)
			Scl <= 1'b0;
		else if(scl_cnt == 16'd0)
			Scl <= 1'b1;
		else
			Scl <= Scl;
	end	

	//scl时钟高低电平中部标志位
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			scl_high <= 1'b0;
		else if(scl_cnt == (SCL_CNT_M>>2))
			scl_high <= 1'b1;
		else
			scl_high <= 1'b0;
	end
	//scl时钟低电平中部标志位
	//(SCL_CNT_M>>2)和(SCL_CNT_M>>1)+(SCL_CNT_M>>2)分别为 1/4 的 SCL_CNT_M 和 3/4 的SCL_CNT_M 的计数值。
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			scl_low <= 1'b0;
		else if(scl_cnt == (SCL_CNT_M>>1)+(SCL_CNT_M>>2))
			scl_low <= 1'b1;
		else
			scl_low <= 1'b0;
	end	

	//主状态机
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)begin
			main_state <= IDLE;
			sda_reg    <= 1'b1;//三态控制信号
			W_flag     <= 1'b0;//写标注      
			R_flag     <= 1'b0;//读标注 			
			Done       <= 1'b0;//完成
			waddr_cnt  <= 2'd1;//地址 
			wdata_cnt  <= 8'd1;//写数据
			rdata_cnt  <= 8'd1;//读数据
		end
		else begin
			case(main_state)
				IDLE:begin//空闲状态
					sda_reg   <= 1'b1;
					W_flag    <= 1'b0;
					R_flag    <= 1'b0;
					Done      <= 1'b0;
					waddr_cnt <= 2'd1;
					wdata_cnt <= 8'd1;
					rdata_cnt <= 8'd1;
					if(Wr)begin
						main_state <= WR_START;
						W_flag     <= 1'b1;
					end
					else if(Rd)begin
						main_state <= WR_START;
						R_flag     <= 1'b1;
					end
					else
						main_state <= IDLE;
				end

				WR_START:begin//写开始状态
					if(scl_low)begin//在scl_low寄存器下个周期发送起始信号
						main_state   <= WR_CTRL;
						sda_data_out <= wr_ctrl_word;
						FF           <= 1'b0;
					end
					else if(scl_high)begin
						sda_reg    <= 1'b0;
						main_state <= WR_START;
					end
					else
						main_state <= WR_START;
				end

				WR_CTRL:begin//写控制状态
					if(FF == 1'b0)
						send_8bit_data;
					else begin
						if(ack == 1'b1) begin//收到响应
							if(scl_low)begin
								main_state   <= WR_WADDR;
								FF           <= 1'b0;
								if(Wdaddr_num == 2'b1)
									sda_data_out <= Word_addr[7:0];
								else
									sda_data_out <= Word_addr[15:8];
							end
							else
								main_state   <= WR_CTRL;
						end
						else//未收到响应
							main_state      <= IDLE;
					end
				end

				WR_WADDR:begin//写地址状态
					if(FF == 1'b0)
						send_8bit_data;
					else begin
						if(ack == 1'b1) begin//收到响应
							if(waddr_cnt == Wdaddr_num)begin
								if(W_flag && scl_low)begin
									main_state   <= WR_DATA;
									sda_data_out <= Wr_data;
									waddr_cnt    <= 2'd1;
									FF           <= 1'b0;
								end
								else if(R_flag && scl_low)begin
									main_state <= RD_START;
									sda_reg    <= 1'b1;
								end
								else
									main_state <= WR_WADDR;
							end
							else begin
								if(scl_low)begin
									waddr_cnt    <= waddr_cnt + 2'd1;
									main_state   <= WR_WADDR;
									sda_data_out <= Word_addr[7:0];
									FF           <= 1'b0;
								end
								else
									main_state   <= WR_WADDR;
							end
						end
						else//未收到响应
							main_state <= IDLE;
					end
				end

				WR_DATA:begin//写数据状态
					if(FF == 1'b0)
						send_8bit_data;
					else begin
						if(ack == 1'b1) begin//收到响应
							if(wdata_cnt == Wrdata_num)begin
								if(scl_low)begin
									main_state <= STOP;
									sda_reg    <= 1'b0;
									wdata_cnt  <= 8'd1;
								end
								else
									main_state <= WR_DATA;
							end
							else begin
								if(scl_low)begin
									wdata_cnt    <= wdata_cnt + 8'd1;
									main_state   <= WR_DATA;
									sda_data_out <= Wr_data;
									FF           <= 1'b0;
								end
								else
									main_state   <= WR_DATA;
							end
						end
						else//未收到响应
							main_state <= IDLE;
					end
				end

				RD_START:begin//读开始状态
					if(scl_low)begin
						main_state   <= RD_CTRL;
						sda_data_out <= rd_ctrl_word;
						FF           <= 1'b0;
					end
					else if(scl_high)begin
						main_state <= RD_START;
						sda_reg    <= 1'b0;
					end
					else
						main_state <= RD_START;
				end

				RD_CTRL:begin//读控制状态
					if(FF == 1'b0)
						send_8bit_data;
					else begin
						if(ack == 1'b1) begin//收到响应
							if(scl_low)begin
								main_state <= RD_DATA;
								FF         <= 1'b0;
							end
							else
								main_state <= RD_CTRL;
						end
						else//未收到响应
							main_state    <= IDLE;
					end
				end
				
				RD_DATA:begin//读数据状态
					if(FF == 1'b0)
						receive_8bit_data;
					else begin
						if(rdata_cnt == Rddata_num)begin
							sda_reg <= 1'b1;
							if(scl_low)begin
								main_state <= STOP;
								sda_reg    <= 1'b0;
							end
							else
								main_state <= RD_DATA;
						end
						else begin
							sda_reg <= 1'b0;
							if(scl_low)begin
								rdata_cnt  <= rdata_cnt + 8'd1;
								main_state <= RD_DATA;
								FF         <= 1'b0;
							end
							else
								main_state <= RD_DATA;
						end
					end
				end

				STOP:begin//结束操作
					if(scl_high)begin
						sda_reg    <= 1'b1;
						main_state <= IDLE;
						Done       <= 1'b1;
					end
					else
						main_state <= STOP;
				end
				
				default:
					main_state <= IDLE;
			endcase
		end
	end
		
	//sda串行接收与发送时scl高低电平计数器根据中计数器 halfbit_cnt 和数据接收方对发送的响应检测标志位 ack 以及串行输出、输入数据任务
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			halfbit_cnt <= 8'd0;
		else if((main_state == WR_CTRL)||
		        (main_state == WR_WADDR)||
				  (main_state == WR_DATA)||
				  (main_state == RD_CTRL)||
				  (main_state == RD_DATA))begin
			if(scl_low | scl_high)begin
				if(halfbit_cnt == 8'd17)
					halfbit_cnt <= 8'd0;
				else
					halfbit_cnt <= halfbit_cnt + 8'd1;
			end
			else
				halfbit_cnt <= halfbit_cnt;
		end
		else
			halfbit_cnt <= 8'd0;
	end

	//数据接收方对发送的响应检测标志位
	always@(posedge Clk or negedge Rst_n)
	begin
		if(!Rst_n)
			ack <= 1'b0;
		else if((halfbit_cnt == 8'd16)&&scl_high&&(Sda==1'b0))
			ack <= 1'b1;
		else if((halfbit_cnt == 8'd17)&&scl_low)
			ack <= 1'b0;
		else
			ack <= ack;
	end
	
   //输出串行数据任务
	task send_8bit_data;
	if(scl_high && (halfbit_cnt == 8'd16))
		FF <= 1;
	else if(halfbit_cnt < 8'd17)begin
		sda_reg <= sda_data_out[7];
		if(scl_low)
			sda_data_out <= {sda_data_out[6:0],1'b0};
		else
			sda_data_out <= sda_data_out;
	end
	else
		;
	endtask
	
	//串行数据输入任务
	task receive_8bit_data;
	if(scl_low && (halfbit_cnt == 8'd15))
		FF <= 1;
	else if((halfbit_cnt < 8'd15))begin
		if(scl_high)
			sda_data_in <= {sda_data_in[6:0],Sda};
		else begin
			sda_data_in <= sda_data_in;
		end
	end
	else
		;
	endtask

	//sda三态使能信号sda_en
	always@(*)
	begin
		case(main_state)
		IDLE:
			sda_en = 1'b0;

		WR_START,RD_START,STOP:
			sda_en = 1'b1;

		WR_CTRL,WR_WADDR,WR_DATA,RD_CTRL:
			if(halfbit_cnt < 16)
				sda_en = 1'b1;
			else
				sda_en = 1'b0;

		RD_DATA:
			if(halfbit_cnt < 16)
				sda_en = 1'b0;
			else
				sda_en = 1'b1;		
		default:

			sda_en = 1'b0;		
		endcase
	end

	//写数据有效标志位
	assign Wr_data_vaild = ((main_state==WR_WADDR)&&
	                       (waddr_cnt==Wdaddr_num)&&
								  (W_flag && scl_low)&&
								  (ack == 1'b1))||
                     	  ((main_state == WR_DATA)&&
								  (ack == 1'b1)&&(scl_low)&&
								  (wdata_cnt != Wrdata_num));

	//读数据有效标志位前寄存器
	assign rdata_vaild_r = (main_state == RD_DATA)
	                        &&(halfbit_cnt == 8'd15)&&scl_low;

	//读出数据有效标志位
	always@(posedge Clk or negedge Rst_n)
	begin
	if(!Rst_n)
		Rd_data_vaild <= 1'b0;
	else if(rdata_vaild_r)
		Rd_data_vaild <= 1'b1;
	else
		Rd_data_vaild <= 1'b0;
	end

	//读出的有效数据
	always@(posedge Clk or negedge Rst_n)
	begin
	if(!Rst_n)
		Rd_data <= 8'd0;
	else if(rdata_vaild_r)
		Rd_data <= sda_data_in;
	else
		Rd_data <= Rd_data;
	end

endmodule 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢谢~谢先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值