先来了解一下数码管的工作原理。如图1所示,这是一个典型的带小数点的一位数码管。如果忽略小数点,我们通常称它为7段数码管(即便有小数点,我们也习惯的称呼为7段数码管),所谓7段,是指着7个发光二极管而言的。任意一个0-9的阿拉伯数字的显示,只要通过这7个发光二极管进行亮或灭的组合都可以实现。例如,我们要显示数字0,那么只要让发光二极管a、b、c、d、e、f点亮(g和dot熄灭)就可以了。
图1数码管示意图
接下来,大家可能就要关心着这7个发光二极管是如何控制的,我们又是如何通过FPGA的I/O口去点亮或熄灭任意一个发光二极管?很简单,原理上来讲,一个带小数点的数码管的所有8个发光二极管的正极或负极有一个公共端,通常必须接GND(共阴极数码管)或者接VCC(共阳极数码管),而另一个非公共端的8个引脚就留给用户的I/O直接控制了。例如,如果我们使用的是共阴极的数码管,那么我们在使用该数码管时就要将其公共端接地(或者接低电平0),我们的应用中,把这个公共端连接到了FPGA的I/O脚上,这便是数码管的片选信号。如果FPGA的这个I/O脚输出低电平0,那么这个数码管就能够显示数字;如果这个I/O输出高电平1,那么无论数码管的8个段选端输出0还是1,都无法将8个发光二极管的任意一个点亮,这也达到了关闭数码管显示的效果。这样一来,这个数码管的公共端被我们当做了数码管片选引脚使用了,虽然不是名副其实的“片选”,但还真达到了异曲同工之妙。
我们的例程要实现的功能比较简单基础:让4个数码管每隔1s不断的递增计数显示,计数范围为0-F。为了便于代码编写控制7个用于段选(不包括小数点)的发光二极管显示不同的字符,这里只做了一个简单的对应表,把不同字符显示时的7个I/O值进行编码,如表3所示。
表3数码管显示字符与驱动编码映射表
数字/字符 0 1 2 3 4 5 6 7
编码(16进制) 3f 06 5b 4f 66 6d 7d 07
数字/字符 8 9 A B C D E F
编码(16进制) 7f 6f 77 7c 39 5e 79 71
本实例的功能框图如图3所示。PLL产生的25MHz时钟,分别供给两个子模块,秒计数器(counter.v)模块产生一个每秒递增的16位数据,这16位数据以16进制形式通过数码管显示驱动模块(seg7.v)显示到数码管上。数码管显示驱动模块以分时复用的片选方式,将数据送到数码管的各个段选位上。
图3数码管驱动功能框图
数码管驱动实例代码
module seven_tube_drive (clk, rst_n, data_show, seven_tube_seg_n, seven_tube_sel);
input wire clk;
input wire rst_n;
input wire [23:0] data_show;
output reg [7:0] seven_tube_seg_n;
output reg [2:0] seven_tube_sel;
localparam S0 = 6'b00_0001;
localparam S1 = 6'b00_0010;
localparam S2 = 6'b00_0100;
localparam S3 = 6'b00_1000;
localparam S4 = 6'b01_0000;
localparam S5 = 6'b10_0000;
parameter T_2ms = 50_000;
reg [5:0] c_state;
reg [5:0] n_state;
reg [15:0] cnt_1ms;
wire flag_1ms;
reg [3:0] temp;
always @ (posedge clk)
begin
if (rst_n == 0)
cnt_1ms <= 0;
else
if (cnt_1ms < T_1ms - 1)
cnt_1ms <= cnt_1ms + 1'b1;
else
cnt_1ms <= 0;
end
assign flag_1ms = (cnt_1ms == T_1ms - 1) ? 1'b1 : 1'b0;
always @ (posedge clk)
begin
if (rst_n == 0)
c_state <= S0;
else
c_state <= n_state;
end
always @ (*)
begin
case (c_state)
S0 : if (flag_1ms)
n_state = S1;
else
n_state = S0;
S1 : if (flag_1ms)
n_state = S2;
else
n_state = S1;
S2 : if (flag_1ms)
n_state = S3;
else
n_state = S2;
S3 : if (flag_1ms)
n_state = S4;
else
n_state = S3;
S4 : if (flag_1ms)
n_state = S5;
else
n_state = S4;
S5 : if (flag_1ms)
n_state = S0;
else
n_state = S5;
default : n_state = S0;
endcase
end
always @ (posedge clk)
begin
if (rst_n == 0)
seven_tube_sel <= 0;
else
case (c_state)
S0 : seven_tube_sel <= 0;
S1 : seven_tube_sel <= 1;
S2 : seven_tube_sel <= 2;
S3 : seven_tube_sel <= 3;
S4 : seven_tube_sel <= 4;
S5 : seven_tube_sel <= 5;
default : seven_tube_sel <= 0;
endcase
end
always @ (*)
begin
case (c_state)
S0 : temp = data_show[23:20];
S1 : temp = data_show[19:16];
S2 : temp = data_show[15:12];
S3 : temp = data_show[11:8];
S4 : temp = data_show[7:4];
S5 : temp = data_show[3:0];
default : temp = 0;
endcase
end
always @ (posedge clk)
begin
if (rst_n == 0)
seven_tube_seg_n <= 8'hff;
else
case (temp)
0 : seven_tube_seg_n <= 8'b1100_0000;
1 : seven_tube_seg_n <= 8'b1111_1001;
2 : seven_tube_seg_n <= 8'b1010_0100;
3 : seven_tube_seg_n <= 8'b1011_0000;
4 : seven_tube_seg_n <= 8'b1001_1001;
5 : seven_tube_seg_n <= 8'b1001_0010;
6 : seven_tube_seg_n <= 8'b1000_0010;
7 : seven_tube_seg_n <= 8'b1111_1000;
8 : seven_tube_seg_n <= 8'b1000_0000;
9 : seven_tube_seg_n <= 8'b1001_0000;
10 : seven_tube_seg_n <= 8'b1000_1000;
11 : seven_tube_seg_n <= 8'b1000_0011;
12 : seven_tube_seg_n <= 8'b1100_0110;
13 : seven_tube_seg_n <= 8'b1010_0001;
14 : seven_tube_seg_n <= 8'b1000_0110;
15 : seven_tube_seg_n <= 8'b1000_1110;
default : seven_tube_seg_n <= 8'hff;
endcase
end
endmodule
|