FPGA和数码管
1.1.1 数码管基础知识
数码管由8个发光二极管(以下简称字段)构成,通过不同的组合可显示数字0~9、字符A~F、H、L、P、R、U、Y、符号“”及小数点“”。数码管的外型结构如图所示。数码管又分为共阴极和共阳极两种结构,分别如图所示。
图1‑94共阴极和共阳极数码管
(a)共阴极(b)共阳极
共阳极数码管的8个发光二极管的阳极(二极管正端)连接在一起,通常接高电平(一般接电源),其它管脚接段驱动电路输出端。当某段驱动电路的输出端为低电平时,该端所连接的字段导通并点亮,根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能吸收额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。
共阴极数码管的8个发光二极管的阴极(二极管负端)连接在一起,通常接低电平(一般接地),其它管脚接段驱动电路输出端。当某段驱动电路的输出端为高电平时,该端所连接的字段导通并点亮,根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能提供额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。
数据线D0与a字段对应,D1字段与b字段对应……,依此类推。如使用共阳极数码管,则数据为0表示对应字段亮,数据为1表示对应字段暗;如使用共阴极数码管,则相反。
表1‑38数码管字型编码表
数码管工作方式有两种:静态显示方式和动态显示方式。
1、静态显示接口
从下图可以看出,静态显示方式的特点是各位数码管相互独立,公共端恒定接地(共阴极)或接正电源(共阳极)。每个数码管的8个字段分别与一个8位I/O口地址相连,I/O口只要有段码输出,相应字符即显示出来并保持不变,直到I/O口输出新的段码。
图1‑95两位的LED数码管静态显示示意图
采用静态显示方式时,用较小的电流即可获得较高的亮度,且占用CPU时间少,编程简单,显示便于监测和控制,但其占用的口线多,硬件电路复杂,成本高,只适用于显示位数较少的场合。
2、动态显示
当需要显示的位数较多时,为了节省硬件接口,往往采用动态显示的方式。
动态显示的特点是将所有位数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。动态显示的亮度比静态显示要差一些,所以在选择限流电阻时应略小于静态显示电路中的。
动态显示是指一位一位地轮流点亮各位数码管,这种逐位点亮显示器的方式称为位扫描。通常,各位数码管的段选线相应并联在一起,由一个8位的I/O口控制;各位的位选线(公共阴极或公共阳极)由另外的I/O口线控制。以动态方式显示时,各数码管分时轮流选通。要使其稳定显示,必须采用扫描方式,即在某一时刻只选通一位数码管,并送出相应的段码,在另一时刻选通另一位数码管,并送出相应的段码。依此规律循环,即可使各位数码管显示将要显示的字符,虽然这些字符是在不同的时刻分别显示的,但由于人眼存在视觉暂留效应,因此只要每位显示间隔足够短就可以给人以同时显示的感觉。
图1‑96四位的LED数码管动态显示示意图
1.1.2 FPGA和数码管驱动
使用动态扫描还是静态扫描其实取决于硬件设计,并不取决于驱动程序,一旦硬件确定下来那么就需要驱动去适配硬件,本次设计使用的数码管为共阳极数码管,动态扫描加载,电路如下:
图1‑97动态数码管设计电路
表1‑39数码管设计资源
设计的资源如表1‑39所示,其中扫描频率是1s扫描的频率,即1/6ms≈166.67Hz。
数码管主要考虑输入调用它的输入。即如何充分减少资源以实现数码管动态显示。
图1‑98数码管电路设计建模图
表 1‑40建模图中模块间信号定义
整个系统设计结构如图1‑98所示,上图的组合模块-数码管接口 smg_interface.v 中,输入信号 Number_Sig 占了 24 位宽,然而 Number_Sig 的位分配如下表:
表1‑42 Number_Sig的位分配表
整个系统设计结构如图1‑98所示,上图的组合模块-数码管接口smg_interface.v 中,输入信号 Number_Sig 占了 24 位宽,然而Number_Sig 的位分配如下表:
为什么每一位数字数码管,都用 4 位位宽来代表呢?
每数码管可以支持显示 0~F,正是因为如此,如果我们用每 4 位位宽来代表某一个数码管显示的信号,那么可以避免“使用除法或者求余运算符执行十进制的取位操作”。一来方便设计,而来减少资源。
举个例子: 24’h123456, 亦即 00010010 0011 0100 0101 0110。
smg_encode_module.v 在这里的功能就是就是将数字 0~F 加码为数码管码。然而,比较特别的是, smg_control_module.v 和 smg_scan_module.v 有并行操作的性质。
smg_interface.v 的大致操作如下:
假设我往 Number_Sig 输入 24’h123456
(1)在 T1,smg_control_module.v 会将 Number_Sig[23:20] 送往至smg_encode_module.v 加码并且送往数码管。在同一时间 smg_scan_module.v 会扫描第一位数码管(使能)。
(2)在 T2,smg_control_module.v 会将 Number_Sig[19:16] 送往至smg_encode_module.v 加码并且送往数码管,在同一时间smg_scan_module.v 会扫描第二位数码管(使能)。
(3)在 T3, smg_control_module.v会将 Number_Sig[15:12]送往至smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第三位数码管(使能)。
(4)在 T4,smg_control_module.v 会将 Number_Sig[11:8] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第四位数码管(使能)。
(5)在 T5,smg_control_module.v 会将 Number_Sig[7:4] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第五位数码管(使能)。
(6)在 T6,smg_control_module.v 会将 Number_Sig[3:0] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第六位数码管(使能)。
在 T1 的时候第一位数码管会显示1。在 T2 的时候第二位数码管会显示 2,其他的依此类推。最后在 T6 的时候,第六位数码管会显示 6。就这样一次性的扫描(六位数码管全扫描)就完成。啊,别忘了!每位数码管扫描停留的时间(使能的时间)大约是 1ms。所以一次性扫描所需要的时间大约是6ms,亦即在每一秒内,一组 6 位的数码管会扫描 166 次左右。
图1‑100数码管控制模块结构图
代码1‑13数码管控制模块(smg_control_module)代码
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-05-18 23:59:39
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2019-05-19 01:27:11
7. //# Description:
8. //# @Modification History: 2019-05-19 01:27:11
9. //# Date By Version Change Description:
10.//# ========================================================================= #
11.//# 2019-05-19 01:27:11
12.//# ========================================================================= #
13.//# | | #
14.//# | OpenFPGA | #
15.//****************************************************************************//
16.module smg_control_module
17.(
18. input CLOCK,
19. input RST_n,
20. input [23:0]Number_Sig,
21. output [3:0]Number_Data
22.);
23.
24. /******************************************/
25.
26. parameter T1MS = 16'd49999; //定义 1ms 的常量
27.
28. /******************************************/
29. //1ms 的定时器
30. reg [15:0]C1;
31.
32. always @ ( posedge CLOCK or negedge RST_n )
33. if( !RST_n )
34. C1 <= 16'd0;
35. else if( C1 == T1MS )
36. C1 <= 16'd0;
37. else
38. C1 <= C1 + 1'b1;
39.
40. /******************************************/
41.
42. reg [3:0]i;
43. reg [3:0]rNumber;
44.
45. always @ ( posedge CLOCK or negedge RST_n )
46. if( !RST_n )
47. begin
48. i <= 4'd0;
49. rNumber <= 4'd0;
50. end
51. else
52. case( i )
53.
54. 0:
55. if( C1 == T1MS ) i <= i + 1'b1;
56. else rNumber <= Number_Sig[23:20];
57.
58. 1:
59. if( C1 == T1MS ) i <= i + 1'b1;
60. else rNumber <= Number_Sig[19:16];
61.
62. 2:
63. if( C1 == T1MS ) i <= i + 1'b1;
64. else rNumber <= Number_Sig[15:12];
65.
66. 3:
67. if( C1 == T1MS ) i <= i + 1'b1;
68. else rNumber <= Number_Sig[11:8];
69.
70. 4:
71. if( C1 == T1MS ) i <= i + 1'b1;
72. else rNumber <= Number_Sig[7:4];
73.
74. 5:
75. if( C1 == T1MS ) i <= 4'd0;
76. else rNumber <= Number_Sig[3:0];
77.
78. endcase
79.
80. /******************************************/
81.
82. assign Number_Data = rNumber;
83.
84. /******************************************/
85.
86. endmodule
rNumber 是每一位数字的暂存器(43 行)用来驱动Number_Data(82 行)。(52~78 行)每隔 1ms该控制模块就会将不同位的数字往 Number_Data 输出
图1‑101数码管加码模块结构图
代码 1‑14数码管加码模块(smg_encode_module)代码
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-05-18 23:59:39
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2019-07-21 03:32:41
7. //# Description:
8. //# @Modification History: 2019-07-20 20:54:39
9. //# Date By Version Change Description:
10.//# ========================================================================= #
11.//# 2019-07-20 20:54:39
12.//# ========================================================================= #
13.//# | | #
14.//# | OpenFPGA | #
15.//****************************************************************************//
16.module smg_encode_module
17.(
18. input CLOCK,
19. input RST_n,
20. input [3:0]Number_Data,
21. output [7:0]SMG_Data
22.);
23.
24. /***************************************/
25.
26. parameter _0 = 8'b1100_0000, _1 = 8'b1111_1001, _2 = 8'b1010_0100,
27. _3 = 8'b1011_0000, _4 = 8'b1001_1001, _5 = 8'b1001_0010,
28. _6 = 8'b1000_0010, _7 = 8'b1111_1000, _8 = 8'b1000_0000,
29. _9 = 8'b1001_0000, _A = 8'b1000_1000, _B = 8'b1000_0011,
30. _C = 8'b1100_0110, _D = 8'b1010_0001, _E = 8'b1000_0110,
31. _F = 8'b1000_1110;
32.
33. /***************************************/
34.
35. reg [7:0]rSMG;
36.
37. always @ ( posedge CLOCK or negedge RST_n )
38. if( !RST_n )
39. begin
40. rSMG <= 8'b1111_1111;
41. end
42. else
43. case( Number_Data )
44.
45. 4'd0 : rSMG <= _0;
46. 4'd1 : rSMG <= _1;
47. 4'd2 : rSMG <= _2;
48. 4'd3 : rSMG <= _3;
49. 4'd4 : rSMG <= _4;
50. 4'd5 : rSMG <= _5;
51. 4'd6 : rSMG <= _6;
52. 4'd7 : rSMG <= _7;
53. 4'd8 : rSMG <= _8;
54. 4'd9 : rSMG <= _9;
55. 4'd10 : rSMG <= _A;
56. 4'd11 : rSMG <= _B;
57. 4'd12 : rSMG <= _C;
58. 4'd13 : rSMG <= _D;
59. 4'd14 : rSMG <= _E;
60. 4'd15 : rSMG <= _F;
61.
62. endcase
63.
64. /***************************************/
65.
66. assign SMG_Data = rSMG;
67.
68. /***************************************/
69.
70.endmodule
第 26~31 行声明了 SMG 码的常量。第 43~62 行是针对“十位数”的加码操作,但是是针对“个位数”(58 行)。第66 行是输出。
图1‑102数码管扫描模块结构图
代码 1‑15数码管扫描模块(smg_scan_module)代码
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-05-18 23:59:39
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2019-07-21 03:37:17
7. //# Description:
8. //# @Modification History: 2019-05-19 01:41:56
9. //# Date By Version Change Description:
10.//# ========================================================================= #
11.//# 2019-05-19 01:41:56
12.//# ========================================================================= #
13.//# | | #
14.//# | OpenFPGA | #
15.//****************************************************************************//
16.module smg_scan_module
17.(
18. input CLOCK,
19. input RST_n,
20. output [5:0]Scan_Sig
21.);
22.
23. /*****************************/
24.
25. parameter T1MS = 16'd49999;
26.
27. /*****************************/
28.
29. reg [15:0]C1;
30.
31. always @ ( posedge CLOCK or negedge RST_n )
32. if( !RST_n )
33. C1 <= 16'd0;
34. else if( C1 == T1MS )
35. C1 <= 16'd0;
36. else
37. C1 <= C1 + 1'b1;
38.
39. /*******************************/
40.
41. reg [3:0]i;
42. reg [5:0]rScan;
43.
44. always @ ( posedge CLOCK or negedge RST_n )
45. if( !RST_n )
46. begin
47. i <= 4'd0;
48. rScan <= 6'b100_000;
49. end
50. else
51. case( i )
52.
53. 0:
54. if( C1 == T1MS ) i <= i + 1'b1;
55. else rScan <= 6'b011_111;
56.
57. 1:
58. if( C1 == T1MS ) i <= i + 1'b1;
59. else rScan <= 6'b101_111;
60.
61. 2:
62. if( C1 == T1MS ) i <= i + 1'b1;
63. else rScan <= 6'b110_111;
64.
65. 3:
66. if( C1 == T1MS ) i <= i + 1'b1;
67. else rScan <= 6'b111_011;
68.
69. 4:
70. if( C1 == T1MS ) i <= i + 1'b1;
71. else rScan <= 6'b111_101;
72.
73. 5:
74. if( C1 == T1MS ) i <= 4'd0;
75. else rScan <= 6'b111_110;
76.
77.
78. endcase
79.
80. /******************************/
81.
82. assign Scan_Sig = rScan;
83.
84. /******************************/
85.
86.
87.endmodule
第 25 行是 1ms 的常量声明,在 31~37 行是 1ms 的定时器。该模块和smg_control_module.v一样,都是每隔 1ms 都有一个动作。smg_scan_module.v 每隔 1ms就会使能不同的数码管(56~78行)。然而数码管实际的扫描顺序是自左向右。在位操作的角度上,逻辑 0 从最高位到最低位交替移位。
接下来将上诉模块进行封装,详细的连接图见图1‑98,综合后的RTL电路图如下:
图1‑103数码管电路综合后的RTL图
结果和图1‑98一样。
下面进行模块的调用和验证,验证结构图如下:
图1‑104数码管电路验证结构图
验证中会建立一个名为demo_control_module.v输出 24’h000000 ~ 24’h999999 用来驱动smg_interface.v 的输入。具体的内容还是直接看代码:
代码 1‑16数码管电路验证代码
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-05-18 23:59:39
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2019-07-20 21:04:39
7. //# Description:
8. //# @Modification History: 2019-07-20 21:04:39
9. //# Date By Version Change Description:
10. //# ========================================================================= #
11. //# 2019-07-20 21:04:39
12. //# ========================================================================= #
13. //# | | #
14. //# | OpenFPGA | #
15. //****************************************************************************//
16. module demo_control_module
17. (
18. input CLOCK,
19. input RST_n,
20. output [23:0]Number_Sig
21. );
22.
23. /******************************/
24.
25. parameter T100MS = 23'd4_999_999;
26.
27. /******************************/
28.
29. reg [22:0]C1;
30.
31. always @ ( posedge CLOCK or negedge RST_n )
32. if( !RST_n )
33. C1 <= 23'd0;
34. else if( C1 == T100MS )
35. C1 <= 23'd0;
36. else
37. C1 <= C1 + 1'b1;
38.
39. /*******************************************************/
40.
41. reg [3:0]i;
42. reg [23:0]rNum;
43. reg [23:0]rNumber;
44.
45. always @ ( posedge CLOCK or negedge RST_n )
46. if( !RST_n )
47. begin
48. i <= 4'd0;
49. rNum <= 24'd0;
50. rNumber <= 24'd0;
51. end
52. else
53. case( i )
54.
55. 0:
56. if( C1 == T100MS ) begin rNum[3:0] <= rNum[3:0] + 1'b1; i <= i + 1'b1; end
57.
58. 1:
59. if( rNum[3:0] > 4'd14 ) begin rNum[7:4] <= rNum[7:4] + 1'b1; rNum[3:0] <= 4'd0; i <= i + 1'b1; end
60. else i <= i + 1'b1;
61.
62. 2:
63. if( rNum[7:4] > 4'd14 ) begin rNum[11:8] <= rNum[11:8] + 1'b1; rNum[7:4] <= 4'd0; i <= i + 1'b1; end
64. else i <= i + 1'b1;
65.
66. 3:
67. if( rNum[11:8] > 4'd14 ) begin rNum[15:12] <= rNum[15:12] + 1'b1; rNum[11:8] <= 4'd0; i <= i + 1'b1; end
68. else i <= i + 1'b1;
69.
70. 4:
71. if( rNum[15:12] > 4'd14 ) begin rNum[19:16] <= rNum[19:16] + 1'b1; rNum[15:12] <= 4'd0; i <= i + 1'b1; end
72. else i <= i + 1'b1;
73.
74. 5:
75. if( rNum[15:12] > 4'd14 ) begin rNum[19:16] <= rNum[19:16] + 1'b1; rNum[15:12] <= 4'd0; i <= i + 1'b1; end
76. else i <= i + 1'b1;
77.
78. 6:
79. if( rNum[19:16] > 4'd14 ) begin rNum[23:20] <= rNum[23:20] + 1'b1; rNum[19:16] <= 4'd0; end
80. else i <= i + 1'b1;
81.
82. 7:
83. if( rNum[23:20] > 4'd14 ) begin rNum <= 24'd0; i <= i + 1'b1; end
84. else i <= i + 1'b1;
85.
86. 8:
87. begin rNumber <= rNum; i <= 4'd0; end
88.
89. endcase
90.
91. /*******************************************************/
92.
93. assign Number_Sig = rNumber;
94.
95. /*******************************************************/
96.
97. endmodule
第 20~37 行之间,包含了100ms 定时的常量(25 行)和 100ms 的定时器(31~37行)。
在 41~89 就是该模块的核心部分。寄存器rNum 操作空间(42 行),然而 rNumber 是用于驱动Number_Sig(93 行)。每隔 100ms 的定时都会是rNum 递增(56 行), 58~84行之间就会执行“4 位宽”数字之间的“进位操作”。
我们假设一个情况,当rNum 的值是 24’h000009,然后在下一个100ms 的定时钟, rNum的值就会 +1 操作。在59行, if条件就会成立,rNum[3:0]就会被赋值位零,然后rNum[7:4]就会执行 +1 操作,rNum 的值成为 24’h000010。接下来的几个步骤也会执行类似的操作。
在 72~84 行表示了当 rNum 的值超过24’h999999 的时候,就会恢复为 24’h000000。
在这里我们有一个问题?为什么不直接使用rNum 驱动 Number_Sig 而是选择使用rNumber寄存器来驱动Number_Sig。如果我们把 rNum 当着 Number_Sig 的驱动对象,在 i 步骤 16~22 之间,由于“进位操作”的关系,会使得Number_Sig 的输出产生许多毛刺,因此才使用 rNumber 驱动Number_Sig。当 rNum 完成“进位操作”以后,再赋值与rNumber,由 rNumber 驱动 Number_Sig ( 87行)。
然后按照图1‑104进行顶层模块的设计,代码如下:
代码 1‑17数码管电路验证代码的顶层代码
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-05-18 23:59:39
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2019-07-21 03:49:46
7. //# Description:
8. //# @Modification History: 2019-05-19 01:41:52
9. //# Date By Version Change Description:
10.//# ========================================================================= #
11.//# 2019-05-19 01:41:52
12.//# ========================================================================= #
13.//# | | #
14.//# | OpenFPGA | #
15.//****************************************************************************//
16.module smg_interface_demo
17.(
18. input CLOCK,
19. input RST_n,
20. output [7:0]SMG_Data,
21. output [5:0]Scan_Sig
22.);
23.
24. /******************************/
25.
26. wire [23:0]Number_Sig;
27.
28. demo_control_module U1
29. (
30. .CLOCK( CLOCK ),
31. .RST_n( RST_n ),
32. .Number_Sig( Number_Sig ) // output - to U2
33. );
34.
35. /******************************/
36.
37. smg_interface U2
38. (
39. .CLOCK( CLOCK ),
40. .RST_n( RST_n ),
41. .Number_Sig( Number_Sig ), // input - from U1
42. .SMG_Data( SMG_Data ), // output - to top
43. .Scan_Sig( Scan_Sig ) // output - to top
44. );
45.
46. /******************************/
47.
48.endmodule
综合后的RTL如下:
图1‑105数码管电路验证代码的顶层代码综合后RTL图
基本和图1‑104一样,将代码下载到目标板上就可以看到数码管已经变成了一个“秒表”了。
代码地址
github
你笑一次,我就可以高兴好几天;可看你哭一次,我就难过了好几年…
https://github.com/suisuisi/FPGA/tree/master/FPGAandPI