新闻中心

EEPW首页 > 嵌入式系统 > 牛人业话 > 小梅哥和你一起深入学习FPGA之PS2键盘驱动

小梅哥和你一起深入学习FPGA之PS2键盘驱动

作者:时间:2015-08-18来源:网络收藏

  五、 代码分析

本文引用地址:http://www.eepw.com.cn/article/278903.htm

  这里,解码的关键是接口的时钟信号,该时钟为异步时钟,我们需要通过边沿检测的方式来检测其下降沿,以便根据下降沿的个数来确定每个时钟我们因该做什么,边沿检测的电路,前面几个实验已经讲过很多次了,这里便不再做过多的解释,贴上代码即可:

  以下是代码片段:

  reg _Clk_Tmp0,_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是什么


蜂鸣器相关文章:蜂鸣器原理

上一页 1 2 下一页

关键词: FPGA PS2

评论


相关推荐

技术专区

关闭