当前位置:网站首页>基于 FPGA 实现数字时钟详细原理讲解及验证结果
基于 FPGA 实现数字时钟详细原理讲解及验证结果
2022-07-28 22:14:00 【可乐有点好喝】
本文内容:基于 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)当时间达到闹钟设的值时,蜂鸣器播放歌曲,播放歌曲期间按键任意按键即可关闭闹钟
边栏推荐
- [detailed and super simple] how to use websocket links
- SAP oracle 复制新实例后数据库远程连接报错 ora-01031
- EN 1873屋面用装配附件.塑料单个屋面灯—CE认证
- 齐博建站指南(艾戈勒)
- PowerCLi VMware vCenter 通过自建的PXE Server一键批量部署常规New-VM
- Arm-A53资料「建议收藏」
- 添加构建依赖项报错
- Best practices for migration of kingbasees v8.3 to v8.6 of Jincang database (2. Compatibility of kingbasees v8.3 and v8.6)
- EN 1935 building hardware. Single axis hinge - CE certification
- With the help of rpa+lcap, the enterprise treasurer management can be upgraded digitally
猜你喜欢

Leetcode60. 排列序列

pycharm配置运行环境

编译原理研究性学习专题 2——递归下降语法分析设计原理与实现

使用Pytorch快速训练网络模型

Equipped with a new generation of ultra safe cellular batteries, Sihao aipao is available from 139900 yuan

多传感器融合定位(二)——基于地图的定位

Is the declarative code of compose so concise?

Multisensor fusion positioning (III) -- inertial technology

Explanation of history and chemical properties of Worthington ribonuclease B

JS高级 之 ES6~ES13 新特性
随机推荐
毕业三年之际写给可能迷茫的你我[转]
【C】逆序字符串(俩种递归思路)
Worthington RNA determination detailed introduction
脲酶丨Worthington杰克豆脲酶的特性及测定方案
ISO 13400(DoIP)标准解读
实时数仓:美团点评Flink的实时数仓应用分享
剑指 Offer 55 - I. 二叉树的深度
智能垃圾桶(七)——SG90舵机的介绍与使用(树莓派pico实现)
RHCE the next day
Powercl batch creates and manages virtual switches
PowerCLi 批量添加esxi到vCenter
Ape anthropology topic 20
多传感器融合定位(三)——惯性技术
Uricase - Characteristics of uricase in Worthington pig liver:
EN 12101-8:2011 smoke dampers for smoke and heat control systems - CE certification
Working principle of fastdfs (technical principle)
C language n*n matrix evaluation and inverse matrix [easy to understand]
Blocking queue
Genomic DNA isolation Worthington ribonuclease A
1-7 解决类中方法的this指向问题