一、前置知识
数码管是将若干发光二极管按一定图形排列并封装在一起的一种数码显示器件。比较常见的是八段数码管,即内部为8个二极管按共阳极或共阴极的方式排列制作的。
另外,生产商为了统一封装,会将8段数码管引出10个引脚,其中有两个引脚是公共脚 com。
静态显示是指将多个数码管的段选相连,位选也相同。这样多个数码管就可以同时显示相同的内容。
动态显示是指利用视觉暂留,在短时间内控制数码管位选,让多个数码管显示不同的内容。
二、原理图分析
为了增加 FPGA 输出信号的驱动能力,开发板使用 PNP 型三极管驱动数码管的位选段,所以给三极管基
极提供低电平时,三极管导通,位选信号为高电平。另外,开发板的数码管是共阳极数码管。
实例化两个模块,一个是用于计时0.5秒的定时器(time_count),每到0.5秒产生一个flag脉冲信号。另一个模块(seg_show_static)用来控制数码管显示的具体数值。
三、代码编写
3.1 计时模块
期望效果:每隔0.5秒产生一个脉冲信号。
Modelsim仿真效果(每隔20个clk产生一次flag脉冲):
// ----------------------------------
// 计时模块:每隔0.5秒产生一个flag脉冲信号
// ----------------------------------
module test(
input clk,
input rst_n,
output reg flag
);
//parameter TIME = 25_000_000;
parameter TIME = 20;
reg [4:0] counter;
// 每隔0.5秒产生一个flag脉冲信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flag <= 1'b0;
counter <= 5'd0;
end
else begin
if(counter >= TIME) begin
flag <= 1'b1;
counter <= 5'd0;
end
else begin
flag <= 1'b0;
counter <= counter + 1'b1;
end
end
end
endmodule
`timescale 1 ns/ 1 ns
module test_vt();
// test vector input registers
reg clk;
reg rst_n;
// wires
wire flag;
// assign statements (if any)
test i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.flag(flag),
.rst_n(rst_n)
);
initial begin
clk = 1'b0;
rst_n = 1'b0;
#10 rst_n = 1'b1;
#1000 $stop;
end
always #10 clk = ~clk;
endmodule
3.2 数码管静态显示模块
// -------------------------
// 控制数码管段选位选的模块
// -------------------------
module seg_show_static(
input clk,
input rst_n,
input add_flag,
output reg [5:0] sel,
output reg [6:0] seg_led
);
reg [3:0] num; // 0-F
// 控制位选
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
sel <= 6'b11_1111; // 位选禁用
else
sel <= 6'b00_0000; // 位选全部选中
end
// 控制数码管显示的数据:每来一个flga脉冲就为num加一
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
num <= 4'd0;
end
else begin
if(add_flag)
if(num > 4'hf)
num <= 4'd0;
else
num <= num + 1'b1;
else
num <= num;
end
end
// 控制数码管段选
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
seg_led <= 7'b1111111;
else begin
case (num)
5'h0: seg_led <= 7'b1_000_000;
5'h1: seg_led <= 7'b1_111_001;
5'h2: seg_led <= 7'b0_100_100;
5'h3: seg_led <= 7'b0_110_000;
5'h4: seg_led <= 7'b0_011_001;
5'h5: seg_led <= 7'b0_010_010;
5'h6: seg_led <= 7'b0_000_010;
5'h7: seg_led <= 7'b1_111_000;
5'h8: seg_led <= 7'b0_000_000;
5'h9: seg_led <= 7'b0_010_000;
5'hA: seg_led <= 7'b0_001_000;
5'hB: seg_led <= 7'b0_000_011;
5'hC: seg_led <= 7'b0_100_111;
5'hD: seg_led <= 7'b0_100_001;
5'hE: seg_led <= 7'b0_000_110;
5'hF: seg_led <= 7'b0_001_110;
default: seg_led <= 7'b1111111;
endcase
end
end
endmodule
四、代码仿真
计时器模块的代码已经仿真过了,下面将顶层模块seg_led_static编写完成,并编写相应的激励文件。
//---------------------- Copyright (c) ----------------------
// Copyright(C) ZHJ0125 2022
// All rights reserved
//-----------------------------------------------------------
// File Name : seg_led_static.v
// Created Date : 2022-04-21 13:32
// Modified Date : 2022-04-25 12:40
// Created By : ZHJ0125
// Last Version : V0.1
// Descriptions : 数码管静态显示
// 开发板上电后数码管显示0~F,以此循环。
//-----------------------------------------------------------
module seg_led_static(
input clk,
input rst_n,
output [5:0] sel,
output [6:0] seg_led
);
wire add_flag;
time_count u_time_count(
.clk(clk),
.rst_n(rst_n),
.flag(add_flag)
);
seg_show_static u_seg_show_static(
.clk(clk),
.rst_n(rst_n),
.add_flag(add_flag),
.sel(sel),
.seg_led(seg_led)
);
endmodule
// ----------------------------------
// 计时模块:每隔0.5秒产生一个flag脉冲信号
// ----------------------------------
module time_count(
input clk,
input rst_n,
output reg flag
);
//parameter TIME = 25_000_000; // 0.5s
parameter TIME = 10; // 仿真时使用
reg [24:0] counter;
// 每隔0.5秒产生一个flag脉冲信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flag <= 1'b0;
counter <= 25'd0;
end
else begin
if(counter >= TIME) begin
flag <= 1'b1;
counter <= 25'd0;
end
else begin
flag <= 1'b0;
counter <= counter + 1'b1;
end
end
end
endmodule
// -------------------------
// 控制数码管段选位选的模块
// -------------------------
module seg_show_static(
input clk,
input rst_n,
input add_flag,
output reg [5:0] sel,
output reg [6:0] seg_led
);
reg [3:0] num; // 0-F
// 控制位选
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
sel <= 6'b11_1111; // 位选禁用
else
sel <= 6'b00_0000; // 位选全部选中
end
// 控制数码管显示的数据:每来一个flga脉冲就为num加一
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
num <= 4'd0;
end
else begin
if(add_flag)
if(num > 4'hf)
num <= 4'd0;
else
num <= num + 1'b1;
else
num <= num;
end
end
// 控制数码管段选
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
seg_led <= 7'b1111111;
else begin
case (num)
5'h0: seg_led <= 7'b1_000_000;
5'h1: seg_led <= 7'b1_111_001;
5'h2: seg_led <= 7'b0_100_100;
5'h3: seg_led <= 7'b0_110_000;
5'h4: seg_led <= 7'b0_011_001;
5'h5: seg_led <= 7'b0_010_010;
5'h6: seg_led <= 7'b0_000_010;
5'h7: seg_led <= 7'b1_111_000;
5'h8: seg_led <= 7'b0_000_000;
5'h9: seg_led <= 7'b0_010_000;
5'hA: seg_led <= 7'b0_001_000;
5'hB: seg_led <= 7'b0_000_011;
5'hC: seg_led <= 7'b0_100_111;
5'hD: seg_led <= 7'b0_100_001;
5'hE: seg_led <= 7'b0_000_110;
5'hF: seg_led <= 7'b0_001_110;
default: seg_led <= 7'b1111111;
endcase
end
end
endmodule
激励文件也很好写,如下:
// *****************************************************************************
// This file contains a Verilog test bench template that is freely editable to
// suit user's needs .Comments are provided in each section to help the user
// fill out necessary details.
// *****************************************************************************
// Generated on "04/25/2022 09:27:52"
// Verilog Test Bench template for design : seg_led_static
//
// Simulation tool : ModelSim-Altera (Verilog)
//
`timescale 1 ns/ 1 ns
module seg_led_static_vt();
// constants
// general purpose registers
// reg eachvec;
// test vector input registers
reg clk;
reg rst_n;
// wires
wire [6:0] seg_led;
wire [5:0] sel;
parameter T = 20;
// assign statements (if any)
seg_led_static i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.rst_n(rst_n),
.seg_led(seg_led),
.sel(sel)
);
initial begin
clk = 1'b0;
rst_n = 1'b0;
#(5*T)
rst_n = 1'b1;
#(200*T) $stop;
end
always begin
#(T/2) clk = ~clk;
end
endmodule
激励实现的功能是:
- 初始化时钟和复位信号
- 5个周期后复位拉高,开始执行程序
- 每个周期让时钟信号clk以50%占空比产生脉冲信号
- 200个周期后停止仿真过程
仿真得到的时序图如下:
在仿真波形图中可以看到:
package require ::quartus::project
system clock:50Mhz
set_location_assignment PIN_M2 -to sys_clk
system reset
set_location_assignment PIN_M1 -to sys_rst_n
digit display
set_location_assignment PIN_N16 -to sel[0] set_location_assignment PIN_N15 -to sel[1] set_location_assignment PIN_P16 -to sel[2] set_location_assignment PIN_P15 -to sel[3] set_location_assignment PIN_R16 -to sel[4] set_location_assignment PIN_T15 -to sel[5]
set_location_assignment PIN_M11 -to seg_led[0] set_location_assignment PIN_N12 -to seg_led[1] set_location_assignment PIN_C9 -to seg_led[2] set_location_assignment PIN_N13 -to seg_led[3] set_location_assignment PIN_M10 -to seg_led[4] set_location_assignment PIN_N11 -to seg_led[5] set_location_assignment PIN_P11 -to seg_led[6]
set_location_assignment PIN_D9 -to seg_led[7]
```
烧录后的效果:
没想到居然会出问题,仿真的效果符合预期,到了板子上居然不能正常运行。
检查一下引脚配置,系统时钟和系统复位引脚没有被正确配置。因为TCL脚本里的引脚名称跟我代码里的引脚名称不一致。
修改TCL脚本,重新运行,引脚配置正常了。。
重新编译烧录,发现只显示数字8:
再检查一下代码,是因为为了方便仿真,把产生flag脉冲的计时时间改成10了,这样的话变化间隔时间太短。重新改正后再次烧录。