一、 引言
1、介绍计数器的概念及应用
计数器是一种数字电路,用于对事件或脉冲进行计数。它可以跟踪数量的增减,通常以二进制或十进制的形式表示。计数器在电子设备和计算机系统中具有广泛的应用,
2、DE2-115 开发板的简介
DE2-115 开发板是 Altera(现为英特尔)推出的一款功能强大的 FPGA 开发平台,专用于数字系统设计与开发。它适合教育、学习和工程原型开发,以下是该开发板的一些主要特点和功能:
主要特点
-
FPGA 芯片:
-
DE2-115 开发板搭载了 Altera 的 EP4CE115 FPGA 芯片,这是 Cyclone IV 系列的一部分,具有 115K 逻辑单元(LEs)。
-
支持丰富的 I/O 引脚可用于各种接口和外设。
-
-
丰富的 I/O 接口:
-
提供多种数字和模拟 I/O 连接,例如 LED、开关、七段显示器、声音输出(DAC)、摄像头接口等。
-
配备多个 USB 接口,方便与计算机进行数据传输和编程。
-
-
内存资源:
-
板载了 SDRAM(64MB)和 Flash 存储器(4MB),可用于存储数据和程序。
-
支持 EEPROM 存储,用于保存配置信息。
-
-
时钟源:
-
内置时钟源(50 MHz)和多个可选时钟输入,供设计中使用。
-
支持外部扩展时钟源。
-
-
模拟输入输出(ADC/DAC):
-
板上集成了音频 DAC,用于音频处理应用。
-
提供模拟输入输出接口,便于传感器和其他模拟设备连接。
-
3、项目的目的和目标
在DE2-115板子上用 Verilog编程实现一个 分秒计数器,并具备按键暂停、按键消抖功能
二、 项目准备
硬件需求
DE2-115 开发板连接线
外部按键模块
软件需求
Quartus 软件
模拟工具(如 ModelSim)
三、分秒计数器的 Verilog 设计
设计模块结
构输入/输出端口定义
输入端口:
clk
:系统时钟信号 (50MHz)。
raw_reset
:复位信号,低电平有效。
raw_start_pause
:启动/暂停信号,低电平有效。
输出端口:
hex3
:分钟十位的七段显示输出。
hex2
:分钟个位的七段显示输出,带小数点。
hex1
:秒十位的七段显示输出。
hex0
:秒个位的七段显示输出。
示例代码
module minute_second_counter (
input wire clk, // 50MHz输入时钟
input wire raw_reset, // 原始复位信号(高电平有效)
input wire raw_start_pause, // 启动/暂停按键(高电平有效)
output reg [7:0] hex3, // 分钟十位(HEX3)
output reg [7:0] hex2, // 分钟个位(带小数点,HEX2)
output reg [7:0] hex1, // 秒十位(HEX1)
output reg [7:0] hex0 // 秒个位(HEX0)
);
计数器逻辑实现
秒钟计数器
实现秒钟计数器逻辑,当秒钟达到 59 时,自动归零并增加分钟计数
分钟计数器
分钟计数器的逻辑与秒钟计数相似,达到 59 分时归零
示例代码
reg [3:0] min_t; // 分钟十位
reg [3:0] min_o; // 分钟个位
reg [3:0] sec_t; // 秒十位
reg [3:0] sec_o; // 秒个位
always @(posedge clk or posedge raw_reset) begin
if (raw_reset) begin
// 复位所有计数器
min_t <= 0;
min_o <= 0;
sec_t <= 0;
sec_o <= 0;
end else if (one_second_enable) begin
if (sec_o == 9) begin
sec_o <= 0;
if (sec_t == 5) begin
sec_t <= 0;
if (min_o == 9) begin
min_o <= 0;
min_t <= (min_t == 5) ? 0 : min_t + 1; // 短时间防溢出
end else begin
min_o <= min_o + 1;
end
end else begin
sec_t <= sec_t + 1;
end
end else begin
sec_o <= sec_o + 1;
end
end
end
时钟分频器的设计
从系统时钟分频到 1Hz 的方法
将输入的 50MHz 时钟分频至 1Hz,以便每秒产生一次脉冲信号供计时器使用
示例代码
reg [25:0] counter; // 用于计数的寄存器
reg one_second_enable; // 1秒脉冲信号
always @(posedge clk or posedge raw_reset) begin
if (raw_reset) begin
counter <= 0;
one_second_enable <= 0;
end else begin
if (counter == 26'd49_999_999) begin
counter <= 0;
one_second_enable <= 1; // 每秒产生一个脉冲
end else begin
counter <= counter + 1;
one_second_enable <= 0;
end
end
end
四、按键暂停功能的实现
实现逻辑
增加一个称为start_pause
的输入信号,用于控制计数的启动和暂停状态。该信号为低电平有效。
在计数器逻辑中,增加对start_pause
信号的检测。
当start_pause
信号为高电平时,计数器将保持当前值,不再增加。当start_pause
为低电平时,计数器会继续正常计数。
示例代码
always @(posedge clk or posedge reset) begin
if (reset) begin
// 复位所有计数器
{sec_ones, sec_tens, min_ones, min_tens} <= 16'd0;
end else if (start_pause) begin // 根据start_pause状态进行暂停
// 不进行计数更新
end else if (one_sec_enable) begin
// 计数逻辑
if (sec_ones == 9) begin
sec_ones <= 0;
if (sec_tens == 5) begin
sec_tens <= 0;
if (min_ones == 9) begin
min_ones <= 0;
min_tens <= (min_tens == 5) ? 0 : min_tens + 1; // 处理分钟进位
end else begin
min_ones <= min_ones + 1;
end
end else begin
sec_tens <= sec_tens + 1;
end
end else begin
sec_ones <= sec_ones + 1;
end
end
end
五、按键消抖功能
实现逻辑
定义消抖模块,输入为原按键信号,输出为经过处理后的稳定信号。
创建一个计数器,当检测到按键状态变化时,开始计时。如果计时器在一定时间内(如20ms)没有再次变化,更新输出为当前状态。如果变化,重置计数器。
示例代码
module debounce (
input clk,
input button_in, // 原始按键输入
output reg button_out // 消抖后输出
);
reg [19:0] count; // 20ms的计数器(50MHz时钟)
always @(posedge clk) begin
button_sync <= button_in; // 添加同步器避免亚稳态
if (button_sync != button_out) begin
count <= count + 1;
if (count == 20'hFFFFF) begin // 达到20ms
button_out <= button_sync; // 更新输出
count <= 0; // 重置计数器
end
end else begin
count <= 0; // 状态未变化时复位计数器
end
end
endmodule
六、完整代码示例
module minute_second_counter (
input clk, // 50MHz时钟(PIN_R8)
input raw_reset, // 原始复位信号(连接按键KEY0,PIN_R22,低电平有效)
input raw_start_pause, // 原始启动/暂停信号(连接按键KEY1,PIN_R21,低电平有效)
output [7:0] hex3, // 分钟十位(HEX3)
output [7:0] hex2, // 分钟个位(带小数点)(HEX2)
output [7:0] hex1, // 秒十位(HEX1)
output [7:0] hex0 // 秒个位(HEX0)
);
// 消抖模块接口
wire debounced_reset; // 消抖后的复位信号(高有效)
wire debounced_start_pause; // 消抖后的启动/暂停信号(高有效)
// 实例化两个消抖模块(分别处理复位和启动/暂停按键)
debounce deb_reset(
.clk(clk),
.button_in(~raw_reset), // 输入信号取反(适配低有效物理按键)
.button_out(debounced_reset)
);
debounce deb_start(
.clk(clk),
.button_in(~raw_start_pause),
.button_out(debounced_start_pause)
);
// BCD计数器:分钟十位/个位,秒十位/个位
reg [3:0] min_tens; // 0-5
reg [3:0] min_ones; // 0-9
reg [3:0] sec_tens; // 0-5
reg [3:0] sec_ones; // 0-9
// 分频逻辑:将50MHz时钟分频到1Hz
reg [25:0] counter;
reg one_sec_enable;
always @(posedge clk or posedge debounced_reset) begin
if (debounced_reset) begin
counter <= 0;
one_sec_enable <= 0;
end else begin
if (counter == 26'd49_999_999) begin // 50MHz -> 1Hz
counter <= 0;
one_sec_enable <= 1;
end else begin
counter <= counter + 1;
one_sec_enable <= 0;
end
end
end
// 状态机定义
typedef enum logic [1:0] {
IDLE, // 暂停状态
RUNNING // 计时状态
} state_t;
state_t current_state, next_state;
// 状态寄存器
always @(posedge clk or posedge debounced_reset) begin
if (debounced_reset) begin
current_state <= IDLE;
{sec_ones, sec_tens, min_ones, min_tens} <= 16'h0; // 全部清零
end else begin
current_state <= next_state;
end
end
// 状态转移逻辑
always @(*) begin
case (current_state)
IDLE: begin
next_state = debounced_start_pause ? RUNNING : IDLE;
end
RUNNING: begin
next_state = debounced_start_pause ? IDLE : RUNNING;
end
default: next_state = IDLE; // 默认返回初始状态
endcase
end
// 计数逻辑
always @(posedge clk) begin
if (current_state == RUNNING) begin
if (one_sec_enable) begin
// 秒个位递增
if (sec_ones == 9) begin
sec_ones <= 0;
// 秒十位递增
if (sec_tens == 5) begin
sec_tens <= 0;
// 分钟个位递增
if (min_ones == 9) begin
min_ones <= 0;
// 分钟十位递增(0-5)
min_tens <= (min_tens == 5) ? 0 : min_tens + 1;
end else begin
min_ones <= min_ones + 1;
end
end else begin
sec_tens <= sec_tens + 1;
end
end else begin
sec_ones <= sec_ones + 1;
end
end
end
end
// 七段译码
function [7:0] seg7;
input [3:0] bcd;
begin
case (bcd)
4'd0: seg7 = 8'hC0; // 0
4'd1: seg7 = 8'hF9; // 1
4'd2: seg7 = 8'hA4; // 2
4'd3: seg7 = 8'hB0; // 3
4'd4: seg7 = 8'h99; // 4
4'd5: seg7 = 8'h92; // 5
4'd6: seg7 = 8'h82; // 6
4'd7: seg7 = 8'hF8; // 7
4'd8: seg7 = 8'h80; // 8
4'd9: seg7 = 8'h90; // 9
default: seg7 = 8'hFF; // 灭
endcase
end
endfunction
// 显示输出(HEX2显示分钟个位并带小数点)
assign hex3 = seg7(min_tens); // 分钟十位
assign hex2 = {1'b0, seg7(min_ones)[6:0]}; // 秒个位带小数点
assign hex1 = seg7(sec_tens); // 秒十位
assign hex0 = seg7(sec_ones); // 秒个位
endmodule
// 消抖模块(参考设计)
module debounce (
input clk,
input button_in,
output reg button_out
);
// 消抖计数器
reg [19:0] count;
reg button_sync;
always @(posedge clk) begin
button_sync <= button_in; // 同步
if (button_sync != button_out) begin
count <= count + 1;
if (count == 20'hFFFFF) begin // 达到20ms效果
button_out <= button_sync;
count <= 0;
end
end else begin
count <= 0;
end
end
endmodule
七、总结
在 DE2-115 开发板上用 Verilog 实现分秒计数器的过程可以让我们深入理解了 FPGA 硬件架构与 Verilog 编程的关系,特别是在设计时钟分频和计数逻辑中学会了如何将软件映射到硬件电路。
通过模块化设计,提高了代码的可读性和可维护性,并在调试过程中掌握了使用仿真工具和逻辑分析仪的技巧。