资讯动态

FPGA按键扫描与点阵显示:行列阵列、动态扫描与ROM地址映射实战

发布时间:2026/10/15 5:14:18 来源:尧图企业网站定制
简介这份文档面向数字电子技术学习者与FPGA初学者围绕按键阵列扫描与点阵显示器控制电路设计展开解决多按键识别与字符动态显示这一典型人机交互问题。资源包共1个doc文件约253KB内容为完整的设计说明文档涵盖设计指标、系统概述、单元电路设计与分析等章节可对照LP-2900开发装置理解硬件实现思路。文档详细讲解3行4列共12个按键的扫描原理包括RS触发器消抖、负脉冲列扫描与行线电平判断以及8行8列点阵显示器的动态扫描驱动方式并给出状态机、ROM代码转换、计数器与译码器的配合方案说明如何保存首个按键键码并封锁后续扫描。已有549人学习适合需要完成课程设计或实验报告的读者参考可据此掌握从原理分析到电路模块划分的完整设计流程。1. 从一块 12 键阵列说起这套按键扫描与点阵显示方案到底能干什么实验室里翻到一块老开发板板子上挂着 3 行 4 列共 12 个按键旁边是一块 8×8 的发光二极管点阵。要求很朴素按下哪个键点阵上就显示对应的符号一直保持到下一个键按下如果几个键同时闭合只认最先作用的那个。听起来像玩具但真动手做你会发现它把数字电子技术里几个核心问题一次性摊开了——机械按键怎么消抖、行列阵列怎么用最少的端口扫出最多按键、点阵怎么靠动态扫描“骗”出稳定字符、多个按键同时按下时怎么仲裁。这套设计就是围绕这些点展开的。它面向的是数字电路与 FPGA 入门阶段的实践者你可能正在做课程设计或者想找一个能把状态机、译码器、ROM、数据选择器串起来的完整案例。硬件环境以 LP-2900 开发装置为例FPGA 通过 RK1~RK3 读行线状态用 74138 译码器控制列线 Y0~Y3点阵行由 ROW1~ROW8 驱动列经反相器由 C1~C8 控制。整套逻辑不复杂但每一环都有它存在的理由少一个都会在调试时翻车。2. 按键扫描与点阵显示的原理为什么这样选、怎么算2.1 按键为什么要接成行列阵列如果 12 个按键每个都单独占一个 FPGA 端口加上消抖和后续逻辑端口很快就不够用。行列阵列的思路是把按键跨接在行线和列线的交叉点上每条行线通过电阻上拉到高电平。没有按键闭合时行线恒为高当某条列线被拉低而某个按键正好闭合对应的行线就会被拉低。这样3 根行线加 4 根列线就能定位 12 个按键端口数从 12 降到 7。扫描的关键在于“分时”列线不是一直低而是用负脉冲序列逐列拉低。每列低电平期间读取 3 根行线的状态就能知道这一列上哪个键被按下。这里有个容易被忽略的参数——阵列扫描周期。它等于单个按键的扫描时间乘以键数。原文给了一个例子16 个键、每键 10ms扫描周期就是 0.16s而人的按键闭合时间通常为零点几秒到一点几秒所以必须保证闭合时间大于扫描周期否则快速点按可能被漏掉。我一般会把单键扫描时间设在 1ms 左右12 个键的周期约 12ms远小于人的动作时间既不会漏键也不会让扫描逻辑跑得太快。机械按键的抖动是另一个必须处理的问题。触点闭合瞬间会产生一串毫秒级的毛刺如果直接拿去触发状态机一次按下可能被当成好几次。原文采用基本 RS 触发器对电平信号整形这是硬件消抖的经典做法。在 FPGA 内部常见做法是加一个计数器做软件消抖检测到电平变化后等 10~20ms 再采样一次两次一致才认。两种方式都行RS 触发器省逻辑资源软件消抖更灵活。2.2 点阵显示怎么用动态扫描“骗”出稳定字符8×8 点阵的本质是 64 个发光二极管行接阳极、列接阴极。如果同时点亮所有需要的点电流会很大驱动也复杂。动态扫描的思路是每次只让一行导通同时给出这一行应该亮哪些列的列码然后迅速切换到下一行循环往复。只要扫描频率足够高人眼因为视觉暂留看到的就是一幅完整的字符。在 LP-2900 上列信号经过反相器驱动所以 FPGA 的列控制逻辑是高电平有效——ROWi 和 Cj 同时为 1 时第 i 行第 j 列的灯亮。这一点和很多教材里“低电平点亮”的写法相反如果照搬书本逻辑显示会整个反过来。原文特别提醒了数据位序与点阵序列的对应关系如果 ROM 数据输出 D7~D0 依次控制 C1~C8那么数据码从高到低对应点阵从左到右的列行扫描计数器的低 3 位地址从低到高对应从上到下的行。建 ROM 数据文件时这两个映射关系必须和硬件连线一致否则字符会镜像或旋转。2.3 键码怎么产生、显示码怎么存按键编码靠一个 4 位二进制计数器。计数器输出 Q3~Q0其中两位控制 74138 译码器产生列扫描信号另外两位控制数据选择器选择要读的行线。当被扫描的按键闭合时数据选择器输出低电平这个信号一方面让寄存器保存当前计数值另一方面封锁计数器让它停止扫描。这样即使之后有其他按键闭合也不会覆盖已经锁存的键码——这就是“只响应最先作用按键”的实现方式。键码和显示字符之间的映射靠 ROM 完成。12 个按键每个字符需要 8 行×8 列的列码数据也就是 8 个存储单元12 个字符共 96 个单元。ROM 的高 4 位地址接键码低 3 位地址接行扫描计数器每个字符的 8 行列码连续存放。原文用的是 128×8 的 ROM多出来的 32 个单元不是浪费——后面调试时会发现正是这些空单元救了场。3. 从分频到颜色选择各单元电路的参数与连接要点3.1 分频器为什么选 7490 的第二种接法系统需要一个稳定的时钟源。LP-2900 上的石英晶振提供 10MHz但扫描和显示不需要这么快。分频器用 7 片 7490 级联每片十分频总共能输出 10MHz 到 1Hz 共 8 档频率。7490 是二-五-十进制计数器做十分频有两种接法一种是把输入时钟进 CLKAQA 接 CLKB用 QD 作输出占空比只有 20%另一种是输入进 CLKBQD 接 CLKA用 QA 作输出占空比 50%。原文选了第二种因为 50% 占空比的时钟在驱动后续状态机时更稳不会因为窄脉冲导致计数误触发。-- 7490 十分频的第二种接法行为级描述用于理解连接关系 -- 外部时钟接 CLKBQD 反馈到 CLKA输出取 QA process(clk_in) begin if rising_edge(clk_in) then -- 内部五进制计数QD 在计数到 9 时翻转 -- QA 输出为 10 分频、50% 占空比 end if; end process;这段不是要你直接综合而是说明连接逻辑CLKB 接收外部时钟QD 反馈回 CLKAQA 作为分频输出。实际用 7490 芯片时CLRA、CLRB 接低电平无效SET9A、SET9B 也接低让计数器正常工作。3.2 状态机 A 和 B74161 的使能与封锁状态机 A 负责按键扫描用一片 74161 构成。它的 ENP 和 ENT 由数据选择器的输出 Y 控制——当 Y 为低电平也就是有键闭合时计数器保持当前状态停止扫描。CLK 接分频器输出ABCD 置数端断开CLRN 和 LDN 接高电平无效。这里的关键是封锁信号必须及时如果计数器多走一步锁存的键码就错了。状态机 B 负责点阵行扫描也是 74161但 ENP 和 ENT 始终接高电平让它自由计数。输出只取 QC、QB、QA 三位形成 8 种状态经 74138 译码后控制 ROW1~ROW8。行扫描频率不能太低否则会看到闪烁也不能太高否则列驱动可能跟不上。我一般会把行扫描时钟设在 1kHz 左右8 行扫完约 8ms刷新率超过 100Hz肉眼完全看不出闪烁。3.3 寄存器 74377锁存端为什么要加反相器74377 是 8 位锁存器这里只用 4 位存键码。它的锁存端 LD 是下降沿有效而数据选择器的输出 Y 在按键闭合时是低电平。如果直接把 Y 接 LD那么按键闭合的瞬间是下降沿确实能锁存但按键松开时 Y 变高又会产生上升沿74377 不响应上升沿所以没问题。原文加了一个反相器把 Y 反相后再接 CLK这样锁存发生在 Y 的上升沿也就是按键松开时。两种接法都能工作但加反相器后锁存时刻更明确——按键稳定闭合后松开数据已经稳定很久了。3.4 显示码存储器128×8 ROM 的地址分配ROM 的 7 位地址中高 4 位 A6~A3 接键码 Q3~Q0低 3 位 A2~A0 接行扫描计数器。每个字符占 8 个连续单元12 个字符占 96 个单元。原文的 ROM 数据表里地址 0~7 对应第一个字符的 8 行列码8~15 对应第二个以此类推。建数据文件时我习惯先用一个脚本把字符点阵转成十六进制码再按地址顺序填进去避免手工抄错。# 把 8x8 点阵字模转成 ROM 初始化数据 # 每个字符 8 行每行 8 位高位对应 C1低位对应 C8 chars { 0: [0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08], 1: [0xFF, 0x81, 0x1F, 0xF8, 0x08, 0x08, 0x0F, 0xF1], # ... 其余字符按同样格式补充 } rom_data [] for key in sorted(chars.keys()): rom_data.extend(chars[key]) # 补齐到 128 个单元空单元填 0x00 while len(rom_data) 128: rom_data.append(0x00) for i, val in enumerate(rom_data): print(faddr {i:03d}: {val:02X})这段脚本的作用是把字模按 ROM 地址顺序展开。参数说明每个字符 8 个字节字节的高位对应点阵左边第一列低位对应第八列地址从 0 开始每 8 个单元一个字符。补齐到 128 是因为后面调试时发现地址映射和书本不同需要留出空单元。3.5 数据选择器、译码器与颜色选择数据选择器用 74153双片 4 选 1这里只用一片。选择信号 A、B 接状态机 A 的 QA、QB数据输入 D0~D2 接三根行线D3 因为无按键时行线为高所以接高电平。译码器 74138 负责行扫描输入接状态机 B 的 QC~QA输出 Y0~Y7 依次控制 ROW1~ROW8。反相选择器用组合逻辑实现外接电平按钮控制正相或反相。真值表显示原信号和输出是同或关系所以用同或门器件9 进 8 出。颜色选择器用 8 片 74138每片根据电平按键选择输出红色或绿色使能信号由 ROM 的每一位数据控制。这部分逻辑门数量多但原理简单就是用一个控制信号在两组输出之间切换。4. 调试时踩过的坑显示码错乱与 ROM 地址映射4.1 现象显示字符和设计完全对不上电路连完、烧录测试按下按键点阵确实亮了但显示的字符和预设的完全不符——有的笔画多一块有的整个错位。第一反应是 ROM 数据写错了于是把数据文件重新核对了一遍逐字节比对没发现错误。又检查了列驱动反相器逻辑也对。问题卡了挺久。4.2 原因状态机输出位序和书本设计不一致后来把电路图和书本上的参考设计并排看发现我的状态机 A 输出 Q3~Q0 的分配和书本不同。书本是 Q3、Q2 进数据选择器的 B1、B0Q1、Q0 进译码器的 A1、A0而我的设计里Q3、Q2 进了译码器Q1、Q0 进了数据选择器。这意味着键码的位序变了ROM 的高 4 位地址和实际键码的对应关系整个偏移显示自然错乱。4.3 解决ROM 扩到 128 单元空出无效状态既然位序不同就不能用 96 个单元的紧凑映射。我把 ROM 扩到 128 个单元让高 4 位地址完整覆盖 0~15 共 16 种状态。12 个按键只用到其中 12 种剩下 4 种状态——“0011”、“0111”、“1011”、“1111”——不会被计数器选中对应的 4×832 个单元空出来填 0x00。这样无论状态机输出位序怎么变只要键码落在有效范围内就能找到正确的字符数据。提示ROM 地址映射和状态机输出位序强相关换芯片或改连线后第一件事就是重新核对地址分配别默认和参考设计一样。4.4 功能测试按键、反相、颜色切换测试步骤很直接依次按下 12 个按键观察点阵显示的字符是否与标识符一致按下电平开关 SW2前景色和背景色互换按下 SW3绿色变红色松开变回绿色。测试时记录每个按键的响应特别注意多个按键同时按下时是否只锁存最先作用的那个。我当时的做法是用两根跳线同时短接两个按键观察显示是否稳定在第一个按下的字符上。5. 进阶技巧用脚本生成 ROM 数据并做位序校验5.1 字模提取与位序校验脚本手工填 ROM 数据容易出错尤其是位序和行列映射。我后来固定用一个 Python 脚本做两件事把字符点阵转成 ROM 数据同时校验位序是否和硬件连线一致。脚本的核心是维护一个映射表把“数据位”和“点阵列”的对应关系写死生成数据前先打印一份对照表确认无误再输出。# 位序校验确认数据位 D7~D0 与列 C1~C8 的对应关系 # 假设硬件连线为 D7-C1, D6-C2, ..., D0-C8 def check_bit_order(data_byte, expected_cols): data_byte: 8 位数据 expected_cols: 长度为 8 的列表表示期望点亮的列号1~8 返回实际点亮的列号列表 actual_cols [] for bit in range(8): if data_byte (1 (7 - bit)): actual_cols.append(bit 1) return actual_cols # 示例数据 0x81 应该点亮 C1 和 C8 print(check_bit_order(0x81, [1, 8])) # 输出 [1, 8]这段脚本的作用是给定一个字节和期望点亮的列反推实际点亮的列用来验证 ROM 数据文件和硬件连线是否匹配。参数说明data_byte 是 ROM 中某一行的列码expected_cols 是你根据字符形状期望点亮的列号。如果输出和期望不一致说明位序反了需要调整数据或连线。5.2 扫描频率与消抖参数的实测调整扫描频率和消抖时间不是拍脑袋定的。我一般会先用一个可调分频器把行扫描频率从 500Hz 调到 2kHz观察显示效果。低于 800Hz 时快速移动视线能看到轻微闪烁高于 1.5kHz 后列驱动的上升沿开始变缓亮度略降。最终停在 1kHz 左右兼顾稳定和亮度。消抖方面RS 触发器在实验室环境够用但如果按键老化或环境干扰大我会在 FPGA 里再加一级 10ms 计数器消抖双保险。5.3 多键同时按下的仲裁验证“只响应最先作用的按键”这个要求验证时不能只靠手动同时按。我的做法是用信号发生器产生两路相隔几毫秒的脉冲分别模拟两个按键的闭合观察锁存的键码是否对应第一个脉冲。如果第二个脉冲也能改变显示说明封锁逻辑有问题——通常是数据选择器的输出没有正确反馈到计数器的使能端。检查时重点看 74161 的 ENP 和 ENT 是否真的被拉低以及寄存器锁存端是否在正确的时刻动作。从那以后我每次做按键扫描类设计都会先把状态机输出位序、ROM 地址映射、数据位与行列对应关系这三张表列出来逐项核对后再连线。这个习惯帮我省掉了至少三次通宵调试。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑