FPGA控制TDC7200时间间隔测量(二)

继续上文内容:

https://blog.csdn.net/qq_46284844/article/details/121716619?spm=1001.2014.3001.5501

上文介绍了TDC7200的一些基本信息,本文说一下使用FPGA来控制TDC7200的过程。

整体思路

TDC7200是使用SPI通信的,所以我们首先确定需要编写SPI通信协议,确定协议之后我们再根据数据手册的寄存器地址和相关的配置发送和接收数据。而对于FPGA的时序控制,我们还是一如既往的使用状态机来控制。

程序介绍

  1. 端口定义
    input   wire            sys_clk,
    input   wire            sys_rst_n,
    input   wire            tdc_interrupt,
    input   wire            miso,
    output  reg             mosi,
    output  reg             cs,
    output  reg             sck,
    output  reg             tdc_en,
    output  wire    [7:0]   data_out,
    output  reg             data_out_flag

如上所示,定义端口如下:
(1)时钟信号和复位信号;
(2)TDC中断输入端口用来响应测时完成信号;
(3)四个SPI口以及TDC7200的使能控制管脚;
(4)两个数据相关的端口,一个为数据,一个为数据有效性端口。

  1. 状态定义
//STATES DEFINE
parameter	DELY	        =   16'b0000_0000_0000_0000;

parameter 	INITIAL_IDLE 	=   16'b0000_0000_0000_0001;
parameter 	ENABLE_LOW   	=   16'b0000_0000_0000_0010;
parameter 	ENABLE_HIGH 	=   16'b0000_0000_0000_0100;

parameter 	CONFIG1_INIT	=   16'b0000_0000_0000_1000;
parameter 	CONFIG1_IN_RD	=   16'b0000_0000_0001_0000;

parameter	CONFIG2_INIT	=   16'b0000_0000_0010_0000;
parameter	CONFIG2_IN_RD	=   16'b0000_0000_0100_0000;

parameter 	INT_MASK_INIT   =   16'b0000_0000_1000_0000;
parameter 	INT_MASK_IN_RD  =   16'b0000_0001_0000_0000;

parameter 	CNTR_OVF_H   	=   16'b0000_0010_0000_0000;
parameter 	CNTR_OVF_H_RD 	=   16'b0000_0100_0000_0000;

parameter 	CNTR_OVF_L     	=   16'b0000_1000_0000_0000;
parameter 	CNTR_OVF_L_RD  	=   16'b0001_0000_0000_0000;

parameter	CONFIG1_START	=   16'b0010_0000_0000_0000;
parameter	CONFIG1_STA_RD	=   16'b0100_0000_0000_0000;

parameter 	WAITING_TDC     =   16'b1000_0000_0000_0000;
parameter 	READING_TDC     =   16'b1100_0000_0000_0000;
parameter 	WAITING_RESET   =   16'b1110_0000_0000_0000;

状态定义包括:
(1)初始状态;
(2)使能信号拉低状态,上电后先拉低使能信号1s用以复位TDC;
(3)使能信号拉高状态,正如上一篇文章所说,TDC使能信号拉高之后需要一定的时间稳定,这边拉高后5ms时间后到下一状态;
(4)之后是一系列初始化的状态,根据SPI数据流转的特点以及TDC7200手册上的说明,FPGA每次向TDC写数据之后必须读回相应数据长度的数据才能进行下一次的读写,所以每一条指令我用了两个状态表示,一写一读;
(5)初始化之后就是等待TDC测量完毕的的状态,当tdc_interrupt端口被拉低之后跳转到下一状态;
(6)读TDC状态;
(7)等待复位状态。

  1. 地址命令定义
//COMMAND DEFINE 
parameter   CONFIG1_COMMAND =   16'b0100_0000_1000_0000;
parameter   CONFIG1_READING =   16'b0000_0000_0000_0000;

parameter   CONFIG2_COMMAND =   16'b0100_0001_0000_0000;
parameter   CONFIG2_READING =   16'b0000_0001_0000_0000;

parameter   INTMASK_COMMAND =   16'b0100_0011_0000_0001;
parameter   INTMASK_READING =   16'b0000_0011_0000_0000;

parameter   CNTRO_H_COMMAND =   16'b0100_0100_1111_1111;
parameter   CNTRO_H_READING =   16'b0000_0100_0000_0000;

parameter   CNTRO_L_COMMAND =   16'b0100_0101_0000_0000;
parameter   CNTRO_L_READING =   16'b0000_0101_0000_0000;

parameter   CONFIG1_TO_STAR =   16'b0100_0000_1000_0001;

parameter   READING_TIME1   =   16'b1001_0000_0000_0000;
parameter   READING_CALI1   =   16'b1001_1011_0000_0000;
parameter   READING_CALI2   =   16'b1001_1100_0000_0000;

parameter   READING_SUPP    =   16'b0000_0000_0000_0000;

前面十一条指令分别对应初始化指令的各个状态,之后三条为读取测量完毕之后的数据时的指令,因为SPI的环回数据格式,所以我们再定义一条全0的指令用以在读取的时候发送。

下面介绍一下指令的组成
FPGA控制TDC7200时间间隔测量(二)_第1张图片
还是回到这幅图,从这幅图中看出数据发送为先地址在数据,上升沿TDC读入,下降沿TDC写出。所以我们的指令格式都得遵循地址在前,数据在后的格式。确定数据格式之后,我们根据数据手册中寄存器的地址确定6位地址信息,外加最高位自动增长位和次高位读写位组成8位地址+8位数据的格式,作为一帧数据发送(如下图所示)。
FPGA控制TDC7200时间间隔测量(二)_第2张图片

  1. SPI时钟确定
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        sck_cnt <= 2'd0;
    else  if( (state == CONFIG1_INIT && (cnt_byte == 8'd1 || cnt_byte == 8'd2)) ||
              (state == CONFIG1_IN_RD && (cnt_byte == 8'd6 || cnt_byte == 8'd7)) ||
              (state == CONFIG2_INIT && (cnt_byte == 8'd11 || cnt_byte == 8'd12)) ||
              (state == CONFIG2_IN_RD && (cnt_byte == 8'd16 || cnt_byte == 8'd17)) ||
              (state == INT_MASK_INIT && (cnt_byte == 8'd21 || cnt_byte == 8'd22)) ||
              (state == INT_MASK_IN_RD && (cnt_byte == 8'd26 || cnt_byte == 8'd27)) ||
              (state == CNTR_OVF_H && (cnt_byte == 8'd31 || cnt_byte == 8'd32)) ||
              (state == CNTR_OVF_H_RD && (cnt_byte == 8'd36 || cnt_byte == 8'd37)) ||
              (state == CNTR_OVF_L && (cnt_byte == 8'd41 || cnt_byte == 8'd42)) ||
              (state == CNTR_OVF_L_RD && (cnt_byte == 8'd46 || cnt_byte == 8'd47)) ||
              (state == CONFIG1_START && (cnt_byte == 8'd51 || cnt_byte == 8'd52)) ||
              (state == CONFIG1_STA_RD && (cnt_byte == 8'd56 || cnt_byte == 8'd57)) ||
              (state == READING_TDC && (cnt_byte == 8'd60 || cnt_byte == 8'd61 || 
                                        cnt_byte == 8'd62 || cnt_byte == 8'd63 ||
                                        cnt_byte == 8'd67 || cnt_byte == 8'd68 || 
                                        cnt_byte == 8'd69 || cnt_byte == 8'd70 ||
                                        cnt_byte == 8'd74 || cnt_byte == 8'd75 || 
                                        cnt_byte == 8'd76 || cnt_byte == 8'd77)) )
        sck_cnt <= sck_cnt + 1'd1;
    else
        sck_cnt <= sck_cnt;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        sck <= 1'b0;
    else if(sck_cnt == 2'd2)
        sck <= 1'b1;
    else if(sck_cnt == 2'd0)
        sck <= 1'b0;
    else
        sck <= sck;
end 

简单起见,我采用直接对50MHz的晶振时钟计数分频得到12.5MHz的SPI时钟,且未超出TDC7200手册中规定的始终上限频率。
这边一大段cnt_byte有关代码请看下一部分讲解。

  1. 字节数确定
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        clk_cnt <= 5'd0;
    else
        if(state == INITIAL_IDLE || state == ENABLE_LOW || state == ENABLE_HIGH || 
            state == WAITING_TDC || state == WAITING_RESET)
            clk_cnt <= 5'd0;
        else if(clk_cnt == 5'd31)
            clk_cnt <= 5'd0;
        else
            clk_cnt <= clk_cnt + 1'd1;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cnt_byte <= 8'd0;
    else
        if(cnt_byte == 8'd78 && clk_cnt == 5'd31)
            cnt_byte <= 8'd78;
        else if(clk_cnt == 5'd31)
            cnt_byte <= cnt_byte + 1'd1;
        else
            cnt_byte <= cnt_byte;
end 

正如上面所说的那样,最长的时间就是数据发送的过程,因为TDC的寄存器要么是8位要么是24位。如果我们使用16位作为一个位单元将会十分不方便,所以我们采用每8位作为一个位单元。所以50MHz的主频时钟,12.5MHz的SCK时钟,50 / 12.5 * 8 = 32个单元。至于这边为什么使用全都用32作为一个时钟计数周期,参考火哥的SPI代码时序控制,因为整个时序中最长的时间就是数据发送的过程,那么我们每次都使用这个时间段作时序延时(比如说上一篇文章说到的建立时间、保持时间、CS拉高时间等),这样一来就大大方便了我们编写时序以及省去了大篇幅的代码。

cnt_byte与每个状态对应的时序图以及计数关系表如下所示:

  1. 两段式状态机状态跳转

根据上图我们编写状态跳转代码如下

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n) begin
        state <= INITIAL_IDLE;
        tdc_en <= 1'b0;
    end 
    else begin
        case(state)
            INITIAL_IDLE:begin
                state <= ENABLE_LOW;
            end 
            ENABLE_LOW:begin
                tdc_en <= 1'b0;
                if(delay_cnt1 == DELAY_ONE_PART)//50_000_000
                    state <= ENABLE_HIGH;
                else
                    state <= ENABLE_LOW;
            end 
            ENABLE_HIGH:begin
                tdc_en <= 1'b1;
                if(delay_cnt2 == DELAY_TWO_PART)//250_000
                    state <= CONFIG1_INIT;
                else
                    state <= ENABLE_HIGH;
            end 
            CONFIG1_INIT:begin
                if(cnt_byte == 8'd3 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CONFIG1_INIT;
            end 
            DELY:begin
                if(cnt_byte == 8'd4 && clk_cnt == 5'd31)
                    state <= CONFIG1_IN_RD;
                else if(cnt_byte == 8'd9 && clk_cnt == 5'd31)
                    state <= CONFIG2_INIT;
                else if(cnt_byte == 8'd14 && clk_cnt == 5'd31)
                    state <= CONFIG2_IN_RD;
                else if(cnt_byte == 8'd19 && clk_cnt == 5'd31)
                    state <= INT_MASK_INIT;//INT_MASK_INIT
                else if(cnt_byte == 8'd24 && clk_cnt == 5'd31)
                    state <= INT_MASK_IN_RD;
                else if(cnt_byte == 8'd29 && clk_cnt == 5'd31)
                    state <= CNTR_OVF_H;
                else if(cnt_byte == 8'd34 && clk_cnt == 5'd31)
                    state <= CNTR_OVF_H_RD;
                else if(cnt_byte == 8'd39 && clk_cnt == 5'd31)
                    state <= CNTR_OVF_L;//WAITING_TDC
                else if(cnt_byte == 8'd44 && clk_cnt == 5'd31)
                    state <= CNTR_OVF_L_RD;
                else if(cnt_byte == 8'd49 && clk_cnt == 5'd31)
                    state <= CONFIG1_START;
                else if(cnt_byte == 8'd54 && clk_cnt == 5'd31)
                    state <= CONFIG1_STA_RD;
                else if(cnt_byte == 8'd65 && clk_cnt == 5'd31)
                    state <= READING_TDC;
                else if(cnt_byte == 8'd72 && clk_cnt == 5'd31)
                    state <= READING_TDC;
                else
                    state <= DELY;
            end 
            CONFIG1_IN_RD:begin
                if(cnt_byte == 8'd8 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CONFIG1_IN_RD;
            end 
            CONFIG2_INIT:begin
                if(cnt_byte == 8'd13 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CONFIG2_INIT;
            end 
            CONFIG2_IN_RD:begin
                if(cnt_byte == 8'd18 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CONFIG2_IN_RD;
            end
            INT_MASK_INIT:begin
                if(cnt_byte == 8'd23 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= INT_MASK_INIT;
            end 
            INT_MASK_IN_RD:begin
                if(cnt_byte == 8'd28 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= INT_MASK_IN_RD;
            end
            CNTR_OVF_H:begin
                if(cnt_byte == 8'd33 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CNTR_OVF_H;
            end 
            CNTR_OVF_H_RD:begin
                if(cnt_byte == 8'd38 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CNTR_OVF_H_RD;
            end 
            CNTR_OVF_L:begin
                if(cnt_byte == 8'd43 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CNTR_OVF_L;
            end 
            CNTR_OVF_L_RD:begin
                if(cnt_byte == 8'd48 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CNTR_OVF_L_RD;
            end 
            CONFIG1_START:begin
                if(cnt_byte == 8'd53 && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= CONFIG1_START;
            end 
            CONFIG1_STA_RD:begin
                if(cnt_byte == 8'd58 && clk_cnt == 5'd31)
                    state <= WAITING_TDC;
                else
                    state <= CONFIG1_STA_RD;
            end 
            WAITING_TDC:begin
                if(tdc_interrupt_flag)
                    state <= READING_TDC;
                else
                    state <= WAITING_TDC;
            end 
            READING_TDC:begin
                if(cnt_byte == 8'd78 && clk_cnt == 5'd31)
                    state <= WAITING_RESET;
                else if((cnt_byte == 8'd64 || cnt_byte == 8'd71) && clk_cnt == 5'd31)
                    state <= DELY;
                else
                    state <= READING_TDC;
            end 
            WAITING_RESET:begin
                state <= WAITING_RESET;
            end 
        endcase 
    end 
end     

初始化各个状态和DELY状态之间的跳转便不再多说了,我们看到WAITING_TDC状态下,若收到TDC测时完成的信号,便跳转到读TDC的状态READING_TDC,在这个状态下根据上一篇文章所述,我们需要读取三个寄存器TIME1、CALI1、CALI2所以有三个读取时序,这三个寄存器都是24位的,所以我们在将地址发送过去之后会有三个数据帧的读取(和之前每帧数据2个周期不同,这里会有4个周期),读取完三个寄存器数据之后计算即可得出时间间隔。

  1. 数据发送
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        bit_cnt <= 4'd0;                       //because it is mode 0
    else
        case(cnt_byte)
            8'd1,8'd2,8'd6,8'd7,8'd11,8'd12,8'd16,8'd17,8'd21,8'd22,8'd26,8'd27,
            8'd31,8'd32,8'd36,8'd37,8'd41,8'd42,8'd46,8'd47,8'd51,8'd52,8'd56,8'd57,
            8'd60,8'd61,8'd62,8'd63,8'd67,8'd68,8'd69,8'd70,8'd74,8'd75,8'd76,8'd77:begin
                if(sck_cnt == 2'd3)             //data updata
					bit_cnt <= bit_cnt + 1'd1;
				else
					bit_cnt <= bit_cnt;
            end 
            default:bit_cnt <= 4'd0;
        endcase 
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		mosi <= 1'b0;
    else begin
        case(cnt_byte)
            8'd1, 8'd2:     mosi <= CONFIG1_COMMAND[4'd15 - bit_cnt];
            8'd6, 8'd7:     mosi <= CONFIG1_READING[4'd15 - bit_cnt];
            8'd11, 8'd12:   mosi <= CONFIG2_COMMAND[4'd15 - bit_cnt];
            8'd16, 8'd17:   mosi <= CONFIG2_READING[4'd15 - bit_cnt];
            8'd21, 8'd22:   mosi <= INTMASK_COMMAND[4'd15 - bit_cnt];
            8'd26, 8'd27:   mosi <= INTMASK_READING[4'd15 - bit_cnt];
            8'd31, 8'd32:   mosi <= CNTRO_H_COMMAND[4'd15 - bit_cnt];
            8'd36, 8'd37:   mosi <= CNTRO_H_READING[4'd15 - bit_cnt];
            8'd41, 8'd42:   mosi <= CNTRO_L_COMMAND[4'd15 - bit_cnt];
            8'd46, 8'd47:   mosi <= CNTRO_L_READING[4'd15 - bit_cnt];
            8'd51, 8'd52:   mosi <= CONFIG1_TO_STAR[4'd15 - bit_cnt];
            8'd56, 8'd57:   mosi <= CONFIG1_READING[4'd15 - bit_cnt];
            8'd60, 8'd61:   mosi <= READING_TIME1[4'd15 - bit_cnt];
            8'd62, 8'd63:   mosi <= READING_SUPP[4'd15 - bit_cnt];
            8'd67, 8'd68:   mosi <= READING_CALI1[4'd15 - bit_cnt];
            8'd69, 8'd70:   mosi <= READING_SUPP[4'd15 - bit_cnt];
            8'd74, 8'd75:   mosi <= READING_CALI2[4'd15 - bit_cnt];
            8'd76, 8'd77:   mosi <= READING_SUPP[4'd15 - bit_cnt];
            default:        mosi <= 1'b0;
        endcase 
    end 
end 

如上述代码所示,我们根据当前周期可以得到需要发送的数据,再根据高位在前的原则把数据发送出去即可。

  1. 数据接收与传出
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        miso_flag <= 1'b0;
    else if(sck_cnt == 2'd1 && (cnt_byte == 8'd7 || cnt_byte == 8'd17 || cnt_byte == 8'd27 || 
                                cnt_byte == 8'd37 || cnt_byte == 8'd47 || cnt_byte == 8'd57 ||
                                cnt_byte == 8'd61 || cnt_byte == 8'd62 ||
                                cnt_byte == 8'd63 || cnt_byte == 8'd68 ||
                                cnt_byte == 8'd69 || cnt_byte == 8'd70 ||
                                cnt_byte == 8'd75 || cnt_byte == 8'd76 || 
                                cnt_byte == 8'd77))
        miso_flag <= 1'b1;
    else
        miso_flag <= 1'b0;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        data <= 8'd1;
    else if(miso_flag == 1'b1)
        data <= {data[6:0],miso};
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        data_ok_flag <= 1'b0;
    else if((cnt_byte == 8'd7 || cnt_byte == 8'd17 || 
             cnt_byte == 8'd27 || cnt_byte == 8'd37 || 
             cnt_byte == 8'd47 || cnt_byte == 8'd57 ||
             cnt_byte == 8'd61 || cnt_byte == 8'd63 || 
             cnt_byte == 8'd68 || cnt_byte == 8'd70 ||
             cnt_byte == 8'd75 || cnt_byte == 8'd77) && (bit_cnt == 4'd15) && (miso_flag == 1'b1))
        data_ok_flag <= 1'b1;
    else if((cnt_byte == 8'd62 || 
             cnt_byte == 8'd69 || 
             cnt_byte == 8'd76) && bit_cnt == 4'd7 && miso_flag == 1'b1)
        data_ok_flag <= 1'b1;
    else
        data_ok_flag <= 1'b0;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        data_out_flag <= 1'b0;
    else
        data_out_flag <= data_ok_flag;
end 

assign data_out = data_ok_flag ? data : data_out;

因为在SCK下降沿数据被读出,跟警察前面SCK控制时序,当SCK_CNT为0时SCK被拉低,所以我们滞后一个时钟周期将数据读出,也就是通过位拼接实现移位,从而将数据读出,当8位也就是1帧数据被读出之后我们即可得到一次数据,将其传出。

  1. 脉冲模拟
    将程序下载到板子上之前先用modelsim仿真一下,我们在初始化完成之后产生START和STOP脉冲信号,测试FPGA控制TDC的时序,编写脉冲程序以及test bench如下:
module pulse_sim(
    input   wire            sys_clk,
    input   wire            sys_rst_n,
    
    input   wire    [7:0]   data_in,
    output  reg             start,
    output  reg             stop,
    output  reg             interrupt
);

reg     start_flag;
reg     [25:0]  delay1_reg;
reg     interrupt_reg;

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        start_flag <= 1'b0;
    else if(data_in == 8'h81)
        start_flag <= 1'b1;
    else
        start_flag <= start_flag;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        delay1_reg <= 26'd0;
    else if(delay1_reg == 26'd70) //50_000_020
        delay1_reg <= delay1_reg;
    else if(start_flag)
        delay1_reg <= delay1_reg + 1'd1;
    else
        delay1_reg <= 26'd0;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        start <= 1'b0;
        stop <= 1'b0;
    end 
    else begin
        if(delay1_reg == 26'd50) //50_000_000
            start <= 1'b1;
        else if(delay1_reg == 26'd62) //50_000_012
            stop <= 1'b1;
        else begin
            start <= start;
            stop <= stop;
        end 
    end 
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        interrupt_reg <= 1'b1;
    else if(stop)
        interrupt_reg <= 1'b0;
    else
        interrupt_reg <= interrupt_reg;
end 

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        interrupt <= 1'b1;
    else
        interrupt <= interrupt_reg;
end 

endmodule 
`timescale 1ns/1ns
module tb_tdc_ctrl();

reg sys_clk;
reg sys_rst_n;
reg miso;

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        miso <= 1'b0;
    else
        miso <= !miso;
end 

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    #20
    sys_rst_n <= 1'b1;
end 

always #10 sys_clk = !sys_clk;

wire    start;
wire    stop;
wire    mosi;
wire    cs;
wire    sck;
wire    tdc_en;
wire 	[15:0]   state = u_tdc_hy.u_tdc_ctrl.state;
wire 	[7:0]   cnt_byte = u_tdc_hy.u_tdc_ctrl.cnt_byte;
wire  	[1:0]  sck_cnt = u_tdc_hy.u_tdc_ctrl.sck_cnt;
wire    miso_flag = u_tdc_hy.u_tdc_ctrl.miso_flag;
wire    data_ok_flag = u_tdc_hy.u_tdc_ctrl.data_ok_flag;
wire  	[3:0]  bit_cnt = u_tdc_hy.u_tdc_ctrl.bit_cnt;
wire   [7:0]  data = u_tdc_hy.u_tdc_ctrl.data;

tdc_hy u_tdc_hy(
    .sys_clk        (sys_clk),
    .sys_rst_n      (sys_rst_n),
    .miso           (miso),
    .mosi           (mosi),
    .cs             (cs),
    .sck            (sck),
    .tdc_en         (tdc_en),
    
    .start          (start),
    .stop           (stop)
);

endmodule 

仿真现象

FPGA控制TDC7200时间间隔测量(二)_第3张图片
从使能信号被拉下之后的初始化,CS信号、SCK信号以及MOSI都正常。
FPGA控制TDC7200时间间隔测量(二)_第4张图片
START和STOP信号产生之后我们模拟了一个INTURRUPT信号,之后就是三个24bit的读取,仿真通过,接下来我们上板子来测试一下。

实际测试

由于屏幕显示较为复杂,暂时先不写了,就先用SignalTap来显示一下结果。然后由于身边暂时没有时间间隔产生仪器,所以就先用FPGA自身产生脉冲来作为被测脉冲了。START和STOP间隔设为240ns(12个时钟周期)。
FPGA控制TDC7200时间间隔测量(二)_第5张图片
如上图所示为读到的数据,根据上篇文章提到的公式计算:
calCount = (cali2_data - cali1_data) / (2 - 1);
normLSB = CLOCKperiod / calCount;
TOP = TIME * normLSB;
所以有calCount = 289,236,normLSB = (1 / 16M) / 289,236, TOP = 1110528 * normLSB = 0.23997us = 239.97ns。
误差:abs(239.97 - 240.00) / 240 = 0.125‰;

更换时间间隔为200ns,再次测得数据如下所示:
FPGA控制TDC7200时间间隔测量(二)_第6张图片
同样的计算方法:
calCount = (cali2_data - cali1_data) / (2 - 1);
normLSB = CLOCKperiod / calCount;
TOP = TIME * normLSB;
有calCount = 289,237,normLSB = (1 / 16M) / 289,237, TOP = 926720 * normLSB = 0.20025us = 200.25ns。
误差:abs(200.25 -200.00) / 200 = 1.25‰;

误差分析

(1)逻辑门时间可能会有延迟;
(2)TDC晶振本身精度有限;
(3)没有差分输入+杜邦线传输时钟脉冲导致信号走时不等。

写在最后

附上本文工程代码,本文采用Altera的CYCLONE4系列EP4CE22作为控制器,编译环境为Quartus II 13.1。
链接:https://pan.baidu.com/s/1VQ-v_mxFfIkhN9Qj9XtEBQ
提取码:dpwe

你可能感兴趣的:(FPGA应用,fpga开发)