有关IIC时序部分:链接
上一篇,一段状态机的iic驱动:链接
/*
iic freq = 250khz
fpga freq = 50Mhz
向内部寄存器地址位数为8的从机写数据:1->3->4->7 need:type16_type8=0,read1_write0=0.
向内部寄存器地址位数为16的从机写数据:1->2->3->4->7 need:type16_type8=1,read1_write0=0.
从内部寄存器地址位数为8的从机读出数据:1->3->5->6->7 need:type16_type8=0,read1_write0=1.
从内部寄存器地址位数为16的从机读出数据:1->2->3->5->6->7 need:type16_type8=1,read1_write0=1.
*/
module i2c_dri(
//fpga
input clk , // 输入时钟(clk_freq)
input rst_n , // 复位信号
//addr and data
input [6:0] slave_address, //从机地址
input [15:0] iic_inner_reg_addr, //从机内部寄存器地址,若为8bit形式,直接用低八位
input [ 7:0] i2c_w_data , //写数据端口
output reg [ 7:0] i2c_r_data , //读数据端口
//select mode
input type16_type8, //从机内部寄存器地址是否属于16bit形式(16-1/8-0)
input read1_write0, //读模式或写模式
//i2c interface
input i2c_exec , // IIC使能【1】
output reg i2c_done , // 一次操作完成的标志信号
output reg scl , // SCL输出
inout sda , // SDA输出
//user interface
output reg dri_clk // 低频时钟输出
);
reg [25:0] clk_freq = 26'd50_000_000; //输入时钟(clk_freq)的频率:26'd50_000_000
reg [17:0] i2c_freq = 18'd250_000; //选用iic的频率:18'd250_000
//############################# 三态门sda ##################################
reg sda_dir ; // (SDA)方向控制读出还是写入
reg sda_out ; // SDA输出信号
wire sda_in ; // SDA输入信号
assign sda = sda_dir ? sda_out : 1'bz; // 如果是输出,引脚sda输出数据就跟随sda_out寄存器的数据,如果是输入就拉为高阻
assign sda_in = sda ; // 输入线,数据跟随sda
//#########################################################################
/*#################### 低频时钟 dri_clk 的产生 ###################################
dri_clk的时钟频率,最好四倍于SCL的频率。
因为SCL的一次低电平为SCL的半个时钟周期
SDA在SCL为低电平时候变化。四倍于SCL的时钟频率
可以让我们准确的找到SCL为低电平的中间点,方便我们
发送数据。
系统时钟频率除以SCL频率=分频值,再/2=翻转值,再/4=dri_clk的翻转值
*/
wire [8:0] clk_divide ; // 模块驱动时钟的分频系数
reg [ 9:0] clk_cnt ; // 分频时钟计数
assign clk_divide = (clk_freq/i2c_freq) >> 2'd3;// 模块驱动时钟的分频系数
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
dri_clk <= 1'b1;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//####################################################################################
//############################# 三段状态机 ############################################
reg [ 7:0] cur_state ;// 状态机当前状态
reg [ 7:0] next_state ;// 状态机下一状态
reg st_done ;// 当前状态结束标志
reg [6:0] slave_address_reg; // 从机地址寄存
reg read1_write0_reg; // 读写控制位寄存
reg [15:0] iic_inner_reg_addr_reg; // 从机内部寄存器地址寄存
reg [ 7:0] data_r_reg ; // 读数据寄存
reg [ 7:0] data_w_reg ; // 写数据寄存
reg [ 6:0] cnt ; // 计数(计数器+多路器的方式控制SCL和SDA)
//状态定义
localparam st_0 = 8'b0000_0001; // 0、空闲状态
localparam st_1 = 8'b0000_0010; // 1、起始位+7位从机地址+1位写标志+从机应答
localparam st_2 = 8'b0000_0100; // 2、高8位从机内部寄存器地址+从机应答
localparam st_3 = 8'b0000_1000; // 3、低8位从机内部寄存器地址+从机应答
localparam st_4 = 8'b0001_0000; // 4、发送八位数据+从机应答
localparam st_5 = 8'b0010_0000; // 5、起始位+7位从机地址+1位读标志+从机应答
localparam st_6 = 8'b0100_0000; // 6、读8bit数据+主机发送非应答
localparam st_7 = 8'b1000_0000; // 7、发送停止位
/*
向内部寄存器地址位数为8的从机写数据:1->3->4->7 need:type16_type8=0,read1_write0=0.
向内部寄存器地址位数为16的从机写数据:1->2->3->4->7 need:type16_type8=1,read1_write0=0.
从内部寄存器地址位数为8的从机读出数据:1->3->5->6->7 need:type16_type8=0,read1_write0=1.
从内部寄存器地址位数为16的从机读出数据:1->2->3->5->6->7 need:type16_type8=1,read1_write0=1.
*/
//第一段:状态转移
always @(posedge dri_clk or negedge rst_n) begin
if(rst_n == 1'b0)
cur_state <= st_0;
else
cur_state <= next_state;
end
//第二段:条件控制状态转移
always @( * ) begin
case(cur_state)
st_0: begin
if(i2c_exec) begin
next_state = st_1;
end
else
next_state = st_0;
end
st_1: begin
if(st_done) begin
if(type16_type8 == 1'b1) // <-此处判断位数
next_state = st_2;
else
next_state = st_3 ;
end
else
next_state = st_1;
end
st_2: begin
if(st_done) begin
next_state = st_3;
end
else begin
next_state = st_2;
end
end
st_3: begin
if(st_done) begin
if(read1_write0_reg == 1'b0) // <-此处判断读写
next_state = st_4;
else
next_state = st_5;
end
else begin
next_state = st_3;
end
end
st_4: begin
if(st_done)
next_state = st_7;
else
next_state = st_4;
end
st_5: begin
if(st_done) begin
next_state = st_6;
end
else begin
next_state = st_5;
end
end
st_6: begin
if(st_done)
next_state = st_7;
else
next_state = st_6;
end
st_7: begin
if(st_done)
next_state = st_0;
else
next_state = st_7 ;
end
default: next_state= st_0;
endcase
end
//第三段:状态输出
always @(posedge dri_clk or negedge rst_n) begin
//复位初始化
if(rst_n == 1'b0) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
cnt <= 1'b0;
i2c_done <= 1'b0;
st_done <= 1'b0;
i2c_r_data <= 1'b0;
read1_write0_reg <= 1'b0;
iic_inner_reg_addr_reg <= 1'b0;
data_r_reg <= 1'b0;
data_w_reg <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt + 1'b1 ;
case(cur_state) //A
//空闲状态
st_0: begin
scl <= 1'b1;
sda_out <= 1'b1; // 根据协议,IIC空闲状态两条线均为高
sda_dir <= 1'b1; // 设置三态门状态为输出
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin //启动IIC则立即开始寄存数据
read1_write0_reg <= read1_write0 ;
iic_inner_reg_addr_reg <= iic_inner_reg_addr;
data_w_reg <= i2c_w_data;
slave_address_reg <= slave_address;
end
end
//1、【发送起始位+7bit从机地址+1bit写标志(0)+1bitack】
st_1: begin
case(cnt) //【注】SDA在SCL高稳定低变化,起始位:在SCL为1时,SDA出现下降沿
7'd1 : sda_out <= 1'b0; //sda下降沿(此时SCL为1,此为起始位)
//7'd2 :; //SCL高电平维持半个周期
7'd3 : scl <= 1'b0; //SCL拉低
7'd4 : sda_out <= slave_address_reg[6];//SCL低电平维持半个周期(同时给SDA线发送提供数据)
7'd5 : scl <= 1'b1; //同理...
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= slave_address_reg[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= slave_address_reg[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= slave_address_reg[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= slave_address_reg[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= slave_address_reg[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= slave_address_reg[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: begin
sda_dir <= 1'b0; // sda线路设为高阻态准备接收从机应答
sda_out <= 1'b1; //此时sda_out已经控制不了SDA线
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1; //此状态完成
7'd39: begin //最后把scl拉低,计数器清0。
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
//2、【发送从机内部寄存器地址的高8bit +1bitack】
st_2: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= iic_inner_reg_addr_reg[15];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= iic_inner_reg_addr_reg[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= iic_inner_reg_addr_reg[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= iic_inner_reg_addr_reg[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= iic_inner_reg_addr_reg[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= iic_inner_reg_addr_reg[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= iic_inner_reg_addr_reg[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= iic_inner_reg_addr_reg[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 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
//3、【发送从机内部寄存器地址的低8bit +1bitack】
st_3: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= iic_inner_reg_addr_reg[7];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= iic_inner_reg_addr_reg[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= iic_inner_reg_addr_reg[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= iic_inner_reg_addr_reg[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= iic_inner_reg_addr_reg[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= iic_inner_reg_addr_reg[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= iic_inner_reg_addr_reg[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= iic_inner_reg_addr_reg[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 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
//4、【发送8bit数据 +1bitack】
st_4: begin
case(cnt)
7'd0: begin
sda_out <= data_w_reg[7];
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_w_reg[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_w_reg[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_w_reg[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_w_reg[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_w_reg[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_w_reg[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_w_reg[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 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
//5、【发送起始位+7bit从机地址+1bit读标志(1)+1bitack】
st_5: begin
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_address_reg[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= slave_address_reg[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= slave_address_reg[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= slave_address_reg[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= slave_address_reg[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= slave_address_reg[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= slave_address_reg[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; // 读标志
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: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
//6、【读8bit数据+主机发送+1bitNOack】
st_6: begin
case(cnt)
7'd0: sda_dir <= 1'b0; //sda线路设为向内读入状态
7'd1: begin
data_r_reg[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r_reg[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r_reg[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r_reg[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r_reg[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r_reg[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r_reg[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r_reg[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin //sda线路设为输出 准备让主机发送非应答
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 <= 1'b0;
i2c_r_data <= data_r_reg; //把读完放到寄存器data_r中的数据,传出来。
end
default : ;
endcase
end
//7、【发送停止位(1次iic操作结束)】
st_7: begin //【注】停止位:在SCL为1时,SDA出现上升沿
case(cnt)
7'd0: begin
sda_dir <= 1'b1; // 结束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1; //在scl为高电平的状态的时候,把sda抬高,从而产生上升沿作为停止位。
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; // 向上层模块传递I2C结束信号
end
default : ;
endcase
end
endcase//A
end
end
endmodule