一天一个设计实例-FPGA和数码管
1.1.1数码管基础知识
数码管由8个发光二极管(以下简称字段)构成,通过不同的组合可显示数字0~9、字符A~F、H、L、P、R、U、Y、符号“”及小数点“”。数码管的外型结构如图所示。数码管又分为共阴极和共阳极两种结构,分别如图所示。
图1‑94 共阴极和共阳极数码管
(a)共阴极(b)共阳极
共阳极数码管的8个发光二极管的阳极(二极管正端)连接在一起,通常接高电平(一般接电源),其它管脚接段驱动电路输出端。当某段驱动电路的输出端为低电平时,该端所连接的字段导通并点亮,根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能吸收额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。
共阴极数码管的8个发光二极管的阴极(二极管负端)连接在一起,通常接低电平(一般接地),其它管脚接段驱动电路输出端。当某段驱动电路的输出端为高电平时,该端所连接的字段导通并点亮,根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能提供额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。
要使数码管显示出相应的数字或字符,必须使段数据口输出相应的字型编码。对照下图,字型码各位定义如下:
表1‑37 数码管字型码定义
数据线 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
I/O口线 |
P0.7 |
P0.6 |
P0.5 |
P0.4 |
P0.3 |
P0.2 |
P0.1 |
P0.0 |
LED段 |
dp |
g |
f |
e |
d |
c |
b |
a |
数据线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.2FPGA和数码管驱动
使用动态扫描还是静态扫描其实取决于硬件设计,并不取决于驱动程序,一旦硬件确定下来那么就需要驱动去适配硬件,本次设计使用的数码管为共阳极数码管,动态扫描加载,电路如下:
图1‑97 动态数码管设计电路
表1‑39 数码管设计资源
设计的资源如表1‑39所示,其中扫描频率是1s扫描的频率,即1/6ms≈166.67Hz。
数码管主要考虑输入调用它的输入。即如何充分减少资源以实现数码管动态显示。
图1‑98 数码管电路设计建模图
表1‑40 建模图中模块间信号定义
模块间信号 |
||
管脚 |
传输方向 |
作用/说明 |
Number_Data |
数码管控制模块传给数码管加码模块 |
显示的数字0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F |
时序 |
见上述分析 |
|
表1‑41 数码管电路设计建模图顶层模块信号定义
顶层模块 |
|||
输入管脚 |
作用/说明 |
输出管脚 |
作用/说明 |
Number_Sig |
每位数码管要显示的内容 |
SMG_data |
数码管段选信号 |
Scan_Sig |
数码管位选信号 |
||
时序 |
见上述时序分析 |
||
整个系统设计结构如图1‑98所示,上图的组合模块-数码管接口 smg_interface.v 中,输入信号 Number_Sig 占了 24 位宽,然而 Number_Sig 的位分配如下表:
表1‑42 Number_Sig 的位分配表
为什么每一位数字数码管,都用 4 位位宽来代表呢?
每数码管可以支持显示 0~F,正是因为如此,如果我们用每 4 位位宽来代表某一个数码管显示的信号,那么可以避免“使用除法或者求余运算符执行十进制的取位操作”。一来方便设计,而来减少资源。
举个例子:24'h123456, 亦即 0001 0010 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在 T1, smg_control_module.v 会将 Number_Sig[23:20] 送往至 smg_encode_module.v 加码并且送往数码管。在同一时间 smg_scan_module.v 会扫描第一位数码管(使能)。在 T2, smg_control_module.v 会将 Number_Sig[19:16] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第二位数码管(使能)。在 T3, smg_control_module.v 会将 Number_Sig[15:12] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第三位数码管(使能)。在 T4, smg_control_module.v 会将 Number_Sig[11:8] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第四位数码管(使能)。在 T5, smg_control_module.v 会将 Number_Sig[7:4] 送往至 smg_encode_module.v 加码并且送往数码管,在同一时间 smg_scan_module.v 会扫描第五位数码管(使能)。在 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‑99 加码扫描过程理想时序图·
图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一样,将代码下载到目标板上就可以看到数码管已经变成了一个“秒表”了。