前言
闲来无事,为2025年蓝桥杯第一次的FPGA赛程做准备,也为挤身成为FPGA工程师做准备。
这次的学习例程是:时钟、开关控制动态数码管,使之完成计时工作。
一、数码管的使用
首先需要了解的是动态数码管的使用方式。在Proteus中,动态数码管7SEG-MPx8-CA-BLUE的样子如图1所示。7SAG代表着是7个液晶显示段A~G、MPx8代表着有8个数码管位COM1~8、CA代表共阳极数码管、BLUE就是底色为蓝色。
当对A~G引脚输入低电平0时,数码管对应的位置点亮(共阴极数码管则是高电平1点亮),即向A~G引脚分别输入0000001时,数码管显示数字‘0’;输入0000000时,数码管显示数字‘8’。DP引脚指示的是小数点,用法和数码管的字段相同。这种对单个数码管内容的编辑称作“段选”。
当对COM1~COM8引脚输入高电平1时,整个数码管中对应位置的数码管将显示A~G引脚中输入的内容(共阴极数码管则是低电平0控制),即向COM1~COM8引脚输入0x01、段选0x03(加小数点段,显示数字‘0’)时,第8位数码管将显示数字‘0’;输入0x11时、段选0x03时,第4位和第8位数码管将显示数字‘0’。这种控制数码管显示位置的编辑称作“位选”。
由此不难发现,这种数码管虽然能够一次显示多个位置的数码管,但一次只能显示一种内容,当需要在数码管行中显示诸如“01234567”一连串的不同的内容时,这种数码管就需要别的知识进行外加了,组成动态数码管。
动态数码管的工作原理与视频的原理类似,即利用光在人眼中的视觉残影:图像在1/24秒之内变换,人眼无法快速反应,致使能够看到连贯的视频。动态数码管是将不同位选与段选组合,并利用高频的变换,使人能够看到不同的数码位上显示不同的内容。比如将位选0x01、段选0x03与位选0x02、段选0x01组合,以1kHz的变换频率进行变换,在数码管中就能显示数字80了。
变换频率的选择也有讲究:变换频率不能够太快,容易出现显示重叠的现象,比如需要显示数字25,变换频率过高就容易出现显示88的情况——前一个的显示还没完全被清楚,下一个的显示就跟上了。变换频率太慢了,就会产生明显的“高频闪烁”的现象,让别人看着不舒服。
二、Verilog HDL的实现
在程序的头部进行输入输出变量的定义。数字硬件电路中最重要的部件之一就是时钟信号,clk变量就是输入的高频时钟信号。dig是6位的位选输出信号,seg是8位的段选输出信号,key是8位的开关输入信号。counter是计数器,与时钟信号相关,count是计时的当前时间记录。
module FPGA_1
(
input wire clk,
output reg [5:0] dig,
output reg [7:0] seg,
input wire [7:0] key
);
reg [25:0] counter;
reg [20:0] count;
随后定义部分常量,包括段选的内容、位选的内容。由于我的FPGA开发板的数码管是共阴极数码管,因此这里都是共阴极的显示状态。
localparam seg_0 = 8'b11111100;
localparam seg_1 = 8'b01100000;
localparam seg_2 = 8'b11011010;
localparam seg_3 = 8'b11110010;
localparam seg_4 = 8'b01100110;
localparam seg_5 = 8'b10110110;
localparam seg_6 = 8'b10111110;
localparam seg_7 = 8'b11100000;
localparam seg_8 = 8'b11111110;
localparam seg_9 = 8'b11110110;
localparam dig_1 = 6'b111110;
localparam dig_2 = 6'b111101;
localparam dig_3 = 6'b111011;
localparam dig_4 = 6'b110111;
localparam dig_5 = 6'b101111;
localparam dig_6 = 6'b011111;
localparam dig_0 = 6'b111111;
localparam seg_p = 8'b00000001;
接着是计数环节。由于我这个开发板的时钟信号为50MHz,因此计数0~49999999正好是50M次约1秒,此时计数器需要清零,且计时+1。
always @(posedge clk)
begin
counter <= counter + 1;
if (counter == 49_999_999)
begin
counter <= 0;
count <= (count + 1) % key;
end
得到当前的时间后,需要进行内容的输出,这里是动态数码的分位显示。
case (counter / 10 % 3)
0:begin
dig <= dig_1;
case (count % 10)
0: seg = seg_0;
1: seg = seg_1;
2: seg = seg_2;
3: seg = seg_3;
4: seg = seg_4;
5: seg = seg_5;
6: seg = seg_6;
7: seg = seg_7;
8: seg = seg_8;
9: seg = seg_9;
endcase
end
1:begin
dig <= dig_2;
case (count / 10 % 10)
0: seg = seg_0;
1: seg = seg_1;
2: seg = seg_2;
3: seg = seg_3;
4: seg = seg_4;
5: seg = seg_5;
6: seg = seg_6;
7: seg = seg_7;
8: seg = seg_8;
9: seg = seg_9;
endcase
end
2:begin
dig <= dig_3;
case (count / 100 % 10)
0: seg = seg_0;
1: seg = seg_1;
2: seg = seg_2;
3: seg = seg_3;
4: seg = seg_4;
5: seg = seg_5;
6: seg = seg_6;
7: seg = seg_7;
8: seg = seg_8;
9: seg = seg_9;
endcase
end
endcase
end
endmodule
由于硬件描述语言与普通的Python、Java等高级语言不同,硬件描述语言的程序直接影响开发板中的门电路连接,因此长程序必须要经过优化(简化),尽量减少对可编辑门电路的使用。