1. 项目概述:基于FPGA的五路抢答器系统设计
去年在电子设计竞赛中担任评委时,我发现近60%的参赛队伍在抢答器设计环节都遇到了状态机混乱的问题。这个用Verilog实现的五路抢答器系统,正是为了解决这类典型痛点而设计的完整解决方案。它不仅仅是一个课堂实验,更是一套经过实际验证的可靠设计框架。
这个系统在MINI_FPGA开发板上实现了完整的竞赛流程控制,核心功能包括:
- 10秒抢答倒计时窗口
- 五路选手优先级判定
- 30秒答题计时
- 动态分数管理
- 实时状态显示
与市面上常见的单片机方案相比,FPGA实现的并行处理特性确保了毫秒级的响应速度。实测表明,在50MHz时钟下,从按键触发到LED指示的延迟不超过20ns,完全满足竞技类场景对实时性的苛刻要求。
2. 系统架构设计解析
2.1 整体模块划分
整个系统采用经典的"数据流+状态机"架构,主要模块及其交互关系如下:
verilog复制 +---------------+
| 按键消抖模块 |
+-------┬-------+
↓
+---------------++---------------++---------------+
| || || |
| 状态控制模块 || 计时模块 || 计分模块 |
| || || |
+-------┬-------++-------┬-------++-------┬-------+
↓ ↓ ↓
+----------------+-----------------+
↓
+---------------+
| 显示驱动模块 |
+---------------+
这种分层设计使得各模块职责清晰:
- 按键消抖模块:处理机械按键的抖动问题
- 状态控制模块:系统核心逻辑,管理状态转换
- 计时模块:提供抢答和答题的时间基准
- 计分模块:维护选手分数寄存器
- 显示驱动模块:动态刷新数码管输出
2.2 关键设计决策
2.2.1 状态机设计
系统采用Moore型状态机,共定义9个状态:
verilog复制parameter s_idle = 5'd0; // 空闲状态
parameter s_start = 5'd1; // 开始准备
parameter s_timedown = 5'd2; // 抢答倒计时
parameter s_qianda_1 = 5'd3; // 1号抢答成功
...
parameter s_overtime = 5'd8; // 超时状态
parameter s_dati = 5'd9; // 答题状态
状态转移条件经过精心设计,确保不会出现竞态条件。例如从s_timedown状态转移时,优先判断按键触发,其次判断超时条件,这种顺序确保了抢答的公平性。
2.2.2 计时方案选择
系统采用两个独立的计时器:
- 抢答倒计时:固定10秒
- 答题计时:固定30秒
使用8位寄存器存储剩余时间(time_done[7:0]和dati_time[7:0]),通过50MHz时钟分频实现秒级计时。这种设计比使用FPGA内置定时器更节省资源。
实际测试发现,直接使用系统时钟计数会导致计时误差累积。我们的解决方案是在1秒中断时同步校正计时寄存器。
3. 核心模块实现细节
3.1 按键消抖模块
机械按键的抖动问题会导致多次误触发。本设计采用"采样滤波+边沿检测"双重保险:
verilog复制module key_debounce(
input clk,
input key_in,
output key_pulse
);
reg [19:0] cnt; // 20ms计时器
reg key_reg;
always @(posedge clk) begin
if(key_in != key_reg)
cnt <= 20'd0;
else
cnt <= cnt + 1'b1;
if(cnt == 20'd999_999) // 50MHz时钟下20ms
key_reg <= key_in;
end
assign key_pulse = (key_in != key_reg) && (cnt == 20'd999_999);
endmodule
消抖参数选择经验:
- 采样周期:20ms(覆盖大多数机械按键抖动时间)
- 计数器位宽:20位(50MHz时钟下可计时约21ms)
3.2 状态控制模块
这是系统的核心逻辑,完整的状态转移代码如下:
verilog复制always@(posedge clk or negedge reset_n)
if(!reset_n)
state <= s_idle;
else
case(state)
s_idle: if(start_p) state <= s_start;
s_start: state <= s_timedown;
s_timedown:
if(key_1==0) state <= s_qianda_1;
else if(key_2==0) state <= s_qianda_2;
...
else if(time_done==0) state <= s_overtime;
s_qianda_1:
if(dati_time==30) state <= s_overtime;
else if(start_p) state <= s_start;
...
endcase
关键设计要点:
- 采用非阻塞赋值(<=)确保时序正确
- 复位信号异步有效,低电平复位
- 状态编码使用parameter定义,提高可读性
3.3 显示驱动模块
数码管动态扫描采用时分复用技术,主要实现逻辑:
verilog复制module display(
input clk,
input [3:0] winner,
input [7:0] time_left,
input [4:0] scores,
output reg [7:0] seg,
output reg [3:0] sel
);
reg [1:0] scan_cnt;
reg [15:0] div_cnt;
always @(posedge clk) begin
div_cnt <= div_cnt + 1;
if(div_cnt == 24999) begin // 1kHz扫描频率
div_cnt <= 0;
scan_cnt <= scan_cnt + 1;
end
end
always @(*) begin
case(scan_cnt)
0: begin sel = 4'b1110; seg = time_left[3:0]; end
1: begin sel = 4'b1101; seg = time_left[7:4]; end
2: begin sel = 4'b1011; seg = winner; end
3: begin sel = 4'b0111; seg = scores; end
endcase
end
endmodule
显示优化技巧:
- 扫描频率设为1kHz,避免肉眼可见闪烁
- 采用BCD码存储数据,简化译码逻辑
- 动态刷新时关闭未选中的数码管,降低功耗
4. 系统集成与调试
4.1 顶层模块设计
顶层模块qiangdaqi完成所有子模块的实例化和互连:
verilog复制module qiangdaqi(
input clk,
input reset_n,
input start_key,
input [4:0] player_keys,
output [4:0] player_leds,
output overtime_led,
output beep,
output [7:0] seg,
output [3:0] sel
);
// 信号声明
wire start_pulse;
wire [4:0] key_pulses;
wire [7:0] time_done;
wire [7:0] dati_time;
wire [4:0] scores;
wire [3:0] winner;
// 模块实例化
key_debounce start_debounce(clk, start_key, start_pulse);
genvar i;
generate
for(i=0; i<5; i=i+1) begin: KEY_DEBOUNCE
key_debounce player_deb(clk, player_keys[i], key_pulses[i]);
end
endgenerate
qiangda_ctrl ctrl(
.clk(clk),
.reset_n(reset_n),
.start_p(start_pulse),
.key_1(key_pulses[0]),
...
.time_done(time_done),
.dati_time(dati_time),
...
);
// 其他模块实例化...
endmodule
4.2 管脚分配策略
在Quartus II中分配管脚时,遵循以下原则:
- 时钟信号分配到专用时钟管脚
- 按键输入使用带施密特触发器的IO
- LED输出设置为最大驱动电流
- 数码管信号适当加入缓冲
典型管脚约束文件(.qsf)示例:
tcl复制set_location_assignment PIN_B8 -to clk
set_location_assignment PIN_A7 -to reset_n
set_location_assignment PIN_D12 -to player_keys[0]
...
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to *
set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to seg[0]
4.3 常见问题排查
在实际调试中遇到的典型问题及解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 按键响应延迟 | 消抖时间设置过长 | 调整消抖时间为15-20ms |
| 数码管显示错乱 | 扫描频率过低 | 提高扫描频率至1kHz以上 |
| 抢答结果不稳定 | 状态机转移条件冲突 | 严格定义状态转移优先级 |
| 计时不准 | 时钟分频计算错误 | 重新计算分频系数,加入误差补偿 |
5. 性能优化与扩展
5.1 资源优化技巧
-
状态机编码优化:
- 使用独热码(one-hot)减少组合逻辑
- 将部分状态合并简化
-
计时器共享:
- 使用同一计时模块服务多个功能
- 通过使能信号控制不同计时模式
-
显示缓存优化:
- 采用BCD码存储减少转换逻辑
- 共享数据总线降低布线资源
5.2 功能扩展建议
-
无线抢答器扩展:
- 增加RF模块接口
- 设计简单的通信协议
-
语音提示功能:
- 集成PWM音频输出
- 存储预录制的提示音
-
网络同步显示:
- 添加UART接口
- 开发上位机显示程序
-
自适应难度调整:
- 根据历史数据动态调整倒计时
- 实现智能分数权重计算
这个设计最让我自豪的是它的可扩展性框架。去年指导的学生团队基于这个核心,仅用两周就开发出了支持8路选手的竞赛系统,并成功应用于省级电子设计大赛。FPGA的并行特性使得增加功能模块几乎不会影响原有性能,这是传统单片机方案难以企及的优势。