1. 项目概述与设计思路
这个基于Verilog的FPGA密码锁项目是我在嵌入式安全系统领域的一次实践探索。系统采用模块化设计思路,通过状态机实现密码验证、修改和清除等核心功能。在实际开发中,我发现FPGA非常适合这类需要实时响应和硬件级安全控制的应用场景。
系统默认初始密码为1234,支持通过4x4矩阵键盘进行交互。特别设计了几个功能键:
- 按键12:进入密码修改流程
- 按键13:清除当前输入
- 按键14:确认/开锁
- 按键15:退出当前操作
提示:在硬件连接时,建议将功能键(12-15)与数字键(0-9)采用不同颜色的按键帽区分,可以大幅降低用户误操作概率。
2. 硬件架构设计
2.1 核心模块划分
系统采用经典的三层架构设计:
- 输入层:矩阵键盘驱动模块(key_board.v)
- 逻辑控制层:密码锁主控模块(mimasuo.v)
- 输出层:
- 数码管显示
- 继电器控制
- 蜂鸣器报警
- 状态指示灯
2.2 关键硬件选型
根据项目需求,我们选择了以下硬件配置:
| 组件类型 | 具体型号 | 选择理由 |
|---|---|---|
| FPGA芯片 | Xilinx xc7a100t / Altera EP4CE6 | 性价比高,I/O资源充足 |
| 矩阵键盘 | 4x4薄膜键盘 | 成本低,防尘性能好 |
| 数码管 | 4位共阴数码管 | 驱动简单,亮度高 |
| 继电器 | SRD-05VDC-SL-C | 5V驱动,触点容量10A |
在实际部署时,我发现薄膜键盘的触点寿命约10万次,如果用在需要高频输入的场景,建议选用机械轴键盘模块。
3. 核心代码实现解析
3.1 键盘扫描与消抖处理
键盘驱动模块采用状态机实现扫描和消抖,这是确保系统稳定性的关键。以下是优化后的消抖逻辑:
verilog复制// 消抖计数器参数
parameter DEBOUNCE_TIME = 10000; // 对应10ms@100MHz时钟
// 消抖状态机
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
debounce_cnt <= 0;
key_stable <= 0;
end
else begin
if(key_raw != key_last) begin
debounce_cnt <= 0;
key_last <= key_raw;
end
else if(debounce_cnt < DEBOUNCE_TIME) begin
debounce_cnt <= debounce_cnt + 1;
end
else begin
key_stable <= 1;
end
end
end
经验分享:消抖时间需要根据实际键盘特性调整。通过示波器观察,我发现薄膜键盘抖动时间通常在5-20ms,因此设置10ms消抖时间能兼顾响应速度和稳定性。
3.2 密码存储与验证
密码存储采用寄存器实现,关键代码如下:
verilog复制reg [15:0] stored_pwd = 16'h1234; // 默认密码
reg [15:0] input_pwd;
reg [2:0] input_cnt;
// 密码输入处理
always @(posedge clk) begin
if(key_valid && key_value <= 9) begin // 只处理数字键
input_pwd[input_cnt*4 +:4] <= key_value[3:0];
input_cnt <= input_cnt + 1;
end
end
// 密码验证
assign pwd_match = (input_pwd == stored_pwd);
在实测中发现,直接比较密码存在安全隐患。改进方案是对比哈希值而非明文:
verilog复制// 简单哈希函数
function [15:0] pwd_hash;
input [15:0] pwd;
begin
pwd_hash = {pwd[3:0], pwd[7:4], pwd[11:8], pwd[15:12]} ^ 16'h55AA;
end
endfunction
4. 状态机设计与实现
4.1 主控制状态机
系统采用两级状态机架构,顶层状态机如下:
verilog复制typedef enum {
IDLE, // 待机状态
INPUT_PASSWORD, // 密码输入
VERIFY_PASSWORD,// 密码验证
CHANGE_PASSWORD,// 修改密码
UNLOCKED // 开锁状态
} system_state_t;
4.2 密码修改流程实现
密码修改是系统最复杂的功能,其状态转换如图:
code复制[IDLE]
|-- KEY12--> [INPUT_OLD_PWD]
|-- 输入完成 --> [VERIFY_OLD_PWD]
| |-- 成功 --> [INPUT_NEW_PWD]
| |-- 失败 --> [IDLE]
|-- KEY13--> [IDLE]
关键实现代码:
verilog复制always @(posedge clk) begin
case(state)
INPUT_OLD_PWD: begin
if(key_valid) begin
if(key_value == 13) begin // 取消
state <= IDLE;
end
else if(input_cnt == 3) begin // 输入完成
state <= VERIFY_OLD_PWD;
end
end
end
VERIFY_OLD_PWD: begin
if(pwd_match) begin
state <= INPUT_NEW_PWD;
stored_pwd_temp <= input_pwd;
end
else begin
state <= IDLE;
beep_error();
end
end
endcase
end
5. 仿真与测试方案
5.1 测试用例设计
为确保系统可靠性,我设计了以下测试场景:
-
正常开锁流程:
- 输入默认密码1234
- 验证继电器是否吸合
- 验证数码管显示"A"
-
密码修改流程:
- 触发修改模式(按键12)
- 输入原密码1234
- 输入新密码5678
- 验证新密码是否生效
-
错误处理测试:
- 连续3次错误密码
- 验证蜂鸣器报警模式
- 验证系统是否锁定
5.2 Vivado仿真技巧
在Vivado仿真时,我发现几个实用技巧:
- 使用TCL脚本自动化仿真:
tcl复制launch_simulation
run 100us
add_wave /tb/uut/*
- 关键信号触发设置:
verilog复制initial begin
// 错误密码测试
press_key(1); press_key(2); press_key(3); press_key(4);
#100;
check_lock_status(0); // 验证未解锁
// 正确密码测试
press_key(1); press_key(2); press_key(3); press_key(4);
#100;
check_lock_status(1); // 验证已解锁
end
6. 常见问题与解决方案
6.1 键盘响应不稳定
现象:偶尔出现按键无响应或重复触发
解决方案:
- 检查消抖参数是否合适
- 确保时钟信号质量
- 在PCB布局时缩短键盘走线
6.2 数码管显示闪烁
现象:数码管亮度不均或闪烁
解决方法:
verilog复制// 增加扫描频率到1kHz以上
parameter SCAN_RATE = 1000; // 1kHz
always @(posedge clk) begin
if(scan_cnt == (CLK_FREQ/SCAN_RATE)) begin
scan_cnt <= 0;
seg_select <= seg_select + 1;
end
else begin
scan_cnt <= scan_cnt + 1;
end
end
6.3 密码存储易失失
风险:FPGA掉电后密码重置
改进方案:
- 外接EEPROM存储密码
- 使用FPGA的配置存储器(需芯片支持)
- 添加备用电池供电
7. 性能优化建议
通过实际测试,我总结出以下优化方向:
-
资源优化:
- 使用LUT6实现状态机
- 共享加法器等运算单元
- 优化数码管扫描逻辑
-
功耗优化:
- 动态时钟门控
- 空闲状态降频
- 分段唤醒机制
-
安全增强:
- 增加输错次数限制
- 实现密码加密存储
- 添加防暴力破解延时
这个项目从构思到实现大约花费了两周时间,期间最大的收获是理解了硬件描述语言与实际电路的关系。特别是在调试键盘扫描电路时,通过示波器观察信号波形,让我对时序控制有了更直观的认识。建议初学者可以先用仿真验证基本功能,再逐步移植到实际硬件,这样可以节省大量调试时间。