基于FPGA和STM32的相位差测量

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

2019年:
这是我的第二篇博文,接上一篇,都是为了准备电赛,结果电赛没用上,特来分享。大家需要结合自身实际进行开发,本工程仅供参考。


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. 在判断相位差的时候,我使用了我工程代码所特殊的地方。这个在相位差测量模块中再提。

相位差测量方法


2.3 FPGA设计

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

工程结构图

RTL图如下图所示:

RTL图

注:

  • 在拿到一个新的工程时,我最先会去看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端代码:
stm32代码
FPGA端代码:
相位差范围的判断


相位差测量模块源码:

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. 仿真指导

  • 直接打开行为仿真:
    行为仿真

  • 打开后的仿真界面:

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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


4. 后言

想必学习这个内容的都是初学者,大家有任何问题评论区留言,我有时间就会回复。

需要工程的小伙伴们,请点赞+评论区留言邮箱,上线会给你发送工程

欢迎打赏

参考博客:FPGA测两路信号相位差-it66ds

源码放这里了,可以自行下载


  • 56
    点赞
  • 185
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 84
    评论
在Lattice Diamond中,首先需要创建一个新的工程,选择对应的FPGA器件型号,然后添加适当的约束文件和设计文件。以下是一个基于iCE40UP5K器件的频率和相位差测量的简单示例设计。 约束文件 仅需要约束输入时钟信号和输出数据信号的引脚。 ``` # Clock constraints set_location_assignment PIN_35 -to clk_i set_io clk_i !PIN_35 # Output data constraints set_location_assignment PIN_45 -to data_out set_io data_out !PIN_45 ``` 设计文件 设计包括一个简单的计数器和两个相位差测量器件来测量两个不同频率的输入信号之间的相位差。该设计在每个时钟周期内将输入信号的触发点作为相位参考,并将其与前一个周期的触发点进行比较,从而计算相位差值。 ``` module freq_phase_meter( input wire clk_i, input wire [1:0] data_i, output reg [15:0] data_out ); reg [15:0] count_reg1; reg [15:0] count_reg2; wire trigger1; wire trigger2; wire[31:0] phase_delta1; wire[31:0] phase_delta2; // Divide input clock by 2 and 3 assign trigger1 = (count_reg1 == 32767); assign trigger2 = (count_reg2 == 21845); always @(posedge clk_i) begin if (trigger1) count_reg1 <= 0; else count_reg1 <= count_reg1 + 1; if (trigger2) count_reg2 <= 0; else count_reg2 <= count_reg2 + 1; end // Phase measurement freq_phase_detector #( .WIDTH(32), .PERIOD1(32767), .PERIOD2(21845) ) phase_detector1 ( .clk_i(clk_i), .trigger_i(trigger1), .phase_delta_o(phase_delta1) ); freq_phase_detector #( .WIDTH(32), .PERIOD1(32767), .PERIOD2(14563) ) phase_detector2 ( .clk_i(clk_i), .trigger_i(trigger2), .phase_delta_o(phase_delta2) ); // Output phase difference between input signals always @(posedge clk_i) begin data_out <= phase_delta1[15:0] - phase_delta2[15:0]; end endmodule module freq_phase_detector( input wire clk_i, input wire trigger_i, output wire [$clog2(WIDTH)-1:0] phase_delta_o // $clog2(WIDTH) is the number of bits needed to represent WIDTH ); reg [31:0] phase_reg; always @(posedge clk_i) begin if (trigger_i) phase_reg <= 0; else phase_reg <= phase_reg + 1; end // Output phase difference between last two trigger points assign phase_delta_o = (phase_reg - WIDTH); endmodule ``` 以上代码可以将两个不同频率的输入信号连接到data_i[1:0] 引脚上,输出测量相位差值将在每个时钟周期下更新到data_out[15:0] 引脚上。 注意:在实际应用中,需要针对特定的输入信号频率和初始相位设置PERIOD1、PERIOD2参数,以便进行准确的相位差测量。 最后,使用Lattice Diamond中的“Build”选项将设计合成并生成位文件,然后将位文件烧录到FPGA器件上即可进行测试。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bigbeea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值