NEXYS 3开发板练手--LED与数码管时钟

  做科研的时候从学校拿到一块基于Xilinx公司Spartan-6主芯片的FPGA开发板,因为之前一直在用Altera公司的FPGA,一开始接触它还真有点不太习惯。但毕竟核心的东西还是不会变的,于是按照惯例,先仔细瞄了瞄这块开发板,看看有哪些可用的资源--拨码开关、按键、LED、七段数码管、USB Host、USB UART、VGA、以太网接口,嗯哼,虽然比不上友晶的DE2那么强大,但是看来做一般的开发还是绰绰有余的。

  瞄完就是上网找资料了,首先找是板子的制造商--digilent,下载原理图,下载能用的开发工具,下载DEMO,然后就是学着怎么操作Xilinx的开发工具,将DEMO下载上去,确定板子有没有问题(这其间从熟悉的quartus转到陌生的Xilinx ISE着实花了我一番功夫,这里就不罗嗦了)。digilent上有一个DEMO挺有意思的,叫Nexys3_ISE_GPIO_UART的工程,它将拨码开关、按键、LED、七段数码管、USB UART这几种板子上的基础资源写进了一个工程里,效果好象是拨码开关控制LED,数码管循环显示几个单词,然后用USB数据线将UART口跟电脑相连,能通过按键控制向电脑发送特定的字符串,然后能在电脑上的超级终端接收到这个字符,看着挺牛的嘛!不过那总是人家的,要变成自己的还得自己动手!说干就干,那就拿他练手吧。

  按理来说这也不难吧,毕竟有现成的东西在,我开始也这么想的,不过当我打开那个DEMO里面的代码一看,顿时有种想骂人的冲动--居然是VHDL!我一开始学的就是Verilog,VHDL压根就没接触过,这两种语言差别还有点大呢!看这里面的代码,就像天书!要我看懂这些家伙我还不如自己研究呢(其实也没那么恐怖)!于是就准备自己写一份测试的DEMO,就当是练手了。

  原本的DEMO功能有点多,于是我就想把它分两部分来完成,把比较复杂的USB UART这部分单独拿出来,其他的放在一个DEMO里面。后来又想,既然有4个数码管,那就做个时钟吧,虽然没有小时部分,但作为练手还是无伤大雅的。这篇文章先说说这个数码管时钟,USB UART部分将在后面的文章中讨论。

  数码管时钟我想对于很多学电子的同学来说都不陌生了,特别是学过单片机的,这个东西是入门练手必备的。闲话不多说,先来说说这个原理。怎么点亮单个数码管我就不说了,无非分清共阴共阳,然后就是置高置低的问题。但是对于4个一起的数码管,要怎么控制他们看起来好像是在同时点亮的而且还能显示不同的内容就比较麻烦了。先上原理图:

                                                          

  显然要控制一个数码管需要两部分的端口--CA...DP和AN0...AN3,我一般叫左边的为段选,右边的为位选。从原理图上来看,位选端接在PNP管的B极,要先让C、E极导通,相应的位选端应该是低电平,比如AN3...AN0四位为1110,就是选择了最后一个数码管。而当数码管选中时(即相应的三极管导通),数码管公共端接的是高电平,显然这些数码管应该是共阳的,于是CA...DP端的编码就可以按共阳数码管的编码方式做了。转换成Verilog代码,可以是一下的常数定义:

parameter AN1 = 4'b1110,AN2 = 4'b1101,AN3 = 4'b1011,AN4 = 4'b0111;//数码管位选定义
parameter zero = 8'b0000_0011,one = 8'b1001_1111,two = 8'b0010_0101,three = 8'b0000_1101,four = 8'b1001_1001,
             five = 8'b0100_1001,six = 8'b0100_0001,seven = 8'b0001_1111,eigth = 8'b0000_0001,nine = 8'b0000_1001;//数码管段选定义

  好了,现在搞清楚了第一个问题--怎么将0-9几个数显示出来,接下来的问题是怎么让4个数码管看起来是一起显示的,而且还能显示不同的内容。这个问题相信做过单片机数码管实验的同学马上就猜到了,没错,原理就是利用人眼视觉的暂留性(我是这么叫的,再专业点就不是我研究的问题了)--当两个数码管之间的变化小于100ms(貌似是这么长时间)时,我们的眼睛是看不到他们之间的闪烁的。于是,当我们要显示1234这4个数时,我们可以在1ms时显示1,25ms时显示2,50ms时显示3,75ms时显示4,然后循环,看起来就是一起显示1234这4个数了。当然,我们的硬件可以做的更快,这个时间间隔可以根据实际情况选择更短。把它翻译成Verilog语言就有下面的程序段了:

//数码管刷新显示模块
always@(posedge div1000hz)
begin
    if(rest)
    begin
        seg_count <= 0;
        an <= 4'b0000;
        seg <= 8'b0000_0000;
    end
    else
    begin
    case(seg_count)
    0 : begin
            case(sec1)
            0 : seg <= zero;
            1 : seg <= one;
            2 : seg <= two;
            3 : seg <= three;
            4 : seg <= four;
            5 : seg <= five;
            6 : seg <= six;
            7 : seg <= seven;
            8 : seg <= eigth;
            9 : seg <= nine;
            endcase
            an <= AN1;
         end
    1 : begin
            case(sec2)
            0 : seg <= zero;
            1 : seg <= one;
            2 : seg <= two;
            3 : seg <= three;
            4 : seg <= four;
            5 : seg <= five;
            6 : seg <= six;
            7 : seg <= seven;
            8 : seg <= eigth;
            9 : seg <= nine;
            endcase
            an <= AN2;
         end
    2 : begin
            case(min1)
            0 : seg <= zero;
            1 : seg <= one;
            2 : seg <= two;
            3 : seg <= three;
            4 : seg <= four;
            5 : seg <= five;
            6 : seg <= six;
            7 : seg <= seven;
            8 : seg <= eigth;
            9 : seg <= nine;
            endcase
            an <= AN3;
         end
    3 : begin
            case(min2)
            0 : seg <= zero;
            1 : seg <= one;
            2 : seg <= two;
            3 : seg <= three;
            4 : seg <= four;
            5 : seg <= five;
            6 : seg <= six;
            7 : seg <= seven;
            8 : seg <= eigth;
            9 : seg <= nine;
            endcase
            an <= AN4;
         end
    endcase
    seg_count <= seg_count + 1;
    end
end

  好,现在剩最后一个问题了,那就是时钟控制部分。熟悉C的同学会说,那还不简单,分频计数,然后整除求余,将秒、分的十位各位分开不就可以了吗?没错,这是最简单的方式,但是,大家千万不要忽略了我们最后的代码是要变成硬件电路的。先不说Xilinx的IDE内嵌的XST综合器不支持对除号的综合,要实现除法必须要设计除法器(调用IP或自己设计),就算是设计出来了,一个除法器占用的硬件资源可能会比我们剩余部分电路占用的资源还多。从这方面出发考虑,我们要想想是不是有更好的方法实现它。其实只要我们稍微思考一下还是不难的,下面的代码就能很简洁的完成这部分的设计:

//时钟模块
always@(posedge div1hz)
begin
    if(rest)
    begin
        sec1 <= 0;
        sec2 <= 0;
        min1 <= 0;
        min2 <= 0;
    end
    else
    begin
    sec1 <= sec1 + 1;
    if(sec1 == 9)
    begin
        sec2 <= sec2 + 1;
        sec1 <= 0;
    end
    if(sec2 == 5 && sec1 == 9)
    begin
        min1 <= min1 + 1;
        sec2 <= 0;
    end
    if(min1 == 9)
    begin
        min2 <= min2 + 1;
        min1 <= 0;
    end
    if(min2 == 5 && min1 == 9)
    begin
        min2 <= 0;
    end
    end
end

可能我们会多写几行代码,但是最后设计出来的电路的性能往往会有大的飞跃,这是我们在做硬件开发时需要时常提醒自己的。

  好了,以上就是这个DEMO的关键部分,其实也是相当基础的,把它拿出来作为我的第一篇技术文章与大家分享,有不当之处还请大家指出。下面是完整的程序,中间还将LED显示加进去了,让八个LED间隔1s亮灭:

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Company: 
  4 // Engineer:         lwy
  5 // 
  6 // Create Date:    00:33:08 11/09/2013 
  7 // Design Name: 
  8 // Module Name:    segclk 
  9 // Project Name: 
 10 // Target Devices: 
 11 // Tool versions: 
 12 // Description:NEXYS3开发板测试程序,用数码管实现时钟功能
 13 //
 14 // Dependencies: 
 15 //
 16 // Revision: 
 17 // Revision 0.01 - File Created
 18 // Additional Comments: 
 19 //
 20 //////////////////////////////////////////////////////////////////////////////////
 21 module segclk(
 22     clk,//时钟输入,100M
 23     rest,//时钟复位信号,BTNU,高电平时复位
 24     seg,//数码管段选
 25     an,//数码管位选
 26     led//8个LED
 27     );
 28 
 29 input clk,rest;
 30 output reg [7:0] seg;
 31 output reg [3:0] an;
 32 output reg [7:0] led;
 33 
 34 parameter AN1 = 4'b1110,AN2 = 4'b1101,AN3 = 4'b1011,AN4 = 4'b0111;//数码管位选定义
 35 parameter zero = 8'b0000_0011,one = 8'b1001_1111,two = 8'b0010_0101,three = 8'b0000_1101,four = 8'b1001_1001,
 36              five = 8'b0100_1001,six = 8'b0100_0001,seven = 8'b0001_1111,eigth = 8'b0000_0001,nine = 8'b0000_1001;//数码管段选定义
 37 parameter led_on = 8'b1111_1111,led_off = 8'b0000_0000;//LED亮灭定义
 38              
 39 reg [25:0] div1hz_temp;//1秒分频计数
 40 reg [12:0] div1000hz_temp;//数码管刷新时间分频计数
 41 reg div1hz;//1Hz时钟
 42 reg div1000hz;//数码管刷新时钟,频率为(100M/2^13)Hz
 43 reg led_temp;//LED亮灭
 44 
 45 reg [3:0] sec1;//秒个位
 46 reg [3:0] sec2;//秒十位
 47 reg [3:0] min1;//分个位
 48 reg [3:0] min2;//分十位
 49 
 50 reg [1:0] seg_count;//数码管位选定位
 51 
 52 //1秒分频模块
 53 always@(posedge clk)
 54 begin
 55     div1hz_temp <= div1hz_temp + 1;
 56     if(div1hz_temp == 50000000)
 57     begin
 58         div1hz_temp <= 0;
 59         div1hz <= ~div1hz;
 60     end
 61 end
 62 
 63 //数码管刷新时间分频模块
 64 always@(posedge clk)
 65 begin
 66     div1000hz_temp <= div1000hz_temp + 1;
 67     if(div1000hz_temp == 8191)
 68     begin
 69         div1000hz <= ~div1000hz;
 70     end
 71 end
 72 
 73 //时钟模块
 74 always@(posedge div1hz)
 75 begin
 76     if(rest)
 77     begin
 78         sec1 <= 0;
 79         sec2 <= 0;
 80         min1 <= 0;
 81         min2 <= 0;
 82     end
 83     else
 84     begin
 85     sec1 <= sec1 + 1;
 86     if(sec1 == 9)
 87     begin
 88         sec2 <= sec2 + 1;
 89         sec1 <= 0;
 90     end
 91     if(sec2 == 5 && sec1 == 9)
 92     begin
 93         min1 <= min1 + 1;
 94         sec2 <= 0;
 95     end
 96     if(min1 == 9)
 97     begin
 98         min2 <= min2 + 1;
 99         min1 <= 0;
100     end
101     if(min2 == 5 && min1 == 9)
102     begin
103         min2 <= 0;
104     end
105     end
106 end
107 
108 //LED亮灭控制模块
109 always@(posedge div1hz)
110 begin
111     led_temp = ~led_temp;
112     if(led_temp)
113         led <= led_on;
114     else
115         led <= led_off;
116 end
117 
118 //数码管刷新显示模块
119 always@(posedge div1000hz)
120 begin
121     if(rest)
122     begin
123         seg_count <= 0;
124         an <= 4'b0000;
125         seg <= 8'b0000_0000;
126     end
127     else
128     begin
129     case(seg_count)
130     0 : begin
131             case(sec1)
132             0 : seg <= zero;
133             1 : seg <= one;
134             2 : seg <= two;
135             3 : seg <= three;
136             4 : seg <= four;
137             5 : seg <= five;
138             6 : seg <= six;
139             7 : seg <= seven;
140             8 : seg <= eigth;
141             9 : seg <= nine;
142             endcase
143             an <= AN1;
144          end
145     1 : begin
146             case(sec2)
147             0 : seg <= zero;
148             1 : seg <= one;
149             2 : seg <= two;
150             3 : seg <= three;
151             4 : seg <= four;
152             5 : seg <= five;
153             6 : seg <= six;
154             7 : seg <= seven;
155             8 : seg <= eigth;
156             9 : seg <= nine;
157             endcase
158             an <= AN2;
159          end
160     2 : begin
161             case(min1)
162             0 : seg <= zero;
163             1 : seg <= one;
164             2 : seg <= two;
165             3 : seg <= three;
166             4 : seg <= four;
167             5 : seg <= five;
168             6 : seg <= six;
169             7 : seg <= seven;
170             8 : seg <= eigth;
171             9 : seg <= nine;
172             endcase
173             an <= AN3;
174          end
175     3 : begin
176             case(min2)
177             0 : seg <= zero;
178             1 : seg <= one;
179             2 : seg <= two;
180             3 : seg <= three;
181             4 : seg <= four;
182             5 : seg <= five;
183             6 : seg <= six;
184             7 : seg <= seven;
185             8 : seg <= eigth;
186             9 : seg <= nine;
187             endcase
188             an <= AN4;
189          end
190     endcase
191     seg_count <= seg_count + 1;
192     end
193 end
194 
195 endmodule
View Code 
原文地址:https://www.cnblogs.com/lwybky/p/3507669.html