由上图数码管接线可知数码管LED灯的所有阳级(正极)接公共端,故该数码管为共阳数码管。这六位数码管段阴级(负极)都并联,在阳极都给高电平的同时阴极拉低,数码管就会被点亮。
数码管显示有分为静态显示和动态显示,静态显示特点是在同一时间上所有位显示是相同的数值,显示单一无法满足更多的需求。往往使用更加更多的是动态显示,程序设计会更加复杂。动态显示可以实现六位数码管上内容都不一样比如显示742347这个数。
在余晖效应的作用下,若一个数码管在 1s 内点亮两次,人眼很明显的看到其亮了两次,若 1s 内点亮 10 次呢?只能看到其在快速的闪烁,若点亮 100 次 1000次呢?总有一个速度是人眼分辨不出来在闪烁的(1ms)。所以一个数码管一直亮不需要一直给高电平,只要在很短的时间点亮一次后熄灭,再点亮熄灭。在六位数码管中,在很短的时间点亮第一位数码管的同时,驱动段选(发此位数码管需显示的数据),然后在点亮下一位同时驱动段选,六位以此循环看起来六位数码管就是同一时刻显示且显示的内容不一样,这样的方法称为动态扫描。
最后总结动态显示的驱动方式:使用 1ms 的刷新时间让六个数码管轮流显示,:第 1ms 点第一个数码管,第 2ms 点亮第二个数码管,以此类推依次点亮六个数码管, 6ms 一个轮回。点亮相应数码管的时候给驱动段选使其显示相应的值,这样就可以使六个数码管显示不同的值,并且人眼察觉不到数码管在闪烁。
重点学习动态,这里只提供波形、代码给大家参考。
实验效果:控制六位数码管让其以 000000、 111111、 222222 一直到 FFFFFF 循环显示。每个字符显示 0.5s 后变化。
`timescale 1ns/1ns
module seg_static
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
parameter CNT_WAIT_MAX = 25'd24_999_999; //计数器最大值(0.5s)
parameter SEG_0 = 8'b1100_0000, SEG_1 = 8'b1111_1001,
SEG_2 = 8'b1010_0100, SEG_3 = 8'b1011_0000,
SEG_4 = 8'b1001_1001, SEG_5 = 8'b1001_0010,
SEG_6 = 8'b1000_0010, SEG_7 = 8'b1111_1000,
SEG_8 = 8'b1000_0000, SEG_9 = 8'b1001_0000,
SEG_A = 8'b1000_1000, SEG_B = 8'b1000_0011,
SEG_C = 8'b1100_0110, SEG_D = 8'b1010_0001,
SEG_E = 8'b1000_0110, SEG_F = 8'b1000_1110;
parameter IDLE = 8'b1111_1111;
//reg define
reg add_flag ;
reg [24:0] cnt_wait ;
reg [3:0] num ;
//cnt_wait:0.5秒计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 25'd0;
else if(cnt_wait == CNT_WAIT_MAX)
cnt_wait <= 25'd0;
else
cnt_wait <= cnt_wait + 1'b1;
//add_flag:0.5s拉高一个标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
add_flag <= 1'b0;
else if(cnt_wait == CNT_WAIT_MAX)
add_flag <= 1'b1;
else
add_flag <= 1'b0;
//num:从 4'h0 加到 4'hf 循环
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
num <= 4'd0;
else if(add_flag == 1'b1)
num <= num + 1'b1;
else
num <= num;
//sel:选中六个数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 6'b000000;
else
sel <= 6'b111111;
//给要显示的值编码
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= IDLE;
else case(num)
4'd0: seg <= SEG_0;
4'd1: seg <= SEG_1;
4'd2: seg <= SEG_2;
4'd3: seg <= SEG_3;
4'd4: seg <= SEG_4;
4'd5: seg <= SEG_5;
4'd6: seg <= SEG_6;
4'd7: seg <= SEG_7;
4'd8: seg <= SEG_8;
4'd9: seg <= SEG_9;
4'd10: seg <= SEG_A;
4'd11: seg <= SEG_B;
4'd12: seg <= SEG_C;
4'd13: seg <= SEG_D;
4'd14: seg <= SEG_E;
4'd15: seg <= SEG_F;
default:seg <= IDLE ; //闲置状态,不显示
endcase
endmodule
实验目标:六位数码管显示从 0 开始计数,每 0.1s 加 1,一直到加到十进制数999999,到达 999999 之后回到 0 开始重新计数,以此循环.
根据框图可以看到实验一共分为 4 个模块,下面分模块介绍。
模块功能:计时器每过0.1s,数据加1直至999_999后回到1又循环计数。如数码管要显示其他内数值,在此模块进行更改即可。
`timescale 1ns / 1ns
module seg_data
#(
parameter CNT_MAX = 23'd4999_999 , //最大计数值0.1s:5000_000个脉冲
//5000_000 x 20ns = 100_000_000ns = 0.1s
parameter DATA_MAX = 20'd99 //计数最大值
)
(
input sys_clk ,
input sys_rst_n ,
output reg [19:0] data
);
//计数器cnt与最大计数值搭配使用,用于计时100ms(0.1s)
reg [22:0] cnt;
//0.1s计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt <= 23'b0;
else if(cnt == CNT_MAX)
cnt <= 23'b0;
else
cnt <= cnt + 1'b1;
//计数器每过0.1s,数码从0累加到999999
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
data <= 20'b0;
else if((cnt == CNT_MAX) && (data == DATA_MAX))
data <= 20'b0;
else if (cnt == CNT_MAX)
data <= data +1'b1;
else
data <= data;
endmodule
模块功能: FPGA内部只能识别进行二进制数0与1,故无法直接在数码管上显示十进制数,这里采用二进制转BCD码的方式进行转换。假设要显示数值 (134)D,它在FPGA内部存储为 (10000110)B,直接将其显示的话是划分为 4位一组的十六进制,效果为 (86)H。这与预期显示的134不同,不方便读取数据。这里采用将(134)D转化为BCD码 (0001_0011_0100)BCD 后显示。
BCD码:又称二 —十进制码,用4位二进制数表示一位十进制数的一种形式。例如4——>0100
下面以显示十进制数234为例,介绍如何将其转化为对应的 8421BCD 码。
首先,准备工作我们需在输入的二进制数前进行补零,补零的个数由十进制数位数决定,故234有三位数需要12个零。第一个脉冲到来进行BCD码的判断,当BCD码大于4则加3,反之BCD码保持不变。再将输入的二进制向BCD码移入一位,第二个脉冲到来又进行BCD码的判断,重复上述过程至第八个脉冲判断移位结束,第九个脉冲取出转换结果给数据驱动模块。(此方法理解会用即可)
`timescale 1ns/1ns
module bcd_8421
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [19:0] data , //输入需要转换的数据
output reg [3:0] unit , //个位BCD码
output reg [3:0] ten , //十位BCD码
output reg [3:0] hun , //百位BCD码
output reg [3:0] tho , //千位BCD码
output reg [3:0] t_tho , //万位BCD码
output reg [3:0] h_hun //十万位BCD码
);
reg [4:0] cnt_shift ;//移位判断计数器 移动次数由十转为二进制的位数决定(这里显示为十进制的六位数码故对应二进制最多为20位)
reg [43:0] data_shift ;//移位判断数据 寄存器 存放BCD码与二进制码
reg shift_flag ;//移位判断标志信号 用于控制移位、判断的顺序
//shift_flag:移位判断标志信号,低电平判断 高电平移位,先移位再判断
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shift_flag <= 1'b0;
else
shift_flag <= ~shift_flag;
//cnt_shift: 从0到21循环计数(当计为20时进行移位计数,21时则是取BCD码数据)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_shift <= 5'd0;
else if((cnt_shift == 5'd21) && (shift_flag == 1'b1))
cnt_shift <= 5'd0;
else if(shift_flag == 1'b1)
cnt_shift <= cnt_shift + 1'b1;
else
cnt_shift <= cnt_shift;
//判断、移位数据寄存器,存放BCD码与二进制码
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_shift <= 44'b0;
else if(cnt_shift == 5'd0) //计数器为0时,BCD码为零
data_shift <= {24'b0,data};
else if((cnt_shift <= 20) && (shift_flag == 1'b0))// <=为小于等于,判断操作
begin //三目运算
data_shift[23:20] <= (data_shift[23:20] > 4) ?
(data_shift[23:20] + 2'd3) : (data_shift[23:20]);
data_shift[27:24] <= (data_shift[27:24] > 4) ?
(data_shift[27:24] + 2'd3) : (data_shift[27:24]);
data_shift[31:28] <= (data_shift[31:28] > 4) ?
(data_shift[31:28] + 2'd3) : (data_shift[31:28]);
data_shift[35:32] <= (data_shift[35:32] > 4) ?
(data_shift[35:32] + 2'd3) : (data_shift[35:32]);
data_shift[39:36] <= (data_shift[39:36] > 4) ?
(data_shift[39:36] + 2'd3) : (data_shift[39:36]);
data_shift[43:40] <= (data_shift[43:40] > 4) ?
(data_shift[43:40] + 2'd3) : (data_shift[43:40]);
end
else if((cnt_shift <= 20) && (shift_flag == 1'b1)) // <=为小于等于,移位操作
data_shift <= data_shift << 1;
else
data_shift <= data_shift;
//当计数器等于21时,将结果输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_hun <= 4'b0;
end
else if(cnt_shift == 5'd21) //输出BCD码
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
endmodule
该模块得到BCD码后进行数码管位控制和段控制,驱动数码管显示。
`timescale 1ns/1ns
module seg_dynamic
#(
parameter CNT_MAX = 16'd49_999 //数码管 刷新 时间计数最大值 1ms
)
(
input wire sys_clk ,
input wire [19:0] data ,
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
wire [3:0] unit ; //个位数
wire [3:0] ten ; //十位数
wire [3:0] hun ; //百位数
wire [3:0] tho ; //千位数
wire [3:0] t_tho ; //万位数
wire [3:0] h_hun ; //十万位数
reg [23:0] data_reg ; //待显示数据寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [5:0] sel_reg ; //位选信号
reg [3:0] data_disp ; //当前数码管显示的数据
//第一部分:生成1ms计数器
//cnt_1ms:1ms计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_MAX)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
//flag_1ms:1ms标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_1ms <= 1'b0;
else if(cnt_1ms == CNT_MAX - 1'b1)
flag_1ms <= 1'b1;
else
flag_1ms <= 1'b0;
//第二部分:位选驱动
//cnt_sel:每过1ms,计数加一
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))
cnt_sel <= 3'd0;
else if(flag_1ms == 1'b1)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
//选通位选信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel_reg <= 6'b000_000;
else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
sel_reg <= 6'b000_001;
else if(flag_1ms == 1'b1)
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
//sel:数码管位选信号赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 6'b000_000;
else
sel <= sel_reg;
//第三部分:段选驱动
//data_reg:控制数码管显示数据 (4'd11 标记为段选内容不显示)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的 十万 位为非零数据则六个数码管全显示
else if(h_hun) //h_hun非空即数据有六位
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的 万 位为非零数据则值显示在5个数码管上
else if(t_tho)
data_reg <= {4'd11,t_tho,tho,hun,ten,unit};
//若显示的十进制数的 千 位为非零数据则值显示4个数码管
else if(tho)
data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
//若显示的十进制数的 百 位为非零数据则值显示3个数码管
else if(hun)
data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十进制数的 十 位为非零数据则值显示2个数码管
else if(ten)
data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
//若上面都不满足就只显示 个 位数码管
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
//区分数码管高位和低位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_disp <= 4'b0;
else if(flag_1ms == 1'b1)
case(cnt_sel)
3'd0: data_disp <= data_reg[3:0] ;//给第1个数码管赋个位值
3'd1: data_disp <= data_reg[7:4] ;//给第2个数码管赋十位值
3'd2: data_disp <= data_reg[11:8] ;//给第3个数码管赋百位值
3'd3: data_disp <= data_reg[15:12];//给第4个数码管赋千位值
3'd4: data_disp <= data_reg[19:16];//给第5个数码管赋万位值
3'd5: data_disp <= data_reg[23:20];//给第6个数码管赋十万位值
default:data_disp <= 4'b0 ;
endcase
else
data_disp <= data_disp;
//控制各位数码管段选,显示内容
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= 8'b1111_1111;
else
case(data_disp)
4'd0 : seg <= 8'b1100_0000; //显示数字0
4'd1 : seg <= 8'b1111_1001; //显示数字1
4'd2 : seg <= 8'b1010_0100; //显示数字2
4'd3 : seg <= 8'b1011_0000; //显示数字3
4'd4 : seg <= 8'b1001_1001; //显示数字4
4'd5 : seg <= 8'b1001_0010; //显示数字5
4'd6 : seg <= 8'b1000_0010; //显示数字6
4'd7 : seg <= 8'b1111_1000; //显示数字7
4'd8 : seg <= 8'b1000_0000; //显示数字8
4'd9 : seg <= 8'b1001_0000; //显示数字9
default:seg <= 8'b1111_1111;
endcase
bcd_8421 bcd_8421_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.data (data ), //输入需要转换的数据
.unit (unit ), //个位BCD码
.ten (ten ), //十位BCD码
.hun (hun ), //百位BCD码
.tho (tho ), //千位BCD码
.t_tho (t_tho ), //万位BCD码
.h_hun (h_hun ) //十万位BCD码
);
endmodule
模块功能:顶层设计划分出来系统的层次,不涉及代码的编写,只是系统的输入输出口与功能模块的例化。
`timescale 1ns / 1ns
module top_seg
(
input sys_clk,
input sys_rst_n,
output [5:0] sel, //数码管位选信号
output [7:0] seg //数码管段选信号
);
wire [19:0] data; //数码管显示的数值
wire en; //数码管显示使能信号,高电平有效
seg_data seg_data_inst( //数据产生模块,每过0.1s数据自加一
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.data (data)
);
seg_dynamic seg_dynamic_inst( //数码管动态显示模块
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.data (data) ,
.sel (sel) ,
.seg (seg)
);
endmodule
逐个模块进行激励,查看波形。过于繁琐这里不展示。
1. 理解并掌握十进制转BCD码的原因和原理。
2. 理解层次化设计的好处。
说明:
本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,以上内容如有疑惑或错误欢迎评论区指出,或者移步B站观看野火家视频教程。
开发软件:ise14.7 仿真:modelsim 10.5
如需上述资料私信或留下邮箱。