avatar

目录
PS/2键盘接口

PS/2接口

介绍

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

数据传输方式

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

  • 开始位0

  • 8位数据位

  • 1位奇偶校验位

  • 停止位1

键盘上每一个按键都有一个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语言编写程序接受键盘信号

老师提供的控制器代码:

verilog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//键盘控制器
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:其他模块处理数据太慢,控制器设置的队列溢出。

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

基本功能:单个按键

verilog
1
2
3
4
5
6
7
8
9
10
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。

打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论