实验技术指标要求
利用硬件描述语言 V e r i l o g Verilog Verilog (或 V H D L VHDL VHDL )、图形描述方式、IP核,结合数字系统设计方法,在 Q u a r t u s Quartus Quartus 开发环境下,利用 F P G A FPGA FPGA 开发板和自行设计的外围电路实现一个简易数字频率计,并满足以下的性能指标要求:
- 能够实现对 1 k H z − 1 M H z 1kHz-1MHz 1kHz−1MHz方波信号的频率测量(60分)
- 能够实现对幅度 0.1 V − 4 V 0.1V-4V 0.1V−4V ,频率 1 k H z − 10 k H z 1kHz-10kHz 1kHz−10kHz 范围的正弦信号进行频率测量(60分)
- 可对上述要求的正弦信号进行幅值的测量(40分)
- 幅值信息与频率信息能通过开发板上之数码管实时显示(20分)
- 幅值信息与频率信息能通过 U A R T UART UART 串口发送至计算机,通过电脑端串口调试助手显示(20分)
总分:200分
实际技术指标
实际得分:198分。
- 方波信号1 H z Hz Hz- 70 M H z MHz MHz
- 正弦信号:不带ADC负载条件下最高40MHz,最低1 H z Hz Hz; 带ADC负载条件下最高 3 M H z 3MHz 3MHz
- 本地显示DE1-Soc所有数码管用满,UART可传输任意大小频率值或幅值
系统方案设计
对于测量方波的需求,仅需直接将信号发生器生成的方波信号输入至
F
P
G
A
FPGA
FPGA 开发板的
G
P
I
O
GPIO
GPIO 管脚,通过记录时间为
0.5
s
0.5s
0.5s 的闸门中上升沿的次数
s
u
m
∗
2
sum*2
sum∗2 来统计频率;幅值信息与频率信息的显示切换通过拨动开关实现。
V
e
r
i
l
o
g
H
D
L
Verilog HDL
VerilogHDL 程序的设计思路主要如下:
对于正弦信号频率的测量,主要采取的策略是利用外围整形电路将正弦波整形为等频率的方波,然后用上述方波频率的测量方法测量即可;对于正弦信号幅值的测量,主要依赖于外部高速
A
D
C
ADC
ADC 对正弦信号进行采样,得到最大电压值
V
m
a
x
V_{max}
Vmax 与最小电压值
V
m
i
n
V_{min}
Vmin ,最终幅值为
V
m
a
x
−
V
m
i
n
2
\frac{V_{max}-V_{min}}{2}
2Vmax−Vmin 。最终,系统的结构框图如下:
硬件设计
1.SMA转排针电路
信号发生器生成的的信号一般是通过鳄鱼夹夹住
G
P
I
O
GPIO
GPIO 口上引出的杜邦线进行输入,这样虽然连接容易,但容易受鳄鱼夹天线效应影响引入杂波,影响频率计精度,在高频条件下(
1
M
H
z
1MHz
1MHz以上),此现象会非常明显。因此,给出的解决方案是,通过同轴电缆引入输入信号,并为此设计一个
S
M
A
SMA
SMA 转排针电路,原理图如下:
2.高速整形电路
方波测量不要任何外围电路,测正弦信号需要整形电路先整形为方波。
高速整形电路主要由两个部分构成:以 L M 319 LM319 LM319 为基础的高速过零比较电路+以 S N 74 L S 14 SN74LS14 SN74LS14 为基础的高速施密特反相整形电路。
2.1 器件选型
L
M
319
LM319
LM319 是一个高速双路运放,支持单
5
V
5V
5V 供电或正负
15
V
15V
15V 供电,具有
80
n
s
80ns
80ns 的快速响应时间,本实验中将利用该运放设计一个过零比较器。
S
N
74
L
S
14
SN74LS14
SN74LS14 是具有施密特触发输入的
6
6
6 通道、
4.75
V
4.75V
4.75V 至
5.25
V
5.25V
5.25V 双极性反相器,电平转换时间典型值为
15
n
s
15ns
15ns ,具有极高的响应速度。
2.2 原理图设计与仿真
模块电路原理图如图所示。
3.ADC
3.1 器件选型
本实验中采用
A
D
I
ADI
ADI 公司的
A
D
9226
AD9226
AD9226 高速ADC模块进行正弦信号幅值的采样。(有条件的同学也可以使用DE1-Soc开发板上的片上ADC测量,官网上有demo可以修改。用AD9226的原因是该ADC是并口ADC,时序控制手搓比较简单。)
A
D
9226
AD9226
AD9226 是一个单端
5
V
5V
5V 供电,
12
b
i
t
12bit
12bit ,
65
M
S
P
S
65MSPS
65MSPS 的流水线型模数转换器,包含一个片上高性能采样-保持电路,具有极低的误码率,典型应用中可以准确采样最高
31
M
H
z
31MHz
31MHz 正弦信号,转换率的影响主要受制于电平转换输出的速度。
4. USB转TTL
由于
U
S
B
USB
USB 通信电平与
T
T
L
TTL
TTL 电平不同,且实验箱上的开发扳没有直接的
U
S
B
USB
USB 接口,因此需要一个
C
H
340
CH340
CH340 芯片对串口通信电平进行转换,以实现上位机(计算机)端串口调试助手的查看。
软件设计
1.闸门频率计设计
通过 G P I O GPIO GPIO 管脚捕获方波的下降沿个数并计数,然后设计一个 0.5 s 0.5s 0.5s 闸门获取频率,实际频率再乘 2 2 2 即可。这样设计的好处是,频率相对于 1 s 1s 1s 闸门能更快地刷新,且能保证准确度。
2.数码管显示
利用 V e r i l o g Verilog Verilog 中 t a s k task task 语句封装每一位数码管点亮的程序,如下所示:
//light seg
task light;
output [7:0] seg;
input [3:0] val;
begin
case (val)
0:seg=7'b0000001;
1:seg=7'b1001111;
2:seg=7'b0010010;
3:seg=7'b0000110;
4:seg=7'b1001100;
5:seg=7'b0100100;
6:seg=7'b0100000;
7:seg=7'b0001111;
8:seg=7'b0000000;
9:seg=7'b0000100;
endcase
end
endtask
由于数码管仅能显示六位,因此选择显示频率的高 6 6 6位,若频率 > 1 0 6 H z >10^6Hz >106Hz ,则用 L E D LED LED 灯亮灭提示数量级的变化。串口端查看具体的缺省数值。
3.AD9226驱动
通过查阅手册可知,
A
D
9226
AD9226
AD9226 与外设通信采用的是同步并行通信,即每一个时钟上升沿到达时,
D
0
D0
D0~
D
12
D12
D12 管脚会输出转换后的电压值(实际上有延迟,是前几个时钟周期的信号采样值,这是流水线式ADC的通病)。
因此,首先需要分频出一个
25
M
H
z
25MHz
25MHz 的触发时钟,然后通过下面的拟合曲线线性映射转换为电压值:
(注:这里拟合曲线是因为我把ADC的数据存储器的值倒Bit存入FPGA寄存器,这是因为实验过程中发现正常时序读写的话最高位的Bit很不稳定,受干扰较大,个人怀疑是淘宝购买模块的电路问题,如果大家买的模块是正常的话请不要用拟合的方法做)。
V
I
N
=
−
4.667
×
D
a
t
a
+
10.26
V_{IN}=-4.667\times Data+10.26
VIN=−4.667×Data+10.26
其中
V
I
N
V_{IN}
VIN 为采样到的电压值,单位
m
V
mV
mV ;
D
a
t
a
Data
Data 为
A
D
9226
AD9226
AD9226 模块输出的转化结果。
此处实际调试中发现最高位的电平总是不稳定,因此选择按二进制位倒着取电压值,就可以获得较好的线性拟合结果。
获得正弦信号幅值的方法是:
- 获取输入信号电压最大值 V m a x V_{max} Vmax
- 获取输入信号电压最小值 V m i n V_{min} Vmin
- V m a x − V m i n 2 \frac{V_{max}-V_{min}}{2} 2Vmax−Vmin 为最终幅值
4.UART发送
按照下图的一般串口发送时序,通过 G P I O GPIO GPIO 模拟 U A R T UART UART 的 T x Tx Tx 管脚,实现 U A R T UART UART 发送的功能,配置的串口参数为:
- 波特率: 9600 9600 9600
- 数据位: 8 b i t 8bit 8bit
- 停止位:
1
b
i
t
1bit
1bit
约定待发送的字符串为统一格式: 8 8 8 位数据(不足则高位补零)+ 2 2 2 位单位( H z / m V Hz/mV Hz/mV)。
下载验证
经过各模块原理验证与仿真分析,最终,频率计系统的搭建如下:
下面分别以方波与正弦信号加以测试
1.方波信号测试
**测试条件:**高电平 2 V 2V 2V,低电平 0 V 0V 0V。
误差率计算公式:
w
=
f
1
−
f
0
f
0
×
100
%
w=\frac{f_1-f_0}{f_0}\times 100\%
w=f0f1−f0×100%
设定值 f 0 f_0 f0 | 测量值 f 1 f_1 f1 | 相对误差 x x x | 误差率 w % w\% w% |
---|---|---|---|
2 | 2 | 0 | 0.000000% |
114514 | 114514 | 0 | 0.000000% |
1000000 | 1000002 | 2 | 0.000200% |
10000000 | 10000022 | 22 | 0.000220% |
20000000 | 20000044 | 44 | 0.000220% |
30000000 | 30000065 | 65 | 0.000217% |
40000000 | 40000086 | 86 | 0.000215% |
50000000 | 50000110 | 110 | 0.000200% |
60000000 | 60000132 | 132 | 0.000200% |
2.正弦信号测试
2.1 频率测试
**测试条件:**高电平 2.1 V 2.1V 2.1V ,低电平 0 V 0V 0V
误差率计算公式:
w
=
f
1
−
f
0
f
0
×
100
%
w=\frac{f_1-f_0}{f_0}\times 100\%
w=f0f1−f0×100%
设定值 f 0 f_0 f0 | 测量值 f 1 f_1 f1 | 相对误差 x x x | 误差率 w % w\% w% |
---|---|---|---|
2 | 2 | 0 | 0.000000% |
1000 | 1000 | 0 | 0.000000% |
114514 | 114514 | 0 | 0.000000% |
1000000 | 1000002 | 2 | 0.000200% |
5000000 | 5000012 | 12 | 0.000240% |
10000000 | 10000022 | 22 | 0.000220% |
20000000 | 20000044 | 44 | 0.000220% |
30000000 | 30000066 | 66 | 0.000220% |
40000000 | 40000086 | 86 | 0.000215% |
2.2 幅度测试
**测试条件:**信号频率为 1 M h z 1Mhz 1Mhz,无额外偏置。电压单位均为 m V mV mV。
误差率计算公式:
w
=
V
1
−
V
0
V
0
×
100
%
w=\frac{V_1-V_0}{V_0}\times 100\%
w=V0V1−V0×100%
设定值 V 0 V_0 V0 | 测量值 V 1 V_1 V1 | 相对误差 x x x | 误差率 w % w\% w% |
---|---|---|---|
500 | 519 | 19 | 3.8% |
1500 | 1456 | -44 | -2.9% |
1700 | 1662 | -38 | –2.2% |
实验心得
这次实验是我“二刷”数字频率计这个题目,第一次制作时的需求是测量 1 M h z − 100 M h z 1Mhz-100Mhz 1Mhz−100Mhz 的正弦信号频率,当时使用的是 S T M 32 STM32 STM32 的 E T R ETR ETR 触发计数,外围电路仅有 S N 74 L S 14 SN74LS14 SN74LS14 反相器和一个加法偏置电路。当时主要遇到的是两个问题:1) S T M 32 STM32 STM32 的 E T R ETR ETR 引脚计数受制于 S T M 32 STM32 STM32 系统时钟的控制,因此最高只能计数到 35 M h z 35Mhz 35Mhz 信号( S T M 32 F 1 STM32F1 STM32F1 时钟频率是 72 M h z 72Mhz 72Mhz),且 20 M h z 20Mhz 20Mhz 以上时,误差已经超过电赛训练题的要求。2) S N 74 L S 14 SN74LS14 SN74LS14 受正弦信号幅值影响严重,尤其在高频状态下,部分幅值的等频率正弦信号无法进行测量。经过这次实验,这两个问题均得到了很好的解决。首先, F P G A FPGA FPGA 的上升沿计数并不受某一时钟的控制,可以直接翻转相关的计数寄存器,因此可以在高频的状态下依旧维持极高的测量精度,果然 F P G A FPGA FPGA 在高频信号处理方面的优势名不虚传。另外,为了解决第一次实验中遇到的第二个问题,我利用高速比较器 L M 319 LM319 LM319 制作了一个过零比较电路,然后再用 S N 74 L S 14 SN74LS14 SN74LS14 整形,效果就好了太多,但由于我的比较器没设计成滞回比较器,因此反而在低频时候下降沿旁抖动有些小严重。此外, L M 319 LM319 LM319 的频率响应时间并没有 S N 74 L S 14 SN74LS14 SN74LS14 那样优秀,且高频状态下波形会失真(数据手册提到了这点),因此离达到 100 M h z 100Mhz 100Mhz 正弦信号频率测量的目标还差的有些远。如需改进,可考虑设计外围分频电路,以减轻整形电路的压力。
附录
附录1:Verilog Codes
module frequency_meter(clk,clk_4000hz,clk_2hz,clk_25Mhz,ff,seg1,seg2,seg3,seg4,seg5,seg6,rst_n,led1,led2,uart_txd,mode,dat);
input clk; //SYSCLK:50Mhz
input ff; //input pin
input rst_n; //reset button
input mode; //0:uart_tx:frequency 1:uart_tx:amplitude of sine
input [11:0] dat; //ADC output
output reg clk_4000hz; //For uart Tx transmission
output reg clk_2hz; //Gatetime:0.5s
output reg clk_25Mhz;
output reg led1; //times 10
output reg led2; //times 100
output reg uart_txd; //Uart_Tx
output reg [6:0] seg1; //seg1
output reg [6:0] seg2; //seg2
output reg [6:0] seg3; //seg3
output reg [6:0] seg4; //seg4
output reg [6:0] seg5; //seg5
output reg [6:0] seg6; //seg6
//parameter define
parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 9600; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
//For Uart
reg uart_en;
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [3:0] tx_cnt; //发送数据计数器
reg tx_flag; //发送过程标志信号
reg [7:0] tx_data; //寄存发送数据
wire en_flag;
//For Clk_div
reg [10:0] cnt;
reg [23:0] cnt1;
reg [1:0] cnt2;
//For Gate
integer owari; //Gate collect owari
reg [27:0] sum;
reg [27:0] ans;
//For AD9226
reg [27:0] sum1;
reg [27:0] maxx;
reg [27:0] minn;
reg [27:0] maxx1;
reg [27:0] minn1;
reg [11:0] dat_r;
//reg [2:0] tot;
//For Uart IQR
reg [3:0] bit1;
reg [3:0] bit2;
reg [3:0] bit3;
reg [3:0] bit4;
reg [3:0] bit5;
reg [3:0] bit6;
reg [3:0] p; //pointer in a string
reg [7:0] uart_bit1; //Byte to Tx
reg [27:0] oper;
reg over;
//div clk
always @ (posedge clk)
begin
if (cnt==1999)
begin
clk_4000hz=~clk_4000hz;
cnt=0;
end
else
begin
cnt=cnt+1;
clk_4000hz=clk_4000hz;
end
if (cnt1==12499999)
begin
clk_2hz=~clk_2hz;
cnt1=0;
end
else
begin
cnt1=cnt1+1;
clk_2hz=clk_2hz;
end
if (cnt2==1)
begin
cnt2=0;
clk_25Mhz=~clk_25Mhz;
end
else
begin
cnt2=cnt2+1;
clk_25Mhz=clk_25Mhz;
end
end
//AD9226
always @ (posedge clk_25Mhz)
begin
dat_r=dat;
end
//Seg show
always @(posedge clk_4000hz or negedge rst_n)
begin
if (!rst_n)
begin
maxx1=0; minn1=0;
end
else
begin
if (!mode)
begin
led1=0; led2=0;
if (sum<1000000)
begin
led1=0; led2=0;
bit1=sum%10;
bit2=(sum%100)/10;
bit3=(sum%1000)/100;
bit4=(sum%10000)/1000;
bit5=(sum%100000)/10000;
bit6=(sum%1000000)/100000;
end
if (sum>=1000000 && sum<10000000)
begin
led1=0; led2=1;
bit1=(sum%100)/10;
bit2=(sum%1000)/100;
bit3=(sum%10000)/1000;
bit4=(sum%100000)/10000;
bit5=(sum%1000000)/100000;
bit6=(sum%10000000)/1000000;
end
if (sum>=10000000 && sum<100000000)
begin
led1=1; led2=0;
bit1=(sum%1000)/100;
bit2=(sum%10000)/1000;
bit3=(sum%100000)/10000;
bit4=(sum%1000000)/100000;
bit5=(sum%10000000)/1000000;
bit6=(sum%100000000)/10000000;
end
end
else
begin
sum1=dat_r;
if (sum1<=1750)
sum1=sum1+130;
sum1=sum1*4667;
sum1=10260000-sum1;
sum1=sum1/1000;
if (sum1>maxx1)
maxx1=sum1;
sum1=maxx1-minn1;
if (sum1<minn)
minn1=sum1;
if (minn1==0)
minn1=sum1;
sum1=sum1/2;
bit1=sum1%10;
bit2=(sum1%100)/10;
bit3=(sum1%1000)/100;
bit4=(sum1%10000)/1000;
bit5=(sum1%100000)/10000;
bit6=(sum1%1000000)/100000;
end
light(seg6,bit6);
light(seg5,bit5);
light(seg4,bit4);
light(seg3,bit3);
light(seg2,bit2);
light(seg1,bit1);
end
end
//AD9226
//UART Tx
always @(posedge clk_4000hz or negedge rst_n)
begin
if (!rst_n)
begin
maxx=0;
minn=0;
end
else
begin
uart_en=0; //Disable Uart Tx
if (!over)
begin
if (!mode)
begin
oper=sum;
end
else
begin
oper=dat_r;
if (oper<=1750)
oper=oper+130;
oper=oper*4667;
oper=10260000-oper;
oper=oper/1000;
if (oper>maxx)
maxx=oper;
oper=maxx-minn;
if (oper<minn)
minn=oper;
if (minn==0)
minn=oper;
oper=oper/2;
end
end
over=1;
case (p)
0:uart_bit1=(oper%100000000)/10000000;
1:uart_bit1=(oper%10000000)/1000000;
2:uart_bit1=(oper%1000000)/100000;
3:uart_bit1=(oper%100000)/10000;
4:uart_bit1=(oper%10000)/1000;
5:uart_bit1=(oper%1000)/100;
6:uart_bit1=(oper%100)/10;
7:uart_bit1=oper%10;
8: begin
if (!mode)
uart_bit1=72; //H
else
uart_bit1=109; //m
end
9:begin
if (!mode)
uart_bit1=122; //z
else
uart_bit1=86; //V
end
10:uart_bit1=13; //'\r'
endcase
if (p>=0 && p<=7)
uart_bit1=uart_bit1+48;
if (!tx_flag) //if Tx procedure over
begin
p=p+1; //Tx next byte
p=p%11;
if (p==0) //if full string Tx over
over=0; //Get next string
uart_en=1; //enable Uart Tx
end
end
end
//frequency_counter
always @ (negedge ff or negedge rst_n)
begin
if (!rst_n)
begin
ans=0;
end
else
begin
if (owari)
ans=0;
ans=ans+1;
end
end
//Gate
always @(posedge clk_2hz or negedge rst_n)
begin
if (!rst_n)
begin
sum=0;
end
else
begin
owari=~owari;
if (owari)
begin
sum=ans*2;
sum=sum-2;
end
end
end
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;
//对发送使能信号uart_en延迟两个时钟周期
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else if (en_flag) begin //检测到发送使能上升沿
tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高
tx_data <= uart_bit1; //寄存待发送的数据
end
else
if ((tx_cnt == 4'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 clk or negedge rst_n) begin
if (!rst_n) begin
clk_cnt <= 16'd0;
tx_cnt <= 4'd0;
end
else if (tx_flag) begin //处于发送过程
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 <= 4'd0;
end
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
uart_txd <= 1'b1;
else if (tx_flag)
case(tx_cnt)
4'd0: uart_txd <= 1'b0; //起始位
4'd1: uart_txd <= tx_data[0]; //数据位最低位
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7]; //数据位最高位
4'd9: uart_txd <= 1'b1; //停止位
default: ;
endcase
else
uart_txd <= 1'b1; //空闲时发送端口为高电平
end
//light seg
task light;
output [7:0] seg;
input [3:0] val;
begin
case (val)
0:seg=7'b0000001;
1:seg=7'b1001111;
2:seg=7'b0010010;
3:seg=7'b0000110;
4:seg=7'b1001100;
5:seg=7'b0100100;
6:seg=7'b0100000;
7:seg=7'b0001111;
8:seg=7'b0000000;
9:seg=7'b0000100;
endcase
end
endtask
endmodule