完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html 第十二章 动态数码管显示实验 经过上一章的学习,我们已经知道如何使用数码管静态驱动的方式使数码管显示数字,但 在很多情况下,我们需要让数码管各个位显示不同的数字,这就需要以动态驱动的方式驱动数 码管。本章我们主要介绍数码管动态驱动的原理以及如何使用动态驱动的方式在数码管上显示 变化的数字。 本章包括以下几个部分: 12.1 数码管动态显示简介 12.2 实验任务 12.3 硬件设计 12.4 程序设计 12.5 下载验证 数码管动态显示简介 在“数码管静态显示实验”章节我们详细地介绍了有关数码管方面的知识和数码管的静态 驱动,让大家对数码管的驱动有了基本的了解。由于一般的静态驱动操作虽然方便,但占用的 I/0口较多,例如要驱动6位8段数码管,以静态驱动方式让数码管各个位显示不同的数值,如 “123456”,需要占用6 × 8 = 48个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时,显示效果能满足使 用需要。数码管的这种驱动方式称为数码管的动态驱动,实际上就是分时轮流控制不同数码管 的显示。 实验任务 本节实验任务是使用FPGA开发板上的6位数码管以动态方式从0开始计数,每100ms计数值 增加一,当计数值从0增加到999999后重新从0开始计数。 硬件设计 数码管接口部分的硬件设计原理及本实验中各端口信号的管脚分配与“数码管静态显示实 验”完全相同,请参考“数码管静态显示实验”中的硬件设计部分。 程序设计 由实验任务和动态驱动的原理我们可以知道,若要让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 module top_seg_led( 2 //global clock 3 input sys_clk , // 全局时钟信号 4 input sys_rst_n, // 复位信号(低有效) 5 6 //seg_led interface 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 //例化动态数码管驱动模块 22 seg_led u_seg_led( 23 //module clock 24 .clk (sys_clk ), // 时钟信号 25 .rst_n (sys_rst_n), // 复位信号 26 //seg_led interface 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 //例化计数模块 37 count 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 module count( 2 //mudule clock 3 input clk , // 时钟信号 4 input rst_n, // 复位信号 5 6 //user interface 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 negedge rst_n) begin 26 if (!rst_n) begin 27 cnt <= 23'b0; 28 flag<= 1'b0; 29 end 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清零。通过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 @(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 62 end 63 64 //将20位2进制数转换为8421bcd码(即使用4位二进制数表示1位十进制数) 65 always @ (posedge dri_clk or negedge rst_n) begin 66 if (!rst_n) 67 num <= 24'b0; 68 else begin 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 else begin 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 else begin //如果显示数据为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 else begin //如果显示数据为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 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[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 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 语句块可知,cnt每1ms的时间变化一次;而从第161行的case语句块可知,cnt控制数码管的位 选和段选。下图为该模块运行时SignalTapII抓取到的波形图: 图 12.4.4 SignalTapb波形图 由该波形图可知,当flag信号拉高时,切换显示信号cnt加1。 下载验证 首先我们打开数码管动态显示实验工程,在工程所在的路径下打开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”文件。开发板电源打开 后,在程序下载界面点击“Hardware Setup”,在弹出的对话框中选择当前的硬件连接为“USB- Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如 图 12.5.2所示。 图 12.5.2 程序下载界面 下载完成后观察到开发板上数码管显示的值从“0”增加到“999999”,如下图所示,说 明数码管动态显示实验程序下载验证成功。 图 12.5.3 动态数码管态显示实验结果显示 |
|
相关推荐
|
|
如何配置Linux操作系统设备树让我的开发板可以将板子上的GPIO接口用作 I2S输出??
1675 浏览 1 评论
1450 浏览 0 评论
2078 浏览 0 评论
2019 浏览 2 评论
1118 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-20 02:43 , Processed in 0.641769 second(s), Total 63, Slave 46 queries .
Powered by 电子发烧友网
© 2015 www.ws-dc.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号