ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA矩阵键盘扫描与消抖实战:从原理到Verilog实现

FPGA矩阵键盘扫描与消抖实战:从原理到Verilog实现 简介FPGA矩阵键盘课程设计PDF面向电子通信类专业学生与FPGA初学者可解决基于Quartus II和Verilog实现4×4矩阵键盘控制LED数码管显示的问题。内容重点讲解矩阵键盘行扫描与列扫描的按键识别原理以及数码管段驱动和位选通的控制方法覆盖硬件电路连接、扫描逻辑设计、程序编写到最终显示输出的完整流程。该设计具有灵活性高、易于操作和可靠性强的特点非常适合作为高校FPGA实训教学案例。压缩包内仅含1个PDF文件大小约827KB报告包含摘要、原理分析、程序调试与流程图等模块便于按章节学习查阅。目前已有333人浏览学习适合正在准备课程设计报告或复习数字逻辑设计的读者能从中获取完整设计思路、核心代码参考与调试经验快速搭建并验证自己的矩阵键盘显示系统。 每个学FPGA的人大概率都绕不过“矩阵键盘”这门课设。不管是Xilinx还是Altera的板子上面总有一块4x4的小键盘作业要求往往很简单按键按下把对应的键值用数码管或者串口显示出来。听起来没什么难度结果一上板就出问题——按下一次跳好几个数、按住一个键疯狂触发、多个键一起按直接乱掉。这篇文章我把矩阵键盘的扫描原理、消抖处理、Verilog代码和调试经验完整拆开讲一遍适合正在做FPGA课程设计的同学也想跟刚接触FPGA的朋友说清楚这个实验为什么值得做做明白了之后你会收获什么。1. 先搞清楚矩阵键盘这门课设到底在考什么1.1 矩阵键盘的本质用更少的IO识别更多按键很多新手拿到题目会先愣一下既然要读16个按键为什么不直接上16个独立按键输入非要搞成4行4列的矩阵不是给自己添麻烦吗原因就俩字省IO。4x4矩阵键盘用8根信号线就能检测16个按键比独立按键方案节省一半引脚如果做成8x8的矩阵16根线能管64个按键差距更明显。实际产品里按键数量一多矩阵扫描基本是标配比如老旧电话机的12键拨号盘、电脑键盘的按键阵列都是这个思路。矩阵键盘的本质是把“每个按键独占一根线”变成“按键位于行列交叉点通过行列信号组合唯一确定”。FPGA课程设计选这个题目表面上是做一个输入外设实际上是在考你怎么用有限的硬件资源解决问题这是FPGA设计和单片机最大的区别管脚、逻辑单元、时序余量都是要算账的。1.2 需要掌握的知识点与能力清单我帮带过不少做课设的本科生矩阵键盘题目能完整做下来基本意味着你掌握了以下这些能力IO口的输入输出方向配置以及内部上拉的用法异步输入信号的同步处理打两拍防亚稳态机械按键的抖动消除理解“信号有效”和“电平稳定”的区别有限状态机FSM的实际应用状态怎么迁移、怎么防卡死组合逻辑编码比如行列信号转键值模块化设计把扫描、消抖、编码拆成清晰的功能块课程设计答辩时老师最爱问的也是这些点你为什么要打两拍消抖时间怎么定的状态机为什么要有“等待释放”这个状态能把这些逻辑关系理清比单纯跑通代码更有价值。2. 核心原理行扫描法与列线读取2.1 行扫描法的完整推演矩阵键盘检测按键的标准做法是“行扫描法”整个推演过程建议在纸上画一遍第一步把四根行线设为输出四根列线设为输入并启用内部上拉不按键时列线读到全高电平。列线默认是“1”当某个按键按下时对应列线会被拉到低电平。第二步逐行拉低。比如第一轮扫描行信号输出4’b1110也就是第0行为低、其他三行为高。此时如果第0行上有任意一个键被按下它对应的列线就会被拉低于是读回列线就能知道“当前扫描行”和“被拉低的列”是哪一个。读完后输出4’b1101扫下一行依次循环下去。这里有个很容易想不通的点为什么所有行都为低时不能直接读列线因为那样只能知道“有键被按下”却不知道按键在第几行col_in读到的低电平是四行中任意一行贡献的行列信息缺失无法唯一编码。逐行扫描等于给列线读取附加了“行坐标”这个前提条件。还有一点必须提行列定义是可以互换的。把行当输入、列当输出用列扫描法也一样可行。实际板子上到底哪边接输出要看原理图。常见的FPGA开发板比如黑金、正点原子的板子习惯把行线接FPGA输出、列线接FPGA输入但每个板子不一定相同。拿到板子第一件事是查原理图别想当然。2.2 扫描频率与扫描方式怎么选扫描频率是设计中需要自己定的参数。我的做法是让每行保持约1ms扫完4行一轮约4ms也就是大概250Hz的扫描率。这个频率足够快——人手按键持续时间至少几十毫秒扫描率远高于按键变化速度不会漏检同时每1ms换一行也给消抖逻辑留了足够的采样窗口。如果外部时钟是50MHz那么每行保持1ms就需要计数50_000个周期。代码里可以用一个计数器实现localparam SCAN_CNT_MAX 50_000 - 1; reg [15:0] scan_cnt; reg [1:0] row_idx; always (posedge clk or negedge rst_n) begin if (!rst_n) begin scan_cnt 16d0; row_idx 2d0; end else if (scan_cnt SCAN_CNT_MAX) begin scan_cnt 16d0; row_idx row_idx 2d1; end else begin scan_cnt scan_cnt 16d1; end endrow_idx就是当前扫描的行号把行号译码成行线输出即可。有的同学把扫描频率设成10Hz这种慢速按键按一下要等100ms才有反应体感明显卡顿不建议。反过来疯狂扫每行1us就换虽然也能工作但给消抖带来的压力很大容易出现误判。3. 重点难点按键消抖与状态机设计3.1 为什么必须消抖机械触点那点事按键消抖是矩阵键盘最容易翻车的环节。机械按键内部是金属弹片按下和释放时弹片不会瞬间达到稳定接触而是会在几毫秒到十几毫秒内反复弹跳导致信号波形出现一连串脉冲。如果代码只检测“电平变低就触发一次”实际效果就是按一次键模块可能会连续输出好几次键值。你在数码管上看到数字跳了好几个不是FPGA坏了是消抖没做好。要理解消抖的本质我们需要的是“确认电平稳定后再输出结果”而不是“检测到边沿就立刻行动”。3.2 消抖方案对比延时采样 vs 计数滤波FPGA里常用的消抖方案有两种。延时采样检测到列线电平变化后先等20ms左右再读一次如果读到的电平和之前一致就认为按键有效。这种方案简单直观资源占用极低对按键这类慢速信号完全够用。但如果抖动时间偶尔超过20ms存在误判的可能一般成品里会把延时放宽到3050ms。计数滤波持续对输入信号采样统计一段时间内低电平出现的次数达到阈值才确认有效。这种方案更稳能过滤窄脉冲干扰但代码复杂度高一些还要占用计数器资源。我把两种方案放在一起对比方案稳定性资源占用代码复杂度适用场景延时采样中低低课程设计、简单按键检测计数滤波高中中工业按键、抗干扰要求高做FPGA课设建议先用延时采样代码好写逻辑也容易答辩。如果你想把设计做得更漂亮再升级到计数滤波也不迟。3.3 用状态机把按键过程管起来消抖和输出不能只靠几条if语句拼最清晰的方式是写成状态机。我习惯把按键检测分成四个状态IDLE空闲正常扫描状态DEBOUNCE检测到可疑按键进入消抖等待CONFIRM消抖确认有效输出键值WAIT_RELEASE等待按键释放防止长按重复触发这里最容易被忽视的是WAIT_RELEASE状态。很多新手做完消抖后发现我把按键按住不放数码管上的数字就会一直在跳。这是因为没有“等待释放”机制只要按键一直是低电平状态机就反复进入DEBOUNCE并输出。正确的做法是输出一次键值后必须等所有列线恢复高电平确认按键已经松开才能回到IDLE继续下一次检测。另外检测按键时绝对不是随便读一下列线就行。如果正在消抖的过程中扫描线换到了下一行原来按下的键在列线上表现出来的电平就可能变化导致消抖失败。我处理的办法是一旦检测到按键按下就立刻停止行扫描把行输出锁存在当前按下的那一行。这样在消抖、确认、等待释放期间列线电平只由那个按键决定非常稳定。IDLE —列线拉低— DEBOUNCE —延时结束且仍为低— CONFIRM —输出键值— WAIT_RELEASE —释放— IDLE4. Verilog实战从扫描到键值输出的完整代码4.1 模块划分与引脚分配我习惯把整个设计拆成四个部分时钟复位处理统一在顶层完成输入同步列线信号打两拍消除亚稳态行扫描产生row_out信号输出当前扫描行状态机负责消抖、锁定、释放检测、键值输出引脚分配上行线作为输出列线作为输入。代码里我会统一命名KEY_ROW为行线KEY_COL为列线。如果你的板子行列相反把信号对应关系调换即可。4.2 核心代码扫描、消抖、编码一体化下面是一份可直接上板验证的Verilog代码我在状态机的注释里写清了关键设计意图module matrix_keyboard ( input wire clk, // 50MHz 系统时钟 input wire rst_n, // 低电平复位 input wire [3:0] col_in, // 列线输入板上必须启用内部上拉 output reg [3:0] row_out, // 行线输出逐行拉低 output reg [3:0] key_code, // 按键编码值0~15 output reg key_valid // 按键有效标志保持到释放 ); // 参数定义 localparam DEBOUNCE_CNT_MAX 1_000_000; // 20ms 50MHz localparam SCAN_CNT_MAX 50_000; // 1ms 50MHz localparam IDLE 3d0; localparam DEBOUNCE 3d1; localparam CONFIRM 3d2; localparam WAIT_REL 3d3; // 列线同步打两拍防亚稳态 reg [3:0] col_sync1, col_sync2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin col_sync1 4b1111; col_sync2 4b1111; end else begin col_sync1 col_in; col_sync2 col_sync1; end end wire any_key_pressed ~(col_sync2); // 任意一列为低说明可能有按键 // 行扫描计数器 reg [15:0] scan_cnt; reg [1:0] row_idx; always (posedge clk or negedge rst_n) begin if (!rst_n) begin scan_cnt 16d0; row_idx 2d0; end else if (scan_cnt SCAN_CNT_MAX - 1) begin scan_cnt 16d0; row_idx row_idx 2d1; end else begin scan_cnt scan_cnt 16d1; end end // 行输出译码IDLE状态正常扫描其他状态锁定按下行 reg [1:0] pressed_row; always (*) begin if (state IDLE) begin case (row_idx) 2d0: row_out 4b1110; 2d1: row_out 4b1101; 2d2: row_out 4b1011; 2d3: row_out 4b0111; default: row_out 4b1111; endcase end else begin case (pressed_row) 2d0: row_out 4b1110; 2d1: row_out 4b1101; 2d2: row_out 4b1011; 2d3: row_out 4b0111; default: row_out 4b1111; endcase end end // 消抖与状态机 reg [2:0] state; reg [19:0] deb_cnt; reg [1:0] cur_row; reg [3:0] cur_col; reg [1:0] col_idx; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; deb_cnt 20d0; cur_row 2d0; cur_col 4b1111; key_code 4d0; key_valid 1b0; end else begin case (state) IDLE: begin key_valid 1b0; deb_cnt 20d0; if (any_key_pressed) begin // 当前扫描行检测到按键 cur_row row_idx; cur_col col_sync2; state DEBOUNCE; end end DEBOUNCE: begin if (deb_cnt DEBOUNCE_CNT_MAX - 1) begin deb_cnt 20d0; if (any_key_pressed) begin // 消抖结束仍是低电平确认有效 state CONFIRM; end else begin // 恢复高电平说明是抖动 state IDLE; end end else begin deb_cnt deb_cnt 20d1; end end CONFIRM: begin // 将行索引和列索引编码为 0~15 的键值 case (cur_col) 4b1110: col_idx 2d0; 4b1101: col_idx 2d1; 4b1011: col_idx 2d2; 4b0111: col_idx 2d3; default: col_idx 2d0; endcase key_code {cur_row, col_idx}; key_valid 1b1; state WAIT_REL; end WAIT_REL: begin if (!any_key_pressed) begin // 列线恢复全高按键已释放 key_valid 1b0; state IDLE; end end default: state IDLE; endcase end end endmodule注意CONFIRM状态里我用了{cur_row, col_idx}拼接成键值这和你实际板子上的按键布局可以通过真值表重新映射。比如你想把某个物理位置的按键映射成数字1直接在case里改就行。4.3 仿真与上板验证要点写完代码先别急着上板仿真能帮你省下大量调试时间。我写testbench时会故意模拟机械抖动每次按键按下时在0.5ms到3ms范围内随机翻转列线验证状态机能否正确过滤。如果仿真时就频繁误触发说明消抖时间不够上板只会更惨。上板验证的步骤顺序也有讲究。先把行扫描和数码管显示跑通确认四个行输出依次拉低、数码管正常显示扫描状态然后再接列线检测按下任意按键看数码管是否有响应。如果直接把所有功能一股脑写完再上板出问题时你根本不知道是扫描的问题还是消抖的问题。5. 调试实录常见问题与排查技巧5.1 常见问题速查表这段时间帮人调得最多的就是下面这几种情况现象可能原因排查方法按键完全没反应行线列线接反IO方向配置错误内部上拉未启用用示波器或ILA观察col_in用手碰触引脚看电平是否变化按一次键输出多次消抖时间太短缺少等待释放状态增大DEBOUNCE_CNT_MAX到20ms以上检查状态机是否回到IDLE后又立刻进入DEBOUNCE按住按键数字不停跳没有WAIT_REL状态长按被当成了连续按补充等待释放逻辑某个按键没反应对应行或列信号接触不良行列索引编码错误检查原理图确认行列编号映射用万用表量信号线多个键同时按时乱码矩阵键盘本身存在按键串扰多键检测需要二极管或更复杂的扫描逻辑课设阶段避免多键同时按如果必须支持要改成行扫描列屏蔽逐键确认的方式5.2 三个我踩过的坑第一个坑行线没拉低就直接读列线。当时写的代码里IO方向设置对了但没有逐行拉低直接读col_in。结果col_in读到全1按什么键都没反应。查了半天发现行线全部输出高电平按键按下时列线根本没有拉低条件自然读不到任何东西。第二个坑消抖计时器位数不够。50MHz时钟下20ms需要计数1_000_000次如果计数器只有16位最大值65535消抖时间连2ms都不到。当时我明明把DEBOUNCE参数设得很大上板还是抖最后打印出来才发现计数器早就溢出了。这个坑很多教程不会写但特别容易犯。第三个坑仿真时没模拟按键释放。我先写的testbench只模拟了“按下有效”的过程没模拟“松开”的过程结果状态机走到WAIT_REL就出不来了仿真后面全是空白。写testbench时一定要把“按下—保持—松开”整个生命周期跑一遍。6. 功能扩展让课程设计超出及格线6.1 数码管显示与串口输出矩阵键盘本身不是一个完整的“作品”它的价值要配合输出设备才能体现。最常见的扩展是接4位数码管动态扫描显示当前按键值。数码管的扫描思想和矩阵键盘刚好是一对镜像一个是输出端逐位扫描一个是输入端逐行扫描两者合在一起练能帮你把“动态扫描”这个概念吃透。如果板子上有串口模块还可以把键值通过UART发送到上位机做一个简单的密码输入系统。这会让课程设计的交互性上一个台阶答辩时也更有东西讲。6.2 长按、组合键与多键冲突如果还想更进一步可以试试给矩阵键盘加长按功能。原理是检测到按键有效后在WAIT_REL状态里额外用一个计数器按住超过比如1秒就触发一次长按事件松开时再算一次短按事件这样同一个按键可以承担两个功能。组合键和多个按键同时按下的情况要复杂得多。4x4矩阵键盘在没有隔离二极管的情况下同时按两个键可能出现“鬼键”——比如第一行第二列和第二行第一列的按键都按下时第一行第一列的交叉点会被错误地认为按下。原因是扫描过程中电流会沿着导通路径串到另一行。要彻底解决硬件层面需要在每个按键上串二极管要么软件扫描时逐行逐列地确认牺牲响应速度。课程设计阶段建议直接告诉评委“本设计不支持多键同时操作”把限制说明白比演示时翻车强。6.3 关于FPGA选型与资源评估最后聊一个很多同学会在答辩时被问到的问题为什么用FPGA而不直接用单片机这里就算你用的只是几块钱的芯片也要说出点门道。矩阵键盘用单片机做可能几十行代码就搞定了但FPGA方案的差异在于所有扫描、消抖逻辑都是并行硬件电路不依赖软件轮询IO电平时序完全由硬件逻辑保证实时性和确定性更强。这也是为什么在需要大量按键输入、严格时序控制的场景里FPGA依然有一席之地。资源占用方面完整实现一个矩阵键盘只用几十个逻辑单元、两个计数器和一个状态机对任何主流的FPGA芯片包括国产高云、紫光同创、安路这类来说都是九牛一毛。做课程设计选型时重点考虑的是开发板外设而不是芯片资源够不够。一点个人体会矩阵键盘看起来只是FPGA入门的一个基础实验但把它做好等于把数字电路设计里最常用的几块基本功都打了一遍基础。以后你再去玩OLED显示、PS2键盘解码、触摸屏驱动甚至做一些简单的接口协议底层的逻辑都是一样的并行扫描、同步采样、状态机管理、模块化拆分。我见过不少同学把这个课设调通之后就以为自己“会FPGA”了其实真正的收获不是那个会亮的数码管而是你调试过程中积累的翻车经验和排查思路。哪天你看到一个bug能下意识地判断“这大概率是消抖问题”或者“这块状态机缺了一个状态”那这个课设才算真的没白做。本文还有配套的精品资源点击获取
返回列表