Arduino开发实例-PS/2键盘驱动

PS/2键盘驱动

1、PS/2通信协议介绍

物理 PS/2 端口是 6 针 DIN 连接器。 连接器引脚如下所示:

在这里插入图片描述

Vcc/Ground 为设备提供电源 (5V),而 Data 和 Clock 是两条集电极开路线,带有上拉电阻到 Vcc。 电阻值并不重要(1 – 10 KOhm),最小值给出最短的上升时间,而较大的值允许更少的功耗。

PS/2 协议是一种双向串行同步协议。 当数据线和时钟线为高电平时,总线空闲,键盘/鼠标可以开始传输数据; 主机可以随时通过将时钟线拉低 100 微秒来禁止传输。 设备总是产生时钟信号,如果主机想要通信,它可以通过将时钟线拉低(禁止设备传输)、将数据线拉低然后释放时钟线来实现:这是发送的请求 状态并告诉设备开始生成时钟脉冲。

总线状态如下:
数据 = 高电平,时钟 = 高电平:空闲状态。
数据 = 高电平,时钟 = 低电平:禁止通信。
数据 = 低电平,时钟 = 高电平:主机请求发送

数据帧由 11 位或 12 位组成(取决于数据方向):

  • 一个起始位(总是低)
  • 8 个数据位,LSB 在前
  • 奇校验位
  • 一个停止位(总是高)
  • 一个确认位(从主机传输到设备时,设备将数据线拉低)

如果数据位中有偶数个 1,则设置奇偶校验位,如果数据位中有奇数个 1,则重置 (0)。数据位中 1 的数量加上奇偶校验位总是加起来为奇数(奇校验)。这用于错误检测。键盘/鼠标必须检查该位,如果不正确,它应该像收到无效命令一样响应。

从设备发送到主机的数据在时钟信号的下降沿被读取;从主机发送到设备的数据在上升沿被读取。时钟频率必须在 10 - 16.7 kHz 范围内。这意味着时钟必须高 30 - 50 微秒,低 30 - 50 微秒。

1)设备到主机通信

数据线和时钟线都是集电极开路。 每条线和+5V之间接一个电阻,所以总线空闲状态为高。 当键盘或鼠标要发送信息时,它首先检查时钟线以确保它处于高逻辑电平。 如果不是,则主机正在禁止通信,并且设备必须缓冲任何要发送的数据,直到主机释放时钟。 在设备可以开始传输其数据之前,时钟线必须连续保持高电平至少 50 微秒。

1 个起始位。 这始终为 0。
8 个数据位,最低有效位在前。
1 个奇偶校验位(奇校验)。
1 个停止位。 这始终是 1。

设备检查时钟线的状态:如果它为高电平,则开始传输数据(时钟线必须在设备开始传输之前连续保持高电平至少 50 微秒)。 设备产生时钟脉冲,数据必须在时钟信号的下降沿稳定,在上升沿后变化:

在这里插入图片描述

从数据跳变到时钟信号下降沿的时间必须大于 5 微秒且小于 25 微秒,而从时钟信号的上升沿到数据跳变的时间必须至少为 5 微秒(因此我们可以采样 时钟信号低电平期间的数据,而高电平期间数据变化)。 如果主机通过在第 11 个时钟脉冲之前将时钟拉低 100 微秒来禁止传输,则设备必须在时钟线再次为高电平时重新发送帧(并且主机不再禁止通信)。 在通信被禁止时创建的任何数据都必须被缓冲(为此目的,键盘有一个 16 字节的缓冲区,而鼠标只存储当前的移动数据包)。

例如,扫描从键盘发送到计算机的“Q”键 (15h) 的代码。 通道 A 是时钟信号; 通道 B 是数据信号。

在这里插入图片描述

2)主机到设备通信

由于设备总是产生时钟信号,主机必须通过将时钟线拉低 100 微秒,然后将数据线拉低并再次拉高时钟线,将时钟线和数据线置于请求发送状态

  • a)当设备检测到这种状态时,它将开始产生时钟脉冲,并将时钟输入帧的数据位
  • b)主机在时钟为低电平时更改数据,而设备在时钟为高电平时对数据线进行采样(此 与设备到主机通信期间发生的情况相反):

在这里插入图片描述

在这里插入图片描述

由上图可以知道,主机要查找两个时间量:

  • (a) 是主机最初将时钟线拉低后设备开始产生时钟脉冲的时间,该时间不得超过 15 毫秒。
  • (b) 是发送数据包所需的时间,必须不大于 2ms。 如果不满足这些时间限制中的任何一个,则主机应生成错误。 收到“ack”后,主机可以立即将时钟线拉低以在处理数据时禁止通信。 如果主机发送的命令需要响应,则必须在主机释放时钟线后 20 毫秒内收到该响应。 如果这没有发生,主机会生成错误。

输入停止位后,设备将数据线拉低以确认数据,然后产生最后一个时钟脉冲并释放数据和时钟线。 主机到设备的通信对于向键盘发送命令很有用。

主机到设备通信步骤如下:

  • 1)将时钟线拉低至少 100 微秒。

  • 2)将数据线拉低。

  • 3)释放时钟线。

  • 4)等待设备将时钟线拉低。

  • 5)设置/复位数据线以发送第一个数据位

  • 6)等待设备将时钟拉高。

  • 7)等待设备将时钟拉低。

  • 8)对其他 7 个数据位和奇偶校验位重复步骤 5-7

  • 9)释放数据线。

  • 10)等待设备将数据拉低。

  • 11)等待设备将时钟拉低。

  • 12)等待设备释放数据和时钟

2、PS/2键盘鼠标驱动

键盘是由板载控制器监控的按键矩阵,称为**键盘编码器(Keyboard Encoder)**。 该控制器监控哪个键被按下或释放,并将相应的数据发送到主机。

控制器向主机发送的数据是已按下或释放的键的扫描码(Scan Code控制器扫描键盘是否有按键)。 有两种代码:Make CodeBreak Code。 每当按下或按住一个键时都会发送一个 Make Code; 释放键时会发送Break Code

键盘上的每个键都有其唯一的Make CodeScan Code,因此主机可以通过查看扫描码知道哪个键发生了什么。 所有的扫码组成一个扫码集:有三个Scan Code集(Scan Code Set 1、Scan Code Set 2或Scan Code Set 3),所有现代键盘默认为Scan Code Set 2

在这里插入图片描述

即使大多数Make Code只有一个字节长,也有一些扩展Make Code由两个或四个字节组成(所有这些扫描码都以字节 0xE0 开头)。

每当按下一个键时,就会向主机发送一个扫描码。 需要注意的是,扫描码对应于键盘上的物理键,与特定字符集的字符无关,主机将扫描码转换为匹配字符。

当一个键被释放时,一个Break Code被发送到主机:Break Code是前面有 0xF0 的Make Code(扩展键Break Code是 0xE0、0xF0 和键码)。

当一个键被按下时,该键变为**打字键(Typematic )**,并且(片刻之后)键盘将继续发送它的 Make Code,直到它被释放或按下另一个键。 打字键数据不会在键盘内缓冲:当按下多个键时,只有最后一个键变成打字键,即使其他键可能仍被按下,打字重复也会停止。

重置时,键盘执行所谓的 BAT(Basic Assurance Test,基本保证测试),如果成功则发送 0xAA,如果失败则发送 0xFC(如果有,键盘上的 LED 会闪烁)。

3、Arduino 驱动PS/2键盘实现

本次PS/2驱动支持库:https://github.com/PaulStoffregen/PS2Keyboard

接线示例如下:

在这里插入图片描述

简单示例如下:

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
#include <PS2Keyboard.h>

const int DataPin = 4;
const int IRQpin = 3;

PS2Keyboard keyboard;

void setup() {
//delay(1000);
keyboard.begin(DataPin, IRQpin);
Serial.begin(9600);
Serial.println("Keyboard Test:");
}

void loop() {
if (keyboard.available()) {

// read the next key
char c = keyboard.read();

// check for some of the special keys
if (c == PS2_ENTER) {
Serial.println();
} else if (c == PS2_TAB) {
Serial.print("[Tab]");
} else if (c == PS2_ESC) {
Serial.print("[ESC]");
} else if (c == PS2_PAGEDOWN) {
Serial.print("[PgDn]");
} else if (c == PS2_PAGEUP) {
Serial.print("[PgUp]");
} else if (c == PS2_LEFTARROW) {
Serial.print("[Left]");
} else if (c == PS2_RIGHTARROW) {
Serial.print("[Right]");
} else if (c == PS2_UPARROW) {
Serial.print("[Up]");
} else if (c == PS2_DOWNARROW) {
Serial.print("[Down]");
} else if (c == PS2_DELETE) {
Serial.print("[Del]");
} else {

// otherwise, just print all normal characters
Serial.print(c);
}
}
}

在这里插入图片描述

文章来源: https://iotsmart.blog.csdn.net/article/details/127932248