1. 项目概述:FPGA矩阵键盘密码锁设计
这个基于Verilog HDL实现的FPGA密码锁系统,是我在嵌入式安全领域的一次有趣实践。系统采用矩阵键盘作为输入设备,通过状态机设计实现密码验证、修改等核心功能。与常见的单片机方案相比,FPGA的并行处理特性使其在响应速度和抗干扰性方面表现更优,特别适合对实时性要求较高的门禁场景。
整个系统的工作流程可以拆解为:矩阵键盘扫描→键值解码→密码比对→状态切换→输出控制。其中最具挑战性的部分在于如何优雅地处理矩阵键盘的抖动问题,以及设计可维护的状态机架构。我在Xilinx Artix-7开发板上实现了这个项目,实测输入响应时间小于5ms,密码比对过程仅需1个时钟周期。
2. 核心模块设计与实现
2.1 矩阵键盘接口设计
4x4矩阵键盘的接口设计是系统的基础模块。我采用行列扫描法,通过FPGA的GPIO口轮流驱动行线(输出),同时读取列线(输入)状态。具体实现时需要注意:
verilog复制always @(posedge clk) begin
case(scan_counter)
2'b00: row <= 4'b0111;
2'b01: row <= 4'b1011;
2'b10: row <= 4'b1101;
2'b11: row <= 4'b1110;
endcase
col_data <= col;
scan_counter <= scan_counter + 1;
end
关键技巧:扫描频率建议设置在1-5kHz范围内,过高会导致功耗增加,过低则可能错过快速按键。实测发现2kHz是最佳平衡点。
消抖处理我采用了三级移位寄存器+计数器的方式,只有连续3次采样到相同键值才认为有效:
verilog复制// 消抖滤波器
always @(posedge clk) begin
shift_reg <= {shift_reg[1:0], raw_key};
if(&shift_reg) debounce_cnt <= debounce_cnt + 1;
else debounce_cnt <= 0;
if(debounce_cnt == DEBOUNCE_MAX)
stable_key <= shift_reg[2];
end
2.2 密码存储与验证机制
密码存储采用寄存器数组实现,支持4位数字密码。为增强安全性,密码寄存器在硬件复位时不会清零:
verilog复制reg [3:0] password [0:3];
reg [3:0] temp_password [0:3]; // 用于密码修改
always @(posedge clk) begin
if(reset) begin
// 初始化默认密码1234
password[0] <= 4'd1;
password[1] <= 4'd2;
password[2] <= 4'd3;
password[3] <= 4'd4;
end
else if(store_new_pw) begin
password <= temp_password;
end
end
密码验证采用完全并行比较,在一个时钟周期内完成:
verilog复制wire pw_match = (input_buffer[0] == password[0]) &&
(input_buffer[1] == password[1]) &&
(input_buffer[2] == password[2]) &&
(input_buffer[3] == password[3]);
2.3 状态机设计
系统采用Moore型状态机,定义以下主要状态:
verilog复制typedef enum {
IDLE, // 待机状态
INPUT, // 输入密码中
VERIFY, // 验证密码
CHANGE_PW, // 修改密码模式
NEW_PW_INPUT, // 输入新密码
CONFIRM_PW, // 确认新密码
ACCESS_GRANTED,// 验证通过
ACCESS_DENIED // 验证失败
} state_t;
状态转移逻辑示例:
verilog复制always @(posedge clk or posedge reset) begin
if(reset) begin
state <= IDLE;
end else begin
case(state)
IDLE:
if(key_pressed == 4'hC) // 按C进入修改密码
state <= CHANGE_PW;
else if(key_valid)
state <= INPUT;
INPUT:
if(input_cnt == 4)
state <= VERIFY;
// 其他转移条件...
endcase
end
end
3. 关键功能实现细节
3.1 密码修改功能实现
通过特定组合键(代码中定义为"12")进入密码修改流程:
- 首先输入原密码验证
- 验证通过后输入新密码(4位)
- 再次确认新密码
- 两次输入一致则更新密码寄存器
verilog复制always @(posedge clk) begin
if(state == NEW_PW_INPUT) begin
if(key_valid) begin
temp_password[input_cnt] <= key_value;
input_cnt <= input_cnt + 1;
end
end
else if(state == CONFIRM_PW) begin
if(key_valid) begin
if(key_value != temp_password[input_cnt])
pw_mismatch <= 1;
input_cnt <= input_cnt + 1;
end
end
end
3.2 输入超时处理
为防止卡死,我设计了30秒输入超时机制:
verilog复制reg [31:0] timeout_counter;
always @(posedge clk) begin
if(state != IDLE) begin
if(timeout_counter == TIMEOUT_VALUE) begin
state <= IDLE;
timeout_counter <= 0;
end
else timeout_counter <= timeout_counter + 1;
end
else timeout_counter <= 0;
end
3.3 输出指示设计
使用LED和蜂鸣器提供操作反馈:
- LED[0]:系统电源指示(常亮)
- LED[1]:输入状态指示(闪烁)
- LED[2]:验证通过(常亮2秒)
- LED[3]:验证失败(快速闪烁)
- 蜂鸣器:按键提示音(短促)、错误报警(长鸣)
4. 工程优化与调试经验
4.1 时序约束关键点
为保证矩阵键盘扫描稳定性,必须添加合适的时序约束:
code复制create_clock -period 20.000 -name clk [get_ports clk]
set_input_delay -clock clk 2 [get_ports {col[*]}]
set_output_delay -clock clk 1 [get_ports {row[*]}]
4.2 资源利用率优化
在Artix-7 XC7A35T上的资源占用:
- LUT: 423/20800 (2%)
- FF: 287/41600 (<1%)
- IO: 12/200 (6%)
通过以下方法优化:
- 共享计数器:扫描计数器和超时计数器复用
- 状态编码:使用格雷码而非二进制编码
- 并行比较:替代串行比较节省时钟周期
4.3 常见问题排查
-
按键无响应:
- 检查行列线接法是否正确
- 测量扫描信号频率(应在1-5kHz)
- 确认消抖参数是否合适
-
密码验证不稳定:
- 检查时钟域是否一致
- 验证输入缓冲区的清零逻辑
- 确认密码寄存器未被意外复位
-
修改密码失败:
- 检查temp_password存储时序
- 验证两次输入比对逻辑
- 确认状态机转换条件
5. 功能扩展建议
- 增加密码复杂度检测:
verilog复制wire pw_weak = (password[0]==password[1]) &&
(password[1]==password[2]) &&
(password[2]==password[3]);
- 实现多用户密码:
扩展密码寄存器为二维数组:
verilog复制reg [3:0] password [0:7][0:3]; // 支持8组密码
- 添加胁迫密码功能:
定义特殊密码触发隐蔽报警:
verilog复制wire duress_pw = (input_buffer == 16'h4321);
- 记录操作日志:
使用片上Block RAM存储最近10次操作记录:
verilog复制reg [7:0] log_ram [0:9];
reg [3:0] log_ptr;
这个项目最让我惊喜的是FPGA在实时控制方面的优势 - 当输入密码最后一个数字时,验证结果实际上在同一个时钟边沿就已经准备好了。相比之下,基于中断的单片机方案至少会有几十微秒的延迟。如果让我重新设计,我会考虑加入PUF(物理不可克隆函数)技术来增强密码存储的安全性。