HIT-数字逻辑 | 一个“密码锁”把按键抖动、数码管和 Verilog 全都逼出来了

HIT-Digital-Logic-Password-Lock

我原本以为,密码锁就是几个数字、几个按键,再加一块数码管。真正开始写 Verilog 以后才发现,硬件不会像软件那样耐心地等你把事情做完。按键会抖,时钟会一直走,数码管需要不断扫描,错误次数和倒计时还要同时存在。

这个项目最后变成了一次很具体的数字逻辑实验:把一个看起来很生活化的“输入密码并开锁”,拆成输入、状态、计时、显示、消抖和顶层连线。每一个模块单独看都不大,接在一起以后却很容易出现那种“仿真没问题,上板以后完全不是这么回事”的瞬间。

仓库地址:HIT-Digital-Logic-Password-Lock

先把密码锁想成一台状态机

项目的顶层模块是 Lock_main,它把输入、状态和输出接起来。真正的行为则分散在不同模块里:Input 处理密码输入、确认、复位、上锁和错误次数;RealTimeInput 在设置模式下接收并保存新密码;Count 负责倒计时;Output 负责数码管的段选和位选。

如果一开始就把所有逻辑塞进一个 always 块,代码很快会变成一团线。输入密码时,系统要知道当前在普通开锁模式还是修改密码模式;输错以后要增加错误次数;错误次数达到条件以后还要进入限制状态;与此同时,显示模块仍然要按时钟刷新数码管。

我后来先把它画成几个相对清楚的状态:等待输入、确认密码、开锁成功、输入错误、倒计时限制和修改密码。状态图一画出来,很多原本混在一起的条件就有了位置。硬件设计里,先问“现在处于什么状态”,往往比先问“这根线接到哪里”更重要。

第一个真正让我抓狂的地方:按键消抖

机械按键按下去以后,电平不会干净利落地从 0 变成 1。它会在很短时间内抖动多次。人按一下,硬件可能观察到好几次变化。如果直接把按键电平当成一次输入,密码可能被重复写入,确认键也可能触发多次。

所以项目里单独有 debounce 模块。它不是为了让代码看起来更完整,而是为了解决一个软件直觉很难想到的硬件事实:你以为自己按了一次,电路看到的可能是一串快速变化。

调试消抖时,最容易出现的误判是“仿真波形看着差不多”。真正要观察的是稳定时间、采样时钟和输出脉冲是否只出现一次。消抖做得太短,抖动还没结束;做得太长,用户又会觉得按键迟钝。

这个小模块让我第一次真正感受到,硬件输入不是一个理想的布尔值,而是带着物理世界的毛刺进入系统。

数码管动态扫描:为什么明明只亮一个数字,却要一直刷新

密码锁需要显示当前输入、错误次数和倒计时。开发板上的数码管通常不是每一位都有一套完整的驱动线路,而是快速轮流点亮不同位。只要切换足够快,人眼就会把它看成同时亮着。

Output 模块要负责段选、位选和动态扫描,AdjustClock 则从开发板时钟分出更慢、更适合显示和控制的时钟。这个过程和软件里直接把字符串打印到屏幕上完全不同:你不是“输出一个数字”,而是在不断安排哪一位、哪一段、什么时候通电。

刚开始我很容易把显示问题归咎于逻辑错误。后来才发现,数码管不亮、显示重影或看起来闪烁,可能来自扫描频率、位选顺序、电平有效方向或者约束文件。硬件项目里,逻辑、时序和引脚约束经常要一起看。

错误次数和倒计时,两个看似简单的数字其实互相牵制

密码输错以后,系统需要记录错误次数;错误达到上限,就进入一段倒计时限制。倒计时结束后,用户才能再次输入。这里有两个数字同时存在:一个是累计错误次数,一个是剩余时间。

如果把它们直接写在同一个条件块里,代码会很快变得难以读懂。密码输入会改变错误次数,错误次数会触发倒计时,倒计时又会限制输入模块,但显示模块需要同时把当前状态告诉用户。

模块它负责什么出问题时的表现
Input 接收密码、确认和复位 输入重复、确认无效或状态不对
Count 倒计时和时间状态 倒计时不动、跳变或提前结束
Output 段选、位选和显示扫描 数字错位、重影或完全不亮
debounce 过滤机械按键抖动 一次按键被识别成多次

把这些模块拆开以后,我才更容易定位问题。是输入错了,还是状态机变了,还是显示把正确状态画错了?这和写软件时的分层思路很像,但硬件里的每个模块还要面对时钟和时序。

修改密码模式:同一组按键,放在不同状态里意义不一样

项目还支持在设置模式下输入并保存新密码。这个功能让我第一次比较明显地遇到“相同输入,在不同状态里含义不同”的问题。

普通模式里,按键是在尝试开锁;设置模式里,按键是在构造新密码。确认键在前一种情况下表示提交,在后一种情况下可能表示保存。复位键、上锁键和错误次数也要根据当前状态决定行为。

如果只看按键本身,很容易写出一串互相冲突的判断;如果先判断状态,再解释输入,逻辑会清楚很多。硬件状态机的好处是行为边界比较明确,代价是每个状态都必须考虑完整:进入条件、允许的输入、输出显示和退出条件都不能漏掉。

Vivado、仿真和上板:代码通过不等于实验完成

这个项目包含 Vivado 工程、Verilog RTL、约束文件、仿真输入和课程报告。仿真能够帮助我观察状态跳转、按键输入和倒计时,但最终能不能在开发板上正常工作,还要看 XDC 引脚约束、时钟和电平标准。

这也是数字逻辑实验和普通软件实验很不一样的地方。软件里的路径写错,通常会在运行时直接报出来;硬件里的引脚绑错,可能表现为某个灯不亮、数码管显示异常,甚至让你误以为逻辑全错了。

仓库里的约束和工程都带有具体开发板的历史背景,换板时需要重新核对引脚和电平标准。旧版 Vivado 工程在新版本里也可能需要重新生成部分元数据。我没有把它包装成可以适配所有开发板的通用 IP,因为那不是这次课程实验真正完成的事情。

现在回头看,我最记得的是“按一下”背后的复杂性

密码锁的界面很简单:按键,显示,开锁或者拒绝。但这几个动作背后,至少有时钟、状态机、消抖、计数器、动态扫描、引脚约束和仿真验证在一起工作。

这个项目让我第一次意识到,硬件设计不是把软件逻辑换一种语言写出来。软件可以等一个事件发生,硬件却一直在时钟驱动下运行;软件看到的是一次点击,硬件可能看到一串抖动;软件输出一个数字,硬件要不停切换段选和位选才能让人眼里出现那个数字。

如果现在重新做,我会更早画出完整状态图和时序图,也会给错误次数、倒计时和显示扫描补上更系统的测试。密码锁仍然只是课程设计,不是安全关键的真实门禁产品;但它作为一次从 RTL 到开发板的练习,已经让我对“输入、状态和输出”有了更具体的理解。

仓库地址:HIT-Digital-Logic-Password-Lock

说明:本文根据个人历史课程项目和公开仓库整理,部分文字经过 AI 辅助润色;项目依赖具体 Vivado 版本和开发板约束,文中实验属于课程设计历史记录。课程资料仅用于学习回顾和个人作品整理,不用于当前课程作业或考试。

posted @ 2026-09-10 22:20  何以牵尘  阅读(4)  评论(0)    收藏  举报