☰
FPGA实现3×4按键阵列与8×8点阵显示:扫描锁存与ROM查表
2026/10/28 14:18:28 网站建设 项目流程

简介:围绕按键阵列扫描与发光二极管点阵显示控制展开的数字电子设计文档,面向电子信息、自动化等专业学生及FPGA入门实践者,对应Quartus II环境下基于LP-2900开发装置的课程设计或实验题目。文中给出3行4列共12个按键的识别方案与8行8列点阵的显示控制思路,涉及74138译码器列扫描、RS触发器消抖、行列电平判断、动态扫描显示及ROM字符码存储,并按分频器、状态机、计数器等单元电路逐一分析,配有设计指标与总体工作过程说明,可对照搭建电路、推演扫描时序或撰写实验报告。整包为1个doc文档,约253KB,篇幅紧凑、结构完整。目前已有551人学习,可作为数字电路综合设计与FPGA交互电路练习的参考资料。

1. 需求先立住:3×4 按键阵列配 8×8 点阵,先判后锁

做过按键阵列扫描和点阵显示器控制电路的人都清楚,真正麻烦的地方往往落在「按下之后状态怎么保持」上,点亮点阵反而没那么难。这道数字电子的经典题目,指标拆开只有一句话:3 行 4 列共 12 个键,配 8 行 8 列发光二极管点阵,按下哪个键就显示对应符号,松手不消失,换键才刷新;多个键同时闭合时只认最先作用的那个。听着简单,落到 FPGA 上却要同时解决扫描编码、状态锁存、字符查表、行列动态驱动四件事,其中任何一环的位序对不上,整屏显示就会错位,而且错误很难一眼看出来。这套方案以 LP-2900 开发装置为硬件环境,用分离器件加一片 128×8 ROM 把上述四件事串成一条完整数据通路,适合正在做数字电子课程设计、FPGA 入门的同学直接参考。

2. 总体架构设计:双状态机与查表 ROM 的三段分工

这套电路最容易讲乱的地方,是把「扫描」和「显示」当成一个整体来设计。实际上两者时钟不同、计数位宽不同、数据流向也不同,硬揉在一起必然出错。我一般会先把整机拆成三条互不干扰的数据通路,再考虑它们在哪一点上交汇。交汇点只有一个:寄存器里锁存的那个 4 位键码。

2.1 从「扫描—锁定—显示」拆出的三条数据通路

第一条是按键扫描与键码锁定通路。分频器送出低频扫描时钟,驱动状态机 A 不断累加,其输出被一分为二:高两位送 3-8 线译码器产生列扫描信号,低两位送数据选择器去选通某一条行线。只要被选中的那条行线读到低电平,就说明该行该列交叉处的按键闭合,此时数据选择器的输出立刻把状态机 A 的使能拉低,计数器停住,同时这个 4 位计数值被打进寄存器。这条通路的核心是「停下来」——计数器不停,键码就会一直变,寄存器也就存不住东西。

第二条是行扫描与取数通路。状态机 B 用另一个频率独立运行,取低 3 位输出,一方面经译码器变成 8 路行驱动信号,另一方面直接作为 ROM 的低 3 位地址。这条通路跟按键完全无关,只要上电就一直在跑,保证点阵始终在被刷新。

第三条是字符查表与输出通路。ROM 的 7 位地址由「寄存器输出的 4 位键码」和「状态机 B 的 3 位行号」拼成,读出的 8 位数据就是当前行该亮的列图样,再经过反相选择器和颜色选择器送到点阵列线。三条通路在 ROM 地址总线上完成汇合,这个结构决定了整个设计只需要一个 7 位地址、8 位数据的存储块,而不需要为每个按键单独做一套译码逻辑。

把这三条通路画清楚之后,后面所有的器件选型和引脚分配都只是在填空。反过来说,如果一开始就按「先扫描再显示」的线性思路画图,很容易把两个计数器的时钟接混,调试时现象会非常古怪。

2.2 器件选型:为什么是 74161、74153、74377 和 128×8 ROM

整机用到的器件不多,但每一片都有明确的职责边界,选错一片就会连带改一片。

器件数量在电路中的职责选它的理由
74907级联分频,10MHz 降到 1Hz,共 8 档二-五-十进制计数器,级联方便,每片正好十分频
741612状态机 A(键扫描)、状态机 B(行扫描)4 位二进制同步计数器,带 ENP/ENT 使能,能原地保持
741531(双片封装用一半)从 3 条行线中选 1 条读电平4 选 1,两位选择端正好接计数的低两位
743771锁存 4 位键码8 位 D 触发器,只用了 4 位,剩下的可以留着扩展
741381 + 81 片做列译码,8 片做颜色二选一3-8 线译码,输出低有效,正好配低电平有效的列扫描
ROM 128×81存 12 个字符、每个 8 行的列码12×8=96 个单元,128 深度留出空档,正好 7 位地址
同或门阵列1 组前景背景反相8 路同或,比用 8 个异或加反相器省事

表里最值得说的是 74161。它跟 74160、74163 长得很像,区别就在使能端的行为:ENP 和 ENT 同时为低时,计数器保持当前值且进位输出 RCO 被强制为高,正好用来做「检测到按键就冻结」这个动作。如果用 74160 这类异步清零的片子,冻结和清零容易混淆,调试时会多走很多弯路。

ROM 的深度选 128 而不是 96,也是被位序逼出来的。12 个键占用 12 个键码,但 4 位计数一共有 16 个状态,多出来的 4 个状态必须占着地址空间,否则地址拼装就会错位。这一点在原始设计里踩过坑,后面第 5 章会详细说。

2.3 时钟分配与频率档位怎么定

整机只有一个 10MHz 石英晶振,所有时序都从它分出来。7 片 7490 级联,每片十分频,从第一级到第七级依次得到 1MHz、100kHz、10kHz、1kHz、100Hz、10Hz、1Hz,加上晶振本身一共 8 档。

分频器的接法有两种,差别在占空比。把输入时钟接 CLKA、把 QA 输出接 CLKB,从 QD 取输出,得到的是占空比 20% 的十分频;把输入接 CLKB、把 QD 接回 CLKA,从 QA 取输出,占空比是 50%。两种都能分频,但 50% 占空比在后续做同步计数时余量更大,尤其是行扫描这一路对高电平宽度敏感,占空比太窄会导致点阵某几行偏暗。所以七级全部采用第二种接法。

频率分配上,行扫描取 1kHz 档,8 行扫一遍是 8ms,帧频 125Hz,人眼完全看不出闪烁;按键扫描取 100Hz 档,每个键的停留时间是 10ms,12 个键扫一轮 120ms。人按键的闭合时间通常在零点几秒到一秒多,覆盖两三轮扫描绰绰有余。这两个频率必须分开,如果图省事让两个状态机共用一个时钟,要么显示闪,要么按键漏判,二者不可兼得。

提示:分频器的输出档位在调试阶段最好引到面板上做成可选跳线,扫描频率不合适时能立刻换档验证,比改代码重新综合快得多。

3. 按键扫描与锁定:4 位计数、74153 与 74377 的闭环

按键这一侧的逻辑量不大,但时序关系是整个设计里最绕的。它要同时满足三个约束:扫得完 12 个键、扫到就停、停了还能被下一个键唤醒。三个约束分别对应计数器位宽、使能反馈和寄存器锁存。

3.1 扫描计数与行列译码的位序约定

4 位计数器输出 Q3~Q0,一共 16 个状态。这 16 个状态怎么分配给 3 行 4 列,直接决定了键码表长什么样。原始设计里采用的分法是:Q3、Q2 送 74138 的 A1、A0 做列译码,Q1、Q0 送 74153 的 B1、B0 做行选择。

这个分法跟不少教材上的接法正好相反。教材通常让高两位去选行、低两位去选列,如果照着书本接线又照着书本填 ROM 数据,显示就会整体错位,而且错得很有规律——按下第一行第一列的键,显示的却是第四行第一列的字符。这不是玄学,纯粹是位序没对齐。

按高两位选列、低两位选行的约定,16 个状态对应的键位如下表。表里的键位布局按 3 行 4 列排列,实际接线时以装置面板丝印为准,这里给出的是一种常见排布。

Q3Q2Q1Q0列(Y)行(X)对应按键是否有效
0000Y0X01有效
0001Y0X14有效
0010Y0X27有效
0011Y0—无空状态
0100Y1X02有效
0101Y1X15有效
0110Y1X28有效
0111Y1—无空状态
1000Y2X03有效
1001Y2X16有效
1010Y2X29有效
1011Y2—无空状态
1100Y3X0#有效
1101Y3X1*有效
1110Y3X20有效
1111Y3—无空状态

四个空状态 0011、0111、1011、1111 之所以存在,是因为只有 3 行却用了 2 位来选择,第 4 种组合必然悬空。这四种组合对应的 ROM 单元不会被正常按键选中,但如果地址译码出错或者计数器跑飞,它们就会被读到,所以数据必须填,不能留空。

3.2 数据选择器读行线、寄存器锁键码

74153 是双 4 选 1,这里只用其中一半。三个数据输入 1C0、1C1、1C2 分别接三条行线 X0~X2,第四个输入 1C3 直接接高电平。接高电平是必须的:当计数器停在空状态时,选择器输出必须是高,否则会被误判成有键按下,整个电路就锁死在空状态里出不来了。

行线本身通过上拉电阻接 +5V,没有按键闭合时全为高。当某条列线被 74138 拉低,而该列上某个键正好闭合,对应的行线就被拉低,选择器输出低电平。这个低电平有两个去处:一路去 74377 的锁存端,把当前 4 位计数打进寄存器;另一路反相后去 74161 的 ENP 和 ENT,把计数器冻住。

74377 的锁存端是边沿有效的。原始设计里在锁存端前面串了一个反相器,因为 74377 本身是上升沿触发器件,而选择器输出的是低电平有效信号,加一级反相正好把「按下瞬间」变成一次上升沿,避免电平持续期间反复锁存。这一步如果省掉,按住键不放的时候寄存器会一直跟着计数器的变化走(虽然计数器已经停了,但上电瞬间的抖动仍可能造成误锁)。

用 VHDL 描述这段逻辑比画原理图更直观:

-- 4 位扫描计数器:时钟取 100 Hz 档 -- en_scan 为 '0' 表示当前选中的键闭合,计数器必须原地保持 process(clk_scan, rst_n) begin if rst_n = '0' then q_scan <= (others => '0'); elsif rising_edge(clk_scan) then if en_scan = '1' then q_scan <= q_scan + 1; -- 无键闭合,继续扫描下一个 end if; -- 有键闭合:不赋值,保持原状态 end if; end process; -- 键码锁存:只在检测到闭合的那一拍更新,其余时间保持 process(clk_scan, rst_n) begin if rst_n = '0' then key_code <= (others => '0'); elsif rising_edge(clk_scan) then if en_scan = '0' then key_code <= q_scan; -- 冻结瞬间的计数值即键码 end if; end if; end process; -- 位序拆分:高两位给列译码,低两位给行选择 col_sel <= q_scan(3 downto 2); -- 送 74138 的 A1、A0 row_sel <= q_scan(1 downto 0); -- 送 74153 的 B1、B0

这段代码里最关键的参数是en_scan的极性。它在无键闭合时为高、有键闭合时为低,跟 74153 的输出极性一致,中间不需要再加反相。如果把极性搞反,现象是上电后一位都不动,或者计数器疯跑完全停不下来,这两种现象都很好辨认,但第一次遇到时容易往时钟上怀疑,白白浪费时间。

另一个参数是key_code的更新条件。它只在en_scan = '0'时赋值,也就是只在按键闭合那一拍更新,松开后保持。这就是「保持显示符直到新的按键作用」这条指标的实现方式——不需要额外的保持电路,寄存器本身就是保持元件。

3.3 扫描周期的计算与消抖处理

扫描周期的算法很直接:单键停留时间乘以键数。按键取 100Hz,单键停留 10ms,12 个键一轮 120ms。人的按键闭合时间通常为零点几秒,最短也有 0.2s 左右,120ms 的扫描周期能覆盖两轮以上,不会漏判。如果扫描时钟取到 1kHz,一轮只要 12ms,虽然也能扫到,但机械抖动期内会反复触发,反而容易锁到错误的键码。

消抖这块,原始装置上用基本 RS 触发器对行线信号做整形,利用触发器的双稳态特性把抖动期间的多次跳变收敛成一次干净的电平变化。这个做法在分离器件方案里很常见,成本低、效果好。如果全部逻辑都放进 FPGA,我一般改成采样计数消抖:用一个 20ms 的计数器对行线连续采样,只有连续采到低电平才认定闭合。两种做法的效果等价,区别是前者占用一片 74HC00,后者只占用几十个逻辑单元。

这里有个容易被忽略的细节:消抖电路的输出必须接到数据选择器之前,而不是接到选择器之后。如果接在后面,选择器切换行线时产生的毛刺也会被当成按键动作,表现为「没按键也偶尔跳字符」。这种偶发故障最难查,因为示波器不一定能抓到。

3.4 计数器封锁的时序细节

封锁动作看着简单——拉低 ENP 和 ENT 就完事——但实际接线时有两个点要注意。

第一,74161 的 ENT 为低时,RCO 进位输出会被强制为高。如果设计里用 RCO 去做级联或者触发别的逻辑,封锁期间这个信号会失效。这套电路里两个状态机都是单片工作,没有级联,所以不受影响,但换成 8 位计数方案时就要留意。

第二,封锁信号来自组合逻辑(74153 的输出),而计数器的时钟是分频器输出的方波,两者之间存在竞争的可能。稳妥的做法是让封锁信号在时钟的低电平期间建立,也就是让 74153 的输出经过一级由同一时钟驱动的触发器再送到 ENP/ENT。原始设计里没有加这一级,在 100Hz 这种低频率下工作正常,但如果把扫描频率提到 1kHz 以上,就可能出现偶发的多计一拍。这是典型的「低频能跑、高频翻车」问题,答辩时如果被问到为什么扫描频率不能取太高,这就是一个能答到点子上的理由。

4. 点阵显示驱动:3 位行扫描与 128×8 字符码表

显示这一侧的工作量主要在两件事上:把 8 行按时序轮流点亮,以及在每一行点亮期间给出正确的 8 位列码。前者靠一个 3 位计数器和一片译码器,后者靠 ROM 和一份填对位序的数据文件。

4.1 动态扫描的行列时序与极性

8×8 点阵内部是 64 个发光二极管,每个二极管跨在一行和一列之间。行线连阳极、列线连阴极的接法叫共阳,行线连阴极、列线连阳极叫共阴。LP-2900 上的点阵列信号经过一级反相器驱动,所以从 FPGA 侧看过去,行和列都是高电平有效:ROWi 和 Cj 同时为 1 时,第 i 行第 j 列的灯亮。

动态扫描的做法是同一时刻只让一行有效,其余七行关闭,然后快速轮换。3 位计数器 B 的输出经 74138 译出 8 路行信号,每路有效时间是一个扫描时钟周期。取 1kHz 时钟,每行亮 1ms,8 行扫完 8ms,帧频 125Hz,肉眼看到的是稳定字符。

这里有个极性陷阱必须提。74138 的输出是低电平有效,而点阵行需要高电平才亮。如果直接把 74138 的输出接到 ROW1~ROW8,结果是只有当前选中的那一行被拉低(因而不亮),其余七行全亮,屏幕上出现的是一片反白的横条,而不是字符。正确做法是在 74138 和点阵之间加一级反相器,或者干脆用 74139 这类高有效输出的译码器。这个坑很常见,因为原理图上看起来「译码器接点阵」顺理成章,不画反相器也看不出问题,只有上板才暴露。

列侧的极性相对简单,因为装置上已经有反相器,FPGA 输出高电平就对应点亮。但要注意反相器是有驱动能力限制的,8 路列信号同时拉高时电流较大,如果用的是普通 74HC04,最好确认一下灌电流是否够;不够就换 74HC240 这类带缓冲的器件。

4.2 ROM 地址拼装:高 4 位键码、低 3 位行号

ROM 的 7 位地址不是随便拼的,它必须跟键码和行号的位宽严格对应。高 4 位 A6~A3 接寄存器输出的键码 Q3~Q0,低 3 位 A2~A0 接状态机 B 的行号输出。这样每个键占 8 个连续单元,12 个键占 96 个单元,加上 4 个空状态各占 8 个单元共 32 个,总共 128 个单元,正好填满。

地址的算法是「键码 × 8 + 行号」。按第 3 章的键码表展开,前几个键的地址区间如下:

键码(Q3~Q0)对应按键地址区间(十六进制)说明
000010x00 ~ 0x07有效
000140x08 ~ 0x0F有效
001070x10 ~ 0x17有效
0011无0x18 ~ 0x1F空状态,填 0x00
010020x20 ~ 0x27有效
010150x28 ~ 0x2F有效
011080x30 ~ 0x37有效
0111无0x38 ~ 0x3F空状态,填 0x00
100030x40 ~ 0x47有效
100160x48 ~ 0x4F有效
101090x50 ~ 0x57有效
1011无0x58 ~ 0x5F空状态,填 0x00
1100#0x60 ~ 0x67有效
1101*0x68 ~ 0x6F有效
111000x70 ~ 0x77有效
1111无0x78 ~ 0x7F空状态,填 0x00

地址拼装用一行代码就够:

-- 7 位 ROM 地址 = 4 位键码 + 3 位行号 -- 键码来自 74377 锁存值,行号来自状态机 B rom_addr <= key_code & q_row; -- 3 位行扫描计数器,时钟取 1 kHz 档 process(clk_row, rst_n) begin if rst_n = '0' then q_row <= (others => '0'); elsif rising_edge(clk_row) then q_row <= q_row + 1; -- 0~7 循环,驱动 8 行轮流点亮 end if; end process;

key_code & q_row这个拼接顺序不能反。如果写成q_row & key_code,地址就变成「行号在高位、键码在低位」,整个码表的组织方式要跟着改,而且 12 个键的字符块会散落在 128 个单元里,填表时极易出错。判断顺序对不对有个简单办法:按住一个键不动,如果显示的是「半个字符加半个别的字符」,说明高低位接反了;如果显示的是完整字符但内容不对,说明码表数据填错了。

4.3 字符码表的组织与生成

码表文件用 Quartus 的 mif 格式,8 位数据对应 8 列,最高位 D7 对最左列 C1,最低位 D0 对最右列 C8。每个字符 8 行,从上到下对应地址从低到高。

DEPTH = 128; % 共 128 个存储单元 WIDTH = 8; % 每单元 8 位,对应 8 列 ADDRESS_RADIX = HEX; DATA_RADIX = HEX; CONTENT BEGIN % ---- 键码 0000,按键 "1" ---- % 00 : 18; % 第 1 行:..XX... 左侧竖笔 01 : 38; 02 : 18; 03 : 18; 04 : 18; 05 : 18; 06 : 7E; % 第 7 行:底部横笔 07 : 00; % 第 8 行留空,行间距 % ---- 键码 1110,按键 "0" ---- % 70 : 3C; % 顶部弧 71 : 66; 72 : 6E; 73 : 76; 74 : 66; 75 : 66; 76 : 3C; 77 : 00; % ---- 空状态:全部填 0,避免误扫时出现乱码 ---- % 18 : 00; 19 : 00; 1A : 00; 1B : 00; 1C : 00; 1D : 00; 1E : 00; 1F : 00; END;

填表时有两个细节决定了最终效果。第一个是行序:地址从低到高对应屏幕从上到下,如果反过来填,字符会上下颠倒,对「8」这种对称字符看不出来,对「4」「7」就很明显。第二个是位序:D7 对最左列是约定,但装置上的反相器可能改变有效电平,如果整屏显示是「黑底白字」变成「白底黑字」,那不是位序问题,是反相选择器的状态不对。

实际做的时候不必手写全部 96 个单元,可以先在纸上把 12 个字符各画一个 8×8 点阵,按行转成二进制再转十六进制,最后按地址顺序抄进 mif。这个过程看着笨,但比写脚本生成更可靠,因为中间任何一步错了都能当场发现。

4.4 反相与颜色两路辅助逻辑

反相选择器的作用是把前景和背景互换。做法是把 ROM 输出的 8 位数据和面板上的一个电平开关做同或运算:开关为 0 时输出原码,开关为 1 时输出反码。真值表只有四行,逻辑上就是一个同或门阵列,9 个输入(8 位数据加 1 位控制)、8 个输出。

-- 反相控制:inv_sw = '0' 正相显示,'1' 反相显示 dot_out <= rom_dout when inv_sw = '0' else not rom_dout;

这段逻辑用同或门搭和用一条并发赋值语句写,效果完全一样,但后者在综合时更容易被优化。原始设计用组合逻辑门实现,好处是能直观看到每一路的门电路,调试时可以用逻辑笔逐点测量。

颜色选择器稍微绕一些。它的目标是在红灯和绿灯之间二选一,由面板开关控制。原始设计用了 8 片 74138,每片的 A 引脚接颜色开关,使能端接 ROM 输出的一位,两个输出端分别送红灯和绿灯。本质上是把 1 位数据分配成两路互补信号,其中一路有效。这个做法功能上没问题,但用了 8 片译码器只为做 8 个二选一,器件数量偏多。如果重新做一版,我会直接用 8 个 2 选 1 门或者一片 74257 就够,占板面积能小一半。答辩时如果被问到器件选型的合理性,这是一个可以主动提出来的优化点,比被动等着被问要好。

5. 避坑与常见问题:位序、地址空档与极性的翻车点

这套电路的知识点不难,难在细节之间互相牵连。同一个错误现象可能有好几种成因,排查顺序错了就会在无关的地方耗掉大半天。下面五条是这类设计里出现频率最高的。

5.1 计数器高低位分配与教材不一致

现象:按下第一行第一列的键,屏幕上显示的却是别的键的字符,而且错得有规律——总是差几行或几列,但 12 个键都能显示,不是完全乱码。

原因:4 位计数器的 Q3~Q0 怎么分配给行和列,教材和实际接线常常不一致。教材习惯高两位选行、低两位选列,而按照本项目采用的接法,高两位送 74138 做列译码、低两位送 74153 做行选择。如果电路按一种方式接、码表按另一种方式填,键码和字符的对应关系就整体错位。

解决:先确认物理接线,再回头核对码表。具体做法是按住一个键不放,用逻辑笔测 74138 的哪一路输出为低、74153 选中哪一路输入,读出实际的 4 位计数值,然后拿这个值去查码表里对应的是哪个字符。如果查出来是别的键,就说明码表的地址分配错了,按实际位序重新排列 96 个单元即可。改的时候不要一个一个改,直接把整张表的地址重排,改一次到位。

5.2 ROM 空单元没填导致的偶发乱码

现象:正常按键显示都对,但偶尔在换键的瞬间会闪一下全黑或者全亮,有时候松开按键后字符会残留一两行。

原因:4 位计数有 16 个状态,只有 12 个对应真实按键,剩下的 0011、0111、1011、1111 四个状态在扫描过程中会短暂出现。如果 ROM 里这四个状态对应的 32 个单元没有填数据,综合工具默认填 0 或者填 FF,读出来就是全灭或全亮。虽然这几个状态只停留一个扫描周期,但动态扫描下会表现为闪烁。

解决:把 0x18~0x1F、0x38~0x3F、0x58~0x5F、0x78~0x7F 这四段全部填 0x00。填 0 比填 FF 好,因为全灭在视觉上只是短暂变暗,不容易被察觉;填 FF 会闪出一整条亮线,非常刺眼。另外在 mif 文件里把这四段单独加注释标出来,下次改码表时不会误删。

5.3 行列极性搞反导致字符镜像或反白

现象:字符能显示,但是左右镜像、上下颠倒,或者整屏变成黑底白字的反色。

原因:三种可能混在一起。左右镜像是 D7~D0 与 C1~C8 的对应关系反了;上下颠倒是地址从低到高对应屏幕从下到上;整体反白是反相选择器的控制电平处于反相状态。三种现象看着都像「显示不对」,但成因完全不同。

解决:先用一个上下左右都对称的字符(比如「8」或「#」)测试,如果对称字符显示正常,说明极性和行序都没问题,问题出在非对称字符的码表数据上。如果对称字符也反白,先查反相选择器的开关状态;如果对称字符上下颠倒,查地址与行序的对应;如果左右镜像,查数据位序。按这个顺序排查,最多三步就能定位。

5.4 扫描频率与按键响应互相打架

现象:显示很稳定,但按键反应迟钝,有时候按下去要等半秒才有反应;或者反过来,按键很灵敏,但屏幕上有轻微闪烁。

原因:两个状态机共用了一个时钟,或者两个时钟档位选得不合适。按键扫描太慢会导致漏判,太快会让抖动期内反复触发;行扫描太慢会闪,太快则单行点亮时间不足、亮度下降。

解决:两个状态机的时钟必须独立。按键扫描取 100Hz,一轮 120ms;行扫描取 1kHz,帧频 125Hz。这两个数值不是随便定的,是按「人的按键时间 0.2s 以上」和「人眼闪烁融合频率约 60Hz」两个生理参数倒推出来的。如果装置的分频器档位是固定的,优先保行扫描,按键扫描可以通过计数器再分频得到。

5.5 多键同时按下时只认扫描顺序,不认人手顺序

现象:两个键几乎同时按下,显示的却不是先按下的那个,而是扫描序列里靠前的那个。

原因:这套电路的「只响应最先作用的按键」是靠「计数器停住」实现的。哪个键先被扫描到,计数器就在哪里停,寄存器就锁哪个键。扫描是从 Q3Q2Q1Q0 = 0000 开始顺序推进的,所以优先级由扫描顺序决定,跟手指按下的先后无关。严格来说,这跟指标描述有出入。

解决:如果要求严格按人手顺序响应,需要额外加一个「全部释放」检测——只有当三条行线全部恢复高电平后,才允许寄存器接受新的键码。实现方式是把三路行线相与后接到寄存器的使能端,任何一键仍被按住时都封锁新键码写入。这样按住旧键再按新键就不会切换,必须完全松手才响应下一次。这个小改动在答辩时是个加分项,因为它说明设计者真的想过指标里的每一个字。

6. 验收与答辩:从逐键核对到讲清一条数据通路的走法

验收这套电路不需要复杂的仪器,一台能跑 Quartus 的机器、一块下载好的板子、一支逻辑笔,再加一张手写的记录表就够了。关键是测的顺序要对,先测静态再测动态,先测单键再测组合。

验收项操作方法判据
单键识别12 个键逐一按下并松开每个键显示对应字符,无错位、无重影
显示保持按住某键 3 秒后松开字符保持不变,不闪、不消失
换键刷新连续按不同键,间隔约 0.5 秒每次换键字符立即切换,无残留
多键同按同时按下相邻两键只显示一个字符,松手后恢复待机
反相功能拨动面板反相开关前景背景互换,字符形状不变
颜色切换按住颜色按键立即变红,松开恢复绿色
刷新稳定性用示波器看任一行驱动信号周期 8ms,占空比约 1/8,无抖动

记录表不要只写「正常」「不正常」,要把每次按键对应的显示字符抄下来,12 行一行不能少。这张表在答辩时就是最直接的证据,比口头说「都测过了」有说服力得多。

调试顺序上,我建议先断开点阵、只测按键通路。用逻辑笔测 74161 的输出,按住某个键看它是否停住,停住时的 4 位值是不是跟预期键码一致。这一步过了,再接上 ROM 和点阵。很多问题其实出在按键这一侧,但因为屏幕没亮就往显示那边查,方向一开始就偏了。

答辩环节通常会被问三个问题,提前准备好答案能省很多临场组织语言的时间。

第一个是「为什么用 ROM 而不用组合逻辑译码」。答法是从可维护性切入:12 个字符、每个 8 行,一共 96 个 8 位码,用组合逻辑实现需要大量门电路,而且改一个字符就要改一片逻辑;用 ROM 只需要改数据文件,硬件不动。这不是技术上的先进与否,是工程上的取舍。

第二个是「扫描周期为什么取这个值」。答法要落到具体数字:按键闭合时间最短按 0.2s 算,扫描周期 120ms 能覆盖一轮半以上;帧频 125Hz 高于人眼融合频率,看不出闪烁。把两个数字的来源说清楚,比背结论强。

第三个是「多个键同时按下怎么处理」。这个问题最容易翻车,因为标准答案「只响应最先作用的键」在实现上其实是「只响应扫描序列里最先被扫到的键」。主动把这个差别讲出来,再说加全释放检测怎么改,比硬说「已经实现了」要稳妥。评审老师通常更在意你有没有意识到这个差别,而不是这个差别本身。

最后说个我自己的习惯。这类设计做完之后,我会把整机的信号流画成一张一页纸的图,标上每一段的位宽和时钟频率,从晶振一直画到点阵的列驱动。画这张图不是为了交作业,是因为每次调试卡住的时候,盯着这张图看两分钟,基本都能定位到是哪一段的时钟或位宽对不上。从那以后我每次做带扫描和查表的电路,都会先画这张图再动手接线,比直接照着原理图连省下的返工时间多得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询