FPGA初级课程第四讲 数码管
FPGA初级课程第四讲 数码管
Hi,大家好!我是至芯科技的李老师。
今天讲课的题目是:数码管。
本节课我先简要地介绍一下数码管的物理原理,然后实际演示一下数码管驱动逻辑电路的建模与仿真,最后下板查看效果。
首先打开ZX_1开发板电路图《ZX_NO1.pdf》文件,我们看一下六个七段数码管的电路图。
然后,我们打开至芯科技编写的《17.炼狱传奇-数码管之战.pdf》文件。
这一节,我们来学习如何驱动数码管,数码管作为一种外设,首先我们需要了解它的工作原理以及它和外设电路的对应关系,七段数码管原理图如下图所示。
顾名思义,七段数码管就是使用七段点亮的线段来拼成常见的数字和某些字母,这种显示方式我们在数字电路中非常容易见到。
再加上右下角显示的小数点,实际上一个显示单元包括了8根信号线。
根据电路设计的不同,这些信号线可能高有效也可能低有效。
我们通过FPGA控制这些线段的亮灭,达到显示效果。
对于多个数码管的显示模块,将每一个都连接到FPGA的管脚会耗用大量FPGA的管脚资源。
因此我们同样引入一种类似矩阵键盘的扫描方式。
任何时刻我们只使用8根信号点亮一个数码管,但是8个数码管是随着时钟步调交替点亮的,只要时钟的速度够快,我们观察到数码管就好像几个同时点亮一样。
再回到至芯ZX_1开发板原理图。
如图所示,我们的开发板使用的是六位共阳极数码管,六个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。
好了,既然思路和原理已经明了于心,那么接下来我们可以开始写驱动代码,验证思路是否可以实现。
分析开发板对应数码管原理图可知FPGA只需要输出位选和段选信号即可,而输入要求则是要显示的十六进制数。
新建一个工程文件夹。
单个数码管显示
我们先进行单个数码管显示。
单个数码管显示从0到F的15个数字,对应的输入为4位。
我们新建工程文件夹seg7。
对单个数码管显示驱动电路进行建模。编写seg7.v模块。
module seg7 (clk, rst_n, data, sel, seg);
input clk, rst_n;
input data;
output reg sel;
output reg seg;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
sel <= 3'b000;
end
else
begin
sel <= 3'b000;
end
end
always @ (*)
begin
if (!rst_n)
begin
seg = 8'b1111_1111;
end
else
begin
case (data)
0 : seg = 8'b1100_0000; //d0
1 : seg = 8'b1111_1001; //d1
2 : seg = 8'b1010_0100; //d2
3 : seg = 8'b1011_0000; //d3
4 : seg = 8'b1001_1001; //d4
5 : seg = 8'b1001_0010; //d5
6 : seg = 8'b1000_0010; //d6
7 : seg = 8'b1111_1000; //d7
8 : seg = 8'b1000_0000; //d8
9 : seg = 8'b1001_0000; //d9
10 : seg = 8'b1000_1000; //dA
11 : seg = 8'b1000_0011; //db
12 : seg = 8'b1100_0110; //dC
13 : seg = 8'b1010_0001; //dd
14 : seg = 8'b1000_0110; //dE
15 : seg = 8'b1000_1110; //dF
default : seg = 8'b1111_1111; //null
endcase
end
end
endmodule
然后编写仿真代码。
`timescale 1ns/1ps
module seg7_tb;
reg clk, rst_n;
reg data;
wire sel;
wire seg;
seg7dut (.clk(clk), .rst_n(rst_n), .data(data), .sel(sel), .seg(seg));
initial
begin
clk = 1;
rst_n = 0;
data = 4'hA;
#200.1
rst_n = 1;
#1000 $stop;
end
always #10 clk = ~clk;
endmodule
观察仿真波形
在本模块中,只是测试了显示“A”,有兴趣的话,可以测试显示全部0~F。
观察仿真波形,在复位期间,seg信号为全“1”,数码管熄灭,在复位结束以后,seg信号变成了“10001000”,正好对应“A”,而sel信号一直是000,对应第一个数码管。
仿真波形完全符合设计要求。
本帖最后由 lcytms 于 2016-11-2 21:55 编辑
6个数码管的显示
刚才我们演示了单个数码管显示驱动逻辑电路的建模与仿真,下面我们来进行6个数码管的显示。
一个数码管对应的data位宽为4位,六个数码管就需要24位,而且分时显示需要对六个数码管进行切换。因此我们需要修改原有的seg7.v模块。
同时由于数码管物理特性的限制,根据经验,我们设置它的时钟信号为1kHz,这需要对板载50MHz信号进行分频处理。
module seg7 (clk, rst_n, data, sel, seg);
input clk, rst_n;
input data;
output reg sel;
output reg seg;
reg clk_1k;
parameter T1ms = 25_000; //half width
reg count;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_1k <= 1;
count <= 0;
end
else
begin
if (count < T1ms - 1)
begin
count <= count + 1;
end
else
begin
count <= 0;
clk_1k <= ~clk_1k;
end
end
end
reg data_temp;
reg state;
always @ (posedge clk_1k or negedge rst_n)
begin
if (!rst_n)
begin
sel <= 3'b000;
data_temp <= 4'h0;
state <= 0;
end
else
begin
case (state)
0 : begin
sel <= 3'b000;
data_temp <= data;
state <= 1;
end
1 : begin
sel <= 3'b001;
data_temp <= data;
state <= 2;
end
2 : begin
sel <= 3'b010;
data_temp <= data;
state <= 3;
end
3 : begin
sel <= 3'b011;
data_temp <= data;
state <= 4;
end
4 : begin
sel <= 3'b100;
data_temp <= data;
state <= 5;
end
5 : begin
sel <= 3'b101;
data_temp <= data;
state <= 0;
end
default : state <= 0;
endcase
end
end
always @ (*)
begin
if (!rst_n)
begin
seg = 8'b1111_1111;
end
else
begin
case (data_temp)
0 : seg = 8'b1100_0000; //d0
1 : seg = 8'b1111_1001; //d1
2 : seg = 8'b1010_0100; //d2
3 : seg = 8'b1011_0000; //d3
4 : seg = 8'b1001_1001; //d4
5 : seg = 8'b1001_0010; //d5
6 : seg = 8'b1000_0010; //d6
7 : seg = 8'b1111_1000; //d7
8 : seg = 8'b1000_0000; //d8
9 : seg = 8'b1001_0000; //d9
10 : seg = 8'b1000_1000; //dA
11 : seg = 8'b1000_0011; //db
12 : seg = 8'b1100_0110; //dC
13 : seg = 8'b1010_0001; //dd
14 : seg = 8'b1000_0110; //dE
15 : seg = 8'b1000_1110; //dF
default : seg = 8'b1111_1111; //null
endcase
end
end
endmodule