目录

  • 1. 前言
  • 2. 正文
  • 2.1 设计要求
  • 2.2 设计方案
  • 2.3 FPGA设计
  • 2.3.1 总体结构
  • 2.3.2 顶层
  • 2.3.3 相位差测量模块
  • 2.3.4 Testbench
  • 2.3.5 约束
  • 2.4 STM32设计
  • 2.4.1 相位差计算函数
  • 3. 仿真指导
  • 4. 后言


1. 前言

两路单端的同频信号,直连到FPGA的两个端口上(要控制输入信号的电压),准确测量两路信号的相位差,信号的频率从1Mhz-1hz,精度在0.1°。并且能够通过UART将数据发送到STM32上,并且用LCD屏幕显示出来。

环境

  • FPGA:黑金的Artix7开发板
  • STM32: 正点原子战舰精英板
  • EDA工具: Vivado2017.4 和 Keil 5

2022年10月25日:重大改动!

  1. 修改代码结构,结构更加清晰,代码更加规范
  2. 增加仿真指导,帮助大家查看仿真结果
  3. vivado代码版本修改为2018.3



2. 正文

2.1 设计要求

两路单端的同频信号,直连到FPGA的两个端口上(要控制输入信号的电压),准确测量两路信号的相位差,信号的频率从1Mhz-1hz,精度在0.1°。并且能够通过UART将数据发送到STM32上,并且用LCD屏幕显示出来。

问:为什么不直接用FPGA直接显示结果呢?
答:因为FPGA计算比较麻烦,所以偷懒就只在FPGA上做了部分工作,计算交给了STM32。刚好又熟悉了STM32,单独使用FPGA和STM32都能测量相位差,但是使用FPGA精度更高。


2.2 设计方案

输入信号为两路方波信号,对两路信号进行异或操作,就能得到能够表示两路信号相位差的信号,然后我们再分别对高电平和低电平持续时间进行计数,将两个计数值发给单片机进行计算。

(ps:这是网上大部分大佬使用的方法没错,但是不知是我的理解有问题还是咋的,我在实验的时候,它只能测量得到相位差小于180度的相位差,所以我就在前人的基础上进行了修改,做了一点小处理,使精度提高到了0.01°,并且能够测量0°到360°的相位差)

特殊处理

  1. 因为在使用两路信号异或的方法时,当两路信号的相位差越小或者频率越高时,计数值比较小,会带来一定的误差,所以我们使用pll时钟倍频,使用200Mhz的时钟。
  2. 在测量时,我们可以多对几个高电平和低电平计数,这样求得的结果也更加准确。
  3. 在判断相位差的时候,我使用了我工程代码所特殊的地方。这个在相位差测量模块中再提。

cordic IP核 算相位_相位差


2.3 FPGA设计

2.3.1 总体结构
  • 相比于频率计,相位差测量的代码比较简短,工程比较小,重点还是在计算的处理上。
  • 顶层模块包含pll模块,相位差测量模块,和串口模块。

cordic IP核 算相位_cordic IP核 算相位_02

RTL图如下图所示:

cordic IP核 算相位_fpga开发_03

注:

  • 在拿到一个新的工程时,我最先会去看RTL图。这样能够最快的了解整个设计的结构。
  • 在设计完成后打开RTL可以检查自己的连线是否存在问题。当然打开RTL的时候Vivado也会自动检查语法

2.3.2 顶层

top层源代码:

//将输入信号异或,然后求得占空比
//state=1 说明相位差在180以内,state=0 说明相位差在180-360度,算法不同
//输出信号为64位,第一位为状态state 63-32为高电平计数值,32-1为低电平计数值
//时钟200Mhz
//编辑xdc文件可以修改与pc还是stm32通信
//测量的频率范围是1hz-1Mhz最高待测
//相位精度0.01度

module phase_top(
    input clk,
    input rst_n,
    input in_signal1,
    input in_signal2,
    input uart_rx,

    output uart_tx
    );


    parameter CNT_TIMES = 8'd5;

    wire        clk_200mhz;
    wire [63:0] out_date;
    wire        locked;




  clk_wiz_0 u0
   (
    // Clock out ports
    .clk_out1(clk_200mhz),     // output clk_out1
    .clk_out2(),     // output clk_out2
    .clk_out3(),     // output clk_out3
    .clk_out4(),     // output clk_out4
    // Status and control signals
    .resetn(rst_n), // input resetn
    .locked(locked),       // output locked
   // Clock in ports
    .clk_in1(clk));      // input clk_in1

    //相位差测量整体逻辑
    Phase_measure #(
        .CNT_TIMES(CNT_TIMES) //设置计数周期,这里设置的是对三个信号周期进行计数
    )u1(    
        .clk(clk_200mhz),
        .rst_n(rst_n),
	    .in_signal1(in_signal1),
	    .in_signal2(in_signal2),
        .out_date(out_date)  //高位为高电平计数值,低位为低电平计数值   out_date={Pon+Poff}
    );


    //例化Uart与单片机通信
    uart_phase u2(
        .clk(clk),
        .rst_n(rst_n),
        .uart_rx(uart_rx),
        .phase_cnt(out_date), 
        .uart_tx(uart_tx)
    );


endmodule

2.3.3 相位差测量模块
  • 代码中我使用了一个flag_on信号,在flag_on信号的时候我们才对异或得到的XOR_OUT信号进行测量,然后我们发现,当相位差在0-180°时,flag_on信号的下降沿到来时,XOR_OUT信号刚好是高电平,当相位差在180°-360°时,XOR_OUT信号刚好是低电平。
  • 所以,我们在测量高电平和低电平的代码加入一个状态的判断模块。将状态位置于输出信号的最高位。

注:老铁们在实际验证的过程中可能会碰到相位差得到的结果相差180度的情况,这与你使用哪路信号作为基准信号有关系,对于这个问题,你们只需要修改我stm32工程中的部分代码就可以完成了。

STM32端代码:

cordic IP核 算相位_fpga开发_04


FPGA端代码:

cordic IP核 算相位_相位差_05


相位差测量模块源码:

module	Phase_measure#(
    parameter CNT_TIMES = 8'd3
    )(
    input clk,
    input rst_n,
	input in_signal1,
	input in_signal2,
    output [63:0]out_date
    );
        


 //异或XOR信号
 wire  XOR_OUT0;


 //计算高低电平宽度
 reg[31:0]	    Pon_cnt     =32'b0, Poff_cnt    =32'b0;
 reg[31:0]      Pon_num     =32'b0, Poff_num    =32'b0;             //用于储存Pon_cnt,Poff_cnt结果
 reg[7:0]       on_cnt      =8'b0;              //用来对XOR_OUT0高电平计数,设置CNT_TIMES可以增加计数值
 reg            flag_on     =1'b0;              //flag_on=1时对XOR_OUT0计数
 reg            state       =1'b0;              //state=1,前180度,state=0,后180度

 
 assign	XOR_OUT0 = in_signal1^in_signal2;       //新状态

 //计数CNT_TIMES个信号源的时间  不好修改,提高高频测量精度可以修改CNT_TIMES的值
 always @(posedge in_signal1)
    begin
        if(! rst_n)
            begin
                on_cnt<=8'd0;
            end
        else if(on_cnt<=CNT_TIMES-2)                    //CNT_TIMES个信号1次
                on_cnt <= on_cnt+1'b1;
        else
            begin
                on_cnt<=8'd0;
                flag_on<=~flag_on;
            end        
    end
 //判断相位差范围
always @(negedge flag_on or negedge rst_n) 
    begin
        if(! rst_n)
            state <=1'b0;
        else if(XOR_OUT0)
            state <=1'b1;           //状态1相位差在180内
        else
            state <=1'b0;           //状态0相位差在180外
    end


always @ (posedge clk)
 begin
    if(! rst_n)
        Pon_cnt  <=32'd0;
	else if(flag_on ==1 && XOR_OUT0 ==1 )
        Pon_cnt  <= Pon_cnt  + 1'b1;
    else if(flag_on == 0)
		Pon_cnt  <= 32'd0;
    else
        Pon_cnt  <=Pon_cnt;

 end

always @ (posedge clk)
 begin
    if(! rst_n)
        Poff_cnt  <= 32'd0;
	else if(flag_on ==1 && XOR_OUT0==0)
        Poff_cnt <= Poff_cnt + 1'b1;
    else if(flag_on == 0)
		Poff_cnt  <= 32'd0;
    else
        Poff_cnt  <=Poff_cnt;  
 end


always @(negedge flag_on or negedge rst_n)
        if(! rst_n)begin
            Pon_num  <= 32'd0;         
            Poff_num <= 32'd0;         
        end
		else begin
            Pon_num  <= Pon_cnt;        //保存计数器值
            Poff_num <= Poff_cnt;       //保存计数器值
		end

 assign out_date={state,Pon_num[30:0],Poff_num[31:0]};    //将状态和计数值发送给stm32处理

endmodule

2.3.4 Testbench
  • tb文件中我使用了一个小技巧,就是引入了一个enable信号。因为仅仅通过延时,你是做不到使两路信号的有相位差。当enable信号为1以后,我们再输入signal2信号。

源代码:

`timescale 1ns / 1ps

module phase_tb(
    );

    reg clk;
    reg rst_n;
    reg in_signal1;
    reg in_signal2;
    reg enable;
    wire uart_tx;


    parameter DELAY1 = 2000>>1;  //周期是1000ns ,延时500ns

    phase_top phase_top1(
    .clk(clk),
    .rst_n(rst_n),
    .in_signal1(in_signal1),
    .in_signal2(in_signal2),
    .uart_rx(),
    .uart_tx(uart_tx)
    );

    initial 
     begin
		// Initialize Inputs
		clk = 0;
        in_signal1=0;
        in_signal2=0;
        enable=0;
        rst_n=0;

        #1000
        rst_n=1;
        #500
        enable=1;

        #50000
        rst_n =1'b0;
        #10000
        rst_n =1'b1;

    end

    always #5 clk=~clk;
    always @(posedge clk)
    begin
        #DELAY1 in_signal1=1;
        #DELAY1 in_signal1=0;
    end

    always @(posedge clk)
    begin
        if(enable==1)begin
                #DELAY1 in_signal2=1;
                #DELAY1 in_signal2=0;
        end else
            in_signal2=0;
    end
    
    

endmodule

2.3.5 约束
  • 最好在我的xdc文件上修改端口,因为在产生bit文件的时候你们会碰到错误。
  • 输入信号加一个IBUF
############## NET - IOSTANDARD ##################
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
#############SPI Configurate Setting##################
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design] 
set_property CONFIG_MODE SPIx4 [current_design] 
set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design] 

create_clock -period 20 [get_ports clk]


set_property PACKAGE_PIN Y18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]

set_property PACKAGE_PIN E17 [get_ports in_signal1]
set_property PACKAGE_PIN F16 [get_ports in_signal2]
set_property IOSTANDARD LVCMOS33 [get_ports in_signal1]
set_property IOSTANDARD LVCMOS33 [get_ports in_signal2]

set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets in_signal1_IBUF]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets in_signal2_IBUF]

set_property PACKAGE_PIN F20 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]

set_property PACKAGE_PIN E14 [get_ports uart_rx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_rx]

set_property PACKAGE_PIN E13 [get_ports uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports uart_tx]

2.4 STM32设计

2.4.1 相位差计算函数
  • 首先判断标志位,然后进行计算,当两路信号相位差相差0°-180°和180°到360°计算的公式是不同的,大家可以自己在纸上画一下,就能知道差别了。
double on_cnt=0;
//u8 buff_on[32];
double off_cnt=0;
//u8 buff_off[32];
int state;
double phase_res;
int int_data;
int dec_data;


void phase_do(u8 buff_fre[68])
{	
	int i;
	//根据引脚确定状态,提前试一下
	if( buff_fre[0]=='1')	state=0;		//0-180度
	if( buff_fre[0]=='0')	state=1;		//180-360度	
	for(i=1;i<64;i++)
			{
				if(i<32)
				{
				on_cnt=on_cnt 	+ ( (double)((buff_fre[i]-48) << (31-i))); //移位,得到实际的数值
//				buff_on[i-1]=buff_fre[i];//显示收到的数据
				}
				else
				{
				off_cnt=off_cnt +	( (double)((buff_fre[i]-48) << (63-i)));
//				buff_off[i-32]=buff_fre[i];//显示收到的数据
				}	
			}
	if(state)
	{
		phase_res=(on_cnt*180)/(on_cnt+off_cnt); 				// 当相位小于180度时,state=1,phase=on*180/(on+off)
	}
	else
	{
		phase_res=(off_cnt*180)/(on_cnt+off_cnt) + 180;		// 当相位大于180度时,state=0,phase=off*180/(on+off) +180
	}
	phase_res= phase_res+0.005; //四舍五入
	phase_res= phase_res*1000;
	int_data=phase_res/1000;
	dec_data=(phase_res-int_data*1000)/10;
}
//

3. 仿真指导

  • 直接打开行为仿真:
  • cordic IP核 算相位_STM32_06

  • 打开后的仿真界面:

cordic IP核 算相位_STM32_07

  • 将相位差测量模块中的型号全部添加到窗口中:

cordic IP核 算相位_相位差_08

  • 刷新,并且运行,然后查看仿真结果

cordic IP核 算相位_sed_09

有条件的可以上板测试一下,博主没有STM32的板子,就不测试了,仿真结果看得出来是正确的。