数字钟的FPGA实现并在VGA上显示

来源:本站
导读:目前正在解读《数字钟的FPGA实现并在VGA上显示》的相关信息,《数字钟的FPGA实现并在VGA上显示》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《数字钟的FPGA实现并在VGA上显示》的详细说明。
简介:之前用FPGA实现数字钟,并用数码管和VGA进行显示,同时还能用按键改变时间。下面我就讲解一下当初是怎么做这个东西的。

数字钟的FPGA实现并在VGA上显示

上图是整个代码文件结构。文件的名字取得很奇怪,因为当时是在其他的文件基础上改的,所以从名字看起来似乎和设计没有什么关系。这个地方大家可以要注意,代码文件的名字要命名得一看就知道功能是什么。

这里说一下,各个文件的作用:

1、LCD_TOP:顶层文件,只是例化了下面的各个模块

2、Led_display:这个其实是实现数字钟的模块,实现时分秒的计时以及改变

3、Display_shanshuo:这个是实现时分秒在数码管上显示,并且在改变时间的时候,实现数码管闪烁,这样,一看就知道是改变哪个时间。

4、VGA_sig:实现VGA显示,这个是整个设计的比较难的模块。

5、Time_vga_data:这个是实现获取时间的字模数据。这样,才能使时间再VGA上显示。

6、LCD_top.ucf:这个看后缀就知道了,约束文件,约束管脚。

下面,我就从最底层的模块开始给大家分析这个设计

一、数字钟模块,实现时分秒的计时以及改变时分秒

先是端口定义

module led_display (     input                   sys_clk   ,     input                     sys_rstn  ,     input                     change  ,    //时钟改变信号,1表示改变时间     input[3:0]              data_5 ,        //改变的时十位     input[3:0]              data_4 ,        //改变的时个位     input[3:0]              data_3 ,        //改变的分十位     input[3:0]              data_2 ,        //改变的分个位     input[3:0]              data_1 ,     //改变的秒十位     input[3:0]              data_0 ,     //改变的秒个位            output reg [3:0]       shi_shi   ,      output reg [2:0]       shi_ge    ,     output reg [2:0]       fen_shi   ,     output reg [3:0]       fen_ge    ,     output reg [3:0]       miao_shi  ,     output reg [1:0]       miao_ge                 );

分别是使用6个信号来定义时间的时分秒的十位和个位。输入有6个信号对应需要改变的时间的时分秒的十位和个位。

以下以秒的个位为例说明:

always@( posedge sys_clk )  begin     if(!sys_rstn)         begin             miao_ge <= 4'd0 ;          end      else         begin             if( change )                  miao_ge <= data_0 ;           else              begin                               if( miao_ge == 4'd10 )                            miao_ge <= 4'd0 ;                      else if(delay_cnt==cnt_number)                            miao_ge <= miao_ge + 1'b1 ;                      else                            miao_ge <= miao_ge ;                  end          end  end

在复位的情况下,miao_ge值为0。在没有复位情况下,首先是判断是否change有效,有效说明这个时候在改变时间,那么miao_ge的值就为data_0的值,这样就实现了时间的改变。如果没有改变时间,1s延迟时间到,就加1。某一时刻的值加到10,就清零。

其他的时间也是一样的原理。

二、数码管模块,实现时间的数码管显示和当改变时间时,数码管的闪烁,以及改变时间时的加减时间

端口定义

module display_shanshuo(    input                clk ,    input                rst_n ,                               input [2:0]          key ,              //按键的输入               input [3:0]          shi_shi   ,    input [3:0]          shi_ge    ,    input [3:0]          fen_shi   ,    input [3:0]          fen_ge    ,    input [3:0]          miao_shi  ,    input [3:0]          miao_ge   ,                                  output            change ,           //是否改变时间    output wire [3:0]    data_0 ,    output wire [3:0]   data_1 ,    output wire [3:0]   data_2 ,    output wire [3:0]   data_3 ,    output wire [3:0]   data_4 ,    output wire [3:0]   data_5 ,                             output reg [7:0]     sm_bit ,       //数码管位选    output reg [7:0]     sm_seg      //数码管段选);

多数信号和数字钟模块的信号一致。我用的数码管是8位的。

模块中有3个按键,一个按键负责控制改变时间的选择,另外两个按键负责对时间进行加或者减操作。所以在程序中,就有检测按键按下的代码,并且要对按键进行消抖操作。这部分代码就不说明了,比较简单。

关键的地方,是以下3个,确定3个信号值。

assign real_change_buttom = ( doudong_cnt == doudong_number ) ? (!key[1]) : 1'b0 ;assign real_add_buttom    = ( doudong_cnt == doudong_number ) ? (!key[0]) : 1'b0 ;assign real_sub_buttom    = ( doudong_cnt == doudong_number ) ? (!key[2]) : 1'b0 ;

数码管的显示是比较简单的了,网上就有很多教程说明,这里要说一下,怎么样实现闪烁的效果。

如果要一个数码管闪烁,那么就让这个数码管隔一段时间亮,然后再隔一段时间灭就可以了。但是怎么控制亮灭了?最简单的方法,控制数码管的位选。一般的数码管,位选为0,数码管亮,位选为1,数码管不亮。所以就去控制这个位选就可以了。

always@( posedge clk ) begin       if( !rst_n )            begin                sm_bit  <= 8'b11111110 ;             end        else            begin                 case( disp_i )                    4'h0 : begin                               if( 4'h0 == change_i )                                      sm_bit <= sm_temp | 8'b11111110 ;                          else                              sm_bit <= 8'b11111110 ;                                                                   end                    4'h1 : begin                               if( 4'h1 == change_i )                                      sm_bit <= sm_temp | 8'b11111101 ;                          else                              sm_bit <= 8'b11111101 ;                                                                   end                    4'h2 : begin                               if( 4'h2 == change_i )                                      sm_bit <= sm_temp | 8'b11111011 ;                          else                              sm_bit <= 8'b11111011 ;                                                                   end                    4'h3 : begin                               if( 4'h3 == change_i )                                      sm_bit <= sm_temp | 8'b11110111 ;                          else                              sm_bit <= 8'b11110111 ;                                                                   end                                  4'h4 : begin                               if( 4'h4 == change_i )                                      sm_bit <= sm_temp | 8'b11101111 ;                          else                              sm_bit <= 8'b11101111 ;                                                                   end                    4'h5 : begin                               if( 4'h5 == change_i )                                      sm_bit <= sm_temp | 8'b11011111 ;                          else                              sm_bit <= 8'b11011111 ;                                                                   end                    4'h6 : begin                               if( 4'h6 == change_i )                                      sm_bit <= sm_temp | 8'b10111111 ;                          else                              sm_bit <= 8'b10111111 ;                                                                   end                    4'h7 : begin                                if( 4'h7 == change_i )                                      sm_bit <= sm_temp | 8'b01111111 ;                           else                              sm_bit <= 8'b01111111 ;                                                               end                    default:                             sm_bit <= 8'hff;                 endcase             endend

这里,先说一下几个信号的作用:

disp_i:显示第几位的数码管。

change_i:改变第几位的数码管

sm_bit:数码管的位选

sm_temp:实现数码管闪烁用

代码中,实现闪烁的关键代码就是

sm_bit <= sm_temp | 8'b11111110 ;

sm_temp这个信号是每隔300ms就会取反一次,那么sm_bit对应的位不就每隔300ms取反一次,而sm_bit的每一位控制一个数码管的位选,这样不就控制数码管的位选,实现数码管闪烁了。

至于加减时间,使用组合逻辑就搞定了,就判断按键的相关信号,然后对时间进行加1或者减1操作。

always@( * ) begin         display_data[2] = 4'ha ;         display_data[5] = 4'ha ;         if( change_i == 0 )              display_data[0] = real_add_buttom ? miao_ge + 1'b1 :                    real_sub_buttom ? miao_ge - 1'b1 : miao_ge ;         else              display_data[0] = miao_ge ;   //秒个位           if( change_i == 1 )              display_data[1] =real_add_buttom ? miao_shi + 1'b1 :                    real_sub_buttom ? miao_shi - 1'b1 : miao_shi ;         else              display_data[1] = miao_shi ;   //秒十位         if( change_i == 3 )              display_data[3] =real_add_buttom ? fen_ge + 1'b1 :                    real_sub_buttom ? fen_ge - 1'b1 : fen_ge ;         else              display_data[3] = fen_ge ;   //分个位         if( change_i == 4 )              display_data[4] =real_add_buttom ? fen_shi + 1'b1 :                    real_sub_buttom ? fen_shi - 1'b1 : fen_shi ;         else              display_data[4] = fen_shi ;   //分十位         if( change_i == 6 )              display_data[6] =real_add_buttom ? shi_ge + 1'b1 :                    real_sub_buttom ? shi_ge - 1'b1 : shi_ge ;         else              display_data[6] = shi_ge ;   //时个位         if( change_i == 7 )              display_data[7] =real_add_buttom ? shi_shi + 1'b1 :                    real_sub_buttom ? shi_shi - 1'b1 : shi_shi ;         else              display_data[7] = shi_shi ;   //时十位 end

其实这个模块也比较简单,先对按键进行检测,不过要注意要进行按键的消抖。然后对按键的判断,实现对时间的改变。稍微麻烦一点的是对数码管的处理,因为数码管是需要闪烁的。

三、VGA模块,这个模块实现图片和时间的显示

采用VGA显示画面,VGA的知识,网上也太多了,这里就不说明怎么设计代码去驱动VGA了。我这里,是想说一下,怎么设置在VGA上某一点显示的数据。因为VGA要显示画面,我们就要设置VGA上每个像素点的值,这样显示出来的画面才和我们想要的一致。

首先,要明确一下我们需要显示什么东西。

数字钟的FPGA实现并在VGA上显示

上图是要显示的单元的各个位置信息。时间单元,依次是:

时十时个空白冒号分十分个空白冒号秒十秒个

每个单元的大小是16*32。这个时候,就需要取字模软件了,将0-9,冒号,空白的字模都给保存到xilinx的coe初始化文件中,这样就可以将字模保存到内置的rom中。不过取模方式是要从上至下,从左至右的方向取。这里取的字模是取二值的,因为显示的时间用单一黑色显示即可,不用多种颜色,这样可以节省字模空间。

以下是得到的coe文件的一部分截图

数字钟的FPGA实现并在VGA上显示

当然图片也是要取模的。不过这个比较简单。同样保存到一个coe文件中,不过取的图片的每个像素的数据要以3位保存,因为图片是有各种颜色的,我是使用8种颜色来进行显示,需要3位二进制位表示。

以下是图片coe文件的部分截图。

数字钟的FPGA实现并在VGA上显示

从显示图可以看出,有两个参数是比较重要的。一个是横坐标,一个是纵坐标,有了这两个,才能知道在该位置要显示什么。在代码中使用address_x和address_y表示。

因为只有两个东西要显示,一个是图片,一个是时间,所以可以通过判断横纵坐标的值,来决定该位置显示什么。

assign  red_0 = time_flag ? ~data:red;      assign  red_1 = time_flag ? ~data:red;      assign  red_2 = time_flag ? ~data:red;      assign  gree_0 = time_flag ? ~data:gree;      assign  gree_1 = time_flag ? ~data:gree;      assign  gree_2 = time_flag ? ~data:gree;      assign  blue_0 = time_flag ? ~data:blue;assign  blue_1 = time_flag ? ~data:blue;

其中data是时间显示的数据,red,gree,blue是图片显示的3种颜色的数据。为什么显示要对data取反了,这个就和颜色合成有关了。在字模提取中,是让显示的地方为1,不显示的地方为0。而上面讲这个数据直接给都给RGB。对于颜色合成,如果RGB都是1的话,那么出来的就是白色,在屏幕上就看不到了,而RGB都是0的话,那么出来的就是黑色,就可以看到了。所以要有个取反的操作。

对于time_flag这个信号的判断,只需判断横纵坐标的范围是否在时间显示区域的范围内即可了。

always@( * ) begin        if( address_x >= 176 && address_x <= 367 &&             address_y >= 300 && address_y <= 331 )               time_flag = 1;         else             time_flag = 0; end

显示图片之前我有写过博客说过,这里就不说明了,以下说明一下,怎么显示时间。这个是稍微有点麻烦的。

这里以显示第一个时间,也就是时十为例说明。以下是显示时十的区域范围。

数字钟的FPGA实现并在VGA上显示

从图中,看出,当横坐标在176到191范围内,纵坐标在300到331范围内,说明这个区域应该显示时十。那先要知道时十是什么数字,假设是数字1。然后将保存在rom的1的字模给取出来,依次的写入到这个区域,那么这个区域不就显示1了。但是问题是怎么将1的字模数据写入到这个区域了?我们知道VGA显示,是逐行扫描的。当现在的坐标是(191,300)时,下一个坐标就是(192,300)了,在这个坐标就要显示其他数据去了。

所以,就不能单一的考虑一个单元的显示了。而是要考虑在每个位置应该要显示什么。

数字钟的FPGA实现并在VGA上显示

假设,现在的横纵坐标是(188,320)。首先判断一下,该坐标应该是处于显示什么的区域。通过简单的判断,得知该点是在显示时十的区域。然后再看时十的数字是多少,这个通过传入的参数可以获取,假设是1。然后再看一下这个点是位于第几行,通过320-300=20,在20行。那么就可以从字模rom中得知,显示1的第20行数据应该是多少,怎么找rom了?这里在将rom的coe文件再次截图:

数字钟的FPGA实现并在VGA上显示

图中红框中的数据就是1的字模数据。因为每个数字的字模大小是16*32,所以就可以知道字模1的字模数据在rom中的起始地址是1*32=32。那么字模1的第20行数据的地址不就有了,就是32+20=52。从rom的52地址中取出16位数据,这16位数据就是第20行显示的数据。假设是0180。

根据横坐标,188-176=12。那么16位数据中的第12位数据0就是该坐标显示的数据。所以来说就有以下公式,假设此时坐标是(x,y)

行显示数据[15:0] = rom[该点区域显示的数值*32+ y - 300]

该点显示数据=行显示数据[x-该点区域的起始x位置]

该点区域的起始x位置是根据显示的时间的不同值而不一样的,对于时十,为176,对于时个,为192,正好是前一个加16,因为显示的数字是16像素宽的。

所以翻译成代码:

reg [4:0] y_position;     reg [3:0] x_position;    always@(*) begin        if(address_y >= 300 && address_y <= 331)             begin                y_position = address_y - 300;                if(address_x >= 176 && address_x <= 191 )  begin                      mode_xuanze = hour_shi;                        x_position  = address_x - 176;  end                 else if(address_x >= 192 && address_x <= 207 )  begin                      mode_xuanze = hour_ge;                        x_position  = address_x - 192;  end                 else if(address_x >= 208 && address_x <= 223 )  begin                      mode_xuanze = kongbai;                        x_position  = address_x - 208;  end                 else if(address_x >= 224 && address_x <= 239 )  begin                      mode_xuanze = maohao;                        x_position  = address_x - 224;  end                 else if(address_x >= 240 && address_x <= 255 )  begin                      mode_xuanze = kongbai;                        x_position  = address_x - 240;  end                 else if(address_x >= 256 && address_x <= 271 )  begin                      mode_xuanze = minu_shi;                        x_position  = address_x - 256;  end                 else if(address_x >= 272 && address_x <= 287 )  begin                      mode_xuanze = minu_ge;                        x_position  = address_x - 272;  end                 else if(address_x >= 288 && address_x <= 303 )  begin                      mode_xuanze = kongbai;                        x_position  = address_x - 288;  end                 else if(address_x >= 304 && address_x <= 319 )  begin                      mode_xuanze = maohao;                        x_position  = address_x - 304;  end                 else if(address_x >= 320 && address_x <= 335 )  begin                      mode_xuanze = kongbai;                        x_position  = address_x - 320;  end                 else if(address_x >= 336 && address_x <= 351 )  begin                      mode_xuanze = seco_shi;                        x_position  = address_x - 336;  end                 else if(address_x >= 352 && address_x <= 367 )  begin                      mode_xuanze = seco_ge;                        x_position  = address_x - 352;  end                 else begin                      mode_xuanze = kongbai;                        x_position  = 0;  end             end          else            begin              mode_xuanze = kongbai;               y_position = 0;                x_position  = 0;             end     end       reg [3:0]  data_time;    always@(*) begin        case(mode_xuanze)         hour_shi :                  data_time = data_5;         hour_ge  :                  data_time = data_4;         minu_shi :                  data_time = data_3;         minu_ge  :                  data_time = data_2;         seco_shi :                  data_time = data_1;         seco_ge  :                  data_time = data_0;         maohao   :                  data_time = 10;         kongbai  :                  data_time = 11;         default:                  data_time = 11;         endcase     end                 reg [8:0]  address_base;     always@( * ) begin         case(data_time)          4'd0 :                address_base = 0;          4'd1 :                address_base = 32;          4'd2 :                address_base = 64;          4'd3 :                address_base = 96;         4'd4 :                address_base = 128;          4'd5 :                address_base = 160;          4'd6 :                address_base = 192;          4'd7 :                address_base = 224;          4'd8 :                address_base = 256;          4'd9 :                address_base = 288;          4'd10:                address_base = 320;          4'd11:                address_base = 352;          default:                address_base =0;          endcase     end           wire [8:0] addra;     assign addra = address_base + y_position;     wire [15:0] douta;           zimo_rom zimo_rom_u1 (  .clka(clk), // input clka  .addra(addra), // input [8 : 0] addra  .douta(douta) // output [15 : 0] douta);     wire [15:0] douta_yiwei;     assign douta_yiwei = douta << x_position;assign data = douta_yiwei[15];

最后在设计一个顶层,将各个模块连接起来,就可以了。

下面贴上效果图:

数码管闪烁

数字钟的FPGA实现并在VGA上显示

数字钟的FPGA实现并在VGA上显示

VGA显示

数字钟的FPGA实现并在VGA上显示

虽然说是实现功能了,但是现在去看去年写的代码,感觉当时写的代码风格可真是挫啊。还好是自己写的,思路还记得一些,所以隔了这么久来看,还能看得懂。但是如果代码是别人写的话,可就不容易看懂了。

不过,看看以前写的代码,再对比现在自己写的,明显感受到了自己在技术方面的成长。

提醒:《数字钟的FPGA实现并在VGA上显示》最后刷新时间 2024-03-14 01:06:02,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《数字钟的FPGA实现并在VGA上显示》该内容的真实性请自行鉴别。