• FPGA
  • FPGA简易开发教程:数码管相关(二)

4 数码管电子时钟

前篇介绍了FPGA数码管的静态显示与动态显示,本篇将在动态显示的基础上,用FPGA实现电子时钟的功能。

4.1 实现的功能

所用的开发板只有四个数码管,无法做到同时显示时分秒,因此忽略秒的变化,表现为HH.MM的电子时钟。且可根据拨码开关来手动校准当前时间。


4.2 设计思路

程序框图与端口信号

Port nameDirectionTypeDescription
clk_24minputwire24MHz时钟
rstninputwire复位
time_setinputwire[12:0]可设定的时间
sm_segoutputwire[7:0]数码管段选
sm_bitoutputwire[3:0]数码管位选
led_13outputwire[12:0]指示led灯

确定计数范围

每过一分钟数码管发生一次跳变,因此主计数器需要精准计数满1min,具体计算如下:开发板提供了一个24MHz的晶振,即时钟周期为(1000/24)ns,计数1min即计数6×1010ns,需要计数6×1010÷(1000/24)=1.44×109,即计数范围0~1_440_000_000-1。

进位设置

当计数满1min时,显示分钟的数码管+1;当计数满60min时,显示分钟的数码管归零,显示时钟的数码管+1;当计数满24h时,显示分钟、时钟的数码管都归零。分钟个位计数0~9,分钟十位计数0~5;时钟个位计数0~9,时钟十位计数02,且时钟计数最大值为23。这里需要注意时钟的进位和普通十进制的不同之处。

位选和段选

与动态显示类似,不过第三个数码管(时钟个位)始终点亮小数点,用于区分时钟和分钟。

手动校准时间

计数器模块counter.v的程序设计中,其每一位的复位的语句不同于动态显示的赋予0,而是将拨码开关输入的当前时间赋予各位。

引脚分配文件seg_clock.adc中,根据每一位的最大计数值,恰好把16个拨码开关分为四组。上拨开关后,按下复位键,数码管即显示设置的时间,并以此时间开始计数。为了更加清晰,还点亮了可调节开关对应的LED灯。


4.3 程序设计

计数器模块counter.v

module counter_hour_min
#(
	//时钟频率24MHz,则时钟周期(1000/24)ns,则计数一分钟需要60_000_000_000÷(1000/24)=1_440_000_000
	parameter CNT_TIME		=1_440_000_000-1,		//计数一分钟
	parameter N				=$clog2(CNT_TIME)		//即计算log2(12_000_000),向上取整,为24
)
(
	input  wire			clk_24m,
	input  wire			rstn,
	input  wire[12:0]	time_set,
	output wire[12:0]	time_out
);

//计数器
reg  [N-1:0] 	cnt;
reg  [3:0]		sm_bit1_num;	//分钟个位,0~9
reg  [2:0]		sm_bit2_num;	//分钟十位,0~5
reg  [3:0]		sm_bit3_num;	//时钟个位,0~9,0~3
reg  [1:0]		sm_bit4_num;	//时钟十位,0~2


//——————————————————————————————主计数器,计数1min——————————————————————————————
always@(posedge clk_24m or negedge rstn) begin
	if(~rstn)
		cnt			<=0;
	else if(cnt==CNT_TIME)
		cnt			<=0;
	else
		cnt			<=cnt + 1'b1;
end

//——————————————————————————————各位的计数逻辑——————————————————————————————
assign time_out		={sm_bit4_num, sm_bit3_num, sm_bit2_num, sm_bit1_num};
//分钟个位
always@(posedge clk_24m or negedge rstn) begin
	if(~rstn)
		sm_bit1_num			<=time_set[3:0];
	else if(cnt==CNT_TIME) begin
		if(sm_bit1_num==9)
			sm_bit1_num		<=0;
		else
			sm_bit1_num		<=sm_bit1_num + 1'b1;
	end
	//else保持,省略
end

//分钟十位
always@(posedge clk_24m or negedge rstn) begin
	if(~rstn)
		sm_bit2_num			<=time_set[6:4];
	else if(cnt==CNT_TIME && sm_bit1_num==9) begin
		if(sm_bit2_num==5)
			sm_bit2_num		<=0;
		else 
			sm_bit2_num		<=sm_bit2_num + 1'b1;
	end
	//else保持,省略
end	

//时钟个位
always@(posedge clk_24m or negedge rstn) begin
	if(~rstn)
		sm_bit3_num			<=time_set[10:7];
	else if(cnt==CNT_TIME && sm_bit2_num==5 && sm_bit1_num==9)begin
		if(sm_bit3_num==9 || sm_bit4_num==2 && sm_bit3_num==3)
			sm_bit3_num		<=0;
		else
			sm_bit3_num		<=sm_bit3_num + 1'b1;
	end
	//else保持,省略
end

//时钟十位
always@(posedge clk_24m or negedge rstn) begin
	if(~rstn)
		sm_bit4_num			<=time_set[12:11];
	else if(cnt==CNT_TIME && sm_bit2_num==5 && sm_bit1_num==9)begin
		if(sm_bit4_num==2 && sm_bit3_num==3)
			sm_bit4_num		<=0;
		else if(sm_bit3_num==9)
			sm_bit4_num		<=sm_bit4_num + 1'b1;
	end
	//else保持,省略
end
	
endmodule

数码管驱动模块seg_driver.v

module seg_driver(
	input	wire		clk_24m,
	input	wire		rstn,
	input	wire [12:0]	time_out,
    output	wire [7:0]	sm_seg,		//数码管段选
    output	wire [3:0]	sm_bit		//数码管位选
);

reg [15:0] 	cnt_w;
reg [3:0] 	sm_seg_num;		//具体数字
reg [7:0]	sm_seg_reg;		//数码管段选,由具体数字译码而来
reg [3:0] 	sm_bit_reg;		//数码管位选
reg 		dp;				//小数点,用作分钟与时钟的分界
localparam
			S0 = 4'b0000 ,
			S1 = 4'b0001 ,
			S2 = 4'b0010 ,
			S3 = 4'b0011 ,
			S4 = 4'b0100 ,
			S5 = 4'b0101 ,
			S6 = 4'b0110 ,
			S7 = 4'b0111 ,
			S8 = 4'b1000 ,
			S9 = 4'b1001 ;	

//———————————————————————————————————扫描计数器——————————————————————————————————
//cnt_w也是计数器,扫描计数要小于个位数据的变化就行。

always@(posedge clk_24m or negedge rstn) begin
	if(~rstn)
		cnt_w		<=0;
	else if(&cnt_w) 		//按位与
		cnt_w		<=0;		//计数到最大就清零。感觉也可以省略。
	else
		cnt_w		<=cnt_w + 1'b1;
end

//sm_seg_num用于显示具体的数据
always@(posedge clk_24m or negedge rstn) begin
	if(~rstn) begin		//这两行自定义复位时哪几个会亮,亮的显示什么
		sm_bit_reg		<=4'b1111;
		sm_seg_num		<=0;
	end
	else begin
		case( cnt_w[15:14] )
			2'b00:		begin		//最高两位为00,则点亮第一个管子,且具体数字由sm_bit1_num决定
							sm_bit_reg		<=4'b1110;
							sm_seg_num		<=time_out[3:0];
							dp				<=1;
						end
			
			2'b01:		begin
							sm_bit_reg		<=4'b1101;
							sm_seg_num		<=time_out[6:4];
							dp				<=1;
						end

			2'b10:		begin
							sm_bit_reg		<=4'b1011;
							sm_seg_num		<=time_out[10:7];
							dp				<=0;
						end

			2'b11:		begin
							sm_bit_reg		<=4'b0111;
							sm_seg_num		<=time_out[12:11];
							dp				<=1;
						end
		endcase
	end
end

//控制每一位的显示数字
always@(*) begin	//共阳极数码管,最高位是小数点,低七位是七段管。0为亮,1为暗。
	case ( sm_seg_num )
		S0:		sm_seg_reg		={dp, 7'b100_0000};
		S1:		sm_seg_reg		={dp, 7'b111_1001};
		S2:		sm_seg_reg		={dp, 7'b010_0100};
		S3:		sm_seg_reg		={dp, 7'b011_0000};
		S4:		sm_seg_reg		={dp, 7'b001_1001};
		S5:		sm_seg_reg		={dp, 7'b001_0010};
		S6:		sm_seg_reg		={dp, 7'b000_0010};
		S7:		sm_seg_reg		={dp, 7'b111_1000};
		S8:		sm_seg_reg		={dp, 7'b000_0000};
		S9:		sm_seg_reg		={dp, 7'b001_0000};
		default:sm_seg_reg		={dp, 7'b100_0000};
	endcase
end
					
assign sm_seg		=sm_seg_reg;
assign sm_bit		=sm_bit_reg;
	
endmodule

指示灯模块led.v

module led_indicate(
	output wire[12:0]	led_13
);

assign led_13		=0;		//指示可供操作的4组共13个led(2+4+3+4)

endmodule

顶层模块top_seg_clock.v

module top_seg_clock
#(
	// parameter CNT_TIME = 1_440_000_000-1		//计数一分钟
	// parameter CNT_TIME = 24_000_000-1		//计数一秒钟
	parameter CNT_TIME = 1_440_000_000-1,		//计数一分钟
	//  parameter CNT_TIME = 300_000-1		//模拟的,快一点
	parameter N			=$clog2(CNT_TIME)
)
( 
	input  wire			clk_24m,
	input  wire			rstn,
	input  wire[12:0]	time_set,
	output wire[7:0]	sm_seg,     //数码管段选
	output wire[3:0]	sm_bit,     //数码管位选
	output wire[12:0]	led_13
);

wire [12:0]	time_out;


led_indicate  led_indicate_inst (
    .led_13				(led_13)
);

counter_hour_min #(
	.CNT_TIME			(CNT_TIME)
  )
  counter_hour_min_inst (
	.time_set			(time_set),
	.clk_24m			(clk_24m),
	.rstn				(rstn),
	.time_out			(time_out)
);

seg_driver  seg_driver_inst (
	.clk_24m			(clk_24m),
	.rstn				(rstn),
	.time_out			(time_out),
	.sm_seg				(sm_seg),
	.sm_bit				(sm_bit)
);

endmodule

引脚分配文件seg_clock.adc

#时钟
set_pin_assignment	{ clk_24m }	{ LOCATION = K14; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }

#复位
set_pin_assignment	{ rstn }	{ LOCATION = G11; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }

#13个LED灯
set_pin_assignment	{ led_13[0] }	{ LOCATION = T13; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[1] }	{ LOCATION = T12; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[2] }	{ LOCATION = R12; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[3] }	{ LOCATION = M7; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[4] }	{ LOCATION = T8; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[5] }	{ LOCATION = T7; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[6] }	{ LOCATION = R7; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[7] }	{ LOCATION = N5; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[8] }	{ LOCATION = P4; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[9] }	{ LOCATION = M5; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[10] }	{ LOCATION = N4; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[11] }	{ LOCATION = M4; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ led_13[12] }	{ LOCATION = M3; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }

#数码管位选
set_pin_assignment	{ sm_bit[0] }	{ LOCATION = B1; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_bit[1] }	{ LOCATION = C3; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_bit[2] }	{ LOCATION = C2; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_bit[3] }	{ LOCATION = F3; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }

#数码管段选
set_pin_assignment	{ sm_seg[0] }	{ LOCATION = E3; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[1] }	{ LOCATION = B3; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[2] }	{ LOCATION = F4; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[3] }	{ LOCATION = E4; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[4] }	{ LOCATION = F5; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[5] }	{ LOCATION = D3; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[6] }	{ LOCATION = B2; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment	{ sm_seg[7] }	{ LOCATION = A2; IOSTANDARD = LVCMOS25; DRIVESTRENGTH = 8; PULLTYPE = NONE; }

#13个拨码开关
set_pin_assignment	{ time_set[0] }	{ LOCATION = R15; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[1] }	{ LOCATION = T15; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[2] }	{ LOCATION = R14; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[3] }	{ LOCATION = T14; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[4] }	{ LOCATION = P9; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[5] }	{ LOCATION = R9; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[6] }	{ LOCATION = N8; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[7] }	{ LOCATION = N6; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[8] }	{ LOCATION = P6; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[9] }	{ LOCATION = M6; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[10] }	{ LOCATION = T6; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[11] }	{ LOCATION = R5; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }
set_pin_assignment	{ time_set[12] }	{ LOCATION = T4; IOSTANDARD = LVCMOS25; PULLTYPE = PULLUP; }

4.4 上板验证

烧录至开发板

在TD5.6.5中新建工程文件,导入四个设计文件,导入引脚文件,编译后将生成的比特流下载到开发板中。具体步骤可参考基于TD5.6.5与vscode的FPGA简易开发教程——以多路选择器为例

观察进位

将顶层模块top_seg_clock.v文件中的parameter CNT_TIME = 1_440_000_000-1修改为其他更小的值,烧录,观察时钟、分钟的进位是否符合电子时钟的逻辑。

手动校准时间

CNT_TIME改回原值,重新烧录。根据当前北京时间,调节四组拨码开关的值,长按复位键,当北京时间为整分钟时松开复位键,数码管即显示当前时间。

长时间内多次对比记录,观察该电子时钟是否准确显示北京时间。

如下图,设定时间为01:57,按下复位键,数码管准确显示01.57。

如下图,过了小半个小时再来看一眼,仍准确吻合北京时间。

落落加油努力 将标题更改为 「FPGA简易开发教程:数码管相关(二)」。
说点什么吧...

qZMtmT.png网站备案号:ICP备16046599号-1