IIC控制器的verilog && C语言实现

verilog实现方法
学习了《FPGA的自学笔记设计与验证》串口读写EEPROM这一章节后,会画时序图和状态转移图,并根据状态转移图和时序图写verilog代码。
I2C 协议整体时序说明如下:
总线空闲状态:SDA 为高电平,SCL 为高电平;
I2C 协议起始位:SCL 为高电平时,SDA 出现下降沿,产生一个起始位;
I2C 协议结束位:SCL 为高电平时,SDA 出现上升沿,产生一个结束位;
I2C 读写数据状态:主要包括数据的串行输出输入和数据接收方对数据发送
方的响应信号。
在这里插入图片描述
I2C 控制命令传输的数据格式示意图
在这里插入图片描述
1 字节地址器件的地址分布
在这里插入图片描述
2 字节地址器件的地址分布
在这里插入图片描述
1 字节地址段器件单字节写时序
在这里插入图片描述
2 字节地址段器件单字节写时序
在这里插入图片描述
I2C 控制器设计
通过上述的讲述,对 I2C 读写器件数据时序有了一定的了解,下面将开始进
行控制程序的设计。根据上面 I2C 的基本概念中有关读写时 SDA 与 SCL 时序,
不管对于从机还是主机,SDA 上的每一位数据在 SCL 的高电平期间保持不变,
而数据的改变总是在 SCL 的低电平期间发生。因此,我们可以选用 2 个标志位
对时钟 SCL 的高电平和低电平进行标记,如下图所示:scl_high 对 SCL 高电平
期间进行标志,scl_low 对 SCL 低电平期间进行标志。这样就可以在 scl_high 有效时读 SDA 数据,在 scl_low 有效时改变数据。scl_high 和 scl_low 产生的时序图如图所示。
在这里插入图片描述
时钟信号 SCL 采用计数器方法产生,计数器最大计数值为系
统时钟频率除以 SCL 时钟频率,即:SCL_CNT_M = SYS_CLOCK/SCL_CLOCK。
对于 scl_high 和 scl_low 则只需要分别在计数到四分之一的最大值和四分之三的
最大值时产生标志位即可

状态转移图如下
在这里插入图片描述
数据串行传输过程计数器和状态变化时序图如下
在这里插入图片描述

/***************************************************/
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时钟低电平中部标志位
	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
						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高低电平计数器
	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 

C语言的实现方式
I2C的头文件如下

#ifndef __I2C_H_
#define __I2C_H_

#include<reg51.h>

//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif

#ifndef uint 
#define uint unsigned int
#endif

//--定义使用的IO口--//
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;

//--声明全局变量--//
void I2C_Delay10us();
void I2C_Start();
void I2C_Stop();
uchar I2C_SendByte(uchar dat, uchar ack);
uchar I2C_ReadByte();

#endif

I2C源文件如下

#include "i2c.h"     
				          
/*******************************************************************************
* 函 数 名         : Delay1us()
* 函数功能		   : 延时
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void I2C_Delay10us()
{
	uchar a, b;		  
	for(b=1; b>0; b--)
	{
		for(a=2; a>0; a--);	      
	}
}
/*******************************************************************************
* 函 数 名         : I2C_Start()
* 函数功能		   : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿
* 输    入         : 无
* 输    出         : 无
* 备    注         : 起始之后I2C_SDA和I2C_SCL都为0
*******************************************************************************/

void I2C_Start()
{
	I2C_SDA = 1;
	I2C_Delay10us();
	I2C_SCL = 1;
	I2C_Delay10us();//建立时间是I2C_SDA保持时间>4.7us
	I2C_SDA = 0;
	I2C_Delay10us();//保持时间是>4us
	I2C_SCL = 0;			
	I2C_Delay10us();		
}
/*******************************************************************************
* 函 数 名           : I2C_Stop()
* 函数功能	         : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
* 输    入           : 无
* 输    出         	 : 无
* 备    注           : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲
*******************************************************************************/

void I2C_Stop()
{
	I2C_SDA = 0;
	I2C_Delay10us();
	I2C_SCL = 1;
	I2C_Delay10us();//建立时间大于4.7us
	I2C_SDA = 1;
	I2C_Delay10us();	
	
}
/*******************************************************************************
* 函 数 名           : I2cSendByte(uchar num)
* 函数功能 	         : 通过I2C发送一个字节。在I2C_SCL时钟信号高电平期间,
*                    * 保持发送信号I2C_SDA保持稳定
* 输    入           : num ,ack
* 输    出         	 : 0或1。发送成功返回1,发送失败返回0
* 备    注           : 发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0
*******************************************************************************/

uchar I2C_SendByte(uchar dat, uchar ack)
{
	uchar a = 0,b = 0;//最大255,一个机器周期为1us,最大延时255us。
	// 为了保证时序正确,这里应该加一句 SCL = 0;		
	for(a=0; a<8; a++)//要发送8位,从最高位开始
	{
		I2C_SDA = dat >> 7;	 //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号
		dat = dat << 1;
		I2C_Delay10us();
		I2C_SCL = 1;
		I2C_Delay10us();//建立时间>4.7us
		I2C_SCL = 0;
		I2C_Delay10us();//时间大于4us		
	}

	I2C_SDA = 1;			// 主设备释放SDA线给从设备去操作
	I2C_Delay10us();
	I2C_SCL = 1;			// 主设备开始了第9个周期
	while(I2C_SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低
	{
		b++;
		if(b > 200)	 //如果超过200us没有应答发送失败,或者为非应答,表示接收结束
		{
			I2C_SCL = 0;
			I2C_Delay10us();
			return 0;
		}
	}

	I2C_SCL = 0;
	I2C_Delay10us();
 	return 1;		
}
/*******************************************************************************
* 函 数 名           : I2cReadByte()
* 函数功能	    	 : 使用I2c读取一个字节
* 输    入           : 无
* 输    出         	 : dat
* 备    注           : 接收完一个字节I2C_SCL=0
*******************************************************************************/

uchar I2C_ReadByte()
{
	uchar a = 0,dat = 0;
	I2C_SDA = 1;			//起始和发送一个字节之后I2C_SCL都是0
	I2C_Delay10us();
	
	for(a=0; a<8; a++)//接收8个字节
	{
		I2C_SCL = 1;		// 通知从设备我要开始读了,可以放1bit数据到SDA了
		I2C_Delay10us();
		dat <<= 1;		  	// 读取的时候是高位在前的
		dat |= I2C_SDA;
		I2C_Delay10us();
		I2C_SCL = 0;		// 拉低,为下一个bit的周期做准备
		I2C_Delay10us();
	}
	return dat;		
}

总结:从这两种实现方式来看,可以看出C语言是面向过程的语言,而verilog是一种描述语言,描述的是电路。

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值