项目描述
1.项目介绍
该项目是基于小脚丫FPGA的电赛训练平台,利用板上的高速比较器和FPGA的逻辑实现高速频率计和计数器的功能。以FPGA输出PWM波,经过一阶低通滤波,与输入信号比较,产生稳定的脉冲信号,方便FPGA捕获以及计数和计算频率。
2.设计思路
频率测量在诸多邻域都有广泛的应用,常用的频率测量方法有两种,分别是频率测量法和周期测量法,前者适合高频信号,后者适合低频信号。也有兼顾了两者的等精度测量法,但需要资源较多,且要求为高速频率计,故选用频率测量法。以FPGA产生PMW波,经过一阶低通滤波,与被测信号作比较,产生较为稳定的脉冲信号,在时间t内对被测信号的时钟周期N进行计数,然后求出单位时间内的时钟周期数,即为被测信号的频率。以该种方法测得的频率,由于门控信号并非被测信号的整数倍,在闸门时间准确,频率测量范围内,理论情况下,会有正负一的误差,且该误差频率越小,出现的机率越小,频率越高,出现的机率越高。而计数器则是累加从FPGA上电以来的脉冲个数。将测得的数值显示在OLED屏上,并以按键切换频率计和计数器模式。
系统框图
3.简单的硬件介绍
该项目使用的是基于小脚丫FPGA的电赛训练平台,核心板上有四个按键,四个拨码按键,两个三色LED,八个单色LED,以及两个数码管。资源丰富且下载代码方式新颖快捷,可连接电脑直接拖拽Diamond生成的jed文件。而底板上则有八位高速ADC和DAC,两个按键,一个编码器,一个OLED显示屏,以及该项目用到的高速比较器和RC网络。
4.实现的功能及图片展示
实现了高速频率计和计数器的功能,以按键K2切换模式,按键K1为全局复位键。测比较器的输出比直接测被测信号更稳定,所以频率计的输入信号接在高速比较器的正输入,FPGA测量比较器的输出。计数器若接高速信号显示会变得很快,不太看得清,所以输入接的是底板上的旋转编码器,编码器转2格,为完整一周期,计数器数值加1。
刚开始使用调式助手作为被测信号信号源,但其最高频率有限,且不如ZYNQ7010产生的信号稳定,于是,我用自己的ZYNQ作为信号源,分别输出1k,10k,50k,100k,500k,5M,12.5M,50M 的信号。
1k,10k,50k均准确且稳定。
100k偶尔跳变为99999,500k在500000和499999之间频繁跳动。
5M检测到4_999_993和4_999_994之间频繁跳动,在信号大于等于1MHz后显示单位变为kHz,为了观察其误差,仍将千位以下数字显示在下方。
12.5MHz测得结果为12_499_980至12_499_981。
50M时测得结果为49_999_918附近跳动
至此。
当被测信号小于1M时,测量结果较为精准,大于1M时,频率越高,误差越大。这种误差应该与小脚丫核心板的12M主频,高速比较器,以及普通杜邦线这三者有关。
以下是FPGA资源报告
5.主要代码片段及说明
首先是可调频率和占空比的PWM波,经过一阶低通滤波器后接入高速比较器负输入端。
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt <= 8'b0;
else if(cnt == 8'b1111)
cnt <= 8'b0;
else
cnt = cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
pwm_out <= 1'b0;
else if(cnt <= 8'd10)
pwm_out <= 1'b1;
else
pwm_out <= 1'b0;
end
然后是频率测量模块
always @(posedge clk_test or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
freq_cnt <= 26'b0;
else
freq_cnt <= freq_cnt + 1'b1;
end
always @(posedge clk_1s or negedge clk_1s or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
freq <= 26'b0;
else begin
if(freq_cnt >= freq_cnt_buff) freq = freq_cnt - freq_cnt_buff;
freq_cnt_buff = freq_cnt;
end
end
按键控制模式变换,同时被测信号频率大于1M时改显示单位,变为kHz
//控制模式翻转
reg h_or_l_r;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
h_or_l_r <= 1'b0;
else
begin
if(key_out == 1'b1) h_or_l_r <= ~h_or_l_r;
end
end
assign h_or_l = h_or_l_r;
//检测到按键输入,切换模式
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
date_buff <= 1'b0;
else if(freq_or_count == 1'b0)
date_buff <= count_buff - 1'b1;
else if(freq_or_count == 1'b1 && freq_buff < 20'd1000000)
date_buff <= freq_buff;
else
date_buff <= freq_buff_div;
end
assign led_R = (freq_or_count == 1'b1) ? 8'b11110000 : 8'b00001111;
这里的计数器同样套用了按键消抖模块
//计数加一
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
count <= 1'b0;
else
if((key_out == 1'b1) && (freq_or_count == 1'b0)) count = count + 1'b1;
end
以上两个主要功能模块获取到频率和计数数据后,转换成BCD码的形式,方便OLED显示
//当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_hun <= 4'b0;
end
else if(cnt_shift == 5'd21)
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
将OLED屏幕划分为4行,每行显示16个字符,在第一行显示模式(frequency/counter),第三行显示数值。第四行当单位为kHz时,显示余下的个十百位。
5'd0: begin state <= INIT; end
5'd1: begin if(freq_or_count == 1'b1) begin
y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " frequency ";state <= SCAN; end
else begin
y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " counter ";state <= SCAN; end
end
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd3: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= str_out ;state <= SCAN; end
5'd4: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= str_out_1 ;state <= SCAN; end
6.结尾感言
虽已有了一些FPGA使用经历,但在项目的完成过程中仍遇到了许多问题,遇到的第一个难题就是在写频率测量模块时综合时间极长且布局失败,但报错并没有明确指出哪里错了,在经过多次尝试修改以及上网搜索后,大概是所使用的资源超过了芯片(不知道对不对),在更换了频率测量方案,节约了很多资源后,得以编译成功,实现功能。然后就是消抖的问题,也是在多次修改后才较好实现。最后在审批后继续测试频率计的量程以及误差,也修改了不少地方的代码。
目前是大二,如果有机会的话希望在大三暑假再参加一次电赛,也希望如今所学能在电赛中有所应用。