正点原子 发表于 2021-1-26 17:25:11

【正点原子FPGA连载】第十四章动态数码管显示实验

1)实验平台:正点原子超越者FPGA开发板
2)章节摘自【正点原子】超越者之FPGA开发指南
3)购买链接:https://item.taobao.com/item.htm?&id=631660290421
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流群:905624739





第十四章动态数码管显示实验



经过上一章的学习,我们已经知道如何使用数码管静态驱动的方式使数码管显示数字,但在很多情况下,我们需要让数码管各个位显示不同的数字,这就需要以动态驱动的方式驱动数码管。本章我们主要介绍数码管动态驱动的原理以及如何使用动态驱动的方式在数码管上显示变化的数字。
本章分为以下几个章节:
1414.1简介
14.2实验任务
14.3硬件设计
14.4程序设计
14.5下载验证

14.1简介
在“数码管静态显示实验”章节我们详细地介绍了有关数码管方面的知识和数码管的静态驱动,让大家对数码管的驱动有了基本的了解。由于一般的静态驱动操作虽然方便,但占用的I/0口较多,例如要驱动6位8段数码管,以静态驱动方式让数码管各个位显示不同的数值,如“123456”,需要占用个I/O口,虽然对于FPGA这种I/O口较多的芯片而言,在资源允许的情况下可以使用,但一般不建议浪费宝贵的I/O口资源,尤其在I/O口资源紧张的情况下,所以对于多位数码管一般采用动态驱动方式使数码管显示数字。那么什么是动态驱动方式呢?
为了更好的理解数码管动态驱动,我们首先了解下市面上常见的多位数码管的内部连接。以两位数码管为例,其内部连接如下图。由此图可知,两位8段数码管共10个引脚,每位数码管的阳极连接在一起,为共阳极数码管,每位数码管相同段的led的阴极连接在一起,这样当给第10和第5脚高电平,给第3脚低电平时,两个数码管的发光二极管A都点亮,对于此种数码管以静态方式驱动显然不可能显示像“18”这种个位与十位不同的数字。那么该如何显示数字“18”呢?

图 14.1.1 多位数码管内部连接图
既然同时给第10和第5脚高电平不可行,那么是不是可以先给第5脚高电平,第10脚低电平,此时,让其显示数字“8”时,左边的数码管不显示,右边的数码管显示数字“8”;然后给第10脚高电平,第5脚低电平,此时,让其显示数字“1”时,左边的数码管显示数字“1”,右边的数码管不显示,这样就可以显示数字“18”了。但有一个问题,多长时间切换显示的数码管呢,时间如果太长就只能看到数字“8”或数字“1”了,时间太短呢,结果是显示不清晰而且显示亮度不够。由于人眼的视觉暂留(人眼在观察景物时,光信号传递到人大脑神经后,会有一段短暂的时间停留,即光的作用结束后,视觉形象并不立即消失,这种残留的视觉称“后像”,视觉的这一现象则被称为“视觉暂留”)及发光二极管的余辉效应(当停止向发光二极管供电时,发光二极管亮度仍能维持一段时间),每位数码管的点亮时间为1~2ms时,显示效果能满足使用需要。数码管的这种驱动方式称为数码管的动态驱动,实际上就是分时轮流控制不同数码管的显示。
14.2实验任务
本节实验任务是使用超越者开发板上的6位数码管以动态方式从0开始计数,每100ms计数值增加一,当计数值从0增加到999999后重新从0开始计数。
14.3硬件设计
数码管接口部分的硬件设计原理及本实验中各端口信号的管脚分配与“数码管静态显示实验”完全相同,请参考“数码管静态显示实验”中的硬件设计部分。
14.4程序设计
由实验任务和动态驱动的原理我们可以知道,若要让6个数码管轮流显示对应的数字,首先需要一个数码管动态显示模块,能够依次点亮6个数码管,并将对应的数据输出至数码管,也就是需要分别控制段选和位选信号;同时还需要一个计数模块,能够将0—999999依次输出至数码管动态显示模块。根据实验任务,我们可以大致规划出系统的控制流程:首先我们需要一个数码管动态显示模块在数码管上显示数据,其次需要一个计数控制模块实现从0到999999的变化,并将产生的数值通过数码管动态显示模块在数码管上显示出来。由此画出系统的功能框图如下所示:

图 14.4.1 数码管动态显示实验系统框图
程序中各模块端口及信号连接如图 14.4.2所示:

图 14.4.2 顶层模块原理图
FPGA顶层(top_seg_led)例化了以下两个模块:计数模块(count)以及数码管动态显示模块(seg_led)。顶层模块实现各模块之间数据的交互。计数模块将计数值通过data端口传递给数码管动态显示模块,使能信号en使能数码管显示数据,小数点显示信号point控制小数点的显示,符号信号sign可以让数码管显示负号。
计数模块(count):显示的数字每100ms加“1”。
数码管动态显示模块(seg_led):数码管动态显示模块在数码管上以动态方式显示数值。
顶层模块的代码如下:
1module seg_led_top(
2      //global clock
3      input            sys_clk,       // 全局时钟信号
4      input            sys_rst_n,       // 复位信号(低有效)
5
6      //seg_led interface
7      output    seg_sel,       // 数码管位选信号
8      output    seg_led          // 数码管段选信号
9);
10
11 //wire define
12 wire    data;               // 数码管显示的数值
13 wire    [ 5:0]point;                // 数码管小数点的位置
14 wire            en;                   // 数码管显示使能信号
15 wire            sign;               // 数码管显示数据的符号位
16
17 //*****************************************************
18 //**                  main code
19 //*****************************************************
20
21 //计数器模块,产生数码管需要显示的数据
22 count u_count(
23   .clk         (sys_clk),       // 时钟信号
24   .rst_n         (sys_rst_n),       // 复位信号
25
26   .data          (data   ),       // 6位数码管要显示的数值
27   .point         (point    ),       // 小数点具体显示的位置,高电平有效
28   .en            (en       ),       // 数码管使能信号
29   .sign          (sign   )      // 符号位
30 );
31
32 //数码管动态显示模块
33 seg_led u_seg_led(
34   .clk         (sys_clk),       // 时钟信号
35   .rst_n         (sys_rst_n),       // 复位信号
36
37   .data          (data   ),       // 显示的数值
38   .point         (point    ),       // 小数点具体显示的位置,高电平有效
39   .en            (en       ),       // 数码管使能信号
40   .sign          (sign   ),       // 符号位,高电平显示负号(-)
41   
42   .seg_sel       (seg_sel),       // 位选
43   .seg_led       (seg_led)      // 段选
44 );
45
46 endmodule
顶层模块中主要完成对其余模块的例化,并且实现各模块之间信号的交互。计数模块输出的数值data连接至数码管显示模块的输入端口data,数码管显示模块将输入的数据data输出至数码管上显示。
计数模块的代码如下所示:
1module count(
2      //mudule clock
3      input                   clk,      // 时钟信号
4      input                   rst_n,      // 复位信号
5      
6      //user interface
7      output   reg    data ,      // 6个数码管要显示的数值
8      output   reg [ 5:0]   point,      // 小数点的位置,高电平点亮对应数码管位上的小数点
9      output   reg            en   ,      // 数码管使能信号
10   output   reg            sign      // 符号位,高电平时显示负号,低电平不显示负号
11 );
12
13 //parameter define
14 parameterMAX_NUM = 23'd5000_000;      // 计数器计数的最大值
15
16 //reg define
17 reg       cnt ;                   // 计数器,用于计时100ms
18 reg             flag;                   // 标志信号
19
20 //*****************************************************
21 //**                  main code
22 //*****************************************************
23
24 //计数器对系统时钟计数达10ms时,输出一个时钟周期的脉冲信号
25 always @ (posedge clk or negedge rst_n) begin
26   if (!rst_n) begin
27         cnt <= 23'b0;
28         flag<= 1'b0;
29   end
30   else if (cnt < MAX_NUM - 1'b1) begin
31         cnt <= cnt + 1'b1;
32         flag<= 1'b0;
33   end
34   else begin
35         cnt <= 23'b0;
36         flag <= 1'b1;
37   end
38 end
39
40 //数码管需要显示的数据,从0累加到999999
41 always @ (posedge clk or negedge rst_n) begin
42   if (!rst_n)begin
43         data<= 20'b0;
44         point <=6'b000000;
45         en    <= 1'b0;
46         sign<= 1'b0;
47   end
48   else begin
49         point <= 6'b000000;             //不显示小数点
50         en    <= 1'b1;                  //打开数码管使能信号
51         sign<= 1'b0;                  //不显示负号
52         if (flag) begin               //显示数值每隔0.01s累加一次
53             if(data < 20'd999999)
54               data <= data +1'b1;   
55             else
56               data <= 20'b0;
57         end
58   end
59 end
60
61 endmodule
代码中第14行的参数MAX_NUM为计数的最大计数值,由于是对时钟计数,相当于计时,第25行的always语句块表示的是当计数器cnt计数值小于MAX_NUM - 1'b1时,标志(flag)为“0”,否则标志(flag)为“1”,并且计数器cnt清零。通过Chipscope抓到的波形图如下图所示,可知,计时100ms正确。

图 14.4.3 Chipscope波形图
数码管动态显示模块的代码如下:
1   module seg_led(
2       input                   clk    ,      // 时钟信号
3       input                   rst_n,      // 复位信号
4   
5       input             data   ,      // 6位数码管要显示的数值
6       input            point,      // 小数点具体显示的位置,从高到低,高电平有效
7       input                   en   ,      // 数码管使能信号
8       input                   sign   ,      // 符号位(高电平显示"-"号)
9   
10      output   reg   seg_sel,      // 数码管位选,最左侧数码管为最高位
11      output   reg   seg_led         // 数码管段选
12      );
13
14//parameter define
15localparamCLK_DIVIDE = 4'd10   ;      // 时钟分频系数
16localparamMAX_NUM    = 13'd5000;      // 对数码管驱动时钟(5MHz)计数1ms所需的计数值
17
18//reg define
19reg    [ 3:0]             clk_cnt;      // 时钟分频计数器
20reg                     dri_clk;      // 数码管的驱动时钟,5MHz
21reg                 num      ;      // 24位bcd码寄存器
22reg                 cnt0   ;      // 数码管驱动时钟计数器
23reg                     flag   ;      // 标志信号(标志着cnt0计数达1ms)
24reg                cnt_sel;      // 数码管位选计数器
25reg                num_disp ;      // 当前数码管显示的数据
26reg                     dot_disp ;      // 当前数码管显示的小数点
27
28//wire define
29wire               data0    ;      // 个位数
30wire               data1    ;      // 十位数
31wire               data2    ;      // 百位数
32wire               data3    ;      // 千位数
33wire               data4    ;      // 万位数
34wire               data5    ;      // 十万位数
35
36//*****************************************************
37//**                  main code
38//*****************************************************
39
40//提取显示数值所对应的十进制数的各个位
41assigndata0 = data % 4'd10;               // 个位数
42assigndata1 = data / 4'd10 % 4'd10   ;    // 十位数
43assigndata2 = data / 7'd100 % 4'd10;    // 百位数
44assigndata3 = data / 10'd1000 % 4'd10 ;   // 千位数
45assigndata4 = data / 14'd10000 % 4'd10;   // 万位数
46assigndata5 = data / 17'd100000;          // 十万位数
47
48//对系统时钟10分频,得到的频率为5MHz的数码管驱动时钟dri_clk
49always @(posedge clk or negedge rst_n) begin
50   if(!rst_n) begin
51         clk_cnt <= 4'd0;
52         dri_clk <= 1'b1;
53   end
54   else if(clk_cnt == CLK_DIVIDE/2 - 1'd1) begin
55         clk_cnt <= 4'd0;
56         dri_clk <= ~dri_clk;
57   end
58   else begin
59         clk_cnt <= clk_cnt + 1'b1;
60         dri_clk <= dri_clk;
61   end
62end
63
64//将20位2进制数转换为8421bcd码(即使用4位二进制数表示1位十进制数)
65always @ (posedge dri_clk or negedge rst_n) begin
66      if (!rst_n)
67          num <= 24'b0;
68      else begin
69          if (data5 || point) begin   //如果显示数据为6位十进制数,
70            num <= data5;         //则依次给6位数码管赋值
71            num <= data4;
72            num <= data3;
73            num<= data2;
74            num[ 7:4]<= data1;
75            num[ 3:0]<= data0;
76          end
77          else begin                        
78            if (data4 || point)begin//如果显示数据为5位十进制数,则给低5位数码管赋值
79                  num <= {data4,data3,data2,data1,data0};
80                  if(sign)                  
81                      num <= 4'd11; //如果需要显示负号,则最高位(第6位)为符号位
82                  else
83                      num <= 4'd10; //不需要显示负号时,则第6位不显示任何字符
84            end
85            else begin               //如果显示数据为4位十进制数,则给低4位数码管赋值
86                  if (data3 || point) begin
87                      num <= {data3,data2,data1,data0};
88                      num <= 4'd10; //第6位不显示任何字符
89                      if(sign)             //如果需要显示负号,则最高位(第5位)为符号位
90                        num <= 4'd11;
91                      else               //不需要显示负号时,则第5位不显示任何字符
92                        num <= 4'd10;
93                  end
94                  else begin             //如果显示数据为3位十进制数,则给低3位数码管赋值
95                      if (data2 || point) begin
96                        num <= {data2,data1,data0};
97                                           //第6、5位不显示任何字符
98                        num <= {2{4'd10}};
99                        if(sign)         //如果需要显示负号,则最高位(第4位)为符号位
100                           num <= 4'd11;
101                         else             //不需要显示负号时,则第4位不显示任何字符
102                           num <= 4'd10;
103                     end
104                     else begin         //如果显示数据为2位十进制数,则给低2位数码管赋值
105                         if (data1 || point) begin
106                           num[ 7: 0] <= {data1,data0};
107                                          //第6、5、4位不显示任何字符
108                           num <= {3{4'd10}};
109                           if(sign)   //如果需要显示负号,则最高位(第3位)为符号位
110                                 num<= 4'd11;
111                           else         //不需要显示负号时,则第3位不显示任何字符
112                                 num <=4'd10;
113                         end
114                         else begin   //如果显示数据为1位十进制数,则给最低位数码管赋值
115                           num <= data0;
116                                          //第6、5位不显示任何字符
117                           num <= {4{4'd10}};
118                           if(sign)   //如果需要显示负号,则最高位(第2位)为符号位
119                                 num <= 4'd11;
120                           else         //不需要显示负号时,则第2位不显示任何字符
121                                 num <= 4'd10;
122                         end
123                     end
124               end
125             end
126         end
127   end
128 end
129
130 //每当计数器对数码管驱动时钟计数时间达1ms,输出一个时钟周期的脉冲信号
131 always @ (posedge dri_clk or negedge rst_n) begin
132   if (rst_n == 1'b0) begin
133         cnt0 <= 13'b0;
134         flag <= 1'b0;
135      end
136   else if (cnt0 < MAX_NUM - 1'b1) begin
137         cnt0 <= cnt0 + 1'b1;
138         flag <= 1'b0;
139      end
140   else begin
141         cnt0 <= 13'b0;
142         flag <= 1'b1;
143      end
144 end
145
146 //cnt_sel从0计数到5,用于选择当前处于显示状态的数码管
147 always @ (posedge dri_clk or negedge rst_n) begin
148   if (rst_n == 1'b0)
149         cnt_sel <= 3'b0;
150   else if(flag) begin
151         if(cnt_sel < 3'd5)
152             cnt_sel <= cnt_sel + 1'b1;
153         else
154             cnt_sel <= 3'b0;
155   end
156   else
157         cnt_sel <= cnt_sel;
158 end
159
160 //控制数码管位选信号,使6位数码管轮流显示
161 always @ (posedge dri_clk or negedge rst_n) begin
162   if(!rst_n) begin
163         seg_sel<= 6'b111111;            //位选信号低电平有效
164         num_disp <= 4'b0;         
165         dot_disp <= 1'b1;                   //共阳极数码管,低电平导通
166   end
167   else begin
168         if(en) begin
169             case (cnt_sel)
170               3'd0 :begin
171                     seg_sel<= 6'b111110;//显示数码管最低位
172                     num_disp <= num ;//显示的数据
173                     dot_disp <= ~point;//显示的小数点
174               end
175               3'd1 :begin
176                     seg_sel<= 6'b111101;//显示数码管第1位
177                     num_disp <= num ;
178                     dot_disp <= ~point;
179               end
180               3'd2 :begin
181                     seg_sel<= 6'b111011;//显示数码管第2位
182                     num_disp <= num;
183                     dot_disp <= ~point;
184               end
185               3'd3 :begin
186                     seg_sel<= 6'b110111;//显示数码管第3位
187                     num_disp <= num;
188                     dot_disp <= ~point;
189               end
190               3'd4 :begin
191                     seg_sel<= 6'b101111;//显示数码管第4位
192                     num_disp <= num;
193                     dot_disp <= ~point;
194               end
195               3'd5 :begin
196                     seg_sel<= 6'b011111;//显示数码管最高位
197                     num_disp <= num;
198                     dot_disp <= ~point;
199               end
200               default :begin
201                     seg_sel<= 6'b111111;
202                     num_disp <= 4'b0;
203                     dot_disp <= 1'b1;
204               end
205             endcase
206         end
207         else begin
208             seg_sel<= 6'b111111;          //使能信号为0时,所有数码管均不显示
209             num_disp <= 4'b0;
210             dot_disp <= 1'b1;
211         end
212   end
213 end
214
215 //控制数码管段选信号,显示字符
216 always @ (posedge dri_clk or negedge rst_n) begin
217   if (!rst_n)
218         seg_led <= 8'hc0;
219   else begin
220         case (num_disp)
221             4'd0 : seg_led <= {dot_disp,7'b1000000}; //显示数字 0
222             4'd1 : seg_led <= {dot_disp,7'b1111001}; //显示数字 1
223             4'd2 : seg_led <= {dot_disp,7'b0100100}; //显示数字 2
224             4'd3 : seg_led <= {dot_disp,7'b0110000}; //显示数字 3
225             4'd4 : seg_led <= {dot_disp,7'b0011001}; //显示数字 4
226             4'd5 : seg_led <= {dot_disp,7'b0010010}; //显示数字 5
227             4'd6 : seg_led <= {dot_disp,7'b0000010}; //显示数字 6
228             4'd7 : seg_led <= {dot_disp,7'b1111000}; //显示数字 7
229             4'd8 : seg_led <= {dot_disp,7'b0000000}; //显示数字 8
230             4'd9 : seg_led <= {dot_disp,7'b0010000}; //显示数字 9
231             4'd10: seg_led <= 8'b11111111;         //不显示任何字符
232             4'd11: seg_led <= 8'b10111111;         //显示负号(-)
233             default:
234                  seg_led <= {dot_disp,7'b1000000};
235         endcase
236   end
237 end
238
239 endmodule
数码管动态显示模块不仅可以将数值显示在数码管上,而且可以控制小数点的显示以及显示负数。数码管驱动模块没有在高位填充“0”,除非该位显示小数点。结合第131行开始的always语句块可知,cnt0每1ms的时间变化一次;而从第161行的case语句块可知,cnt_sel控制数码管的位选和段选。下图为该模块运行时Chipscope抓取到的波形图:

图 14.4.4 Chipscope波形图
由该波形图可知,当flag信号拉高时,切换显示信号cnt_sel加1。
14.5下载验证
首先我们打开动态数码管显示实验工程,在工程所在的路径下打开seg_led_top/prj/ seg_led_top文件夹,在里面找到“seg_led_top.xise”并双击打开。注意工程所在的路径名只能由字母、数字以及下划线组成,不能出现中文、空格以及特殊字符等。seg_led_top工程打开后如图 14.5.1所示。

图 14.5.1 动态数码管显示实验工程
工程打开后双击“Configure Target Device”一栏中的“Manage Configuration Project(iMAPCT)” (上图红框位置),在弹出的界面中双击“Boundary Scan”,下载界面如图 14.5.2所示。

图 14.5.2 程序下载界面
如下图 14.5.3所示。将Xilinx下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,然后连接电源线。



图 14.5.3 超越者开发板实物图
打开电源开关,接下来我们下载程序,验证动态数码管显示实验功能。
开发板电源打开后,点击工具栏中的“Initialize chain”图标(图 14.5.4红框位置),添加工程目录下的“seg_led_top.bit”文件。然后双击“Program”将工程编译完成后得到的bit文件下载到开发板中,如图 14.5.5所示。


图 14.5.4 硬件连接

图 14.5.5 程序下载完成界面
下载完成后,就能在开发板上看到动态数码管显示实验的效果如下图所示。

图 14.5.6 动态数码管显示实验

页: [1]
查看完整版本: 【正点原子FPGA连载】第十四章动态数码管显示实验