写在前面的话
在项目设计中,我们通常需要一些显示设备来显示我们需要的信息,可以选择的显示设备更是种类繁多,玲琅满目,数码管无疑是最常用,最简单的显示设备之一。本节,梦翼师兄和大家一起学习数码管的显示原理和驱动方式,为我们以后项目的开发做好准备。
项目需求
设计一个数码管的驱动电路,使数码管能够同时显示出任意的六位数字(梦翼师兄使用的开发板集成的数码管为六位连体数码管)。
原理分析
数码管作为一种外设,我们首先需要了解它的工作原理以及它的对应电路连接关系,七段数码管结构示意图如下:
顾名思义,七段数码管就是使用七段点亮的线段来拼成常见的数字和某些字母,这种显示方式我们在数字电路中非常容易见到。再加上右下角显示的小数点,实际上一个显示单元包括了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,所以我们的输入应该是四位。
单个数码管显示的模块模块功能介绍
模块名 |
功能描述 |
SEG7 |
输出控制线 |
单个数码管显示模块的端口描述
端口名 |
端口说明 |
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] |
段选信号输出 |
模块内部连线
连线名 |
连线说明 |
Clk_1K |
数码管的切换时钟 |
代码解释
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 |
仿真分析
在对应的数码管上,给出对应数值的段选信号,经过比对,我们的设计都是正确的。