紫龙FPGA笔记之
计算器 (上)
紫龙
一点闲话: 咳咳 ,最近这两周弄得计算器简直要发疯,自己每天做的都能及时弄完,但是到最后一综合,问题百出,好多自己找了很久都找不到,在此感谢寇老师的细心解答哈哈,不然本宝宝今天得熬到很晚了~~~
前言: 首先,当我们要用fpga实现一个简易计算器的时候,我们不妨将手离开键盘,先别急着敲代码,先静下来想一想······一个计算器到底包含哪几个部分呢? 我们平时都用过计算器,回顾一下,当我们拿到计算器
进行运算的时候,我们第一步是用键盘按下自己想要的数,然后按下运算符,再按下等于号 ,他的运算结果将会通过显示屏显示出来,这些都是我们能够用眼睛所能看到的。而看不到的部分,就是我们输入数据的运算
处理了。====================就上面而言,我们所需要的部分,是不是简而言之就是 按键部分 ,显示部分 还有运算部分了~
FPGA从根本上说,我们只是对01进行编码,让他实现我们所想要的功能。那么如何对01进行编码实现一个简易计算器呢? 嘿嘿~没我说的这么复杂,其实我们懂原理很简单就做出来了~~~
原理图: 这个是我本人手写的,因为实在不会用这个发帖栏编辑这么高端的东西哈哈哈,图我也不知道传到哪一行了,到时候传完看看~~
原理图小结: 由图我们可以更加直观的看到这个计算器模块的框架,显然,计算器的设计 分为三个等级 :第一个等级,也就是最高级,我们叫做计算器总模块,本质上来说,他就是一个top文件,将第二级的分top文件
进行连线例化; 第二个等级,包块三个小模块 :按键驱动模块 ,数码管驱动模块,计算模块。 之所以前面两个模块我称之为驱动,我认为,他们将所对应的按键,数码管等小模块联系起来,让其有条不紊的进行工作,这也算
是一种驱动了吧! 但是计算模块只是一个单独的小模块 ,他不包含小程序 ,我也没把他称为驱动; 第三个等级:也就是各种小模块了 ,分属于二级之下 ,二级相对于三级而言,就是他们的顶层文件,将三级模块进行连线例
化。 按键驱动模块之下的三级模块包括 按键扫描和按键消抖 , 数码管驱动模块之下的三级模块包括 BCD转换,亮灭控制以及显示控制 。
由于计算器这几个模块 一次发帖占用篇幅太长 ,我分三次发吧 ,第一次讲的的是 二级模块中的第一个 :按键模块 。
按键模块: 要实现按键的输入,首先 ,我们得对按键按下这个状态进行采样,这也是我们所说的按键扫描 ,但是 ,由于机械性误差,我们每次按下按键的时候, 他并不是马上就能准确的接上,毕竟我们所采用的按键内部其实就是
一个金属弹片,两个金属弹片按下相撞一起,他们或多或少都会经过一个“缓冲期” ,由开始接触,再到弹开,再接触·····周而复始,一直到最后稳定下来,接触到一起。这就涉及到物理中的机械能守恒了,这里就不多了,不然我可以
和你得得三天三夜哈哈~ 话说回来 ,我们对按键工作的分析,很清楚就能知道 要实现一个完整的按键输入,一般而言我们要有三个模块:一个模块对按键进行扫描,一个模块对按键进行消抖,另一个模块对前两个模块进行例化,让他们有
条不紊的工作,充当驱动部分。
按键扫描:我们所用的ZX2开发板上的外设 为4 x 4的矩阵键盘 ,他包括四行 (row)和四列 (col),其中,他的行(col)与fpga相连,我们所能控制的就是列(col)的输入,当行一定,我们对列进行扫描,扫到一个值,由已知的行列相对应
我们便能得到唯一的数值knum,由于数值knum有16个,我们用四个位宽来进行定义。下面直接上程序吧 ,我将对各个步骤进行解说。
module keyscan (
input clk , // 系统时钟
input rst_n , //复位键
input [3:0] row , //输入行
output [3:0] col , //输出列
output reg [4:0] knum //扫描得到的按键码
);
//==========middle_siganal====================
reg [3:0] col_r; //输出列电位
wire [7:0] scancode; //扫描码, 用八位的,因为我们扫描矩阵键盘,行和列各占用四位
reg [7:0] scancode_r1,scancode_r2; //两个寄存器 ,register
//==========寄存扫描码======================
assign col=col_r; //首先 ,我们定义将扫描的列寄存到col_r这个寄存器之中
always @(posedge clk )begin //这个模块 ,是将scancode值存到寄存器中两次,这样,咱们的扫描码将慢两拍,这个操作很多程序中都会使用,其意义在于使数据更加稳定,消除亚稳态
scancode_r1<=scancode;
scancode_r2<=scancode_r1;end
assign scancode={col_r[3:0],row[3:0]}; //定义扫描码由行和列组成
always @(posedge clk or negedge rst_n)
if(~rst_n)
col_r<=4'b0000; //复位时,我们要扫描的列清0
else case(col_r) // 否则 我们让列从 4‘b1011 开始扫描,0代表列选中,default语句表明0开始左移。这样可以实现列的扫描
4'b1011:col_r<=4'b0000;
4'b0000:col_r<=4'b0111;
default:col_r<={col_r[2:0],col_r[3]};
endcase
// =======扫描=================
always @(posedge clk or negedge rst_n)
if(~rst_n)
knum<=5'd0;
else case(scancode_r2) //若我们的列都为0,行再怎么变 ,扫描结果还是0 ,表明未进行列扫描
8'b0000_1111:knum<={1'b0,knum[3:0]};
8'b0000_1110:knum<={1'b1,knum[3:0]};
8'b0000_1101:knum<={1'b1,knum[3:0]};
8'b0000_1011:knum<={1'b1,knum[3:0]};
8'b0000_0111:knum<={1'b1,knum[3:0]};
//=====后四位代表行,低电平有效,当行为1时============
8'b1110_1110:knum<=5'h10; //========knum为扫描结果,扫描的为16进制 ,结果为 0
8'b1101_1110:knum<=5'h11;
8'b1011_1110:knum<=5'h12;
8'b0111_1110:knum<=5'h13;
//==========当行为2时=========================
8'b1110_1101:knum<=5'h14;
8'b1101_1101:knum<=5'h15;
8'b1011_1101:knum<=5'h16;
8'b0111_1101:knum<=5'h17;
//=========当行为3时==========================
8'b1110_1011:knum<=5'h18;
8'b1101_1011:knum<=5'h19;
8'b1011_1011:knum<=5'h1A;
8'b0111_1011:knum<=5'h1B;
//=========当行为4时==========================
8'b1110_0111:knum<=5'h1C;
8'b1101_0111:knum<=5'h1D;
8'b1011_0111:knum<=5'h1E;
8'b0111_0111:knum<=5'h1F;
default:knum<=knum; //default语句意义在于当出现不是事件值得情况,扫描出的值保持不变
endcase
endmodule
按键消抖模块: 我们对按键扫描多次,但是,一次扫描7个结果,我们想想,如果,这个按键并没有抖动,那么这扫描的七个结果是不是相同的,于是我们对这七个结果进行比较,如果相同,让这个结果保存到一个寄存器之中,如果不同,
那么这个采样短周期能采样的结果无效。这样一直判断6次,其中,如果后来数据一样,那么将顶替掉前一个采集结果,当他值不改变时,我们得到的按键值换句话说也就稳定了,没有抖动的效果,那么我们就达到了按键消抖的效果。
程序如下:
module keyshake(
input clk ,
input rst_n ,
input [4:0]knum , //由按键扫描模块扫描而来,作为消抖的输入
output reg [3:0]kreanum , //键盘标志
output reg kreanum_vld //按键使能,这个模块不必看这个,为以后的模块做的准备
);
//========定义状态==========================
parameter S1 = 7'b000_0001 ;
parameter S2 = 7'b000_0010 ;
parameter S3 = 7'b000_0100 ;
parameter S4 = 7'b000_1000 ;
parameter S5 = 7'b001_0000 ;
parameter S6 = 7'b010_0000 ;
parameter S7 = 7'b100_0000 ;
//=======middle_signal========================
reg [5:0]keyf ;
reg [3:0]knum_r;
reg [6:0]state ;
//=======扫描按键按下,并将结果给寄存器knum_r=======
always @(posedge clk or negedge rst_n)
begin
if(!rst_n) // 复位时对寄存器清0
knum_r <= 4'd0 ;
else case(state)
S1:if(knum[4]) //=========knum第五位,代表的是有无按键摁下,有的话值为1,这里为检测到按键摁下
begin
state<=S2; //跳转到第二个状态
knum_r<=knum[3:0]; // 按键值赋给寄存器
end
S2:begin
state<=S3;
knum_r<=knum[3:0];
end
S3:begin
state<=S4;
knum_r<=knum[3:0];
end
S4: begin
state<=S5;
knum_r<=knum[3:0];
end
S5: begin
state<=S6;
knum_r<=knum[3:0];
end
S6: begin
state<=S7;
knum_r<=knum[3:0];
end
S7:state<=S1; //跳转到状态1 也就是让这扫描赋值过程循环多次
default:begin
state<=S1 ;
knum_r<=knum_r;
end
endcase
end
//=========判断是否抖动===================
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
keyf<=6'b000_0000;
else case(state)
S1:begin
keyf<=6'd0;end
S2:begin
if(knum[3:0]==knum_r) // 如果寄存器knum_r的值与输入的按键后三位值一样 那么 我们将寄存器keyf 第1位赋值为1
keyf <={5'd0,1'b1};
else
keyf <={5'd0,1'b0}; //否则为0
end
S3:begin
if(knum[3:0]==knum_r) // 如果寄存器knum_r的值与输入的按键后三位值一样 那么 我们将寄存器keyf 第2位赋值为1
keyf <={4'd0,1'b1,keyf[0]};
else
keyf <={4'd0,1'b0,keyf[0]};
end
S4:begin
if(knum[3:0]==knum_r) // 如果寄存器knum_r的值与输入的按键后三位值一样 那么 我们将寄存器keyf 第3位赋值为1
keyf <={3'd0,1'b1,keyf[1:0]};
else
keyf <={3'd0,1'b0,keyf[1:0]};
end
S5:begin
if(knum[3:0]==knum_r) // 如果寄存器knum_r的值与输入的按键后三位值一样 那么 我们将寄存器keyf 第4位赋值为1
keyf <={2'd0,1'b1,keyf[2:0]};
else
keyf <={2'd0,1'b0,keyf[2:0]};
end
S6:begin
if(knum[3:0]==knum_r) // 如果寄存器knum_r的值与输入的按键后三位值一样 那么 我们将寄存器keyf 第5位赋值为1
keyf <={1'd0,1'b1,keyf[3:0]};
else
keyf <={1'd0,1'b0,keyf[3:0]};
end
S7:begin
if(knum[3:0]==knum_r) // 如果寄存器knum_r的值与输入的按键后三位值一样 那么 我们将寄存器keyf 第6位赋值为1
keyf <={1'b1,keyf[4:0]};
else
keyf <={1'b0,keyf[4:0]};
end
endcase
end
//============判断寄存器keyf中值是否一样================
always @(posedge clk or negedge rst_n)
if(!rst_n)
kreanum<=4'd0;
else if(keyf[5]& keyf[4] &keyf[3]&keyf[2]&keyf[1]&keyf[0]) //如果其中六个值一样
kreanum<=knum_r ; //我们将结果赋值给输出kreanum
else
begin
kreanum<=kreanum ; // 如果不一样 ,我们让输出值保持原样
end
//============今天可以不看=========================
always @(posedge clk or negedge rst_n)
if(~rst_n)
kreanum_vld<=1'b0;
else if(~knum[4])
kreanum_vld<=1'b0;
else begin
if(keyf[5]& keyf[4]& keyf[3]& keyf[2]& keyf[1]& keyf[0])
kreanum_vld<=1'b1;
else
kreanum_vld<=kreanum_vld;
end
endmodule
按键驱动模块: 就是将按键扫描模块和按键消抖模块例化相连,这个就不细说了
module key (
input clk ,
input rst_n ,
input [3:0]row ,
output kreanum_vld ,
output [3:0]col ,
output [3:0]kreanum
);
//========middle_signal================
reg [31:0]cnt ;
reg clk_1khz ;
wire [4:0]knum ;
//=========fre_1000hz==================
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt<= 0;
clk_1khz<=0;
end
else if (cnt==49999)
begin
cnt<= 0;
clk_1khz<=~clk_1khz;
end
else
cnt<=cnt+1 ;
end
//========inst======================
keyscan keyscan(
. clk (clk_1khz),
.rst_n (rst_n ),
. row (row ),
.col (col ),
.knum (knum )
);
keyshake keyshake(
.clk (clk_1khz ) ,
.rst_n (rst_n ) ,
.knum (knum ) ,
.kreanum (kreanum ) ,
.kreanum_vld (kreanum_vld )
);
endmodule |