【黑金原创教程】【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 [9: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 】实验六:数码管模块的更多相关文章
- [黑金原创教程] FPGA那些事儿《设计篇 III》- 图像处理前夕·再续
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《设计篇 II》- 图像处理前夕·续
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《设计篇 I》- 图像处理前夕
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《数学篇》- CORDIC 算法
简介 一本为完善<设计篇>的书,教你CORDIC算法以及定点数等,内容请看目录. 贴士 这本教程难度略高,请先用<时序篇>垫底. 目录 Experiment 01:认识CORD ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二:按键模块① - 消抖
实验二:按键模块① - 消抖 按键消抖实验可谓是经典中的经典,按键消抖实验虽曾在<建模篇>出现过,而且还惹来一堆麻烦.事实上,笔者这是在刁难各位同学,好让对方的惯性思维短路一下,但是惨遭口 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验七:PS/2模块① — 键盘
实验七:PS/2模块① — 键盘 实验七依然也是熟烂的PS/2键盘.相较<建模篇>的PS/2键盘实验,实验七实除了实现基本的驱动以外,我们还要深入解PS/2时序,还有PS/2键盘的行为.不 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十三:串口模块② — 接收
实验十三:串口模块② - 接收 我们在实验十二实现了串口发送,然而这章实验则要实现串口接收 ... 在此,笔者也会使用其它思路实现串口接收. 图13.1 模块之间的数据传输. 假设我们不考虑波特率,而 ...
随机推荐
- 异常 try catch finally return 执行关系 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- UE.getEditor('editor')
<script type="text/javascript"> $().ready(function(){ var editor = document.getEleme ...
- (亲测解决)每次打开excel文件都会出现两个窗口,一个是空白的sheet1,另一个是自己的文档
版权声明:本文为博主原创文章,转载请注明出处! https://blog.csdn.net/sinat_34104446/article/details/80210424 一.问题描述每次打开Exce ...
- 基于CentOS体验万象优图鉴黄服务
系统要求:CentOS 7.2 64 位操作系统 初始化配置 使用万象优图图片鉴黄 API 接口,我们需要先完成以下步骤: 获取腾讯云账号 APP ID 配置云 API 公钥/密钥 配置优图 buck ...
- dos命令dir查找文件的用法及实例
功能:显示目录命令 格式:dir[盘符][路径][/W][/P][/L][/O:排序][/A:属性][/S] 参数介绍: /W -- 以宽行排列方式显示. /P -- 每显示满一屏停顿一下,待用户 ...
- django --- DetailView源码分析
[背景] 最近在看django官方文档的class-based-views这一节的时候一直不得要领,感觉自己清楚,但是回想起来又没有脉络:于是没有办法只 能是“暗中观察”django的源码了. 刚打开 ...
- struts2:多业务方法的处理(动态调用,DMI)
struts2支持调用指定Action类中某一个业务方法.如果没有指定,则调用execute方法. 1. 第一种实现方式,通过URL叹号参数 1.1 创建Action类,带多个方法 package c ...
- 物联网架构成长之路(22)-Docker练习之Etcd服务搭建
0. 前言 时隔多日,前段时间忙完一个可有可无的项目后,又进入摸鱼时间,没有办法,非互联网公司,就是闲得蛋疼.又开始了自学之路.以前入门过Docker,然后又很久没有看了,最近重新看了一下,推荐一下这 ...
- epoll的由来
reference https://www.zhihu.com/question/20122137 感谢 @静海听风 @蓝形参 数据流有两个重要的参与者: 1.往流中写入数据者 2.从流中读取数据者 ...
- pycharm如何在debug的时候动态执行python语句
在调试MATLAB的时候,这一点很容易实现,比如动态修改变量的值,在VS2017中调试python程序,这一点也很容易实现,但是我在pycharm里面找了半天,如下图: