FPGA实战:基于Quartus II的矩阵键盘扫描与数码管动态显示系统设计
1. 项目背景与核心功能
刚接触FPGA开发时,我最先想实现的就是这种能"看得见摸得着"的交互系统。基于Quartus II的矩阵键盘扫描与数码管动态显示,堪称FPGA入门的黄金搭档项目——既包含输入设备处理,又涉及输出设备控制,还能学习模块化设计思想。
这个系统的核心功能很简单:当你按下4x4矩阵键盘上的任意按键(0-F),对应的十六进制数值会实时显示在数码管上。但实现过程会涉及三个关键技术点:
- 矩阵键盘扫描:如何用4根行线和4根列线检测16个按键
- 动态显示驱动:如何用有限IO口控制多位数码管
- 时钟分频:不同模块需要不同频率的时钟信号
我用的硬件平台是Cyclone IV EP4CE10开发板,搭配共阳极四位一体数码管。实际测试时发现,如果直接照搬教科书代码,会遇到按键抖动、显示残影等问题。下面我就结合踩坑经历,说说怎么实现稳定可靠的系统。
2. 矩阵键盘扫描模块设计
2.1 硬件连接原理
矩阵键盘的16个按键排成4行4列,通过8个IO口连接FPGA。扫描原理类似电影院找座位:先确定行号,再找列号。具体操作时:
- 初始化所有列线输出高电平
- 逐列输出低电平(每次仅一列为低)
- 读取行线状态,如果某行为低,说明该行该列的按键被按下
// 列扫描信号生成示例 always @(posedge clk_1kHz) begin case(scan_col) 2'b00: col <= 4'b1110; 2'b01: col <= 4'b1101; 2'b10: col <= 4'b1011; 2'b11: col <= 4'b0111; endcase scan_col <= scan_col + 1; end2.2 状态机去抖设计
新手最容易忽略的是按键抖动问题。实测发现机械按键按下时会产生约20ms的抖动信号。我的解决方案是:
- 检测到按键变化后启动20ms计时器
- 计时结束后再次检测按键状态
- 只有稳定按下的状态才被确认
// 去抖状态机片段 parameter DEBOUNCE = 3'b100; always @(posedge clk) begin case(state) IDLE: if(key_pressed) begin timer <= 0; state <= DEBOUNCE; end DEBOUNCE: if(timer >= 20_000) begin if(key_pressed) state <= PRESSED; else state <= IDLE; end else timer <= timer + 1; PRESSED: if(!key_pressed) state <= IDLE; endcase end3. 数码管动态显示实现
3.1 动态扫描原理
四位一体数码管如果静态驱动需要8×4=32个IO口,而动态扫描只需8+4=12个口。秘诀在于利用人眼视觉暂留效应:
- 每次只点亮1位数码管
- 以100Hz以上频率轮询4位数码管
- 每位数码管显示对应数据
// 数码管选择信号生成 always @(posedge clk_200Hz) begin case(sel) 2'b00: begin seg_data <= digit0; an <= 4'b1110; end 2'b01: begin seg_data <= digit1; an <= 4'b1101; end //...其他位同理 endcase sel <= sel + 1; end3.2 亮度不均解决方案
早期版本发现不同位数码管亮度不一致,原因是:
- 扫描间隔固定导致各点位亮时间不均
- 高位数码管因扫描周期结束可能显示时间不足
改进方案:
- 使用PWM调节每个数码管的点亮时间
- 增加扫描频率到400Hz以上
- 采用时间片轮转算法分配显示时长
4. 时钟管理与模块整合
4.1 分频器设计
系统需要三个时钟频率:
- 键盘扫描时钟(1kHz)
- 数码管扫描时钟(200Hz)
- 去抖计时时钟(50MHz)
module clk_div( input clk_50MHz, output reg clk_1kHz, output reg clk_200Hz ); reg [15:0] cnt1; reg [17:0] cnt2; always @(posedge clk_50MHz) begin if(cnt1 >= 25_000) begin clk_1kHz <= ~clk_1kHz; cnt1 <= 0; end else cnt1 <= cnt1 + 1; if(cnt2 >= 125_000) begin clk_200Hz <= ~clk_200Hz; cnt2 <= 0; end else cnt2 <= cnt2 + 1; end endmodule4.2 顶层模块连接
在Quartus II中创建Block Diagram文件,将各模块按以下方式连接:
- 时钟分频器接收50MHz系统时钟
- 分频后的时钟分别驱动键盘和显示模块
- 键盘模块输出按键编码给显示模块
- 显示模块直接连接数码管引脚
实际布线时建议使用"Auto-Fit"功能自动分配IO口,再手动调整关键信号引脚到合适位置
5. 调试技巧与性能优化
5.1 SignalTap II逻辑分析仪使用
遇到按键响应异常时,可以:
- 添加SignalTap II IP核
- 抓取行线、列线信号
- 观察扫描时序是否符合预期
- 检查去抖逻辑是否正确工作
5.2 资源占用优化
当使用低端FPGA时需要注意:
- 将频繁使用的状态机编码为one-hot格式
- 分频器计数器改用格雷码计数
- 数码管译码器改用查找表(LUT)实现
- 启用Quartus的优化编译选项
6. 完整实现步骤
- 新建Quartus工程,选择对应器件型号
- 创建四个Verilog文件:
- clk_div.v(时钟分频)
- key_scan.v(键盘扫描)
- seg_display.v(数码管显示)
- top.v(顶层模块)
- 为每个模块创建Symbol
- 在Block Diagram中连接各模块
- 分配物理引脚(参考开发板原理图)
- 设置未用引脚为三态输入
- 全编译并下载到开发板
7. 常见问题排查
按键无响应:
- 检查行列线是否接反
- 测量按键按下时是否真正导通
- 确认去抖时间常数是否合适
显示乱码:
- 确认共阴/共阳配置是否正确
- 检查段码表是否与硬件匹配
- 测量数码管扫描频率是否过低
显示闪烁:
- 提高扫描频率到200Hz以上
- 检查各位数码管使能信号重叠
- 确认电源供电是否稳定
这个项目最让我惊喜的是,当所有模块终于协同工作时,按下按键瞬间数码管亮起的那个反馈感。建议大家在基本功能实现后,可以尝试扩展功能比如长按检测、显示滚动等,这对理解FPGA的并行处理特性很有帮助。
