本帖最后由 Zhao_kar 于 2023-12-9 22:40 编辑
【Tang Primer25K Dock】四——数码管的动态显示测试
备注:
- 前面讲过数码管原理了,本节就讲一下动态显示,然后本节有两个演示:
- 第一个演示是数码管两个管一起显示,从0-9,同时低四位的led也跟着显示二进制值
- 第二个演示是正常的00-99,然后因为演示问题,低位个位是用0.1s,也就是高位是1s,然后8个led用来显示值,高位四个显示十位,低位四个显示个位的值。
- 下一节打算把按键混进来了,两个数码管显示0-99的数值范围,然后四个按键分别实现+1、2、4、8的加数,然后四个拨码开关,分别为rst,1s+1,1s-1,闪烁。
- 后面真的打算画一个数码管模块,起码四个才能显示时钟吧,画完之后,就按照那个模块来实现,然后去做一个数字时钟,也就是测评计划的最后项目。目前我测试下来,感觉比预期想的快,然后我打算做完数字时钟后,有时间的话,移植一下那个软核,我看有人的计划有,暂时没有移植过,比较感兴趣。
一、动态显示的原理
- 前面说过数码管的原理了,先以最常见的多个位选单独控制的数码管分析,其工作状态应该为:假定我有一个四位的数码管,那么控制数码管的显示的信号是四条位选信号sel,如果我想让他全亮,那就是4‘b1111,如果只要第一位,那就是4’b1000,而又因为四个数码管的数值是同一个值,所以要想让四个数码管显示不同的值,我们要使用动态显示。
- 对于1,假如我想让四个数码管显示1234,那么数码管的显示流程应该如下,先让位选选择第一个,也就是第一个数码管亮,此时给的数值为1,然后让位选变成第二个,第二个数码管开始亮,此时给的是值2,以此类推,如果你给这个位选信号是1s,那么效果应该为每隔1s数码管亮一个,而且从第一个到第四个循环。
- 而在Verilog里面的话,那么我应该给一个reg,存1234这四个值,然后因为数码管译码是4位,所以应该用一个16进制,比如4‘h1234,也就是16’0001_0010_0011_0100,然后用一个高频的信号,一个100hz的信号,为什么用这个频率,是因为这个频率以上,四个位选的变化可以说是0.01s变一次,人是看不出来的,你看到的视觉效果会是四个连续的值,因为是四个位选,所以给一个2位的reg,做计数器,从00-11刚好四个状态,四个状态下,位选信号的值都会变化,而且同时给数码管的数据也跟着变,具体见代码。
- 说了那么多,不如直接上代码+注释直观,往下看吧
二、第一个测试的代码+注释+演示
1、代码+注释
module digital_cnt
#(
parameter CNT_1HZ=28'd24_999_999,
parameter CNT_100HZ=28'd49_999
)
(
input clk,
input rst,
output[7:0]led,
output reg [6:0]seg_n,
output sel
);
/***************parameter*************/
localparam P_0 = 7'b0000001 ;
localparam P_1 = 7'b1111001 ;
localparam P_2 = 7'b0010010 ;
localparam P_3 = 7'b0110000 ;
localparam P_4 = 7'b1101000 ;
localparam P_5 = 7'b0100100 ;
localparam P_6 = 7'b0000100 ;
localparam P_7 = 7'b1110001 ;
localparam P_8 = 7'b0000000 ;
localparam P_9 = 7'b0100000 ;
localparam P_X = 7'b1111111 ;
/***************reg*******************/
//分1s时钟
reg [27:0] cnt_1hz;
reg clk_1hz=1'b0;
always @(posedge clk or negedge rst)
if (rst == 1'b0) begin
cnt_1hz <= 28'd0;
end else if (cnt_1hz == CNT_1HZ) begin
cnt_1hz <= 28'd0;
clk_1hz = ~clk_1hz;
end else begin
cnt_1hz <= cnt_1hz + 1'b1;
end
//分100hz的动态扫描时钟
reg [27:0] cnt_100hz;
reg clk_100hz=1'b0;
always @(posedge clk or negedge rst)
if (rst == 1'b0) begin
cnt_100hz <= 28'd0;
end else if (cnt_100hz == CNT_100HZ) begin
cnt_100hz <= 28'd0;
clk_100hz = ~clk_100hz;
end else begin
cnt_100hz <= cnt_100hz + 1'b1;
end
//数码管计数0-9
reg [3:0] data_temp;//中间值
wire [3:0] data;//值
assign data=data_temp;
always@(posedge clk_1hz or negedge rst)
if(rst==1'b0)
data_temp<=4'b0000;
else if(data_temp==4'b1001)begin//如果等于9
data_temp<=4'b0000;
end else
data_temp<=data_temp+1'b1;
//led的计数
reg [3:0] led_out;
assign led=~led_out;
always@(posedge clk_1hz or negedge rst)
if(rst==1'b0)
led_out<=4'b0000;
else if(led_out==4'b1001)
led_out<=4'b0000;
else
led_out<=led_out+1'b1;
//数码管数据选择
always @ (data)
case(data)
4'b0000: seg_n <= P_0;
4'b0001: seg_n <= P_1;
4'b0010: seg_n <= P_2;
4'b0011: seg_n <= P_3;
4'b0100: seg_n <= P_4;
4'b0101: seg_n <= P_5;
4'b0110: seg_n <= P_6;
4'b0111: seg_n <= P_7;
4'b1000: seg_n <= P_8;
4'b1001: seg_n <= P_9;
default: seg_n <= P_X;
endcase
//动态扫描部分
reg sel_temp;
assign sel=sel_temp;
//给一个100hz的时钟,然后因为这个数码管是通过sel的0和1选择左边和右边的值的
//所以把中间值当成计数器,就0、1计数,然后通过这个值控制数码管的左右显示
//然后因为这里没有对数值进行处理,所以效果为两个数码管一起显示一个值。
always@(posedge clk_100hz or negedge rst)
if(rst==1'b0)
sel_temp<=1'b0;
else if(sel_temp==1'b1)
sel_temp<=1'b0;
else
sel_temp<=sel_temp+1'b1;
endmodule
2、演示视频(如下)
测试1
三、第二个效果的代码
1、这一个是从00+到99,8个led的高位低位显示十位和个位的值
2、代码+注释
module digital_cnt
#(
parameter CNT_1HZ=28'd24_999_999,
parameter CNT_10HZ=28'd2_499_999,
parameter CNT_100HZ=28'd49_999
)
(
input clk,
input rst,
output[7:0]led,
output reg [6:0]seg_n,
output reg sel
);
/***************parameter*************/
localparam P_0 = 7'b0000001 ;
localparam P_1 = 7'b1111001 ;
localparam P_2 = 7'b0010010 ;
localparam P_3 = 7'b0110000 ;
localparam P_4 = 7'b1101000 ;
localparam P_5 = 7'b0100100 ;
localparam P_6 = 7'b0000100 ;
localparam P_7 = 7'b1110001 ;
localparam P_8 = 7'b0000000 ;
localparam P_9 = 7'b0100000 ;
localparam P_X = 7'b1111111 ;
/***************reg*******************/
/*分频0.1s部分*/
reg [27:0] cnt_10hz;
reg clk_10hz=1'b0;
always @(posedge clk or negedge rst)
if (rst == 1'b0) begin
cnt_10hz <= 28'd0;
end else if (cnt_10hz == CNT_10HZ) begin
cnt_10hz <= 28'd0;
clk_10hz = ~clk_10hz;
end else begin
cnt_10hz <= cnt_10hz + 1'b1;
end
/*分频1s部分*/
reg [27:0] cnt_1hz;
reg clk_1hz=1'b0;
always @(posedge clk or negedge rst)
if (rst == 1'b0) begin
cnt_1hz <= 28'd0;
end else if (cnt_1hz == CNT_1HZ) begin
cnt_1hz <= 28'd0;
clk_1hz = ~clk_1hz;
end else begin
cnt_1hz <= cnt_1hz + 1'b1;
end
/*分频100hz,动态扫描部分*/
reg [27:0] cnt_100hz;
reg clk_100hz=1'b0;
always @(posedge clk or negedge rst)
if (rst == 1'b0) begin
cnt_100hz <= 28'd0;
end else if (cnt_100hz == CNT_100HZ) begin
cnt_100hz <= 28'd0;
clk_100hz = ~clk_100hz;
end else begin
cnt_100hz <= cnt_100hz + 1'b1;
end
/*数码管计数0-9*/
reg [3:0] data_temp_L,data_temp_H;
reg [3:0] data;//值
//下面为了显示效果,没有取1s的时钟,用的是10hz的,这样子高位就是1s的变化。
always@(posedge clk_10hz or negedge rst)
if(rst==1'b0)begin
data_temp_L <= 4'b0000;
data_temp_H <= 4'b0000;
end else if(data_temp_L == 4'b1001)begin//如果等于9
data_temp_L <= 4'b0000;
data_temp_H <= data_temp_H + 1'b1; //如果低位计满时,高位先加1
end else if(data_temp_H == 4'b1010)begin
data_temp_H <= 4'b0000;
end else
data_temp_L <= data_temp_L + 1'b1;//低位根据时钟的变化+1
//led的计数,以低四位表述个位的数值,高四位表述十位的值
wire [3:0] led_out_L;
wire [3:0] led_out_H;
assign led_out_H = data_temp_H;
assign led_out_L = data_temp_L;
assign led = ~{led_out_H,led_out_L};
//数码管数据选择
always @ (data)
case(data)
4'b0000: seg_n <= P_0;
4'b0001: seg_n <= P_1;
4'b0010: seg_n <= P_2;
4'b0011: seg_n <= P_3;
4'b0100: seg_n <= P_4;
4'b0101: seg_n <= P_5;
4'b0110: seg_n <= P_6;
4'b0111: seg_n <= P_7;
4'b1000: seg_n <= P_8;
4'b1001: seg_n <= P_9;
default: seg_n <= P_X;
endcase
//动态扫描电路
wire cnt_digital;
reg cnt_digital_temp;
assign cnt_digital = cnt_digital_temp;
//100hz的动态扫描时钟,然后使用一个计数器,因为这里只有一个位选,所以计数器是1位的,只能从0到1
always@(posedge clk_100hz or negedge rst)
if(rst == 1'b0)
cnt_digital_temp <= 1'b0;
else if(cnt_digital_temp == 1'b1)
cnt_digital_temp <= 1'b0;
else
cnt_digital_temp <= cnt_digital_temp + 1'b1;
//通过计数器的值,确定两个状态
//状态1选择第2个数码管,显示低位的数据
//状态2选择第一个数码管,显示高位的数据
//动态显示部分就两个,一个是动态扫描时钟,一个是case的处理
always@(cnt_digital)
case(cnt_digital)
1'b0:begin sel = 1'b0; data = data_temp_L; end
1'b1:begin sel = 1'b1; data = data_temp_H; end
default: begin sel = 1'b0; data = 4'b0000; end
endcase
endmodule
3、视频演示
测试2
四、cst文件,约束文件,两个都是一模一样的
//Copyright (C)2014-2023 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Physical Constraints file
//GOWIN Version: V1.9.9 Beta-5
//Part Number: GW5A-LV25MG121NES
//Device: GW5A-25
//Device Version: A
//Created Time: Fri 12 08 18:00:14 2023
IO_LOC "sel" K5;
IO_PORT "sel" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[6]" L11;
IO_PORT "seg_n[6]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[5]" K11;
IO_PORT "seg_n[5]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[4]" L5;
IO_PORT "seg_n[4]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[3]" E10;
IO_PORT "seg_n[3]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[2]" E11;
IO_PORT "seg_n[2]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[1]" A11;
IO_PORT "seg_n[1]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "seg_n[0]" A10;
IO_PORT "seg_n[0]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[7]" C10;
IO_PORT "led[7]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[6]" C11;
IO_PORT "led[6]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[5]" B10;
IO_PORT "led[5]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[4]" B11;
IO_PORT "led[4]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[3]" D10;
IO_PORT "led[3]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[2]" D11;
IO_PORT "led[2]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[1]" G10;
IO_PORT "led[1]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "led[0]" G11;
IO_PORT "led[0]" PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3;
IO_LOC "rst" F5;
IO_PORT "rst" PULL_MODE=NONE DRIVE=OFF BANK_VCCIO=3.3;
IO_LOC "clk" E2;
IO_PORT "clk" PULL_MODE=NONE DRIVE=OFF BANK_VCCIO=3.3;