数码管显示分为静态显示和动态显示。静态显示其实并没有用,和led灯没区别。而动态显示用处很大,基本上数码管的使用都是动态显示。其原理很简单:视觉暂留效应。数码管从右到左,一个接一个的亮起熄灭,让其总的速度加快,人眼看上去就像是一直亮着一样。扫描时间间隔建议为20ms以内,人眼才不会感到闪烁。一般来说一位数码管扫描1ms就能得到不错的效果了。
一、方法1
第一种数码管动态显示:采用分频时钟扫描方法
//======================================================================
// --- 名称 : seg_display
// --- 作者 : 木子
// --- 日期 : 2024-8-30
// --- 描述 : 数码管动态显示模块,改自小梅哥FPGA
//======================================================================
module seg_display
//---------------------<端口声明>---------------------------------------
(
input clk , //时钟,50Mhz
input rst_n , //复位,低电平有效
input en , //数码管显示使能
input [31:0] data , //输入数据
output [ 7:0] seg_sel , //数码管位选
output reg [ 7:0] seg_data //数码管段选,即内容显示
);
//---------------------<信号定义>---------------------------------------
reg [14:0] cnt ;
wire add_cnt ;
wire end_cnt ;
reg clk_1k ;
reg [ 7:0] seg_sel_r ;
reg [ 3:0] data_tmp ;
//----------------------------------------------------------------------
//-- 1k分频,扫描一个数目管时间为1ms
//----------------------------------------------------------------------
//计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else
cnt <= cnt;
end
assign add_cnt = en; //en为1才允许计数
assign end_cnt = add_cnt && cnt==25000-1;
//分频
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
clk_1k <= 0;
else if(end_cnt)
clk_1k <= ~clk_1k;
else
clk_1k <= clk_1k;
end
//----------------------------------------------------------------------
//-- 数码管扫描,8位循环扫描,频率为1k
//----------------------------------------------------------------------
always @(posedge clk_1k or negedge rst_n)begin
if(!rst_n)
seg_sel_r <= 8'b0000_0001;
else if(seg_sel==8'b1000_0000)
seg_sel_r <= 8'b0000_0001;
else
seg_sel_r <= seg_sel << 1;
end
//----------------------------------------------------------------------
//-- 位选,不同计数对应不同位选编码,也对应分割的不同数据
//----------------------------------------------------------------------
always @(*)begin
case(seg_sel_r)
8'b0000_0001: data_tmp = data[ 3: 0] ; // 位1
8'b0000_0010: data_tmp = data[ 7: 4] ; // 位2
8'b0000_0100: data_tmp = data[11: 8] ; // 位3
8'b0000_1000: data_tmp = data[15:12] ; // 位4
8'b0001_0000: data_tmp = data[19:16] ; // 位5
8'b0010_0000: data_tmp = data[23:20] ; // 位6
8'b0100_0000: data_tmp = data[27:24] ; // 位7
8'b1000_0000: data_tmp = data[31:28] ; // 位8
default: data_tmp = 4'b0000 ;
endcase
end
assign seg_sel = en ? seg_sel_r : 8'b0000_0000; // 位选使能
//----------------------------------------------------------------------
//-- 段选,将不同分割数据进行段选编码
//----------------------------------------------------------------------
always @(*)begin
case(data_tmp)
4'h0: seg_data = 8'b1100_0000;
4'h1: seg_data = 8'b1111_1001;
4'h2: seg_data = 8'b1010_0100;
4'h3: seg_data = 8'b1011_0000;
4'h4: seg_data = 8'b1001_1001;
4'h5: seg_data = 8'b1001_0010;
4'h6: seg_data = 8'b1000_0010;
4'h7: seg_data = 8'b1111_1000;
4'h8: seg_data = 8'b1000_0000;
4'h9: seg_data = 8'b1001_0000;
4'ha: seg_data = 8'b1000_1000;
4'hb: seg_data = 8'b1000_0011;
4'hc: seg_data = 8'b1100_0110;
4'hd: seg_data = 8'b1010_0001;
4'he: seg_data = 8'b1000_0110;
4'hf: seg_data = 8'b1011_1111;
default:seg_data = 8'b1111_1111;
endcase
end
endmodule
二、方法2
第二种数码管动态显示方法:采用直接计数扫描方法
//======================================================================
// --- 名称 : seg_display
// --- 作者 : 木子
// --- 日期 : 2024-8-30
// --- 描述 : 数码管动态显示模块
//======================================================================
module seg_display
//---------------------<参数定义>---------------------------------------
#(
parameter SEG_SEL = 8 , //数码管位数:8
parameter SEG_DATA = 8 , //数码管段数:8
parameter TIME_1MS = 50000 , //1ms时间
parameter TIME_1MS_W = 16 //1ms时间位宽
)
//---------------------<端口声明>---------------------------------------
(
input clk , //时钟,50Mhz
input rst_n , //复位,低电平有效
input en , //数码管显示使能
input [SEG_SEL*4-1:0] data , //输入数据
output [SEG_SEL -1:0] seg_sel , //数码管位选
output reg [SEG_DATA -1:0] seg_data //数码管段选,即内容显示
);
//---------------------<信号定义>---------------------------------------
reg [TIME_1MS_W-1:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
reg [2:0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg [3:0] data_tmp ;
reg [SEG_SEL-1:0] seg_sel_r ;
//----------------------------------------------------------------------
//-- 1个数码管亮1ms
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt0 <= 0;
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
else
cnt0 <= cnt0;
end
assign add_cnt0 = en; // 使能有效才计数
assign end_cnt0 = add_cnt0 && cnt0==TIME_1MS-1;
//----------------------------------------------------------------------
//-- 对各位数码管不断扫描
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt1 <= 0;
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
else
cnt1 <= cnt1;
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==SEG_SEL-1;
//----------------------------------------------------------------------
//-- 位选,不同计数对应不同位选编码,也对应分割的不同数据
//----------------------------------------------------------------------
always @(*)begin
case(cnt1)
3'd0 : begin seg_sel_r = 8'b0000_0001; data_tmp = data[ 3: 0]; end // 位1
3'd1 : begin seg_sel_r = 8'b0000_0010; data_tmp = data[ 7: 4]; end // 位2
3'd2 : begin seg_sel_r = 8'b0000_0100; data_tmp = data[11: 8]; end // 位3
3'd3 : begin seg_sel_r = 8'b0000_1000; data_tmp = data[15:12]; end // 位4
3'd4 : begin seg_sel_r = 8'b0001_0000; data_tmp = data[19:16]; end // 位5
3'd5 : begin seg_sel_r = 8'b0010_0000; data_tmp = data[23:20]; end // 位6
3'd6 : begin seg_sel_r = 8'b0100_0000; data_tmp = data[27:24]; end // 位7
3'd7 : begin seg_sel_r = 8'b1000_0000; data_tmp = data[31:28]; end // 位8
default : begin seg_sel_r = 8'b0000_0000; data_tmp = 4'b0000; end
endcase
end
assign seg_sel = en ? seg_sel_r : 8'b0000_0000; // 位选使能
//----------------------------------------------------------------------
//-- 段选,将不同分割数据进行段选编码
//----------------------------------------------------------------------
always @(*)begin
case(data_tmp)
4'h0: seg_data = 8'b1100_0000;
4'h1: seg_data = 8'b1111_1001;
4'h2: seg_data = 8'b1010_0100;
4'h3: seg_data = 8'b1011_0000;
4'h4: seg_data = 8'b1001_1001;
4'h5: seg_data = 8'b1001_0010;
4'h6: seg_data = 8'b1000_0010;
4'h7: seg_data = 8'b1111_1000;
4'h8: seg_data = 8'b1000_0000;
4'h9: seg_data = 8'b1001_0000;
4'ha: seg_data = 8'b1000_1000;
4'hb: seg_data = 8'b1000_0011;
4'hc: seg_data = 8'b1100_0110;
4'hd: seg_data = 8'b1010_0001;
4'he: seg_data = 8'b1000_0110;
4'hf: seg_data = 8'b1000_1110;
default:seg_data = 8'b1111_1111;
endcase
end
endmodule
/*
//----------------------------------------------------------------------
//-- 位选这样写也行,代码变得很秀
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_sel <= {SEG_SEL{1'b0};
else if(en)
seg_sel <= ~(1'b1<<cnt1);
else
seg_sel <= {SEG_SEL{1'b0};
end
always @(*)begin
data_tmp = data[(cnt1+1)*4-1 -:4];
end
*/
三、HC595
附上小梅哥FPGA的HC595模块的代码,可以直接搭配数码管动态显示模块使用,在顶层例化连接一下就行了。
//======================================================================
// --- 名称 : HC595
// --- 作者 : 木子
// --- 日期 : 2024-8-30
// --- 描述 : HC595驱动
//======================================================================
module HC595
//---------------------<参数定义>---------------------------------------
#(
parameter SEG_SEL = 8 , //8位
parameter SEG_DATA = 8 //8段
)
//---------------------<端口声明>---------------------------------------
(
input clk , //时钟,50Mhz
input rst_n , //复位,低电平有效
input en , //数码管使能信号
input [SEG_SEL -1:0] seg_sel , //数码管位选
input [SEG_DATA-1:0] seg_data , //数码管段选
output reg ST_CP , //存储寄存器时钟输出
output reg SH_CP , //移位寄存器时钟输出
output reg DS //串行数据输入
);
//---------------------<信号定义>---------------------------------------
reg [ 1:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
reg [ 5:0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
wire sclk ; //分频时钟信号
wire [ 5:0] sclk_cnt ; //序列机计数器
//----------------------------------------------------------------------
//-- 4分频计数器
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt0 <= 0;
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
else
cnt0 <= cnt0;
end
assign add_cnt0 = en;
assign end_cnt0 = add_cnt0 && cnt0==4-1;
assign sclk = end_cnt0;
//----------------------------------------------------------------------
//-- 序列机计数器
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt1 <= 0;
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
else
cnt1 <= cnt1;
end
assign add_cnt1 = sclk;
assign end_cnt1 = add_cnt1 && cnt1==35-1;
assign sclk_cnt = cnt1;
//-------------------------------------------------------------------------------
//-- SHCP:移位寄存器的时钟输入,上升沿时移位寄存器中的数据依次移动一位
//-- STCP:存储寄存器的时钟输入,上升沿时移位寄存器中的数据进入存储寄存器
//-- 通常STCP置为低电平,移位结束后再在ST_CP端产生一个正脉冲更新显示数据
//-------------------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ST_CP <= 0;
SH_CP <= 0;
DS <= 0;
end
else begin
case(sclk_cnt)
0: begin SH_CP <= 0; end
1: begin ST_CP <= 0; SH_CP <= 1; end
2: begin SH_CP <= 0; DS <= seg_data[7]; end
3: begin SH_CP <= 1; end
4: begin SH_CP <= 0; DS <= seg_data[6]; end
5: begin SH_CP <= 1; end
6: begin SH_CP <= 0; DS <= seg_data[5]; end
7: begin SH_CP <= 1; end
8: begin SH_CP <= 0; DS <= seg_data[4]; end
9: begin SH_CP <= 1; end
10: begin SH_CP <= 0; DS <= seg_data[3]; end
11: begin SH_CP <= 1; end
12: begin SH_CP <= 0; DS <= seg_data[2]; end
13: begin SH_CP <= 1; end
14: begin SH_CP <= 0; DS <= seg_data[1]; end
15: begin SH_CP <= 1; end
16: begin SH_CP <= 0; DS <= seg_data[0]; end
17: begin SH_CP <= 1; end
18: begin SH_CP <= 0; DS <= seg_sel[7]; end
19: begin SH_CP <= 1; end
20: begin SH_CP <= 0; DS <= seg_sel[6]; end
21: begin SH_CP <= 1; end
22: begin SH_CP <= 0; DS <= seg_sel[5]; end
23: begin SH_CP <= 1; end
24: begin SH_CP <= 0; DS <= seg_sel[4]; end
25: begin SH_CP <= 1; end
26: begin SH_CP <= 0; DS <= seg_sel[3]; end
27: begin SH_CP <= 1; end
28: begin SH_CP <= 0; DS <= seg_sel[2]; end
29: begin SH_CP <= 1; end
30: begin SH_CP <= 0; DS <= seg_sel[1]; end
31: begin SH_CP <= 1; end
32: begin SH_CP <= 0; DS <= seg_sel[0]; end
33: begin SH_CP <= 1; end
34: begin ST_CP <= 1; end
default:
begin ST_CP <= 0; SH_CP <= 0; DS <= 0 ; end
endcase
end
end
endmodule