本次做的题目是:基于FPGA的倒计时显示电路设计,
题目要求如下:
详细要求如下:
本次设计的代码通过了仿真。(用的是quartus自带的仿真器)
这次设计分为两个模块,一个是倒计时的控制模块,另一个是数码管的显示模块。在倒计时控制模块中调用(例化)显示模块,从而实现分层设计。
关于数码管动态显示,可以参考我的这篇博客:
FPGA项目(5)--FPGA控制数码管动态显示的原理_fpga数码管显示实验原理_嵌入式小李的博客-CSDN博客https://blog.csdn.net/guangali/article/details/130754726?spm=1001.2014.3001.5501
唯一的区别就是 这次使用的数码管是4位数码管。经过稍加修改就可以直接使用。
下面重点讲解倒计时模块的逻辑设计:
首先是模块的接口设计:
各按键的作用如注释所示,其中minute_out 和 sec_out信号主要是为了在仿真的时候观察输出的时间数值(如果没有这个信号,就需要从数码管的输出段码和位选信号来判断当前倒计时的时间数值,会比较麻烦。用这个信号观察就会比较直观)。
内部使用到的信号如下:
其中hz_num是分频数值。仿真时,此值不应太大。因为quartus自带的仿真器在仿真时,所能观测的最大时间也才几百微秒。如果不缩小分频数值,那么仿真时就观察不到试验现象。其他各信号的含义如注释所示。
接着就是分频模块。这种分频非常简单,就不具体解释了,直接给出代码:
下面重点来了
首先,要实现k2按下数值清0的效果,这个非常简单。直接在if语句里面判断,符合条件直接清0就行。然后要实现时间的预设功能,那么假定只有在倒计时暂停的情况下才可以进行时间设定,倒计时运行过程中不能对时间进行修改。所以在设定时间时首先要判断k1的状态,如果k1为0,才可以进行时间预设。当k1为1时,就是正常的倒计时状态。Sec信号在1HZ脉冲的驱动下(clk_hz信号每隔1S会置1一次)减1,每次减到0都判断一次minute是否为0,如果minute不为0,说明倒计时过程还未结束,应该重新给sec赋值并且minute减1,否则说明倒计时结束了,蜂鸣器报警,同时暂停倒计时的过程。这部分的逻辑代码如下:
需要注意这里使用的是 if else if 语句,里面的条件是有优先级顺序的。复位信号的优先级最高,然后的清0按键k2,然后是k1.
最后就是将所得的时间数值,取出来,送给显示模块进行显示。
仿真图如下:
这是不加数码管显示的,为的是更清楚的看到时间的变化。一开始我令k3按下去一个周期 K5按下去2个周期,那么对应的效果就是minute+1,sec+2。从仿真的结果看,确实是这样。随后当k1置为高电平以后,分钟和秒钟在进行倒计时的过程。当时间减完以后,可以看到蜂鸣器输出了高电平,倒计时过程暂停了!
接下来给出一张完整的仿真图:
完整代码如下:
module count_down(
input clk , // 时钟信号 假设时钟信号为50M
input rst_n , // 复位信号
input k1, //用于控制计时的启动和停止 1表示启动 0表示停止
input k2, //用于控制计数时间的归零
input k3, //用于设置倒计时时间时 对分钟的+
input k4, //用于设置倒计时时间时 对分钟的-
input k5, //用于设置倒计时时间时 对秒钟的+
input k6, //用于设置倒计时时间时 对秒钟的- //所有按键都假设 不按下为1 按下为0
output [5:0] minut_out, //将时间输出 便于观察
output [5:0] sec_out,
output reg beep, //蜂鸣器报警 高电平报警
output [3:0] seg_sel, // 数码管位选,最左侧数码管为最高位
output [6:0] seg_led // 数码管段选
);
//parameter hz_num=32'd50_000_000;
parameter hz_num=32'd50; //将50M时钟分频到1HZ所需要的分频系数 仿真时取50 实物取50_000_000
reg [5:0] minute; //预设时间的分钟
reg [5:0] sec; //预设时间的秒钟
reg clk_hz; //分频后的1hz信号
reg [31:0] hz_cnt; //分频模块使用的寄存器
wire [3:0] minute_ge; //保存分钟的个位 用于输出到数码管显示
wire [3:0] minute_shi; //保存分钟的十位 用于输出到数码管显示
wire [3:0] sec_ge; //保存秒钟的个位 用于输出到数码管显示
wire [3:0] sec_shi; //保存秒钟的时位 用于输出到数码管显示
wire [15:0] data_out; //要传递到数码管显示的数据
//产生1hz的模块
always @(posedge clk) begin
if(hz_cnt==hz_num-1)
begin
hz_cnt<=32'd0;
clk_hz<=1'b1;
end
else
begin
hz_cnt<=hz_cnt+1;
clk_hz<=1'b0;
end
end
//读取按键信息 进行倒计时预设 假设只有在停止状态下才能进行时间预设 (也就是k1=0 才可以设置时间)
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
begin //复位时 时间都归0
minute<=6'd0;
sec<=6'd0;
beep<=1'b0; //蜂鸣器不响
end
else if(k2==0) //按下k2 时间也归0
begin
minute<=6'd0;
sec<=6'd0;
end
else if(k1==0) //如果是在停止状态下,则对时间进行设定
begin
if(k3==0)
begin
minute<=minute+6'd1;
end
else if(k4==0)
begin
minute<=minute-6'd1;
end
else if(k5==0)
begin
sec<=sec+6'd1;
end
else if(k6==0)
begin
sec<=sec-6'd1;
end
else
begin
minute<=minute;
sec<=sec;
end
end
else if(k1==1) //如果实在开启状态下
begin
if(clk_hz) //在1S的脉冲驱动下 进行倒计时操作
begin
if(sec>0)
sec<=sec-6'd1;
else //秒钟倒计时到0了
begin
if(minute>0) //如果分钟不为0
begin
sec<=6'd60; //秒钟重新赋值为60
minute<=minute-6'd1; //分钟 减1
end
else //如果分钟为0 说明倒计时结束了
begin
beep<=1'b1; //蜂鸣器响
sec<=6'd0;
minute<=6'd0;
end
end
end
else
begin
minute<=minute;
sec<=sec;
end
end
end
assign minut_out=minute;
assign sec_out=sec;
assign minute_ge=minute%10;
assign minute_shi=minute/10;
assign sec_ge=sec%10;
assign sec_shi=sec/10;
assign data_out={minute_shi,minute_ge,sec_shi,sec_ge}; //输出的数据由分钟 和秒钟 的各个位组成
//例化数码管模块
seg_led seg_led_inst
(
.clk (clk),
.rst_n (rst_n),
.data (data_out),
.seg_sel (seg_sel),
.seg_led (seg_led)
);
endmodule