在很多初级开发板上面,数码管属于标配的外设,其基本的单元是发光二极管,一般是由八个发光二极管组成“8”字形的结构,如图1所示。
图1 数码管二极管结构
八个发光二极管中的一端会连接一起,若是阳极(正极)连到一起称为数码管共阳极,阴极(负极)连到一起称为共阴极,没有连接到一起的一端分别连接到FPGA芯片的八个引脚上。当我们想在数码管上显示所需的数字时,只需要导通对应的发光二极管即可。
一般来说在一个开发板上面可能会有多个数码管,每一个数码管需要占用FPGA芯片的八个引脚,如此FPGA芯片的引脚会被数码管占用很多,一个芯片的引脚资源是比较紧缺且重要的,为了解决这个问题,我们可以在硬件上将八个数码管连接FPGA芯片的一端连接到一起,然后使用一个三八译码器来选通我们所要显示数字的数码管,当我们需要在所需的数码管上显示数字时,只需要先控制三八译码器选通对应的数码管,然后再选择性的导通八个发光二极管即可。如图2所示为三八译码器选择六个数码管。
图2 3-8译码器控制六个数码管
假设我们想在第一个数码管上面显示一个数据,该数据在复位的时候为0,不复位的时候为1。为了能在第一个数码管上面显示数据,首先我们应该通过3-8译码器选通第一个数码管,然后再选通对应的发光二极管即可。
代码示例1:
1 module led(
2 input wire clk,
3 input wire rst_n,
4
5 output reg[2:0] sel,
6 output reg[7:0] seg
7 );
8
9 parameter NUM_0 = 8‘hc0;
10 parameter NUM_1 = 8‘hf9;
11
12
13 always@(posedge clk or negedge rst_n)
14 if(rst_n==1‘b0)
15 sel <= 3‘b0;
16 else
17 sel <= 3‘b0;
18
19 always@(posedge clk or negedge rst_n)
20 if(rst_n==1‘b0)
21 seg <= NUM_0;
22 else
23 seg <= NUM_1;
24
25 endmodule
代码解析1:
① 第5、6行分别定义了数码管的位选和段选,通过控制sel来选择选通哪一个数码管,通过控制seg来控制在对应的数码管显示什么数据;
② 第9、10行定义了两个参数,分别是0和1在数码管上的译码数据;
③ 第13行的always控制了sel的赋值,无论是否复位均选择第一个数码管;
④ 第19行的always控制了seg的赋值,当复位时给出0的译码值,不复位时给出1的译码值。
以上给出了在一个数码管上面显示数据的代码及解析,但是在大部分的情况下,多个数码管同时使用的概率更大,在此我们讨论一下如何在固定的两个数码管上显示固定的数值。
假设我们在前两个数码管上显示数据10,由于我们需要在两个数码管上显示数值,因此我们需要选通两个数码管,但是由于3-8译码器每个时刻只能选通一个子路,因此同时选通两个数码管是不可行的,所以我们需要一些其他方法解决该问题,我们可以利用人眼的识别能力,在两个数码管之间快速的选择,当选择的频率(一般为60Hz)超过人眼暂留效应后,即可在两个数码管上同时显示数据。首先我们需要通过sel选通一个数码管,并给出需要在该数码管上显示的seg的值,当sel选通另外一个数码管时,我们给出另外一个seg的值即可。
代码示例2:
1 module led(
2 input wire clk,
3 input wire rst_n,
4
5 output reg[2:0] sel,
6 output reg[7:0] seg
7 );
8
9 reg[19:0] div_cnt;
10
11 reg div_flag;
12
13 reg[3:0] num;
14
15 parameter CNT_END = 20‘d49999;
16
17 parameter NUM_0 = 8‘hc0;
18 parameter NUM_1 = 8‘hf9;
19 parameter NUM_2 = 8‘ha4;
20
21 //1ms计数器
22 always@(posedge clk or negedge rst_n)
23 if(rst_n==1‘b0)
24 div_cnt <= 20‘b0;
25 else if(div_cnt==CNT_END)
26 div_cnt <= 20‘b0;
27 else
28 div_cnt <= div_cnt+1‘b1;
29
30 //1ms标志位
31 always@(posedge clk or negedge rst_n)
32 if(rst_n==1‘b0)
33 div_flag <= 1‘b0;
34 else if(div_cnt==CNT_END)
35 div_flag <= 1‘b1;
36 else
37 div_flag <= 1‘b0;
38
39 //产生sel
40 always@(posedge clk or negedge rst_n)
41 if(rst_n==1‘b0)
42 sel <= 3‘d5;
43 else if(div_flag==1‘b1&&sel==3‘d5)
44 sel <= 3‘d4;
45 else if(div_flag==1‘b1)
46 sel <= sel+1‘b1;
47
48 //产生num
49 always@(posedge clk or negedge rst_n)
50 if(rst_n==1‘b0)
51 num <= 4‘b0;
52 else case(sel)
53 4: num <= 4‘b1;
54 5: num <= 4‘b0;
55 default:num <= 4‘b0;
56 endcase
57
58 //产生seg
59 always@(posedge clk or negedge rst_n)
60 if(rst_n==1‘b0)
61 seg <= NUM_0;
62 else case(num)
63 4‘d0: seg <= NUM_0;
64 4‘d1: seg <= NUM_1;
65 default:seg <= NUM_0;
66 endcase
67
68 endmodule
代码解析2:
① 第22行的always产生了一个1ms的计数器,主要作用是控制选择每一个数码管的频率;
② 第31行的always是根据前面的计数器产生的标志位;
③ 第40行的always产生了sel的变化,每隔1ms变化一次;
④ 第49行的always是在选择不同的数码管时,对变量num赋不同的值;
⑤ 第59行的always是对num进行相应的译码。
通过对数码管的学习,可以更好地掌握Verilog HDL的编程思想和风格。若是有兴趣,读者可以试着完成一个秒表,在对应数码管上显示毫秒数、秒数及分数,结合以前的按键消抖,可以设定一个按键为暂停键、一个按键为复位键。
出自于 v3学院 www.v3edu.org
更多内容请 扫码关注 v3学院微信群