verilog的EEPROM驱动-单字节读写
1.读写
c
module i2c_dri(
input clk ,
input rst_n ,
//i2c interface
input i2c_rw , //0是写,1是读
input i2c_begin , //I2C触发执行信号
input [15:0] i2c_addr , //I2C器件内地址
input [ 7:0] i2c_data_w , //I2C要写的数据
output reg [ 7:0] i2c_data_r , //I2C读出的数据
output reg i2c_busy , //I2C忙信号
output reg i2c_done , //I2C一次操作完成
output reg scl , //I2C的SCL时钟信号
inout sda , //I2C的SDA信号
output reg led2 ,
//
output reg dri_clk //I2C数据(SDA)方向控制
);
parameter SLAVE_ADDR = 7'b1010000 ; //EEPROM从机地址
parameter CLK_FREQ = 26'd50_000_000; //模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000; //IIC_SCL的时钟频率
//reg define
reg [ 9:0] clk_cnt ; //分频时钟计数
reg [ 7:0] data_wr_t ; //I2C需写的数据的临时寄存
reg [15:0] addr_t ; //读写地址临时寄存
reg [ 7:0] data_r ; //数据
reg [ 7:0] data_w ; //数据
reg r_or_w ; //读写
reg sda_dir ; //I2C数据(SDA)方向控制
reg sda_out ; //
reg [ 3:0] flow_cnt ; // 流转计数
//wire define
wire sda_in ; //SDA输入信号
wire [8:0] clk_divide ; //模块驱动时钟的分频系数
assign sda = sda_out ; //SDA数据输出或高阻
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//////////////////////////////////
reg [ 7:0] data_temp ; //数据
reg flow_cnt_ok ; // 流转计数
reg [ 6:0] cnt ; //计数
////////////////////////////////////////////////
/*
写
0:空闲
1:加载器件地址
2:写字节
3:加载16为地址高字节
4:写字节
5:加载16为地址低字节
6:写字节
7:加载数据
8:写字节
9:发送停止位
读
0:空闲
1:加载器件地址
2:写字节
3:加载16为地址高字节
4:写字节
5:加载16为地址低字节
6:写字节
10:加载器件地址+读
8:写字节
11:读数据
12:发送停止
*/
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
cnt<= 7'b0;
flow_cnt<=4'b0;
i2c_busy<=1'b0;
flow_cnt_ok<= 1'b0;
cnt<= 1'b0;
end
////////////////////////////////////////////
else if (flow_cnt_ok&&(r_or_w==0))begin
flow_cnt<=((flow_cnt>=4'd9)? (4'd0):(flow_cnt+4'd1));//状态技术+1
i2c_done<=1'b0;
flow_cnt_ok<= 1'b0;
end
else if (flow_cnt_ok&&(r_or_w==1))begin
if(flow_cnt<=4'd5)
flow_cnt<=flow_cnt+1'b1;
else if (flow_cnt==4'd6)
flow_cnt<=4'd10;
else if (flow_cnt==4'd10)
flow_cnt<=4'd8;
else if (flow_cnt==4'd8)
flow_cnt<=4'd11;
else if (flow_cnt==4'd11)
flow_cnt<=4'd12;
else if (flow_cnt==4'd12)
flow_cnt<=4'd0;
i2c_done<=1'b0;
flow_cnt_ok<= 1'b0;
end
///////////////////////////////////////////////
else if(flow_cnt == 4'b0) begin
if(i2c_begin==1'b1&&i2c_busy==1'b0) begin
r_or_w<=i2c_rw;
addr_t<=i2c_addr;
data_w<=i2c_data_w;
i2c_busy<=1'b1;
flow_cnt_ok<=1'b1;
end
else
flow_cnt = 4'b0;
end
else if(flow_cnt == 4'd1) begin//加载写字节
data_temp<={SLAVE_ADDR,1'b0};
flow_cnt_ok<=1'b1;
end
else if(flow_cnt == 4'd3) begin//加载eeprom地址
data_temp<=addr_t[15:8];
flow_cnt_ok<=1'b1;
end
else if(flow_cnt == 4'd5) begin//加载低8位地址
data_temp<=addr_t[7:0];
flow_cnt_ok<=1'b1;
end
else if(flow_cnt == 4'd7) begin//加载要写入的数据
data_temp<=data_w[7:0];
flow_cnt_ok<=1'b1;
end
else if(flow_cnt == 4'd9) begin//发送STOP
cnt <= cnt +1'b1 ;
case(cnt)
7'd1 : sda_out <= 1'b0; //开始I2C
7'd2 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1; //传送器件地址
7'd4 : begin
i2c_busy<=1'b0;
i2c_done<=1'b1;
scl <= 1'b1;
sda_out <= 1'b1;
flow_cnt_ok<=1'b1;
cnt <= 1'b0;
end
default : ;
endcase
end
else if(flow_cnt == 4'd2||flow_cnt == 4'd4||flow_cnt == 4'd6||flow_cnt == 4'd8) begin//发送一个字节
cnt <= cnt +1'b1 ;
case(cnt)
//7'd0 :begin scl <= 1'b1;sda_out <= 1'b1;end
7'd1 : sda_out <= 1'b0; //开始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_temp[7]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_temp[6];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_temp[5];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_temp[4];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_temp[3];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_temp[2];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_temp[1];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= data_temp[0]; //0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: sda_out <= 1'bz;
//
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
if(sda == 1'b1) begin //高电平表示未应答
cnt <= 1'b0;
flow_cnt = 4'b0;
end
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
flow_cnt_ok<=1'b1;
end
default : ;
endcase
end
else if(flow_cnt == 4'd10) begin//加载要读的芯片地址
data_temp<={SLAVE_ADDR,1'b1};
flow_cnt_ok<=1'b1;
scl <= 1'b1;//添加起始位
end
else if(flow_cnt == 4'd12) begin//发送STOP
cnt <= cnt +1'b1 ;
case(cnt)
7'd1 : sda_out <= 1'b0; //开始I2C
7'd2 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1; //传送器件地址
7'd4 : begin
i2c_busy<=1'b0;
i2c_done<=1'b1;
scl <= 1'b1;
sda_out <= 1'b1;
flow_cnt_ok<=1'b1;
cnt <= 1'b0;
end
default : ;
endcase
end
else if(flow_cnt == 4'd11) begin//加载要读的芯片地址
cnt <= cnt +1'b1 ;
case(cnt)
7'd1 : sda_out <= 1'bz; //开始I2C
7'd3 : scl <= 1'b0;
7'd5 : scl <= 1'b1;
7'd6 : data_r[7]<=sda;
7'd7 : scl <= 1'b0;
7'd9 : scl <= 1'b1;
7'd10 : data_r[6]<=sda;
7'd11: scl <= 1'b0;
7'd13: scl <= 1'b1;
7'd14 : data_r[5]<=sda;
7'd15: scl <= 1'b0;
7'd17: scl <= 1'b1;
7'd18: data_r[4]<=sda;
7'd19: scl <= 1'b0;
7'd21: scl <= 1'b1;
7'd22: data_r[3]<=sda;
7'd23: scl <= 1'b0;
7'd25: scl <= 1'b1;
7'd26: data_r[2]<=sda;
7'd27: scl <= 1'b0;
7'd29: scl <= 1'b1;
7'd30:data_r[1]<=sda;
7'd31: scl <= 1'b0;
7'd33: scl <= 1'b1;
7'd34: data_r[0]<=sda;
7'd35: scl <= 1'b0;
7'd36: sda_out <= 1'b0;
7'd37: scl <= 1'b1;
7'd39: scl <= 1'b0;
7'd40: sda_out <= 1'b1;
7'd42: begin
cnt <= 1'b0;
flow_cnt_ok<=1'b1;
end
default : ;
endcase
end
else
;
end
endmodule
2.例化
c
module e2prom_top(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位
//eeprom interface
output iic_scl , //eeprom的时钟线scl
inout iic_sda , //eeprom的数据线sda
output iic_scl_b ,
output iic_sda_b ,
output dri_clk ,
output reg led0 ,
output reg led1 ,
output led2 ,
input key0 ,
input key1
);
assign iic_scl_b=iic_scl;
assign iic_sda_b=iic_sda;
//parameter define
parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR)
parameter CLK_FREQ = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率
//*****************************************************
//** main code
//****************************************************//
reg i2c_rw;
reg i2c_begin;
reg [15:0] i2c_addr;
reg [ 7:0] i2c_data_w ; //I2C要写的数据
wire [ 7:0] i2c_data_r ; //I2C读出的数据
always @(posedge dri_clk or negedge sys_rst_n) begin
if (!sys_rst_n)begin
led0<=1'b0;
i2c_begin<=1'b0;
i2c_rw<=1'b0;//写数据
i2c_addr<=16'h0000;
i2c_data_w<=8'h00;
end
else if (key0_flag&&key0_value&&(!i2c_busy))begin
i2c_begin<=1'b1;
i2c_rw<=1'b0;//写数据
i2c_addr<=16'h0001;
i2c_data_w<=8'h12;
led0<=~led0;
end
else if (key1_flag&&key1_value&&(!i2c_busy))begin
i2c_begin<=1'b1;
i2c_rw<=1'b1;//读数据
i2c_addr<=16'h0001;
i2c_data_w<=8'h12;
led1<=~led1;
end
else
i2c_begin<=1'b0;
end
//i2c驱动模块
i2c_dri #(
.SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址
.CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率
.I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率
) u_i2c_dri(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.i2c_rw (i2c_rw ),
.i2c_begin (i2c_begin ), //I2C触发执行信号
.i2c_addr (i2c_addr ), //I2C器件内地址
.i2c_data_w (i2c_data_w ), //I2C要写的数据
.i2c_data_r (i2c_data_r ), //I2C读出的数据
.i2c_busy (i2c_busy ), //I2C忙信号
.i2c_done (i2c_done ), //I2C忙信号
.led2 (led2 ), //I2C忙信号
.scl (iic_scl ), //I2C的SCL时钟信号
.sda (iic_sda ) , //I2C的SDA信号
.dri_clk (dri_clk)
);
/*
always @(posedge dri_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led0<=1'b0;
else if (key0_flag&&key0_value)begin
//led0<=~led0;
;
end
else
//led0<=led0;
;
end
always @(posedge dri_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
led1<=1'b0;
else if (key1_flag&&key1_value)
led1<=~led1;
else
led1<=led1;
end
*/
wire key0_value;
wire key0_flag;
key_in u0_key_in(
.sys_clk (dri_clk ),
.rst_n (sys_rst_n ),
.key_in (key0 ),
.key_flag (key0_flag ),
.key_value (key0_value )
);
key_in u1_key_in(
.sys_clk (dri_clk ),
.rst_n (sys_rst_n ),
.key_in (key1 ),
.key_flag (key1_flag ),
.key_value (key1_value )
);
endmodule
c
module key_in(
input sys_clk, //外部50M时钟
input rst_n, //外部复位信号,低有效
input key_in, //外部按键输入
output reg key_flag, //按键数据有效信号
output reg key_value //按键消抖后的数据
);
reg [31:0] delay_cnt;
reg key_reg;
always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
key_reg <= 1'b1;
delay_cnt <= 32'd0;
end
else begin
key_reg <= key_in; //非阻塞赋值,按键输入是wire类型的,随时可能有值的变化
if(key_reg != key_in) //一旦检测到按键状态发生变化(有按键被按下或释放)
delay_cnt <= 32'd500_000; //给延时计数器重新装载初始值(计数时间为10ms)
else if(key_reg == key_in) begin //在按键状态稳定时,计数器递减,开始10ms倒计时
if(delay_cnt > 32'd0)
delay_cnt <= delay_cnt - 1'b1;
else
delay_cnt <= delay_cnt;
end
end
end
always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
key_flag <= 1'b0;
key_value <= 1'b1;
end
else begin
if(delay_cnt == 32'd1) begin //当计数器递减到1时,说明按键稳定状态维持了20ms
key_flag <= 1'b1; //此时消抖过程结束,给出一个时钟周期的标志信号
key_value <= key_in; //并寄存此时按键的值
end
else begin
key_flag <= 1'b0;
key_value <= key_value;
end
end
end
endmodule
3.波形