基于FPGA的等精度频率计,频率测量范围0~25M正弦波(方波),相位测量范围0~100k
完整工程请见:https://download.csdn.net/download/qq_42838291/12573047


实物图及其测试效果,由于测试时没拍太多照片。高频部分的没有,只贴上低频的。

由于这里用到的多是集成的芯片和模块,所以画的比较简单。图中的CH1和CH2时信号源产生的两路正弦波或方波。方波可以直接输入测量,但是注意信号源要设置偏移量,将输出方波抬高到0上。因为信号源直接出来的方波含有负电压会导致测量不准,甚至损坏I/O口。正弦波要经过整形放大模块后才能进入FPGA。
进来的两路信号中,一路直接送进FPGA进行测频。异或门是将两路同频同幅的方波信号进行异或后再送入FPGA进行计数(测相)。D触发器是判断两路信号的超前滞后关系。

这里如果测量两路正弦波的频率和相位的话要先通过整形放大电路,将两路带负电压的正弦波整形成为单极性的方波。常用的电压比较器有LM311P或LM393之类的(延时在50~200ns之间),但是效果不尽人意,整形的波形不够干净。只能用来做测频用,测相会产生很大的误差。因为测相是直接对异或的方波进行计数,如果波形不好,计数也不会准确。附一张图:
紫色是信号源产生的标准方波,下面是经过整形电路后的波形,测相要不得。
这里推荐使用高速电压比较器TLV3501(单通道)淘宝几块钱就有。也可以直接使用双通道TLV3502(淘宝十多块钱)。这是淘宝上比较便宜的价钱,可根据自己的心情选择购买。

如果不想自己在做整形电路的话可以直接购买模块,淘宝上有单通道/双通道的TLV3501/3502模块。推荐一个单通道的,当时买来测试用的,单通道。每个35块,两块加起来还比双通道的便宜。
注意一定要使用SMA线,不管你自己做电路还是买模块。即使是低频状态下,信号源输出到整形电路这一段距离都会受到比较大的干扰,突出的表现是频率测量准确,相位误差很大。购买时要注意如果SMA头是针头则要配合SMA内孔线。
想自己做整形电路的朋友可以参考这张原理图(单通道),这是购买模块的商家的原理图。

打开FPGA工程

顶层模块是一些端口的声明,和几个模块的例化。
module Fre_measure(
input clk,
input rst_n,
input clk_measure, //频率测量输入引脚
input Phase_Measure, //相位测量输入引脚
input uart_rx,
output uart_tx
);
wire[31:0] fre_cnt;
wire[31:0] xiang_wei;
//parameter define
parameter CLK_FREQ = 50000000; //定义系统时钟频率
parameter UART_BPS = 115200; //定义串口波特率
wire pulse;
wire [31:0]pulse_high_reg, pulse_low_reg; //脉宽高低位寄存器
Pulse_width_messure Pulse_width_messure//例化脉宽测量模块
(
.clk (clk),
.rst_n (rst_n),
.pulse_in (Phase_Measure), //相位测量
.pulse_high_reg (pulse_high_reg),//高电平宽度寄存器
.pulse_low_reg (pulse_low_reg)//低电平宽度寄存器
);
Time_1s time_1s(
.clk (clk ),
.rst_n (rst_n ),
.flag_1s (flag_1s ),
.xiang_wei (xiang_wei )
);
dff1 dff_1(
.clk_measure (clk_measure ),
.flag_1s (flag_1s ),
.clk_match (clk_match )
);
Fre_cnt fre_cnt1(
.clk_measure (clk_measure ),
.rst_n (rst_n ),
.clk_match (clk_match ),
.fre_cnt (fre_cnt )
);
uart_send #( //串口发送模块
.CLK_FREQ (CLK_FREQ), //设置系统时钟频率
.UART_BPS (UART_BPS)) //设置串口发送波特率
u_uart_send(
.sys_clk (clk),
.sys_rst_n (rst_n),
.pulse_low_reg (pulse_low_reg),
.pulse_high_reg (pulse_high_reg),
.uart_din_a (fre_cnt), //把接收到的数据放到发送模块再发送给PC
.uart_txd (uart_tx)
);
这是FPGA通用端口模拟串口发送测量得到的频率,占空比(相位测量用),还有一些模块由于篇幅有限没有贴出。可下载完整工程查看。
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
state <= 1'b0;
end
else if (en_flag)
begin //检测到发送使能上升沿
tx_flag <= 1'b1;
if(state < 13) state <= state + 4'd1;
else
state <= 4'd1; //进入发送过程,标志位tx_flag拉高
if(state==4'd1) tx_data <= check_fre[7:0]; //先发送一个校验数据帧
if(state==4'd2) tx_data <= pulse_high_reg[7:0]; //寄存待发送的数据
if(state==4'd3) tx_data <= pulse_high_reg[15:8];
if(state==4'd4) tx_data <= pulse_high_reg[23:16];
if(state==4'd5) tx_data <= pulse_high_reg[31:24];
if(state==4'd6) tx_data <= pulse_low_reg[7:0]; //寄存待发送的数据
if(state==4'd7) tx_data <= pulse_low_reg[15:8];
if(state==4'd8) tx_data <= pulse_low_reg[23:16];
if(state==4'd9) tx_data <= pulse_low_reg[31:24];
if(state==4'd10) tx_data <= uart_din_a[7:0];
if(state==4'd11) tx_data <= uart_din_a[15:8];
if(state==4'd12) tx_data <= uart_din_a[23:16];
if(state==4'd13) tx_data <= uart_din_a[31:24];
end
else
if ((tx_cnt == 6'd9)&&(clk_cnt == BPS_CNT/2))
begin //计数到停止位中间时,停止发送过程
tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
tx_data <= 8'd0;
end
else
begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
//进入发送过程后,启动系统时钟计数器与发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
begin
clk_cnt <= 16'd0;
tx_cnt <= 6'd0;
end
else if (tx_flag)
begin //处于发送过程434-1=433
if (clk_cnt < BPS_CNT - 1)
begin
clk_cnt <= clk_cnt + 1'b1;
tx_cnt <= tx_cnt;
end
else //系统时钟计数超出一个波特率
begin
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1
end
end
else //如果发送标志位拉低
begin //发送过程结束
clk_cnt <= 16'd0;
tx_cnt <= 6'd0;
end
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
uart_txd <= 1'b1; //复位就是高电平,表示不传输数据时uart_txd为高电平
else if (tx_flag)
case(tx_cnt) //每个波特率时间内就传输一个数据,每位数据传输的时间是一个波特率
6'd0: uart_txd <= 1'b0; //起始位
6'd1: uart_txd <= tx_data[0]; //数据位最低位
6'd2: uart_txd <= tx_data[1];
6'd3: uart_txd <= tx_data[2];
6'd4: uart_txd <= tx_data[3];
6'd5: uart_txd <= tx_data[4];
6'd6: uart_txd <= tx_data[5];
6'd7: uart_txd <= tx_data[6];
6'd8: uart_txd <= tx_data[7]; //数据位最高位
6'd9: uart_txd <= 1'b1; //停止位
default: ;
endcase
else
uart_txd <= 1'b1; //空闲时发送端口为高电平
end
endmodule
打开单片机的工程

这里是单片机部分,串口接收FPGA送出的相关数据。FPGA先发送0XFF作为一个起始标志,当单片机接收到0Xff后开始接收后面的数据并放到寄存器中,当接收完毕后将接收到的数据进行移位。
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2,USART_IT_RXNE))
{
res= USART_ReceiveData(USART2);
if(check_fre!=0xff)
check_fre = res;
if(check_fre==0xff)
{
if(time_uart==13)
time_uart=0;
time_uart++;
if(time_uart==13)
check_fre =0x00;
}
USART_ClearFlag(USART2,USART_FLAG_TC);
}
}
这里是单片机接收到数据后的处理过程。time_uart从2开始计算是因为第一个是起始标志0XFF。
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "usart2.h"
#include "Dtriggers.h"
u32 Freq,pulse_high_reg, pulse_low_reg;
float Duty_Cycle,Phase;
__align(4) u8 dtbuf[50]; //打印缓存器 __align(4)
int main(void)
{
u8 temp1,temp2,temp3,temp4;
u8 pulse_high_reg_8, pulse_high_reg_16,pulse_high_reg_24, pulse_high_reg_32;
u8 pulse_low_reg_8, pulse_low_reg_16, pulse_low_reg_24, pulse_low_reg_32;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(); //延时函数初始化
usart2_init(115200);
Dtriggers_Init();
OLED_Init(); //初始化OLED
OLED_Clear();
while(1)
{
if(time_uart==10) temp1 = res;
if(time_uart==11) temp2 = res;
if(time_uart==12) temp3 = res;
if(time_uart==13)
{
Freq = 0;
temp4 = res;
Freq = (temp4<<24)|(temp3<<16)|(temp2<<8)|temp1;
time_uart = 0 ;
}
if(time_uart==2) pulse_high_reg_8 = res;
if(time_uart==3) pulse_high_reg_16 = res;
if(time_uart==4) pulse_high_reg_24 = res;
if(time_uart==5)
{
pulse_high_reg_32 = res;
pulse_high_reg = 0;
pulse_high_reg = (pulse_high_reg_32<<24)|(pulse_high_reg_24<<16)|(pulse_high_reg_16<<8)|pulse_high_reg_8;
}
if(time_uart==6) pulse_low_reg_8 = res;
if(time_uart==7) pulse_low_reg_16 = res;
if(time_uart==8) pulse_low_reg_24 = res;
if(time_uart==9)
{
pulse_low_reg_32 = res;
pulse_low_reg = 0;
pulse_low_reg = (pulse_low_reg_32<<24)|(pulse_low_reg_24<<16)|(pulse_low_reg_16<<8)|pulse_low_reg_8;
Duty_Cycle = ((float)pulse_high_reg/((float)pulse_low_reg + (float)pulse_high_reg))*100;
Phase = ((float)pulse_high_reg/((float)pulse_low_reg + (float)pulse_high_reg))*180;
}
// OLED_ShowString(0,0,"Frequency : ",16);
OLED_ShowNum(0,4,Freq,13,16);
OLED_ShowString(112,4,"Hz",16);
// sprintf((char *)dtbuf,"Duty : %0.1f",Duty_Cycle);
// OLED_ShowString(0,4,dtbuf,16);
// OLED_ShowString(112,4,"%",16);
//D触发器用于检测同频方波的超前滞后关系
//超前输出高电平,滞后输出低电平
if(FLAG==1)
sprintf((char *)dtbuf,"Phase: %0.1f",Phase);
else if(FLAG==0)
sprintf((char *)dtbuf,"Phase: %0.1f",360.-Phase);
OLED_ShowString(0,6,dtbuf,16);
OLED_ShowString(112,6,"*",16);
}
}
完整的工程在我的下载页面有。