1.为什么要分频?
主要是为了给其他模块一个时钟源,不同的模块需要不同的时钟源,比如时间计数,时间分为一秒一秒的,LED的现实模块,LED是通过人的视觉差来达到让人觉
的它在亮的效果,实际上说它是扫描比较的闪烁比较好。
2.怎么分频?
通过计数器来实现分频,所需要的频率与基础频率之间的计数关系,因为采用的是上升沿或者下降沿触发计数,两者有些不同,上升沿触发的分频时钟输出,如果
N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%,得到的分频时钟正周期比负周期多一个clk时钟,下降沿触发的分频时钟输出,和用上
升沿触发的相差半个时钟,如果是上升沿和下降沿混合用的,需要注意这个问题,特别是模块分不同的人写的时候,因为我们这个都是我自己写的,所以都是采用
的上升沿。(12MHz)
3.键盘扫描模块的原理
扫描并锁存行值,该行按下为1,否则为0,列同理,然后用case选择情况根据键盘所代表的数字在寄存器里存数字
4.密码模块
密码模块用了一个24位的寄存器,密码一共有4位,每位可以输入0-9十个任意数。
5.报警模块
判断锁的状态位,因为频率慢的时钟不能在快频率模块进行判断、赋值这些操作,我们把报警器和报警器所需要的计时模块放在了一起
在频率快的模块中赋值的量不能在在频率慢的模块中再进行赋值,慢频率模块的值可以在快模块中进行判断操作
6.状态灯模块
5.状态的判定
锁的状态、是否重置、是否开始输入密码、确认密码
3.总的流程?
实现电子密码锁密码输入、密码储存、密码删除、密码重置,要求在5秒内输入正确密码,则绿灯亮,锁打开;密码错误或者未在规定时间内输完都算解锁失败,
红灯亮,报警20秒,20秒后可重新输入,rst 键按下当前输入密码设置为电子锁的密码
主要模块:
1.分频模块
2.LED灯模块,LED灯显示输入密码和时间计数
3.键盘扫描模块
4.密码模块
5.辅助模块(防抖模块)【此次代码未添加该模块】
功能模块部分
module code123(sw1,sw2,sw3,clk,row,col,rst,key,alarm,clear,xianshi,play2,play3,play4,green_led,yellow_led,red_led,ring);
//parameter N=5
input sw1;
input sw2;
input sw3;
input rst;//复位
input clk;//时钟
input clear;//清除
input [3:0]row;
output reg[3:0]col;
output reg ring;
input [3:0] key;
reg [3:0] key0=4'b1111;//预存密码都为1
reg [4:0] counter=5'b00000;
output [8:0]xianshi;
wire[8:0]shumaguan;
assign xianshi=shumaguan;//一个计算秒数,还有4位显示密码
output [8:0]play2;
wire[8:0] led2;
assign play2=led2;
output [7:0]play3;
wire[7:0] led3;
assign play3=led3;
output [7:0]play4;
wire[7:0] led4;
assign play4=led4;
reg clk1;
reg flag=0;
reg flag1=0;
reg flag2=0;
reg [23:0] previous_password=24'd0;
reg [23:0] password=24'd0;
reg flag_in=1'b0;
reg [3:0] keyboard_val;
//=========================
reg [3:0]cout=4'b0000;
//output [2:0] cout0;
//assign cout0=cout;
//=========================
reg [3:0] temp=4'b0000;
//wire temp1;
//assign temp=temp1;
//output [3:0] temp1;
//assign temp1=temp;
wire clk2;
//========================
output reg green_led;
output reg yellow_led;
output reg red_led;
output reg alarm;
// assign ring=alarm;
reg [19:0] count1=20'b0;
reg [29:0] count2=30'b0;
reg [4:0] break1=5'b0;
reg [4:0] break2=5'b0;
reg [5:0] current_state, next_state; // 现态、次态
reg key_pressed_flag=0; // 键盘按下标志
reg [3:0] col_val, row_val; // 列值、行值
reg [19:0] cnt;
parameter NO_KEY_PRESSED = 6'b000_001; // 没有按键按下
parameter SCAN_COL0 = 6'b000_010; // 扫描第0列
parameter SCAN_COL1 = 6'b000_100; // 扫描第1列
parameter SCAN_COL2 = 6'b001_000; // 扫描第2列
parameter SCAN_COL3 = 6'b010_000; // 扫描第3列
parameter KEY_PRESSED = 6'b100_000; // 有按键按下
//---------------------------------
parameter time1=1'd5;//在5秒内输入密码并确认
parameter time2=2'd20;//警报响触发后警报响20S
parameter N=5'd50000;
//警报置初始状态,alarm为灯,ring为铃
initial begin
alarm<=1'b1;
ring<=1'b0;
end
//==================================
//模块调用部分
LED u1(
.seg_data_1(counter[3:0]),
.seg_data_2(cout),
.seg_led_1(shumaguan),
.seg_led_2(led2)
);
divide #(.WIDTH(32),.N(12000000)) u2(
.clk(clk),
.rst_n(rst),
.clkout(clk2)
);
LED_Display u3(
.an(led4),
.clk(clk),
.seg(led3),
.temp(password [23:20])
);
/*anjian u3(
.i_clk(clk),
.i_rst_n(rst),
.row(row),
.col(col),
.password(password),
.flag1(flag1)
);*/
//============================
//分频1:
always @(posedge clk)
begin
count1<=count1+1'b1;
if (count1==N)
begin
clk1<=~clk1;
count1<=20'd0;
end
end
always@(posedge clk2)begin//采用的秒作时钟
if(flag1==1) begin //flag=1时,开始输入密码
counter=counter+1;
if(counter<5)begin
if(green_led==0)begin //绿灯亮
ring<=1'b0;
flag2=0; //电铃是否响的标志
counter=5'b00000;end end
else if(counter<25 && counter>=5)begin
ring<=1'b1;
flag2=1;
end
else begin //保持初始状态
ring<=1'b0;
flag2=0;
counter=5'b00000;
end
end
else begin
counter=5'b00000;
ring<=1'b0;
flag2=0;
end
end
always @ (posedge clk)
cnt <= cnt + 1'b1;
wire key_clk = cnt[15];
always @ (posedge key_clk) begin
if(rst==1'b1)
current_state <= next_state;end
always @ * begin
case (current_state)
NO_KEY_PRESSED : // 没有按键按下
if (row != 4'hF)
next_state = SCAN_COL0;
else
next_state = NO_KEY_PRESSED;
SCAN_COL0 : // 扫描第0列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL1;
SCAN_COL1 : // 扫描第1列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL2;
SCAN_COL2 : // 扫描第2列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = SCAN_COL3;
SCAN_COL3 : // 扫描第3列
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = NO_KEY_PRESSED;
KEY_PRESSED : // 有按键按下
if (row != 4'hF)
next_state = KEY_PRESSED;
else
next_state = NO_KEY_PRESSED;
endcase
end
// 根据次态,给相应寄存器赋值
always @ (posedge key_clk) begin
if(rst==1'b1)begin
case (next_state)
NO_KEY_PRESSED : // 没有按键按下
begin
col <= 4'h0;
key_pressed_flag <= 1'b0; // 清键盘按下标志
end
SCAN_COL0 : // 扫描第0列
col <= 4'b1110;
SCAN_COL1 : // 扫描第1列
col <= 4'b1101;
SCAN_COL2 : // 扫描第2列
col <= 4'b1011;
SCAN_COL3 : // 扫描第3列
col <= 4'b0111;
KEY_PRESSED : // 有按键按下
begin
col_val <= col; // 锁存列值
row_val <= row; // 锁存行值
key_pressed_flag <= 1'b1; // 置键盘按下标志
end
endcase
end
end
//--------------------------------------
// 状态机部分 结束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// 扫描行列值部分 开始
//++++++++++++++++++1++++++++++++++++++++
always @ (posedge key_clk) begin
if(rst==1'b1)begin
if (key_pressed_flag==1'b1)begin
case ({col_val, row_val})
8'b1110_1110 : begin keyboard_val <= 4'h0; flag<=1; end
8'b1110_1101 : begin keyboard_val <= 4'h4; flag<=1; end
8'b1110_1011 : begin keyboard_val <= 4'h8; flag<=1; end
// 8'b1110_0111 : keyboard_val <= 4'hC;
8'b1101_1110 : begin keyboard_val <= 4'h1; flag<=1;end
8'b1101_1101 : begin keyboard_val <= 4'h5;flag<=1;end
8'b1101_1011 : begin keyboard_val <= 4'h9;flag<=1;end
// 8'b1101_0111 : keyboard_val <= 4'hD;
8'b1011_1110 : begin keyboard_val <= 4'h2;flag<=1;end
8'b1011_1101 : begin keyboard_val <= 4'h6;flag<=1;end
// 8'b1011_1011 : keyboard_val <= 4'hA;
// 8'b1011_0111 : keyboard_val <= 4'hE;
8'b0111_1110 : begin keyboard_val <= 4'h3; flag<=1;end
8'b0111_1101 : begin keyboard_val <= 4'h7;flag<=1;end
// 8'b0111_1011 : keyboard_val <= 4'hB;
// 8'b0111_0111 : keyboard_val <= 4'hF;
default:flag=0;
endcase
end
else flag=0;
end
end
//--------------------------------------
// 扫描行列值部分 结束
//--------------------------------------
//写入密码模块
always@(posedge flag)//flag 是判断一个数字按下并且生效时
begin
case(cout)
0:
begin
password [23:20]<=keyboard_val;
cout<=cout+1;
flag1=1;
end
1:
begin
password[19:16]<=keyboard_val;
cout<=cout+1;
end
2:
begin
password[15:12]<=keyboard_val;
cout<=cout+1;
end
3:
begin
password[11:8]<=keyboard_val;
cout<=cout+1;
end
4:
begin
password[7:4]<=keyboard_val;
cout<=cout+1;
end
5:
begin
password[3:0]<=keyboard_val;
cout<=4'd0;
end
default:begin cout<=4'd0;
flag1=0;end
endcase
end
//判断部分
//密码操作部分:当按rst时当前的密码被设置为初始密码,否则将输入的密码与初始密码进行比较
always@(posedge clk)
begin
if(rst==1'b1)
begin
if(sw1==1'b0)
begin
previous_password<=password;//当按下rst时当前的密码被存为所密码
end
if(sw1==1'b1) begin
if(sw2==1'b0)
begin
if(previous_password==password)begin
red_led<=1'b1;
green_led<=1'b0; end
else begin
red_led<=1'b0;
green_led<=1'b1; end
end
else begin
red_led<=1'b1;
green_led<=1'b1; end
end
if(flag2==1) begin
green_led<=1'b1;
yellow_led<=1'b1;
red_led<=1'b1; end
end
end
endmodule
数码管模块
module LED (seg_data_1,seg_data_2,seg_led_1,seg_led_2);
input [3:0] seg_data_1; //数码管需要显示0~9十个数字,所以最少需要4位输入做译码
input [3:0] seg_data_2; //小脚丫上第二个数码管
output [8:0] seg_led_1; //在小脚丫上控制一个数码管需要9个信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
output [8:0] seg_led_2; //在小脚丫上第二个数码管的控制信号 MSB~LSB=DIG、DP、G、F、E、D、C、B、A
reg [8:0] seg [9:0]; //定义了一个reg型的数组变量,相当于一个10*9的存储器,存储器一共有10个数,每个数有9位宽
initial //在过程块中只能给reg型变量赋值,Verilog中有两种过程块always和initial
//initial和always不同,其中语句只执行一次
begin
seg[0] = 9'h3f; //对存储器中第一个数赋值9'b00_0011_1111,相当于共阴极接地,DP点变低不亮,7段显示数字 0
seg[1] = 9'h06; //7段显示数字 1
seg[2] = 9'h5b; //7段显示数字 2
seg[3] = 9'h4f; //7段显示数字 3
seg[4] = 9'h66; //7段显示数字 4
seg[5] = 9'h6d; //7段显示数字 5
seg[6] = 9'h7d; //7段显示数字 6
seg[7] = 9'h07; //7段显示数字 7
seg[8] = 9'h7f; //7段显示数字 8
seg[9] = 9'h6f; //7段显示数字 9
end
assign seg_led_1 = seg[seg_data_1]; //连续赋值,这样输入不同四位数,就能输出对于译码的9位输出
assign seg_led_2 = seg[seg_data_2];
endmodule
分频模块
module divide (clk,rst_n,clkout);
input clk,rst_n; //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz
output clkout; //输出信号,可以连接到LED观察分频的时钟
//parameter是verilog里常数语句
parameter WIDTH = 3; //计数器的位数,计数的最大值为 2**WIDTH-1
parameter N = 12000000/8; //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出
// reg N1=N/count;
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器
reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟
//上升沿触发时计数器的控制
always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿
//当clk上升沿来临或者rst_n变低的时候执行一次always里的语句
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
end
//上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<N>>1) //N>>1表示右移一位,相当于除以2去掉余数
clk_p<=0;
else
clk_p<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
//下降沿触发时计数器的控制
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
//下降沿触发的分频时钟输出,和clk_p相差半个时钟
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<N>>1)
clk_n<=0;
else
clk_n<=1; //得到的分频时钟正周期比负周期多一个clk时钟
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式
//当N=1时,直接输出clk
//当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p
//当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
endmodule