我是靠谱客的博主 无情手机,最近开发中收集的这篇文章主要介绍基于 FPGA 实现滑动显示、多功能数字时钟【设置年月日时分秒以及闹钟】一、数码管原理二、基础篇三、进阶篇四、数字时钟,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
目录
- 一、数码管原理
- 二、基础篇
- 2.1 原理及代码
- 2.2 验证结果
- 三、进阶篇
- 3.1 原理及代码
- 3.2 验证结果
- 四、数字时钟
- 4.1 原理及代码
- 4.2 验证结果
本文内容:基于 FPGA 实现数字时钟,如果后续有时间可以添加一些额外的功能,比如设置时间、闹钟等等
中间的基础篇和进阶篇主要训练数码管的灵活应用,如果熟悉了并完全掌握的话,可以更加熟练的实现数字时钟
一、数码管原理
- 我使用的开发板型号为 EP4CE6F17C8,它的数码管有六位,原理图如下:
- 主要是由 DIG 和 SEL 这两个信号控制 6 位数码管显示,高电平灭,低电平亮,下面主要介绍如何控制
SEL 信号
- SEL 信号主要用来控制数码管的每一位,共有 6 位,SEL 位宽也就是 6 位,如下图所示:
举个例子,在代码中位宽的表示是低位在右,高位在左,所以在代码中写成 SEL = 6’b111_110 时,实际上就是第 0 位数码管亮,其余五位灭,如下图所示:
当 SEL = 6’b101_110 时,实际上就是第 0 位和第 4 位数码管亮,如下图所示:
但是只有一个 SEL 信号不足以完全控制每一位数码管中的每一段
DIG 信号
- DIG 信号主要控制每一位数码管中的 8 个段的数码管亮灭,如下图所示:
- 当 DIG = 8’b1010_0100 时,一位数码管就显示数字 2,如下图所示:
其它每个数字所对应的 DIG 值就不一一列举出来了 - 这两个信号时需要相互配合才可以在数码管上呈现我们想要的效果
- 比如说当 SEL = 6’b111_101 且 DIG = 8’b1010_0100 时,前面 SEL 表示第 1 位(从 0 开始的)数码管亮,配合 DIG 显示数字 2,那么在开发板上就可以呈现第 1 位数码管显示数字 2 其余数码管灭,代码如下:
module display(
input clk ,
input rst_n ,
output [7:0] DIG ,
output [5:0] SEL
);
assign SEL = 6'b111_101;
assign DIG = 8'b1010_0100;
endmodule
- 效果图如下:
二、基础篇
2.1 原理及代码
- 如何让数码管依次显示 123456 呢?首先要知道 SEL 和 DIG 如何配合控制数码管显示的
- 原理图:
(1) 首先设置 SEL 显示第 0 位,也就是 SEL = 6’b111_110,再设置 DIG 显示 1 的比特值,这样的话,数码管就第 0 位显示数字 1 了,但是其它位是灭的
(2) 然后设置 SEL 显示第 1 位,也就是 SEL = 6’b111_101,再设置 DIG 显示 2 的比特值,这样的话,数码管就第 1 位显示数字 2 了,但是其它位是灭的
(3) 按照上面的套路,依次让每一位显示相应数字,其它位灭,当 SEL 的值改变的速度不断的增加,那么就可以连续显示 123456 了,这主要应用到了视觉残留的机制 - 仔细好好的理一下逻辑
- 代码也是十分简单的,当然可以不用 SEL_num 来做选择,直接对 SEL 使用拼接运算符也可以
方法一:普通版
module display #(parameter MS_1 = 17'd100_000)(
input clk ,
input rst_n ,
output reg [7:0] DIG ,
output reg [5:0] SEL
);
// 参数定义
parameter SEL_MAX = 3'd6 ; // 数码管位数
// 信号定义
reg [ 2:0] SEL_num ; // SEL序号选择
reg [16:0] cnt_flicker ; // 数码管闪烁频率计数器
// 逻辑实现
// 闪烁频率计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 17'd0;
end
else if (cnt_flicker >= MS_1 - 17'd1) begin
cnt_flicker <= 17'd0;
end
else begin
cnt_flicker <= cnt_flicker + 17'd1;
end
end
// SEL序号选择
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL_num <= 3'd0;
end
else if (cnt_flicker >= MS_1 - 17'd1) begin
if (SEL_num >= SEL_MAX - 3'd1) begin
SEL_num <= 3'd0;
end
else begin
SEL_num <= SEL_num + 3'd1;
end
end
else begin
SEL_num <= SEL_num;
end
end
// SEL信号输出
always @(*) begin
case (SEL_num)
3'd0 : SEL = 6'b111_110;
3'd1 : SEL = 6'b111_101;
3'd2 : SEL = 6'b111_011;
3'd3 : SEL = 6'b110_111;
3'd4 : SEL = 6'b101_111;
3'd5 : SEL = 6'b011_111;
default: SEL = 6'b111_111;
endcase
end
// DIG信号输出
always @(*) begin
case (SEL_num)
3'd0 : DIG = 8'b1111_1001;
3'd1 : DIG = 8'b0010_0100;
3'd2 : DIG = 8'b1011_0000;
3'd3 : DIG = 8'b0001_1001;
3'd4 : DIG = 8'b1001_0010;
3'd5 : DIG = 8'b1000_0010;
default: DIG = 8'b1111_1111;
endcase
end
endmodule
方法二:拼接运算版
module display #(parameter MS_1 = 17'd100_000)(
input clk ,
input rst_n ,
output reg [7:0] DIG ,
output reg [5:0] SEL
);
// 信号定义
reg [16:0] cnt_flicker ; // 数码管闪烁频率计数器
wire [ 0:0] SEL_change ;
// 逻辑实现
// 闪烁频率计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 17'd0;
end
else if (SEL_change) begin
cnt_flicker <= 17'd0;
end
else begin
cnt_flicker <= cnt_flicker + 17'd1;
end
end
assign SEL_change = cnt_flicker >= MS_1 - 17'd1 ? 1'b1 : 1'b0;
// SEL信号输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL <= 6'b111_110;
end
else if (SEL_change) begin
SEL <= {SEL[4:0], SEL[5]};
end
else begin
SEL <= SEL;
end
end
// DIG信号输出
always @(*) begin
case (SEL)
6'b111_110 : DIG = 8'b1111_1001;
6'b111_101 : DIG = 8'b0010_0100;
6'b111_011 : DIG = 8'b1011_0000;
6'b110_111 : DIG = 8'b0001_1001;
6'b101_111 : DIG = 8'b1001_0010;
6'b011_111 : DIG = 8'b1000_0010;
default: DIG = 8'b1111_1111;
endcase
end
endmodule
2.2 验证结果
- 效果图如下:
三、进阶篇
3.1 原理及代码
- 该部分主要进一步探究数码管 SEL 与 DIG 之间配合显示
实现目标:让 6 位数码管从右往左滑动显示 0 - 9,当显示 9 时,后面连续显示 “-”,直到 9 消失,又从 0 开始滑动
- 可以自己画图分析如何实现,这里我将数码管显示分为 16 个状态,每经过 0.5 s 的时间就向左滑动一下,也就是从一个状态跳转到下一个状态
- 将一些特殊的情况罗列出来,可以发现用状态序号+位选号来确定每一位所对应的 DIG 信号
- 进一步分析,随着状态的转移,它们之间的和就大于 9 了,那么就需要分情况讨论
- 和 <= 9
直接输出对应的 DIG 二进制
15>= 和 >= 10
输出 “-” 对应的 DIG 二进制
和 >= 16
输出 和 - 16 对应的 DIG 二进制
- 现在思路清晰了,实现代码如下:
module dig_demo #(parameter MS_1 = 17'd100_000,
MS_200 = 25'd2500_0000)(
input clk , // 50MHz时钟
input rst_n , // 复位信号
output reg [ 7:0] DIG , // 输出DIG
output reg [ 5:0] SEL // 输出SEL
);
// 信号定义
reg [16:0] cnt_flicker ; // SEL刷新频率计数器
wire [ 0:0] end_cnt_flicker ; // cnt_flicker停止计数信号
reg [24:0] cnt_200ms ; // 200ms计数器
wire [ 0:0] end_cnt_200ms ; // cnt_200ms停止计数使能信号
reg [ 3:0] cnt_16state ; // 16 个状态计数器
wire [ 0:0] end_cnt_16state ; // 结束计时
reg [ 2:0] SEL_now ; // SEL现态
wire [ 4:0] cnt_16state_and_SEL_now ; // cnt_16state + SEL_now
reg [ 1:0] DIG_now_status ; // DIG现态所处的情况
reg [ 3:0] DIG_now ; // DIG现态
// 逻辑实现
// SEL刷新频率计数器
/*
每过10_0000个时钟周期刷新SEL值
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 17'd0;
end
else if (end_cnt_flicker) begin
cnt_flicker <= 17'd0;
end
else begin
cnt_flicker <= cnt_flicker + 17'd1;
end
end
assign end_cnt_flicker = cnt_flicker >= MS_1 - 17'd1 ? 1'b1 : 1'b0;
// SEL现态
/*
当cnt_flicker计数到最大值10_0000个时钟周期后
SEL就从第n位跳到第n+1位
当跳到第5位后,就回到第0位
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL_now <= 'd0;
end
else if (end_cnt_flicker) begin
if (SEL_now >= 'd5) begin
SEL_now <= 'd0;
end
else begin
SEL_now <= SEL_now + 'd1;
end
end
else begin
SEL_now <= SEL_now;
end
end
// SEL信号输出
/*
根据SEL_now选择SEL输出二进制
*/
always @(*) begin
case (SEL_now)
3'd0 : SEL = 6'b111_110;
3'd1 : SEL = 6'b111_101;
3'd2 : SEL = 6'b111_011;
3'd3 : SEL = 6'b110_111;
3'd4 : SEL = 6'b101_111;
3'd5 : SEL = 6'b011_111;
default: SEL = 6'b111_111;
endcase
end
// cnt_200ms
/*
计数200ms
每过200ms,数码管就向左滑动一下
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_200ms <= 'd0;
end
else if (end_cnt_200ms) begin
cnt_200ms <= 'd0;
end
else begin
cnt_200ms <= cnt_200ms + 'd1;
end
end
assign end_cnt_200ms = cnt_200ms >= MS_200 - 'd1 ? 1'b1 : 1'b0;
// cnt_16state
/*
数码管滑动分为16个状态
每个状态持续200ms
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_16state <= 'd0;
end
else if (end_cnt_200ms) begin
if (end_cnt_16state) begin
cnt_16state <= 'd0;
end
else begin
cnt_16state <= cnt_16state + 'd1;
end
end
else begin
cnt_16state <= cnt_16state;
end
end
assign end_cnt_16state = cnt_16state >= 4'd15 ? 1'b1 : 1'b0;
// 计算当前16个状态中的一个状态值与SEL现在位数的和
assign cnt_16state_and_SEL_now = cnt_16state + SEL_now;
// DIG现态值判断
/*
根据上面计算出的和
判断当前数码管处于3个状态中的哪一个状态
0:数码管每一位显示数字
1:数码管前面显示数字,后面显示"-"
2:数码管前面显示"-",后面显示数字
*/
always @(*) begin
if (cnt_16state_and_SEL_now > 9 && cnt_16state_and_SEL_now < 16) begin
DIG_now_status <= 'd1;
end
else if (cnt_16state_and_SEL_now >= 16) begin
DIG_now_status <= 'd2;
end
else begin
DIG_now_status <= 'd0;
end
end
// DIG现态
/*
根据前面状态的判断计算出当前SEL所对应的DIG的值
*/
always @(*) begin
if (DIG_now_status == 'd0) begin
DIG_now = cnt_16state_and_SEL_now;
end
else if (DIG_now_status == 'd1) begin
DIG_now = 'd10;
end
else if (DIG_now_status == 'd2) begin
DIG_now = cnt_16state_and_SEL_now - 'd16;
end
else begin
DIG_now = DIG_now;
end
end
// DIG对应数字输出
always @(*) begin
case (DIG_now)
4'd0 : DIG = 8'b1100_0000;
4'd1 : DIG = 8'b1111_1001;
4'd2 : DIG = 8'b1010_0100;
4'd3 : DIG = 8'b1011_0000;
4'd4 : DIG = 8'b1001_1001;
4'd5 : DIG = 8'b1001_0010;
4'd6 : DIG = 8'b1000_0010;
4'd7 : DIG = 8'b1111_1000;
4'd8 : DIG = 8'b1000_0000;
4'd9 : DIG = 8'b1001_0000;
4'd10 : DIG = 8'b1011_1111;
default : DIG = 8'b1111_1111;
endcase
end
endmodule
3.2 验证结果
数码管滑动显示
四、数字时钟
4.1 原理及代码
需求分析:
- 数码管显示拥有多个界面
(1)时分秒:显示界面、设置界面
(2)年月日:显示界面、设置界面
(3)闹钟:状态界面(开启/关闭)、设置界面 - 年月日:
(1)默认主界面为时分秒显示界面,通过按键可调出年月日界面,并显示三秒钟后自动跳转回主界面(时分秒界面)
(2)进入到年月日界面后,可通过设置按键设置年月日的值,设置完成后自动保存 - 时分秒:
(1)默认主界面为时分秒显示界面,设置按键可设置当前显示界面时分秒的值,设置完成后自动保存 - 闹钟:
(1)闹钟设置为闹钟显示界面,如果开启闹钟,则显示设置的闹钟时间,如果关闭闹钟,则显示连续的 “-”,表示关闭了闹钟
(2)当时间达到闹钟设的值时,蜂鸣器播放歌曲,播放歌曲期间按键任意按键即可关闭闹钟
原理讲解:
- 说实话,这原理其实真没啥好讲的,根据上面写出来的需求一步一步添加相应的条件或者使能信号就实现出来了
- 上面两部分倒是有些原理可讲,如果能熟练写出上面的代码,那么对于一些信号的灵活应用以及代码编写的能力也能提高很多,而这时钟是真不好讲,所以只说说模块划分吧
- 数字时钟,无非就是用按键设置时钟的值,这里空口白话的说也理不清楚里面的逻辑关系,涉及到很多使能信号之间的条件关系
- 可以仿真出来看看,就很清楚明白了
- 工程文件链接:https://pan.baidu.com/s/1NR6dzZ2G6TEWzoaGswhYww?pwd=30gh——提取码:30gh
- 简单说一下工程内的文件
- 源码主要是在 rtl 下面的 .v 文件
- 看看上面那张系统设计的图,就可以知道每个文件之间的依赖关系
- 贴一张代码图片,看着还不算脑袋疼吧,有点些许的强迫症
4.2 验证结果
- 最后来看看在开发板上的效果吧
FPGA多功能数字时钟
- 视频中可以看到设置闹钟的分,递减到 59 就减不下去了,这是由于代码中闹钟模块设置分的部分,条件写岔劈了,有点粗心大意,这个我改了,将大于(>)改成了小于(<)
- 同时有一个小 bug ,在 display.v 模块中,也就是数码管显示模块,处于闹钟关闭状态下,如果一直按设置键,即使当前闹钟显示关闭状态,也还是可以设值,只是我们看不到而已
- 这是由于给 select_place 信号赋值的条件少写了,这个我还没改了,有时间可以自行研究
最后
以上就是无情手机为你收集整理的基于 FPGA 实现滑动显示、多功能数字时钟【设置年月日时分秒以及闹钟】一、数码管原理二、基础篇三、进阶篇四、数字时钟的全部内容,希望文章能够帮你解决基于 FPGA 实现滑动显示、多功能数字时钟【设置年月日时分秒以及闹钟】一、数码管原理二、基础篇三、进阶篇四、数字时钟所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复