天天看點

IIC總線協定 與 EEPROM 的使用

需要注意以下幾點:

1、wire類型不能設定初值

2、當在module後的括号中寫了output之後 對應的變量名不能再定義為reg

3、使用Modelsim隻能檢查輸出信号是否正确 不能仿真接收信号是否正确

4、注意 inout 類型的 port 的用法

inout  SDA;  //輸出或輸入資料信号
reg rSDA = 1'b1;
assign SDA = isOut ? rSDA : 1'bz; //輸出時選擇rSDA,輸入時設為高阻态 
           

在輸入狀态下 應當使用SDA為其他變量指派 不應使用rSDA

5、注意 在下面程式中 在 IIC 讀數協定據中 第二次寫裝置位址最後一位是 1

二級檔案

`timescale 1 ns/ 1 ps

//本module實作IIC總線的讀與寫的功能,讀寫都是一次讀寫,一次8bit資料
module IIC_test(
	clk,
	rst_n,
	read_or_write,
	Addr,  
	WrData,
	RdData,
	isDone,
	SCL,
	SDA
);

input clk;    //50MHz時鐘信号
input rst_n;  //複位信号,0電平使能

input  [1:0] read_or_write; //選擇讀還是寫,01表示寫,10表示讀
input  [7:0] Addr;          //讀或者寫的位址
input  [7:0] WrData;        //需要寫入的資料
output [7:0] RdData;        //讀出的資料
output isDone;              //讀或者寫操作是否完成,1表示完成

output SCL;  //輸出時鐘信号
inout  SDA;  //輸出或輸入資料信号

parameter freq_div = 9'd500; //時鐘分頻系數,500MHz經分頻得到100KHz

reg [4:0] state = 5'd0; //表明目前的狀态
reg [4:0] Go = 5'd0;    //表明跳轉到哪個狀态
reg [8:0] cnt = 9'd0;   //分頻計數
reg [7:0] Data = 8'd0;  //目前操作的資料

reg rSCL = 1'b1; 
reg rSDA = 1'b1;  

reg isAck = 1'b1;   //應答信号,0表示接收正常
reg isDone = 1'b0;  //1表示操作完畢
reg isOut = 1'b1;   //1表示SDA為輸出狀态,0表示SDA為輸入狀态

assign RdData = Data;
assign SCL = rSCL;
assign SDA = isOut ? rSDA : 1'bz; //輸出時選擇rSDA,輸入時設為高阻态 

always @(posedge clk or negedge rst_n)
begin
	if(~rst_n)  //複位
	begin
		state <= 5'd0;
		Go <= 5'd0;  
		cnt <= 9'd0; 
		Data <= 8'd0;
		rSCL <= 1'b1;
		rSDA <= 1'b1;
		isAck <= 1'b1; 
		isDone <= 1'b0;
		isOut <= 1'b1; 
	end
	else if(read_or_write[0])  //IIC寫資料
	begin
		case(state)
		
		5'd0: //start開始狀态
		begin
			isOut <= 1'b1;//設定SDA為輸出狀态
			
			if(cnt == 9'd0) rSDA <= 1'b1; //SDA先由高變低
			else if(cnt == 9'd200) rSDA <= 1'b0;
			else rSDA <= rSDA;
			
			if(cnt == 9'd0) rSCL <= 1'b1; //SCL再由高變低
			else if(cnt == 9'd400) rSCL <= 1'b0;
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd1: //擷取裝置的位址,在開發闆上EEPROM位址為0xA0
		begin
			Data <= {4'b1010, 3'b000, 1'b0}; //裝置位址為A0
			state <= 5'd7;       //跳轉到狀态7
			Go <= state + 5'd1;  //在寫入裝置位址之後再跳轉到狀态2
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;		
		end
		
		5'd2: //擷取資料的位址
		begin
			Data <= Addr;       //擷取資料的位址
			state <= 5'd7;      //跳轉到狀态7
			Go <= state + 5'd1; //在寫入資料位址之後再跳轉到狀态3
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;			
		end
		
		5'd3: //擷取需要寫入的資料
		begin
			Data <= WrData;     //擷取需要寫入的資料
			state <= 5'd7;      //跳轉到狀态7
			Go <= state + 5'd1; //在寫入資料位址之後再跳轉到狀态3
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd4: //stop停止狀态
		begin
			isOut <= 1'b1;//設定SDA為輸出狀态
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL先由低變高
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else rSCL <= rSCL;
			
			if(cnt == 9'd0) rSDA <= 1'b0; //SDA再由低變高
			else if(cnt == 9'd300) rSDA <= 1'b1;
			else rSDA <= rSDA;			
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd5: //狀态5和6是發送一個操作完成信号,1表示操作完成
		begin
			isDone <= 1'b1;
			state <= state + 5'd1; //跳轉到下一個狀态]
			
			Go <= Go;  
			cnt <= cnt; 
			Data <= Data;
			rSCL <= rSCL;
			rSDA <= rSDA;
			isAck <= isAck; 
			isOut <= isOut;
		end
		
		5'd6: //狀态5和6是發送一個操作完成信号
		begin
			isDone <= 1'b0;
			state <= 5'd0; //回到初始狀态
			
			Go <= Go;  
			cnt <= cnt; 
			Data <= Data;
			rSCL <= rSCL;
			rSDA <= rSDA;
			isAck <= isAck; 
			isOut <= isOut;
		end
		
		5'd7, 5'd8, 5'd9, 5'd10, 5'd11, 5'd12, 5'd13, 5'd14: 
		//狀态7到狀态14發送資料,從高位開始發送,每個狀态發送1bit
		begin
			isOut <= 1'b1;
			
			rSDA <= Data[5'd14-state]; //從高位開始發送
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL由低變高再由高變低
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else if(cnt == 9'd300) rSCL <= 1'b0; 
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end		
		
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd15: //等待接收方傳回應答ACK信号
		begin
			isOut <= 1'b0; //将SDA設為輸入模式
			
			if(cnt == 9'd200) isAck <= SDA; //讀取傳回的應答ACK信号,特别注意這裡是SDA!!!
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL由低變高再由高變低
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else if(cnt == 9'd300) rSCL <= 1'b0;
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data; 
			isDone <= isDone;
		end
		
		5'd16: //處理應答信号
		begin
			if(isAck != 1'b0)
				state <= 5'd0; //未收到應答,跳轉到初始狀态
			else 
				state <= Go;   //收到應答,則跳轉到其他相應的狀态
			
			cnt <= cnt; 
			Go <= Go;
			Data <= Data;
			rSCL <= rSCL;
			rSDA <= rSDA;
			isDone <= isDone;
			isAck <= isAck; 
			isOut <= isOut;
		end		
		
		endcase
	end
	else if(read_or_write[1])  //IIC讀資料
	begin
		case(state)
		
		5'd0: //start起始狀态
		begin
			isOut <= 1'b1;//設定SDA為輸出狀态
			
			if(cnt == 9'd0) rSDA <= 1'b1; //SDA先由高變低
			else if(cnt == 9'd200) rSDA <= 1'b0;
			else rSDA <= rSDA;
			
			if(cnt == 9'd0) rSCL <= 1'b1; //SCL再由高變低
			else if(cnt == 9'd400) rSCL <= 1'b0;
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd1: //擷取裝置的位址,在開發闆上EEPROM位址為0xA0
		begin
			Data <= {4'b1010, 3'b000, 1'b0}; //裝置位址為A0
			state <= 5'd9;       //跳轉到狀态9
			Go <= state + 5'd1;  //在寫入裝置位址之後再跳轉到狀态2
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;		
		end
		
		5'd2: //擷取資料的位址
		begin
			Data <= Addr;       //擷取資料的位址
			state <= 5'd9;      //跳轉到狀态9
			Go <= state + 5'd1; //在寫入資料位址之後再跳轉到狀态3
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;			
		end
		
		5'd3: //再一次start起始狀态
		begin
			isOut <= 1'b1;//設定SDA為輸出狀态
			
			if(cnt == 9'd0) rSDA <= 1'b1; //SDA先由高變低
			else if(cnt == 9'd200) rSDA <= 1'b0;
			else rSDA <= rSDA;
			
			if(cnt == 9'd0) rSCL <= 1'b1; //SCL再由高變低
			else if(cnt == 9'd400) rSCL <= 1'b0;
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd4: //再一次擷取裝置位址,在開發闆上EEPROM位址為0xA0
		begin
			Data <= {4'b1010, 3'b000, 1'b1}; //裝置位址為A1,特别注意這裡最後一位是1 !!!!!!!!
			state <= 5'd9;       //跳轉到狀态9
			Go <= state + 5'd1;  //在寫入裝置位址之後再跳轉到狀态2
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;		
		end
		
		5'd5: //準備讀取資料
		begin
			Data <= 8'd0;       //清空Data
			state <= 5'd19;     //跳轉到狀态19
			Go <= state + 5'd1; //讀資料之後再跳轉到狀态6
			
			rSCL <= rSCL;
			rSDA <= rSDA;
			cnt <= cnt;
			isOut <= isOut;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd6: //stop停止狀态
		begin
			isOut <= 1'b1;//設定SDA為輸出狀态
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL先由低變高
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else rSCL <= rSCL;
			
			if(cnt == 9'd0) rSDA <= 1'b0; //SDA再由低變高
			else if(cnt == 9'd300) rSDA <= 1'b1;
			else rSDA <= rSDA;			
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd7: //狀态7和8是發送一個操作完成信号,1表示操作完成
		begin
			isDone <= 1'b1;
			state <= state + 5'd1; //跳轉到下一個狀态]
			
			Go <= Go;  
			cnt <= cnt; 
			Data <= Data;
			rSCL <= rSCL;
			rSDA <= rSDA;
			isAck <= isAck; 
			isOut <= isOut;
		end
		
		5'd8: //狀态5和6是發送一個操作完成信号
		begin
			isDone <= 1'b0;
			state <= 5'd0; //回到初始狀态
			
			Go <= Go;  
			cnt <= cnt; 
			Data <= Data;
			rSCL <= rSCL;
			rSDA <= rSDA;
			isAck <= isAck; 
			isOut <= isOut;
		end
		
		5'd9, 5'd10, 5'd11, 5'd12, 5'd13, 5'd14, 5'd15, 5'd16:
		//狀态9到狀态16據,從高位開始發送,每個狀态發送1bit
		begin
			isOut <= 1'b1;
			
			rSDA <= Data[5'd16-state]; //從高位開始發送
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL由低變高再由高變低
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else if(cnt == 9'd300) rSCL <= 1'b0; 
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end		
		
			Go <= Go;
			Data <= Data;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd17: //等待接收方傳回應答ACK信号
		begin
			isOut <= 1'b0; //将SDA設為輸入模式
			
			if(cnt == 9'd200) isAck <= SDA; //讀取傳回的應答ACK信号,特别注意這裡是SDA!!!
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL由低變高再由高變低
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else if(cnt == 9'd300) rSCL <= 1'b0;
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data; 
			isDone <= isDone;
		end
		
		5'd18: //處理應答信号
		begin
			if(isAck != 1'b0)
				state <= 5'd0; //未收到應答,跳轉到初始狀态
			else 
				state <= Go;   //收到應答,則跳轉到其他相應的狀态
			
			cnt <= cnt; 
			Go <= Go;
			Data <= Data;
			rSCL <= rSCL;
			rSDA <= rSDA;
			isDone <= isDone;
			isAck <= 1'b1; 
			isOut <= isOut;
		end
		
		5'd19, 5'd20, 5'd21, 5'd22, 5'd23, 5'd24, 5'd25, 5'd26:
		//接收資料,發送端從高位開始發送,主要器開始讀取資料
		begin
			isOut <= 1'b0;
			
			if(cnt == 9'd200) Data[5'd26-state] <= SDA; //從高位開始發送,特别注意這裡是SDA!!!
			else Data <= Data;
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL由低變高再由高變低
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else if(cnt == 9'd300) rSCL <= 1'b0; 
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= state + 5'd1; //跳轉到下一個狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end		
		
			Go <= Go;
			isAck <= isAck; 
			isDone <= isDone;
		end
		
		5'd27: //主要器在接收完畢後發送一個NACK信号
		begin
			isOut <= 1'b1; //将SDA設為輸出模式
			
			rSDA <= 1'b1; //SDA輸出高電平
			
			if(cnt == 9'd0) rSCL <= 1'b0; //SCL由低變高再由高變低
			else if(cnt == 9'd100) rSCL <= 1'b1;
			else if(cnt == 9'd300) rSCL <= 1'b0;
			else rSCL <= rSCL;
			
			if(cnt == freq_div - 9'd1)
			begin
				cnt <= 9'd0;
				state <= Go; //跳轉到stop狀态
			end
			else
			begin
				cnt <= cnt + 9'd1;
				state <= state;
			end
			
			Go <= Go;
			Data <= Data; 
			isDone <= isDone;
		end
		
		endcase
	end
	else
	begin
		state <= 5'd0;
		Go <= 5'd0;  
		cnt <= 9'd0; 
		Data <= 8'd0;
		rSCL <= 1'b1;
		rSDA <= 1'b1;
		isAck <= 1'b1; 
		isDone <= 1'b0;
		isOut <= 1'b1; 
	end
end

endmodule
           

一級檔案:

`timescale 1 ns/ 1 ps

//基于IIC總線的EEPROM讀寫程式
module EEPROM_test(
	clk,     //50MHz時鐘信号
	rst_n,   //複位信号,0電平使能
	led,     //LED燈訓示信号
	SCL,     //IIC時鐘線
	SDA      //IIC資料線
);

input  clk, rst_n;
output [3:0] led;
output SCL;
inout  SDA;

reg [31:0] cnt = 32'd0;
reg  [7:0] state = 8'b0;
reg  [3:0] led = 4'd0;

reg  [7:0] w_Data = 8'd0;         //需要寫入EEPROM的資料
reg  [1:0] read_or_write = 2'b00; //控制讀還是寫,01為寫,10為讀,00為無操作,不可置為11
reg  [7:0] Addr = 8'd0;           //讀或寫操作的資料位址

wire isDone;        //操作完成訓示信号,1表示操作完成
wire [7:0] r_Data;  //讀到到資料

always @(posedge clk or negedge rst_n)
begin
	if(~rst_n)
	begin
		cnt <= 32'd0;
		state <= 8'd0;
	end
	else 
	begin
		if(cnt >= 32'd0 && cnt < 32'd49999999)
		begin
			cnt <= cnt + 32'd1;
			state <= state;
		end
		else if(cnt == 32'd49999999)
		begin
			cnt <= 32'd0;
			if(state == 8'd31) state <= 8'd16;
			else state <= state + 8'd1;
		end
		else
		begin
			cnt <= 32'd0;
			state <= 8'd0;
		end
	end
end

always @(posedge clk or negedge rst_n)
begin
	if(~rst_n)
	begin
		led <= 4'd0;
		w_Data <= 8'd0;
		read_or_write <= 2'b00;
		Addr <= 8'd0;
	end
	else
	begin
		case(state)
		
		8'd0,8'd1,8'd2,8'd3,8'd4,8'd5,8'd6,8'd7,8'd8,8'd9,8'd10,
		8'd11,8'd12,8'd13,8'd14,8'd15:
		begin
			if(isDone == 1'b1) 
			begin
				read_or_write <= 2'b00;
				Addr <= Addr;
				w_Data <= w_Data;
				led <= led;
			end
			else
			begin
				read_or_write <= 2'b01;  //寫入資料
				Addr <= state;
				w_Data <= state;
				led <= led;
			end
		end
		
		8'd16,8'd17,8'd18,8'd19,8'd20,8'd21,8'd22,8'd23,8'd24,8'd25,8'd26,
		8'd27,8'd28,8'd29,8'd30,8'd31:
		begin
			if(isDone == 1'b1) 
			begin
				read_or_write <= 2'b00;
				Addr <= Addr;
				w_Data <= w_Data;
				led <= r_Data[3:0];
			end
			else
			begin
				read_or_write <= 2'b10; //讀出資料
				Addr <= state - 8'd16;
				w_Data <= w_Data;
				led <= led;
			end
		end
		
		default:
		begin
			led <= 4'd0;
			w_Data <= 8'd0;
			read_or_write <= 2'b00;
			Addr <= 8'd0;
		end
		
		endcase
	end
end

IIC_test #(
	.freq_div        (9'd500)
)
IIC_test_inst(
	.clk             (clk),
	.rst_n           (rst_n),
	.read_or_write   (read_or_write),
	.Addr            (Addr),  
	.WrData          (w_Data),
	.RdData          (r_Data),
	.isDone          (isDone),
	.SCL             (SCL),
	.SDA             (SDA)
);

endmodule