简介:一份基于FPGA的简易电子密码锁完整工程资料包,面向FPGA初学者、数字电路课程设计及电子竞赛备赛者,帮助解决4位密码校验、直流电机开锁控制、5秒超时自锁与20秒声光报警等典型设计问题。资料从顶层方案到模块实现均有覆盖,包含程序源码、设计电路、设计报告和测试流程,可依据其中的状态机设计、按键消抖和定时报警思路开展项目实践。包内共215个文件,约17.52MB,主要包括Quartus工程文件(cdb/hdb/qsf/qpf)、VHDL/Verilog设计源文件(vhd/tdf/bdf)、仿真测试文件(vwf)、设计文档(docx/pdf)及烧录配置(sof/pof),目录结构清晰,便于按模块检索学习。已有2038人学习,作为完整项目样例,对需要系统参考的读者很有帮助。通过这份资料可掌握FPGA密码锁的系统设计方法,并借助测试流程与设计报告快速完成从仿真到下载验证的闭环,是课程设计或竞赛准备的实用素材。
1. 项目概述
1.1 为什么选电子密码锁作为FPGA入门项目
电子密码锁几乎是FPGA学习路上绕不开的一个经典设计题目。说实话,我做了这几年FPGA开发,见过太多人一上来就奔着图像处理、PCIe、DDR控制器去,结果折腾几周连状态机都写不顺,最后灰心丧气。反倒是像电子密码锁这种小而完整的项目,能把FPGA设计中最核心的几个知识点串起来——时序逻辑、按键消抖、状态机设计、模块划分、仿真验证、上板调试,全套流程走一遍,基本功就打牢了。
这个项目的定位很明确:不追求复杂功能,但求把每一个环节都做得扎实。适合的人群包括:正在学习Verilog/VHDL的在校学生、刚接触FPGA开发的转行工程师、想通过具体项目验证开发板功能的爱好者。它不需要高档芯片,不需要外挂DDR或者高速接口,一块入门级开发板加几个按键、几颗数码管就能跑起来。
我把整个项目的源码、约束文件和仿真脚本整理好后,压缩成了一个zip包分享出来。这篇文章就把项目的设计思路、核心模块的实现细节、上板调试中踩过的坑一一讲清楚,让你拿到代码后不光能跑通,还能真正理解每一行逻辑为什么要这么写。
1.2 项目的技术参数与功能需求
先定义清楚我们要实现的密码锁要具备哪些功能。既然是“简易”版,功能上不追求花哨,但核心逻辑必须完整:
- 密码长度为4位,每位取值0~9,默认密码设定为“1234”
- 通过4x4矩阵键盘或独立按键输入密码,本次实现采用独立按键方案(课程设计/小型项目最常见,也最容易扩展)
- 输入过程中,数码管实时显示当前已输入的位数和数字
- 密码正确时,开锁信号拉高,控制LED灯或电磁锁动作,同时蜂鸣器鸣响一声作提示
- 密码错误时,蜂鸣器连续鸣响三声,并自动清除已输入的数字,等待重新输入
- 支持密码修改功能(扩展):按下“修改”键后输入新密码,再次确认后完成修改
技术指标上,系统主时钟采用50MHz(大多数开发板板载晶振的默认频率),按键消抖时间为20ms左右,数码管采用动态扫描方式刷新,刷新频率需大于50Hz以避免肉眼可见的闪烁。
密码锁的状态转移是整个设计的灵魂。如果不提前定义清楚状态,写代码的时候就容易东一榔头西一棒子。我习惯先把状态图画出来再动手写RTL,这个习惯帮我省了无数调试时间。
2. 整体架构与模块划分
2.1 模块划分的设计思路
模块化设计是FPGA工程的基本素养。把功能拆成一个个独立的小模块,每个模块职责单一、接口清晰,不仅方便单独仿真调试,也方便将来复用扩展。电子密码锁我把它拆成了五个模块:
| 模块名 | 功能职责 | 关键技术点 |
|---|---|---|
| key_debounce | 按键消抖与按键值输出 | 边沿检测、定时计数消抖 |
| password_controller | 密码输入控制与校验 | 状态机设计、密码比较逻辑 |
| seg_driver | 数码管动态扫描驱动 | 动态刷新、段码译码 |
| beep_controller | 蜂鸣器提示控制 | 分频计数、短鸣/长鸣模式 |
| top_module | 顶层模块,完成信号互联 | 例化各子模块、时钟/复位分配 |
这种“自顶向下”的划分方式,本质上就是数字电路中的层次化设计。你可以把每个模块看成一块独立的“芯片”,模块之间只有少数的信号线相连(比如按键值、开锁标志、数码管段选位选信号),互不干扰。这样任何一级出了问题,都能快速定位到对应的模块去查,不用在几百行代码里大海捞针。
2.2 按键消抖模块的两种常见实现方式
按键消抖是FPGA设计中避不开的基础问题。机械按键在按下和释放的瞬间,触点会发生物理抖动,持续时间一般在5~20ms不等。如果不做消抖处理,一次按键可能会被识别成多次,密码输入就乱套了。
常见的消抖方案有两种:一种是延时采样法,另一种是状态机消抖法。
延时采样法比较简单:检测到按键电平变化后,启动一个20ms的定时器,等定时结束再采样一次按键电平,如果电平已经稳定,就确认本次按键有效。这种方法的缺点在于从按键按下到确认有效有约20ms的延时,但密码输入这种场景完全够用。
状态机消抖法则通过三段式状态机检测信号的上升沿/下降沿,在电平稳定后输出一个单时钟周期的脉冲。这种方案响应更快,但代码量也更大一些。我在这个项目里用的是延时采样法,逻辑简单,也方便讲解。
// 按键消抖模块核心代码 reg [19:0] cnt; // 20bit计数器,50MHz时钟下可计1ms reg [3:0] key_reg; // 按键电平打拍寄存器 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin key_reg <= 4'b1111; cnt <= 20'd0; key_valid <= 1'b0; end else begin key_reg <= key_in; // 同步打拍,消除亚稳态 if (key_reg != key_in) begin cnt <= 20'd0; // 电平发生变化,清零计数器 end else if (cnt == 20'd999_999) begin // 计数到20ms,说明电平已稳定 if (key_in == 4'b1111) key_valid <= 1'b0; else key_valid <= 1'b1; end else begin cnt <= cnt + 1'b1; end end end注意代码里我先做了一级同步打拍(key_reg <= key_in),这是异步信号进入FPGA必须做的一步。很多人第一次写按键模块时不加这级打拍,结果仿真没问题、上板就偶发误触发,就是因为没有处理亚稳态。
3. 核心模块的RTL实现细节
3.1 密码控制状态机的三段式写法
密码控制器是整个设计的核心模块,我用有限状态机(FSM)来实现。状态机的设计是FPGA工程师的基本功,而三段式写法又是目前工程中最推荐、可读性最好的写法。三段式指的是:第一段描述状态转移(时序逻辑),第二段描述次态组合逻辑,第三段描述输出逻辑。
状态机一共定义了四个状态:
- IDLE:空闲状态,等待第一个按键输入
- INPUT:正在输入密码(内部用计数器记录已输入的位数)
- CHECK:4位密码输入完毕,进行校验
- CHANGE:密码修改模式
IDLE状态下,一旦检测到有效按键值(非0~9的数字键除外),立即跳转到INPUT状态,并把该数字存入寄存器password_buf[0]。INPUT状态下,每收到一个数字键就存入下一位,当存满4位时,状态跳转到CHECK。CHECK状态下执行比较逻辑:如果password_buf与预设密码逐位相等,拉高unlock信号,回到IDLE等待下一次输入;如果不相等,拉低unlock信号,清空password_buf,回到IDLE。
关于状态机的编码方式,我优先推荐使用一段式或两段式?不,我还是推荐经典的“三段式”。很多教材和培训喜欢讲一段式(整个状态机写在一个always块里),代码量少但可读性差,输出容易带毛刺。三段式将时序逻辑与组合逻辑分离,每个always块职责单一,综合器和仿真器的表现都更好,调试时也容易定位问题。
还需要强调的是:状态机的状态寄存器必须有可靠的复位逻辑。FPGA上电后,如果没有显式复位,状态寄存器是随机值,状态机会跑到非法状态去,整个模块就“死”了。所以每一段状态转移的时序逻辑里,都必须检查复位信号,确保上电先回到IDLE。
3.2 密码比较与错误计数逻辑
密码比较逻辑其实很简单:用两个4位的寄存器数组分别存储预设密码和输入密码,逐位对比。因为密码是数字0~9,正好用4位二进制数(BCD码)表示,比直接用ASCII码节省资源。
比较逻辑放在CHECK状态里执行:
// 密码比较模块核心逻辑 always @(*) begin if (password_buf[0] == PASSWORD[0] && password_buf[1] == PASSWORD[1] && password_buf[2] == PASSWORD[2] && password_buf[3] == PASSWORD[3]) password_ok = 1'b1; else password_ok = 1'b0; end这里有一个设计细节值得注意:密码错误时,我们不仅需要清空输入缓冲区,最好还给用户一个“错误次数”的反馈。我在设计中添加了一个错误计数器error_cnt,连续错误达到3次时,密码锁进入10秒的锁定状态(LOCK),期间任何按键都不响应。这个功能虽然简单,但很贴近真实产品的行为逻辑,也顺便让初学者理解了“定时锁定”状态的设计思路。
3.3 数码管动态扫描与显示策略
数码管显示属于FPGA里典型的动态扫描应用。8个数码管共用一组段选信号,通过快速轮流点亮各个位来实现“同时”显示的效果。人眼的视觉暂留效应大约在50ms,所以只要刷新频率不低于50Hz,看起来就是稳定的。
我在这个项目中使用的是六位数码管,前四位显示当前输入的密码(未输入的位显示为“-”),后两位显示状态信息,比如“OK”表示密码正确,“ER”表示密码错误。
动态扫描的时钟频率我这里选在1kHz左右,也就是用50MHz主时钟分频50000次得到。这个频率下,6个数码管轮流扫一遍大约需要6ms,刷新率约166Hz,完全满足无闪烁要求。分频系数计算公式:div_cnt = 50_000_000 / 1000 / 2。
如果数码管出现亮度不均或者闪烁,多半是扫描频率设置不当。太低了闪烁,太高了(超过几kHz)会出现“拖影”现象,因为每个位点亮时间太短导致亮度下降。这个时候调节分频系数即可。
4. 工程实现与上板调试流程
4.1 完整的工程文件结构
压缩包里包含了一套完整的Vivado工程(也兼容Quartus,只需要重新创建工程把源码文件添加进去就行)。工程目录结构如下:
├── src/ │ ├── top_module.v // 顶层模块,完成所有子模块例化 │ ├── key_debounce.v // 按键消抖模块 │ ├── password_controller.v // 密码控制状态机模块 │ ├── seg_driver.v // 数码管动态扫描模块 │ ├── beep_controller.v // 蜂鸣器控制模块 │ └── clk_divider.v // 时钟分频模块 ├── sim/ │ ├── tb_top.v // 顶层模块的测试激励 │ └── tb_password_controller.v // 密码控制器的独立测试 ├── constraint/ │ └── pin_constraints.xdc // 引脚约束文件 └── docs/ └── 设计文档.md // 详细的设计说明与引脚分配表引脚约束文件是我亲手根据自己使用的开发板写的,不同板卡的按键、数码管、蜂鸣器引脚位置差异极大,如果你用的是其他型号的板子,需要对照自己的原理图修改xdc文件中的引脚编号。
这里分享一个经验:引脚约束文件是整个工程里最容易出问题的地方。很多初学者代码写得没问题,但就是因为引脚没有约束对,导致上板毫无反应。建议拿到一个新的开发板时,先把原理图里所有需要用到的外设引脚列一个表格,逐个对照修改约束文件,不要盲目相信网上的模板。
4.2 从仿真到上板的关键调试步骤
整个调试流程我推荐按照“仿真验证→逻辑分析仪验证→上板验证”三步走。跳过仿真直接上板调试,属于“裸奔”,出了问题你会非常痛苦,因为板级调试的可见性太差了。
第一步,仿真阶段。tb_top.v文件里我写好了完整的测试激励,模拟了按键按下、释放的完整时序。测试用例包括:正确密码输入流程、错误密码输入流程、连续三次错误触发锁定等场景。运行仿真时,重点观察状态机的状态转移是否符合预期,特别是IDLE→INPUT→CHECK→IDLE这条主路径。
第二步,用ILA(Integrated Logic Analyzer)进行在线逻辑分析。Vivado提供了ILA核,可以直接在顶层模块中例化一个,探测状态机的当前状态、按键值、密码缓冲区等内部信号。这样比仿真更接近真实运行情况。
第三步才真正上板跑。上板之后如果出现问题,优先用ILA查,不要盲目改代码。比如按键没反应,就看看ILA里key_valid信号有没有拉高;有反应但数码管显示乱码,就去查段选信号和位选信号的时序关系是否正确。
4.3 我在调试中踩过的三个坑
第一个坑:复位信号的异步复位、同步释放处理。我开始做这个项目时,直接用了简单的异步复位,结果上电瞬间数码管偶尔会显示一帧乱码。后来查资料发现,最好的做法是做一个“异步复位、同步释放”的复位电路,即复位信号异步拉低,但释放时与时钟同步。这样能避免复位释放时刻恰好落在时钟边沿附近时引起的亚稳态问题。
第二个坑:按键消抖时间太短。我第一次测试时把消抖计数设成了2ms,结果按键按快了还是会出现一次按下被识别成两次的情况。后来我把消抖时间加到了20ms就稳定了。具体时间取决于你的按键机械特性,但20ms是一个比较保险的数值。
第三个坑:蜂鸣器的驱动方式。我用的开发板上蜂鸣器是无源蜂鸣器,需要给一个一定频率的方波才能发声,而不是简单的拉高电平。很多新手把蜂鸣器当成有源的来驱动,结果发现响不了或者声音不对。这里需要查看板卡原理图确认蜂鸣器是有源还是无源。如果是无源的,就需要生成一个2~4kHz左右的方波信号去驱动它。
5. 常见问题与排查技巧
5.1 仿真正常但上板无反应的排查思路
这是一个极其经典的问题,几乎每个FPGA初学者都会遇到。代码仿真跑得完美,下载到板子上却一片死寂。常见的排查顺序应该按以下路径走:
先确认时钟是否正常。用ILA抓一个信号的波形,或者用示波器直接测FPGA芯片的时钟引脚频率。很多时候板卡上有多个时钟源,你约束文件里写的时钟引脚可能根本就是错的。
再确认复位是否释放。开发板上常见的复位按键按下时是低电平,释放后是高电平。如果你的复位逻辑写反了,芯片会一直被复位拽住,整个设计根本无法运行。我见过太多人在这里翻车。
接着排查引脚约束。做一个最简单的LED闪烁设计,把IO口逐个测试一遍,确认引脚号和板卡上的位置对应关系无误。这一步虽然笨,但最可靠。
最后才去怀疑逻辑本身的问题。这时候ILA就该出场了——把状态机状态、按键信号都挂到ILA上,看看板子上真实运行时是什么情况。
5.2 按键偶尔失灵或误触发的处理
按键问题十有八九出在消抖逻辑上。我总结了一个排查表格:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 按键偶尔被识别成多次 | 消抖时间不足 | 检查消抖计数器时长是否大于20ms |
| 按键完全无响应 | 引脚约束错误或同步打拍缺失 | 检查xdc引脚、检查是否有同步寄存器 |
| 不同按键之间互相干扰 | 按键检测逻辑未做“释放检测” | 确认状态机是否等按键释放后才接收下一个输入 |
| 偶尔出现乱码值 | 信号跨时钟域或亚稳态 | 检查是否所有异步输入都做了打拍同步 |
5.3 如何扩展成更完整的密码锁系统
项目跑通之后,别急着收工,按下面几个方向去扩展,你的FPGA能力会再上一个台阶:
第一个方向是增加密码修改功能。这是最自然的扩展方向,需要在状态机里增加CHANGE状态,处理旧密码验证、新密码输入、新密码二次确认这三个子流程。
第二个方向是把独立按键换成矩阵键盘。矩阵键盘可以大大减少IO口占用,但扫描逻辑会复杂不少,需要处理行列扫描和按键释放检测,是个很好的进阶练习。
第三个方向是引入IIC或UART接口,用上位机配置密码。这需要你了解简单的通信协议,在FPGA里实现IIC主机或UART接收模块。做完这个扩展,你对FPGA通信接口的理解会有质的飞跃。
第四个方向是加入掉电保存功能。把密码存储在EEPROM(比如板载的AT24C04)里,掉电不丢失。这同样会牵涉到IIC通信,但比单纯的上位机配置更贴近真实产品需求——毕竟真实的密码锁,密码是不能因为断电就恢复出厂设置的。
6. 基于FPGA实现的性能优化思考
6.1 资源占用与功耗分析
这个简易密码锁整个工程的资源占用非常小。以Xilinx Artix-7系列芯片为例,综合后的资源占用大概只有几十个LUT和FF,连芯片资源的1%都不到。但这恰恰是学习的好项目——资源占用小意味着逻辑可以看得透,出了问题容易排查。
功耗方面,整个设计几乎可以忽略不计。但这引出一个值得思考的点:在实际应用场景中,电子密码锁通常是电池供电的,这时候功耗优化就变得极其重要。可以考虑的优化方向包括:空闲时关闭数码管扫描(进入低功耗模式)、使用低频时钟降低动态功耗、按键输入时再唤醒系统等。把这些思路加进去,设计就从“能跑”进步到“能用”了。
6.2 时序约束与设计可靠性
很多人觉得这个小项目随便跑跑就行,不按时序约束也没事。这种想法在简单设计中确实不会出大问题——毕竟逻辑很简单,时序很难不满足。但为了养成良好的设计习惯,我还是建议哪怕是小项目也做完整的时序约束。
主要需要约束的是时钟引脚(create_clock)和输入输出延时(set_input_delay/set_output_delay)。在Vivado中,可以在综合后打开Timing Summary看时序报告。如果出现时序违例,优先检查是不是跨时钟域的信号没有做同步处理。
6.3 项目设计的可复用性
这个项目的模块设计有很强的可复用性。key_debounce模块几乎不加修改就能用到任何需要按键输入的工程中;seg_driver模块可以适配不同位数的数码管;password_controller模块的状态机框架可以套用到各种需要多状态切换的控制逻辑里。
我后来做正式的FPGA项目时,确实多次回到了这个小项目中“抄”模块——按键消抖、数码管扫描、蜂鸣器驱动,稍微改改就能用。这就是模块化的价值所在:你在小项目里积累的这些“积木”,以后会撑起更大的设计。
拿到这个zip包后,我建议你按照这样的路径去学习:先跑一遍仿真,把状态机的行为理解透;再上板把默认密码跑通;然后自己动手改一改密码默认值、消抖时长、蜂鸣器声音频率这些参数,观察变化;最后再尝试自己加一个新功能模块进去。只有动手改过,这些代码才会真正变成你自己的东西。
我在做这个项目的过程中,最大的体会是:FPGA学习不怕做小项目,就怕不把基础打牢。状态机、消抖、动态扫描,这些基本功一旦掌握,后面不管做通信接口还是图像处理,心里都有底。希望这个项目能成为你FPGA学习之路上一个扎实的台阶。
本文还有配套的精品资源,点击获取