FPGA数码管动态显示

发布时间 2023-07-17 10:36:13作者: 浅晓寒

FPGA驱动6位数码管,主控芯片EP4CE6F17C8N。

所使用实验板的数码管原理图如图所示,所使用的数码管3661BS是6位共阳极的数码管。使用PNP三极管驱动数码管,当三极管基极SMG_W0引脚输入低电平时,PNP三极管导通。通过控制SMG_W0~W5的电平来控制三极管的导通,从而控制位选信号。共阳极数码管,段选信号输入低电平有效。

整个数码管动态项目共有4个模块。顶层模块seg_top、数据生成模块data_generate、数码管动态显示模块seg_dynamic、数据转换模块bcd8421。

seg_top.v

module seg_top
(
	input   wire            clk     	, //系统时钟,频率50MHz
    input   wire            reset_n   	, //复位信号,低有效

    output  wire     [5:0]   bit         , //数码管位选信号
    output  wire     [7:0]   seg           //数码管段选信号
);

	wire    [19:0]  data        ; //数码管要显示的值
    wire    [5:0]   point       ; //小数点显示,高电平有效
    wire            seg_en      ; //数码管使能信号,高电平有效
    wire            sign        ; //符号位,高电平显示负号

	smg_dynamic smg_dynamic_inst1
	(
		.clk     	(clk), //系统时钟,频率50MHz
		.reset_n   	(reset_n), //复位信号,低有效
		.data       (data), //数码管要显示的值
		.point      (point), //小数点显示,高电平有效
		.seg_en     (seg_en), //数码管使能信号,高电平有效
		.sign       (sign), //符号位,高电平显示负号
		.bit        (bit), //数码管位选信号
		.seg        (seg)//数码管段选信号
	);
	
	data_generate data_generate_inst1
	(
		.clk			(clk	),
		.reset_n		(reset_n),
		.data       	(data	),
		.point       	(point	),
		.seg_en      	(seg_en	),
		.sign           (sign	)	
	);
	
	
endmodule

data_generate.v

//数据生成模块,生成数码管要显示的数据
module data_generate
#(
	parameter CNT_MAX = 32'd4999_9999,	//20ns*50000000=1s
	parameter DATA_MAX = 20'd999_999	//数码管显示的最大数999_999
)
(
	input clk,
	input reset_n,
	
	output  reg     [19:0]  data        ,   //数码管要显示的数据,0~999_999需要20bit
    output  wire    [5:0]   point       ,   //小数点显示,高电平有效
    output  reg             seg_en      ,   //数码管使能信号,高电平有效
    output  wire            sign            //符号位,高电平显示负号
);

	//不显示小数点以及负数
	assign  point   =   6'b000_000;
	assign  sign    =   1'b0;

	reg [31:0] cnt_1s;		//1s计数器
	reg flag_1s;			//1s标志位
	
	//计时1s
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			cnt_1s <= 32'b0;
		else if(cnt_1s == CNT_MAX)
			cnt_1s <= 32'b0;
		else 
			cnt_1s <= cnt_1s + 1;
	
	//记满1s产生一次标志位	
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			flag_1s <= 1'b0;
		else if(cnt_1s == CNT_MAX - 1)	//这里减一是为了和下次脉冲信号之间相差1s
			flag_1s <= 1'b1;
		else 
			flag_1s <= 1'b0;
			
	//根据脉冲生成要显示的数码管数据,每1s使数码管的值+1	
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			data <= 20'b0;
		else if((data == DATA_MAX) && (flag_1s == 1'b1))	//这里减一是为了和下次脉冲信号之间相差1s
			data <= 20'b0;
		else if(flag_1s == 1'b1)
			data <= data + 1;
		else
			data <= data;
			
	//数码管使能信号给高即可
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			seg_en  <=  1'b0;
		else
			seg_en  <=  1'b1;
	
endmodule

seg_dynamic.v

//数码管动态显示模块
module smg_dynamic
#(
	parameter CNT_MAX = 49999
)
(
	input   wire            clk     	, //系统时钟,频率50MHz
    input   wire            reset_n   	, //复位信号,低有效
    input   wire    [19:0]  data        , //数码管要显示的值
    input   wire    [5:0]   point       , //小数点显示,高电平有效
    input   wire            seg_en      , //数码管使能信号,高电平有效
    input   wire            sign        , //符号位,高电平显示负号

    output  reg     [5:0]   bit         , //数码管位选信号
    output  reg     [7:0]   seg           //数码管段选信号
);
	wire	[3:0] 		unit;	//个位
	wire 	[3:0] 		ten;	//十位
	wire 	[3:0] 		hun;	//百位
	wire 	[3:0] 		tho;	//千位
	wire 	[3:0] 		t_tho;	//万位
	wire 	[3:0] 		h_tho;	//十万位
	
	//BCD模块例化
	bcd8421 bcd8421_inst1(
		.clk		(clk	),
		.reset_n	(reset_n),
		.data		(data	),
		.unit		(unit	),	
		.ten		(ten	),	
		.hun		(hun	),	
		.tho		(tho	),	
		.t_tho		(t_tho	),	
		.h_tho		(h_tho	)	
	);
	
	//reg   define
	reg     [23:0]  data_reg    ;   //待显示数据寄存器
	reg     [15:0]  cnt_1ms     ;   //1ms计数器
	reg             flag_1ms    ;   //1ms标志信号
	reg     [2:0]   cnt_sel     ;   //数码管位选计数器
	reg     [5:0]   sel_reg     ;   //位选信号
	reg     [3:0]   data_disp   ;   //当前数码管显示的数据
	reg             dot_disp    ;   //当前数码管显示的小数点

	//data_reg:控制数码管显示数据
	always@(posedge clk or  negedge reset_n)
		if(reset_n == 1'b0)
			data_reg    <=  24'b0;
	//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
		else if((h_tho) || (point[5]))
			data_reg    <=  {h_tho,t_tho,tho,hun,ten,unit};
	//若显示的十进制数的万位为非零数据或需显示小数点,则值显示在5个数码管上
	//打比方我们输入的十进制数据为20’d12345,我们就让数码管显示12345而不是012345
		else if(((t_tho) || (point[4])) && (sign == 1'b1))//显示负号
			data_reg <= {4'd10,t_tho,tho,hun,ten,unit};//4'd10我们定义为显示负号
		else if(((t_tho) || (point[4])) && (sign == 1'b0))
			data_reg <= {4'd11,t_tho,tho,hun,ten,unit};//4'd11我们定义为不显示
	//若显示的十进制数的千位为非零数据或需显示小数点,则值显示4个数码管
		else if(((tho) || (point[3])) && (sign == 1'b1))
			data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
		else if(((tho) || (point[3])) && (sign == 1'b0))
			data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
	//若显示的十进制数的百位为非零数据或需显示小数点,则值显示3个数码管
		else if(((hun) || (point[2])) && (sign == 1'b1))
			data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
		else if(((hun) || (point[2])) && (sign == 1'b0))
			data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
	//若显示的十进制数的十位为非零数据或需显示小数点,则值显示2个数码管
		else if(((ten) || (point[1])) && (sign == 1'b1))
			data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
		else if(((ten) || (point[1])) && (sign == 1'b0))
			data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
	//若显示的十进制数的个位且需显示负号
		else if(((unit) || (point[0])) && (sign == 1'b1))
			data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
	//若上面都不满足都只显示一位数码管
		else
			data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};

	//cnt_1ms:1ms循环计数
	always@(posedge clk or negedge reset_n)
		if(reset_n == 1'b0)
			cnt_1ms <=  16'd0;
		else    if(cnt_1ms == CNT_MAX)
			cnt_1ms <=  16'd0;
		else
			cnt_1ms <=  cnt_1ms + 1'b1;

	//flag_1ms:1ms标志信号
	always@(posedge clk or negedge reset_n)
		if(reset_n == 1'b0)
			flag_1ms    <=  1'b0;
		else    if(cnt_1ms == CNT_MAX - 1'b1)
			flag_1ms    <=  1'b1;
		else
			flag_1ms    <=  1'b0;

	//cnt_sel:从0到5循环数,用于选择当前显示的数码管
	always@(posedge clk or negedge reset_n)
		if(reset_n == 1'b0)
			cnt_sel <=  3'd0;
		else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))	
			cnt_sel <=  3'd0;
		else if(flag_1ms == 1'b1)
			cnt_sel <=  cnt_sel + 1'b1;
		else
			cnt_sel <=  cnt_sel;

	//数码管位选信号寄存器
	always@(posedge clk or negedge reset_n)
		if(reset_n == 1'b0)
			sel_reg <=  6'b000_000;
		else    if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
			sel_reg <=  6'b000_001;
		else    if(flag_1ms == 1'b1)	//每1ms修改一次位选信号
			sel_reg <=  sel_reg << 1;
		else
			sel_reg <=  sel_reg;

	//控制数码管的位选信号,使六个数码管轮流显示
	always@(posedge clk or  negedge reset_n)
		if(reset_n == 1'b0)
			data_disp    <=  4'b0;
		else    if((seg_en == 1'b1) && (flag_1ms == 1'b1))
			case(cnt_sel)
			3'd0:   data_disp    <=  data_reg[3:0]  ;  //给第1个数码管赋个位值
			3'd1:   data_disp    <=  data_reg[7:4]  ;  //给第2个数码管赋十位值
			3'd2:   data_disp    <=  data_reg[11:8] ;  //给第3个数码管赋百位值
			3'd3:   data_disp    <=  data_reg[15:12];  //给第4个数码管赋千位值
			3'd4:   data_disp    <=  data_reg[19:16];  //给第5个数码管赋万位值
			3'd5:   data_disp    <=  data_reg[23:20];  //给第6个数码管赋十万位值
			default:data_disp    <=  4'b0        ;
			endcase
		else
			data_disp   <=  data_disp;

	//dot_disp:小数点低电平点亮,需对小数点有效信号取反
	always@(posedge clk or negedge reset_n)
		if(reset_n == 1'b0)
			dot_disp    <=  1'b1;
		else    if(flag_1ms == 1'b1)
			dot_disp    <=  ~point[cnt_sel];
		else
			dot_disp    <=  dot_disp;

	//控制数码管段选信号,显示数字
	always@(posedge clk or  negedge reset_n)
		if(reset_n == 1'b0)
			seg <=  8'b1111_1111;
		else    
			case(data_disp)									//共阳极,段选低电平有效
				4'd0  : seg  <=  {dot_disp,7'b100_0000};    //显示数字0
				4'd1  : seg  <=  {dot_disp,7'b111_1001};    //显示数字1
				4'd2  : seg  <=  {dot_disp,7'b010_0100};    //显示数字2
				4'd3  : seg  <=  {dot_disp,7'b011_0000};    //显示数字3
				4'd4  : seg  <=  {dot_disp,7'b001_1001};    //显示数字4
				4'd5  : seg  <=  {dot_disp,7'b001_0010};    //显示数字5
				4'd6  : seg  <=  {dot_disp,7'b000_0010};    //显示数字6
				4'd7  : seg  <=  {dot_disp,7'b111_1000};    //显示数字7
				4'd8  : seg  <=  {dot_disp,7'b000_0000};    //显示数字8
				4'd9  : seg  <=  {dot_disp,7'b001_0000};    //显示数字9
				4'd10 : seg  <=  8'b1011_1111          ;    //显示负号
				4'd11 : seg  <=  8'b1111_1111          ;    //不显示任何字符
				default:seg  <=  8'b1100_0000;
			endcase

	//bit:数码管位选信号赋值,位选低电平有效
	always@(posedge clk or negedge reset_n)
		if(reset_n == 1'b0)
			bit <=  6'b000_000;
		else
			bit <=  ~sel_reg;

endmodule

本实验使用的是6位数码管,data_generate模块每1s数字加一,从0~999999,共需要20位二进制数。当把这20位数据传输给数码管动态显示模块时,需要将数据的每一位显示到数码管上,该20位数据不能直接反映出数码管的每一位。使用4位8421码来表示一位十进制数。

bcd8421.v

//数据转换模块,二进制转8421码,方便数码管数据的显示
module bcd8421(
	input 	wire				clk,
	input 	wire				reset_n,
	
	input 	wire	[19:0] 		data,	//要转换的二进制数据,0~999_999需要20bit
	
	//分别输出6位
	output	reg		[3:0] 		unit,	//个位
	output	reg 	[3:0] 		ten,	//十位
	output	reg 	[3:0] 		hun,	//百位
	output	reg 	[3:0] 		tho,	//千位
	output	reg 	[3:0] 		t_tho,	//万位
	output	reg 	[3:0] 		h_tho	//十万位
);
	reg [43:0]	data_all;	//43~20是BCD码,19~0是要转换的二进制数据
							//低20位数据每次左移一位至高24位
	
	reg shift_flag;		//左移标志位
	reg [4:0] shift_cnt;	//移位计数,原来有20位二进制数要左移20次 
	
	//完成一次判断和移位操作后,计数加一
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			shift_cnt <= 5'b0;
		else if((shift_cnt == 5'd21) && (shift_flag == 1'b1))
			shift_cnt <= 5'b0;
		else if(shift_flag == 1'b1)
			shift_cnt <= shift_cnt + 1;
		else
			shift_cnt <= shift_cnt;
		
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			data_all <= 44'b0;
		else if(shift_cnt <= 5'd0)
			data_all <= {24'b0,data};
		else if((shift_cnt <= 5'd20) && (shift_flag == 1'b0))	//判断是否大于4,大于4则加3,即4之后为8
			begin
				data_all[23:20]   <=  (data_all[23:20] > 4) ? (data_all[23:20] + 2'd3) : (data_all[23:20]);
				data_all[27:24]   <=  (data_all[27:24] > 4) ? (data_all[27:24] + 2'd3) : (data_all[27:24]);
				data_all[31:28]   <=  (data_all[31:28] > 4) ? (data_all[31:28] + 2'd3) : (data_all[31:28]);
				data_all[35:32]   <=  (data_all[35:32] > 4) ? (data_all[35:32] + 2'd3) : (data_all[35:32]);
				data_all[39:36]   <=  (data_all[39:36] > 4) ? (data_all[39:36] + 2'd3) : (data_all[39:36]);
				data_all[43:40]   <=  (data_all[43:40] > 4) ? (data_all[43:40] + 2'd3) : (data_all[43:40]);
			end
		else if((shift_cnt <= 5'd20) && (shift_flag == 1'b1))
			data_all <= data_all << 1;
		else
			data_all <= data_all;
	
	always@(posedge clk or negedge reset_n)
		if(!reset_n)
			shift_flag <= 1'b0;
		else 
			shift_flag <= ~shift_flag;	//每次上升沿状态取反,0:大于4加三,1:执行左移操作
			
	//当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值		
	always@(posedge clk or negedge reset_n)
		if(!reset_n) begin
			unit	<= 4'b0;	
			ten		<= 4'b0;	
			hun		<= 4'b0;	
			tho		<= 4'b0;	
			t_tho	<= 4'b0;
			h_tho	<= 4'b0;
		end
		else if(shift_cnt == 5'd21) begin	//shift_cnt等于21时表示所有操作已经完成
			unit	<= data_all[23:20];	
			ten		<= data_all[27:24];	
			hun		<= data_all[31:28];	
			tho		<= data_all[35:32];	
			t_tho	<= data_all[39:36];
			h_tho	<= data_all[43:40];
		end

endmodule

实验结果:从0一直加到999999,1秒加一个数