小梅哥和你一起深入学习FPGA之PS2键盘驱动
五、 代码分析
本文引用地址:https://www.eepw.com.cn/article/278903.htm这里,解码的关键是PS2接口的时钟信号,该时钟为异步时钟,我们需要通过边沿检测的方式来检测其下降沿,以便根据下降沿的个数来确定每个时钟我们因该做什么,边沿检测的电路,前面几个实验已经讲过很多次了,这里便不再做过多的解释,贴上代码即可:
以下是代码片段:
reg PS2_Clk_Tmp0,PS2_Clk_Tmp1,PS2_Clk_Tmp2,PS2_Clk_Tmp3;
wire nedge_PS2_Clk; /*PS2从机时钟下降沿检测标志信号*/
always @ (posedge Clk or negedge Rst_n)
if(!Rst_n) begin
PS2_Clk_Tmp0 <= 1'b0;
PS2_Clk_Tmp1 <= 1'b0;
PS2_Clk_Tmp2 <= 1'b0;
PS2_Clk_Tmp3 <= 1'b0;
end
else begin
PS2_Clk_Tmp0 <= PS2_Clk;
PS2_Clk_Tmp1 <= PS2_Clk_Tmp0;
PS2_Clk_Tmp2 <= PS2_Clk_Tmp1;
PS2_Clk_Tmp3 <= PS2_Clk_Tmp2;
end
/*-------获取PS时钟信号的下降沿-------------*/
assign nedge_PS2_Clk = !PS2_Clk_Tmp0 & !PS2_Clk_Tmp1 & PS2_Clk_Tmp2 & PS2_Clk_Tmp3;
一个PS2的数据包总共由11位组成,因此会有11个时钟下降沿,因此我们必须对下降沿的个数准确计数,才能保证我们能够解码得到正确的数据,这里,使用我们的PS2时钟下降沿标志信号来使能我们的计数器自加,当计数器加到11后,表示一个数据包接收完成,将计数器清零,等待下一个下降沿的到来,相关代码如下:
以下是代码片段:
/*------------PS2时钟下降沿个数计数器-----------------------*/
always @(posedge Clk or negedge Rst_n)
if(!Rst_n)
Cnt1 <= 4'd0;
else if(Cnt1 == 4'd11)
Cnt1 <= 4'd0;
else if(nedge_PS2_Clk)
Cnt1 <= Cnt1 + 1'b1;
接下来,就是根据时钟下降沿的计数个数,来读取对应位的数据了,因为采用了非阻塞赋值的方式,因此,PS2时钟下降沿到来时,此时Cnt1执行自加1操作,但同时如果也来用Cnt1的值来确定数据位数,就一定会造成错误,因为此时,Cnt1的加1操作并没有执行,而是会在下一个时钟上升沿到来之时才变,因此,为了保证我们使用的Cnt1的数据是已经更新了的,我们需要在Cnt1已经变化之后再来使用其值做判断,即在PS2时钟下降沿检测成功后,滞后一个系统时钟周期后再来读取PS2_Din上的值,比较简单的操作方式就是将PS2时钟下降沿检测标志信号再用寄存器打一拍,对应代码如下:
以下是代码片段:
always @(posedge Clk)nedge_PS2_Clk_Shift <= nedge_PS2_Clk;
可能这里相对比较难以理解,希望大家结合仿真结果自学揣摩体会。
接下来就是根据Cnt1的计数值来读取每一位的数据了,这部分代码很简单,如下所示:
以下是代码片段:
/*--------------读取8位数据位---------------*/
always @ (posedge Clk or negedge Rst_n)
if(!Rst_n)
Data_tmp <= 8'd0;
else if(nedge_PS2_Clk_Shift) begin
case(Cnt1)
4'd2:Data_tmp[0] <= PS2_Din;
4'd3:Data_tmp[1] <= PS2_Din;
4'd4:Data_tmp[2] <= PS2_Din;
4'd5:Data_tmp[3] <= PS2_Din;
4'd6:Data_tmp[4] <= PS2_Din;
4'd7:Data_tmp[5] <= PS2_Din;
4'd8:Data_tmp[6] <= PS2_Din;
4'd9:Data_tmp[7] <= PS2_Din;
default:Data_tmp <= Data_tmp;
endcase
end
else
Data_tmp <= Data_tmp;
通过以上操作,我们就能正确的解码PS2键盘发送过来的每一个字节的数据了,但是,这些数据代表了什么呢,如果是断码标志,或者是长码标志,我们又该如何进行操作呢,这里,小梅哥先贴上我的处理代码:
以下是代码片段:
always @ (posedge Clk or negedge Rst_n)
if(!Rst_n) begin
Break_r <= 1'b0;
Key_Valve <= 10'd0;
Key_Flag <= 1'b0;
Long_Code_r <= 1'b0;
end
else if(Cnt1 == 4'd11) begin
if(Data_tmp == 8'hE0) /*判断是否为长码*/
Long_Code_r <= 1'b1; /*将长码标志置1*/
else if(Data_tmp == 8'hF0) /*判断是否为断码*/
Break_r <= 1'b1; /*将断码标志置1*/
else begin /*检测到的数据为通码*/
Key_Valve <= {Break_r,Long_Code_r,Data_tmp};/*将长码标志、断码标志和解码到的按键码输出*/
Key_Flag <= 1'b1; /*产生解码成功标志信号*/
Long_Code_r <= 1'b0; /*清零长码标志*/
Break_r <= 1'b0; /*清零断码标志*/
end
end
else begin
Key_Valve <= Key_Valve;
Key_Flag <= 1'b0;
Break_r <= Break_r;
Long_Code_r <= Long_Code_r;
end
这里,小梅哥使用了两个标志寄存器,当检测数据完成后,即Cnt1=11时,就对解码到到数据进行判断,如果Data_tmp == 8'hE0,则解码到到数据为长码(扩展码)标志,此时便将长码标志寄存器置1,如果Data_tmp == 8'hF0,则解码到到数据为断码标志,此时便将断码标志寄存器置1。然后,当解码到其他数据(如单字节通码或双子节通码的第二个字节)后,便将解码到的数据连同断码和长码标志寄存器的状态输出,并给出按键检测成功标志(Key_Flag置1)。
六、 仿真分析
为了对小梅哥设计的PS2键盘解码驱动进行验证,小梅哥编写了一个Testbench来模拟键盘发送数据,通过观察键盘解码驱动的输出来验证该解码模块的正确性,关于模拟键盘发送数据,在一份介绍PS2协议的手册中有如下描述:
我推荐仿真键盘/鼠标采用下面的过程发送一字节的数据到主机:
1) 等待Clock线为高电平, 即等待主机释放Clock线;
2) 延时50us;
3) 判断Clock线是否为高电平?
No―― 跳到第1步;
4) Data线是否为高电平?
No―― 放弃(跳到从主机读取字节的程序中) 。
5) 延迟20us,输出起始位(0) , 然后延迟20us, 再拉低Clock线保持40us后释放Clock线, 形成一个脉冲;
6) 延时20us, 测试Clock线是否为高电平?No―― 跳到第1步;
7) 输出第1个数据位, 然后延时20us, 再拉低Clock线保持40us后释放Clock线, 形成一个脉冲;
8) 重复6-7步发送剩下的7个数据位和校验位;
9) 延时20us, 测试Clock线是否为高电平?
No―― 跳到第1步;
因此,我们的模拟键盘发送数据的过程只需要依照上面的流程来即可,这里贴上小梅哥编写的testbench:
以下是代码片段:
`timescale 1ns/1ns
module PS2_Key_Board_Driver_tb;
reg Clk;/*system clock*/
reg Rst_n;/*复位信号*/
reg PS2_Din;/*PS2键盘数据线*/
reg PS2_Clk;/*PS2键盘时钟线*/
wire Key_Flag;/*解码得到键值标志信号*/
wire [9:0] Key_Valve;/*解码结果,其中最高位为通/断码识别位,0为通码,1为断码,低八位为码值*/
PS2_Key_Board_Driver u1(
.Clk(Clk),
.Rst_n(Rst_n),
.PS2_Din(PS2_Din),
.PS2_Clk(PS2_Clk),
.Key_Flag(Key_Flag),
.Key_Valve(Key_Valve)
);
initial begin
Clk = 1;
Rst_n = 0;
PS2_Din = 1;
PS2_Clk = 1;
#200;
Rst_n = 1;
Key_Event(8'h1A); /* Z */
#400;
Key_Event(8'h35); /* X */
#800;
Key_Event(8'h44); /* O */
#1320;
Key_Event(8'h4D); /* P */
#2560;
Key_Event(8'h24); /* E */
#1230;
Key_Event(8'h31); /* N */
#20000;
Long_Key_Event(8'h70); /* "INSERT" */
#400;
Long_Key_Event(8'h6c); /* "HOME" */
#800;
Long_Key_Event(8'h7d); /* "PAGE UP" */
#1320;
Long_Key_Event(8'h71); /* "DELETE" */
#2560;
Long_Key_Event(8'h69); /* "END" */
#1230;
Long_Key_Event(8'h7a); /* "PAGE DOWN" */
#2000000;
$stop;
end
/*---------生成工作时钟-----------*/
always #10 Clk = ~Clk;
/*----任务:以PS2协议发送一个字节的数据-----*/
task Send_data;
input [7:0]Data;
begin
PS2_Din = 0; /*发送起始位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[0];/*发送第0位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[1];/*发送第1位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[2];/*发送第2位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[3];/*发送第3位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[4];/*发送第4位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[5];/*发送第5位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[6];/*发送第6位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[7];/*发送第7位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = 0;/*暂时忽略校验位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = 1;/*停止位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
end
endtask
/*-----任务:模拟按下按键的操作------*/
task press_key;
input [7:0]Key_Number;
begin
Send_data(Key_Number);
#50000;
end
endtask
/*-----任务:模拟释放按键的操作------*/
task release_key;
input [7:0]Key_Number;
begin
Send_data(8'hF0);
#50000;
Send_data(Key_Number);
#50000;
end
endtask
/*----任务:模拟一次短码的按下和释放操作-----*/
task Key_Event;
input [7:0]Key_Number;
begin
press_key(Key_Number);
#30000;
release_key(Key_Number);
end
endtask
/*----任务:模拟一次长码的按下和释放操作-----*/
task Long_Key_Event;
input [7:0]Key_Number;
begin
press_key(8'he0);
#30000;
press_key(Key_Number);
#30000;
press_key(8'he0);
#30000;
release_key(Key_Number);
end
endtask
endmodule
testbench中使用了一个主任务来模拟键盘的数据发送,并使用了其他几个基于此任务的任务来模拟按键按下、按键释放、普通按键按下+释放、扩展按键按下+释放的过程,通过模拟进行部分按键的按下和释放操作,来观察解码模块的结果,便可获知解码是否成功。
以下为小梅哥的仿真结果,与我发送的数据一致,因此表明我的PS2解码是成功的。

七、 下板验证
这里,小梅哥在至芯科技ZX2的板子上验证通过,如下图:
其中,第三个数码管,为0表示普通按键通码,为2表示普通按键断码,为1表示扩展按键通码,为3表示扩展按键断码。



fpga相关文章:fpga是什么
蜂鸣器相关文章:蜂鸣器原理
评论