FPGA初学记录——数字时钟系统搭建(下)

  • Post author:
  • Post category:其他




FPGA初学记录——数字时钟系统搭建(下)

野火征途Pro开发板教程——数码管动态展示拓展训练,数字时钟系统搭建





前言

最近在学习FPGA,因为研究生需要用到,所用的开发板是野火的征途Pro,也是在跟着野火的教程学习,学到了数码管动态展示,看到拓展训练,是一个挺适合练手的题目,主要是利用各个模块,比较锻炼模块化设计的思路,感觉有必要记录下来,说不定以后会有用。

整个rtl代码网盘链接:

链接:https://pan.baidu.com/s/1BhShyqqB2FL2PLLF225cuw

提取码:0101

–来自百度网盘超级会员V4的分享



一、问题简述

1、更改数据生成模块,生成一个时钟(时、分、秒)显示在数码管上。

2、在1的基础上加入按键信号,可通过按键去设置时钟值。

本文在第一个问题的基础上解决第二个问题



二、功能解析和源代码



1.功能解析

1、看到问题呢,首先分析要实现的功能有哪些,可以通过按键设置时钟值,想想以前调整电子表的时候,先把表停了,然后分别调时和分,这个调整方式也可以是这样。

2、按照第1点的思路,然后看野火的板子上一共有五个机械按键,其中一个被固定用在复位上,因此我们可以使用的是四个按键,我们需要用四个按键实现我们想要的功能。

3、首先得有暂停/播放功能的按键,暂停的时候对时/分/秒进行加减,既然有加减了,所以就得有个按键负责加减,开始的思路是一个按键负责加,一个按键负责减,还剩一个按键就负责切换时/分/秒,但是呢,这种设置在写代码的时候遇到一个问题,该怎么说呢,就是如果我现在调的是秒的数值,我调到58了,继续+1的话,会直接跳到00,没有59;如果现在是00的话,继续-1,会直接跳到58,忽略了59,这个问题发现之后我尝试了一些思路,都失败了,最后换了个想法。

4、这个想法是啥呢,就是按键功能换以下,一个负责暂停和播放,一个负责切换加法模式和减法模式,一个负责±1,意思是如果是在加法模式下,按下该按键值会+1,反之在减法模式下会-1,最后一个按键负责切换时/分/秒模式,按照这个就可以把上述问题解决,具体怎么解决的就看代码吧。

5、博客展示的代码是核心代码:包括按键功能模块key_function和数据生成模块data_gen,全部代码可以参考百度网盘,目前调试还未发现问题,如有问题欢迎下方评论区讨论。之后会录个视频发,链接后续补充。

6、噢,对了,为了方便看按键的效果,所以还添加了对应功能的指示灯,也方便调试。



2.源代码——key_function

先把代码奉上。

module key_function
#(
    parameter CNT_MAX = 20'd999_999 // 按键消抖计数器
)
(
    input  wire      sys_clk     , // 系统时钟
    input  wire      sys_rst_n   , // 复位信号
    input  wire      key_play    , // 负责暂停/播放的按键
    input  wire      key_add_sub , // 加法/减法模式切换按键
    input  wire      key_one     , // +-1按键
    input  wire      key_mode    , // 时/分/秒模式切换
    
    output reg       play_fun    , // 1:执行播放功能;0:执行暂停功能
    output reg       add_sub_fun , // 1: 减法模式    ;0:加法模式
    output reg       one_fun     , // 加法模式就+1   ;减法模式就-1
    output reg [1:0] mode_fun      // 0:秒模式;1:分模式;2:时模式

);
/*暂停/播放功能 中间参数*/
wire      play_flag;
reg       last_play_fun;
/*切换加法/减法模式 中间参数*/
wire      add_sub_flag;
reg       last_addsub_fun;
/*+-1功能 中间参数*/
wire      one_flag;
reg       last_one_fun;
/*切换秒/分/时模式 中间参数*/
wire      mode_flag;
reg [1:0] last_mode_fun;

/*按键消抖 定义中间参数*/
reg [19:0] cnt_20ms; // 按键消抖计数器
reg key_flag; //1:表示消抖后检测到按键被按下;0:表示没有检测到按键被按下
/************************************************************************按键消抖代码****************************************************************************/
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else if(key_play == 1'b1 && key_add_sub == 1'b1 && key_one == 1'b1 && key_mode == 1'b1)
        cnt_20ms <= 20'b0;
    else if(cnt_20ms == CNT_MAX && (key_play == 1'b0 || key_add_sub == 1'b0 || key_one ==1'b0 || key_mode == 1'b0))
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else if(cnt_20ms == CNT_MAX-1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;
/*暂停/播放功能*/        
assign play_flag = !key_play && key_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        last_play_fun <= 1'b0;
    else
        last_play_fun <= play_fun;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        play_fun <= 1'b0;
    else if(last_play_fun == 1'b1 && play_flag == 1'b1)
        play_fun <= 1'b0;
    else if(last_play_fun == 1'b0 && play_flag == 1'b1)
        play_fun <= 1'b1;
    else
        play_fun <= play_fun;

/*加/减法模式切换功能*/        
assign add_sub_flag = !key_add_sub && key_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        last_addsub_fun <= 1'b0;
    else
        last_addsub_fun <= add_sub_fun;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        add_sub_fun <= 1'b0;
    else if(last_addsub_fun == 1'b1 && add_sub_flag == 1'b1)
        add_sub_fun <= 1'b0;
    else if(last_addsub_fun == 1'b0 && add_sub_flag == 1'b1)
        add_sub_fun <= 1'b1;
    else
        add_sub_fun <= add_sub_fun;

/*+-1功能*/        
assign one_flag = !key_one && key_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        last_one_fun <= 1'b0;
    else
        last_one_fun <= one_fun;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        one_fun <= 1'b0;
    else if(last_one_fun == 1'b0 && one_flag == 1'b1)
        one_fun <= 1'b1;
    else
        one_fun <= 1'b0;     

/*秒/分/时模式切换功能*/  
assign mode_flag = !key_mode && key_flag;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        last_mode_fun <= 2'b00;
    else
        last_mode_fun <= mode_fun;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mode_fun <= 2'b00;
    else if(last_mode_fun == 2'b10 && mode_flag == 1'b1)
        mode_fun <= 2'b00;
    else if(last_mode_fun == 2'b00 && mode_flag == 1'b1)
        mode_fun <= 2'b01;
    else if(last_mode_fun == 2'b01 && mode_flag == 1'b1)
        mode_fun <= 2'b10;
    else
        mode_fun <= mode_fun;
               
endmodule

这里可以发现±1功能和其他功能好像有些不一样,其他功能按键每按一次是更新一次状态并维持住,等待下一次按键按下,而±1按键每按一次是产生一个脉冲,如果也跟其他按键一样维持状态的话,则会一直累加或者累减,拦都拦不住。



2.源代码——data_gen

同样先把代码奉上

module data_gen
#(
    parameter CNT_MAX         = 25'd49_99_99  , // 1000ms计数值
    parameter KEY_MAX        = 20'd999_999   , // 消抖计数器
    parameter DATA_SECOND_MAX = 20'd59        , // 秒的最大值
    parameter DATA_MINUTE_MAX = 20'd59        , // 分钟的最大值
    parameter DATA_HOUR_MAX   = 20'd23          // 小时的最大值

   // parameter DATA_MAX1 = 18'd250_000
    
)
(
    input  wire         sys_clk     , // 系统时钟,频率50MHz
    input  wire         sys_rst_n   , // 复位信号,低电平有效
    input  wire         key_play    , // 暂停/播放按键
    input  wire         key_add_sub , // 加法/减法模式切换按键
    input  wire         key_one     , // +-1按键
    input  wire         key_mode    , // 时/分/秒模式切换
    
    output wire  [19:0] data        , // 数码管要显示的值
    output wire  [5:0]  point       , // 小数点显示,高电平有效
    output reg          seg_en      , // 数码管使能信号,高电平有效
    output wire         sign        , // 符号位,高电平显示负号
    output wire         led0        , // 暂停/播放指示灯
    output wire         led1        , // 加减法模式指示灯
    output wire  [1:0]  led_mode      // 时/分/秒模式切换指示灯
);

reg [24:0]  cnt_100ms           ; // 100ms计数器
reg         cnt_flag            ; // 100ms标志信号

reg [19:0]  data_sencond        ; // 秒的数据  

reg [19:0]  data_hour           ; // 小时的数据
reg [19:0]  hour_multiplicand   ; // 小时的被乘数

reg [19:0]  data_minute         ; // 分钟的数据
reg [19:0]  minute_multiplicand ; // 分钟的被乘数

reg         sencond_minute_flag ; // 秒-分钟的标志位
reg         minute_hour_flag    ; // 分钟-小时的标志位

wire [19:0] result_minute       ; // 计算的分钟的结果
wire [19:0] result_hour         ; // 计算的小时的结果

wire        play_fun            ; // 暂停播放信号,1:暂停;0:播放
wire        add_sub_fun         ; // 切换加减法模式,1:减法;0:加法
wire        one_fun             ; // 加法模式下:1:+1,0:不变;减法模式下:1:-1,0:不变
wire [1:0]  mode_fun            ; 
// 不显示小数点以及负数
assign point = 6'b010_100 ;
assign sign  = 1'b0       ;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        minute_multiplicand <= 20'd0;
    else
        minute_multiplicand <= 20'd100;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        hour_multiplicand <= 20'd0;
    else
        hour_multiplicand <= 20'd10000;
        
// cnt_100ms:用50MHz时钟从0到4999_999计数即为100ms
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_100ms <= 25'd0;
    else if(cnt_100ms == CNT_MAX)
        cnt_100ms <= 25'd0;
    else if(play_fun == 1'b1)
        cnt_100ms <= cnt_100ms;
    else if(play_fun == 1'b0)
        cnt_100ms <= cnt_100ms + 1'b1;
        
// cnt_flag:每100ms产生一个标志信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else if(cnt_100ms == CNT_MAX - 1'b1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;

// 秒显示的数据:0-60       
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0) 
        data_sencond <= 20'd0;
    else if(data_sencond == DATA_SECOND_MAX && cnt_flag == 1'b1)
        data_sencond <= 20'd0;
    else if(data_sencond == DATA_SECOND_MAX && play_fun == 1'b1 && add_sub_fun == 1'b0 && one_fun == 1'b1 && mode_fun == 2'b00)
        data_sencond <= 20'd0;
    else if(data_sencond == 20'd0 && play_fun == 1'b1 && add_sub_fun == 1'b1 && one_fun == 1'b1 && mode_fun == 2'b00)
        data_sencond <= DATA_SECOND_MAX;
    else if(data_sencond != 20'd0 && play_fun == 1'b1 && add_sub_fun == 1'b1 && one_fun == 1'b1 && mode_fun == 2'b00)
        data_sencond <= data_sencond - 1'b1;
    else if(data_sencond != DATA_SECOND_MAX && play_fun == 1'b1 && add_sub_fun == 1'b0 && one_fun == 1'b1 && mode_fun == 2'b00)
        data_sencond <= data_sencond + 1'b1;
    else if(cnt_flag == 1'b1)
        data_sencond <= data_sencond + 1'b1;
    else
        data_sencond <= data_sencond;
        
//sencond_minute_flag:秒-分钟的标志位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sencond_minute_flag <= 1'b0;
    else if(data_sencond == DATA_SECOND_MAX && (cnt_flag == 1'b1))
        sencond_minute_flag <= 1'b1;
    else
        sencond_minute_flag <= 1'b0;
        
// 分钟显示的数据:0-60       
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0) 
        data_minute <= 20'd0;
    else if(data_minute == DATA_MINUTE_MAX && sencond_minute_flag == 1'b1)
        data_minute <= 20'd0;
    else if(data_minute == DATA_MINUTE_MAX && play_fun == 1'b1 && add_sub_fun == 1'b0 && one_fun == 1'b1 && mode_fun == 2'b01)
        data_minute <= 20'd0;
    else if(data_minute == 20'd0 && play_fun == 1'b1 && add_sub_fun == 1'b1 && one_fun == 1'b1 && mode_fun == 2'b01)
        data_minute <= DATA_MINUTE_MAX;
    else if(data_minute != 20'd0 && play_fun == 1'b1 && add_sub_fun == 1'b1 && one_fun == 1'b1 && mode_fun == 2'b01)
        data_minute <= data_minute - 1'b1;
    else if(data_minute != DATA_MINUTE_MAX && play_fun == 1'b1 && add_sub_fun == 1'b0 && one_fun == 1'b1 && mode_fun == 2'b01)
        data_minute <= data_minute + 1'b1;
    else if(sencond_minute_flag == 1'b1)
        data_minute <= data_minute + 1'b1;
    else
        data_minute <= data_minute;

//minute_hour_flag:分钟-小时的标志位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        minute_hour_flag <= 1'b0;
    else if(data_minute == DATA_MINUTE_MAX && sencond_minute_flag == 1'b1)
        minute_hour_flag <= 1'b1;
    else
        minute_hour_flag <= 1'b0;

// 小时显示的数据:0-24       
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0) 
        data_hour <= 20'd0;
    else if(data_hour == DATA_HOUR_MAX && minute_hour_flag == 1'b1)
        data_hour <= 20'd0;
    else if(data_hour == DATA_HOUR_MAX && play_fun == 1'b1 && add_sub_fun == 1'b0 && one_fun == 1'b1 && mode_fun == 2'b10)
        data_hour <= 20'd0;
    else if(data_hour == 20'd0 && play_fun == 1'b1 && add_sub_fun == 1'b1 && one_fun == 1'b1 && mode_fun == 2'b10)
        data_hour <= DATA_HOUR_MAX;
    else if(data_hour != 20'd0 && play_fun == 1'b1 && add_sub_fun == 1'b1 && one_fun == 1'b1 && mode_fun == 2'b10)
        data_hour <= data_hour - 1'b1;
    else if(data_hour != DATA_HOUR_MAX && play_fun == 1'b1 && add_sub_fun == 1'b0 && one_fun == 1'b1 && mode_fun == 2'b10)
        data_hour <= data_hour + 1'b1;
    else if(minute_hour_flag == 1'b1)
        data_hour <= data_hour + 1'b1;
    else
        data_hour <= data_hour;    
        
// 数码管使能信号给高即可
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        seg_en <= 1'b0;
    else
        seg_en <= 1'b1;        

assign   data = result_hour + result_minute + data_sencond;
assign   led0 = ~play_fun;
assign   led1 = ~add_sub_fun;
assign   led_mode = ~mode_fun;

multiply 
#(
    .DATA_CARRY_MAX(DATA_MINUTE_MAX)
)
multiply_inst
(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    .data_multiplier(data_minute),
    .data_multiplicand(minute_multiplicand),
    
    .result(result_minute)
);

multiply 
#(
    .DATA_CARRY_MAX(DATA_HOUR_MAX)
)
multiply_inst0
(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    .data_multiplier(data_hour),
    .data_multiplicand(hour_multiplicand),
    
    .result(result_hour)
);

key_function
#(
    .CNT_MAX(KEY_MAX)
    
)
key_function_inst
(
    .sys_clk(sys_clk),
    .sys_rst_n(sys_rst_n),
    .key_play(key_play),
    .key_add_sub(key_add_sub),
    .key_one(key_one),
    .key_mode(key_mode),
    
    .play_fun(play_fun),
    .add_sub_fun(add_sub_fun),
    .one_fun(one_fun),
    .mode_fun(mode_fun)
);
        
endmodule

基这部分代码不好说,反正就是根据当前按键的功能做出相应的操作,这里要注意的是,所有的加减操作,必须得是暂停之后才能可视化加减的情况,这也是为了更加符合我想的实际情况。这部分代码调试的时候还是遇到了很多问题的,不过还是一步步分析给解决了,但是如果要追求完美的话,肯定是有些瑕疵的,但无伤大雅,要改的话应该也不难,应该…吧。



三、总结

啊啊啊啊啊啊啊,这道题终于搞定了,本来一个周末可以解决的,结果还是高估了自己,经过这次的练习,自己对模块化设计的思路更加清晰了,还有就是发现问题和解决问题,看波形分析问题以及解决问题,利用led灯来帮助自己调试代码,还有代码规范等等等等,这个练习还是学到了好多好多,下一步终于IP核了,不容易啊,IP核之后就是结合数字信号处理实验的练习了,任重道远啊小伙子。如果大家有更加简单的写法欢迎评论区讨论哈哈哈哈。



版权声明:本文为weixin_43982855原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。