FPGA之IIC(I2C)通信EEPROM控制器设计

关于IIC通信用一个具体实例来进行说明(含设计代码)

几个重要的信号

空闲状态:SDA,SCL均处于高电平状态。

起始信号:SCL为高电平时SDA由高到低跳变

停止信号:SCL为高电平时SDA由低到高跳变

数据有效性:在SCL为高期间,SDA信号必须保持稳定不能发生变化,只有在SCL为低电平时SDA信号才能发生变化。

应答信号ACK:要求在发送或者接受时候每第8个周期下降沿后的低低电平将SDA释放同时拉低SDA,在SCL在第9个时钟周期高电平时SDA为低电平说明主机或者从机接受到数据,为高说明未接受或者不想接受了。

下面以EEPROM为例型号为AT24C64A这是一个存储容量为65536bit的存储芯片,地址为8192个地址位是13位,每位一个字节。

    共有8个引脚其中在控制器设计时候只需要操控SDA和SCL这两根线,A0-A2是它的地址位低三位,可以扩展8片,高四位是固定的为:1010。WP是写保护位,此位置高则写无效。

    时序参数如图:

总线时序图:

 

SDA在SCL为低的时候才可以变化,且在SCL为高时禁止变化。在eeprom工作之前SCL和SDA必须为高,当SDA由高到底后才标志着器件可以工作,而结束位是在SCL为高时SDA由低变高。在传入地址或者数据时接收方在地址或者数据接收到后必须发送应答位,应答位为低,如果是高说明没有正确接收或者不想接受了。我一开始不解为什么在从机发送数据时主机释放总线SDA,那接收的数据由谁判断呢?最后才明白,这个“释放”是从主机给输出一端的信号是高阻态,这时sda_out并不输出到sda总线,sda_en让sda为高阻了,这个高阻一般是OC门或者集电极开路状态。这时总线的状态就不由sda_out来控制了,此时eeprom是发送设备,而控制器也就是FPGA这时是接收器,可以接收此时sda上的电平高低来判断是ACK(低)还是NO ACK(高),由sda_in接收。可见数电(模电)学好很有必要,阎石老师的数字电路基础第五版的第三章有详细讲OC门和各种CMOS组成的基本电路。

系统框架:

状态转移图

在设计中代码已经给了比较详细的注释,其中分频时钟为了仿真分析方便没有按照芯片要求的范围进行设计,如有需要可以自行修改。注意下面这种scl时钟一定是像I2C这种在低速(不是I2C传输模式中的低速,而是I2C所有模式相对来说都是低速)传输的条件下才能以这种形式产生!

/*--------------------------------------------
description:	AT24C64 65532bit/32byte x 256 
				addr:0 —— 8192 13bit
				one page 32byte
---------------------------------------------*/
module	iic_driver(
		input				sclk,//系统时钟50MHz		
		input				rst_n,//复位信号低有效
		input				dri_en,//驱动器使能信号
		input				wr,//读写信号
		input		[15:0]	addr,//输入的地址
		input		[7:0]	w_data,//写入的数据
		
		output	reg	[7:0]	rd_data,//读出的数据
		output				i2c_done,//完成标志
		output				scl_o,//输出的时钟
		inout				sda//总线
					
);
		reg		[15:0]	addr_r;		//输入地址寄存器,用于并转串
		reg		[7:0]	w_data_r;	//写数据寄存器
		reg				st_done;	//一次操作完标志
		reg				d_clk;		//驱动器的时钟
		reg				wr_flag;	//读写标志,低表示写,高表示读
		reg				sda_en;		//控制总线的三态输出
		reg		[6:0]	cnt;		//每一个状态进度计数
		reg		[9:0]	clk_cnt;	//分频计数
		reg				sda_out;	//总线数据输出:控制器到eeprom
		reg		[3:0]	state;		//状态
		reg		[7:0]	rd_data_r;	//读取数据寄存器
		reg				scl;		//用于生成eeprom的时钟
		reg				i2c_done_r; //产生结束标志寄存器
		reg				sda_in;//wire			sda_in;		//从eeprom读出的数据或者ACK OR     
                                                               NOACK,为了仿真暂时不设为 
                                                                wire类型
		reg		[9:0]	wr_cyc;//写等待5ms
		reg				wdone;//写完标志

		localparam	IDLE	= 4'b0000;	//默认状态              
		localparam	SADDR	= 4'b0001;  //发送从设备地址        
		localparam	ADDR_H	= 4'b0010;  //加载读或者写的地址高位
		localparam	ADDR_L	= 4'b0011;  //加载地址低位          
		localparam	WRITE	= 4'b0100;  //写状态                
		localparam	READ	= 4'b0101;  //读状态                
		localparam	DATA_RD	= 4'b0110;  //读出数据串转并        
		localparam	STOP	= 4'b0111;	//结束状态
		
		parameter		SLAVE_ADDR = 7'b101_0000;//eeprom的器件地址
		
assign  sda     = sda_en ?  sda_out : 1'bz;     // sda_en 为高SDA数据输出,为低主机释放sda总线
//assign  sda_in  = sda ;                     	// SDA数据输入,从eeprom中读取的数据
assign	scl_o = scl;
assign	i2c_done = i2c_done_r;

//分频模块
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0 )
		begin
          d_clk <= 1'b1;
          clk_cnt <= 'd0;
		end
	else if(clk_cnt	== 3'd7)
		begin
   			d_clk <= ~d_clk;
   			clk_cnt <= 'd0;
		end
	else
		clk_cnt <= clk_cnt + 1'b1;
		
always @(posedge d_clk or negedge rst_n)
	if(rst_n == 1'b0 )
		begin
        	wr_cyc <= 'd0;
        	wdone <= 1'b0;
		end
	else if(state == STOP)
		begin
			if(wr_cyc == 10'd200)
				begin
					wr_cyc <= 'd0;
					wdone <= 1'b1;
				end
			else
				begin
					wr_cyc <= wr_cyc + 1'b1;
					wdone <= 1'b0;
				end	   
		end
				
always @(posedge d_clk or negedge rst_n)
	if(rst_n == 1'b0 )
		begin
        	scl <= 1'b1;     
			sda_out <= 1'b1;  
			sda_en <= 1'b1;
			i2c_done_r <= 1'b0;  
 			cnt <= 'd0;      
			st_done <= 1'b0;   
			rd_data_r <= 'd0;   
			wr_flag <= 1'b0;  
			addr_r <= 'd0; 
			w_data_r<='d0;
			state <= IDLE;
			sda_in <= 1'b0;
		end
	else 
		begin
			cnt <= cnt + 1'b1;
			st_done <= 1'b0;
   			case (state)
   				IDLE :	if(dri_en == 1'b0)
   							begin
   								state <= IDLE;
   							end
   						else 
   							begin
   								addr_r <= addr;
   								wr_flag <= wr;
   								w_data_r <= w_data;
   								state <= SADDR; 
   							end
   				SADDR : if(st_done == 1'b0)
   							begin
   								state <= SADDR;
   								case (cnt)
   									7'd1 : sda_out <= 1'b0;// 开始I2C
                    				7'd3 : scl <= 1'b0;
                    				7'd4 : sda_out <= SLAVE_ADDR[6];// 传送器件地址
                    				7'd5 : scl <= 1'b1;
                    				7'd7 : scl <= 1'b0;
                    				7'd8 : sda_out <= SLAVE_ADDR[5];
                    				7'd9 : scl <= 1'b1;
                    				7'd11: scl <= 1'b0;
                    				7'd12: sda_out <= SLAVE_ADDR[4];
                    				7'd13: scl <= 1'b1;
                    				7'd15: scl <= 1'b0;
                    				7'd16: sda_out <= SLAVE_ADDR[3];
                    				7'd17: scl <= 1'b1;
                    				7'd19: scl <= 1'b0;
                    				7'd20: sda_out <= SLAVE_ADDR[2];
                    				7'd21: scl <= 1'b1;
                    				7'd23: scl <= 1'b0;
                    				7'd24: sda_out <= SLAVE_ADDR[1];
                    				7'd25: scl <= 1'b1;
                    				7'd27: scl <= 1'b0;
                    				7'd28: sda_out <= SLAVE_ADDR[0];
                    				7'd29: scl <= 1'b1;
                    				7'd31: scl <= 1'b0;
                    				7'd32: sda_out <= 1'b0;// 0:写
                    				7'd33: scl <= 1'b1;
                    				7'd35: scl <= 1'b0;
                    				7'd36: begin
                    				    	sda_en <= 1'b0;// 主机释放sda总线从机应答
                    				    	sda_out <= 1'b1;//将输出拉高
                    				end
                    				7'd37: scl     <= 1'b1;
                    				7'd38: st_done <= 1'b1;
                    				7'd39: begin
                    				   		 scl <= 1'b0;
                    				    	 cnt <= 1'b0;
                    					   end
                    			 	default :  ;						 			
   								endcase
   							end
   						else begin
   								if(sda_in == 1'b0)
   									begin	
   										state <= ADDR_H;//应答位位低电平说明收到数据
										scl <= 1'b0;
                    					cnt <= 1'b0;
                    				end
   								else 
   									state <= SADDR; //没有收到完整数据,重新传输器件地址
   							 end
   				ADDR_H : if(st_done == 1'b0) 
   							begin
                				case(cnt)
                				    7'd0 : begin
										   sda_en <= 1'b1;//主机重新控制总线
										   sda_out <= addr_r[15];// 传送字地址高8位
									end
                				    7'd1 : scl <= 1'b1;
                				    7'd3 : scl <= 1'b0;
                				    7'd4 : sda_out <= addr_r[14];
                				    7'd5 : scl <= 1'b1;
                				    7'd7 : scl <= 1'b0;
                				    7'd8 : sda_out <= addr_r[13];
                				    7'd9 : scl <= 1'b1;
                				    7'd11: scl <= 1'b0;
                				    7'd12: sda_out <= addr_r[12];
                				    7'd13: scl <= 1'b1;
                				    7'd15: scl <= 1'b0;
                				    7'd16: sda_out <= addr_r[11];
                				    7'd17: scl <= 1'b1;
                				    7'd19: scl <= 1'b0;
                				    7'd20: sda_out <= addr_r[10];
                				    7'd21: scl <= 1'b1;
                				    7'd23: scl <= 1'b0;
                				    7'd24: sda_out <= addr_r[9];
                				    7'd25: scl <= 1'b1;
                				    7'd27: scl <= 1'b0;
                				    7'd28: sda_out <= addr_r[8];
                				    7'd29: scl <= 1'b1;
                				    7'd31: scl <= 1'b0;
                				    7'd32: begin
                				        sda_en <= 1'b0;// 从机应答
                				        sda_out <= 1'b1;
                				    end
                				    7'd33: scl <= 1'b1;
                				    7'd34: st_done <= 1'b1;
                				    7'd35: begin
                				        scl <= 1'b0;
                				        cnt <= 1'b0;
                				    end
                				    default :  ;
                				endcase
            				end
            			 else begin
            			 		if(sda_in == 1'b0)begin
            			 			state <= ADDR_L;
            			 			 scl <= 1'b0;
                				        cnt <= 1'b0;
                				    end
            			 		else
            			 			state <= ADDR_H;
            				  end
   				ADDR_L	: if(st_done == 1'b0)
   						 begin
   						 	case(cnt)
                		    		7'd0: begin
                		    		   sda_en <= 1'b1;//主机重新控制总线
                		    		   sda_out <= addr_r[7];            // 字地址
                		    		end
                		    		7'd1 : scl <= 1'b1;
                		    		7'd3 : scl <= 1'b0;
                		    		7'd4 : sda_out <= addr_r[6];
                		    		7'd5 : scl <= 1'b1;
                		    		7'd7 : scl <= 1'b0;
                		    		7'd8 : sda_out <= addr_r[5];
                		    		7'd9 : scl <= 1'b1;
                		    		7'd11: scl <= 1'b0;
                		    		7'd12: sda_out <= addr_r[4];
                		    		7'd13: scl <= 1'b1;
                		    		7'd15: scl <= 1'b0;
                		    		7'd16: sda_out <= addr_r[3];
                		    		7'd17: scl <= 1'b1;
                		    		7'd19: scl <= 1'b0;
                		    		7'd20: sda_out <= addr_r[2];
                		    		7'd21: scl <= 1'b1;
                		    		7'd23: scl <= 1'b0;
                		    		7'd24: sda_out <= addr_r[1];
                		    		7'd25: scl <= 1'b1;
                		    		7'd27: scl <= 1'b0;
                		    		7'd28: sda_out <= addr_r[0];
                		    		7'd29: scl <= 1'b1;
                		    		7'd31: scl <= 1'b0;
                		    		7'd32: begin
                		    		    sda_en <= 1'b0;//主机释放sda线,从机应答,应答信号由sda线传给sda_in,由主机判断应答信号高低
                		    		    sda_out <= 1'b1;
                		    		end
                		    		7'd33: scl <= 1'b1;
                		    		7'd34: st_done <= 1'b1;
                		    		7'd35: begin
                		    		    scl <= 1'b0;
                		    		    cnt <= 1'b0;
                		    		    wr_flag <= wr;
                		    		end
                		    		default :  ;
                		 	endcase
           	    		 end 
           	    		  else	begin	
           	    		  			if(sda_in == 1'b0)
           	    		  				begin
           	    		  					if(wr_flag == 1'b0)begin
           	    		  						state <= WRITE;
           	    		  						scl <= 1'b0;
                		    		   			cnt <= 1'b0;
                		    		    		wr_flag <= wr;
                		    				end
           	    		  					else
           	    		  						state <= READ;
           	    		  				end
           	    		  				
           	    		  			else
           	    		  				state <= ADDR_L;
           	    		  		end
           	    		  	  				
   			
   				WRITE	: if(st_done == 1'b0)
   						begin	// 写数据(8 bit)
                				case(cnt)
                				    7'd0: begin
                				        sda_out <= w_data_r[7];        // I2C写8位数据
                				        sda_en <= 1'b1;
                				    end
                				    7'd1 : scl <= 1'b1;
                				    7'd3 : scl <= 1'b0;
                				    7'd4 : sda_out <= w_data_r[6];
                				    7'd5 : scl <= 1'b1;
                				    7'd7 : scl <= 1'b0;
                				    7'd8 : sda_out <= w_data_r[5];
                				    7'd9 : scl <= 1'b1;
                				    7'd11: scl <= 1'b0;
                				    7'd12: sda_out <= w_data_r[4];
                				    7'd13: scl <= 1'b1;
                				    7'd15: scl <= 1'b0;
                				    7'd16: sda_out <= w_data_r[3];
                				    7'd17: scl <= 1'b1;
                				    7'd19: scl <= 1'b0;
                				    7'd20: sda_out <= w_data_r[2];
                				    7'd21: scl <= 1'b1;
                				    7'd23: scl <= 1'b0;
                				    7'd24: sda_out <= w_data_r[1];
                				    7'd25: scl <= 1'b1;
                				    7'd27: scl <= 1'b0;
                				    7'd28: sda_out <= w_data_r[0];
                				    7'd29: scl <= 1'b1;
                				    7'd31: scl <= 1'b0;
                				    7'd32: begin
                				        sda_en <= 1'b0;                // 从机应答
                				        sda_out <= 1'b1;
                				    end
                				    7'd33: scl <= 1'b1;
                				    7'd34: st_done <= 1'b1;
                				    7'd35: begin
                				        scl  <= 1'b0;
                				        cnt  <= 1'b0;
                				    end
                				    default  :  ;
                				endcase
            			end
						  else
						  	begin
						  		if(sda_in == 1'b0)begin
						  			state <= STOP;
						  			scl  <= 1'b0;
                				    cnt  <= 1'b0;
                				end
						  		else
						  			state <= WRITE;
						  	end
            	READ	: if(st_done == 1'b0)
            			begin                           // 写地址以进行读数据
                			case(cnt)
                			    7'd0 : begin
                			        sda_en <= 1'b1;
                			        sda_out <= 1'b1;
                			    end
                			    7'd1 : scl <= 1'b1;
                			    7'd2 : sda_out <= 1'b0;             // 重新开始
                			    7'd3 : scl <= 1'b0;
                			    7'd4 : sda_out <= SLAVE_ADDR[6];    // 传送器件地址
                			    7'd5 : scl <= 1'b1;
                			    7'd7 : scl <= 1'b0;
                			    7'd8 : sda_out <= SLAVE_ADDR[5];
                			    7'd9 : scl <= 1'b1;
                			    7'd11: scl <= 1'b0;
                			    7'd12: sda_out <= SLAVE_ADDR[4];
                			    7'd13: scl <= 1'b1;
                			    7'd15: scl <= 1'b0;
                			    7'd16: sda_out <= SLAVE_ADDR[3];
                			    7'd17: scl <= 1'b1;
                			    7'd19: scl <= 1'b0;
                			    7'd20: sda_out <= SLAVE_ADDR[2];
                			    7'd21: scl <= 1'b1;
                			    7'd23: scl <= 1'b0;
                			    7'd24: sda_out <= SLAVE_ADDR[1];
                			    7'd25: scl <= 1'b1;
                			    7'd27: scl <= 1'b0;
                			    7'd28: sda_out <= SLAVE_ADDR[0];
                			    7'd29: scl <= 1'b1;
                			    7'd31: scl <= 1'b0;
                			    7'd32: sda_out <= 1'b1;             // 1:读
                			    7'd33: scl <= 1'b1;
                			    7'd35: scl <= 1'b0;
                			    7'd36: begin
                			        sda_en <= 1'b0;                // 从机应答
                			        sda_out <= 1'b1;
                			    end
                			    7'd37: scl <= 1'b1;
                			    7'd38: st_done <= 1'b1;
                			    7'd39: begin
                			        scl <= 1'b0;
                			        cnt <= 1'b0;
                			    end
                			    default : ;
                			endcase
            			end
            			  else
            			  	begin
            			  		if(sda_in == 1'b0)begin
            			  			state <= DATA_RD;
            			  			scl <= 1'b0;
                			        cnt <= 1'b0;
                			    end
            			  		else
            			  			state <= READ;
            			  	end
            	DATA_RD	: if(st_done == 1'b0)
            			begin                          // 读取数据(8 bit)
							case(cnt)
                    			7'd0: sda_en <= 1'b0;
                    			7'd1: begin
                    			    rd_data_r[7] <= sda_in;
                    			    scl       <= 1'b1;
                    			end
                    			7'd3: scl  <= 1'b0;
                    			7'd5: begin
                    			    rd_data_r[6] <= sda_in ;
                    			    scl       <= 1'b1   ;
                    			end
                    			7'd7: scl  <= 1'b0;
                    			7'd9: begin
                    			    rd_data_r[5] <= sda_in;
                    			    scl       <= 1'b1  ;
                    			end
                    			7'd11: scl  <= 1'b0;
                    			7'd13: begin
                    			    rd_data_r[4] <= sda_in;
                    			    scl       <= 1'b1  ;
                    			end
                    			7'd15: scl  <= 1'b0;
                    			7'd17: begin
                    			    rd_data_r[3] <= sda_in;
                    			    scl       <= 1'b1  ;
                    			end
                    			7'd19: scl  <= 1'b0;
                    			7'd21: begin
                    			    rd_data_r[2] <= sda_in;
                    			    scl       <= 1'b1  ;
                    			end
                    			7'd23: scl  <= 1'b0;
                    			7'd25: begin
                    			    rd_data_r[1] <= sda_in;
                    			    scl       <= 1'b1  ;
                    			end
                    			7'd27: scl  <= 1'b0;
                    			7'd29: begin
                    			    rd_data_r[0] <= sda_in;
                    			    scl       <= 1'b1  ;
                    			end
                    			7'd31: scl  <= 1'b0;
                    			7'd32: begin
                    			    sda_en <= 1'b1;              // 非应答
                    			    sda_out <= 1'b1;
                    			end
                    			7'd33: scl     <= 1'b1;
                    			7'd34: st_done <= 1'b1;
                    			7'd35: begin
                    			    scl <= 1'b0;
                    			    cnt <= 1'b0;
                    			    rd_data <= rd_data_r;
                    			end
                    			default  :  ;
							endcase
            			end
            			  else
            			  	begin
            			  		if(sda_in == 1'b0)begin
            			  			state <= STOP;
            			  			scl <= 1'b0;
                    			    cnt <= 1'b0;
                    			    rd_data <= rd_data_r;
                    			end
            			  		else
            			  			state <= DATA_RD;
            			  	end
            	STOP	:
            		begin                            // 结束I2C操作
                		case(cnt)
                		    7'd0: begin
                		    	sda_en <= 1'b1;             
                        		sda_out <= 1'b0;
                		        sda_en <= 1'b1;//主机控制sda总线
                		        sda_out <= 1'b0;
                		    end
                		    7'd1 : scl     <= 1'b1;
                		    7'd3 : sda_out <= 1'b1;//sda信号在scl为高时由低变高产生结束信号
                		    7'd15: st_done <= 1'b1;
                		    7'd16: begin
                		        cnt<= 1'b0;
                		        i2c_done_r <= 1'b1;// 读或写结束                		        
                		    end
                		    default  : ;
                		endcase
                		
                		if(wdone == 1'b1)
                			state <= IDLE;
                		else
                			state <= STOP;
            		end			
   			endcase
		end
endmodule

仿真图;写入的地址为15(0_0000_0000_1111)状态为2的时候sda写入地址高八位准确说是高5位,在状态3sda写入地址低八位00001111,在状态4可以看到sda写入数据为170(1010_1010)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值