目录
- 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日:重大改动!
- 修改代码结构,结构更加清晰,代码更加规范
- 增加仿真指导,帮助大家查看仿真结果
- 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°的相位差)
特殊处理:
- 因为在使用两路信号异或的方法时,当两路信号的相位差越小或者频率越高时,计数值比较小,会带来一定的误差,所以我们使用pll时钟倍频,使用200Mhz的时钟。
- 在测量时,我们可以多对几个高电平和低电平计数,这样求得的结果也更加准确。
- 在判断相位差的时候,我使用了我工程代码所特殊的地方。这个在相位差测量模块中再提。
2.3 FPGA设计
2.3.1 总体结构
- 相比于频率计,相位差测量的代码比较简短,工程比较小,重点还是在计算的处理上。
- 顶层模块包含pll模块,相位差测量模块,和串口模块。
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端代码:
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的板子,就不测试了,仿真结果看得出来是正确的。