一、实验原理
等精度测量:设一段时间作为GATE,这段时间是fx的整数倍。计数这段时间的的跳变。fs_cnt*fs_t=GATE;fx_cnt*fx_t=GATE。其中fx_t对应fx的时钟周期,fs_t对应fs的时钟周期。fs是基准信号频率,fx是待测信号频率。把周期换成频率clk则得到下面式子。
其实可以看成待测信号频率等于CLK_FS的一定比例,这个比例就是fx_cnt/fs_cnt。这样算得的误差最多就是一个基准时钟周期,误差就是基准时钟周期比上GATE时间。
二、程序设计
首先根据实验要求设计出原理图。由与没有示波器我们用ZYNQ自己生产一个待测信号。然后输入到待测模块进行计算,将结果输出给LCD模块输出对应数据。
那么我们主要就是频率计模块和测试时钟的设计。
先产生测试时钟吧。我们之前已近详细讲过,分频系数是什么和真正分频后时钟频率是多少。
比如我们想把50MHZ 100分频成500KHZ;50000000/100=500000。而分频系数是指一个周期的计数值,我们正真要变成500KHZ应该在计数一半时翻转。这里cnt设为10位是通用性。
module clk_test #(parameter DIV_N = 7'd100) //分频系数
(
//源时钟
input clk_in , // 输入时钟
input rst_n , // 复位信号
//分频后的时钟
output reg clk_out // 输出时钟
);
//reg define
reg [9:0] cnt; // 时钟分频计数
//*****************************************************
//** main code
//*****************************************************
//时钟分频,生成500KHz的测试时钟
always @(posedge clk_in or negedge rst_n) begin
if(rst_n == 1'b0) begin
cnt <= 0;
clk_out <= 0;
end
else begin
if(cnt == DIV_N/2 - 1'b1) begin
cnt <= 10'd0;
clk_out <= ~clk_out;
end
else
cnt <= cnt + 1'b1;
end
end
endmodule
接下来是根据原理设计的等精度频率计模块。
首先我们需要产生门控信号,我们是5000个fx宽度所以在fx时钟下计数5000个就好为了防止亚稳态等我们设5020个,从10到5010是高电平。
always @(posedge clk_fx or negedge rst_n) begin
if(!rst_n)
gate_cnt <= 16'd0;
else if(gate_cnt == GATE_TIME + 5'd20)
gate_cnt <= 16'd0;
else
gate_cnt <= gate_cnt + 1'b1;
end
//门控信号,拉高时间为GATE_TIME个实测时钟周期
always @(posedge clk_fx or negedge rst_n) begin
if(!rst_n)
gate <= 1'b0;
else if(gate_cnt < 4'd10)
gate <= 1'b0;
else if(gate_cnt < GATE_TIME + 4'd10)
gate <= 1'b1;
else if(gate_cnt <= GATE_TIME + 5'd20)
gate <= 1'b0;
else
gate <= 1'b0;
end
然后我们需要操作将门控信号同步到基准时钟下。异步信号同步都是这样处理的。一般我们就是设置两个寄存器比如以下
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
gate_fs_r <= 1'b0;
gate_fs <= 1'b0;
end
else begin
gate_fs_r <= gate;
gate_fs <= gate_fs_r;
end
end
这一段就会把gate同步到基准时钟,gate_fs_r和gate_fs始终差一拍。gate_fs就是我们同步后的信号。gate_fs是要比gate晚一拍的。关于这种方法的详解见链接(17条消息) 异步时钟的同步化,俗称“慢打一拍",寄存一拍_kaopuguyue110的博客-CSDN博客_打一拍为什么可以同步
用这个方法还可以抓边缘。我们需要采集在基准和被测时钟下的门控信号边沿(下降沿)以基准时钟下为例。这个就是在基准时钟下抓取了门控信号的边沿。同时我们还需要抓在待测时钟下。抓取边缘就是为了找到阈值,在这个范围内对基准时钟和待测时钟计数。
assign neg_gate_fs = gate_fs_d1 & (~gate_fs_d0);//抓取下降沿
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
gate_fs_d0 <= 1'b0;
gate_fs_d1 <= 1'b0;
end
else begin
gate_fs_d0 <= gate_fs;
gate_fs_d1 <= gate_fs_d0;
end
end
抓取 neg_gate_fs后就可以在门控时间内对fs计数。fx同理。这里我们用临时值来在ALWAYS里计数,将求得的值赋值给需要的fs_cnt.
reg [MAX-1:0] fs_cnt_temp ; // fs_cnt 临时值
//门控时间内对基准时钟计数
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
fs_cnt_temp <= 32'd0;
fs_cnt <= 32'd0;
end
else if(gate_fs)
fs_cnt_temp <= fs_cnt_temp + 1'b1;
else if(neg_gate_fs) begin
fs_cnt_temp <= 32'd0;
fs_cnt <= fs_cnt_temp;
end
end
关于fx下计数操作和fs一样。这里不赘述。
最后就是计算了,我们已经求得需要的值fs_cnt和fx_cnt,也已知CLK_FS.用公式就可以算了。
//计算被测信号频率
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
data_fx_t <= 64'd0;
end
else if(gate_fs == 1'b0)
data_fx_t <= CLK_FS * fx_cnt ;
end
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
data_fx <= 20'd0;
end
else if(gate_fs == 1'b0)
data_fx <= data_fx_t / fs_cnt ;
end
data_fx就是我们求得的频率。我们将其输出连接到LCD模块。
以下是完整代码。
module cymometer
#(parameter CLK_FS = 26'd50_000_000) // 基准时钟频率值
( //system clock
input clk_fs , // 基准时钟信号
input rst_n , // 复位信号
//cymometer interface
input clk_fx , // 被测时钟信号
output reg [19:0] data_fx // 被测时钟频率输出
);
//parameter define
localparam MAX = 6'd32; // 定义fs_cnt、fx_cnt的最大位宽
localparam GATE_TIME = 16'd5_000; // 门控时间设置
//reg define
reg gate ; // 门控信号
reg gate_fs ; // 同步到基准时钟的门控信号
reg gate_fs_r ; // 用于同步gate信号的寄存器
reg gate_fs_d0 ; // 用于采集基准时钟下gate下降沿
reg gate_fs_d1 ; //
reg gate_fx_d0 ; // 用于采集被测时钟下gate下降沿
reg gate_fx_d1 ; //
reg [ 63:0] data_fx_t ; //
reg [ 15:0] gate_cnt ; // 门控计数
reg [MAX-1:0] fs_cnt ; // 门控时间内基准时钟的计数值
reg [MAX-1:0] fs_cnt_temp ; // fs_cnt 临时值
reg [MAX-1:0] fx_cnt ; // 门控时间内被测时钟的计数值
reg [MAX-1:0] fx_cnt_temp ; // fx_cnt 临时值
//wire define
wire neg_gate_fs; // 基准时钟下门控信号下降沿
wire neg_gate_fx; // 被测时钟下门控信号下降沿
//*****************************************************
//** main code
//*****************************************************
//边沿检测,捕获信号下降沿
assign neg_gate_fs = gate_fs_d1 & (~gate_fs_d0);
assign neg_gate_fx = gate_fx_d1 & (~gate_fx_d0);
//门控信号计数器,使用被测时钟计数
always @(posedge clk_fx or negedge rst_n) begin
if(!rst_n)
gate_cnt <= 16'd0;
else if(gate_cnt == GATE_TIME + 5'd20)
gate_cnt <= 16'd0;
else
gate_cnt <= gate_cnt + 1'b1;
end
//门控信号,拉高时间为GATE_TIME个实测时钟周期
always @(posedge clk_fx or negedge rst_n) begin
if(!rst_n)
gate <= 1'b0;
else if(gate_cnt < 4'd10)
gate <= 1'b0;
else if(gate_cnt < GATE_TIME + 4'd10)
gate <= 1'b1;
else if(gate_cnt <= GATE_TIME + 5'd20)
gate <= 1'b0;
else
gate <= 1'b0;
end
//将门控信号同步到基准时钟下
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
gate_fs_r <= 1'b0;
gate_fs <= 1'b0;
end
else begin
gate_fs_r <= gate;
gate_fs <= gate_fs_r;
end
end
//打拍采门控信号的下降沿(被测时钟下)
always @(posedge clk_fx or negedge rst_n) begin
if(!rst_n) begin
gate_fx_d0 <= 1'b0;
gate_fx_d1 <= 1'b0;
end
else begin
gate_fx_d0 <= gate;
gate_fx_d1 <= gate_fx_d0;
end
end
//打拍采门控信号的下降沿(基准时钟下)
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
gate_fs_d0 <= 1'b0;
gate_fs_d1 <= 1'b0;
end
else begin
gate_fs_d0 <= gate_fs;
gate_fs_d1 <= gate_fs_d0;
end
end
//门控时间内对被测时钟计数
always @(posedge clk_fx or negedge rst_n) begin
if(!rst_n) begin
fx_cnt_temp <= 32'd0;
fx_cnt <= 32'd0;
end
else if(gate)
fx_cnt_temp <= fx_cnt_temp + 1'b1;
else if(neg_gate_fx) begin
fx_cnt_temp <= 32'd0;
fx_cnt <= fx_cnt_temp;
end
end
//门控时间内对基准时钟计数
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
fs_cnt_temp <= 32'd0;
fs_cnt <= 32'd0;
end
else if(gate_fs)
fs_cnt_temp <= fs_cnt_temp + 1'b1;
else if(neg_gate_fs) begin
fs_cnt_temp <= 32'd0;
fs_cnt <= fs_cnt_temp;
end
end
//计算被测信号频率
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
data_fx_t <= 64'd0;
end
else if(gate_fs == 1'b0)
data_fx_t <= CLK_FS * fx_cnt ;
end
always @(posedge clk_fs or negedge rst_n) begin
if(!rst_n) begin
data_fx <= 20'd0;
end
else if(gate_fs == 1'b0)
data_fx <= data_fx_t / fs_cnt ;
end
endmodule
接着介绍LCD模块。这个模块只有显示模块我们需要重新设计。但是很简单没有上次那么多。
我们这里又有个新 的东西就是计算数据每个位的数,我们500K就是500000,所以就到十万位,上面我们设置data_fx的位宽是20位,500k=0111 1010 0001 0010 0000 ,19位才够。
assign data5 = data / 17'd100000; // 十万位数
assign data4 = data / 14'd10000 % 4'd10; // 万位数
assign data3 = data / 10'd1000 % 4'd10 ; // 千位数
assign data2 = data / 7'd100 % 4'd10 ; // 百位数
assign data1 = data / 4'd10 % 4'd10 ; // 十位数
assign data0 = data % 4'd10; // 个位数
之后在特定字符区域显示对应的值就和RTC里面那个一样了。我们数字字母取模软件取出来是8*16。我们只需要在特定区域填入DATA就可以了。因为我们只有一行所以和上次有一点点不一样,不需要设置第二行。
if(char [10] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
比如这个这里如果要到第二行即pixel_ypos是大于CHAR_HEIGHT+CHAR_POS_Y的,我们需要再额外设置CHAR_POS_Y,作为第二行起始纵坐标。
module lcd_display(
input lcd_pclk, //lcd驱动时钟
input sys_rst_n, //复位信号
input [19:0] data ,
input [10:0] pixel_xpos, //像素点横坐标
input [10:0] pixel_ypos, //像素点纵坐标
output reg [23:0] pixel_data //像素点数据,
);
//parameter define
localparam CHAR_POS_X = 11'd1; //字符区域起始点横坐标
localparam CHAR_POS_Y = 11'd1; //字符区域起始点纵坐标
localparam CHAR_WIDTH = 11'd64; //字符区域宽度
localparam CHAR_HEIGHT = 11'd16; //字符区域高度
localparam WHITE = 24'b11111111_11111111_11111111; //背景色,白色
localparam BLACK = 24'b00000000_00000000_00000000; //字符颜色,黑色
//reg define
reg [127:0] char [11:0] ; //字符数组
//wire define
wire [3:0] data0 ; // 十万位数
wire [3:0] data1 ; // 万位数
wire [3:0] data2 ; // 千位数
wire [3:0] data3 ; // 百位数
wire [3:0] data4 ; // 十位数
wire [3:0] data5 ; // 个位数
//*****************************************************
//** main code
//*****************************************************
assign data5 = data / 17'd100000; // 十万位数
assign data4 = data / 14'd10000 % 4'd10; // 万位数
assign data3 = data / 10'd1000 % 4'd10 ; // 千位数
assign data2 = data / 7'd100 % 4'd10 ; // 百位数
assign data1 = data / 4'd10 % 4'd10 ; // 十位数
assign data0 = data % 4'd10; // 个位数
//给字符数组赋值,用于存储字模数据
always @(posedge lcd_pclk) begin
char[0 ] <= 128'h00000018244242424242424224180000 ; // "0"
char[1 ] <= 128'h000000107010101010101010107C0000 ; // "1"
char[2 ] <= 128'h0000003C4242420404081020427E0000 ; // "2"
char[3 ] <= 128'h0000003C424204180402024244380000 ; // "3"
char[4 ] <= 128'h000000040C14242444447E04041E0000 ; // "4"
char[5 ] <= 128'h0000007E404040586402024244380000 ; // "5"
char[6 ] <= 128'h0000001C244040586442424224180000 ; // "6"
char[7 ] <= 128'h0000007E444408081010101010100000 ; // "7"
char[8 ] <= 128'h0000003C4242422418244242423C0000 ; // "8"
char[9 ] <= 128'h0000001824424242261A020224380000 ; // "9"
char[10] <= 128'h000000E7424242427E42424242E70000 ; // "H"
char[11] <= 128'h000000000000007E44081010227E0000 ; // "z"
end
//给不同的区域赋值不同的像素数据
always @(posedge lcd_pclk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
pixel_data <= BLACK;
end
else if((pixel_xpos >= CHAR_POS_X) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*1)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [data5] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK; //显示字符为黑色
else
pixel_data <= WHITE; //显示字符区域背景为白色
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*1) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*2)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [data4] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*2) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*3)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [data3] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*3) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*4)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [data2] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*4) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*5)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [data1] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*5) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*6)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [data0] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*6) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/8*7)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [10] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else if((pixel_xpos >= CHAR_POS_X + CHAR_WIDTH/8*7) && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH)
&& (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
if(char [11] [ (CHAR_HEIGHT+CHAR_POS_Y - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else begin
pixel_data <= WHITE; //绘制屏幕背景为白色
end
end
endmodule
到此主要部分就结束了。
总结:这个实验主要是学习了频率计的设计。还有复习RGB显示模块设计,还有数据取每个位的算法。