【相关课程推荐】 课程名称:梦翼师兄FPGA培训(视频加板卡),手把手带你入门FPGA
写在前面的话
在项目设计中,我们通常需要一些显示设备来显示我们需要的信息,可以选择的显示设备更是种类繁多,玲琅满目,数码管无疑是最常用,最简单的显示设备之一。本节,梦翼师兄和大家一起学习数码管的显示原理和驱动方式,为我们以后项目的开发做好准备。
项目需求
设计一个数码管的驱动电路,使数码管能够同时显示出任意的六位数字(梦翼师兄使用的开发板集成的数码管为六位连体数码管)。
原理分析
数码管作为一种外设,我们首先需要了解它的工作原理以及它的对应电路连接关系,七段数码管结构示意图如下:
顾名思义,七段数码管就是使用七段点亮的线段来拼成常见的数字和某些字母,这种显示方式我们在数字电路中非常容易见到。再加上右下角显示的小数点,实际上一个显示单元包括了8根信号线。根据电路设计的不同,这些信号线可能高有效也可能低有效。我们通过FPGA控制这些线段的亮灭,就可以达到相应的显示效果。
对于多个数码管的显示模块,将每一个都连接到FPGA的管脚会耗用大量FPGA的管脚资源。因此我们同样引入一种类似矩阵键盘的扫描方式。任何时刻我们只使用8根信号点亮一个数码管,但是8个数码管是随着时钟步调交替点亮的,只要时钟的速度够快,我们观察到数码管就好像几个同时点亮一样。梦翼师兄使用的开发板原理图如下:
如图所示,我们的开发板使用的是六位共阳极数码管,六个PNP型三极管分别作为六组数码管电源的输入开关,也就是我们常说的位选信号,PNP三极管为低电平导通,所以我们的位选信号低有效。在这里,为了节约FPGA的IO资源,我们把六个位选信号连接到了三八译码器74HC138D,该三八译码器的真值表如下:
由此,我们可以得出结论,当{SEL2, SEL1, SEL0}=3’b000时,Y0变为低电平,而由于Y0连接到了第一个数码管,所以第一个数码管点亮。当{SEL2, SEL1, SEL0}=3’b001时,对应第二个数码管点亮,以此类推。SEG_0到SEG_7分别对应二极管a-g以及“小数点”,即我们所说的段选信号。由于是共阳极数码管,所以二极管只要给低电平就可以点亮,根据点亮的二极管不同,就可以显示出不同的字符。假如我们要点亮第一个数码管,并且显示出字符“A”,那么我们就只需要选中第一个数码管{SEL2, SEL1, SEL0}=3’b000,而且SEG=8’b1000_1000。
如果要让数码管“全部亮起来”,并同时显示相同字符,那我们只能通过比较快速的切换位选信号来实现这一目的。但是切换频率如果过高,数码管显示也会出现不稳定的状态,这和器件的工艺有关,我们可以选择切换的经验频率1KHZ。那么这时,我们就需要用到分频模块,将50MHZ的晶振时钟分频成我们所需要的1KHZ。
单个数码管显示
单个数码管显示的系统架构
单个数码管显示最大的数字是十六进制中的F(15),15对应的二进制数是4’b1111,所以我们的输入应该是四位。
单个数码管显示的模块模块功能介绍
单个数码管显示模块的端口描述
端口名
| 端口说明
| clk
| 系统时钟输入
| Rst_n
| 系统复位
| Data[3:0]
| 数据输入
| sel[2:0]
| 片选信号输出
| seg[7:0]
| 段选信号输出
| 代码解释
SEG7模块代码 /****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function:控制单个数码管显示任意的数字
*****************************************************/
00 module SEG7 (
01 clk, //系统时钟
02 rst_n,//系统复位
03 data, //输入数据
04 seg,//数码管段选
05 sel//数码管位选
06 );
07 //系统输入
08 input clk;//系统时钟
09 input rst_n;//系统复位
10 input [3:0] data;//输入数据
11 //系统输出
12 output reg [7:0] seg;//数码管段选
13 output reg [2:0] sel;//数码管位选
14
15 always @ (posedge clk or negedge rst_n)
16 begin
17 if (!rst_n)//复位的时候选择第一个数码管
18 begin
19 sel <= 0;
20 end
21 else
22 begin//选择第一个数码管
23 sel <= 0;
24 end
25 end
26
27 always @ (*)//用组合逻辑进行输出段选信号
28 begin
29 if (!rst_n)//复位的时候数码管熄灭
30 begin
31 seg = 8'b1111_1111;
32 end
33 else
34 begin
35 case(data)
36 0 : seg = 8'b1100_0000;//显示“0”
37 1 : seg = 8'b1111_1001;//显示“1”
38 2 : seg = 8'b1010_0100;//显示“2”
39 3 : seg = 8'b1011_0000;//显示“3”
40 4 : seg = 8'b1001_1001;//显示“4”
41 5 : seg = 8'b1001_0010;//显示“5”
42 6 : seg = 8'b1000_0010;//显示“6”
43 7 : seg = 8'b1111_1000;//显示“7”
44 8 : seg = 8'b1000_0000;//显示“8”
45 9 : seg = 8'b1001_0000;//显示“9”
46 10 : seg = 8'b1000_1000;//显示“A”
47 11 : seg = 8'b1000_0011;//显示“B”
48 12 : seg = 8'b1100_0110;//显示“C”
49 13 : seg = 8'b1010_0001;//显示“D”
50 14 : seg = 8'b1000_0110;//显示“E”
51 15 : seg = 8'b1000_1110;//显示“F”
52 default : seg = 8'b1111_1111;//全灭
53 endcase
54 end
55 end
56
57 endmodule
|
/****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function:测试SEG7模块,并显示“A”
*****************************************************/
00 `timescale 1ns/1ps //定义时间单位和精度
01
02 module SEG7_tb;
03 //系统输入
04 reg clk;//系统时钟
05 reg rst_n;//系统复位
06 reg [3:0] data;//输入数据
07 //系统输出
08 wire [7:0] seg;//数码管段选
09 wire [2:0] sel;//数码管位选
10
11 initial begin
12 clk = 1;
13 rst_n = 0;
14 data = 10;//data = 4'hA; 这两种方式都可以
15 # 200.1 //复位200ns
16 rst_n = 1;
17 end
18
19 always # 10 clk = ~clk;//50M的时钟
20
21 SEG7 SEG7 (
22 .clk(clk), //系统时钟
23 .rst_n(rst_n),//系统复位
24 .data(data), //输入数据
25 .seg(seg),//数码管段选
26 .sel(sel)//数码管位选
27 );
28
29 endmodule
| 在本模块中,梦翼师兄只是测试了显示”A”,有兴趣的话,可以把0~9、A~F,全部测试一下。
单个数码管显示的仿真分析
在复位期间,seg信号全部为“1”,数码管熄灭。当复位信号拉高以后,seg信号变成了“10001000”,正好是“A”的段选,而sel(位选)一直就是0(选择第一个数码管),证明我们的设计是正确的。
六个数码管显示
六个数码显示的系统架构
应用六个数码管去显示任意数字,每个数码管显示的数字需要用4位二进制数去表示,那么六个数码管一共需要24位二进制数
数码管各模块功能介绍
模块名
| 功能描述
| SEG7
| 输出数码管控制位选和段选信号
| freq
| 时钟分频模块,输出1KHz时钟
| top
| 顶层模块,负责模块级联
| 端口和内部连线描述
顶层端口 端口名
| 端口说明
| clk
| 系统时钟输入
| Rst_n
| 系统复位
| Data[23:0]
| 数据输入
| sel[2:0]
| 片选信号输出
| seg[7:0]
| 段选信号输出
|
模块内部连线 代码解释
freq模块代码 /****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function: 产生慢时钟
*****************************************************/
00 module freq (
01 clk, //系统时钟
02 rst_n, //系统复位
03 clk_1K//切换时钟
04 );
05 //系统输入
06 input clk;//系统时钟
07 input rst_n;//系统复位
08 //系统输出
09 output reg clk_1K;//切换时钟
10 //定义中间寄存器
11 reg [19:0] count;//定义一个计数的寄存器
12
13 always @ (posedge clk or negedge rst_n)
14 begin
15 if (!rst_n)
16 begin
17 clk_1K <= 1;
18 count <= 0;
19 end
20 else
21 begin
22 if (count < 24999)// 50000分频,得出1K的时钟
23 count <= count + 1;
24 else
25 begin
26 count <= 0;
27 clk_1K <= ~clk_1K;
28 end
29 end
30 end
31
32 endmodule
|
SEG7模块代码 /****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function: 产生段选信号和位选信号
*****************************************************/
000 module SEG7 (
001 clk, //模块时钟
002 rst_n,//系统复位
003 data, //输入数据
004 seg,//数码管段选
005 sel//数码管位选
006 );
007 //系统输入
008 input clk;//模块时钟
009 input rst_n;//系统复位
010 input [23:0] data;//输入数据
011 //系统输出
012 output reg [7:0] seg;//数码管段选
013 output reg [2:0] sel;//数码管位选
014 //定义中间寄存器
015 reg [3:0] data_temp;//数码管显示的数值
016 reg [2:0] state;//状态寄存器
017
018 always @ (posedge clk or negedge rst_n)
019 begin
020 if (!rst_n)//复位的时候选择第一个数码管
021 begin
022 sel <= 0;
023 data_temp <= 0;
024 state <= 0;
025 end
026 else
027 begin
028 case (state)
029 0 : begin//将最高位的数显示在第一个数码管上
030 sel <= 0;
031 data_temp <= data[23:20];
032 state <= 1;
033 end
034
035 1 : begin//将第2位的数显示在第二个数码管上
036 sel <= 1;
037 data_temp <= data[19:16];
038 state <= 2;
039 end
040
041 2 : begin//将第3位的数显示在第三个数码管上
042 sel <= 2;
043 data_temp <= data[15:12];
044 state <= 3;
045 end
046
047 3 : begin//将第4位的数显示在第四个数码管上
048 sel <= 3;
049 data_temp <= data[11:8];
050 state <= 4;
051 end
052
053 4 : begin//将最5位的数显示在第五个数码管上
054 sel <= 4;
055 data_temp <= data[7:4];
056 state <= 5;
057 end
058
059 5 : begin//将最低位的数显示在第六个数码管上
060 sel <= 5;
061 data_temp <= data[3:0];
062 state <= 0;
063 end
064
065 default : state <= 0;
066 endcase
067 end
068 end
069
070 always @ (*)//根据data_temp的中的值,用组合逻辑进行输出段选信号
071 begin
072 if (!rst_n)//复位的时候数码管熄灭
073 begin
074 seg = 8'b1111_1111;
075 end
076 else
077 begin
078 case(data_temp)
079 0 : seg = 8'b1100_0000;//显示“0”
080 1 : seg = 8'b1111_1001;//显示“1”
081 2 : seg = 8'b1010_0100;//显示“2”
082 3 : seg = 8'b1011_0000;//显示“3”
083 4 : seg = 8'b1001_1001;//显示“4”
084 5 : seg = 8'b1001_0010;//显示“5”
085 6 : seg = 8'b1000_0010;//显示“6”
086 7 : seg = 8'b1111_1000;//显示“7”
087 8 : seg = 8'b1000_0000;//显示“8”
088 9 : seg = 8'b1001_0000;//显示“9”
089 10 : seg = 8'b1000_1000;//显示“A”
090 11 : seg = 8'b1000_0011;//显示“B”
091 12 : seg = 8'b1100_0110;//显示“C”
092 13 : seg = 8'b1010_0001;//显示“D”
093 14 : seg = 8'b1000_0110;//显示“E”
094 15 : seg = 8'b1000_1110;//显示“F”
095 default : seg = 8'b1000_1110;//显示“F”
096 endcase
097 end
098 end
099
100 endmodule
|
top模块代码 /****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function: 顶层模块
*****************************************************/
00 module top (
01 clk, //系统时钟
02 rst_n, //系统复位
03 data, //输入数据
04 seg, //数码管段选
05 sel//数码管位选
06 );
07 //系统输入
08 input clk;//系统时钟
09 input rst_n;//系统复位
10 input [23:0] data;//输入数据
11 //系统输出
12 output [7:0] seg;//数码管段选
13 output [2:0] sel;//数码管位选
14 //定义中间连线
15 wire clk_1K;//定义切换时钟
16 //调用pll(锁相环)
17 freq freq(
18 .clk( clk ),//外部时钟
19 .rst_n(rst_n),//系统复位
20 .clk_1K( clk_1K ) //切换时钟
21 );
22 //实例化SEG7
23 SEG7 SEG7 (
24 .clk(clk_1K), //切换时钟
25 .rst_n(rst_n),//系统复位
26 .data(data), //输入数据
27 .seg(seg),//数码管段选
28 .sel(sel)//数码管位选
29 );
30
31 endmodule
| 编写完可综合代码之后查看RTL视图如下:
由RTL视图可知代码综合以后得到的电路和我们设计的系统框图一致,说明顶层连接关系正确,接下来编写测试代码如下:
/****************************************************
* Engineer : 梦翼师兄
* QQ : 761664056
* The module function: 测试数码管模块
*****************************************************/
00 `timescale 1ns/1ps //定义时间单位和精度
01
02 module top_tb;
03 //系统输入
04 reg clk;//系统时钟
05 reg rst_n;//系统复位
06 reg [23:0] data;//输入数据
07 //系统输出
08 wire [7:0] seg;//数码管段选
09 wire [2:0] sel;//数码管位选
10
11 initial begin
12 clk = 1;
13 rst_n = 0;
14 data = 24'h123456;
15 # 200.1 //复位200.1ns
16 rst_n = 1;
17 end
18
19 always # 10 clk = ~clk;//50M的时钟
20
21 top top(
22 .clk(clk), //系统时钟
23 .rst_n(rst_n),//系统复位
24 .data(data), //输入数据
25 .seg(seg),//数码管段选
26 .sel(sel)//数码管位选
27 );
28
29 endmodule
|
仿真分析
在对应的数码管上,给出对应数值的段选信号,经过比对,我们的设计都是正确的。
|