FPGA读写操作常用的EEPROM芯片24lc64
速度250k,刚好400个时钟周期,容易计数。
代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/07/25 09:21:01
// Design Name:
// Module Name: iic_test
// Project Name:
// Target Devices:
// Tool Versions:
// Description: eeprom 24lc64
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module iic_test(
Clk,
Rst_n,
En,
Wr_en,
Rd_en,
Addr,
Wdata,
Rdata,
Sclk,
Sdata,
Busy,
Done
);
input Clk ;
input Rst_n;
input En;
input Wr_en;
input Rd_en;
input [12:00] Addr;
input [07:00] Wdata;
output [07:00] Rdata;
output Sclk;
inout Sdata;
output Busy;
output Done;
parameter IDWADDR = 8'h42;
parameter IDRADDR = 8'h43;
parameter SCLK_TIME = 9'd400;
parameter SCLK_HALF_TIME = 9'd200;
parameter SCLK_W_TIME = 9'd100;
parameter SCLK_R_TIME = 9'd300;
reg [07:00] Rdata;
reg work_flag;
reg [08:00] cnt1;
wire add_cnt1;
wire end_cnt1;
reg [05:00] cnt2;
wire add_cnt2;
wire end_cnt2;
reg [01:00] cnt3;
wire add_cnt3;
wire end_cnt3;
reg rd_flag;
reg wr_flag;
reg [12:00] addr;
reg [38:00] wdata;
reg [07:00] wrdata;
reg [05:00] byte_num;
reg [01:00] step_num;
wire rd_0_flag;
wire rd_1_flag;
wire rd_get_flag;
reg Sclk;
wire start_area;
wire stop_area;
wire sclk_h2l;
wire sclk_l2h;
reg sio_out;
reg [07:00] rdata;
wire sio_get;
wire sio_in;
reg sio_out_en;
reg Done;
wire sio_send;
wire Ack;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
work_flag <= 1'b0;
else if(En&&!Busy)
work_flag <= 1'b1;
else if(end_cnt3)
work_flag <= 1'b0;
else
work_flag <= work_flag;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt1 <= 9'd0;
else if(add_cnt1)
if(end_cnt1)
cnt1 <= 9'd0;
else
cnt1 <= cnt1+1'b1;
assign add_cnt1 = work_flag == 1'b1;
assign end_cnt1 = add_cnt1 && cnt1== SCLK_TIME-1;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt2 <= 6'd0 ;
else if(add_cnt2)
if(end_cnt2)
cnt2 <= 6'd0 ;
else
cnt2 <= cnt2 +1'b1 ;
assign add_cnt2 = end_cnt1 ;
assign end_cnt2 = add_cnt2 && cnt2 == byte_num-1'b1 ;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt3 <= 2'd0;
else if(add_cnt3)
if(end_cnt3)
cnt3 <= 2'd0;
else
cnt3 <= cnt3 + 1'b1;
assign add_cnt3 = end_cnt2;
assign end_cnt3 = add_cnt3 && cnt3 == step_num-1;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
rd_flag <= 1'b0 ;
else if(En)
rd_flag <= Rd_en;
else
rd_flag <= rd_flag;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
wr_flag <= 1'b0 ;
else if(En)
wr_flag <= Wr_en;
else
wr_flag <= wr_flag;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
addr <= 13'd0 ;
else if(En)
addr <= Addr ;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
wrdata <= 8'd0 ;
else if(En)
wrdata <= Wdata ;
always@(*)
if(!Rst_n)
begin
wdata <= 39'd0;
byte_num <= 6'd0;
step_num <= 2'd0;
end
else if(wr_flag)
begin
wdata <= {1'b0,8'b1010_0000,1'b1,3'd0,addr[12:8],1'b1,addr[7:0],1'b1,wrdata[7:0],1'b1,1'b0,1'b1};
byte_num <= 6'd39;
step_num <= 1;
end
else if(rd_0_flag)
begin
wdata <= {1'b0,8'b1010_0000,1'b0,3'd0,addr[12:8],1'b0,addr[7:0],1'b0,2'b11,9'd0};
byte_num <= 6'd30;
step_num <= 2;
end
else if(rd_1_flag)
begin
wdata <= {1'b0,8'b10100001,1'b0,8'b0,1'b1,1'b0,1'b1,18'd0};
byte_num <= 6'd21;
step_num <= 2;
end
else
begin
wdata <= 39'd0;
byte_num <= 6'd0;
step_num <= 2'd0;
end
assign rd_0_flag = rd_flag && cnt3 == 0;
assign rd_1_flag = rd_flag && cnt3 == 1;
assign rd_get_flag = rd_1_flag && (cnt2>=10 && cnt2<18);
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Sclk <= 1'b1;
else if(sclk_h2l)
Sclk <= 1'b0;
else if(sclk_l2h)
Sclk <= 1'b1;
assign start_area = work_flag && cnt2 == 1'b0;
assign stop_area = work_flag && cnt2 == byte_num-1;
assign sclk_h2l = work_flag && ((!start_area)&&(!stop_area))&&cnt1==0;
assign sclk_l2h = work_flag && ((!start_area)&&(!stop_area))&&cnt1==SCLK_HALF_TIME-1;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
sio_out <= 1'b1 ;
else if (sio_send)
sio_out <= wdata[38-cnt2];
assign sio_send = work_flag && (cnt1 == SCLK_W_TIME-1'b1) && rd_get_flag == 1'b0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
rdata <= 8'd0;
else if (work_flag && end_cnt3)
rdata <= 8'd0;
else if(sio_get)
rdata <= {rdata[6:0],sio_in};
assign sio_get = work_flag && (cnt1 == SCLK_R_TIME-1'b1) && rd_get_flag == 1'b1;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
sio_out_en <= 1'b0;
else if(Ack)
sio_out_en <= 1'b0;
else if(work_flag && (rd_get_flag == 1'b0))
sio_out_en <=1'b1;
else if(work_flag && (rd_get_flag == 1'b1))
sio_out_en <= 1'b0;
assign Ack = ((rd_0_flag && ((cnt2==9)||(cnt2==18)||(cnt2==27))))||((rd_1_flag && ((cnt2==9)||(cnt2==18))));
reg [05:00] ack_data_reg;
wire ack_get;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
ack_data_reg <=6'd0;
else if(ack_get)
ack_data_reg <= {ack_data_reg[04:00],sio_in} ;
assign ack_get=Ack&& (cnt1 == SCLK_R_TIME-1'b1);
assign Sdata = sio_out_en ? sio_out: 1'bz;
assign sio_in = Sdata;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Rdata <= 8'd0;
else if(rd_1_flag && end_cnt3)
Rdata <= rdata;
assign Busy = work_flag;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Done <= 1'b0;
else if(work_flag && end_cnt3)
Done <= 1'b1;
else
Done <= 1'b0;
endmodule
手册时序要求如下:
设计时序如下:
1处为开始信号,在时钟为高时,数据线由高拉低。
2处为数据传输要求,在时钟拉低的时候数据才可以进行跳变,不能在为高时变化,否则可能会出错。
3处为ACK信号,要求主机释放数据线,所以变为高阻态,这时候采集数据,如果EEPROM相应,则读取值为0.
4处为开始信号,读取模式下,手册要求共有两次开始信号。
5处为停止信号,在时钟为高时将数据线由低拉高,表示结束。
仿真结果如下:
图一:
图二:
图一为写入数据时的时序。
图二为读出数据时的时序,注意图中蓝色为释放数据线后的高阻态,可以读数据。
可以看到仿真结果是与设计时序图一致的。
图三:
板子实验程序如下:
顶层模块由串口模块与iic_test_top模块组成,当串口发送任意字符,接受完成的信号Rx_Done作为iic_test_top的起始信号,将在EEPROM中指定的地址中写入指定的值,并将其读出,将读出的值用来驱动LED灯发光。
module uart_iic(
Clk,
Rst_n,
Rs232_rx,
Sclk,
Sdata,
Led
);
input Clk;
input Rst_n;
input Rs232_rx;
output Sclk;
inout Sdata;
output [03:00] Led;
wire Start;
iic_test_top u1
(
.Clk (Clk),
.Rst_n(Rst_n),
.Sclk (Sclk),
.Sdata(Sdata),
.Start(Start),
.Led (Led)
);
uart_byte_rx u2(
.Clk (Clk),
.Rst_n (Rst_n),
.Rs232_rx (Rs232_rx),
.baud_set (3’d4),
.Data_Byte(),
.Rx_Done (Start)
);
可以看到LED0灭掉,与预期结果一致。
工程如下,需要的可以下载指教讨论: