IIC模块Verilog实现–用IIC协议从FPGA端读取E2PROM
下面是 design 设计
I2C_dri.v
module IIC_CONTROL #(
parameter SLAVE_ADDR = 7'b1010000 , // E2PROM 从机地址
parameter CLK_FREQ = 26'd50_000_000 , // 50MHz 的时钟频率
parameter I2C_FREQ = 18'd250_000 // SCL 的时钟频率
)
(
input clk ,
input rst_n ,
// ---------------------------------------------- //
input [15 : 0] i2c_addr , // 地址
input [7 : 0] i2c_data_w , // 数据
input i2c_rh_wl , // 判断 是 read or write
input bit_control , // 1是 16位 0 是 8位
input i2c_exec ,
// ------------------------------------------------ //
output reg dri_clk ,
output reg [7 : 0] i2c_data_r ,
output reg i2c_ack ,
output reg i2c_done ,
// -------------------------------------------------- //
output reg scl ,
inout sda
);
// --------------------------------------------------------//
// next is define //
// --------------------------------------------------------//
reg [9 : 0] clk_cnt ;
wire [8 : 0] dri_cnt ;
reg [2 : 0] state ;
reg [2 : 0] next_state ;
reg st_done ; // 在 状态机里面用来提示数据完成可以跳转
reg sda_dir ; // sda方向控制器
reg sda_out ; // 选择FPGA输入模式之后赋予sda线上
wire sda_in ; // sda输入信号
reg [6 : 0] cnt ; // 我们为了第三部分状态机而准备的
reg [15: 0] addr_save ; // 地址存储
reg [7 : 0] data_w_save ; // 数据写的暂存
reg wr_flag ; // 0 是 写 1 是 读
// 这三个是 暂存的方便调度的
reg [7 : 0] data_r_save ; // 读到的数据存储方便整合
// --------------------------------------------------------- //
// parameter define //
parameter st_idle = 3'b000 ; // 空闲状态
parameter st_sladdr = 3'b001 ; // 发送器件地址
parameter st_addr16 = 3'b010 ; // 发送高八位地址
parameter st_addr8 = 3'b011 ; // 发送低八位地址
parameter st_data_wr = 3'b100 ; // 写数据
parameter st_addr_rd = 3'b101 ; // 再次发送器件地址读
parameter st_data_rd = 3'b110 ; // 读数据
parameter st_stop = 3'b111 ; // 结束操作停止位
// ---------------------------------------------------- //
// next is main code //
// -------------------------------------------------------//
assign dri_cnt = (CLK_FREQ/I2C_FREQ ) >> 2 ;
always@(posedge clk or negedge rst_n )
begin
if(rst_n == 0)
begin
dri_clk <= 0 ;
clk_cnt <= 0 ;
end
else if( clk_cnt == dri_cnt[8:1] - 1)
begin
clk_cnt <= 0 ;
dri_clk <= ~dri_clk ;
end
else
begin
dri_clk <= dri_clk ;
clk_cnt <= clk_cnt + 1 ;
end
end
// 下面开始状态机的叙述
// 同步时序描述状态转移
always@(posedge dri_clk or negedge rst_n)
begin
if(rst_n == 0)
begin
state <= st_idle ;
end // 处于空闲状态
else
begin
state <= next_state ;
end
end
// 组合逻辑判断状态转移条件
always@(*)
begin
next_state <= st_idle ;
case(state)
st_idle :
begin
if(i2c_exec == 1)
begin
next_state <= st_sladdr ;
end
else
begin
next_state <= st_idle ;
end
end
// 当触发了i2c_exec 时候 可以由 空闲状态转移到
st_sladdr :
begin
if(st_done == 1)
begin
if(bit_control == 1)
next_state <= st_addr16 ;
else
next_state <= st_addr8 ;
end
else
begin
next_state <= st_sladdr ;
end
end
// 当 触发了 st_done 之后 通过 bit_control 选择是低八位 还是高八位的传输
st_addr16 :
begin
if(st_done == 1)
begin
next_state <= st_addr8 ;
end
else
begin
next_state <= st_addr16 ;
end
end
// 高位 用完 轮到 低位的 传输
st_addr8 :
begin
if(st_done == 1)
begin
if(wr_flag == 0)
next_state <= st_data_wr ;
else
next_state <= st_addr_rd ;
end
else
begin
next_state <= st_addr8 ;
end
end
// 先来判断 写数据的 st_data_wr 数据代号是 4
st_data_wr :
begin
if(st_done == 1)
begin
next_state <= st_stop ;
end
else
begin
next_state <= st_data_wr ;
end
end
//
st_addr_rd :
begin
if(st_done == 1)
begin
next_state <= st_data_rd ;
end
else
begin
next_state <= st_addr_rd ;
end
end
//
st_data_rd :
begin
if(st_done == 1)
begin
next_state <= st_stop ;
end
else
begin
next_state <= st_data_rd ;
end
end
//
st_stop :
begin
if(st_done == 1)
begin
next_state <= st_idle ;
end
else
begin
next_state <= st_stop ;
end
end
default:
next_state <= st_idle ;
endcase
end
/ 下面来考虑另一个状态机的第三部分 --- 时序电路描述状态输出
// 设置一个变量 来控制 SDA的朝向
assign sda = sda_dir ? sda_out : 1'bz ; // sda_dir 为1 FPGA控制
assign sda_in = sda ; // 把sda当成了输出
always@(posedge dri_clk or negedge rst_n )
begin
if( rst_n == 0)
begin
//首先根据输入输出 来判断 SCL 与 SDA 必须都为高
scl <= 1 ;
sda_dir <= 1 ;
sda_out <= 1 ;
// 剩下的输出 i2c_data_r(输出) == data_r_save
i2c_data_r <= 0 ;
data_r_save <= 0 ;
// 下面是端口的另外两个输出 i2c_ack 和 i2c_done
i2c_ack <= 0 ;
i2c_done <= 0 ;
// 接下里是 内部信号的调节 这两个一个是内部后续的计数 还有一个本次case完成的结束信号
cnt <= 0 ;
st_done <= 0 ;
// 下面是三个暂存信号一个是 读写标志位 还有 传入的地址暂存 传入的数据暂存
wr_flag <= 0 ;
addr_save <= 0 ;
data_w_save <= 0 ;
end
else
begin
st_done <= 0 ; // 脉冲信号
cnt <= cnt + 1 ;
//这里写在了 case之前就代表了 不用刻意在内部去调配 st_done 或是cnt
case(state)
st_idle :
begin
scl <= 1 ;
sda_dir <= 1 ;
sda_out <= 1 ;
//这两个写不写不所谓 因为根本没用到
i2c_data_r <= 0 ;
data_r_save <= 0 ;
i2c_done <= 0 ;
//
cnt <= 0 ;
st_done <= 0 ;
// 开始
if( i2c_exec == 1)
begin
wr_flag <= i2c_rh_wl ;
addr_save <= i2c_addr ;
data_w_save <= i2c_data_w ;
i2c_ack <= 0 ;
end
end
// 这里先传递的是
st_sladdr :
begin
case(cnt)
7'd1 :
sda_out <= 0 ;
7'd3 :
scl <= 0 ;
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 ;
// 此处完成了 数据的传递 接下来的任务是 反馈
7'd33 :
scl <= 1'b1 ;
7'd35 :
scl <= 1'b0 ;
7'd36 :
sda_dir <= 1'b0 ; // 下放控制权给从机端口
7'd37 :
scl <= 1'b1 ;
// 下一时刻判断是否 有正确的反馈拉低 并确定 st_done = 1
7'd38 :
begin
st_done <= 1'b1 ;
if( sda_in == 1)
i2c_ack <= 1'b1 ;
end
7'd39 :
begin
scl <= 1'b0 ;
cnt <= 7'b0 ;
end
default :
;
endcase
end
//发送高8位字节
st_addr16 :
begin
case(cnt)
7'd0 :
begin // 39之后移动一格就是0 0 此处即可以开始
//把使能交还给FPGA端
sda_dir <= 1'b1 ;
sda_out <= addr_save[15] ;
end
// 第一个转换有点时序差距 后面都是 每隔4 sda变化一次
7'd1 :
scl <= 1'b1 ;
7'd3 :
scl <= 1'b0 ;
7'd4 :
sda_out <= addr_save[14] ;
7'd5 :
scl <= 1'b1 ;
7'd7 :
scl <= 1'b0 ;
7'd8 :
sda_out <= addr_save[13] ;
7'd9 :
scl <= 1'b1 ;
7'd11 :
scl <= 1'b0 ;
7'd12 :
sda_out <= addr_save[12] ;
7'd13 :
scl <= 1'b1 ;
7'd15 :
scl <= 1'b0 ;
7'd16 :
sda_out <= addr_save[11] ;
7'd17 :
scl <= 1'b1 ;
7'd19 :
scl <= 1'b0 ;
7'd20 :
sda_out <= addr_save[10] ;
7'd21 :
scl <= 1'b1 ;
7'd23 :
scl <= 1'b0 ;
7'd24 :
sda_out <= addr_save[9] ;
7'd25 :
scl <= 1'b1 ;
7'd27 :
scl <= 1'b0 ;
7'd28 :
sda_out <= addr_save[8] ;
// 29 拉升 31下降 32放控制权 33拉升 34结束并作判断 35 拉低 cnt归零为下一状态准备
7'd29 :
scl <= 1'b1 ;
7'd31 :
scl <= 1'b0 ;
7'd32 :
sda_dir <= 1'b0 ;
7'd33 :
scl <= 1'b1 ;
7'd34 :
begin
st_done <= 1'b1 ; //完成
if(sda_in == 1)
i2c_ack <= 1'b1 ; // scl拉高时 反馈 i2c_ack = 1 表示有错误
end
7'd35 :
begin
scl <= 1'b0 ;
cnt <= 7'b0 ;
end
default :
;
endcase
end
//发送低8位字节
st_addr8 :
begin
// 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值
case(cnt)
7'd0:
begin
sda_dir <= 1'b1 ;
sda_out <= addr_save[7]; //字地址
end
7'd1 :
scl <= 1'b1;
7'd3 :
scl <= 1'b0;
7'd4 :
sda_out <= addr_save[6];
7'd5 :
scl <= 1'b1;
7'd7 :
scl <= 1'b0;
7'd8 :
sda_out <= addr_save[5];
7'd9 :
scl <= 1'b1;
7'd11 :
scl <= 1'b0;
7'd12 :
sda_out <= addr_save[4];
7'd13 :
scl <= 1'b1;
7'd15 :
scl <= 1'b0;
7'd16 :
sda_out <= addr_save[3];
7'd17 :
scl <= 1'b1;
7'd19 :
scl <= 1'b0;
7'd20 :
sda_out <= addr_save[2];
7'd21 :
scl <= 1'b1;
7'd23 :
scl <= 1'b0;
7'd24 :
sda_out <= addr_save[1];
7'd25 :
scl <= 1'b1;
7'd27 :
scl <= 1'b0;
7'd28 :
sda_out <= addr_save[0];
7'd29 :
scl <= 1'b1 ;
7'd31 :
scl <= 1'b0 ;
7'd32 :
sda_dir <= 1'b0 ;
7'd33 :
scl <= 1'b1 ;
7'd34 :
begin
st_done <= 1'b1 ; //完成
if(sda_in == 1)
i2c_ack <= 1'b1 ; // scl拉高时 反馈 i2c_ack = 1 表示有错误
end
7'd35 :
begin
scl <= 1'b0 ;
cnt <= 7'b0 ;
end
default :
;
endcase
end
//
st_data_wr :
begin
// 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值
case(cnt)
7'd0:
begin
sda_dir <= 1'b1 ;
sda_out <= data_w_save[7]; //字地址
end
7'd1 :
scl <= 1'b1;
7'd3 :
scl <= 1'b0;
7'd4 :
sda_out <= data_w_save[6];
7'd5 :
scl <= 1'b1;
7'd7 :
scl <= 1'b0;
7'd8 :
sda_out <= data_w_save[5];
7'd9 :
scl <= 1'b1;
7'd11 :
scl <= 1'b0;
7'd12 :
sda_out <= data_w_save[4];
7'd13 :
scl <= 1'b1;
7'd15 :
scl <= 1'b0;
7'd16 :
sda_out <= data_w_save[3];
7'd17 :
scl <= 1'b1;
7'd19 :
scl <= 1'b0;
7'd20 :
sda_out <= data_w_save[2];
7'd21 :
scl <= 1'b1;
7'd23 :
scl <= 1'b0;
7'd24 :
sda_out <= data_w_save[1];
7'd25 :
scl <= 1'b1;
7'd27 :
scl <= 1'b0;
7'd28 :
sda_out <= data_w_save[0];
// 29 拉升 31下降 32放控制权 33拉升 34结束并作判断 35 拉低 cnt归零为下一状态准备
7'd29 :
scl <= 1'b1 ;
7'd31 :
scl <= 1'b0 ;
7'd32 :
sda_dir <= 1'b0 ;
7'd33 :
scl <= 1'b1 ;
7'd34 :
begin
st_done <= 1'b1 ; //完成
if(sda_in == 1)
i2c_ack <= 1'b1 ; // scl拉高时 反馈 i2c_ack = 1 表示有错误
end
7'd35 :
begin
scl <= 1'b0 ;
cnt <= 7'b0 ;
end
default :
;
endcase
end
// 读控制信号 可以开始读了
st_addr_rd :
begin
// 这里的过程应该和上面的那个 st_sladdr一样 先写地址
// 一样又不太一样
case(cnt)
7'd0 :
begin
sda_dir <= 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_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37:
scl <= 1'b1;
7'd38:
begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39:
begin
scl <= 1'b0;
cnt <= 7'b0;
end
default :
;
endcase
end
st_data_rd :
begin //读取数据(8 bit)
case(cnt)
7'd0:
sda_dir <= 1'b0;
7'd1:
scl <= 1'b1;
7'd2 :
data_r_save[7] <= sda_in;
7'd3:
scl <= 1'b0;
7'd5:
scl <= 1'b1 ;
7'd6:
data_r_save[6] <= sda_in ;
7'd7:
scl <= 1'b0;
7'd9:
scl <= 1'b1 ;
7'd10 :
data_r_save[5] <= sda_in;
7'd11:
scl <= 1'b0;
7'd13:
scl <= 1'b1 ;
7'd14:
data_r_save[4] <= sda_in;
7'd15:
scl <= 1'b0;
7'd17:
scl <= 1'b1 ;
7'd18:
data_r_save[3] <= sda_in;
7'd19:
scl <= 1'b0;
7'd21:
scl <= 1'b1 ;
7'd22:
data_r_save[2] <= sda_in;
7'd23:
scl <= 1'b0;
7'd25:
scl <= 1'b1 ;
7'd26:
data_r_save[1] <= sda_in;
7'd27:
scl <= 1'b0;
7'd29:
scl <= 1'b1 ;
7'd30:
data_r_save[0] <= sda_in;
7'd31:
scl <= 1'b0;
7'd32:
begin
sda_dir <= 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 <= 7'b0;
i2c_data_r <= data_r_save;
end
default :
;
endcase
end
st_stop:
begin //结束I2C操作
case(cnt)
7'd0:
begin
sda_dir <= 1'b1; //结束I2C
sda_out <= 1'b0;
end
7'd1 :
scl <= 1'b1;
7'd3 :
sda_out <= 1'b1;
7'd15:
st_done <= 1'b1;
7'd16:
begin
cnt <= 7'b0;
i2c_done <= 1'b1; //向上层模块传递I2C结束信号
end
default :
;
endcase
end
endcase
end
end
endmodule
E2PROM.v
module E2PROM #(
// //EEPROM写数据需要添加间隔时间,读数据则不需要
parameter WR_WAIT_TIME = 14'd5000, //写入间隔时间
parameter MAX_BYTE = 16'd256 //读写测试的字节个数
) (
input clk ,
input rst_n ,
// from I2C-control
input [7 : 0] i2c_data_r , // 读出来的数据
input i2c_ack , // 应答
input i2c_done , // i2c完成信号
// give to i2c
output reg i2c_exec ,
output reg i2c_rh_wl ,
output reg [15 : 0] i2c_addr ,
output reg [7 : 0] i2c_data_w ,
// gvie it to led
output reg rw_done , // e2prom 读写测试完成
output reg rw_result // e2prom 的结果 0 : 失败 1 :成功
);
// reg define
reg [1:0] flow_cnt ; //状态流控制
reg [13:0] wait_cnt ; //延时计数器
always@(posedge clk or negedge rst_n) begin
if(rst_n == 0) begin
i2c_exec <= 0 ;
i2c_rh_wl <= 0 ;
i2c_addr <= 0 ;
i2c_data_w <= 0 ;
rw_done <= 0 ;
rw_result <= 0 ;
flow_cnt <= 0 ;
wait_cnt <= 0 ; end
else begin
i2c_exec <= 0 ; // 把 i2c_ecec 看成是一个脉冲信号
case(flow_cnt)
2'd0 : begin
if(wait_cnt == (WR_WAIT_TIME - 1) ) begin
wait_cnt <= 0 ;
if(i2c_addr == MAX_BYTE) begin // 表示256个数据写入其中
i2c_addr <= 16'b0;
i2c_rh_wl <= 1'b1;
flow_cnt <= 2'd2; end
else begin
flow_cnt <= flow_cnt + 2'b1;
i2c_exec <= 1'b1;
end
end
else begin
wait_cnt <= wait_cnt + 1 ; end
end
2'd1 : begin
if(i2c_done == 1'b1) begin //EEPROM单次写入完成
flow_cnt <= 2'd0;
i2c_addr <= i2c_addr + 16'b1; //地址0~255分别写入
i2c_data_w <= i2c_data_w + 8'b1; //数据0~255
end
else begin
flow_cnt <= flow_cnt ;
i2c_addr <= i2c_addr ;
i2c_data_w <= i2c_data_w ; end
end
2'd2 : begin
flow_cnt <= flow_cnt + 2'b1;
i2c_exec <= 1'b1; end
2'd3 : begin
if(i2c_done == 1'b1) begin //EEPROM单次读出完成
//读出的值错误或者I2C未应答,读写测试失败
if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
rw_done <= 1'b1;
rw_result <= 1'b0;
end
else if(i2c_addr == (MAX_BYTE - 16'b1))begin //读写测试成功
rw_done <= 1'b1;
rw_result <= 1'b1;
end
else begin
flow_cnt <= 2'd2;
i2c_addr <= i2c_addr + 16'b1;
end
end
end
default : ;
endcase
end
end
endmodule
led.v
module LED
#(parameter L_TIME = 17'd125_000
)
(
input clk , //时钟信号
input rst_n , //复位信号
input rw_done , //错误标志
input rw_result , //E2PROM读写测试完成
output reg led //E2PROM读写测试结果 0:失败 1:成功
);
//reg define
reg rw_done_flag; //读写测试完成标志
reg [16:0] led_cnt ; //led计数
//*****************************************************
//** main code
//*****************************************************
//读写测试完成标志
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
rw_done_flag <= 1'b0;
else if(rw_done)
rw_done_flag <= 1'b1;
end
//错误标志为1时PL_LED0闪烁,否则PL_LED0常亮
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
led_cnt <= 17'd0;
led <= 1'b0;
end
else begin
if(rw_done_flag) begin
if(rw_result) //读写测试正确
led <= 1'b1; //led灯常亮
else begin //读写测试错误
led_cnt <= led_cnt + 17'd1;
if(led_cnt == (L_TIME - 17'b1)) begin
led_cnt <= 17'd0;
led <= ~led; //led灯闪烁
end
else
led <= led;
end
end
else
led <= 1'b0; //读写测试完成之前,led灯熄灭
end
end
endmodule
top.v
module IIC_top #(
parameter SLAVE_ADDR = 7'b1010000 , //器件地址(SLAVE_ADDR)
parameter BIT_CTRL = 1'b1 , //字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000 , //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 , //I2C的SCL时钟频率
parameter L_TIME = 17'd125_000 , //led闪烁时间参数
parameter MAX_BYTE = 16'd256 //读写测试的字节个数
)(
input sys_clk ,
input sys_rst_n ,
// i2c interface
output i2c_scl ,
inout i2c_sda ,
// led
output led
);
wire dri_clk ; //I2C操作时钟
wire i2c_exec ; //I2C触发控制
wire [15:0] i2c_addr ; //I2C操作地址
wire [ 7:0] i2c_data_w; //I2C写入的数据
wire i2c_done ; //I2C操作结束标志
wire i2c_ack ; //I2C应答标志 0:应答 1:未应答
wire i2c_rh_wl ; //I2C读写控制
wire [ 7:0] i2c_data_r; //I2C读出的数据
wire rw_done ; //E2PROM读写测试完成
wire rw_result ; //E2PROM读写测试结果 0:失败 1:成功
E2PROM#(
.WR_WAIT_TIME ( 14'd5000 ),
.MAX_BYTE ( MAX_BYTE )
)u_E2PROM(
.clk ( dri_clk ),
.rst_n ( sys_rst_n ),
.i2c_data_r ( i2c_data_r ),
.i2c_ack ( i2c_ack ),
.i2c_done ( i2c_done ),
.i2c_exec ( i2c_exec ),
.i2c_rh_wl ( i2c_rh_wl ),
.i2c_addr ( i2c_addr ),
.i2c_data_w ( i2c_data_w ),
.rw_done ( rw_done ),
.rw_result ( rw_result )
);
IIC_CONTROL#(
.SLAVE_ADDR ( SLAVE_ADDR ),
.CLK_FREQ ( CLK_FREQ ),
.I2C_FREQ ( I2C_FREQ )
)u_IIC_CONTROL(
.clk ( sys_clk ),
.rst_n ( sys_rst_n ),
.i2c_addr ( i2c_addr ),
.i2c_data_w ( i2c_data_w ),
.i2c_rh_wl ( i2c_rh_wl ),
.bit_control ( BIT_CTRL ),
.i2c_exec ( i2c_exec ),
.dri_clk ( dri_clk ),
.i2c_data_r ( i2c_data_r ),
.i2c_ack ( i2c_ack ),
.i2c_done ( i2c_done ),
.scl ( i2c_scl ),
.sda ( i2c_sda )
);
LED#(
.L_TIME ( L_TIME )
)u_LED(
.clk ( dri_clk ),
.rst_n ( sys_rst_n ),
.rw_done ( rw_done ),
.rw_result ( rw_result ),
.led ( led )
);
endmodule
下面是testbench
EEPROM_AT24C64.v
`timescale 1ns/1ns
`define timeslice 1250
module EEPROM_AT24C64(
scl,
sda
);
input scl;
inout sda;
reg out_flag;
reg[7:0] memory[8191:0];
reg[12:0]address;
reg[7:0]memory_buf;
reg[7:0]sda_buf;
reg[7:0]shift;
reg[7:0]addr_byte_h;
reg[7:0]addr_byte_l;
reg[7:0]ctrl_byte;
reg[1:0]State;
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
initial
begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
always@(negedge sda)
begin
if(scl == 1)
begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end
always@(posedge sda)
begin
if(scl == 1)
stop_W_R;
else
begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4
|| ctrl_byte == w3 || ctrl_byte == w2
|| ctrl_byte == w1 || ctrl_byte == w0)
begin
State = 2'b10;
write_to_eeprom;
end
else
State = 2'b00;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask
task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
State = 2'b00;
end
endtask
task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0)
begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl)
begin
#(`timeslice);
out_flag = 1;
sda_buf = 0;
end
@(negedge scl)
begin
#(`timeslice-250);
out_flag = 0;
end
end
endtask
task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1)
begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1;
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
e2prom_tb.v
module e2prom_tb;
//parameter define
parameter T = 20 ; //时钟周期为20ns
parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR)
parameter BIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率
parameter L_TIME = 17'd1 ; //led闪烁时间参数
parameter MAX_BYTE = 16'd3 ; //读写测试的字节个数
//reg define
reg sys_clk ; //时钟信号
reg sys_rst_n; //复位信号
//wire define
wire iic_scl;
wire iic_sda;
wire led ;
//*****************************************************
//** main code
//*****************************************************
//给输入信号初始值
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0; //复位
#(T+1) sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高
end
//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;
//将SDA数据线上拉
pullup(iic_sda);
IIC_top#(
.SLAVE_ADDR ( 7'b1010000 ),
.BIT_CTRL ( 1'b1 ),
.CLK_FREQ ( 26'd50_000_000 ),
.I2C_FREQ ( 18'd250_000 ),
.L_TIME ( 17'd125_000 ),
.MAX_BYTE ( 16'd256 )
)u_IIC_top(
.sys_clk ( sys_clk ),
.sys_rst_n ( sys_rst_n ),
.i2c_scl ( iic_scl ),
.i2c_sda ( iic_sda ),
.led ( led )
);
//例化e2prom仿真模型
EEPROM_AT24C64 u_EEPROM_AT24C64(
.scl (iic_scl),
.sda (iic_sda)
);
endmodule
下面是注意事项
README.md
I2C 转换接口的设计思路
因为时钟的不同 我们先设计出本次时钟所需要的dri_clk
在配置完dri_clk 之后 我们需要做的是对整个I2C结构 进行状态机的 书写
建议 写成经典的三段状态机的形式
- 同步时序描述状态转移
- 组合逻辑判断状态转移条件
- 时序电路描述状态输出
前两部分是相对来说好处理的 后面第三部分的 时序逻辑电路描述状态有些复杂
代码第639行 和之前的存储地址一样又 不太一样
需要先 交还给 FPGA端控制权 再执行
为什么在之前的传递数据在cnt = 0 的时候 交还了控制权 直接赋值呢
原因是因为 它们并不是传递地址 只有首次传递地址的时候 需要保证在SCL在拉高的情况下 SDA先拉底
我们采用倒推法
cnt | 35 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|---|
SCL | 1->0 | 0 | 0->1 | 1 | 1->0 | 0 |
实验效果 在 35 这一时刻来自于上一时刻
在0时刻 SCL处于中间低处 此时 我们对于 SDA交还控制权给 FPGA 再 给赋值 sda_out = 1
因为我们SCL总是 dri_clk 的四倍 我们在35时刻拉低 就在1时刻拉高
在稳定的 2时刻 拉低 sda_out 即 传输器件地址需要先拉低 sda
最后对于 1拉高 3拉低 4处于低的洼地 -> 可以使得 sda_out 变化
完整介绍一下模块的设计
首先本次实验完成的是 IIC转化的串口形式
在执行处理的时候,把整个元件当成了一个黑盒模块
就像之前的AXI-Stream转Native接口的RTL模块一样
现在所做的更像是把 Native模块 转化成IIC协议的接口形式
通俗易懂来讲 别的模块我根本不在乎 这个东西 就是将地址,数据等信息传递进入这个转换模块
转换模块通过内部的调度与适配 传输成符合标准与要求的 SCL与SDA的形式
(我个人觉得 IIC要比 AXI-Stream难 10倍 UART 最简单浅显易懂)
这是I2C的读写流程图
我们本次实验主要在这个图上编写状态机
这里模块内部时钟的编辑
通常使用的时钟频率是50MHz = 26’d50_000_000
这个50MHz 的意思 就是 1s 有 50_000_000个时钟周期 即 每个时钟周期的时长为 20ns
彼时 需要使用一个新的时钟频率 假如是 18’d250_000 的时钟频率 即每秒有250000个周期间隔
我们可以通过number = 50_000_000 / 250_000 求得计数
注意在用cnt 计数时 除以2 再减 1 因为时钟总是在一半的时候进行翻转
完整的画一下 单次读 与 单次写的 时序流程图
下面是单次写的时序图
我觉得有一个不妥当的地方是为什么它非要在上升沿采样数据呢 为什么不等高电平 数据稳定的时候采样
第278行 我将 i2c_exec删除了 我觉得没问题
最后上板验证也未出现问题
// if( i2c_exec == 1) begin
wr_flag <= i2c_rh_wl ;
addr_save <= i2c_addr ;
data_w_save <= i2c_data_w ;
i2c_ack <= 0 ;
// end
```
# 第734行 它的做法总是在上升沿触发 我吐槽过了 我觉得电平触发更加稳定
它的做法
```verilog
case(cnt)
7'd0:
sda_dir <= 1'b0;
7'd1:
begin
data_r_save[7] <= sda_in;
scl <= 1'b1;
end
7'd3:
scl <= 1'b0;
7'd5:
begin
data_r_save[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7:
scl <= 1'b0;
7'd9:
begin
data_r_save[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11:
scl <= 1'b0;
7'd13:
begin
data_r_save[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15:
scl <= 1'b0;
7'd17:
begin
data_r_save[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19:
scl <= 1'b0;
7'd21:
begin
data_r_save[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23:
scl <= 1'b0;
7'd25:
begin
data_r_save[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27:
scl <= 1'b0;
7'd29:
begin
data_r_save[0] <= sda_in;
scl <= 1'b1 ;
end
```
上面是别人的对的
我自己改成高电平触发 也是正确的
```verilog
case(cnt)
7'd0:
sda_dir <= 1'b0;
7'd1:
scl <= 1'b1;
7'd2 :
data_r_save[7] <= sda_in;
7'd3:
scl <= 1'b0;
7'd5:
scl <= 1'b1 ;
7'd6:
data_r_save[6] <= sda_in ;
7'd7:
scl <= 1'b0;
7'd9:
scl <= 1'b1 ;
7'd10 :
data_r_save[5] <= sda_in;
7'd11:
scl <= 1'b0;
7'd13:
scl <= 1'b1 ;
7'd14:
data_r_save[4] <= sda_in;
7'd15:
scl <= 1'b0;
7'd17:
scl <= 1'b1 ;
7'd18:
data_r_save[3] <= sda_in;
7'd19:
scl <= 1'b0;
7'd21:
scl <= 1'b1 ;
7'd22:
data_r_save[2] <= sda_in;
7'd23:
scl <= 1'b0;
7'd25:
scl <= 1'b1 ;
7'd26:
data_r_save[1] <= sda_in;
7'd27:
scl <= 1'b0;
7'd29:
scl <= 1'b1 ;
7'd30:
data_r_save[0] <= sda_in;
7'd31:
scl <= 1'b0;
7'd32:
begin
sda_dir <= 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 <= 7'b0;
i2c_data_r <= data_r_save;
end
default :
;
endcase
end
```