OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 8854|回复: 0
打印 上一主题 下一主题

[ALTERA] 【正点原子FPGA连载】第十二章 动态数码管显示实验--摘自《正点原子开拓者FPGA开发指南》

[复制链接]

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165756
金钱
165756
注册时间
2010-12-1
在线时间
2123 小时
跳转到指定楼层
楼主
发表于 2019-5-8 18:55:51 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1)实验平台:正点原子开拓者FPGA开发板
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
3)本实例源码下载: 6_seg_led_dynamic.rar (5.82 MB, 下载次数: 93)
4)对正点原子Linux感兴趣的同学可以加群讨论:712557122  点击加入:
5)关注正点原子公众号,获取最新资料更新:

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

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

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

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

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

图 12.4.2 顶层模块原理图
FPGA顶层(top_seg_led)例化了以下两个模块:计数模块(count)以及数码管动态显示模块(seg_led)。实现各模块之间数据的交互。计数模块将计数值通过data端口传递给数码管动态显示模块,使能信号en使能数码管显示数据,小数点显示信号point控制小数点的显示,符号信号sign可以让数码管显示负号。
计数模块(count):显示的数字每100ms加“1”。
数码管动态显示模块(seg_led):数码管动态显示模块在数码管上以动态方式显示数值。
顶层模块的代码如下:
1  moduletop_seg_led(
2      //global clock
3      input            sys_clk  ,       // 全局时钟信号
4      input            sys_rst_n,       // 复位信号(低有效)
5  
6      //seg_ledinterface
7      output    [5:0]  sel     ,       // 控制数码管的亮灭
8      output    [7:0] seg_led          // 控制数码管中的8个灯的亮灭
9  );
10
11 //wire define
12 wire    [19:0]  data;                // 数码管显示的数值
13 wire    [ 5:0]  point;               // 数码管小数点的位置
14 wire           en;                   //数码管显示使能信号
15 wire           sign;                 // 符号位
16
17 //*****************************************************
18 //**                   main code
19 //*****************************************************
20
21 //例化动态数码管驱动模块
22seg_led u_seg_led(
23     //module clock
24     .clk           (sys_clk  ),       // 时钟信号
25     .rst_n         (sys_rst_n),       // 复位信号
26     //seg_ledinterface
27     .sel           (sel      ),       // 位选
28     .seg_led       (seg_led  ),       // 段选
29     //user interface
30     .data          (data     ),       // 显示的数值
31     .point         (point    ),       // 小数点具体显示的位置,从高到低,高电平有效
32     .en            (en       ),       // 数码管使能信号
33     .sign          (sign     )        // 符号位(低电平显示“-”号)
34 );
35
36 //例化计数模块
37count u_count(
38     //mudule clock
39     .clk           (sys_clk  ),       // 时钟信号
40     .rst_n         (sys_rst_n),       // 复位信号
41     //user interface
42     .data          (data     ),       // 6个数码管要显示的数值
43     .point         (point    ),       // 小数点具体显示的位置,从高到低,高电平有效
44     .en            (en       ),       // 数码管使能信号
45     .sign          (sign     )        // 符号位
46 );
47
48 endmodule
顶层模块中主要完成对其余模块的例化,并且实现各模块之间信号的交互。计数模块输出的数值data连接至数码管显示模块的输入端口data,数码管显示模块将输入的数据data输出至数码管上显示。
计数模块的代码如下所示:
1  modulecount(
2      //mudule clock
3      input                   clk  ,      // 时钟信号
4      input                   rst_n,      // 复位信号
5      
6      //userinterface
7      output   reg [19:0]     data ,      // 6个数码管要显示的数值
8      output   reg [ 5:0]     point,      // 小数点的位置,高电平点亮对应数码管位上的小数点
9      output   reg            en  ,      // 数码管使能信号
10     output   reg            sign        // 符号位,高电平时显示负号,低电平不显示负号
11 );
12
13 //parameter define
14 parameter MAX_NUM = 23'd5000_000;      // 计数器计数的最大值
15
16 //reg define
17 reg    [22:0]   cnt ;                   //计数器,用于计时100ms
18 reg            flag;                   //标志信号
19
20 //*****************************************************
21 //**                   main code
22 //*****************************************************
23
24 //计数器对系统时钟计数达10ms时,输出一个时钟周期的脉冲信号
25 always @ (posedge clk or negedgerst_n) begin
26     if (!rst_n) begin
27         cnt <=23'b0;
28         flag<=1'b0;
29     end
30     elseif (cnt< MAX_NUM -1'b1)begin
31         cnt <=cnt + 1'b1;
32         flag<=1'b0;
33     end
34     elsebegin
35         cnt <=23'b0;
36         flag <=1'b1;
37     end
38 end
39
40 //数码管需要显示的数据,从0累加到999999
41 always @ (posedge clk or negedgerst_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     elsebegin
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清零。通过SignalTapII抓到的波形图如下图所示,可知,计时100ms正确。

图 12.4.3 SignalTap波形图
数码管动态显示模块的代码如下:
1   module seg_led(
2       input                   clk    ,        // 时钟信号
3       input                   rst_n  ,        // 复位信号
4   
5       input         [19:0]    data  ,        // 6位数码管要显示的数值
6       input         [5:0]     point ,        // 小数点具体显示的位置,从高到低,高电平有效
7       input                   en     ,        // 数码管使能信号
8       input                   sign   ,        // 符号位(高电平显示“-”号)
9   
10      output   reg  [5:0]     seg_sel,        // 数码管位选,最左侧数码管为最高位
11      output   reg  [7:0]     seg_led        // 数码管段选
12      );
13  
14  //parameter define
15  localparam  CLK_DIVIDE =4'd10    ;        // 时钟分频系数
16  localparam  MAX_NUM   = 13'd5000  ;        // 对数码管驱动时钟(5MHz)计数1ms所需的计数值
17  
18  //reg define
19  reg    [ 3:0]             clk_cnt  ;        // 时钟分频计数器
20  reg                       dri_clk  ;        // 数码管的驱动时钟,5MHz
21  reg    [23:0]             num      ;        // 24位bcd码寄存器
22  reg    [12:0]             cnt0     ;        // 数码管驱动时钟计数器
23  reg                       flag     ;        // 标志信号(标志着cnt0计数达1ms)
24  reg    [2:0]              cnt_sel  ;        // 数码管位选计数器
25  reg    [3:0]              num_disp ;        // 当前数码管显示的数据
26  reg                       dot_disp ;        // 当前数码管显示的小数点
27  
28  //wire define
29  wire   [3:0]              data0    ;        // 个位数
30  wire   [3:0]              data1    ;        // 十位数
31  wire   [3:0]              data2    ;        // 百位数
32  wire   [3:0]              data3    ;        // 千位数
33  wire   [3:0]              data4    ;        // 万位数
34  wire   [3:0]              data5    ;        // 十万位数
35  
36  //*****************************************************
37  //**                    main code
38  //*****************************************************
39  
40  //提取显示数值所对应的十进制数的各个位
41  assign  data0 =data % 4'd10;              // 个位数
42  assign  data1 =data / 4'd10% 4'd10   ;    // 十位数
43  assign  data2 =data / 7'd100% 4'd10  ;    // 百位数
44  assign  data3 =data / 10'd1000% 4'd10;  // 千位数
45  assign  data4 =data / 14'd10000% 4'd10;   // 万位数
46  assign  data5 =data / 17'd100000;          // 十万位数
47  
48  //对系统时钟10分频,得到的频率为5MHz的数码管驱动时钟dri_clk
49  always@(posedgeclk or negedgerst_n) begin
50     if(!rst_n) begin
51         clk_cnt <=4'd0;
52         dri_clk <=1'b1;
53     end
54     elseif(clk_cnt== CLK_DIVIDE/2 - 1'd1) begin
55         clk_cnt <=4'd0;
56         dri_clk <=~dri_clk;
57     end
58     elsebegin
59         clk_cnt <=clk_cnt + 1'b1;
60         dri_clk <=dri_clk;
61     end
62  end
63  
64  //将20位2进制数转换为8421bcd码(即使用4位二进制数表示1位十进制数)
65  always@ (posedge dri_clk ornegedge rst_n)begin
66      if(!rst_n)
67          num <=24'b0;
68      elsebegin
69          if(data5 ||point[5]) begin     //如果显示数据为6位十进制数,
70              num[23:20] <= data5;         //则依次给6位数码管赋值
71              num[19:16] <= data4;
72              num[15:12] <= data3;
73              num[11:8]  <=data2;
74              num[7:4]  <=data1;
75              num[3:0]  <=data0;
76          end
77          elsebegin                        
78              if (data4 ||point[4]) begin //如果显示数据为5位十进制数,则给低5位数码管赋值
79                  num[19:0] <= {data4,data3,data2,data1,data0};
80                  if(sign)                    
81                      num[23:20] <= 4'd11; //如果需要显示负号,则最高位(第6位)为符号位
82                  else
83                      num[23:20] <= 4'd10; //不需要显示负号时,则第6位不显示任何字符
84              end
85              elsebegin                  //如果显示数据为4位十进制数,则给低4位数码管赋值
86                  if(data3 ||point[3]) begin
87                      num[15: 0] <= {data3,data2,data1,data0};
88                      num[23:20] <= 4'd10; //第6位不显示任何字符
89                      if(sign)            //如果需要显示负号,则最高位(第5位)为符号位
90                          num[19:16] <= 4'd11;
91                      else                 //不需要显示负号时,则第5位不显示任何字符
92                          num[19:16] <= 4'd10;
93                  end
94                  elsebegin             //如果显示数据为3位十进制数,则给低3位数码管赋值
95                      if (data2 || point[2]) begin
96                          num[11: 0] <= {data2,data1,data0};
97                                          //第6、5位不显示任何字符
98                          num[23:16] <= {2{4'd10}};
99                          if(sign)       //如果需要显示负号,则最高位(第4位)为符号位
100                             num[15:12] <= 4'd11;
101                         else          //不需要显示负号时,则第4位不显示任何字符
102                             num[15:12] <= 4'd10;
103                     end
104                     else begin         //如果显示数据为2位十进制数,则给低2位数码管赋值
105                         if (data1 || point[1]) begin
106                             num[ 7: 0] <= {data1,data0};
107                                        //第6、5、4位不显示任何字符
108                             num[23:12] <= {3{4'd10}};
109                             if(sign)   //如果需要显示负号,则最高位(第3位)为符号位
110                                 num[11:8]  <= 4'd11;
111                             else       //不需要显示负号时,则第3位不显示任何字符
112                                 num[11:8] <=  4'd10;
113                         end
114                         else begin     //如果显示数据为1位十进制数,则给最低位数码管赋值
115                             num[3:0] <=data0;
116                                        //第6、5位不显示任何字符
117                             num[23:8] <= {4{4'd10}};
118                             if(sign)   //如果需要显示负号,则最高位(第2位)为符号位
119                                 num[7:4] <= 4'd11;
120                             else       //不需要显示负号时,则第2位不显示任何字符
121                                 num[7:4] <= 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 negedgerst_n) begin
132     if (rst_n == 1'b0) begin
133         cnt0 <=13'b0;
134         flag <=1'b0;
135      end
136     elseif (cnt0< MAX_NUM -1'b1)begin
137         cnt0 <=cnt0 + 1'b1;
138         flag <=1'b0;
139      end
140     elsebegin
141         cnt0 <=13'b0;
142         flag <=1'b1;
143      end
144 end
145
146 //cnt_sel从0计数到5,用于选择当前处于显示状态的数码管
147 always @ (posedge dri_clk or negedgerst_n) begin
148     if (rst_n == 1'b0)
149         cnt_sel <=3'b0;
150     elseif(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 negedgerst_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     elsebegin
168         if(en) begin
169             case(cnt_sel)
170                 3'd0:begin
171                     seg_sel  <= 6'b111110;  //显示数码管最低位
172                     num_disp <= num[3:0] ;  //显示的数据
173                     dot_disp <= ~point[0];  //显示的小数点
174                 end
175                 3'd1:begin
176                     seg_sel  <= 6'b111101;  //显示数码管第1位
177                     num_disp <= num[7:4] ;
178                     dot_disp <= ~point[1];
179                 end
180                 3'd2:begin
181                     seg_sel  <= 6'b111011;  //显示数码管第2位
182                     num_disp <= num[11:8];
183                     dot_disp <= ~point[2];
184                 end
185                 3'd3:begin
186                     seg_sel  <= 6'b110111;  //显示数码管第3位
187                     num_disp <= num[15:12];
188                     dot_disp <= ~point[3];
189                 end
190                 3'd4:begin
191                     seg_sel  <= 6'b101111;  //显示数码管第4位
192                     num_disp <= num[19:16];
193                     dot_disp <= ~point[4];
194                 end
195                 3'd5:begin
196                     seg_sel  <= 6'b011111;  //显示数码管最高位
197                     num_disp <= num[23:20];
198                     dot_disp <= ~point[5];
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         elsebegin
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 negedgerst_n) begin
217     if (!rst_n)
218         seg_led <=8'hc0;
219     elsebegin
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语句块可知,cnt每1ms的时间变化一次;而从第161行的case语句块可知,cnt控制数码管的位选和段选。下图为该模块运行时SignalTapII抓取到的波形图:

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

图 12.5.1 数码管动态显示实验工程
然后将下载器一端连接电脑,另一端与开发板上的JTAG下载口相连,最后连接电源线并打开电源开关。
接下来我们下载程序,验证数码管动态显示的功能。
工程打开后通过点击工具栏中的“Programmer”图标打开下载界面,通过“Add File”按钮选择top_seg_led/par/output_files目录下的“top_seg_led.sof”文件。开发板电源打开后,在程序下载界面点击“HardwareSetup”,在弹出的对话框中选择当前的硬件连接为“USB-Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如图 12.5.2所示。

图 12.5.2 程序下载界面
下载完成后观察到开发板上数码管显示的值从“0”增加到“999999”,如下图所示,说明数码管动态显示实验程序下载验证成功。

图 12.5.3 动态数码管态显示实验结果显示

我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子产品购买店铺https://zhengdianyuanzi.tmall.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-12-14 17:23

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表