正点原子 发表于 2019-5-16 21:28:26

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

本帖最后由 正点原子 于 2020-10-23 10:55 编辑

1)实验平台:正点原子开拓者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=579749209820
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
4)本实例源码下载:
5)对正点原子FPGA感兴趣的同学可以加群讨论:712557122点击加入:
6)关注正点原子公众号,获取最新资料更新:

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

经过上一章的学习,我们已经知道如何使用数码管静态驱动的方式使数码管显示数字,但在很多情况下,我们需要让数码管各个位显示不同的数字,这就需要以动态驱动的方式驱动数码管。本章我们主要介绍数码管动态驱动的原理以及如何使用动态驱动的方式在数码管上显示变化的数字。本章包括以下几个部分: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):数码管动态显示模块在数码管上以动态方式显示数值。顶层模块的代码如下:1moduletop_seg_led(2      //global clock3      input            sys_clk,       // 全局时钟信号4      input            sys_rst_n,       // 复位信号(低有效)56      //seg_ledinterface7      output    sel   ,       // 控制数码管的亮灭8      output    seg_led          // 控制数码管中的8个灯的亮灭9);1011 //wire define12 wire    data;                // 数码管显示的数值13 wire    [ 5:0]point;               // 数码管小数点的位置14 wire         en;                   //数码管显示使能信号15 wire         sign;               // 符号位1617 //*****************************************************18 //**                   main code19 //*****************************************************2021 //例化动态数码管驱动模块22seg_led u_seg_led(23   //module clock24   .clk         (sys_clk),       // 时钟信号25   .rst_n         (sys_rst_n),       // 复位信号26   //seg_ledinterface27   .sel         (sel      ),       // 位选28   .seg_led       (seg_led),       // 段选29   //user interface30   .data          (data   ),       // 显示的数值31   .point         (point    ),       // 小数点具体显示的位置,从高到低,高电平有效32   .en            (en       ),       // 数码管使能信号33   .sign          (sign   )      // 符号位(低电平显示“-”号)34 );3536 //例化计数模块37count u_count(38   //mudule clock39   .clk         (sys_clk),       // 时钟信号40   .rst_n         (sys_rst_n),       // 复位信号41   //user interface42   .data          (data   ),       // 6个数码管要显示的数值43   .point         (point    ),       // 小数点具体显示的位置,从高到低,高电平有效44   .en            (en       ),       // 数码管使能信号45   .sign          (sign   )      // 符号位46 );4748 endmodule顶层模块中主要完成对其余模块的例化,并且实现各模块之间信号的交互。计数模块输出的数值data连接至数码管显示模块的输入端口data,数码管显示模块将输入的数据data输出至数码管上显示。计数模块的代码如下所示:1modulecount(2      //mudule clock3      input                   clk,      // 时钟信号4      input                   rst_n,      // 复位信号5      6      //userinterface7      output   reg    data ,      // 6个数码管要显示的数值8      output   reg [ 5:0]   point,      // 小数点的位置,高电平点亮对应数码管位上的小数点9      output   reg            en,      // 数码管使能信号10   output   reg            sign      // 符号位,高电平时显示负号,低电平不显示负号11 );1213 //parameter define14 parameter MAX_NUM = 23'd5000_000;      // 计数器计数的最大值1516 //reg define17 reg       cnt ;                   //计数器,用于计时100ms18 reg            flag;                   //标志信号1920 //*****************************************************21 //**                   main code22 //*****************************************************2324 //计数器对系统时钟计数达10ms时,输出一个时钟周期的脉冲信号25 always @ (posedge clk or negedgerst_n) begin26   if (!rst_n) begin27         cnt <=23'b0;28         flag<=1'b0;29   end30   elseif (cnt< MAX_NUM -1'b1)begin31         cnt <=cnt + 1'b1;32         flag<=1'b0;33   end34   elsebegin35         cnt <=23'b0;36         flag <=1'b1;37   end38 end3940 //数码管需要显示的数据,从0累加到99999941 always @ (posedge clk or negedgerst_n) begin42   if (!rst_n)begin43         data <= 20'b0;44         point <=6'b000000;45         en   <= 1'b0;46         sign <= 1'b0;47   end48   elsebegin49         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             else56               data <= 20'b0;57         end58   end59 end6061 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             data,      // 6位数码管要显示的数值6       input            point ,      // 小数点具体显示的位置,从高到低,高电平有效7       input                   en   ,      // 数码管使能信号8       input                   sign   ,      // 符号位(高电平显示“-”号)9   10      output   reg   seg_sel,      // 数码管位选,最左侧数码管为最高位11      output   reg   seg_led      // 数码管段选12      );1314//parameter define15localparamCLK_DIVIDE =4'd10    ;      // 时钟分频系数16localparamMAX_NUM   = 13'd5000;      // 对数码管驱动时钟(5MHz)计数1ms所需的计数值1718//reg define19reg    [ 3:0]             clk_cnt;      // 时钟分频计数器20reg                     dri_clk;      // 数码管的驱动时钟,5MHz21reg                 num      ;      // 24位bcd码寄存器22reg                 cnt0   ;      // 数码管驱动时钟计数器23reg                     flag   ;      // 标志信号(标志着cnt0计数达1ms)24reg                cnt_sel;      // 数码管位选计数器25reg                num_disp ;      // 当前数码管显示的数据26reg                     dot_disp ;      // 当前数码管显示的小数点2728//wire define29wire               data0    ;      // 个位数30wire               data1    ;      // 十位数31wire               data2    ;      // 百位数32wire               data3    ;      // 千位数33wire               data4    ;      // 万位数34wire               data5    ;      // 十万位数3536//*****************************************************37//**                  main code38//*****************************************************3940//提取显示数值所对应的十进制数的各个位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;          // 十万位数4748//对系统时钟10分频,得到的频率为5MHz的数码管驱动时钟dri_clk49always@(posedgeclk or negedgerst_n) begin50   if(!rst_n) begin51         clk_cnt <=4'd0;52         dri_clk <=1'b1;53   end54   elseif(clk_cnt== CLK_DIVIDE/2 - 1'd1) begin55         clk_cnt <=4'd0;56         dri_clk <=~dri_clk;57   end58   elsebegin59         clk_cnt <=clk_cnt + 1'b1;60         dri_clk <=dri_clk;61   end62end6364//将20位2进制数转换为8421bcd码(即使用4位二进制数表示1位十进制数)65always@ (posedge dri_clk ornegedge rst_n)begin66      if(!rst_n)67          num <=24'b0;68      elsebegin69          if(data5 ||point) begin   //如果显示数据为6位十进制数,70            num <= data5;         //则依次给6位数码管赋值71            num <= data4;72            num <= data3;73            num<=data2;74            num<=data1;75            num<=data0;76          end77          elsebegin                        78            if (data4 ||point) begin //如果显示数据为5位十进制数,则给低5位数码管赋值79                  num <= {data4,data3,data2,data1,data0};80                  if(sign)                  81                      num <= 4'd11; //如果需要显示负号,则最高位(第6位)为符号位82                  else83                      num <= 4'd10; //不需要显示负号时,则第6位不显示任何字符84            end85            elsebegin                  //如果显示数据为4位十进制数,则给低4位数码管赋值86                  if(data3 ||point) begin87                      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                  end94                  elsebegin             //如果显示数据为3位十进制数,则给低3位数码管赋值95                      if (data2 || point) begin96                        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                     end104                     else begin         //如果显示数据为2位十进制数,则给低2位数码管赋值105                         if (data1 || point) begin106                           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                         end114                         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                         end123                     end124               end125             end126         end127   end128 end129130 //每当计数器对数码管驱动时钟计数时间达1ms,输出一个时钟周期的脉冲信号131 always @ (posedge dri_clk or negedgerst_n) begin132   if (rst_n == 1'b0) begin133         cnt0 <=13'b0;134         flag <=1'b0;135      end136   elseif (cnt0< MAX_NUM -1'b1)begin137         cnt0 <=cnt0 + 1'b1;138         flag <=1'b0;139      end140   elsebegin141         cnt0 <=13'b0;142         flag <=1'b1;143      end144 end145146 //cnt_sel从0计数到5,用于选择当前处于显示状态的数码管147 always @ (posedge dri_clk or negedgerst_n) begin148   if (rst_n == 1'b0)149         cnt_sel <=3'b0;150   elseif(flag) begin151         if(cnt_sel <3'd5)152             cnt_sel <= cnt_sel + 1'b1;153         else154             cnt_sel <= 3'b0;155   end156   else157         cnt_sel <=cnt_sel;158 end159160 //控制数码管位选信号,使6位数码管轮流显示161 always @ (posedge dri_clk or negedgerst_n) begin162   if(!rst_n) begin163         seg_sel<= 6'b111111;            //位选信号低电平有效164         num_disp <=4'b0;         165         dot_disp <=1'b1;                   //共阳极数码管,低电平导通166   end167   elsebegin168         if(en) begin169             case(cnt_sel)170               3'd0:begin171                     seg_sel<= 6'b111110;//显示数码管最低位172                     num_disp <= num ;//显示的数据173                     dot_disp <= ~point;//显示的小数点174               end175               3'd1:begin176                     seg_sel<= 6'b111101;//显示数码管第1位177                     num_disp <= num ;178                     dot_disp <= ~point;179               end180               3'd2:begin181                     seg_sel<= 6'b111011;//显示数码管第2位182                     num_disp <= num;183                     dot_disp <= ~point;184               end185               3'd3:begin186                     seg_sel<= 6'b110111;//显示数码管第3位187                     num_disp <= num;188                     dot_disp <= ~point;189               end190               3'd4:begin191                     seg_sel<= 6'b101111;//显示数码管第4位192                     num_disp <= num;193                     dot_disp <= ~point;194               end195               3'd5:begin196                     seg_sel<= 6'b011111;//显示数码管最高位197                     num_disp <= num;198                     dot_disp <= ~point;199               end200               default:begin201                     seg_sel<= 6'b111111;202                     num_disp <= 4'b0;203                     dot_disp <= 1'b1;204               end205             endcase206         end207         elsebegin208             seg_sel<= 6'b111111;          //使能信号为0时,所有数码管均不显示209             num_disp <= 4'b0;210             dot_disp <= 1'b1;211         end212   end213 end214215 //控制数码管段选信号,显示字符216 always @ (posedge dri_clk or negedgerst_n) begin217   if (!rst_n)218         seg_led <=8'hc0;219   elsebegin220         case(num_disp)221             4'd0: seg_led <={dot_disp,7'b1000000};//显示数字 0222             4'd1: seg_led <={dot_disp,7'b1111001};//显示数字 1223             4'd2: seg_led <={dot_disp,7'b0100100};//显示数字 2224             4'd3 : seg_led <={dot_disp,7'b0110000};//显示数字 3225             4'd4: seg_led <={dot_disp,7'b0011001};//显示数字 4226             4'd5: seg_led <={dot_disp,7'b0010010};//显示数字 5227             4'd6: seg_led <={dot_disp,7'b0000010};//显示数字 6228             4'd7: seg_led <={dot_disp,7'b1111000};//显示数字 7229             4'd8: seg_led <={dot_disp,7'b0000000};//显示数字 8230             4'd9: seg_led <={dot_disp,7'b0010000};//显示数字 9231             4'd10: seg_led <=8'b11111111;         //不显示任何字符232             4'd11: seg_led <=8'b10111111;         //显示负号(-)233             default:234                  seg_led <= {dot_disp,7'b1000000};235         endcase236   end237 end238239 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”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如图 12.5.2所示。
图 12.5.2 程序下载界面下载完成后观察到开发板上数码管显示的值从“0”增加到“999999”,如下图所示,说明数码管动态显示实验程序下载验证成功。
图 12.5.3 动态数码管态显示实验结果显示

njhying 发表于 2019-5-16 21:44:03

学数字电路时点数码管
学单片机时点数码管
学FPGA又点数码管

prince2010 发表于 2019-5-16 22:13:17

njhying 发表于 2019-5-16 21:44
学数字电路时点数码管
学单片机时点数码管
学FPGA又点数码管

下半年原子哥教你在linux上点数码管{:lol:}

enterpriseZ 发表于 2019-5-17 10:20:48

prince2010 发表于 2019-5-16 22:13
下半年原子哥教你在linux上点数码管

哈哈哈哈哈
页: [1]
查看完整版本: 【正点原子FPGA连载】第十二章 动态数码管显示实验--摘自【正点原子】开拓者 FPGA 开发指南