MAIN CODE
IIC时序见:链接
有关细节部分,代码中已经给出了详细的注释!
/*
iic freq = 250khz
fpga freq = 50Mhz
-------------------------------------
1、起始位+7位从机地址+1位写标志+从机应答
2、高8位从机内部寄存器地址+从机应答
3、低8位从机内部寄存器地址+从机应答
4、8位发送数据+从机应答
5、停止位
6、起始位+7位从机地址+1位读标志+从机应答
7、读8位数据+主机发送非应答
向内部寄存器地址位数为8的从机写数据:1->3->4->5 need:type16_type8=0,read1_write0=0.
向内部寄存器地址位数为16的从机写数据:1->2->3->4->5 need:type16_type8=1,read1_write0=0.
从内部寄存器地址位数为8的从机读出数据:1->3->6->7->5 need:type16_type8=0,read1_write0=1.
从内部寄存器地址位数为16的从机读出数据:1->2->3->6->7->5 need:type16_type8=1,read1_write0=1.
*/
module iic
(
//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_data_w , //写数据端口
output reg [ 7:0] i2c_data_r , //读数据端口
//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
//####################################################################################
//######################################### MAIN #######################################
reg [6:0] main_cnt;
reg [6:0] minor_cnt;
reg [6:0] slave_addr;
reg [15:0] inner_reg_addr;
reg [ 7:0] data_w;
reg [ 7:0] data_r;
always @(posedge dri_clk or negedge rst_n) begin//(0)
if(rst_n == 1'b0) begin
scl <= 1'b1;
sda_dir <= 1'b1;
sda_out <= 1'b1; //根据协议,IIC空闲状态两条线均为高
main_cnt <= 1'b0;
minor_cnt <= 1'b0;
data_r <= 1'b0;
i2c_data_r <= 1'b0;
data_w <= 1'b0;
inner_reg_addr <= 1'b0;
slave_addr <= 1'b0;
i2c_done <= 1'b0;
end
else begin//(1)
if(i2c_exec == 1'd1 && main_cnt == 7'd0) begin
inner_reg_addr <= iic_inner_reg_addr; //将IIC【内部寄存器地址】寄存
data_w <= i2c_data_w; //将需要发送的【数据】寄存
slave_addr <= slave_address; //将【从机地址】寄存
main_cnt <= 7'd1; //主计数器+1,进入下一状态!
sda_dir <= 1'b1; //设置三态门状态为输出
i2c_done <= 1'b0; //iic一次完成操作标志清0
end
//1、【发送起始位+7bit从机地址+1bit写标志(0)+1bitack】
else if(main_cnt == 7'd1)begin//(a2)
//【注】SDA在SCL高稳定低变化,起始位:在SCL为1时,SDA出现下降沿
minor_cnt <= minor_cnt + 1'b1;
case(minor_cnt) //一个计数值代表SCL的半个周期(可以这样理解)
7'd1 : sda_out <= 1'b0; //sda下降沿(此时SCL为1,此为起始位)
7'd2 :; //SCL高电平维持半个周期
7'd3 : scl <= 1'b0; //SCL拉低
7'd4 : sda_out <= slave_addr[6];//SCL低电平维持半个周期(同时给SDA线发送提供数据)
7'd5 : scl <= 1'b1; //同理...
7'd6 :;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= slave_addr[5];
7'd9 : scl <= 1'b1;
7'd10:;
7'd11: scl <= 1'b0;
7'd12: sda_out <= slave_addr[4];
7'd13: scl <= 1'b1;
7'd14:;
7'd15: scl <= 1'b0;
7'd16: sda_out <= slave_addr[3];
7'd17: scl <= 1'b1;
7'd18:;
7'd19: scl <= 1'b0;
7'd20: sda_out <= slave_addr[2];
7'd21: scl <= 1'b1;
7'd22:;
7'd23: scl <= 1'b0;
7'd24: sda_out <= slave_addr[1];
7'd25: scl <= 1'b1;
7'd26:;
7'd27: scl <= 1'b0;
7'd28: sda_out <= slave_addr[0];
7'd29: scl <= 1'b1;
7'd30:;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; //写标志
7'd33: scl <= 1'b1;
7'd34:;
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: ;
7'd39: begin//最后把scl拉低,minor_cnt计数器清0,main_cnt计数器+1或+2进入下一状态。
scl <= 1'b0;
minor_cnt <= 1'b0;
//-------------------------------
if(type16_type8 == 1'b1)
main_cnt <= 7'd2;
else
main_cnt <= 7'd3;
//-------------------------------
end
default : ;
endcase
end//(a2)
//2、【发送从机内部寄存器地址的高8bit +1bitack】
else if(main_cnt == 7'd2)begin//(a3)
minor_cnt <= minor_cnt + 1'b1;
case(minor_cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= inner_reg_addr[15];
end
7'd1 : scl <= 1'b1;
7'd2 :;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= inner_reg_addr[14];
7'd5 : scl <= 1'b1;
7'd6 :;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= inner_reg_addr[13];
7'd9 : scl <= 1'b1;
7'd10:;
7'd11: scl <= 1'b0;
7'd12: sda_out <= inner_reg_addr[12];
7'd13: scl <= 1'b1;
7'd14:;
7'd15: scl <= 1'b0;
7'd16: sda_out <= inner_reg_addr[11];
7'd17: scl <= 1'b1;
7'd18:;
7'd19: scl <= 1'b0;
7'd20: sda_out <= inner_reg_addr[10];
7'd21: scl <= 1'b1;
7'd22:;
7'd23: scl <= 1'b0;
7'd24: sda_out <= inner_reg_addr[9];
7'd25: scl <= 1'b1;
7'd26:;
7'd27: scl <= 1'b0;
7'd28: sda_out <= inner_reg_addr[8];
7'd29: scl <= 1'b1;
7'd30:;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: ;
7'd35: begin
scl <= 1'b0;
minor_cnt <= 1'b0;
//-------------------------------
main_cnt <= 7'd3;
//-------------------------------
end
default :;
endcase
end//(a3)
//3、【发送从机内部寄存器地址的低8bit +1bitack】
else if(main_cnt == 7'd3)begin//(a4)
minor_cnt <= minor_cnt + 1'b1;
case(minor_cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= inner_reg_addr[7];
end
7'd1 : scl <= 1'b1;
7'd2 :;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= inner_reg_addr[6];
7'd5 : scl <= 1'b1;
7'd6 :;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= inner_reg_addr[5];
7'd9 : scl <= 1'b1;
7'd10:;
7'd11: scl <= 1'b0;
7'd12: sda_out <= inner_reg_addr[4];
7'd13: scl <= 1'b1;
7'd14:;
7'd15: scl <= 1'b0;
7'd16: sda_out <= inner_reg_addr[3];
7'd17: scl <= 1'b1;
7'd18:;
7'd19: scl <= 1'b0;
7'd20: sda_out <= inner_reg_addr[2];
7'd21: scl <= 1'b1;
7'd22:;
7'd23: scl <= 1'b0;
7'd24: sda_out <= inner_reg_addr[1];
7'd25: scl <= 1'b1;
7'd26:;
7'd27: scl <= 1'b0;
7'd28: sda_out <= inner_reg_addr[0];
7'd29: scl <= 1'b1;
7'd30:;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34:;
7'd35: begin
scl <= 1'b0;
minor_cnt <= 1'b0;
//-------------------------------
if(read1_write0 == 1'b1)
main_cnt <= 7'd6;
else
main_cnt <= 7'd4;
//-------------------------------
end
default :;
endcase
end//(a4)
//4、【发送8bit数据 +1bitack】
else if(main_cnt == 7'd4)begin//(a5)
minor_cnt <= minor_cnt + 1'b1;
case(minor_cnt)
7'd0: begin
sda_dir <= 1'b1;
sda_out <= data_w[7];
end
7'd1 : scl <= 1'b1;
7'd2 :;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_w[6];
7'd5 : scl <= 1'b1;
7'd6 :;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_w[5];
7'd9 : scl <= 1'b1;
7'd10:;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_w[4];
7'd13: scl <= 1'b1;
7'd14:;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_w[3];
7'd17: scl <= 1'b1;
7'd18:;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_w[2];
7'd21: scl <= 1'b1;
7'd22:;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_w[1];
7'd25: scl <= 1'b1;
7'd26:;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_w[0];
7'd29: scl <= 1'b1;
7'd30:;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: ;
7'd35: begin
scl <= 1'b0;
minor_cnt <= 1'b0;
//-------------------------------
main_cnt <= 7'd5;
//-------------------------------
end
default :;
endcase
end//(a5)
//5、【发送停止位(1次iic操作结束)】
else if(main_cnt == 7'd5)begin//(a6)
//【注】停止位:在SCL为1时,SDA出现上升沿
minor_cnt <= minor_cnt + 1'b1;
case(minor_cnt)
7'd0: begin
sda_dir <= 1'b1;
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1; //把scl抬高
7'd2 :;
7'd3 : sda_out <= 1'b1; //在scl为高电平的状态的时候,把sda抬高,从而产生上升沿作为停止位。
7'd16: begin
minor_cnt <= 1'b0;
i2c_done <= 1'b1; //向上层模块传递I2C操作结束
//-------------------------------
main_cnt <= 7'd0;
//-------------------------------
end
default : ;
endcase
end//(a6)
//6、【发送起始位+7bit从机地址+1bit读标志(1)+1bitack】
else if(main_cnt == 7'd6)begin//(a7)
minor_cnt <= minor_cnt + 1'b1;
case(minor_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'd6 :;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= slave_addr[5];
7'd9 : scl <= 1'b1;
7'd10:;
7'd11: scl <= 1'b0;
7'd12: sda_out <= slave_addr[4];
7'd13: scl <= 1'b1;
7'd14:;
7'd15: scl <= 1'b0;
7'd16: sda_out <= slave_addr[3];
7'd17: scl <= 1'b1;
7'd18:;
7'd19: scl <= 1'b0;
7'd20: sda_out <= slave_addr[2];
7'd21: scl <= 1'b1;
7'd22:;
7'd23: scl <= 1'b0;
7'd24: sda_out <= slave_addr[1];
7'd25: scl <= 1'b1;
7'd26:;
7'd27: scl <= 1'b0;
7'd28: sda_out <= slave_addr[0];
7'd29: scl <= 1'b1;
7'd30:;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; // 1:读
7'd33: scl <= 1'b1;
7'd34:;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38:;
7'd39: begin
scl <= 1'b0;
minor_cnt <= 1'b0;
//-------------------------------
main_cnt <= 7'd7;
//-------------------------------
end
default : ;
endcase
end//(a7)
//7、【读8bit数据+主机发送+1bitNOack】
else if(main_cnt == 7'd7)begin//(a8)
minor_cnt <= minor_cnt + 1'b1;
case(minor_cnt)
7'd0: sda_dir <= 1'b0; //sda线路设为向内读入状态
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1; //sda线路设为输出 准备让主机发送非应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd35: begin
scl <= 1'b0;
minor_cnt <= 1'b0;
i2c_data_r <= data_r; //把读完放到寄存器data_r中的数据,传出来。
//-------------------------------
main_cnt <= 7'd5;
//-------------------------------
end
default : ;
endcase
end//(a8)
end//(1)
end//(0)
//###################################################################################
endmodule
写测试
`timescale 1ns/1ns
module iic_tb;
reg clk ;
reg rst_n ;
wire i2c_done ;
wire scl ;
wire sda ;
iic f1
(
//fpga
.clk(clk) , // 输入时钟(clk_freq)
.rst_n(rst_n) , // 复位信号
//addr and data
.slave_address(7'b1010000), //从机地址
.iic_inner_reg_addr(16'haa), //从机内部寄存器地址,若为8bit形式,直接用低八位
.i2c_data_w(8'hbb) , //写数据端口
.i2c_data_r() , //读数据端口
//select mode
.type16_type8(1'b0), //从机内部寄存器地址是否属于16bit形式(16-1/8-0)
.read1_write0(1'b0), //读模式或写模式
//i2c interface
.i2c_exec(1'b1) , // IIC使能【1】
.i2c_done(i2c_done) , // 一次操作完成的标志信号
.scl(scl) , // SCL输出
.sda(sda) , // SDA输出
//user interface
.dri_clk() // 低频时钟输出
);
initial clk = 1'b1;
always#10 clk = ~clk;
initial begin
rst_n = 1'b0; //未运行状态
#400;
rst_n = 1'b1;
@(posedge i2c_done)
$stop;
end
endmodule