简介《按键阵列扫描及点阵显示器控制电路设计》面向学习数字电子技术与FPGA应用的学生及工程人员围绕3行4列按键阵列的识别与8行8列点阵显示器的字符显示控制展开重点解决按键消抖、逐列扫描、键码锁存以及动态扫描显示等典型问题。资源包内为1个doc文档约253KB内容按设计指标、系统概述、单元电路设计与分析等章节组织结合LP-2900开发装置说明74138译码、计数器、状态机、ROM键符译码和行列驱动电路的具体实现。已有551人浏览学习可作为课程设计、实验报告或同类人机交互电路设计的参考帮助读者梳理从按键编码、显示码存储到点阵循环显示的完整设计思路并理解多键同时按下时只响应首个按键的处理逻辑。1. 12个键、64个点从一次按键到一屏字符的完整链路按下“5”8×8 点阵屏上出现一个“5”再按“*”它换成“*”两个键同时按住只认先到的那个。键盘处理、屏幕显示、多键先响应这三条需求拆开看都不复杂但要落在 FPGA 上就得把分频、扫描计数、列译码、行线选择、锁存、字模 ROM、动态扫描、反相和红绿配色串成一条环环相扣的链路。这套按键阵列扫描与点阵显示控制电路设计正是把这条链路完整走一遍的工程样例3 行 4 列共 12 个按键8 行 8 列共 64 个点的显示器12 个符号一一对应。它适合两类人数字电子技术课程设计要交板子的以及想弄明白“矩阵键盘 点阵屏”这对经典组合在门级是怎么跑起来的人。它没有现成库可以调用只能照着原理图连写 AHDL 或 Verilog再上板点亮。2. 系统架构与器件选型扫描链和显示链怎么共用一套键码整套电路可以拆成两条并行链路它们只在“键码”这个 4 位信号上汇合。按键扫描链路负责在 12 个键里找出被按下的那个把它的编号锁进寄存器显示链路负责把这个编号当作字模表的高位地址配合行扫描计数循环取出 8 个字节逐行刷到点阵上。两条链路共享一个时钟源但节拍完全不同扫描要慢到人手动一下就扫过一轮显示要快到人眼看不出逐行切换。2.1 两条链路的数据流按键侧的顺序是4 位计数器产生扫描码低两位送 3-8 译码器拉低某一列高两位送 4 选 1 数据选择器选中某一行行线电平经数据选择器输出后取反作为锁存器的时钟把当前计数值存下来。显示侧的顺序是3 位计数器同时驱动行译码器和 ROM 的低 3 位地址ROM 输出 8 位列码经同或门做可选的极性反转再进颜色选择网络最后从 C1~C8 出去驱动点阵列线。信号来源去向位宽说明扫描码 Q3~Q0状态机 A74161列译码器 A、数据选择器、寄存器4同时编码行和列键码寄存器74377ROM 地址 A6~A34锁存后保持不变行扫描码状态机 B74161行译码器 B、ROM 地址 A2~A03循环 0~7列码 D7~D0ROM128×8反相选择器、颜色选择器8一位控制一列ROW1~ROW8译码器 B74138点阵行线8高电平选通C1~C8颜色选择器8×74138点阵列线经反相器8逻辑高有效这张表值得对着原理图看一遍因为它解释了后面所有“接反了会怎样”的问题键码和行扫描码在 ROM 地址里是拼在一起的高 4 位是键低 3 位是行地址位序一旦调换取出来的字模就会整体错位成另一个字符。2.2 单元电路分工分频器用 7 片 7490 级联把板上 10MHz 晶振分成 10MHz 到 1Hz 共 8 档给两个状态机提供不同节拍。状态机 A、B 各用一片 74161前者做按键扫描计数后者做行扫描计数。寄存器用 743778 位只用其中 4 位。显示码存储器是自建的 128×8 ROM7 位地址、8 位数据。数据选择器用 74153双片封装只取一片选 3 路行线。译码器用 74138一路管列、一路管行。反相选择器是纯组合逻辑搭的 9 进 8 出同或门外接一个电平开关控制正相或反相。颜色选择器用了 8 片 74138每位数据配一片靠 A 脚的电平在红、绿两路输出之间二选一。后两个器件看起来有点“浪费”为了一个取反和一个换色动用了 9 片芯片。但在实验箱这种以验证逻辑为主的场景里用现成译码器搭选择网络比写一段组合逻辑更容易在原理图上定位问题波形也能直接探到每个引脚。2.3 时钟预算10MHz 怎么分到八档7490 是二-五-十进制计数器内部一个 2 分频和一个 5 分频。做 10 分频有两种接法时钟进 CLKA、QA 接 CLKB从 QD 取输出占空比只有 20%时钟进 CLKB、QD 接 CLKA从 QA 取输出占空比 50%。这里选第二种原因是扫描信号的高低电平时间要均衡列线拉低的时间和释放的时间各占一半行线选通与列码建立的时间余量才一致不会出现某一档频率下“列码还没稳定行线就关了”的情况。7 片级联后总的分频比是 10⁷从 10MHz 一路抽头得到 8 档档位频率典型用途010MHz系统主频一般只做参考11MHz高速计数、示波器观察用2100kHz行扫描上限试验310kHz行扫描快速档41kHz行扫描常用档8 行一轮 125Hz 刷新5100Hz慢速扫描肉眼可见逐行点亮610Hz单步观察71Hz指示灯闪烁按键扫描的节拍要另算。12 个键每个键占一个扫描周期如果单键扫描时间取 1ms整轮就是 12ms。人按一次键的闭合时间通常在零点几秒到一秒多12ms 远小于这个量级不会漏键。反过来如果单键扫描时间取 10ms整轮 120ms虽然还能用但已经接近一些快速点按的边界了这是后面排查“偶尔按了没反应”时要先怀疑的参数。3. 按键扫描链路拆解74161、74153、74377如何锁住第一个按下的键这条链路的精髓不在于“扫”而在于“停”。扫描谁都会做难的是在扫到第一个闭合键的瞬间把计数器按住让它不再往前走这样后续再按别的键也不会改写显示。整个“只响应最先作用的按键”的语义就是靠使能端实现的。3.1 4位计数器产生键码谁管行、谁管列状态机 A 是一片 74161复位端和置数端都接高电平使其失效ENP 和 ENT 并联后由数据选择器的输出 Y 控制时钟接分频器输出。它的 4 位输出 Q3~Q0 要拆成两组用一组去 74138 选列一组去 74153 选行。哪两位管哪一组直接决定了键码的编号方式。按教材上的常规接法Q3、Q2 进数据选择器的 B1、B0 选行Q1、Q0 进译码器的 A1、A0 选列。这样计数值 0001 时Y0 为低第 0 列同时选中 X1第 1 行扫到的是第 1 行第 0 列的键。12 个有效键码是 0~11无效的是 12~15因为只有 3 行。如果反过来接Q3、Q2 去选列、Q1、Q0 去选行有效键码就变成了另一套编号无效码也跟着变成 3、7、11、15。两种接法都能跑但字模表的排布必须跟着改ROM 的高 4 位地址就是键码键码编号变了12 个字模块的位置全得挪。这一点在后面第 5 章会展开它是这个项目里最容易翻车的地方。用等效的硬件描述语言写出来扫描核心大概是这样// 12键(3行×4列)扫描核心等效于 74161 74153 74377 // 行线 X0~X2 上拉无键按下为 1列线 Y0~Y3 低电平有效 module key_scan_core ( input wire clk_scan, // 扫描节拍取 1kHz input wire rst_n, input wire [2:0] row_in, // X0~X2 output reg [3:0] key_code, // 锁存后的键码送 ROM 的 A6~A3 output reg [3:0] cnt, // 4位扫描计数器等效 74161 output wire locked // 1 已锁存暂停扫描 ); wire [1:0] col cnt[1:0]; // Q1Q0 - 74138 的 A1A0选列 wire [1:0] row cnt[3:2]; // Q3Q2 - 74153 的 B1B0选行 // 74138 只用了低 2 位地址输出低有效 reg [3:0] col_line; always (*) begin case (col) 2d0: col_line 4b1110; 2d1: col_line 4b1101; 2d2: col_line 4b1011; default: col_line 4b0111; endcase end // 74153 按行号选一路行线行号 3 不存在等效于接高电平 wire row_sel (row 2d3) ? row_in[row] : 1b1; assign locked ~row_sel; // 有键按下时 Y0封锁计数器 reg locked_d; always (posedge clk_scan or negedge rst_n) begin if (!rst_n) locked_d 1b0; else locked_d locked; end // 74377 在 Y 的下降沿即 locked 的上升沿锁存一次 always (posedge clk_scan or negedge rst_n) begin if (!rst_n) key_code 4d0; else if (locked ~locked_d) key_code cnt; end // ENPENTlocked 取反没键按下就继续数按下就停 always (posedge clk_scan or negedge rst_n) begin if (!rst_n) cnt 4d0; else if (!locked) cnt cnt 4d1; end endmodule代码里几个关键点对应到硬件cnt是 74161 的输出locked是数据选择器输出经反相后的结果key_code是 74377 的锁存值。锁存条件写成locked ~locked_d是为了复现“下降沿有效”只有一个扫描节拍内新出现的按下动作才写入按住不放不会反复刷新。row 3这个判断对应 74153 的第 4 路输入接高电平因为只有 3 行第 4 路永远不该被选中。参数上clk_scan取 1kHz 意味着单键 1ms、整轮 12ms如果板上分频器只给到 100Hz单键就变成 10ms、整轮 120ms这时候快速点按就可能漏需要把分频档位往上调一档。3.2 行线选择与锁存时序74153 在这里干的事很朴素4 选 1把当前扫描行对应的那根行线接到输出 Y 上。三路行线接 1C0、1C1、1C2第四路 1C3 因为无键按下时应当为高直接接 VCC。这样扫描到不存在的第 4 行时Y 恒为高不会误判成有键按下。锁存环节有个细节容易忽略74377 的锁存端是下降沿有效而数据选择器在“有键按下”时输出低电平。也就是说按下动作本身产生的是一个下降沿如果直接把 Y 接到锁存端锁存会发生在按下瞬间电平跳变的那一刻此时计数值可能还没稳定。所以中间要加一级反相器把 Y 取反后再送时钟端让锁存动作落在反相后的上升沿上正好对应计数器已经停稳之后的那个节拍边缘。这个反相器不是可有可无的“逻辑取反”它是时序上的一个缓冲。少了它上板后常见的现象是显示的字偶尔差一格像是按了“5”出来“6”因为锁存抓到了计数器跳变的中间态。3.3 消抖与“只认第一个”的封锁逻辑机械按键的触点在闭合瞬间会弹跳几毫秒到十几毫秒波形上是一串快速脉冲。如果扫描节拍正好落在这串脉冲里Y 就会产生多个下降沿锁存器被反复写入虽然写进去的可能是同一个键码但如果抖动期间计数器已经走到下一格就会锁成相邻的键。基础做法是用基本 RS 触发器对行线信号整形把抖动的模拟波形变成干净的单次跳变。这个做法在单键电路里很好用但在阵列扫描里有个前提RS 触发器要放在数据选择器之后、锁存器之前而且两只交叉耦合的与非门要共用同一组电源和地否则整形出来的边沿仍然带毛刺。更稳妥的常见做法是加两级同步采样用扫描节拍对row_sel连续采两次两次一致才认。这相当于一个数字低通把短于一个扫描周期的毛刺全部滤掉。代价是响应延迟一个节拍对 1ms 的扫描周期来说完全可以接受。“只认第一个”的实现则完全落在使能端上Y 为低时74161 的 ENP 和 ENT 同时为 0计数器保持当前值不再递增。此时无论再按下哪个键数据选择器的输出怎么变计数值都不动ROM 的高 4 位地址也就不动显示自然停在第一个键上。等到手松开Y 回到高计数器恢复计数下一次按下才会写入新的键码。这里没有复杂的优先级仲裁电路一个使能信号就完成了全部语义。4. 点阵显示链路拆解128×8 ROM、动态扫描与红绿配色显示侧要解决的是“怎么用 8 根行线加 8 根列线让 64 个点在任何时刻看起来都是同时亮的”。答案是把时间切片任一时刻只点亮一行8 行轮一遍只要够快人眼就会把残影拼成完整的字符。所有设计都围绕这个切片展开。4.1 ROM地址映射高4位是键低3位是行12 个符号每个符号 8 行每行 8 位一共需要 12×896 个存储单元。地址线至少要 7 位2⁷128所以 ROM 做成 128×8。地址的高 4 位 A6~A3 接寄存器输出的键码低 3 位 A2~A0 接行扫描计数器的输出。这样每个键码在地址空间里占连续的 8 个单元从低地址到高地址正好对应点阵从上到下的 8 行。这个映射关系有个直接推论ROM 里会有 32 个单元永远取不到因为 4 位键码有 16 种取值而实际只有 12 个键。取不到的块是键码 12~15 对应的地址段如果计数器位序接反取不到的就变成 3、7、11、15 对应的段。这 32 个单元必须显式填 0不能留空让综合工具随便填否则上电瞬间点阵上会冒出几行乱码。存储数据的位序同样要对齐。ROM 的 D7~D0 依序控制 C1~C8那么一个字节从高位到低位就对应点阵从左到右的 8 列。以字符“0”为例第一行只想亮中间四列数据就是0x3C00111100边框行想亮最左最右两点数据就是0x8110000001。写数据文件时如果把这个顺序搞反显示出来的字会左右镜像。-- 128×8 ROM 初始化片段键码 0 占 0x00~0x07 DEPTH 128; WIDTH 8; ADDRESS_RADIX HEX; DATA_RADIX HEX; CONTENT BEGIN 00 : 3C; -- 第 1 行中间四点亮 01 : 66; -- 第 2 行 02 : 6E; 03 : 76; 04 : 66; 05 : 66; 06 : 3C; 07 : 00; -- 第 8 行留空字模不顶到边 18 : 00; -- 键码 3 是无效码整块清零 19 : 00; -- 其余无效块同理每块 8 个单元 END;这段 mif 片段里00~07是键码 0 的 8 行字模18~1F是键码 3 的 8 个单元。填 0 的意义不只是“好看”它让无效键码对应的显示是黑的而不是随机亮点调试时一眼就能看出是取到了无效块还是字模本身有错。4.2 行扫描与列码同步状态机 B 也是单片 74161使能端接高电平常开复位和置数端接高失效时钟从分频器取一档。输出只取 QC、QB、QA 三位组成 0~7 八个状态送 74138 译码成 8 根行选通线 ROW1~ROW8。同一组三位信号还要接到 ROM 的 A2~A0保证“选通第 i 行”和“取出第 i 行字模”是同一个时刻。这里的同步靠的是物理上的同一根线不是靠两个计数器碰巧同频。如果行译码和 ROM 地址分别来自两个不同的计数器哪怕频率一样上电初相也可能差一拍结果是每一行显示的都是上一行的内容整个字看起来像被向上平移了一格。这类问题在仿真里不一定暴露上板才看得见。行扫描频率的选择要兼顾刷新率和亮度。8 行扫一轮若单行停留 1ms整屏刷新率是 125Hz远高于人眼 50Hz 的临界闪烁频率看起来是稳定的。如果单行停留 10ms刷新率掉到 12.5Hz屏幕会明显抖动。另一方面单行停留时间太短LED 的导通时间不足亮度会掉下来在环境光强的地方看不清。常见折中是单行 0.5~2ms 之间对应分频档位的 1kHz 到 4kHz。列信号的建立要在行选通之前完成。74138 译码有传输延迟ROM 读出也有访问时间如果行线已经拉高、列码还在变化那一行会闪出一段错误的亮点。稳妥做法是在行选通和列输出之间留半个节拍的余量或者干脆接受这点轻微拖影因为 8×8 的点阵面积小肉眼很难分辨。4.3 反相选择与红绿切换反相选择器解决的是“前景背景互换”的需求。它由 9 个输入、8 个输出组成8 个输入来自 ROM 的数据位第 9 个是外接电平开关输出是每一位与开关做同或的结果。真值表很直白开关为 0 时输出等于输入开关为 1 时输出取反整屏的黑白就翻过来了。用同或门而不是异或门是因为这里的开关信号默认是高电平无效。同或的逻辑是“相同为 1、不同为 0”开关为 1 时输出取反开关为 0 时输出跟随正好对应“按下开关翻转、松开恢复”。这个器件的 9 进 8 出结构在原理图上表现为 8 组并行的门电路每组一个与门、一个或门加若干非门看起来繁琐但延迟一致不会出现某几位翻转快、某几位翻转慢导致的颜色撕裂。颜色选择器用了 8 片 74138每片负责一位数据。每片 74138 的 A 脚接同一个电平开关B、C 脚接固定电平使能端由 ROM 对应位的数据控制。这样当该位为 1 时译码器让 Y0 或 Y1 之一有效分别对应红色和绿色两路输出该位为 0 时两路都不亮。8 片并排就实现了“同一位数据可以按开关选红或选绿”。这里有个实际约束74138 的输出是低有效而点阵列线经反相器驱动后是逻辑高有效。所以从颜色选择器出来的信号还要再取一次反或者干脆在点阵接口处统一反相。原文提到列信号经反相器驱动以满足电流驱动能力这个反相器同时承担了逻辑极性和驱动能力两个职责接线时不能只当作逻辑门看。5. 避坑与排查显示错乱、误触发、闪烁的定位顺序这块电路在仿真里跑通不难难的是上板。下面几条是这个项目里最容易出现的问题每条都给出从现象到定位的路径。5.1 现象显示的字和按下的键对不上或者整屏错位原因几乎都是 ROM 地址的位序和扫描计数器的位序不一致。如果状态机 A 的 Q3、Q2 去选了列、Q1、Q0 去选了行那么有效键码就变成另一套编号原本按教材接法排好的 96 个单元取出来的块会整体偏移按“1”显示成“4”这类现象就会出现。解决分两步。先把 ROM 从 96 个有效单元扩到 128 个把无效键码对应的 4 个块每块 8 个单元全部填 0让错位时至少是黑屏而不是乱码。再按实际的位序重新排字模块让键码编号和物理按键一一对应。改完之后最好列一张键码对照表把“计数值、行号、列号、按键标号、ROM 块起始地址”五列写清楚贴在手边。5.2 现象按一次键显示跳好几个字符原因是抖动没有被滤掉锁存器在几毫秒内被写了多次而计数器在这期间已经走到了下一格。用示波器看数据选择器输出 Y能看到按下瞬间有一串密集的负脉冲。解决是在 Y 之后加两级由扫描节拍同步的采样连续两次读到低才认。如果板上已经有 RS 触发器整形电路检查两只交叉耦合门是否共地、上拉电阻是否够小接触不良的按键也会产生类似波形换个按键试试能快速区分是电路问题还是器件问题。5.3 现象按住一个键不放再按别的键没反应这不是故障是设计语义。计数器被 Y 封锁后不再递增键码保持不变其他键的按下动作根本不会被扫描到。如果需求改成“后按的覆盖先按的”就要把封锁逻辑改成边沿触发的一次性暂停或者加一个释放检测在按键全部松开后强制复位计数器。排查时先确认是“设计如此”还是“卡死”松开第一个键看显示是否还能响应新按键。能响应就是语义正确不能响应就是 Y 没有回到高电平检查行线上拉电阻和按键接触。5.4 现象点阵整体闪烁或者某一行明显偏暗闪烁通常是行扫描频率太低。8 行一轮单行 10ms 对应刷新率 12.5Hz肉眼很容易察觉。把分频档位往上调一到两档或者直接改行扫描计数器的时钟源。某一行偏暗则是那一行的驱动能力不足。检查对应的 ROW 线译码输出是否接错、该行 LED 的限流电阻是否偏大以及列信号反相器是否在这一行上漏接。8 行里如果只有一行异常基本可以锁定在译码器输出或连线不用怀疑 ROM 数据。5.5 现象一切换颜色整屏全灭颜色选择器里 74138 的使能极性搞反了。74138 的 G1 是高有效、G2A 和 G2B 是低有效三个使能端要同时满足才工作。如果只接了 G1 而把另外两个悬空译码器可能一直处于禁止状态输出全高经反相后全低屏幕就黑了。排查方法很直接用万用表量 74138 的三个使能脚确认电平符合数据手册的有效条件再量 A、B、C 三个地址脚是否跟随开关变化。这一步花不了五分钟但能省掉半天怀疑 ROM 的时间。6. 二次开发加键、换字模、改配色的三条改造路径这套电路的扩展性比看上去好因为它把“键码”“字模”“颜色”三件事拆在了三个独立的位置改哪一件都不用动另外两件。下面按改动量从小到大排一下。第一条路是换字模改动量最小收益最直接。把 12 个符号换成箭头、字母、简单图标都行只要还是 8×8。手工填 mif 容易出错我一般用脚本生成# 把 8×8 点阵字模写成 Quartus 的 .mif键码即块号 GLYPHS { 0x0: [0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00], # 数字 0 0x1: [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00], # 数字 1 # 其余 10 个键码照此补齐无效键码块留空 } LINES [DEPTH 128;, WIDTH 8;, ADDRESS_RADIX HEX;, DATA_RADIX HEX;, CONTENT BEGIN] for code in range(16): # 16 个键码块无效块填 0 rows GLYPHS.get(code, [0x00] * 8) for r, byte in enumerate(rows): # 低 3 位地址对应行号 LINES.append(f {code * 8 r:02X} : {byte:02X};) LINES.append(END;) with open(font_8x8.mif, w, encodingutf-8) as f: f.write(\n.join(LINES))脚本的关键是code * 8 r这个地址计算高 4 位是键码、低 3 位是行号所以第 code 个字符的第 r 行落在code*8r。GLYPHS.get(code, [0x00]*8)保证没有定义的键码块自动填 0不会因为漏写而让综合工具填入随机值。生成之后在 Quartus 里重新编译 ROM注意 mif 文件路径要用相对路径换机器打开工程时最容易在这里报错。第二条路是加键从 12 键扩到 16 键4 行 4 列。要动的地方有三处74153 的第 4 路输入不能再接高电平要接到第 4 根行线ROM 从 128×8 扩到 256×8地址加到 8 位无效键码块从 4 个变成 0 个字模表刚好填满。如果继续扩到 5 行以上4 位计数器就不够用了得换 5 位或者改用二进制以外的编码方式。加键之后记得重算扫描周期16 键 × 1ms 16ms仍然远小于人手按键时间不用调节拍。第三条路是改显示效果比如让字符滚动或者做多字符切换。滚动不需要改 ROM只需要在行扫描计数器之外再加一个帧计数器每过若干帧把 ROM 的高 4 位地址整体加一取下一个字符的块。这里要注意帧计数器的进位不能影响行扫描的连续性否则滚动的时候会撕裂。我的做法是让帧计数器只在行扫描归零的那一刻递增保证每次切换都发生在帧边界上。上板验证的顺序也有讲究别一上来就按键盘。先断开按键部分只给显示链路上电用固定键码强制 ROM 输出某个字符确认 8 行扫描正常、字形正确、没有镜像再接上按键用单步时钟一格一格走看锁存是不是发生在正确的计数值上最后才测多键同时按下和颜色切换。这个顺序能把问题限制在一条链路里省掉“到底是扫描错了还是显示错了”的反复怀疑。说个我自己踩过的坑有一次改完字模显示一直整体下移一行查了两个小时 ROM 数据最后发现是 mif 文件里第一行的地址写成了01而不是00整个块往后错了一位。从那以后我每次生成 mif 都强制走一遍脚本不再手工改任何一个字节生成完先打印前 16 行地址和数据对一遍再编译。希望帮到你。本文还有配套的精品资源点击获取