9.6正点原子领航者V1开发板学习之频率计

一、实验原理

        等精度测量:设一段时间作为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显示模块设计,还有数据取每个位的算法。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值