赞
踩
任务:使用FPGA开发板上的6位数码管以动态方式从0开始计数,每100ms计数值增加一,当计数值从0增加到999999后重新从0开始计数。
我们首先下图这种两个数码管的情况来进行分析:假设我们现在需要让这两个数码管显示:“12”,那应该怎么做呢?
通过上图我们可以看出:数码管的位选信号(10引脚和5引脚)是独立的,而且是高电平有效。这也就意味着如果我们给 10 引脚高电平,5引脚低电平(即 “10” 信号),那么就可以让左边的数码管亮,右边的数码管不亮。反之,如果给 “01” 信号,那么就是左边的数码管不亮,右边的数码管亮。
不过值得注意的是:如果只是看上图的位选,那么确实是高电平有效。但是在实际的FPGA开发板,驱动数码管所需要的电流要比较大,因此需要使用三极管来对位选信号进行放大,笔者所使用的FPGA开发板上位选信号输入的原理图如下:
上图中的 S E L 0 _ T SEL0\_T SEL0_T ~ S E L 5 _ T SEL5\_T SEL5_T就是我们刚刚所讨论的位选信号,而 S E L 0 SEL0 SEL0~ S E L 5 SEL5 SEL5就是真正作用于数码管的信号,可见位选信号是 PNP 三极管的基级,因此当 S E L 0 _ T SEL0\_T SEL0_T ~ S E L 5 _ T SEL5\_T SEL5_T 中某一些位是低电平时, S E L 0 SEL0 SEL0~ S E L 5 SEL5 SEL5 中的对应位才变为高电平,用于驱动对应位置的数码管。因此在本例中,数码管的位选信号是低电平有效的。
【不过在2.1节的剩余部分,考虑到内容的连贯性,还是假设位选信号就是第一幅图里面的10口和5口,且为高电平有效,但是在代码部分,我们将会重新考虑实际的原理图】
然而我们又发现:这两个数码管他们的段选信号是公共的,而且是低电平有效。 段选信号指的就是让数码管显示具体的哪一个数字,段选信号与数字显示的对应关系如下图所示。 比如说,咱们现在给位选信号是 “10”,这样就是左边的数码管亮,右边的数码管不亮;同时我们给段选信号是:【注:我们这里暂且假设a位是高位,这个具体看实际电路的情况】 ( a b c d e f g D P ) = ( 1 0 0 1 1 1 1 1 ) (a\space b\space c\space d \space e \space f \space g \space D_P)= (1\space 0\space 0\space 1 \space 1\space 1\space1\space1) (a b c d e f g DP)=(1 0 0 1 1 1 1 1)
那么就是说我们让左边的数码管显示 “1”,而右边的数码管不亮。
那么如果我们给位选信号是 “01”,段选信号是: ( a b c d e f g D P ) = ( 0 0 1 0 0 1 0 1 ) (a\space b\space c\space d \space e \space f \space g \space D_P)= (0\space 0\space 1\space 0 \space 0\space 1\space0\space1) (a b c d e f g DP)=(0 0 1 0 0 1 0 1)
这样一来,就是左边的数码管不亮,右边的数码管亮,且显示数字 “2”。
可是我们希望能够显示 “12”,如果说给位选信号是 “11”,也就是两个数码管同时亮,但是这样的话因为段选信号是公共的,那么也就是说两个数码管一定会显示相同的数字。
幸运的是,我们的人眼存在 “视觉暂留现象”,我们假设:左边数码管显示“1”,右边数码管不亮这个状态为 “状态1”;左边数码管不亮,右边数码管显示 “2”这个状态为 “状态2”。那么,只要状态1 和状态2 他俩之间来回切换的频率高于一定的程度,我们的人眼就分辨不出来,从而以为是这两个数码管分别显示了不同的数字。 一般来讲,状态1和状态2之间的间隔时间可以是 1ms ~ 2ms。
现在我们已经了解了数码管的动态显示是如何工作的,那么回到我们本次项目的需求:
使用FPGA开发板上的6位数码管以动态方式从0开始计数,每100ms计数值增加一,当计数值从0增加到999999后重新从0开始计数。
那么显示 1到999999之间的任何数字 这一环节就是用我们在上一个section 里面提到的方法.
那么我们可以知道需要两个定时器:
然后我们还需要一个数码管显示模块,它接收来自 1ms 定时器输出的 flag 信号,每隔1ms就切换数码管的显示状态,同时它也需要接受 100ms 定时器输出的 data 信号,用于给当前时刻被点亮的数码管赋予一个需要显示的数字。
这里数码管显示模块又涉及到两种问题:
对于第一个问题,首先我们的 1ms定时器 在定时到1ms时会给我们的数码管显示模块发送一个切换指示信号 flag,那么我们可以再在数码管显示模块里面设一个 reg 类型的信号 c n t _ s e l cnt\_sel cnt_sel,每来一个 flag,那么我的 c n t _ s e l cnt\_sel cnt_sel 就加一(最多到5),因此 c n t _ s e l cnt\_sel cnt_sel 每隔 1ms 就会在 0, 1,2,3,4,5这五个数字之间来回变化,那么最后我们只需要给一个 case 语句,分配好这6个数字与每一个数码管亮的对应关系即可。
对于第二个问题,我们可以首先获取输入到数码管显示模块的 data 的各个位。把他们分别转换成二进制,最后还是用一个 case 判断不同的二进制对应的段选信号情况。(值得注意的是:由于我们将要显示的数字里面没有 A,B,C,D,E,F这几个情况,因此在case的default里面,除了1~10其他情况都应该给所有段选信号高电平)。
显然,对于我们的工程,1个verilog文件是不够用的,元件例化就非常重要,我们可以设计好一个一个的小模块,然后把这些小模块连接起来。下面是笔者的工程文件架构:
module main( input clk, input rst, output wire[5:0] sel, output wire[7:0] seg_led); wire [19:0] data; wire en; wire flag; counter_100ms u0( .clk(clk), .rst(rst), .en(en), .data(data)); counter_1ms u1( .clk(clk), .rst(rst), .flag(flag)); seg u2( .clk(clk), .rst(rst), .data(data), .flag(flag), .sel(sel), .seg_led(seg_led)); endmodule
module counter_100ms( input clk, input rst, output reg en, output reg[19:0] data); parameter max_count_100ms = 23'd5000_000; reg[22:0] cnt; reg flag1; always@(posedge clk or negedge rst) begin if(!rst) begin cnt <= 23'b0; flag1 <= 1'b0; end else begin if(cnt < max_count_100ms -1) begin cnt <= cnt + 23'b1; flag1 <= 1'b0; end else begin cnt <= 23'b0; flag1 <= 1'b1; //When flag1 = 1'b1,it means that the data should be self increasing end end end always@(posedge clk or negedge rst) begin if(!rst) begin en <= 1'b0; //close the enable signal data <= 20'b0; //clear the data end else begin en <= 1'b1; if(flag1) begin if(data < 20'd999_999) data <= data + 20'b1; else data <= 20'b0; end else data <= data; end end endmodule
`timescale 1ns/1ns module counter_100ms_tb(); reg clk; reg rst; wire en; wire[19:0] data; parameter T = 20; always #(T/2) clk = ~clk; initial begin clk = 0; rst = 0; #(T) rst = 1; end counter_100ms u0( .clk(clk), .rst(rst), .en(en), .data(data)); endmodule
module counter_1ms( input clk, input rst, output reg flag); parameter max_count_1ms = 50000; reg[15:0] cnt1; always@(posedge clk or negedge rst) begin if(!rst) begin cnt1 <= 16'b0; flag <= 1'b0; end else begin if(cnt1 < max_count_1ms - 1) begin cnt1 <= cnt1 + 16'b1; flag <= 1'b0; end else begin cnt1 <= 16'b0; flag <= 1'b1; end end end endmodule
`timescale 1ns/1ns module counter_1ms_tb(); reg clk; reg rst; wire flag; parameter T = 20; always #(T/2) clk = ~clk; initial begin clk = 0; rst = 0; #(T) rst = 1; end counter_1ms u0( .clk(clk), .rst(rst), .flag(flag)); endmodule
module seg( input clk, input rst, input [19:0] data, input flag, //flag is used for deciding which tube should be chosen. output reg[5:0] sel, //Digital tube selection output reg[7:0] seg_led); //-----------------------Set 1 ------------------------------ reg [2:0] cnt_sel; //----------------------------------------------------------- //-----------------------Set 2 ------------------------------ reg [3:0] num_disp; wire[3:0] data0; //Individual wire[3:0] data1; //Ten wire[3:0] data2; //Hundred wire[3:0] data3; //Thousand wire[3:0] data4; //Ten thousand wire[3:0] data5; //One hundred thousand reg [23:0] num; /*Note:Why the reg num is 24 bit? For example: if the data is 789210,it means we want the six tube display :"7","8","9","2","1","0" respectively. Let the digital tube display these numbers, we need to turn them into binary.Like: "0111","1000","1001","0010","0001","0000". so 6-bit decimal number needs to 24 bit binary number at most */ //----------------------------------------------------------- //It is used to switch the state of digital tube every 1ms always@(posedge clk or negedge rst) begin if(!rst) cnt_sel <= 3'b0; else begin if(flag) begin if(cnt_sel < 3'd5) cnt_sel <= cnt_sel + 1'b1; else cnt_sel <= 3'b0; end else cnt_sel <= cnt_sel; end end assign data0 = data % 4'd10; assign data1 = data / 4'd10 % 4'd10; assign data2 = data / 7'd100 % 4'd10; assign data3 = data / 10'd1000 % 4'd10; assign data4 = data / 14'd10000 % 4'd10; assign data5 = data / 17'd100000; always@(posedge clk or negedge rst) begin if(!rst) num <= 24'b0; else begin if(data5) begin //If data is six digits num[23:0] <= {data5,data4,data3,data2,data1,data0}; end else begin if(data4) begin num[19:0] <= {data4,data3,data2,data1,data0}; num[23:20] <= 4'd10; //Don't show anything end else begin if(data3) begin num[15:0] <= {data3,data2,data1,data0}; num[23:16] <= {2{4'd10}}; end else begin if(data2) begin num[11:0] <= {data2,data1,data0}; num[23:12] <= {3{4'd10}}; end else begin if(data1) begin num[7:0] <= {data1,data0}; num[23:8] <= {4{4'd10}}; end else begin if(data0) begin num[3:0] <= data0; num[23:4] <= {5{4'd10}}; end end end end end end end end always@(posedge clk or negedge rst) begin if(!rst) begin sel <= 6'b111111; //Bit select signal low level valid num_disp <= 4'b0; end else begin case(cnt_sel) 3'd0: begin sel <= 6'b111110; num_disp <= num[3:0]; end 3'd1: begin sel <= 6'b111101; num_disp <= num[7:4]; end 3'd2: begin sel <= 6'b111011; num_disp <= num[11:8]; end 3'd3: begin sel <= 6'b110111; num_disp <= num[15:12]; end 3'd4: begin sel <= 6'b101111; num_disp <= num[19:16]; end 3'd5: begin sel <= 6'b011111; num_disp <= num[23:20]; end endcase end end always@(posedge clk or negedge rst) begin if(!rst) seg_led <= 8'hc0; else begin case(num_disp) 4'd0 : seg_led <= 8'b1100_0000; 4'd1 : seg_led <= 8'b1111_1001; 4'd2 : seg_led <= 8'b1010_0100; 4'd3 : seg_led <= 8'b1011_0000; 4'd4 : seg_led <= 8'b1001_1001; 4'd5 : seg_led <= 8'b1001_0010; 4'd6 : seg_led <= 8'b1000_0010; 4'd7 : seg_led <= 8'b1111_1000; 4'd8 : seg_led <= 8'b1000_0000; 4'd9 : seg_led <= 8'b1001_0000; default: //when num_disp is 4'd10, The default branch will be executed //hence the tube will not show anything. seg_led <= 8'b11111111; endcase end end endmodule
下图是本次项目的RTL图:
上板发现实验结果符合预期。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。