文章目录
- 一、数码管简介
- 二、C4开发板数码管原理图
- 三、代码实现
- 四、实现效果
- 五、总结
博主在之前曾经编写过一篇电子时钟的博客(详情请见此篇博文),但曾经编写的电子时钟,未显示小数点位,同时当时的数码管模块是为了电子时钟而进行修改的,并没有对数码管驱动模块进行模块化处理。而此篇博文的数码管驱动已经进行了模块化处理,十分便于重复使用,在此篇博客之前的电子秒表模拟中,博主已经使用过该数码管驱动模块,因此后文不再赘述此模块(详情请见此篇博文)。
一、数码管简介
博主所用的开发板为Cyclone Ⅳ的EP4CE6F17C8,Cyclone IV开发板上的数码管一共有6个,6个数码管共用八个段选信号引脚,因此我们每次只能选择其中一个显示。
怎么解决电子时钟时、分、秒同时显示呢?要实现电子时钟首先要了解什么是余晖效应。
余晖效应一般指视觉暂留。 视觉暂留现象即视觉暂停现象(Persistence of vision,Visual staying phenomenon,duration of vision)又称“余晖效应”。只要数码管位选信号切换得足够快,数码管由亮到灭这一过程是需要一段时间的,由于时间很短,我们的眼睛是没有办法分清此时此刻数码管的状态,给人的感觉就是数码管是一直亮的。以此来达到欺骗人眼的效果,这样就可以实现同时显示时、分、秒。
二、C4开发板数码管原理图
三、代码实现
本项目博主一共设计了三个模块,分别为:时钟计数器模块、数码管驱动快以及顶层模块。
时钟计数器模块:
源码分析:
- 在时钟计数器模块中,博主一共设计了四个计数器,分别为:1s基准计数器,时钟秒位计数器,时钟分位计数器,时钟小时位计数器
- 四个计数器通过级联的方式,依次递增,从而实现时钟的计数功能
- 如果对计数器级联较为陌生,可以参考博主开头所说的电子秒表博文,在此不再赘述
module counter_clock(
input wire clk ,
input wire rst_n ,
output wire [23:0] dout ,//输出六位数码管的值
output wire [5:0] point_out //输出小数点
);
//参数定义
parameter MAX1S = 26'd5000_0000 ;//1s基准单位
parameter TIME_SEC = 6'd60 ;//秒计数器最大值
parameter TIME_MIN = 6'd60 ;//分计数器最大值
parameter TIME_HOUR = 5'd24 ;//小时计数器最大值
//内部信号定义
reg [25:0] cnt_1s ;
wire add_cnt_1s ;
wire end_cnt_1s ;
reg [5:0] cnt_sec ;
wire add_cnt_sec ;
wire end_cnt_sec ;
reg [5:0] cnt_min ;
wire add_cnt_min ;
wire end_cnt_min ;
reg [4:0] cnt_hour ;
wire add_cnt_hour ;
wire end_cnt_hour ;
//1s基准计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1s <= 1'b0;
end
else if(add_cnt_1s)begin
if(end_cnt_1s)begin
cnt_1s <= 1'b1;
end
else begin
cnt_1s <= cnt_1s + 1'b1;
end
end
else begin
cnt_1s <= cnt_1s;
end
end
assign add_cnt_1s = 1'b1;
assign end_cnt_1s = add_cnt_1s && cnt_1s == MAX1S - 1'b1;
//秒计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sec <= 1'b0;
end
else if(add_cnt_sec)begin
if(end_cnt_sec)begin
cnt_sec <= 1'b0;
end
else begin
cnt_sec <= cnt_sec + 1'b1;
end
end
else begin
cnt_sec <= cnt_sec;
end
end
assign add_cnt_sec = end_cnt_1s;
assign end_cnt_sec = add_cnt_sec && cnt_sec == TIME_SEC - 1'b1;
//分钟计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_min <= 1'b0;
end
else if(add_cnt_min)begin
if(end_cnt_min)begin
cnt_min <= 1'b0;
end
else begin
cnt_min <= cnt_min + 1'b1;
end
end
else begin
cnt_min <= cnt_min;
end
end
assign add_cnt_min = end_cnt_sec;
assign end_cnt_min = add_cnt_min && cnt_min == TIME_MIN - 1'b1;
//小时计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_hour <= 1'b0;
end
else if(add_cnt_hour)begin
if(end_cnt_hour)begin
cnt_hour <= 1'b0;
end
else begin
cnt_hour <= cnt_hour + 1'b1;
end
end
else begin
cnt_hour <= cnt_hour;
end
end
assign add_cnt_hour = end_cnt_min;
assign end_cnt_hour = add_cnt_hour && cnt_hour == TIME_HOUR - 1'b1;
//dout point_out赋值
assign dout[23:20] = cnt_sec % 10;
assign dout[19:16] = cnt_sec / 10;
assign dout[15:12] = cnt_min % 10;
assign dout[11:8] = cnt_min / 10;
assign dout[7:4] = cnt_hour % 10;
assign dout[3:0] = cnt_hour / 10;
assign point_out = 6'b110_101 ;
endmodule
数码管驱动模块:
/**************************************功能介绍***********************************
Date : 2023-08-01 11:08:11
Author : majiko
Version : 1.0
Description: 动态数码管模块(动态扫描)
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module seg_driver(
input clk ,
input rst_n ,
input [23:0] din ,//输入6位数码管显示数据,每位数码管占4位
input [5:0] point_n ,//输入小数点控制位
output reg [5:0] seg_sel ,//输出位选
output reg [7:0] seg_dig //输出段选
);
//---------<参数定义>---------------------------------------------------------
parameter TIME_1MS = 50_000;//1ms
//数码管显示字符编码
localparam NUM_0 = 7'b100_0000,//0
NUM_1 = 7'b111_1001,//1
NUM_2 = 7'b010_0100,//
NUM_3 = 7'b011_0000,//
NUM_4 = 7'b001_1001,//
NUM_5 = 7'b001_0010,//
NUM_6 = 7'b000_0010,//
NUM_7 = 7'b111_1000,//
NUM_8 = 7'b000_0000,//
NUM_9 = 7'b001_1000,//
A = 7'b000_1000,//
B = 7'b000_0011,//b
C = 7'b100_0110,//
D = 7'b010_0001,//d
E = 7'b000_0110,//
F = 7'b000_1110;//
//---------<内部信号定义>-----------------------------------------------------
reg [15:0] cnt_1ms ;//1ms计数器(扫描间隔计数器)
wire add_cnt_1ms ;
wire end_cnt_1ms ;
reg [3:0] disp_data ;//每一位数码管显示的数值
reg point_n_r ;//每一位数码管显示的小数点
//****************************************************************
//--cnt_1ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1ms <= 'd0;
end
else if(add_cnt_1ms)begin
if(end_cnt_1ms)begin
cnt_1ms <= 'd0;
end
else begin
cnt_1ms <= cnt_1ms + 1'b1;
end
end
end
assign add_cnt_1ms = 1'b1;//数码管一直亮
assign end_cnt_1ms = add_cnt_1ms && cnt_1ms == TIME_1MS - 1;
//****************************************************************
//--seg_sel
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
seg_sel <= 6'b111_110;//循环移位实现时,需要给位选赋初值
end
else if(end_cnt_1ms)begin
seg_sel <= {seg_sel[4:0],seg_sel[5]};//循环左移
end
end
//****************************************************************
//--disp_data
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
disp_data <= 'd0;
point_n_r <= 1'b1;
end
else begin
case (seg_sel)
6'b111_110 : begin disp_data <= din[3:0] ; point_n_r <= point_n[0]; end//第一位数码管显示的数值
6'b111_101 : begin disp_data <= din[7:4] ; point_n_r <= point_n[1]; end
6'b111_011 : begin disp_data <= din[11:8] ; point_n_r <= point_n[2]; end
6'b110_111 : begin disp_data <= din[15:12]; point_n_r <= point_n[3]; end
6'b101_111 : begin disp_data <= din[19:16]; point_n_r <= point_n[4]; end
6'b011_111 : begin disp_data <= din[23:20]; point_n_r <= point_n[5]; end
default: disp_data <= 'd0;
endcase
end
end
//****************************************************************
//--seg_dig
//****************************************************************
// always @(posedge clk or negedge rst_n)begin
// if(!rst_n)begin
// seg_dig <= 8'hff;//数码管的段选如何赋值好?
// end
// else begin
// case (disp_data)
// 0 : seg_dig <= {point_n_r,NUM_0};
// 1 : seg_dig <= {point_n_r,NUM_1};
// 2 : seg_dig <= {point_n_r,NUM_2};
// 3 : seg_dig <= {point_n_r,NUM_3};
// 4 : seg_dig <= {point_n_r,NUM_4};
// 5 : seg_dig <= {point_n_r,NUM_5};
// 6 : seg_dig <= {point_n_r,NUM_6};
// 7 : seg_dig <= {point_n_r,NUM_7};
// 8 : seg_dig <= {point_n_r,NUM_8};
// 9 : seg_dig <= {point_n_r,NUM_9};
// 10 : seg_dig <= {point_n_r,A };
// 11 : seg_dig <= {point_n_r,B };
// 12 : seg_dig <= {point_n_r,C };
// 13 : seg_dig <= {point_n_r,D };
// 14 : seg_dig <= {point_n_r,E };
// 15 : seg_dig <= {point_n_r,F };
// default: seg_dig <= 8'hff;
// endcase
// end
// end
always @(*)begin
case (disp_data)
0 : seg_dig <= {point_n_r,NUM_0};
1 : seg_dig <= {point_n_r,NUM_1};
2 : seg_dig <= {point_n_r,NUM_2};
3 : seg_dig <= {point_n_r,NUM_3};
4 : seg_dig <= {point_n_r,NUM_4};
5 : seg_dig <= {point_n_r,NUM_5};
6 : seg_dig <= {point_n_r,NUM_6};
7 : seg_dig <= {point_n_r,NUM_7};
8 : seg_dig <= {point_n_r,NUM_8};
9 : seg_dig <= {point_n_r,NUM_9};
10 : seg_dig <= {point_n_r,A };
11 : seg_dig <= {point_n_r,B };
12 : seg_dig <= {point_n_r,C };
13 : seg_dig <= {point_n_r,D };
14 : seg_dig <= {point_n_r,E };
15 : seg_dig <= {point_n_r,F };
default: seg_dig <= 8'hff;
endcase
end
endmodule
顶层模块:
module top_clock (
input wire clk ,
input wire rst_n ,
output wire [5:0] sel ,//输出位选信号
output wire [7:0] seg //输出段选信号
);
//内部信号定义
wire [23:0] din ;
wire [5:0] point_out ;
//计数器例化
counter_clock u_counter_clock(
.clk (clk ),
.rst_n (rst_n ),
.dout (din ),
.point_out (point_out)
);
seg_driver u_seg_driver(
.clk (clk ),
.rst_n (rst_n ),
.din (din ),
.point_n (point_out),
.seg_sel (sel ),
.seg_dig (seg )
);
endmodule
四、实现效果
后续添加
五、总结
本项目与之前的电子秒表模拟并无大异,理解了数码管驱动模块的原理并自己成功编写一次后,后续再有相关数码管的项目均可直接调用此模块,无需再次编写。
除去数码管驱动模块,该项目和电子秒表一样,实际都是在训练计数器的级联。
博主在学习FPGA时曾听过一句话:FPGA实际上就是无数个计数器和状态机。因此请大家不要忽视对计数器的练习,经过电子秒表模拟和电子时钟模拟后,博主对计数器和数码管的基础知识掌握的还不错,因此撰写了几篇博文,希望对大家能有所帮助。