续接上文,介绍完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控制比较简单,就是在每一次数据帧发送之前给一个脉冲即可,就不多介绍了。
结语
以上就是整个工程的实现过程,下一篇文章将会介绍数据的测量以及精度分析。