Menu Close

I2C 主设备分频器设计

为了适应不同的速率,如100K,400K,1M,3.4M的收发速度,同时又能适应不同的系统时钟频率,因此需要采用配置寄存器以及参数化设计相配合,做到适用性广,方便集成到CPU,DSP,HDMI 等智能器件及接口中。该部分内容与UART的波特率发生器的设计非常类似,可以参照该部分内容进行设计与理解,后面附有I2C时钟代码。

1.I2C 的时钟频率分频器设计

I2C有四种速度匹配,分别为100K,400K,1M,3.4M四种,由于是同步时钟,从机的位捕获直接使用主机时钟,因此这一点与UART等类型的异步通信有较大区别。首先I2C的从设备速度等级表示从机的在速度方面的解码能力,如速度为100K,是指0-100K的速度范围内都可以运行(这一点与SMbus不同)。其次从机直接使用主机时钟,因此从机对时钟的精确度要求不高,如主机设置100K,实际运行时由于时钟晶振误差,分频系数不准确(非整数倍分频)等带来的误差影响不大。

  • 设计要求
    • 参数化设计,假设时钟为可以提供133MHz,100MHz,50MHz,25MHz四种,速度满足3.4M,1M,400K, 100K。
    • 可编程,虽然参数化设计解决了大部分问题,但依然无法满足众多IC的匹配的需求,如某MCU的主时钟66MHz,或80MHz等,这些都不在上面提供的参数化范围之内。因此提供一套可编程接口接波特率寄存器,可以由应用程序计算分频系数并写入分频寄存器中。还有参数化的设计一旦设计综合成硬件之后便不能修改,可编程接口提供好了在线修改的可能。
    • 输出为SCL(I2C时钟)两倍频的系统主时钟脉冲信号。如图2,用于在其它模块中生成SCL时钟。

 

图1

  • 设计代码:
`timescale 1ns / 1ps
module SCl_en
#(
    parameter CLK_F = 100_000_000 ,   //默认的输入频率值,可以由参数传递修改
    parameter SPEED = 100_000         //默认的通信速率
)
(
    input  sys_clk,

    input  [ 31: 0 ] div_addr,
    input  [ 31: 0 ] div_in,
    input  div_en,

    output reg clk_en,
    output reg clk_en_half,
    input  rst
);

localparam DIV_COE = CLK_F / ( SPEED * 2 ) - 1;   //计算分频系数

reg [ 31: 0 ] div_f_r;
reg [ 31: 0 ] count_r;
reg rst_r;         //可编程写时,复位分频计数程序

//下面程序实现在特定地址选通的情况下,由wr_en将输入分频值div_in写入到分频寄存器div_f_r中,并提供复位信号。
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
    div_f_r <= DIV_COE;
    rst_r <= 1'b1;
end
else
begin
    rst_r <= 1'b0;

    if ( div_en && ( div_addr == 32'hFFFF_0001 ) )
    begin
        div_f_r <= div_in;
        rst_r <= 1'b1;
    end
end


//分频计数程序,注意每次分频寄存器div_f_r的值修改时,应复位计数器,即从0开始计数
always@( posedge sys_clk or posedge rst_r )
if ( rst_r )
begin
    count_r <= 0;
    clk_en <= 0;
    clk_en_half <= 0;
end
else
begin
    clk_en <= 1'b0;
    clk_en_half <= 0;

    if ( count_r == div_f_r[31:1] )
        clk_en_half <= 1;
        
    if ( count_r == div_f_r )
    begin
        count_r <= 0;
        clk_en <= 1'b1;
    end
    else
        count_r <= count_r + 1;
end


endmodule

 

上面的程序分为两个部分,第一部分为写分频寄存器,在复位状态下由参数传递提供默认值(DIV_COE),在没有写信号的情况下div_f_r将会一直保持该值。在地址选通,写使能wr_en的驱动下将由应用程序提供的div_in的值写入到分频寄存器div_f_r中。并为后面的分频计数程序产生复位信号rst_r。rst_r的作用防止计数器count_r在计数的某个值后,突然写入一个比count_r小的值,使得计数32位计数器不能及时回归(当然也可以用if(count_r>=div_f_r)替代if(count_r==div_f_r)实现)。

always@( posedge sys_clk or posedge rst )
if ( rst )
begin
    div_f_r <= DIV_COE;
    rst_r <= 1'b1;
end
else
begin
    rst_r <= 1'b0;

    if ( wr_en && ( div_addr == 32'hFFFF_0001 ) )
    begin
        div_f_r <= div_in;
        rst_r <= 1'b1;
    end
end

 

第二部分程序提供分频的作用,在分频寄存器的值被修改时,分频程序复位,计数器从0开始计数。clk_en将作为时钟使能使用,每个SCL周期对应两个clk_en时钟脉冲。clk_en_half 作为辅助时钟脉冲, 主要是处理I2C START,STOP, 以及主控制发送数据使能等。 每个SCL对应4个clk_en_half 时钟脉冲。

//分频计数程序,注意每次分频寄存器div_f_r的值修改时,应复位计数器,即从0开始计数
always@( posedge sys_clk or posedge rst_r )
if ( rst_r )
begin
    count_r <= 0;
    clk_en <= 0;
    clk_en_half <= 0;
end
else
begin
    clk_en <= 1'b0;
    clk_en_half <= 0;

    if ( count_r == div_f_r[31:1] )
        clk_en_half <= 1;
        
    if ( count_r == div_f_r )
    begin
        count_r <= 0;
        clk_en <= 1'b1;
    end
    else
        count_r <= count_r + 1;
end

 

由于分频程序是整体I2C程序实现的一个部分,因此由I2C_engine.v程序实现组装,后续课程将会详细介绍I2C_engine的原理。

module I2C_engine
#
(
    parameter CLK_F = 100000000 ,
    parameter SPEED = 100000
)
(
    input rst,
    input inclk,

    input wr_en,
    input [ 31: 0 ] div_addr,
    input [ 31: 0 ] div_in,

    output clk_en
);


SCl_en
#(
    .CLK_F ( CLK_F ) ,
    .SPEED ( SPEED )
)
SCl_en_inst
(
    .rst      ( rst ),
    .sys_clk  ( inclk ),

    .wr_en    ( wr_en ),
    .div_addr ( div_addr ),
    .div_in   ( div_in ),

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


endmodule

2. 测试程序:

本测试程序为了体现测试的通用性,尽量模拟应用程序在时钟频率,通信速率等方面的需求进行多样化测试,在tb_clk_en.v中设置了4种频率,4种速率。通过延时提供了各种频率和速率组合进行测试,同时应注意时钟切换时的异步时钟域的处理。在MCU,DSP等嵌入式处理中,该部分程序可由软件实现。

`timescale   1 ns / 1ps
module tb_clk_en ( );

parameter PERIOD = 10 ;   //基本周期

//声明4种频率参数,用于计算分频系数
parameter FREQUANCY0 = 133000000; //133M
parameter FREQUANCY1 = 100000000; //100M
parameter FREQUANCY2 = 50000000;  //50M
parameter FREQUANCY3 = 25000000;  //25M

//声明4种速率用于计算分频系数
parameter SPEED0 = 100000;  // 100K
parameter SPEED1 = 400000;  // 400K
parameter SPEED2 = 1000000; // 1M
parameter SPEED3 = 3400000; // 3.4M

reg inclk0, inclk1, inclk2, inclk3;

//产生4个不同频率的时钟
always #( PERIOD / 3 ) inclk0 = ~inclk0;  //133M
always #( PERIOD / 2 ) inclk1 = ~inclk1;  //100M
always #( PERIOD ) inclk2 = ~inclk2;    //50M
always #( PERIOD * 2 ) inclk3 = ~inclk3;    //25M

reg rst;  //

wire clk_en;

reg wr_en;   //write enable signal ,synchronized with sys_clk;
reg [ 31: 0 ] div_in;  //for baudrate register

wire [ 31: 0 ] div_addr = 32'hFFFF_0001;   //分频寄存器对应的地址
reg [ 19: 0 ] delay;


//延时后的四种状态,用于不同频率的时钟选择。
reg [ 1: 0 ] dly_done, dly_done_r, dly_done_rr;
reg [ 3: 0 ] st;

//频率选择
wire sys_clk =  ( dly_done == 0 ? inclk0 :
                ( dly_done == 1 ? inclk1 :
                ( dly_done == 2 ? inclk2 : inclk3 ) ) );

initial
begin
    rst = 1'b1;
    inclk0 = 0;
    inclk1 = 0;
    inclk2 = 0;
    inclk3 = 0;
    #50 rst = 1'b0;
end

always@( posedge inclk0 or posedge rst )
if ( rst )
begin
    delay <= 0;
    dly_done <= 0;
end
else
begin
    if ( delay == 299999 )
    begin
        dly_done <= dly_done + 1'b1;
        delay <= 0;
    end
    else
    begin
        delay <= delay + 1;
    end
end

//实现异步时钟处理,消除亚稳态
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
    dly_done_r <= 0;
    dly_done_rr <= 0;
end
else
begin
    dly_done_r <= dly_done;
    dly_done_rr <= dly_done_r;
end

//实现不同频率,速率的组合写入到分频寄存器中
always@( posedge sys_clk or posedge rst )
if ( rst )
begin
    st <= 0;
    div_in <= 0;
    wr_en <= 1'b0;
end
else
begin
    case ( st )
    0:
    begin
        div_in <= ( FREQUANCY0 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 0 )
        begin
            wr_en <= 1'b1;
            st <= 1;
        end
    end
    1:
    begin
        div_in <= ( FREQUANCY1 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 1 )
        begin
            wr_en <= 1'b1;
            st <= 2;
        end
    end
    2:
    begin
        div_in <= ( FREQUANCY2 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 2 )
        begin
            wr_en <= 1'b1;
            st <= 3;
        end
    end
    3:
    begin
        div_in <= ( FREQUANCY3 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 3 )
        begin
            wr_en <= 1'b1;
            st <= 4;
        end
    end
    4:
    begin
        div_in <= ( FREQUANCY1 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 0 )
        begin
            wr_en <= 1'b1;
            st <= 5;
        end
    end
    5:
    begin
        div_in <= ( FREQUANCY1 / SPEED1 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 1 )
        begin
            wr_en <= 1'b1;
            st <= 6;
        end
    end
    6:
    begin
        div_in <= ( FREQUANCY1 / SPEED2 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 2 )
        begin
            wr_en <= 1'b1;
            st <= 7;
        end
    end
    7:
    begin
        div_in <= ( FREQUANCY1 / SPEED3 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 3 )
        begin
            wr_en <= 1'b1;
            st <= 8;
        end
    end
    8:
    begin
        div_in <= ( FREQUANCY2 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 0 )
        begin
            wr_en <= 1'b1;
            st <= 9;
        end
    end
    9:
    begin
        div_in <= ( FREQUANCY2 / SPEED1 * 2 ) - 1;
        wr_en <= 0;

        if ( dly_done_rr == 1 )
        begin
            wr_en <= 1'b1;
            st <= 10;
        end
    end
    10:
    begin
        div_in <= ( FREQUANCY2 / SPEED2 * 2 ) - 1;
        wr_en <= 1'b0;
        
        if ( dly_done_rr == 2 )
        begin
            wr_en <= 1'b1;
            st <= 11;
        end
    end
    11:
    begin
        div_in <= ( FREQUANCY2 / SPEED3 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 3 )
        begin
            wr_en <= 1'b1;
            st <= 12;
        end
    end
    12:
    begin
        div_in <= ( FREQUANCY3 / SPEED0 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 0 )
        begin
            wr_en <= 1'b1;
            st <= 13;
        end
    end
    13:
    begin
        div_in <= ( FREQUANCY3 / SPEED1 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 1 )
        begin
            wr_en <= 1'b1;
            st <= 14;
        end
    end
    14:
    begin
        div_in <= ( FREQUANCY3 / SPEED2 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 2 )
        begin
            wr_en <= 1'b1;
            st <= 15;
        end
    end
    15:
    begin
        div_in <= ( FREQUANCY3 / SPEED3 * 2 ) - 1;
        wr_en <= 1'b0;

        if ( dly_done_rr == 3 )
        begin
            wr_en <= 1'b1;
            st <= 0;
        end
    end
    default:
        st <= 0;
    endcase
end

//I2C_engine.v 例化
I2C_engine
#(
    .CLK_F( FREQUANCY3 ) ,       //default with 100Mhz
    .SPEED( SPEED3 )             //default speed 100k
)
I2C_engine_inst
(
    .rst      ( rst ),
    .inclk    ( sys_clk ),

    .wr_en    ( wr_en ),
    .div_addr ( div_addr ),
    .div_in   ( div_in ),

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


endmodule

 

仿真波形如下:

图2

从图2中可以看出,写入不同的分频系数值,clk_en的输出的频率随之变化,也可以追踪到SCL_en.v内部观察rst_r,计数,分频系数的写入等,由于上面已做了较详细的分析,这里不再累述。

 

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

2 Comments

  1. 李红梅

    老师,如果clk_en需要在SCL半个时钟的时候输出一个脉冲,1.设计代码 中的 DIV_COE = (CLK_F/SPEED) * 2 -1 括号的位置我算出来的结果需要不对,需要改为下面这个:DIV_COE = CLK_F / (SPEED * 2) -1,我理解的对么?

发表回复

相关链接