Menu Close

I2C 主设备PHY层STOP控制Verilog程序设计

在I2C在完成相关的读写操作后,就要向设备发送STOP操作,STOP作为一个独立模块,是因为在I2c总线发生错误时,也需要发送STOP,不仅仅是正常的I2c读写结束时。STOP 信号是在SCL信号为高电平期间,SDA信号由低变高。这个部分就是控制器对设备的STOP操作。在I2C PHY层 Verilog程序结构化设计 文章中的op_cmd[1:0] = 2’b11 来实现。如图1:

图1

1.I2C读数据部分控制器程序设计

  • 接口要求:

    • 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
      • input rst,
      • input sys_clk,
      • input clk_en, clk_en_half,
    • 命令信号
      • input [1:0] op_cmd,      // 2’b11 means controller send STOP 
      • input op_cmd_en,        // op_cmd enable , sys_clk single pulse
    • I2C 总线的内部信号:由于inout信号一般只在端口使用,因此在FPGA的内部逻辑(内部模块)将会把inout(双向口)变换成input, output类型进行传递
      • input sda_i,
      • output sda_o,
      • output sda_t,
      • input scl_i,
      • output scl_o,
      • output scl_t,
    • 握手信号:op_cmd_done 操作完成、从设备应答。当op_cmd_done产生,从设备应答后(dev_ack)同时送给上层模块。op_cmd_done 保持一个sys_clk 时钟宽度。
      • output op_cmd_done, // operation finished
  • 主体程序设计

该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b11),在SCL为高电平期间等待半个时钟周期再将SDA由低变高。在STOP之后,总线处于idle状态,即控制器和设备都不去控制SDA,SCL, SDA,SCL 都是处于高阻状态。

程序的流程图如下:

图2

  • 程序代码如下:

`timescale 1ns / 1ps

module i2c_stop(
   input            sys_clk,
   
   input      [1:0] op_cmd,
   input            op_cmd_en,
   output reg       op_cmd_done,
   
   input            clk_en,
   input            clk_en_half,
   
   output reg       sda_t,
   output           sda_o,
   input            sda_i,
   
   output reg       scl_t,
   output           scl_o,
   input            scl_i,
   
   input            rst
);

/*
op_cmd[1:0] : 00 send start + address + wr/rd (data_in)
               01 send data
               10 receive data (data_out)
               11 send stop flag
*/


assign scl_o = 1'b0;
assign sda_o = 1'b0;


reg [7:0] i2c_st;
reg [2:0] sft_count;
reg [7:0] i2c_wr_data;             //将需要写的数据存入本地寄存器中

always@(posedge sys_clk or posedge rst)
if(rst)
begin
    op_cmd_done <= 0;
    
    scl_t <= 1'b0;
    sda_t <= 1'b1;
    
    sft_count <= 7;
    i2c_wr_data <= 0;
    
    i2c_st <= 0;
end
else 
case(i2c_st)
0:
begin
    sft_count <= 7;
    op_cmd_done <= 1'b0;
    
    scl_t <= scl_i;
    sda_t <= sda_i;
    
    if({op_cmd_en,op_cmd[1:0]} == 3'b111)
        i2c_st <= 1;    // stop flag
end
1: //stop flag
begin
    sda_t <= 1'b0;
    
    if(clk_en)
    begin
        scl_t <= 1'b1;
        i2c_st <= 2;
    end
end
2:
begin
    if(clk_en)
    begin
        sda_t <= 1'b1;
        i2c_st <= 3;
    end
end
3:
begin
    if(clk_en)
    begin
        sda_t <= 1'b1;
        scl_t <= 1'b1;
        op_cmd_done <= 1'b1;
        i2c_st <= 0;
    end
end
default:i2c_st <= 0;
endcase




endmodule

 

  • iic_phy 模块代码
`timescale 1ns / 1ps


module iic_phy(
   input            sys_clk,
   
   input      [1:0] op_cmd,
   input            op_cmd_en,
   output reg       op_cmd_done,
   output reg       dev_ack,
   input            master_ack,
   input      [7:0] data_in,
   output     [7:0] data_out,
   
   input            clk_en,
   input            clk_en_half,
   
   output reg       sda_t,
   output           sda_o,
   input            sda_i,
   
   output reg       scl_t,
   output           scl_o,
   input            scl_i,
   
   input            rst
);

/*
op_cmd[1:0] : 00 send start + address + wr/rd (data_in)
               01 send data
               10 receive data (data_out)
               11 send stop flag
*/
wire dev_ack_addr;
wire dev_ack_wr;
always @(*)
begin
    case(op_cmd)
    2'b00: dev_ack = dev_ack_addr;
    2'b01: dev_ack = dev_ack_wr;
    default: ;
    endcase
end

wire op_cmd_done_addr;
wire op_cmd_done_wr;
wire op_cmd_done_read;
wire op_cmd_done_stop;
always @(*)
begin
    case(op_cmd)
    2'b00: op_cmd_done = op_cmd_done_addr;
    2'b01: op_cmd_done = op_cmd_done_wr;
    2'b10: op_cmd_done = op_cmd_done_read;
    2'b11: op_cmd_done = op_cmd_done_stop;
    endcase
end

wire sda_t_addr;
wire sda_t_wr;
wire sda_t_read;
wire sda_t_stop;

wire scl_t_addr;
wire scl_t_wr;
wire scl_t_read;
wire scl_t_stop;
always @(*)
begin
    case(op_cmd)
    2'b00: begin sda_t = sda_t_addr; scl_t = scl_t_addr; end
    2'b01: begin sda_t = sda_t_wr;   scl_t = scl_t_wr;   end
    2'b10: begin sda_t = sda_t_read; scl_t = scl_t_read; end
    2'b11: begin sda_t = sda_t_stop; scl_t = scl_t_stop; end
    endcase
end


i2c_addr  i2c_addr_inst
(
   .sys_clk     (sys_clk),

   .op_cmd      (op_cmd),
   .op_cmd_en   (op_cmd_en),
   .op_cmd_done (op_cmd_done_addr),
   .dev_ack     (dev_ack_addr),
   .data_in     (data_in),

   .clk_en      (clk_en),
   .clk_en_half (clk_en_half),

   .sda_t       (sda_t_addr),
   .sda_i       (sda_i),

   .scl_t       (scl_t_addr),
   .scl_i       (scl_i),

   .rst         (rst)
);

i2c_write  i2c_write_inst
(
   .sys_clk     (sys_clk),

   .op_cmd      (op_cmd),
   .op_cmd_en   (op_cmd_en),
   .op_cmd_done (op_cmd_done_wr),
   .dev_ack     (dev_ack_wr),
   .data_in     (data_in),

   .clk_en      (clk_en),
   .clk_en_half (clk_en_half),

   .sda_t       (sda_t_wr),
   .sda_i       (sda_i),

   .scl_t       (scl_t_wr),
   .scl_i       (scl_i),

   .rst         (rst)
);

i2c_read  i2c_read_inst
(
   .sys_clk     (sys_clk),

   .op_cmd      (op_cmd),
   .op_cmd_en   (op_cmd_en),
   .op_cmd_done (op_cmd_done_read),
   .master_ack  (master_ack),
   .data_out    (data_out),

   .clk_en      (clk_en),
   .clk_en_half (clk_en_half),

   .sda_t       (sda_t_read),
   .sda_i       (sda_i),

   .scl_t       (scl_t_read),
   .scl_i       (scl_i),

   .rst         (rst)
);

i2c_stop  i2c_stop_inst
(
   .sys_clk     (sys_clk),

   .op_cmd      (op_cmd),
   .op_cmd_en   (op_cmd_en),
   .op_cmd_done (op_cmd_done_stop),

   .clk_en      (clk_en),
   .clk_en_half (clk_en_half),

   .sda_t       (sda_t_stop),
   .sda_i       (sda_i),

   .scl_t       (scl_t_stop),
   .scl_i       (scl_i),

   .rst         (rst)
);

assign sda_o = 1'b0;
assign scl_o = 1'b0;

endmodule

 

  • 将设计的模块例化,并组装到 iic_engine的模块中,这样就可以使用SCl_en模块,充分利用以设计的成果,逐步组装成程序代码及接口如下:
`timescale 1ns / 1ps


module iic_engine #
(
    parameter DIV_ADDR = 32'hFFFF_0001,       
    parameter DEV_ADDR = 7'b101_0000,         
    parameter CLK_F = 100_000_000,            
    parameter SPEED = 100_000                 

)
(

    input         sys_clk,

    inout         SDA,
    inout         SCL,

    input [31:0]  div_addr,
    input [31:0]  div_in,
    input         div_en,
   
    input         div_rd,
    output [31:0] div_dout,
   
    input  [1:0] op_cmd,
    input        op_cmd_en,
    output       op_cmd_done,

    output       dev_ack,
    input        master_ack,
    input  [7:0] data_in,   
    output [7:0] data_out,
                  
    output       i2c_error,
    input        rst
);

wire  sda_i;
wire  sda_o; 
wire  sda_t;
wire  scl_o;
wire  scl_t;
wire  scl_i;

assign SDA = sda_t ? 1'bz: sda_o;
assign sda_i = SDA;

assign SCL = scl_t ? 1'bz: scl_o;
assign scl_i = SCL;


wire        clk_en;
wire        clk_en_half;

//=========================== i2c clock baudtick
scl_en #
(
   .DIV_ADDR       ( DIV_ADDR ),
   .CLK_F          ( CLK_F ),
   .SPEED          ( SPEED )
)
scl_en_inst
(
   .sys_clk        ( sys_clk ),
   
   .div_addr       ( div_addr ),
   .div_in         ( div_in ),
   .div_en         ( div_en ),
   .clk_en         ( clk_en ),
   .div_rd         ( div_rd ),
   .div_dout       ( div_dout ),
   .clk_en_half    ( clk_en_half ),
   .rst            ( rst )
);


//===============================iic phy 


iic_phy iic_phy_inst(
   .sys_clk       (sys_clk),
   
   .op_cmd        ( op_cmd ),
   .op_cmd_en     ( op_cmd_en ),
   .op_cmd_done   ( op_cmd_done ),
   .dev_ack       ( dev_ack ),
   .master_ack    ( master_ack ),
   .data_in       ( data_in ),
   .data_out      ( data_out ),
             
   .clk_en        ( clk_en ),
   .clk_en_half   ( clk_en_half ),            
   .sda_t         ( sda_t ),
   .sda_o         ( sda_o ),
   .sda_i         ( sda_i ),
                     
   .scl_t         ( scl_t ),
   .scl_o         ( scl_o ),
   .scl_i         ( scl_i ),
                     
   .rst           ( rst )
);

 
endmodule

 

  • 仿真程序设计:

`timescale 1ns / 1ps


module tb_dev_stop();

parameter PERIOD    = 10 ;
parameter FREQUANCY = 100000000; //100M
parameter SPEED     = 100000; // 100K
parameter SPEED1    = 400000;  //400K
parameter DEV_ADDR  = 7'b101_0001;

reg inclk;

always #( PERIOD / 2 ) inclk = ~inclk;  //133M
wire sys_clk = inclk;

reg rst;  //
reg  [ 31: 0 ] div_in;  //for baudrate register
wire [ 31: 0 ] div_addr = 32'hFFFF_0001;
reg            div_en = 0;

   
reg  [1:0] op_cmd = 0;
reg        op_cmd_en = 0;
wire       op_cmd_done;

wire       dev_ack;
reg        master_ack = 0;
reg  [7:0] data_in = 0;
wire [7:0] data_out;
  
reg        i2c_error = 0;

initial
begin
    rst = 1'b1;
    inclk = 0;
    #50 rst = 1'b0;
end

reg [7:0] dev_data = 0;
reg dev_data_valid = 0;

reg [3:0] st = 0;
reg [31:0] cnt = 0;
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
    master_ack <= 0;
    i2c_error <= 0;
    cnt <= 0;
    st <= 0;
    op_cmd_en <= 1'b0;
    op_cmd <= 0;
end
else
begin
    case ( st )
    0:
    begin
        master_ack <= 0;
        dev_data <= 0;
        dev_data_valid <= 0;
    
        i2c_error <= 0;
        op_cmd <= 2'b00;
        op_cmd_en <= 0;

        if(cnt == 5000)
        begin
            cnt <= 0;
            st <= 1;
        end
        else cnt <= cnt + 1;
    end
    1:
    begin
        data_in <= {DEV_ADDR, 1'b0};
        op_cmd <= 2'b00; // send device_addr + write
        op_cmd_en <= 1;

        st <= 2;
    end
    2:
    begin
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack == 1) 
            begin
                i2c_error <= 1;
                st <= 13;
            end
            else
            begin    
                data_in <= 8'h00; // write memory address 8'h00
                op_cmd <= 2'b01;
                op_cmd_en <= 1;
                
                cnt <= 0;
                st <= 3;
            end
        end
    end
    3:
    begin
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack == 1) 
            begin
                i2c_error <= 1;
                st <= 13;
            end
            else
            begin
                data_in <= data_in + 1; // wirte data to device
                op_cmd <= 2'b01;
                op_cmd_en <= 1;
                
                cnt <= cnt + 1;
                if(cnt == 7)
                    st <= 4;
            end
        end
    end
    4:
    begin
        cnt <= 0;
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack == 1) 
            begin
                i2c_error <= 1;
                st <= 13;
            end
            else
            begin
                op_cmd <= 2'b11; //send STOP command
                op_cmd_en <= 1;
                
                st <= 5;
            end
        end
    end      
    5:
    begin
        cnt <= 0;
        op_cmd_en <= 0;
        if(op_cmd_done)
            st <= 6;
    end    
    6: // delay 5ms according to EEPROM spec
    begin
        if(cnt == 5_000_00)
        begin
            cnt <= 0;
            st <= 7;
        end
        else cnt <= cnt + 1;
    end
    7:
    begin
        cnt <= 0;
        op_cmd_en <= 0;
                
        data_in <= {DEV_ADDR, 1'b0};
        op_cmd <= 2'b00; // send START + dev_addr + write
        op_cmd_en <= 1;
        
        st <= 8;
    end    
    8:
    begin
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack == 1) 
            begin
                i2c_error <= 1;
                st <= 13;
            end
            else
            begin
                data_in <= 8'h00;
                op_cmd <= 2'b01;  // send memory address 8'h00
                op_cmd_en <= 1;
                
                st <= 9;
            end
        end
    end
    9:
    begin
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack == 1) 
            begin
                i2c_error <= 1;
                st <= 13;
            end
            else
            begin
                data_in <= {DEV_ADDR, 1'b1};
                op_cmd <= 2'b00; // send RESTART + dev_addr + read
                op_cmd_en <= 1;
                
                st <= 10;
            end
        end
    end
    10:
    begin
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack == 1) 
            begin
                i2c_error <= 1;
                st <= 13;
            end
            else
            begin
                op_cmd <= 2'b10; // send read
                op_cmd_en <= 1;
                master_ack <= 0;
                
                cnt <= cnt + 1;
                st <= 11;
            end
        end
    end
    11:
    begin
        op_cmd_en <= 0;
        dev_data_valid <= 0;
        
        if(op_cmd_done)
        begin
            dev_data <= data_out;
            dev_data_valid <= 1;
            
            op_cmd <= 2'b10; //send read
            op_cmd_en <= 1;
            
            cnt <= cnt + 1;
            if(cnt == 7)
            begin
                master_ack <= 1; // send NACK for the last data
                st <= 12;
            end
        end
    end
    12:
    begin
        dev_data_valid <= 0;
        cnt <= 0;
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            dev_data <= data_out;
            dev_data_valid <= 1;

            st <= 13;
        end
    end
    13: // STOP command
    begin
        dev_data_valid <= 0;
        op_cmd <= 2'b11; // send STOP command
        op_cmd_en <= 1;
        st <= 14;
    end    
    14:
    begin
        cnt <= 0;
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            st <= 15;
        end
    end
    15: 
    begin
        if(cnt == 5000)
        begin
            st <= 15;
            $stop;
        end
        else cnt <= cnt + 1;
    end
    default:
        st <= 0;
    endcase
end


wire SDA;
wire SCL;

iic_engine
#(
    .CLK_F ( FREQUANCY ) ,   //default with 100Mhz
    .SPEED ( SPEED )         //default speed 100k
)
iic_engine_inst
(
    .sys_clk    ( sys_clk ),

    .div_addr   ( div_addr ),
    .div_in     ( div_in ),
    .div_en     ( div_en ),

    .op_cmd     ( op_cmd ),
    .op_cmd_en  ( op_cmd_en ),
    .op_cmd_done( op_cmd_done ),
    
    .dev_ack    ( dev_ack ),
    .master_ack ( master_ack ),

    .data_in    ( data_in ),
    .data_out   ( data_out ),

    .SDA        ( SDA ),
    .SCL        ( SCL ),

    .rst        ( rst )
);
   

PULLUP SDA_inst ( .O(SDA)  );
PULLUP SCL_inst ( .O(SCL)  );


AT24C02D  #
(.SLAVE_ADDRESS(DEV_ADDR))
AT24C02D_inst
(
    .SDA    (SDA),
    .SCL    (SCL),
    .WP     (0)
);


endmodule

 

  • 仿真结果

仿真波形如图3

图3

从图3可以看出:

1)start + dev_addr +wr。设备回ack

2)send memory addr, 设备会ack

3)restart + dev_addr +rd, 设备回ack

4)receive data, 控制器回ack

5) 。。。。

6)receive the last data ,控制器回nack

7 )send STOP command

在等待设备回ack期间,如果设备回nack, I2c总线发生错误(设备无应答)。这时,控制器需要停止总线(发送STOP),将SDA,SCL 线变为高阻。之后其他的I2c 命令才能正常执行。

 

Posted in FPGA, FPGA 教材教案, Verilog, 教材与教案, 文章

发表回复

相关链接