写在前面:本设计仅供学习参考,不保证正确,免费分享,恳请关注一下
源码来自大佬:http://t.csdn.cn/Oxtcg 稍作改动
实物演示视频:基于FPGA的电子密码锁,Verilog HDL语言实现_哔哩哔哩_bilibili
基于FPGA的电子密码锁设计
摘要
基于FPGA的电子密码锁已经是现代生活中经常用到的工具之一,用于各类保险柜、房门、防盗门等等。用电子密码锁代替传统的机械式密码锁,克服了机械式密码锁密码量少、安全性能差的缺点。由于采用的是可编程逻辑器件FPGA,使得系统有相当大的灵活性,随时可以进行硬件升级、扩展。而且由于硬件可升级,还可随时增加密码位数或增加新的功能,使得密码锁有更高的安全性、可靠性和方便性。因此对于基于FPGA的电子密码锁的探讨和研究具有较大的理论意义和实践意义。
本次基于现场可编程门阵列FPGA的电子密码锁设计方法。课题主要解决系统硬件和软件两方面的问题。硬件方面要解决FPGA可编程器件与其外围电路的接口设计的问题;软件方面主要问题是利用Verilog HDL语言完成基于FPGA的电子密码锁的编程问题。除此之外,程序还要完成基本的密码开锁功能。本设计是由FPGA可编程逻辑器件编程实现的控制电路,具体有按键指示、密码有效指示、控制开锁、控制复位等功能。它具有安全可靠、连接方便、简单易用、结构紧凑、系统可扩展性好等特点。
关键字:电子密码锁;现场可编程门阵列;Verilog;仿真验证
一、课程设计任务及要求
1.1设计要求
1.2 设计报告要求
二、总体电路设计
2.1总体设计方案
2.2设计方案比较
2.3总体设计流程图
三、模块设计
3.1初始化模块
3.2显示模块
3.3分频模块
3.4消抖模块
3.5密码处理模块
3.6蜂鸣器模块
四、管脚绑定
4.1管脚绑定表
4.2管脚绑定说明
五、仿真及FPGA开发板调试
5.1仿真调试
5.2开发板整体调试
六、 设计总结与心得体会
6.1总结与心得
6.2成本分析
七、参考文献
八、附录
附录 1 RTL 代码:
附录 2 Testbench 代码
1.1设计要求
(1)设计一个密码锁的控制电路,当输入正确代码时,输出开锁信号以推动执行机构工作,用红灯亮、绿灯熄灭表示关锁,用绿灯亮、红灯熄灭表示开锁。
(2)在锁的控制电路中储存一个可以修改的4位代码,当开锁按钮开关(可设置成6位至8位,其中实际有效为4位,其余为虚设)的输入代码等于储存代码时,开锁。
(3)从第一个按钮触动后的5秒内若未将锁打开,则电路自动复位并进入自锁状态,使之无法再打开,并由扬声器发出持续20秒的报警信号。
1.2 设计报告要求
(1)分析系统的工作原理。
(2)画出顶层原理图,写出顶层文件源程序。
(3)写出各功能模块的源程序。
(4)仿真各功能模块,要求有仿真波形。
(5)书写实验报告应结构合理,层次分明。
2.1总体设计方案
采用可编程逻辑器件FPGA,成本较低,性能优秀,可以使得系统有相当大的灵活性,随时可以进行硬件升级、扩展。而且由于硬件可升级,还可随时增加密码位数或增加新的功能,使得密码锁有更高的安全性、可靠性和方便性。
在电路设计方面,我们可以用同bit位宽的寄存器把密码寄存起来,在按键时不断将现在按键的值与密码的寄存器对比,当所有对应位都相同时,蜂鸣器响,可以用一个标志位表示所有对应位相同,蜂鸣器工作的条件就是标志位给出的。
由于需要按键手动修改密码,那么液晶屏显示就有两种状态,一种是修改密码,一种是输入密码。我通过增加一个按键来控制这两种状态,当按下这个按键时进入修改密码阶段,当再次按下时进入输入密码阶段。
并且由于开关的波动,可能会产生抖动导致密码输入错误,为此设计了消抖模块,对需要用到的按键进行消抖。
2.2设计方案比较
FPGA设计密码锁相比其他方案的好处包括以下几个方面:
(1)灵活性:FPGA可以灵活地实现各种密码锁的功能和算法,可以根据用户需求、安全要求和硬件资源的情况进行优化和定制,使其更加适合具体的应用场景。
(2)可编程性:FPGA可以被编程多次,可以随时升级和修改密码锁的算法,以应对新的安全威胁和攻击手段。
(3) 高性能:FPGA可以实现并行计算,具有高性能和低延迟的特点,能够快速响应和处理密码验证请求,降低用户等待的时间。
(4)高安全性:FPGA设计密码锁可以实现硬件加密和安全认证,防止密码泄露和攻击,提高系统的安全性和可靠性。
(5)低功耗:FPGA设计密码锁可以利用其优异的低功耗性能,降低系统的能耗和运行成本。
除了FPGA以外,还有不少方法能实现电子密码锁功能,可以利用非编程电子元件电路、51单片机,STM32等实现电子密码锁功能,但是存在很多缺点和问题,不能便捷的修改密码,安全性低,易损坏。
2.3总体设计流程图
3.1初始化模块
初始化主要是针对FPGA内部有记忆的单元,例如寄存器、BLOCK RAM等,而对于无记忆的单元,例如硬件连线,没有必要也无法对它们赋初值。而对于支持赋初值的FPGA芯片,我们可以利用这一特性让FPGA在上电后就处于一个可被确定的状态。除此以外,赋初值对于仿真也有类似的影响。
运用initial语句给变量赋初值,初始密码为“00000000”,d48为0的ASCLL值,初始化完以后,就显示空字符让初始化显示的地址。按键按下后,数字会加一,9加一变为10,10以后跳会0,所以数字变量设为9,ASCLL码值为57。同理,初始化字母为小写z,按键按下后,字母会加一,z加一变为a,所以字母变量设为9,ASCLL码值为122。
3.2显示模块
本次设计中在FPGA开发板外接了一个LCD1602,相比于数码管,LCD1602显示更加直观,能显示大小写字母和数字等。可以显示16列2行的字符,LCD1602显示的原理 跟 8* 8的点阵显示原理很相似,1602显示字符的原理也是8*8的点组成的。通过点亮相应的点就可以,但与点阵不同的是,因为其内部自带了字库,LCD1602不需要取字模,通过对照ASCLL码表就可以显示相应的数字:例如还是要显示数字“0”的话,我们只需要往DB0-DB7写入数值48,就可显示数字“0”。
(1)VSS接电源地。
(2)VDD接+5V。
(3)VO是液晶显示的偏压信号,可接电位器,改变背光,电压过高和过低会看不清字符。
(4)RS是命令/数据选择引脚,接FPGA的一个I/O,当RS为低电平时,选择命令;当RS为高电平时,选择数据。
(5)RW是读/写选择引脚,接FPGA一个I/O,当RW为低电平时,向LCD1602写入命令或数据;当RW为高电平时,从LCD1602读取状态或数据。
共有四种状态:
输入RS=0,RW=1,E=高脉冲。输出:D0—D7为状态字。(读命令)
输入RS=1,RW=1,E=高脉冲。输出:D0—D7为数据。 (读数据)
输入RS=0,RW=0,E=高脉冲。输出:无。 (写命令)
输入RS=1,RW=0,E=高脉冲。输出:无。(写数据)
(6)E,执行的使能引脚,接FPGA的一个I/O。
(7)D0—D7,并行数据输入/输出引脚,接FPGA的的8个I/O口。
(8)A背光正极,可接到VCC。
(9)K背光负极,接GND。
LCD控制使能端,rs=0时为写命令,命令值为H38,设置8位格式,2行,5*7,LCD的使能端E为1(en_sel=1);rs=1时为写数据,en为LCD的E引脚。rw=0时为写数据,rw=1为读数据(读数据没有用到,所以rw始终为0)。同时需延时一段时间10MS,LCD速度较慢必须延时。LCD的使能端E为0(en_sel=0),下降沿的时候把数据读进去,lcd_flag=0表示LCD正在写入一个数据,lcd_flag为1时表示数据写入完成,可以写入下一个数据。
3.3分频模块
由于开发板提供的时钟脉冲频率20MHz和50MHz,远高于各模块工作所需要的时钟频率。因此设计了分频模块对开发板的时钟脉冲进行分频。分频模块含有两个输入信号(20MHz的时钟脉冲信号clk、复位信号rst_n),两个输出信号(2Hz的时钟脉冲信号clk2hz、1kHz的时钟脉冲信号clk1000hz)。
分频原理是用可并行预置的加法计数器设计完成的,当加法计数器溢出时进行并行预置。例如,对20MHz分出2Hz频率时,需要给加法计数器预置终点4999999,该加法计数器的工作频率为20MHz。当计数器计数到4999999时,当且仅当,时钟上升沿到来(即20MHz脉冲的第500万的周期的上升沿),分频模块的输出信号clk2hz发生翻转,从计数开始到clk2hz发生翻转的时间即为2Hz信号的半个周期时长。
3.4消抖模块
因为我们使用的按键开关为机械弹性快关,当机械触点断开、闭合时,由于机械触点的弹性作用,按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。为了不使抖动影响我们芯片的控制产生误判,就必须要进行按键消抖。
按键处于低电平的时刻大于20ms时,按键为按下状态,而按键按下的干扰是短时、多次的从高电平到低电平的跳变。因此,我们可以用一个计数模块,当输入信号为低电平时,开始计时,计时途中,如果跳变为高电平,即计数器清0,等待下一次低电平到来后再次开始计时,如此反复,如果记满20ms,那么,认为按键为一次按下状态,使按键标志产生一个脉冲,代表按键按下。
该模块含有四个输入信号,通过检测时钟的上升沿和复位的下降沿,复位信号低有效,则计数器清 0,20ms 扫描一次按键,20ms 计数(50M/50-1=999_999) ,计数器计到 20ms,计数器清零,采样按键输入电平计数器加 1。
3.5密码处理模块
i用于指示密码的第几位,flag用于判断判断密码输入错误的次数,3次后,键盘锁定,不可以输入数据。定义xianshi为显示的变量,shuzi,zimu显示的暂存变量,定义add_flag为地址改变标志位。led,led1,led2,led3为输出。a,b,c,d,e,f,g,h,A,B,C,D,E,F,G,H为存储输入的密码和设定密码的变量。如果显示的地址发生变化且LCD显示完成(lcd_flag=1),才显示为空字符;按键 KEY0 按下时,数字加一,第一次按下时,初始shuzi为9,按下后加一,等于10,就自动变成0。key_flag==0表示输入密码状态,key_flag==1表示设置密码状态,i表示给第i位密码赋值。密码的位数与显示的地址同步,按键 KEY2 值变化时,地址显示加一,字符右移一位,到达第8后不再加,因为密码为8位。按键 KEY3 按下时,一位一位的比对密码,如果密码输入正确,按下KEY4按键可以修改密码,按下KEY5按键可以退回上一位。最后shuzi复位为数字9,zimu复位为小写字母z。
LED功能:
LED1:亮起表示密码正确。
LED2:亮起表示密码错误。
LED3:亮起表示密码输入错误次数为3次。
LED4:亮起表示修改密码状态。
按键功能:
DKAO:输入数字。
DKA1:输入字母。
DKA2:进位(最高八位)。
DKA3:密码输入完成确认键。
DKA4:修改密码状态开关。
DKA5:删除一位。
DKA6:自定义按键。
3.6蜂鸣器模块
密码输入正确后,蜂鸣器响一声(当bee_clk=0,bee_flag=1时(也就是密码输入正确时)蜂鸣器在响),当蜂鸣器时钟bee_clk=1且蜂鸣器标志位bee_flag为1时,蜂鸣器停止。 p=1用来表示蜂鸣器已经响了,p=0 清零等待下一次密码输入正确时响起。
4.1管脚绑定表
Node Name |
Direction |
Location |
Node Name |
Direction |
Location |
bee |
Output |
PIN_A20 |
key_in[4] |
Input |
PIN_G13 |
clk |
Input |
PIN_G1 |
key_in[3] |
Input |
PIN_F13 |
data[7] |
Output |
PIN_U8 |
key_in[2] |
Input |
PIN_E13 |
data[6] |
Output |
PIN_AB8 |
key_in[1] |
Input |
PIN_D13 |
data[5] |
Output |
PIN_U7 |
key_in[0] |
Input |
PIN_C13 |
data[4] |
Output |
PIN_V7 |
led |
Output |
PIN_AB14 |
data[3] |
Output |
PIN_W7 |
led1 |
Output |
PIN_AA14 |
data[2] |
Output |
PIN_Y7 |
led2 |
Output |
PIN_W14 |
data[1] |
Output |
PIN_AA7 |
led3 |
Output |
PIN_V14 |
data[0] |
Output |
PIN_AB7 |
rs |
Output |
PIN_Y4 |
en |
Input |
PIN_AA5 |
rst |
Input |
PIN_E4 |
key_in[6] |
Input |
PIN_E11 |
rw |
Output |
PIN_AB5 |
key_in[5] |
Input |
PIN_E12 |
4.2管脚绑定说明
bee:用于蜂鸣器发出声音
clk:外部为20MHZ的时钟,用于整个系统的基础时序
data[0-7]: LCD1602并行数据输入/输出引脚
key_in[0-7]: 8个按键用于按键扫描
led[0-3]: 分别绑定4个LED灯,用于密码正确与否和修改状态的显示
en: 执行的使能引脚
rs: 命令/数据选择引脚
rw: 读/写选择引脚
rst:用于整个系统的初始化,是整个系统的复位信号
在 Quartus II 中的 Modelsim 仿真平台对各个模块及总体设计进行 仿真及测试,用于验证系统的可行性及稳定性。
5.1仿真调试
在Testbench中给一个时钟信号让它每25ns翻转一次,先让复位信号为高电平和令clk信号为1;再给他100ns使复位信号为低电平,使整个系统初始化,然后让开始信号KEY_IN输入密码。密码输入正确时。如图5-1所示,led显示为高电平,这则说明密码锁有效,密码输入错误时,LED高低电平波动,发生闪烁,如图5-2所示,由此可以看出系统主体功能正常,所以可以得到这个设计是可行的。
5.2开发板整体调试
系统上电,首先进行系统初始化,液晶屏上显示出两个动态的光标,如图5—3所示,等待输入密码。
按下DKA0输入初始密码为00000000,按下DKA3后,LED1亮,如图5—4所示,说明密码正确。
密码输入正确后,按下DKA4后,LED4亮起,说明目前状态为修改密码状态。按下DKA4删除之前的密码,重新输入想要设置的密码,如图5—5所示,再次按下DKA4,LED4熄灭,证明密码修改成功。
密码修改成功后,按下复位键,重新输入修改后的密码,LED1亮,说明密码修改成功,如图5—6所示。
结论:密码锁功能实现成功,并且能实现修改密码的功能。
6.1总结与心得
作为一种智能安全设备,电子密码锁在现代家居中越来越受到欢迎。针对这种需求,我们设计了一种基于FPGA的电子密码锁系统。在设计过程中,我们遇到了许多挑战与问题,下面我将总结一些心得和体会。
首先,设计电子密码锁系统需要较高的安全性和可靠性。我们采用了多种加密算法,并且保证系统在密码错误、电源变化等复杂环境下也能正常运行。其次,在选用FPGA芯片时,需要根据系统的需要选择适当的芯片,并且针对系统的各种需求进行定制化设计。我们设计的电子密码锁系统采用了Altera Cyclone IV芯片,方便进行逻辑扩展和状态转移。同时,我们也考虑到了系统的可维护性和固件升级问题。
其次,设计电子密码锁系统需要考虑用户的操作体验。在界面设计上,我们采用了物理按键控制和LCD1602液晶屏显示,使得用户可以直观地看到系统的工作状态和操作结果。另外,在密码输入过程中,我们增加了音效提示,让用户在输入错误时能够及时调整,提升用户体验。最后,在设计电子密码锁系统时,需要考虑到系统的可扩展性和应用场景。我们采用了模块化设计,并且保证各个模块的独立性,使得系统能够灵活扩展。此外,在应用场景方面,我们也考虑到了多种需求,例如支持多种开锁方式、多种开锁设备等,提升了系统的应用范围。
总之,设计电子密码锁系统需要考虑到安全性、可靠性、用户体验、可维护性、固件升级等多个方面,这需要我们在实践中不断探索和改进。
在设计基于FPGA的电子密码锁时,成本分析是必不可少的一部分。下面我将从硬件成本和软件成本两个方面对其进行分析。
6.2成本分析
硬件成本分析:
1. FPGA芯片成本:FPGA芯片是电子密码锁的核心部件,成本较高,但不需要大量采购。
2. 显示屏:我们采用了LCD1602液晶屏,成本较低。
3. 蓝牙、Wi-Fi等通信模块:如果需要增加通信功能,需要采购通信模块,其中Wi-Fi模块成本较高,蓝牙模块成本较低。
4. 电源模块:电子密码锁需要电源模块来支持,成本中等。
软件成本分析:
1. FPGA开发工具成本:FPGA设计过程中需要使用由芯片厂商提供的开发工具,常见的如Quartus II,成本较低。
2. 系统集成成本:电子密码锁的系统集成需要大量的时间和人力成本,较高。
3. 软件维护成本:电子密码锁在运行过程中可能会出现一些问题或需求,需要进行软件维护和升级,成本较低。
根据以上分析,我们可以得到以下结论:基于FPGA的电子密码锁的硬件成本相对较高,但不需要大量采购;软件成本相对较低,除了系统集成和一些特定需求的软件开发,维护和升级成本较低。综合起来,设计基于FPGA的电子密码锁的成本相对较高,但可以通过不断调整和优化方案,逐步降低成本。同时,在设计过程中还需充分考虑成本与功能之间的平衡,确保设计方案能够实现业务需求和市场需求,达到收益最大化。
[1]康华光,张林,数字电子技术基础(第七版)[M],北京:高等教育出版社,2021.
[2]宋万杰,罗杰,吴顺君.CPLD技术及其应用[M].西安:西安电子科技大学出版社,1999.
[3]王金明,杨吉斌.数字系统设计与Verilong HDL[M].北京:北京电子工业出版社,2002.
[4]夏宇闻. Verilog数字系统设计教程[M].北京:北京航天航空大学出版社,2003.
[5]罗杰. VerilogHDL与FPGA数字系统设计[M].北京:机械工业出版社,2022.
附录 1 RTL 代码:
module CD
(
rst,
key_in,
clk,
rw,
rs,
en,
data,
led,
led1,
led2,
led3,
bee,
);
input clk,rst; //CLK 50M,RSTw为复位信号
/***********************给变量赋初值************************/
initial A<=8'd48;//初始密码 0000——0000,d48为0的ASCALL值
initial B<=8'd48;
initial C<=8'd48;
initial D<=8'd48;
initial E<=8'd48;
initial F<=8'd48;
initial G<=8'd48;
initial H<=8'd48;
initial bee=0;
initial bee_flag=0;
initial p=0;
initial i<=1;
initial led=0;
initial led1=0;
initial led2=0;
initial led3=0;
initial flag=0;
initial xianshi<=8'd16; //让初始化完以后,就显示空字符
initial add<=8'h80; //让初始化显示的地址
initial shuzi<=8'd57; //让初始化完以后,数字变量为9,ascll码值为57,
initial zimu<=8'd122; //为什么是9,因为按键按下后,数字会加一,9加一变为10,10以后跳会0,
//所以第一按按键显示0
//让初始化字母为小写z
/**************************LCD的初始化函数********************************/
/*******************计数器函数用于在状态机中做延时使用********************/
reg [31:0]count;
always @(posedge clk )
begin
if(!rst)
count<=0;
else
begin
count<=count+1;
if(count==2_000_001)
begin
count<=0;
end
end
end
/********************************状态定义********************************/
parameter state0 =6'h00, //设置8位格式,2行显示,5*7 8'h38;
state1 =6'h01, //开显示,开光标,闪烁 8'h0e 关光标,不闪烁 8'h0c
state2 =6'h02, //输入数据光标右移,显示字符不移动 8'h06
state3 =6'h03, //清屏 8'h01
state4 =6'h04, //显示的地址 add
state5 =6'h05; //显示的字符
output rs,en,rw; //LCD控制使能端,rs=0时为写命令,rs=1;为写数据,en为LCD的E引脚,rw=0时,为写,rw=1为读数据(读数据没有用到,所以rw始终为0)
output [7:0] data;//输出LCD显示的字符//LCD的D0到D7引脚
reg rs,en_sel; //OUTput的变量要为定义为reg类型,en_sel为LCD E引脚的寄存器变量,最终为E=en_sel
reg [7:0]data;
reg [7:0]add; //LCD字符显示的地址
reg [7:0] next; //next为状态定义
reg[3:0]lcd_flag; // lcd_flag=0表示LCD正在写入字符,lcd_flag=1;表示LCD写入数据完成
always @(posedge clk,negedge rst)
begin
if(!rst)
begin
next<=state0;
lcd_flag=0;
end
else
begin
case(next)
state0 :
begin if(count<1000_000)begin rs=0;en_sel=1;data=8'h38;end //rs=0;为写命令;命令值为H38,设置8位格式,2行,5*7,LCD的使能端E为1(en_sel=1);
if(count>1000_000)begin en_sel=0;end //LCD的使能端E为0(en_sel=0),下降沿的时候把数据读进去;
if(count>2000_000)begin next<=state1;end //延时一段时间10MS,LCD速度较慢必须延时
end
state1 :
begin if(count<1000_000)begin rs=0;en_sel=1;data=8'h0e;end
if(count>1000_000)begin en_sel=0;end
if(count>2000_000)begin next<=state2;end
end
state2 :
begin if(count<1000_000)begin rs=0;en_sel=1;data=8'h06;end
if(count>1000_000)begin en_sel=0;end
if(count>2000_000)begin next<=state3;end
end
state3 :
begin if(count<1000_000)begin rs=0;en_sel=1;data=8'h01;end
if(count>1000_000)begin en_sel=0;end
if(count>2000_000)begin next<=state4;end
end
state4 :
begin if(count<1000_000)begin lcd_flag=0; rs=0;en_sel=1;data=add;end //lcd_flag=0表示LCD正在写入一个数据,
if(count>1000_000)begin en_sel=0;end // 此时的数据应该保持稳定
if(count>2000_000)begin next<=state5;end
end
state5 :
begin if(count<1000_000)begin rs=1;en_sel=1;data=xianshi;end //rs=1;为写数据,xinashi是要显示的数据
if(count>1000_000)begin en_sel=0;lcd_flag=1;end //lcd_flag为1时表示数据写入完成,可以写入下一个数据
if(count>2000_000)begin next<=state4;end
end
default: next<=state0;
endcase
end
end
/*************************以下函数为密码锁功能实现************************/
/********************计数器函数用于在蜂鸣器中做延时***********************/
reg [31:0]count2; // CLK 分频计数器
output bee; //蜂鸣器控制端,bee为高电平时蜂鸣器响
reg bee;
reg bee_flag; //蜂鸣器使能标志
reg bee_clk; //蜂鸣器时钟
reg p; //蜂鸣器响标志
always @(posedge clk )
begin
if(!rst)
count2<=0;
else
count2<=count2+1;
if(count2==50_000_000)
begin
bee_clk=1;
end
if(count2==50_900_000)
begin
bee_clk=0;
end
if(count2==51_000_000)
begin
count2<=0;
end
end
//按键检测函数
reg [31:0]count1; // CLK 分频计数器
always @(posedge clk or negedge rst) //检测时钟的上升沿和复位的下降沿
begin
if(!rst) //复位信号低有效
count1 <= 32'd0; //计数器清 0
else
begin
if(count1 ==32'd999_999) //20ms 扫描一次按键,20ms 计数(50M/50-1=999_999)
begin
count1 <= 1'b0; //计数器计到 20ms,计数器清零
key_scan <= key_in; //采样按键输入电平
end
else
count1 <= count1 + 1'b1; //计数器加 1
end
end
// 按键信号锁存一个时钟节拍
input [6:0] key_in;//按键的定义 key0为数字0-9输入 key1为大小写字母输入 key2为输入密码的下一位 key3为确定键
reg [6:0] key_scan_r;
reg [6:0] key_scan;
always @(posedge clk)
key_scan_r <= key_scan;//利用非阻塞赋值实现下降沿检测
wire [6:0] flag_key = key_scan_r[6:0] & (~key_scan[6:0]); //检测按键的下降沿
//密码输入处理函数
reg key_flag; //修改密码标记位
reg [3:0]i,flag;//i用于指示密码的第几位,flag用于判断判断密码输入错误的次数,3次后,键盘锁定,不可以输入数据
reg[7:0]xianshi,shuzi,zimu;//定义xianshi为显示的变量,shuzi,zimu显示的暂存变量
reg add_flag;//地址改变标志位
output led,led1,led2,led3;//LED灯的定义
reg led,led1,led2,led3;
reg[7:0] a,b,c,d,e,f,g,h,A,B,C,D,E,F,G,H;//存储输入的密码和设定密码的变量
always @ (posedge clk or negedge rst)
begin
if (!rst) //复位信号低有效
begin
key_flag=0;
shuzi<=8'd57; //复位为数字9
zimu<=8'd122; //复位为小写字母z
xianshi<=8'd16;//复位为空字符
add=8'h80; //复位第一行第一个显示
i=1;
flag=0;
led=0;
led1=0;
led2=0;
led3=0;
bee=0;
add_flag=0;
a=0;
b=0;
c=0;
d=0;
e=0;
f=0;
g=0;
h=0;
end
else if(flag<=2) //flag为错误密码的次数,等于3if条件不满足,键盘不能使用
begin
if(lcd_flag&&add_flag)//如果显示的地址发生变化且LCD显示完成(lcd_flag=1),才显示为空字符,
begin
xianshi=8'd16;add_flag=0;
end
if ( flag_key[0] )begin shuzi=shuzi+1'b1;if(shuzi==8'd58)shuzi=8'd48;xianshi=shuzi; //按键 KEY0 按下时,数字加一,第一次按下时,
// 初始shuzi为9,按下后加一,等于10,就自动变成0
if(key_flag==0) // key_flag==0表示输入密码状态
begin
if(i==1)a=shuzi;
if(i==2)b=shuzi;
if(i==3)c=shuzi;
if(i==4)d=shuzi;
if(i==5)e=shuzi;
if(i==6)f=shuzi;
if(i==7)g=shuzi;
if(i==8)h=shuzi;
end
if(key_flag==1) // key_flag==1表示设置密码状态,i表示给第i位密码赋值
begin
if(i==1)A=shuzi;
if(i==2)B=shuzi;
if(i==3)C=shuzi;
if(i==4)D=shuzi;
if(i==5)E=shuzi;
if(i==6)F=shuzi;
if(i==7)G=shuzi;
if(i==8)H=shuzi;
end
end
if ( flag_key[1] ) begin zimu=zimu+1;if(zimu==91)zimu=8'd97;if(zimu==8'd123)zimu=8'd65;xianshi=zimu;//65为A,97为a,122为小写字母z
if(key_flag==0) // key_flag==0表示输入密码状态
begin
if(i==1)a=zimu;
if(i==2)b=zimu;
if(i==3)c=zimu;
if(i==4)d=zimu;
if(i==5)e=zimu;
if(i==6)f=zimu;
if(i==7)g=zimu;
if(i==8)h=zimu;
end
if(key_flag==1) // key_flag==1表示设置密码状态
begin
if(i==1)A=zimu;
if(i==2)B=zimu;
if(i==3)C=zimu;
if(i==4)D=zimu;
if(i==5)E=zimu;
if(i==6)F=zimu;
if(i==7)G=zimu;
if(i==8)H=zimu;
end
end
if ( flag_key[2] ) begin add_flag<=1;
i=i+1;if(i>7)i=8; //密码的位数与显示的地址应该同步
add<=add+1'b1; //按键 KEY2 值变化时,地址显示加一,字符右移一位
if(add>8'h86)add<=8'h87;//到达第8后不再加,因为密码为8位,可根据需要修改
shuzi<=8'd57;zimu<=8'd122;end
if ( flag_key[3] )begin //按键 KEY3 按下时,一位一位的比对密码
if(a==A)
begin
if(b==B)
begin
if(c==C)
begin
if(d==D)
begin
if(e==E)
begin
if(f==F)
begin
if(g==G)
begin
if(h==H)
begin
led=1;led1=0;bee_flag=1;bee=1;
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
end
else begin led1=1;led=0; end
if(led==1)flag=0;
if(led1==1)flag=flag+1;
if(flag==3)led2=1;
end
if ( flag_key[4] )begin //如果密码输入正确,按下此按键可以修改密码
if(led==1)
begin
key_flag=~key_flag;
if(key_flag==1)
led3=1;
else
led3=0;
end
end
if ( flag_key[5] )begin//按下此按键可以退回上一位
xianshi=8'd16; //显示为空字符
shuzi<=8'd57; //复位为数字9
zimu<=8'd122; //复位为小写字母z
add<=add-1;
if(add<8'h80)add<=8'h87;
i=i-1;
if(i==0)i=8;
end
//****************************蜂鸣器代码*********************************//
下面这段代码实现的功能为:密码输入正确后,蜂鸣器响一声//(当bee_clk=0,bee_flag=1时(也就是密码输入正确时)蜂鸣器在响)
if(bee_clk&&bee_flag==1)//当蜂鸣器时钟bee_clk=1且蜂鸣器标志位bee_flag为1时
begin
bee=0; //蜂鸣器停止
p=1; //p=1用来表示蜂鸣器已经响了
end
else if(p==1)
begin
p=0; //清零等待下一次响
bee_flag=0; //清零等待下一次密码输入正确
end
end
end
assign en=en_sel;
assign rw=0;//rw为0表示LCD的rw引脚为低电平,此时为写,rw为1时读
endmodule
附录 2 Testbench 代码:
`timescale 1ns/1ns
module CD;
reg clk;
reg [3:0]key_in;
reg rst;
wire [2:0]led;
wire [2:0]led_test;
CD uut(.clk(clk),.key_in(key_in),.rst(rst),.led(led),.led_test(led_test));
initial begin
clk = 1;
forever #25 clk = ~clk;
end
initial begin
rst = 1;
#100;
rst = 0;
key_in = 4'b0000;
#100;
key_in = 4'b0001;
#200000000;
key_in = 4'b0000;
#10000;
key_in = 4'b0010;
#200000000;
key_in = 4'b0000;
#10000;
key_in = 4'b0100;
#200000000;
key_in = 4'b0000;
#10000;
key_in = 4'b1000;
#200000000;
key_in = 4'b0000;
#10000;
key_in = 4'b0010;
#200000000;
key_in = 4'b0100;
#200000000;
key_in = 4'b0000;
#200000000;
$stop;
end
endmodule