Menu Close

I2C 主设备PHY层启动及读写控制Verilog程序设计

在I2C发起读/写过程之前,主设备会先发起I2C的启动(START), 从设备的地址匹配(从设备地址写),I2C的读/写控制以及从设备应答检测的等一系列过程。由于无论是I2C读还是I2C写,该部分的操作过程都是必须的,因此把该部分内容单独作为一个模块进行设计,便于代码的重用。时序如图1:

图1

1.I2C启动部分主设备程序设计

  • 接口要求:

    • 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
      • input rst,
      • input sys_clk,
      • input clk_en, clk_en_half,
    • 从设备地址、启动信号、读/写标志(即该段程序之后的I2C 总线行为
      • input [7:0] data_in,  // {dev_addr[6:0], 1’b0/1’b1}
      • input [1:0] op_cmd, // 2’b00 means START + dev_addr + R/W
      • 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
      • output reg dev_ack//从设备是否有应答
  • 总体接口如下:

module i2c_addr(

input sys_clk,

input [1:0] op_cmd,
input op_cmd_en,
output reg op_cmd_done,

output reg dev_ack,
input [7:0] data_in,

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

);

  • 主体程序设计

该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b00),对从设备的地址与读写控制信号移位进行串行化,等待从设备应答。串行化期间对SCL,SDA的要求是,在SCL为高电平期间SDA保持不变(start除外),SDA的任何变化都在SCL为低电平的时段发生。程序的流程图如下:

图2

  • 程序代码如下:

`timescale 1ns / 1ps



module i2c_addr(
   input            sys_clk,
   
   input      [1:0] op_cmd,
   input            op_cmd_en,
   output reg       op_cmd_done,

   output reg       dev_ack,
   input      [7:0] data_in,
   
   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;
    dev_ack <= 0;
    
    scl_t <= 1'b1;
    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;
    
    dev_ack <= 0;
    scl_t <= scl_i;
    sda_t <= sda_i;
    
    i2c_wr_data <= data_in;
    
    if({op_cmd_en,op_cmd[1:0]} == 3'b100)
        i2c_st <= 1;     // start flag
end
1:      //start flag
begin
    if(clk_en)
    begin
        scl_t <= 1'b1;
        sda_t <= 1'b1;
        i2c_st <= 2;
    end
end
2:
begin
    scl_t <= 1'b1;
    
    if(clk_en)
    begin
        sda_t <= 1'b0;
        i2c_st <= 3;
    end
end
3:
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        i2c_st <= 4;
    end
end

4:  //write 
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        i2c_st <= 5;
    end
end
5:
begin
    if(clk_en_half)
       sda_t <= i2c_wr_data[sft_count];
       
    if(clk_en)
    begin
        scl_t <= 1'b1;
        sft_count <= sft_count - 1;
        i2c_st <= 4;
        
        if( sft_count == 0 )
            i2c_st <= 6;
    end
end
6:
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        i2c_st <= 7;
    end
end
7:     //read ack/nack from device
begin
    if( clk_en_half )
        sda_t <= 1'b1;
        
    if(clk_en)
    begin
        scl_t <= 1'b1;
        dev_ack <= sda_i;
        i2c_st <= 8; 
    end
end
8:                           
begin
    if(clk_en)
    begin
        scl_t <= 1'b0;
        op_cmd_done <= 1'b1;
        i2c_st <= 9;
    end
end
9:
begin
    op_cmd_done <= 1'b0;
    if(clk_en)
    begin
        scl_t <= 1'b1;
        i2c_st <= 0;
    end
end

default:i2c_st <= 0;
endcase




endmodulefsfsf
  • 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)
);

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_addr();

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 [3:0] st = 0;
reg [31:0] cnt = 0;
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
    i2c_error <= 0;
    cnt <= 0;
    st <= 0;
    op_cmd_en <= 1'b0;
    op_cmd <= 0;
end
else
begin
    case ( st )
    0:
    begin
        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;
        op_cmd_en <= 1;

        st <= 2;
    end
    2:
    begin
        op_cmd_en <= 0;
        if(op_cmd_done)
        begin
            if(dev_ack) 
                i2c_error <= 1;
                
            st <= 3;
        end
    end
    3:
    begin
        st <= 3;
    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

 

仿真程序中使用at24c02D IP ,这个IP 是由厂家提供的,这里我们只是使用这个I2C EEPROM ip 核。 IP核具体信息,可以参考文章https://www.microchip.com/en-us/product/AT24C02D

图3

注意,厂家提供的verilog IP ,目前只能在vivado下或者是questaSim 下仿真。 在quartus下或者在modelsim,questaSim_starter下不能仿真。(有些仿真语法不支持)。

在仿真工程中,我们主要学习三态门如何在仿真程序中仿真。

  • 仿真结果

仿真波形如图4

图4

从图4可以看出在写阶段串行了1010_001(地址)及0(写控制)。24c02 iic设备应答。在I2C PHY层 Verilog程序结构化设计中介绍过op_cmd 定义:op_cmd[1:0] = 2’b00;代表start + write + dev_ack。 op_cmd[1:0] 和op_cmd_en 同时由仿真程序发出,当iic_phy完成相关操作后,返回op_cmd_done 信号, 代表当前操作命令(op_cmd)完成。

 

Posted in FPGA, FPGA 教材教案, IC, Quartus II, Verilog, Verilog, 开发语言, 教材与教案, 文章, 编程语言

发表回复

相关链接