Skip to content

PS/2键盘接口

Published:

PS/2 接口

介绍

PS/2 是一种常见的键盘接口(快淘汰了),它使用两根信号线,一根传输时钟信号 PS2_CLK,另一根传输数据 PS2_DAT。PS2_CLK 主要用于指示数据线上的比特位在什么时候是有效的。 键盘和主机之间可以进行双向的数据传递,本实验只讨论键盘向主机发送数据。

数据传输方式

当用户按键/松开时,键盘以每帧 11 位比特的格式串行传递数据给主机,同时在 PS2_CLK 时钟信号上传输对应的时钟。 这 11 位分别是:

键盘上每一个按键都有一个 8 位的“通码”(特殊的有 16 位),比如 w 的通码是“8’h1D”,当按下这个键时,键盘会向主机发送一个中间 8 位是“1D”的 11 位串数据,如果按下不松,键盘将一直不停地发送这个 11 位串。 每一个按键又都有一个“断码”,一般情况下,断码=F0+通码,比如 W 的断码是 16 位串“F01D”,当你松开 w 时,键盘向主机发送一个 F0,之后发送一个 1D,再之后就不发送数据了。 所以如果你按下 W,保持一段时间,再松开,接收到数据串是这样的:“1D-1D-1D-1D-F0-1D” 当有多个按键被按下时,将逐个的传递数据,“按下”这个过程总是有先后的。 按下 shift,开始传递 12、12、12,再按下 w,不再传递 12,而是开始不断传递 w 的通码 1D,当松开 shift 时,传递 F012,再松开 w,传递 F01D。

使用 Verilog 语言编写程序接受键盘信号

老师提供的控制器代码:

//键盘控制器
module ps2_keyboard(clk,clrn,ps2_clk,ps2_data,data,ready,nextdata_n,overflow);
    input clk,clrn,ps2_clk,ps2_data;
    input nextdata_n;
    output [7:0] data;
    output reg ready;
    output reg overflow; // fifo overflow
  // internal signal, for test
    reg [9:0] buffer; // ps2_data bits
  reg [7:0] fifo[7:0]; // data fifo
  reg [2:0] w_ptr,r_ptr; // fifo write and read pointers
  reg [3:0] count; // count ps2_data bits
    // detect falling edge of ps2_clk
    reg [2:0] ps2_clk_sync;
    always @(posedge clk)
    begin
            ps2_clk_sync <= {ps2_clk_sync[1:0],ps2_clk};
        end
    wire sampling = ps2_clk_sync[2] & ~ps2_clk_sync[1];
  always @(posedge clk)
    begin
        if(clrn == 0)
          begin // reset
                        count <= 0; w_ptr <= 0; r_ptr <= 0; overflow <= 0; ready<= 0;
            end
          else
          begin
            if(ready)
              begin // read to output next data
                if(nextdata_n == 1'b0) //read next data begin
                                    begin
                      r_ptr <= r_ptr + 3'b1;
                        if(w_ptr==(r_ptr+1'b1)) //empty
                             ready <= 1'b0;
                      end
                            end
                        if(sampling)
              begin
                               if(count == 4'd10)
                  begin
                                        if((buffer[0] == 0) && // start bit
                                            (ps2_data) && // stop bit
                       (^buffer[9:1]))
                      begin // odd parity
                          fifo[w_ptr] <= buffer[8:1]; // kbd scan code
                                                w_ptr <= w_ptr+3'b1;
                        ready <= 1'b1;
                        overflow <= overflow | (r_ptr == (w_ptr + 3'b1));// for next
                      end
                    count <= 0;
                                    end
                else
                  begin
                                        buffer[count] <= ps2_data; // store ps2_data
                                    count <= count + 3'b1;
                                    end
                 end
          end
    end
        assign data = fifo[r_ptr]; //always set output data
endmodule

input clk:使用 DE10 开发板自带的 CLOCK_50。

input clrn:使能端,接入 SW[0]。

input ps2_clk:键盘时钟,使用 DE10 的 PS2_CLK 引脚。

input ps2_data:键盘数据,使用 DE10 的 PS2_DAT 引脚。

output data:键盘发来的 11 位串中的 8 位键码,在时钟信号附近短时间内有效。

output ready:当 ready 为 1 时,data 有效,其他模块可以接收 data。

input nextdata_n:当 nextdata_n 为 0 时,控制器模块继续读取下一个数据。为 1 时暂停读取。

注意:实验要求在自己的处理模块(下面的 solve)中处理完一个数据后,将 nextdata_n 置为 0,这样控制器模块才能继续读取并发送数据,且只能置空 1 个时钟周期。(否则,solve 模块将有可能重复接收 data)

overflow:其他模块处理数据太慢,控制器设置的队列溢出。

自行完成的数据处理代码:

基本功能:单个按键

module solve(clk,clrn,data,ready,nextdata_n,count,state,key_data,sig,sig_cap)
    input clk;//CLOCK_50
  input clrn;//使能端,SW[0]
  input [7:0] data;//控制模块输出的8位键码
  input ready;//控制模块给的是否能够读取的信号
  output reg nextdata_n;//本模块输出的信号,控制模块用它判断是否应该继续读数
  output reg [7:0] count;//用于记录按键次数
  output reg [1:0] state;//设置状态机
  output reg [7:0] key_data;//在data有效时,接收data
  ……

状态机设计:

00:初始化状态,数码管不显示。

01:一直按键状态,数码管持续显示键码和 ASCII 码。

10:按键松开状态,数码管不显示。

在 00 状态下,若遇到通码,则跳转 01。

在 01 状态下,若接收通码,则仍然跳转 01;若接收到 F0(断码的前 8 位),则跳转 10。

在 10 状态下,直接跳转至初始化状态 00。

这样,就可以实现单个按键的信号接收过程了。

拓展功能:shift&ctrl

要实现组合键,状态机就变得复杂了。

把上面基础功能的状态机当成一个三角形,那么这里组合键的出现将状态机升级为两个三角形衔接的组合。

总共有 5 个状态。

A:接收到通码就跳转 B,记录下这个通码为 f。

B:接收到 F0 就跳转 E。如果 f 是 12(shift)/14(ctrl),接收到其他通码,就跳转 C;否则,留在 B。

C:若继续接收其他非 12/14 的通码,则留在 C。若接收到 F0,跳转 D。

D:直接跳转回 B。

E:直接跳转 A。

caps_lock

这个功能就比较简单了,接收到断码的时候,判断一下下一位是不是 caps_lock 的键码,是的话就把 sig_cap 取反。

最终输出的时候,根据 sig_cap 的值,判断是否输出大写。

反思

这次实验,老师已经提供了键盘控制器模块的代码,只需要自行完成状态机处理数据即可。主要是为了学习 PS/2 接口键盘的数据传输原理,而且还是只学了键盘到主机单向传值的原理。

之前的实验都是小实验,模块很少,这次最大的难度就在于,信号很多,变量很多,模块很多,模块与模块之前是有时序关系的,很难处理。

比如说,ps2_keyboard 模块接收键盘的信号,同时提供数据 data 给 solve 模块,但却要根据 solve 模块输出的 nextdata_n 进行是否读数的判断。在 solve 模块处理完数据后,display 模块要把处理的结果显示在数码管上,这两者是有先后顺序的,必须先处理,再显示。但 solve 模块是时序电路,用的是非阻塞语句。这样就会出现一个问题:在一个 clk 触发后,solve 要处理数据 key_data,要把它从 A 变成 B,display 则要显示数据 B,这两个模块如果同时进行会怎样?display 到底显示的是 A 还是 B。就仿真结果来看,是 B。