【verilog】FPGA 一主多从模式下的IIC驱动(一段状态机法)

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

在这里插入图片描述

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搞IC的那些年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值