尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA电子密码锁设计与实现:从状态机到Verilog实战

FPGA电子密码锁设计与实现:从状态机到Verilog实战 简介面向FPGA初学者的电子密码锁完整工程适合数字逻辑、EDA技术或FPGA课程设计及入门练习。项目实现6位十进制密码锁由四个独立按键分别控制系统启动、密码自增、自减与确认密码逐位输入时数码管左移显示校验正确亮绿灯系统运行红灯指示默认密码可在代码中修改方便自定义。资源共147个文件压缩包仅3.36MB核心文件包括tdf设计源文件、Verilog源码、bdf原理图、vwf仿真波形、sof/pof下载配置及rpt报告等目录结构清晰可完整复现从代码设计、综合仿真到下载验证的流程。已有1085人学习该资源。通过工程内的按键消抖、数码管动态扫描、状态机控制、密码校验等模块读者能快速理解FPGA组合逻辑和时序逻辑的配合方法同时提供的仿真波形和项目说明便于对照排查问题、修改密码或扩展功能整体是一份实用且易上手的课设参考模板。1. 从按键到门锁FPGA电子密码锁到底在做什么FPGA 电子密码锁是数字逻辑课与嵌入式实战交界处最常见也最容易讲透的项目。它的技术核心不是“锁”这个物理对象而是把按键输入、数据存储、状态比较、使能输出这一整套流程用硬件状态机描述出来。你按下的每一个键经过消抖、编码后在时钟节拍里与预设密码逐位比较所有位全部对齐才拉高开锁信号。相比单片机方案FPGA 最大的优势是时序透明状态转移可以逐拍追踪极少出现“逻辑明明对但跑起来不工作”的黑盒子问题。因此它既适合刚接触 Verilog 的人练手也是理解有限状态机、时钟分频和引脚约束最直观的载体整个设计对芯片资源和时钟频率的要求都很低入门级开发板就能跑通。2. FPGA电子密码锁的模块划分与选型思路从矩阵键盘到开锁驱动2.1 独立按键还是矩阵键盘先决定面板物理形态电子密码锁的输入面板通常有两种形态一种是开发板上自带的几个独立按键另一种是 3×4 或 4×4 的矩阵键盘。独立按键的好处是省去扫描逻辑一个按键占用一个 FPGA 引脚适合验证状态机本身坏处是将来要扩到 0-9、#、* 十几个键时引脚不够也不符合真实门锁的面板形态。我一般建议毕设或产品原型直接上矩阵键盘。矩阵扫描的常规做法是列线输出低电平、行线回读分时点亮每一列这样 4×4 键盘只需要 8 个引脚。这里最容易翻车的是回读毛刺列切换瞬间的 RC 充放电会产生短暂的不确定电平如果不在行回读前插入几个空时钟周期一次按键会被识别成两三次。因此扫描模块至少要拆成列驱动和行读回两个环节中间插一个固定延时窗口。2.2 消抖与按键编码把物理抖动变成单周期脉冲按键按下时机械触点在 5 到 20 毫秒内会反复通断。以 50 MHz 系统时钟计算20 毫秒对应 100 万个时钟周期用计数器做消抖窗口非常容易。消抖本质上是低通滤波不是把信号“变得干净”而是持续采样到同一个电平一段时间后才认为有效这能避开按键抖动产生的多个边沿。下面是一个可综合的消抖模块加入了常见的两步同步降低单比特信号跨时钟域时的亚稳态风险// 按键消抖连续采样稳定 CNT_MAX 拍后输出稳定电平 module debounce #( parameter CNT_MAX 1_000_000 // 20ms 50MHz )( input wire clk, input wire key_in, // 原始按键电平 output reg key_out // 消抖后电平 ); reg [19:0] cnt; reg [1:0] sync; always (posedge clk) begin sync {sync[0], key_in}; // 两步同步 if (sync[1] ! sync[0]) begin cnt 20d0; // 仍在抖动清零重新计数 end else if (cnt CNT_MAX) begin key_out sync[0]; // 稳定时间足够确认电平 end else begin cnt cnt 1; end end endmodule这段代码把 key_in 先打两拍避免直接采集外部信号时出现亚稳态。计数器只在同步后的前后两拍值相同时才累加一旦检测到跳变就清空所以抖动期间不会误输出。实际使用时还要在消抖输出后面再跟一个检测上升沿的组合逻辑生成单周期脉冲 key_pulse状态机只认脉冲不认电平否则按键一直按住时状态机会被反复触发。2.3 键值编码和密码存储寄存器堆比RAM好用消抖和扫描之后每个按键要对应一个固定的键值码。四位二进制能表示 0-15正好装下 0-9、#、* 和一些功能键。下表是常见编码约定按键key_code含义04b0000数字 01-94b0001 ~ 4b1001数字 1-9#4b1010当前密码输入结束触发校验*4b1011清除当前已输入内容功能键 A4b1100进入修改密码模式密码位数的存储我倾向用移位寄存器而不是 RAM。4 位密码用 16 比特 reg 就能放下每一位存成 4 比特 BCD 码直接一个左移拼接就能完成输入缓冲。如果密码长度是 6 位就需要 24 比特8 位需要 32 比特对 FPGA 来说都是微不足道的寄存器资源。相比用 Block RAM 按地址写、按地址读寄存器堆不需要地址译码综合后的时序也更可控。3. 用Verilog写电子密码锁核心状态机从IDLE到OPEN再到锁定3.1 状态定义与独热码编码先列出所有可能状态电子密码锁的状态机从上电复位到一次完整开锁至少要包含 IDLE、INPUT、CHECK、OPEN、LOCKED 这五个基本状态。更完整的设计还应该加上 CHANGE 状态用于输入旧密码通过后的在线改密流程。把状态拆细在仿真阶段收益很大比如单独留一个 CHECK 状态示波器或在线逻辑分析仪上能清楚看到校验结果是在哪个周期产生的。状态编码我建议使用独热码而不是二进制编码状态独热码进入条件退出条件IDLE5b00001复位或定时结束收到有效按键脉冲INPUT5b00010IDLE 收到按键收到 # 确认键CHECK5b00100# 键触发校验结果落定OPEN5b01000校验通过开锁定时结束LOCKED5b10000连续错误达到次数锁定时长结束独热码状态译码用一条相等比较就能完成组合逻辑延迟最小时序分析时不常出现列选通毛刺。缺点是状态数量多一个就多一个寄存器但对这种几百个 LUT 的小设计完全无感。3.2 三段式状态机模板次态、现态和输出分开写三段式状态机是 FPGA 开发的常见套路分别用两个时序块寄存 state 和输出用一个组合块计算 next_state。这样做最大的好处是检查状态跳转逻辑时不会被输出逻辑干扰出现问题时能快速定位故障属于组合判断还是寄存器时序。module lock_fsm( input wire clk, input wire rst_n, input wire key_pulse, // 消抖后的单周期按键脉冲 input wire [3:0] key_code, // 按键编码 output reg unlock, // 开锁信号 output reg err_ind // 错误提示可接LED ); localparam IDLE 5b00001; localparam INPUT 5b00010; localparam CHECK 5b00100; localparam OPEN 5b01000; localparam LOCKED 5b10000; reg [4:0] state, next_state; reg [15:0] secret_pwd; // 预设密码4位BCD reg [15:0] input_buf; // 当前输入缓冲 reg [1:0] err_cnt; // 错误计数最大3 wire pwd_match (input_buf secret_pwd); // 第一段现态寄存 always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next_state; end // 第二段次态组合跳转 always (*) begin next_state state; case (state) IDLE: begin if (key_pulse) next_state INPUT; end INPUT: begin if (key_pulse key_code 4b1010) // # 键 next_state CHECK; end CHECK: begin if (pwd_match) next_state OPEN; else if (err_cnt 2d2) next_state LOCKED; // 连续错满3次 else next_state IDLE; // 未满次数下次重新输入 end OPEN: begin next_state IDLE; // 开锁定时后自复位 end LOCKED: begin if (lock_timer_full) next_state IDLE; end endcase end // 第三段输出与错误计数 always (posedge clk or negedge rst_n) begin if (!rst_n) begin unlock 1b0; err_cnt 2d0; end else begin if (state CHECK !pwd_match) err_cnt err_cnt 2d1; else if (state LOCKED) err_cnt 2d0; if (state OPEN) unlock 1b1; else unlock 1b0; end end endmodule注意第二个 always 块里没有遗漏默认赋值开头先写next_state state;再进 case这是防止组合逻辑产生锁存器的标准写法。err_cnt 的增长没有放在组合块里而是在第三个时序块中根据 CHECK 状态和比较结果触发这样连续按错三次时每次错误只会计数一次不会因为同一轮校验重复自增。unlock 的赋值也不要做成锁存模式除了 OPEN 状态外直接置低综合后不会生成意外的存储单元。3.3 错误计数与定时锁定防暴力试探的最简实现错误计数和锁定定时是成品电子密码锁的标配没有这一步的密码锁在安全意识上是不合格的。上面的代码已经把错误计数放在第三段现在要补的是 lock_timer_full 信号一个自由运行的计数器在 LOCKED 状态下从零累加计到 30 秒对应的周期数后拉高。注意这个计数器只在 LOCKED 时使用其他状态要保持清零避免重新进入 LOCKED 时有残留计数值。调试时最常遇到的坑是错误计数提前复位。有些设计把复位条件写成state ! LOCKED结果从 CHECK 回到 IDLE 的瞬间计数被清掉第二次按错又从零开始永远进不了锁定。正确的做法是只在 LOCKED 状态内清零或者干脆做成独占复位条件IDLE 状态里只负责等待按键不碰计数。4. 在Vivado或Quartus里完成约束、仿真和板级调试4.1 最小工程结构与芯片选型LUT资源和引脚数量哪个更关键拿到一块入门级开发板无论上面是 Artix-7、Cyclone IV 还是国产的高云或安路芯片电子密码锁的资源消耗都很小几十个 LUT、几十个寄存器就够用。真正决定选型的是引脚数量要接矩阵键盘、数码管、LED、蜂鸣器、电磁锁驱动和扩展接口引脚少于 50 的芯片会非常紧张。因此建工程前先查板卡的引脚分配表确认用到的外设都能映射到可用引脚上。如果工程向导里找不到板卡对应的芯片型号通常是器件支持包没装全需要去厂商工具网站补装对应系列而不是随便选一个相近型号代替不然综合后引脚和约束文件全部对不上。4.2 约束文件怎么写给后续排错留余地约束文件至少要覆盖主时钟、复位、按键输入和开锁输出四类信号。没有时钟约束的工程时序分析结果没有参考意义Vivado 会默认一个非常宽松的时钟周期综合和实现也能过但时序报告完全失真。以 50 MHz 外部晶振为例最少要写下面这几行create_clock -period 20.000 -name sys_clk [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports {clk}] set_property IOSTANDARD LVCMOS33 [get_ports {rst_n}] set_property IOSTANDARD LVCMOS33 [get_ports {key_col0}] set_property PACKAGE_PIN E3 [get_ports {clk}] set_property PACKAGE_PIN C4 [get_ports {rst_n}] set_property PACKAGE_PIN A2 [get_ports {key_col0}]IOSTANDARD 要和板卡 Bank 电压严格一致3.3V 的管脚写成 LVCMOS25 后上板电平不确定严重时会损伤引脚。若按键输入距离 FPGA 较远还应补 input delay 约束例如set_input_delay -clock sys_clk -max 5 [get_ports {key_col*}]让时序分析把走线延迟纳入计算。不写这条约束综合工具会默认外部信号与时钟边沿对齐板上延迟稍大就会出现偶发采样错误。4.3 testbench这样写才能快速暴露状态机问题仿真测试要覆盖三条核心路径正常密码开锁、错误密码未满次数的恢复、错误满三次后的锁定。下面这个 testbench 跑的是正常开锁流程它用一个 task 模拟每次按键的单周期脉冲module tb_lock_fsm; reg clk 0; reg rst_n 0; reg key_pulse 0; reg [3:0] key_code 0; wire unlock; wire err_ind; lock_fsm uut( .clk(clk), .rst_n(rst_n), .key_pulse(key_pulse), .key_code(key_code), .unlock(unlock), .err_ind(err_ind) ); always #10 clk ~clk; // 50MHz时钟 task press(input [3:0] code); begin key_pulse 1; key_code code; #40; key_pulse 0; #40; end endtask initial begin #10 rst_n 1; press(4d1); press(4d2); press(4d3); press(4d4); press(4d10); // 按#触发校验 #200; $display(unlock%b, unlock); $stop; end endmodule里面 task press 把 key_pulse 拉高两个时钟周期再拉低模拟一次完整的有效按键。如果仿真结果里 unlock 没有按预期拉高建议在 testbench 里加一句打印 state通过状态编码判断是卡在 INPUT 还是 CHECK。时序仿真里最容易被忽略的是复位释放后的第一个时钟沿如果 rst_n 释放时间和 clk 上升沿靠得太近第一拍状态可能采样不到从而多花一到两个时钟周期才进入 IDLE。4.4 上板调试顺序先看最小系统再看状态跳转上板和仿真完全是两种排错思路仿真看不到引脚毛刺和外部干扰。我的调试顺序固定为四步先确认时钟引脚上有正确频率的波形再确认复位释放后 LED 有反应然后按单键观察消抖输出最后才连上完整状态机。如果时钟正常但按键无响应先测按键在 FPGA 引脚上静态是高还是低很多板载按键默认上拉到高电平按下为低而为高的时候 key_in 可以用逻辑分析仪里键值来对齐。遇到状态机总是来回跳的情况直接使用 ILA 在线逻辑分析仪抓 state 信号触发条件设为state CHECK立刻能看出是校验失败还是跳转条件被时序阻塞这比一段段翻代码快得多。5. 上生产前必调的三个参数掉电存储、开锁脉冲和复位策略5.1 把密码搬进EEPROM或SPI Flash才能掉电保存FPGA 的寄存器密码一掉电就丢演示阶段无所谓要做成产品就必须外挂非易失存储。最省事的方案是挂一颗 I2C 接口的 EEPROM容量 2Kbit 就足够上电后由状态机从 EEPROM 读出密码并做一次校验确认读回数据合法后开放按键输入。要注意 I2C 速率一般只有 100KHz 或 400KHzFPGA 主频是 50MHz 以上必须单独做一个 I2C 分频时序不能让主状态机直接跨两个时钟域去操作 EEPROM。如果你的板卡上有一颗 SPI Flash例如 W25Q16 这类常用型号也能存密码但 SPI Flash 是按页写入而不是按字节任意改写写一页前必须先擦除整个扇区做起来比 I2C EEPROM 繁琐除非 Flash 本身闲置且想顺带实现 FPGA 在线升级否则我更建议老老实实挂 I2C。5.2 开锁脉冲不是越宽越好还要避开默认上电解锁开锁信号的持续时间要匹配电磁锁动作时间。我一般设置在 0.5 到 1 秒之间太短锁舌还没完全复位就断电长期使用会加速机械结构磨损太长则白白耗电。驱动方式上很多入门板直接让 FPGA 引脚去推三极管再驱动 MOSFET这种做法要格外留意开漏和上拉的配合。假如你的驱动板是低电平触发开锁输出就应该设计成默认高阻或上拉而不是上电时为低否则 FPGA 配置完成前输出未定状态就可能出现上电瞬间误开锁的现象。// 低电平有效的开锁输出示例默认置高避免上电误开锁 parameter OPEN_TIME_CNT 25_000_000; // 0.5s 50MHz reg open_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin open_cnt 0; end else if (state OPEN) begin if (open_cnt OPEN_TIME_CNT) begin open_cnt 0; end else begin open_cnt open_cnt 1; end end else begin open_cnt 0; end end assign lock_out (state OPEN) ? 1b0 : 1b1; // 低电平脱锁上面这个 assign 把输出定义成低电平使能方式FPGA 配置完成前外部驱动电路的主控端默认是高电平不会误触发开锁。如果你的电磁锁驱动板是高电平触发就把这段逻辑取反但务必要确认上电瞬间驱动板的主控端是被电阻拉死的而不是悬空。5.3 错误阈值、锁定时间与ILA抓状态验证错误次数阈值到底选 3 次还是 5 次取决于场景。家用门锁我倾向 5 次配 10 秒短锁定减少老人小孩误触造成的长时间等待仓储柜这类防暴力试探需求高的用 3 次配 60 秒以上。这两个参数做成顶层 parameter 后现场部署时只需要改约束或者由上位机下发配置就能调整不需要为不同场景重新综合一版固件。验证锁定逻辑最直接的方式是打开 ILA把触发条件设置为state LOCKED然后故意输错三次密码观察 err_cnt 是否依次自增到 3锁定期间触发条件只命中一次。如果 err_cnt 一次跳了两个数说明按键消抖后产生了多余脉冲问题多半出在扫描模块而不是状态机本身。这种验证方法比示波器量引脚信号可靠ILA 抓的是 FPGA 内部真实寄存器值能把错误计数、状态编码、开锁信号放在同一时间轴上看因果顺序。调试通过后记得把这几个参数收敛成一组适合批量生产的默认值并把 ILA 的调试核从最终工程里移除避免占用额外的 BRAM 布线资源。本文还有配套的精品资源点击获取
返回列表