【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验六:数码管模块

实验六:数码管模块

有关数码管的驱动,想必读者已经学烂了 ... 不过,作为学习的新仪式,再烂的东西也要温故知新,不然学习就会不健全。黑金开发板上的数码管资源,由始至终都没有改变过,笔者因此由身怀念。为了点亮多位数码管从而显示数字,一般都会采用动态扫描,然而有关动态扫描的信息请怒笔者不再重复。在此,同样也是动态扫描,但我们却用不同的思路去理解。

图6.1 6位数码管。

如图6.1所示,哪里有一排6位数码管,其中包好8位DIG信号还有6位SEL信号。DIG为digit,即俗称的数码管码,如果数码管预要显示“A”,那么DIG必须输入“A”的数码管码。SEL为select,即俗称的位选,从左至右即SEL[0]~SEL[5],如果想要使能第一位最左边的数码管,SEL[0]必须设置有效位。不管DIG还是SEL,黑金开发板都是拉低有效,亦即0值表示有效位。

图6.2 信号DIG与数码管码。

DIG信号位宽为8,亦即一个数码管资源内藏8个LED,8位DIG信号分别表示各个LED,结果如图6.2所示。除了DIG[7]较为少用以外,DIG[0]~[6] 一般都用来显示十六进制的数字 0~F。笔者在此强调,黑金开发板所采用的数码管是拉低有效,亦即点亮LED笔者设置为0。为此,十六进制的数字0~F可以用Verilog这样表示,如代码6.1所示:

1.         parameter     _0 = 8‘b1100_0000, _1 = 8‘b1111_1001, _2 = 8‘b1010_0100, 
2.                        _3 = 8‘b1011_0000, _4 = 8‘b1001_1001, _5 = 8‘b1001_0010, 
3.                      _6 = 8‘b1000_0010, _7 = 8‘b1111_1000, _8 = 8‘b1000_0000,
4.                      _9 = 8‘b1001_0000, _A = 8‘b1000_1000, _B = 8‘b1000_0011,
5.                      _C = 8‘b1100_0110, _D = 8‘b1010_0001, _E = 8‘b1000_0110,
6.                      _F = 8‘b1000_1110;

代码6.1

如代码6.1所示,笔者用常量声明16个16进制的数字。

图6.3 16进制数字的数码管码(拉低有效)。

为了方便读者,笔者也顺便绘制一张直观的示意图 ... 如图6.3所示,哪里有16个16进制数字以及相关的数码管码。理解DIG信号与数码管码的关系以后,接下来笔者会解释SEL信号与数码管的关系。

图6.4 信号SEL与数码管码。

如图6.4所示,我们可以看见SEL信号与数码管的关系,每当SEL为值不同,相关的数码管就会显示数字,例如SEL为值6’b111_110,最左边的数码管就会显示数字;SEL为值6’b011_111,最右边的数码管就会显示数字。如何实现自左向右轮流显示数字,就是将 6’b111_110 其中的“0值”按间隔向左位移即可,这也是动态扫描最基本的理论。

图6.5 流水灯的理想时序图(脑补)。

例如实验一的流水灯实验,流水操作负责轮流点亮4位1组的LED资源。假设流水间隔是一个时钟,如图6.5所示,LED信号分别在T0~T3之间输出4’b0001,4’b0010,4’b0100,4’b10000,上述行为重复N次以后便产生流水效果。Verilog 则可以这样描述,结果如代码6.2所示:

1.        case( i )
2.    
3.            0:
4.            begin LED <= 4’b0001; i <= i + 1’b1; end
5.            1:
6.            begin LED <= 4’b0010; i <= i + 1’b1; end
7.            2:
8.            begin LED <= 4’b0100; i <= i + 1’b1; end
9.            3:
10.            begin LED <= 4’b1000; i <= 4’d0; end
11.    
12.        endcase

代码6.2

如代码6.2所示, 相较实验一的内容,步骤0~3也是实现流水效果,不过步骤0~3却没有考虑每个步骤所保持的时间,亦即流水间隔仅有一个时钟而已。

图6.6 数码管显示数字的例子。

相较实验一的流水灯实验,动态扫描就是功能稍微复杂一点的流水等而已,SEL信号类似LED信号,不过不是点亮LED而是负责位选工作,换之DIG信号则是显示内容。

如图6.6所示,假设笔者想要显示上述的结果,即自左向右轮流显示数字0~5,其中DIG信号负责数字0~5等信息,至于数字的显示次序则是SIG信号负责。

图6.7 自左向右显示数字0~5的理想时序图(脑补)。

假设流水间隔亦然是1个时钟,为了自左向右轮流显示数字“012345”,每个时钟的SEL(位选)信息,必须对应有效的DIG内容(数码管码),结果如图6.6所示。时序发生过程如下:

T0的时候,DIG发送未来值8’b1100_0000,SEL发送未来值6’b111_110;

T1的时候,DIG发送未来值8’b1111_1001,SEL发送未来值6’b111_101;

T2的时候,DIG发送未来值8’b1010_0100,SEL发送未来值6’b111_011;

T3的时候,DIG发送未来值8’b1011_0000,SEL发送未来值6’b110_111;

T4的时候,DIG发送未来值8’b1001_1001,SEL发送未来值6’b101_111;

T5的时候,DIG发送未来值8’b1001_0010,SEL发送未来值6’b011_111;

Verilog则可以这样表示,如代码6.3所示:

1.        case( i )
2.    
3.            0:
4.            begin DIG <= 8’b1100_0000; SEL <= 6’b111_110; i <= i + 1’b1; end
5.            1:
6.            begin DIG <= 8’b1111_1001; SEL <= 6’b111_101; i <= i + 1’b1; end
7.            2:
8.            begin DIG <= 8’b1010_0100; SEL <= 6’b111_011; i <= i + 1’b1; end
9.            3:
10.            begin DIG <= 8’b1011_0000; SEL <= 6’b110_111; i <= i + 1’b1; end
11.            4:
12.            begin DIG <= 8’b1001_1001; SEL <= 6’b101_111; i <= i + 1’b1; end
13.            5:
14.            begin DIG <= 8’b1001_0010; SEL <= 6’b011_111; i <= 4’d0; end
15.    
16.    endcase

代码6.3

如代码6.3所示,步骤0~5分别对应6位数码管的显示次序。如步骤0为DIG被赋予8’b1100_0000,即数字0的数码管码,期间SEL也被赋予6’b111_110,即点亮第一位数码管(左边第一个);步骤1为DIG被赋予8’b1111_1001,即数字1的数码管码,期间SEL也被赋予6’b111_101,即点亮第二位数码管(左边第二个);至于步骤2~5以此类推,完后便返回步骤0,重复一样的操作。

流水间隔亦即动态扫描频率,常规是10ms,不过丧心病狂的笔者却设置为100us。100us经过50Mhz的时钟量化以后是 5000,Verilog则可以这样表示:

parameter T100US = 13‘d5000;

如果步骤6.3的流水间隔不是一个时钟而是100us,那么代码6.3可以这样修改,修改结果如代码6.4所示:

1.    case( i )
2.    
3.            0:
4.            if( C1 == T100US -1 ) begin C1 <= 13’d0; i <= i + 1’b1; end
5.            else DIG <= 8’b1100_0000; SEL <= 6’b111_110; end
6.            ...
7.            5:
8.            if( C1 == T100US -1 ) begin C1 <= 13’d0; i <= i + 1’b1; end
9.            else DIG <= 8’b1001_0010; SEL <= 6’b011_111; end
10.    
11.    endcase

代码6.4

理解这些内容以后,我们便可以开始建模了 ...

图6.8 实验六的建模图。

如图6.8所示,那是实验六的建模图,其中smg_basemod 是组合模块,它包含数码管功能模块,还有数码管加码模块。接下来,让我们来分析一下内部情况,数码管功能模块它有一组24位的iData,然后又有10位oData。随后oData[9:6]会经由加码模块(即时模块)成为8位的数码管信息,并且驱动DIG顶层信号。反之oData[5:0]则会直接驱动SEL顶层信号。

smg_funcmod.v

图6.9 数码管功能模块。

接下来,让我们独自分析个体模块 ... 首先是数码管功能模块,人如其名它是负责所有数码管驱动工作的功能模块,24位的iData分别针对6位数码管的显示内容(数字),位分配如表6.1所示:

表 6.1 输入数据位分配


位分配


[23..20]


[19..16]


[15..12]


[11..8]


[7..4]


[3..0]


数码管分配


第一位


第二位


第三位


第四位


第五位


第六位

至于oData的作用如上所示,oData[9:6]必须经由数码管加码模块,oData[5:0]则直接驱动SEL顶层信号。详细内容就让我们直接窥视代码吧:

1.    module smg_funcmod
2.    (
3.         input CLOCK, RESET,
4.         input [23:0]iData,
5.         output [3:0]oData
6.    );
7.         parameter T100US = 13‘d5000;

以上内容为相关出入端声明。第8行则是100us的常量声明(流水间隔/停留时间)。

8.     
9.         reg [3:0]i;
10.         reg [12:0]C1;
11.         reg [3:0]D1;
12.         reg [5:0]D2;
13.         
14.         always @ ( posedge CLOCK or negedge RESET )
15.             if( !RESET )
16.                  begin
17.                          i <= 4‘d0;
18.                         C1 <= 13‘d0;
19.                             D1 <= 4‘d0;
20.                         D2 <= 6‘b111_110;
21.                    end
 
以上内容为相关的寄存器声明以及复位操作。D1暂存iData的部分数据,D2则暂存位选数据。第17~21则是这些寄存器的复位操作。
 
22.              else 
23.                  case( i )
24.                    
25.                         0:
26.                         if( C1 == T100US -1 ) begin C1 <= 13‘d0; i <= i + 1‘b1; end
27.                         else begin C1 <= C1 + 1‘b1; D1 <= iData[23:20]; D2 <= 6‘b111_110; end
28.                         
29.                         1:
30.                         if( C1 == T100US -1 ) begin C1 <= 13‘d0; i <= i + 1‘b1; end
31.                         else begin C1 <= C1 + 1‘b1; D1 <= iData[19:16]; D2 <= 6‘b111_101; end
32.                         
33.                         2:
34.                         if( C1 == T100US -1 ) begin C1 <= 13‘d0; i <= i + 1‘b1; end
35.                         else begin C1 <= C1 + 1‘b1; D1 <= iData[15:12]; D2 <= 6‘b111_011; end
36.                         
37.                         3:
38.                         if( C1 == T100US -1 ) begin C1 <= 13‘d0; i <= i + 1‘b1; end
39.                         else begin C1 <= C1 + 1‘b1; D1 <= iData[11:8]; D2 <= 6‘b110_111; end
40.                         
41.                         4:
42.                         if( C1 == T100US -1 ) begin C1 <= 13‘d0; i <= i + 1‘b1; end
43.                         else begin C1 <= C1 + 1‘b1; D1 <= iData[7:4]; D2 <= 6‘b101_111; end
44.                         
45.                         5:
46.                         if( C1 == T100US -1 ) begin C1 <= 13‘d0; i <= 4‘d0; end
47.                         else begin C1 <= C1 + 1‘b1; D1 <= iData[3:0]; D2 <= 6‘b011_111; end
48.                         
49.                    endcase
50.         
51.         assign oData = { D1,D2 };
52.         
53.    endmodule

以上内容为核心操作以及输出驱动声明,步骤0~5用来轮流点亮数码管,大概的思路之前已经解释过 ... 举例来说,步骤0为D1赋予iData[23:20]的内容,D2赋予 6’b111_110的内容,简单说就是将iData[23:20]的数字显示在第一位数码管,至于步骤1~5也是以此类推,完后操作会返回步骤0。第51行则是输出驱动声明。

smg_encode_immdmod.v

图6.10 数码管加码(即时)模块。

如图6.10所示,数码管加码模块是一只即时模块,它有一组4位的iData与一组8位的oData。该模块接收iData的内容,然后转换为数码管信息,最后再经由oData输出。具体内容,还是来浏览代码吧:

1.    module smg_encode_immdmod
2.    (
3.         input [3:0]iData,
4.         output [7:0]oData
5.    );
6.         parameter      _0 = 8‘b1100_0000, _1 = 8‘b1111_1001, _2 = 8‘b1010_0100, 
7.                       _3 = 8‘b1011_0000, _4 = 8‘b1001_1001, _5 = 8‘b1001_0010, 
8.                      _6 = 8‘b1000_0010, _7 = 8‘b1111_1000, _8 = 8‘b1000_0000,
9.                      _9 = 8‘b1001_0000, _A = 8‘b1000_1000, _B = 8‘b1000_0011,
10.                      _C = 8‘b1100_0110, _D = 8‘b1010_0001, _E = 8‘b1000_0110,
11.                      _F = 8‘b1000_1110;
12.         
13.         reg [7:0]D = 8‘b1111_1111;
14.         
15.         always @ ( * )
16.              if( iData == 4‘d0 ) D = _0;
17.              else if( iData == 4‘d1 ) D = _1;
18.              else if( iData == 4‘d2 ) D = _2;
19.              else if( iData == 4‘d3 ) D = _3;
20.              else if( iData == 4‘d4 ) D = _4;
21.              else if( iData == 4‘d5 ) D = _5;
22.              else if( iData == 4‘d6 ) D = _6;
23.              else if( iData == 4‘d7 ) D = _7;
24.              else if( iData == 4‘d8 ) D = _8;
25.              else if( iData == 4‘d9 ) D = _9;
26.              else if( iData == 4‘hA ) D = _A;
27.              else if( iData == 4‘hB ) D = _B;
28.              else if( iData == 4‘hC ) D = _C;
29.              else if( iData == 4‘hD ) D = _D;
30.              else if( iData == 4‘hE ) D = _E;
31.              else if( iData == 4‘hF ) D = _F;
32.              else D = 8‘dx;
33.         
34.         assign oData = D;
35.    
36.    endmodule

第3~4行是出入端声明。第6~11行是数码管码0~F的常量声明。第13行是相关的寄存器声明。第15~32行则是加码操作。第34行是输出驱动声明。

smg_basemod.v

至于组合模块smg_basemod笔者就不重复贴图了,读者请自行看回图6.8。详细的内容让我们来浏览代码吧:

1.    module smg_basemod
2.    (
3.         input CLOCK, RESET,
4.         output [7:0]DIG,
5.         output [5:0]SEL
6.    );     
7.        wire [9:0]DataU1;
8.    
9.        smg_funcmod U1
10.        (
11.              .CLOCK( CLOCK ),
12.              .RESET( RESET ),
13.              .iData( 24‘hABCDEF ), // < top
14.              .oData( DataU1 )  // > U2
15.        );
16.        
17.        assign SEL = DataU1[5:0];
18.    
19.         smg_encode_immdmod U2
20.         (
21.              .iData( DataU1[9:6] ),  // < U1
22.              .oData( DIG )       // > top
23.         );
24.         
25.    endmodule

该代码由于演示的作用,并没有将U1的iData直接引出,而是直接在其输入设置常量24’hABCDEF(第13行)。至于相关的连线部署就复习图6.8吧。编译完后下载程序,我们便会发现数字“ABCDEF”分别自左向右显示在6位数码管。

细节一:完整的个体模块

图6.11 数码管基础模块的建模图。

如图6.11所示,那是完整的数码管基础模块,除了将smg_funcmod的iData向外引出,余下都一样。

smg_basemod.v
1.    module smg_basemod
2.    (
3.         input CLOCK, RESET,
4.         input [23:0]iData,
5.         output [7:0]DIG,
6.         output [5:0]SEL
7.    );     
8.        wire [3:0]DataU1;
9.    
10.        smg_funcmod U1
11.        (
12.              .CLOCK( CLOCK ),
13.              .RESET( RESET ),
14.              .iData( iData ), // < top
15.              .oData( DataU1 ),  // > U2
16.         );
17.        
18.        assign SEL = DataU1[5:0];
19.     
20.         smg_encode_immdmod U2
21.         (
22.              .iData( DataU1[9:6] ),  // < U1
23.              .oData( DIG )        // > top
24.         );
25.         
26.    endmodule

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验六:数码管模块,布布扣,bubuko.com

时间: 2024-11-03 03:33:24

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验六:数码管模块的相关文章

【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读

前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例如UART,VGA等,但是<驱动篇I>贵就贵在建模技巧的升华,亦即低级建模II. 话说低级建模II,读过<建模篇>的朋友多少也会面熟几分,因为它是低级建模的进化形态.许久以前,笔者就有点燃低级建模II的念头,但是懒惰的性格让笔者别扭许久.某天,老大忽然说道:"让咱们大干一场吧

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验三:按键模块② — 点击与长点击

实验三:按键模块② - 点击与长点击 实验二我们学过按键功能模块的基础内容,其中我们知道按键功能模块有如下操作: l 电平变化检测: l 过滤抖动: l 产生有效按键. 实验三我们也会z执行同样的事情,不过却是产生不一样的有效按键: l 按下有效(点击): l 长按下有效(长点击). 图3.1 按下有效,时序示意图. 图3.2 长按下有效,时序示意图. 如图3.1所示,按下有效既是"点击",当按键被按下并且消抖完毕以后,isSClick信号就有被拉高一个时钟(Short Click).

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二:按键模块① - 消抖

实验二:按键模块① - 消抖 按键消抖实验可谓是经典中的经典,按键消抖实验虽曾在<建模篇>出现过,而且还惹来一堆麻烦.事实上,笔者这是在刁难各位同学,好让对方的惯性思维短路一下,但是惨遭口水攻击 ... 面对它,笔者宛如被甩的男人,对它又爱又恨.不管怎么样,如今 I'll be back,笔者再也不会重复一样的悲剧. 按键消抖说傻不傻说难不难.所谓傻,它因为原理不仅简单(就是延迟几下下而已),而且顺序语言(C语言)也有无数不尽的例子.所谓难,那是因为人们很难从单片机的思维跳出来 ... 此外,

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验四:按键模块③ — 单击与双击

实验四:按键模块③ — 单击与双击 实验三我们创建了“点击”还有“长点击”等有效按键的多功能按键模块.在此,实验四同样也是创建多功能按键模块,不过却有不同的有效按键.实验四的按键功能模块有以下两项有效按键: l 单击(按下有效): l 双击(连续按下两下有效). 图4.1 单击有效按键,时序示意图. 实验四的“单击”基本上与实验三的“点击”一模一样,既按键被按下,经过消抖以后isSClick信号被拉高一个时钟,结果如图4.1所示,过程非常单调.反之,“双击”实现起来,会比较麻烦一些,因为我们还要

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验五:按键模块④ — 点击,长点击,双击

实验五:按键模块④ — 点击,长点击,双击 实验二至实验四,我们一共完成如下有效按键: l 点击(按下有效) l 点击(释放有效) l 长击(长按下有效) l 双击(连续按下有效) 然而,不管哪个实验都是只有两项“功能”的按键模块而已,如今我们要创建三项“功能”的按键模块,亦即点击(按下有效),长击,还有双击.实验继续之前,让我们先来复习一下各种有效按键. 图5.1 点击(按下有效). 如图5.1所示,所谓点击(按下有效)就是按键按下以后,isSClick信号(Single Click) 产生一

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验九:PS/2模块③ — 键盘与多组合键

实验九:PS/2模块③ — 键盘与多组合键 笔者曾经说过,通码除了单字节以外,也有双字节通码,而且双字节通码都是 8’hE0开头,别名又是 E0按键.常见的的E0按键有,<↑>,<↓>,<←>,<→>,<HOME>,<PRTSC> 等编辑键.除此之外,一些组合键也是E0按键,例如 <RCtrl> 或者 <RAlt> .所以说,当我们设计组合键的时候,除了考虑“左边”的组合键以外,我们也要考虑“右边”的组合键.&

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验七:PS/2模块① — 键盘

实验七:PS/2模块① — 键盘 实验七依然也是熟烂的PS/2键盘.相较<建模篇>的PS/2键盘实验,实验七实除了实现基本的驱动以外,我们还要深入解PS/2时序,还有PS/2键盘的行为.不过,为了节省珍贵的页数,怒笔者不再重复有关PS/2的基础内容,那些不晓得的读者请复习<建模篇>或者自行谷歌一下. 市场上常见的键盘都是应用第二套扫描码,各种扫描码如图7.2所示.<建模篇>之际,笔者也只是擦边一下PS/2键盘,简单读取单字节通码与断码而已.所谓单字节通码,就是有效的按下

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验八:PS/2模块② — 键盘与组合键

实验八:PS/2模块② — 键盘与组合键 实验七之际,我们学习如何读取PS/2键盘发送过来的通码与断码,不过实验内容也是一键按下然后释放,简单按键行为而已.然而,实验八的实验内容却是学习组合键的按键行为. 不知读者是否有类似的经历?当我们使用键盘的时候,如果5~6按键同时按下,电脑随之便会发出“哔哔”的警报声,键盘立即失效.这是键盘限制设计,不同产品也有不同限制的按键数量.默认下,最大按键数量是5~7个.所谓组合键就是两个以上的按键所产生的有效按键.举例而言,按下按键 <A> 输出“字符a”,

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十三:DS1302模块

实验二十三:DS1302模块 DS1302这只硬件虽然曾在<建模篇>介绍过,所以重复的内容请怒笔者懒惰唠叨了,笔者尽可以一笑带过,废话少说让我们进入正题吧.DS1302是执行事实时钟(Real Time Clock)的硬件,采用SPI传输. 表示23.1 访问(地址)字节. [7] [6] [5] [4] [3] [2] [1] [0] 1 A5 A4 A3 A2 A1 A0 R/W DS1302作为从机任由主机蹂躏 ... 啊,是任由主机访问才对.对此,访问便有方向之分.如表23.1所示,访