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核之后就是结合数字信号处理实验的练习了,任重道远啊小伙子。如果大家有更加简单的写法欢迎评论区讨论哈哈哈哈。