【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会

文章目录

一、项目要求

任务:使用FPGA开发板上的6位数码管以动态方式从0开始计数,每100ms计数值增加一,当计数值从0增加到999999后重新从0开始计数。

二、问题分析与思路设计

2.1 从基本的动态显示入手

我们首先下图这种两个数码管的情况来进行分析:假设我们现在需要让这两个数码管显示:“12”,那应该怎么做呢?

【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会

通过上图我们可以看出:数码管的位选信号(10引脚和5引脚)是独立的,而且是高电平有效。这也就意味着如果我们给 10 引脚高电平,5引脚低电平(即 “10” 信号),那么就可以让左边的数码管亮,右边的数码管不亮。反之,如果给 “01” 信号,那么就是左边的数码管不亮,右边的数码管亮。

不过值得注意的是:如果只是看上图的位选,那么确实是高电平有效。但是在实际的FPGA开发板,驱动数码管所需要的电流要比较大,因此需要使用三极管来对位选信号进行放大,笔者所使用的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”,而右边的数码管不亮。

【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会

那么如果我们给位选信号是 “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。

2.2 算法设想

现在我们已经了解了数码管的动态显示是如何工作的,那么回到我们本次项目的需求:

使用FPGA开发板上的6位数码管以动态方式从0开始计数,每100ms计数值增加一,当计数值从0增加到999999后重新从0开始计数。

那么显示 1到999999之间的任何数字 这一环节就是用我们在上一个section 里面提到的方法.

那么我们可以知道需要两个定时器:

  1. 一个是 1ms 定时器(也就是我们在 2.1 节提到的在每隔 1ms 就分别让6个数码管中的其中一个点亮,其余不亮),它每隔1ms就可以输出一个 flag 信号指示数码管的切换。
  2. 另外一个就是 100ms 定时器,他的作用是每隔100ms就产生一个 data信号(1到999999)。

然后我们还需要一个数码管显示模块,它接收来自 1ms 定时器输出的 flag 信号,每隔1ms就切换数码管的显示状态,同时它也需要接受 100ms 定时器输出的 data 信号,用于给当前时刻被点亮的数码管赋予一个需要显示的数字

这里数码管显示模块又涉及到两种问题:

  1. 如何切换数码管的显示状态?
  2. 如何给当前时刻被点亮的数码管赋予数字?

对于第一个问题,首先我们的 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其他情况都应该给所有段选信号高电平)。

三、实际操作

3.1 程序架构

显然,对于我们的工程,1个verilog文件是不够用的,元件例化就非常重要,我们可以设计好一个一个的小模块,然后把这些小模块连接起来。下面是笔者的工程文件架构:

【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会

3.2 Verilog代码

3.2.1 main模块

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 

3.2.2 100ms定时器模块

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 

3.2.1.1 100ms定时器Testbench文件

`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

3.2.2 1ms定时器模块

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

3.2.2.1 1ms定时Testbench文件

`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

【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会

3.2.3 数码管显示模块

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

3.3 实验结果

下图是本次项目的RTL图:
【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会
上板发现实验结果符合预期。

上一篇:h5上传图片,lrz压缩图片


下一篇:FPGA——串口通信——使用三状态的状态机实现任意字节的数据发送