请参阅博主以前写过的一篇电子时钟模拟,在此不再赘述。
https://blog.csdn.net/qq_54347584/article/details/130402287
数码管驱动模块:
代码分析:
/**************************************功能介绍***********************************
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_1110,//E替换为F
F = 7'b011_1111,//F替换为--
DIV = 7'b011_1111;
//---------<内部信号定义>-----------------------------------------------------
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 };
16 : seg_dig <= {point_n_r,DIV };
default: seg_dig <= 8'b1101_1111;
endcase
end
endmodule
计数器模块:
代码分析:
module counter (
input wire clk ,
input wire rst_n ,
input wire [1:0] key_in ,//按键信号输入
output wire [23:0] dout ,//数码管各位值输出
output wire [5:0] point_out //小数点输出
);
//内部参数定义
parameter TIME_10ms = 19'd500_000;//10ms计数器,计满秒表毫秒位加1
parameter TIME_990ms = 7'd100 ;//计数器毫秒位,以10ms为单位
parameter TIME_1s = 6'd60 ;//计数器秒位,计满清零
parameter TIME_1min = 6'd60 ;//计数器分位,计满清零
//内部信号定义
reg [18:0] cnt_10ms ;//10毫秒计数器寄存器
reg [6:0] cnt_99ms ;//毫秒位计数寄存器
reg [5:0] cnt_1s ;//秒位计数器寄存器
reg [5:0] cnt_1min ;//分位计数器寄存器
wire add_cnt_10ms ;
wire end_cnt_10ms ;
wire add_cnt_990ms ;
wire end_cnt_990ms ;
wire add_cnt_1s ;
wire end_cnt_1s ;
wire add_cnt_1min ;
wire end_cnt_1min ;
reg flag ;//运行/暂停标志信号寄存器
reg flag_0 ;//清零信号标志寄存器
//10毫秒计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_10ms <= 1'b0;
end
else if(add_cnt_10ms)begin
if(end_cnt_10ms)begin
cnt_10ms <= 1'b0;
end
else begin
cnt_10ms <= cnt_10ms + 1'b1;
end
end
else begin
cnt_10ms <= cnt_10ms;
end
end
assign add_cnt_10ms = 1'b1 && flag;//运行标志位有效
assign end_cnt_10ms = (add_cnt_10ms && cnt_10ms == TIME_10ms - 1'b1) || key_in[1];//按下清零按键也会清零
//毫秒位计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_99ms <= 1'b0;
end
else if(add_cnt_990ms)begin
if(end_cnt_990ms)begin
cnt_99ms <= 1'b0;
end
else begin
cnt_99ms <= cnt_99ms + 1'b1;
end
end
else begin
cnt_99ms <= cnt_99ms;
end
end
assign add_cnt_990ms = end_cnt_10ms;
assign end_cnt_990ms = (add_cnt_990ms && cnt_99ms == TIME_990ms - 1'b1) || key_in[1];
//秒位计数器
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'b0;
end
else begin
cnt_1s <= cnt_1s + 1'b1;
end
end
else begin
cnt_1s <= cnt_1s;
end
end
assign add_cnt_1s = end_cnt_990ms;
assign end_cnt_1s = (add_cnt_1s && cnt_1s == TIME_1s - 1'b1) || key_in[1];
//分位计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1min <= 1'b0;
end
else if(add_cnt_1min)begin
if(end_cnt_1min)begin
cnt_1min <= 1'b0;
end
else begin
cnt_1min <= cnt_1min + 1'b1;
end
end
else begin
cnt_1min <= cnt_1min;
end
end
assign add_cnt_1min = end_cnt_1s;
assign end_cnt_1min = (add_cnt_1min && cnt_1min == TIME_1min - 1'b1) || key_in[1];
//flag信号控制秒表运行/暂停
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(key_in[0])begin
flag <= ~flag;
end
else begin
flag <= flag;
end
end
//输出值赋值
assign dout[23:20] = cnt_99ms % 10;
assign dout[19:16] = cnt_99ms / 10;
assign dout[15:12] = cnt_1s % 10;
assign dout[11:8] = cnt_1s / 10;
assign dout[7:4] = cnt_1min % 10;
assign dout[3:0] = cnt_1min / 10;
assign point_out = 6'b110_101 ;
endmodule
按键消抖模块:
代码分析:请详细参阅博主所写的按键消抖模块博文,在此不再赘述
module key_filter#(parameter WIDTH = 2)//参数化按键位宽
(
input wire clk ,
input wire rst_n ,
input wire [WIDTH - 1:0] key_in ,//按键输入信号
output reg [WIDTH - 1:0] key_out //输出稳定的脉冲信号
);
parameter MAX = 20'd1_000_000;
reg [19:0] cnt_delay ; //20ms延时计数寄存器
wire add_cnt_delay ; //开始计数的标志
wire end_cnt_delay ; //结束计数的标志
reg [WIDTH - 1:0] key_r0 ; //同步
reg [WIDTH - 1:0] key_r1 ; //打一拍
reg [WIDTH - 1:0] key_r2 ; //打两拍
wire [WIDTH - 1:0] nedge ; //下降沿寄存器
//同步打拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in; //同步
key_r1 <= key_r0; //寄存一拍
key_r2 <= key_r1; //寄存两拍
end
end
//20ms计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 1'b0;
end
else if(add_cnt_delay )begin
if(nedge)begin //检测到下降沿从0开始计数
cnt_delay <= 1'b0;
end
else if(cnt_delay == MAX - 1'b1)begin
cnt_delay <= cnt_delay; //计数计满结束后保持,避免产生多个输出脉冲
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
else begin
cnt_delay <= 1'b0;
end
end
assign nedge = ~key_r1 & key_r2; //下降沿检测
assign add_cnt_delay = 1'b1;
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX - 1'b1;
//key_out脉冲信号赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out <= 'd0;
end
else if(cnt_delay == MAX - 2'd2)begin //计数计满前一个脉冲时产生按键脉冲
key_out <= ~key_in;
end
else begin
key_out <= 'd0;
end
end
endmodule
顶层模块:
/****
项目说明:本次项目是为了通过数码管实现秒表模拟。其中,六位数码管分别显示秒表的分位,秒位,毫秒位(百位和十位)。
本次项目拟设置四个模块,分别为:按键消抖模块,计数模块,数码管驱动模块,以及顶层模块。
本项目按键消抖模块要求传出两个按键的脉冲信号,一个用来暂停/开始秒表的计数,一个用来清空秒表的计数。
本项目计数模块要求能传出秒表的各位值以及小数点位置
本项目数码管驱动模块要求能正常显示秒表各位值
****/
module top(
input wire clk ,
input wire rst_n ,
input wire [1:0] key_in ,
output wire [5:0] sel ,
output wire [7:0] seg
);
wire [1:0] key_out ;
wire [23:0] din ;
wire [5:0] point_n ;
seg_driver u_seg_driver(
.clk (clk ),
.rst_n (rst_n ),
.din (din ),
.point_n (point_n),
.seg_sel (sel ),
.seg_dig (seg )
);
key_filter u_key_filter(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_in ),
.key_out (key_out)
);
counter u_counter(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_out ),
.dout (din ),
.point_out (point_n )
);
endmodule
本项目主要锻炼了FPGA的数码管开发和计数器编写,实际仍未FPGA学习基础,博主学习时常常被数码管位选信号的段选信号的赋值绕晕,解决方法也只有自己亲自编写几遍,否则仍然无法理解余晖效应和动态扫面是如何让数码管同时显示不同字符的。后续对于基础部分的学习博主应该还会写几篇关于蜂鸣器的博文,至此算是基础语法学习结束。
再往后博主学习到IP核HLS,通讯协议时,也许会继续编写博客,但也要看博主是否有足够的精力,以及能否自己理解并讲解清楚,否则话的还是请各位自行上网寻找视频教学资料,不管是野火还是正点原子,都有针对的FPGA学习视频。
学海无涯,大家有缘再见。