ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-01-24 16:01:05  阅读:240  来源: 互联网

标签:动态显示 心得体会 FPGA clk 1ms space 数码管 rst reg


文章目录

一、项目要求

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

二、问题分析与思路设计

2.1 从基本的动态显示入手

我们首先下图这种两个数码管的情况来进行分析:假设我们现在需要让这两个数码管显示:“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。

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文件是不够用的,元件例化就非常重要,我们可以设计好一个一个的小模块,然后把这些小模块连接起来。下面是笔者的工程文件架构:

在这里插入图片描述

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

在这里插入图片描述

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,clk,1ms,space,数码管,rst,reg
来源: https://blog.csdn.net/weixin_44586473/article/details/113081783

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有