FPGA控制TDC-GPX2时间间隔测量(二)

续接上文,介绍完TDC-GPX2的芯片详情之后,本文介绍一下如何使用FPGA控制TDC-GPX2进行时间间隔测量。

硬件条件

本设计采用赛灵思的XC7A35T作为主控FPGA,校标方面,由于身边暂时拿不到专业的时间测量仪器,所以就采用FPGA内部自己产生STOP脉冲,脉冲数量可控,且时间间隔准确。下图为硬件连接图:FPGA输出CLK、STOP1(连续产生两个脉冲)、STOP2(实际上没用到)给TDC-GPX2进行时间间隔测量。
在这里插入图片描述

软件介绍

首先放上本设计的schematic如下所示:
在这里插入图片描述
图片可能比较模糊,主要分为:按键输入消抖、STOP脉冲生成、TDC控制、DCM时钟管理单元以及ILA逻辑分析仪。案件主要是两个作用,第一生成脉冲,第二启动测量;DCM用来提供5M的参考时钟,接下来着重介绍一下TDC控制模块。

TDC控制模块

首先是模块得输入以及输出接口:输入的包括寄存器配置参数以及TDC芯片的各个控制、数据引脚。

module gpx2_working#(
    parameter PIN_ENABLE0_REGISTER  = 8'b0001_0001,     //ADDR0
    parameter HIT_ENABLE_REGISTER   = 8'b0000_0001,     //ADDR1
    parameter DATA_OUT_REGISTER     = 8'b0000_0000,     //ADDR2
    parameter REFCLK_DIVISION_LOW   = 8'b0100_0000,     //ADDR3
    parameter REFCLK_DIVISION_MID   = 8'b0000_1101,     //ADDR4
    parameter REFCLK_DIVISION_HIGH  = 8'b0000_0011,     //ADDR5
    parameter CONTENT1_REGISTER     = 8'b1100_0000,     //ADDR6
    parameter PIN_ENABLE1_REGISTER  = 8'b0100_0011,     //ADDR7
    parameter FIX0_REGISTER         = 8'b1010_0001,     //ADDR8
    parameter FIX1_REGISTER         = 8'b0001_0011,     //ADDR9
    parameter FIX2_REGISTER         = 8'b0000_0000,     //ADDR10
    parameter FIX3_REGISTER         = 8'b0000_1010,     //ADDR11
    parameter FIX4_REGISTER         = 8'b1100_1100,     //ADDR12
    parameter FIX5_REGISTER         = 8'b1100_1100,     //ADDR13
    parameter FIX6_REGISTER         = 8'b1111_0001,     //ADDR14
    parameter FIX7_REGISTER         = 8'b0111_1101,     //ADDR15
    parameter CMOS_INPUT_REGISTER   = 8'b0000_0100      //ADDR16
)(
    input   wire                sys_clk,
    input   wire                sys_rst,
    
    input   wire                start_measure,
    
    input   wire                tdc_interrupt,
    input   wire                tdc_miso,
    output  wire                tdc_sck,
    output  wire                tdc_rstidx,
    output  wire    [47:0]      tdc_ch1_result0_out,
    output  wire    [47:0]      tdc_ch1_result1_out,
    output  reg                 tdc_mosi,
    output  reg                 tdc_ssn,
    
    output  wire    [135:0]     config_data_check_out,
    output  reg     [3:0]       tdc_work_state,
    output  wire    [15:0]      test_cnt_out
    );

设计的整体框架依旧是状态机为主,依据各个状态输出对应的数据,本设计的状态跳转图如下所示:
在这里插入图片描述
一共是8个状态:
(1)空闲状态,这一状态不多介绍了;
(2)启动状态,这一状态用来启动电源,并且复位芯片;
(3)配置状态,这一状态用来配置各项寄存器;
(4)检验状态,这一状态把上一过程的寄存器读出,检验写入是否有误;
(5)预备状态,这一状态用以初始化TDC的测量程序,TDC进入测量;
(6)等待状态,这一状态用于等待STOP脉冲到来,检测到STOP之后INTERRUPT拉低;
(7)读取状态,这一状态用来读取TDC的结果;
(8)读取状态,同上,如果读完一组数据后还有数据没读完,继续读,知道FIFO空为止。

localparam tdc_idle_state       = 4'd0;
localparam tdc_power_state      = 4'd1;
localparam tdc_wr_cof_state     = 4'd2;
localparam tdc_rd_cof_state     = 4'd3;
localparam tdc_init_state       = 4'd4;
localparam tdc_wait_state       = 4'd5;
localparam tdc_rd_reg0_state    = 4'd6;
localparam tdc_rd_reg1_state    = 4'd7;

然后介绍一下状态跳转的过程,空闲到启动的状态是由外部按键信号控制的,当按键按下表示启动,这一过程不多介绍了,随后的几个状态除了等待状态,都是通过计数跳转的,因为每一个状态所需要发送的bit数都是固定的,所以我们可以通过计数来实现,对于SPI通信本文就不多介绍了,FPGA如何实现SPI通信这个网上教学很多。具体操作如下所示:
首先贴上来的是SPI的时钟控制端口:

//******************************************************************
//SPI时钟信号控制
//******************************************************************
always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        sys_clk_cnt <= 2'd0;
    else if(sck_en)
        sys_clk_cnt <= sys_clk_cnt + 1'd1;
    else
        sys_clk_cnt <= 2'd0;
end 

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        sck_en <= 1'b0;
    else begin
        case(tdc_work_state)
            tdc_power_state:begin
                if(tdc_work_cnt >= 16'd3 && tdc_work_cnt < 16'd36)
                    sck_en <= 1'b1;
                else
                    sck_en <= 1'b0;
            end 
            tdc_wr_cof_state:begin
                if(tdc_work_cnt >= 16'd3 && tdc_work_cnt < 16'd580)
                    sck_en <= 1'b1;
                else
                    sck_en <= 1'b0;
            end 
            tdc_rd_cof_state:begin
                if(tdc_work_cnt >= 16'd3 && tdc_work_cnt < 16'd580)
                    sck_en <= 1'b1;
                else
                    sck_en <= 1'b0;
            end 
            tdc_init_state:begin
                if(tdc_work_cnt >= 16'd3 && tdc_work_cnt < 16'd36)
                    sck_en <= 1'b1;
                else
                    sck_en <= 1'b0;
            end 
            tdc_wait_state:begin
                sck_en <= 1'b0;
            end 
            tdc_rd_reg0_state:begin
                if(tdc_work_cnt >= 16'd3 && tdc_work_cnt < 16'd228)
                    sck_en <= 1'b1;
                else
                    sck_en <= 1'b0;
            end 
            tdc_rd_reg1_state:begin
                if(tdc_work_cnt >= 16'd3 && tdc_work_cnt < 16'd228)
                    sck_en <= 1'b1;
                else
                    sck_en <= 1'b0;
            end 
            default:sck_en <= 1'b0;
        endcase 
    end 
end 

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        spi_sck <= 1'b0;
    else if(sys_clk_cnt == 2'd2)
        spi_sck <= 1'b1;
    else if(sys_clk_cnt == 2'd0)
        spi_sck <= 1'b0;
    else
        spi_sck <= spi_sck;
end 

由上可见SCK的时钟频率是主频的四分频,也就是50M/4 = 12.5M,也就是说主频计数四次等于SPI通信的一个周期,所以控制状态跳转的计数信号每计数4次表示一位数据,计数32次表示一个byte的数据。由于每一次发送数据之前都需要给SSN一个脉冲,所以0-3的计数用来控制SSN,我们从计数7开始,到38结束,表示第一个字节,第二个字节从39开始到70结束…以此类推。可以得到如下代码:

//计数代码
always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        tdc_work_cnt <= 16'd0;
    else begin
        case(tdc_work_state)
            tdc_power_state:begin
                if(tdc_work_cnt < 16'd38)	
                    tdc_work_cnt <= tdc_work_cnt + 1'd1;
                else
                    tdc_work_cnt <= 16'd0;
            end 
            tdc_wr_cof_state:begin
                if(tdc_work_cnt < 16'd582)
                    tdc_work_cnt <= tdc_work_cnt + 1'd1;
                else
                    tdc_work_cnt <= 16'd0;
            end 
            tdc_rd_cof_state:begin
                if(tdc_work_cnt < 16'd582)
                    tdc_work_cnt <= tdc_work_cnt + 1'd1;
                else
                    tdc_work_cnt <= 16'd0;
            end 
            tdc_init_state:begin
                if(tdc_work_cnt < 16'd38)
                    tdc_work_cnt <= tdc_work_cnt + 1'd1;
                else
                    tdc_work_cnt <= 16'd0;
            end 
            tdc_wait_state:begin
                tdc_work_cnt <= 16'd0;
            end 
            tdc_rd_reg0_state, tdc_rd_reg1_state:begin
                if(tdc_work_cnt < 16'd230)
                    tdc_work_cnt <= tdc_work_cnt + 1'd1;
                else
                    tdc_work_cnt <= 16'd0;
            end 
            default:tdc_work_cnt <= 16'd0;
        endcase 
    end 
end 

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        tdc_work_state <= tdc_idle_state;
    else begin
        case(tdc_work_state)
            tdc_idle_state:begin
                if(start_measure_raising == 1'b1)
                    tdc_work_state <= tdc_power_state;
                else
                    tdc_work_state <= tdc_idle_state;
            end 
            tdc_power_state:begin
                if(tdc_work_cnt == 16'd38)	//只需要发送一个字节
                    tdc_work_state <= tdc_wr_cof_state;
                else
                    tdc_work_state <= tdc_power_state;
            end 
            tdc_wr_cof_state:begin
                if(tdc_work_cnt == 16'd582)	//一共需要发送17个字节
                    tdc_work_state <= tdc_rd_cof_state;
                else
                    tdc_work_state <= tdc_wr_cof_state;
            end 
            tdc_rd_cof_state:begin              //跳转条件为读出数据判别正确
//                if(tdc_work_cnt == 16'd582)     //仿真测试,直接往下走了,实际操作的时候用下面那四行即可
//                    tdc_work_state <= tdc_init_state;
//                else
//                    tdc_work_state <= tdc_rd_cof_state;
                if((tdc_work_cnt == 16'd582) && (config_data_check == config_data_stander))
                    tdc_work_state <= tdc_init_state;
                else if((tdc_work_cnt == 16'd582))
                    tdc_work_state <= tdc_idle_state;
                else
                    tdc_work_state <= tdc_rd_cof_state;
            end 
            tdc_init_state:begin
                if(tdc_work_cnt == 16'd38)
                    tdc_work_state <= tdc_wait_state;
                else
                    tdc_work_state <= tdc_init_state;
            end 
            tdc_wait_state:begin
                if(!tdc_interrupt_reg)
                    tdc_work_state <= tdc_rd_reg0_state;
                else
                    tdc_work_state <= tdc_wait_state;
            end 
            tdc_rd_reg0_state:begin
                if((tdc_work_cnt == 16'd230) && (!tdc_interrupt_reg))
                    tdc_work_state <= tdc_rd_reg1_state;
                else if(tdc_work_cnt == 16'd230)
                    tdc_work_state <= tdc_idle_state;
                else
                    tdc_work_state <= tdc_rd_reg0_state;
            end 
            tdc_rd_reg1_state:begin
                if(tdc_work_cnt == 16'd230)
                    tdc_work_state <= tdc_idle_state;
                else
                    tdc_work_state <= tdc_rd_reg1_state;
            end 
            default:tdc_work_state <= tdc_idle_state;
        endcase 
    end 
end 

以上就是状态跳转的代码。接下来就是FPGA在各个状态下需要向TDC发送的数据了,我所设计的是在每一个byte的前两个计数的时候将数据进行更换,然后每一个sck周期中对发送的那一个字节进行移位操作,每次取首位发送即可,代码如下所示:

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        current_data <= 8'd0;
    else begin
        case(tdc_work_state)
            tdc_power_state:begin
                if(tdc_work_cnt == 16'd5)
                    current_data <= spiopc_power;
                else if(sys_clk_cnt == 2'd2)    //上升沿,数据前推
                    current_data <= {current_data[6:0], 1'b0};
                else
                    current_data <= current_data;
            end 
            tdc_wr_cof_state:begin
                if(tdc_work_cnt == 16'd5)
                    current_data <= spiopc_wr_config;
                else if(tdc_work_cnt == 16'd36)
                    current_data <= PIN_ENABLE0_REGISTER;
                else if(tdc_work_cnt == 16'd68)
                    current_data <= HIT_ENABLE_REGISTER;
                else if(tdc_work_cnt == 16'd100)
                    current_data <= DATA_OUT_REGISTER;
                else if(tdc_work_cnt == 16'd132)
                    current_data <= REFCLK_DIVISION_LOW;
                else if(tdc_work_cnt == 16'd164)
                    current_data <= REFCLK_DIVISION_MID;
                else if(tdc_work_cnt == 16'd196)
                    current_data <= REFCLK_DIVISION_HIGH;
                else if(tdc_work_cnt == 16'd228)
                    current_data <= CONTENT1_REGISTER;
                else if(tdc_work_cnt == 16'd260)
                    current_data <= PIN_ENABLE1_REGISTER;
                else if(tdc_work_cnt == 16'd292)
                    current_data <= FIX0_REGISTER;
                else if(tdc_work_cnt == 16'd324)
                    current_data <= FIX1_REGISTER;
                else if(tdc_work_cnt == 16'd356)
                    current_data <= FIX2_REGISTER;
                else if(tdc_work_cnt == 16'd388)
                    current_data <= FIX3_REGISTER;
                else if(tdc_work_cnt == 16'd420)
                    current_data <= FIX4_REGISTER;
                else if(tdc_work_cnt == 16'd452)
                    current_data <= FIX5_REGISTER;
                else if(tdc_work_cnt == 16'd484)
                    current_data <= FIX6_REGISTER;
                else if(tdc_work_cnt == 16'd516)
                    current_data <= FIX7_REGISTER;
                else if(tdc_work_cnt == 16'd548)
                    current_data <= CMOS_INPUT_REGISTER;
                else if(sys_clk_cnt == 2'd2)
                    current_data <= {current_data[6:0], 1'b0};
                else
                    current_data <= current_data;
            end 
            tdc_rd_cof_state:begin
                if(tdc_work_cnt == 16'd5)
                    current_data <= spiopc_rd_config;
                else if(sys_clk_cnt == 2'd2)
                    current_data <= {current_data[6:0], 1'b0};
                else
                    current_data <= current_data;
            end 
            tdc_init_state:begin
                if(tdc_work_cnt == 16'd5)
                    current_data <= spiopc_init;
                else if(sys_clk_cnt == 2'd2)
                    current_data <= {current_data[6:0], 1'b0};
                else
                    current_data <= current_data;
            end 
            tdc_wait_state:begin
                current_data <= current_data;
            end 
            tdc_rd_reg0_state, tdc_rd_reg1_state:begin
                if(tdc_work_cnt == 16'd5)
                    current_data <= spiopc_rd_result;
                else if(sys_clk_cnt == 2'd2)
                    current_data <= {current_data[6:0], 1'b0};
                else
                    current_data <= current_data;
            end 
            default:current_data <= 8'd0;
        endcase 
    end 
end 

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        tdc_mosi <= 1'b0;
    else begin
        case(tdc_work_state)
            tdc_power_state:begin
                if(sys_clk_cnt == 2'd2)
                    tdc_mosi <= current_data[7];
                else
                    tdc_mosi <= tdc_mosi;
            end 
            tdc_wr_cof_state:begin
                if(sys_clk_cnt == 2'd2)
                    tdc_mosi <= current_data[7];
                else
                    tdc_mosi <= tdc_mosi;
            end 
            tdc_rd_cof_state:begin
                if(sys_clk_cnt == 2'd2)
                    tdc_mosi <= current_data[7];
                else
                    tdc_mosi <= tdc_mosi;
            end 
            tdc_init_state:begin
                if(sys_clk_cnt == 2'd2)
                    tdc_mosi <= current_data[7];
                else
                    tdc_mosi <= tdc_mosi;
            end 
            tdc_wait_state:begin
                tdc_mosi <= tdc_mosi;
            end 
            tdc_rd_reg0_state, tdc_rd_reg1_state:begin
                if(sys_clk_cnt == 2'd2)
                    tdc_mosi <= current_data[7];
                else
                    tdc_mosi <= tdc_mosi;
            end 
            default:tdc_mosi <= 1'b0;
        endcase 
    end 
end 

发送的操作结束之后就是数据接收了,根据手册中说明的,高位在前,所以我们同样采用移位的方式进行数据读取:

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)begin
        config_data_check <= 136'd0;
    end 
    else if((tdc_work_state == tdc_rd_cof_state) && (tdc_work_cnt > 16'd38))begin
        if((sys_clk_cnt == 2'd0) && (sck_en))begin
            config_data_check <= {config_data_check[134:0], tdc_miso};
        end 
        else begin
            config_data_check <= config_data_check;
        end 
    end 
    else begin
        config_data_check <= config_data_check;
    end 
end 

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)begin
        tdc_ch1_result0 <= 48'd0;
        tdc_ch1_result1 <= 48'd0;
    end 
    else if((tdc_work_state == tdc_rd_reg0_state) && (tdc_work_cnt > 16'd38))begin  //第一个字节
        if((sys_clk_cnt == 2'd0) && (sck_en))
            tdc_ch1_result0 <= {tdc_ch1_result0[46:0], tdc_miso};
        else
            tdc_ch1_result0 <= tdc_ch1_result0;
    end 
    else if((tdc_work_state == tdc_rd_reg1_state) && (tdc_work_cnt > 16'd38))begin  //第一个字节
        if((sys_clk_cnt == 2'd0) && (sck_en))
            tdc_ch1_result1 <= {tdc_ch1_result1[46:0], tdc_miso};
        else
            tdc_ch1_result1 <= tdc_ch1_result1;
    end 
    else begin
        tdc_ch1_result0 <= tdc_ch1_result0;
        tdc_ch1_result1 <= tdc_ch1_result1;
    end 
end 

以上就是全部的数据收发的代码了,最后贴上SSN信号的控制代码:

always @(posedge sys_clk or posedge sys_rst)begin
    if(sys_rst)
        tdc_ssn <= 1'b0;
    else begin
        case(tdc_work_state)
            tdc_power_state, tdc_wr_cof_state, tdc_rd_cof_state, tdc_init_state:begin
                if(tdc_work_cnt == 16'd0)
                    tdc_ssn <= 1'b1;
                else if(tdc_work_cnt == 16'd2)
                    tdc_ssn <= 1'b0;
                else
                    tdc_ssn <= tdc_ssn;
            end 
            tdc_rd_reg0_state, tdc_rd_reg1_state:begin
                if(tdc_work_cnt == 16'd0)
                    tdc_ssn <= 1'b1;
                else if(tdc_work_cnt == 16'd2)
                    tdc_ssn <= 1'b0;
                else
                    tdc_ssn <= tdc_ssn;
            end 
            default:tdc_ssn <= 1'b0;
        endcase 
    end 
end 

SSN控制比较简单,就是在每一次数据帧发送之前给一个脉冲即可,就不多介绍了。

结语

以上就是整个工程的实现过程,下一篇文章将会介绍数据的测量以及精度分析。

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值