PS2KeyRaw:轻量级PS/2键盘原始字节驱动库

张开发
2026/5/21 23:27:17 15 分钟阅读
PS2KeyRaw:轻量级PS/2键盘原始字节驱动库
1. PS2KeyRaw 库概述PS2KeyRaw 是一个专为嵌入式平台设计的轻量级 PS/2 键盘底层驱动库面向 Arduino 生态AVR、SAM、Teensy 等主流架构其核心定位是提供原始字节级访问能力与最小化资源占用。该库不进行键盘扫描码到 ASCII 或 Unicode 的高层映射也不处理按键语义如 ShiftK → k、功能键组合FnF5或多媒体键逻辑而是忠实捕获并缓存 PS/2 键盘发送的每一个原始字节数据流——这使其成为硬件验证、协议分析、固件调试及定制化键盘协议栈开发的理想基础组件。与同系列的 PS2KeyAdvanced支持 UTF-8 编码、多媒体键、F1–F24 功能键和 PS2KeyMap提供可配置拉丁键盘布局映射不同PS2KeyRaw 的设计哲学是“零抽象、零转换、零假设”。它不预设键盘类型AT vs. PS/2 兼容、不解析扫描码序列结构如通电自检响应0xFA、重发请求0xFE、错误标志0xFF亦不维护按键状态机。所有接收到的字节均按 FIFO 顺序存入内部环形缓冲区由用户通过read()显式逐字节消费。这种设计使代码体积压缩至极致在 Arduino UnoATmega328P平台上编译后 Flash 占用仅约 1.2KBRAM 消耗低于 64 字节远低于多数同类库常见为 3–5KB Flash。工程实践中该库的价值体现在三类典型场景硬件 Bring-up 阶段快速验证 PS/2 接口物理连接线序、上拉、电平兼容性、中断引脚响应能力及时序稳定性协议逆向分析捕获真实键盘通信流含 E0/E1 前缀、断开码0xF0、扩展序列0xE0 0xXX用于构建私有键盘协议或兼容性测试实时性敏感应用在 FreeRTOS 或裸机系统中作为低延迟输入源配合自定义状态机实现专用控制逻辑如工业 HMI 的快捷键触发、医疗设备的防误触双击检测。2. PS/2 键盘通信协议底层解析理解 PS2KeyRaw 的工作原理必须深入 PS/2 接口的电气特性与协议规范。该接口采用双向同步串行通信仅需两根信号线CLKClock由键盘主动输出的时钟信号频率范围 10–16.7 kHz典型值 13.7 kHz空闲态为高电平DATAData双向数据线空闲态由键盘内部上拉电阻拉至高电平通常 5V主机与键盘通过开漏Open-Drain方式驱动。2.1 数据帧结构与时序约束每次有效数据传输以下降沿触发当键盘准备发送字节时先将 CLK 拉低至少 60μs起始位随后在 CLK 下降沿采样 DATA 线状态。标准数据帧包含 11 位位序含义说明0起始位固定为0标志数据帧开始1–8数据位LSB 在前共 8 位对应 PS/2 扫描码Scan Code9奇偶校验位奇校验1 表示数据位中1的个数为奇数用于检测传输错误10停止位固定为1标志数据帧结束关键时序参数符合 IBM PS/2 规范位时间Bit TimeCLK 周期典型值 72–100μs起始位宽度CLK 低电平持续 ≥60μs数据采样点在 CLK 下降沿后约 20–30μs 处读取 DATA释放时间Release Time数据帧结束后CLK 和 DATA 均需保持高电平 ≥50μs。2.2 键盘状态机与关键事件PS/2 键盘内部维持独立状态机主机Arduino仅作为被动接收方。典型交互流程如下上电自检POST键盘上电后发送0xAA自检成功或0xFC自检失败随后发送0xFA确认按键按下发送对应扫描码如0x1C为回车键按键释放发送0xF0 原扫描码如0xF0 0x1C重复触发Auto-repeat长按按键时键盘以固定间隔约 30Hz重复发送扫描码部分键盘在重复时省略0xF0前缀此行为差异正是 PS2KeyRaw 保留原始字节的价值所在错误响应若主机未及时响应如未在规定时间内拉低 CLK键盘发送0xFF错误标志。PS2KeyRaw 的中断服务程序ISR严格遵循上述时序在 CLK 下降沿触发后精确延时 30μs 读取 DATA 线电平并将 8 位数据拼装为字节存入缓冲区。整个 ISR 执行时间控制在 15μs 内确保不丢失后续位。3. 硬件连接与平台适配规范PS2KeyRaw 对硬件连接有明确且不可妥协的要求违反将导致通信失败或硬件损伤。3.1 物理连接准则引脚分配DATA线连接至任意 GPIO 引脚无特殊要求CLK线必须连接至支持外部中断的引脚因库依赖硬件中断捕获 CLK 下降沿电平兼容性绝大多数 PS/2 键盘为5V TTL 电平输出高电平 ≈ 4.5–5V低电平 ≈ 0VArduino Uno/MegaAVR为 5V 系统可直连Arduino DueSAM3X8E为 3.3V I/O其 GPIO 无法承受键盘端 5V 上拉电压必须使用电平转换器如 TI TXS0102、NXP PCA9306 或分立 MOSFET 电路禁用引脚Pin 13LED 引脚在所有平台均禁止使用因其内部连接板载 LED存在额外电容与驱动能力问题易导致 CLK 信号畸变3.2 平台中断引脚映射表平台支持的 IRQ 引脚CLK 连接点备注Arduino Uno2,3对应 INT0, INT1Arduino Mega25602,3,18,19,20,21INT0–INT7Arduino Due除 Pin 13 外所有数字引脚SAM3X8E 支持任意 GPIO 外部中断Teensy 2.05,6,7,8不含 Pin 13Teensy 2.00,1,2,3,18,19,36,37需查 Teensy 引脚定义Sanguino2,10,11ATmega644P 平台实践提示在 Mega2560 上优先选用Pin 2INT0而非Pin 21INT7因低编号中断向量响应更快Due 平台虽支持全引脚中断但建议避开Pin 2–Pin 13与 USB 通信相关引脚选择Pin 22–Pin 53更稳妥。3.3 电平转换电路设计针对 Due 等 3.3V 平台推荐采用TXS0102 双向电平转换 IC成本低、无需方向控制Keyboard CLK ──┬──► TXS0102 A1 ──► Arduino Due CLK (3.3V) │ Keyboard DATA ──┴──► TXS0102 A2 ──► Arduino Due DATA (3.3V) TXS0102 VCCA 5V (接键盘电源) TXS0102 VCCB 3.3V (接 Due 电源) TXS0102 GND 共地若使用分立元件方案推荐 N 沟道 MOSFET如 2N7002搭建单向转换适用于 CLK 输入键盘 CLK → MOSFET 漏极DMOSFET 源极S→ Due CLK 引脚 10kΩ 上拉至 3.3VMOSFET 栅极G→ 3.3V开启键盘与 Due 共地4. API 接口详解与使用范式PS2KeyRaw 提供极简 API 集全部为public成员函数无构造函数参数初始化通过begin()完成。4.1 核心 API 函数签名与语义函数名原型功能说明关键约束begin()void begin(uint8_t dataPin, uint8_t irqPin)初始化 PS/2 接口配置 DATA 引脚为输入注册 CLK 中断服务程序irqPin必须为平台支持的中断引脚调用前确保引脚未被其他外设占用available()int available()返回缓冲区中待读取字节数0–64非阻塞查询值为瞬时快照多线程环境下需注意竞态FreeRTOS 中建议加临界区read()int read()读取缓冲区队首字节返回0–255若缓冲区为空返回-1必须在available() 0后调用否则返回无效值peek()int peek()未在 README 明确提及但源码实现查看队首字节但不移除用于预判下一个字节内容如检测0xF0判断是否为释放码flush()void flush()清空缓冲区所有字节通常在初始化后或错误恢复时调用4.2 初始化与中断配置源码逻辑begin()函数内部执行以下关键操作以 AVR 平台为例void PS2KeyRaw::begin(uint8_t dataPin, uint8_t irqPin) { // 1. 配置 DATA 引脚为输入默认上拉已由键盘提供无需额外启用 pinMode(dataPin, INPUT); this-dataPin dataPin; // 2. 配置 CLK 引脚为输入并启用内部上拉增强抗干扰非必需但推荐 pinMode(irqPin, INPUT_PULLUP); this-irqPin irqPin; // 3. 根据 irqPin 映射到对应中断向量AVR: digitalPinToInterrupt() int interruptNum digitalPinToInterrupt(irqPin); if (interruptNum ! NOT_AN_INTERRUPT) { // 4. 注册 ISRattachInterrupt(interruptNum, ps2_isr, FALLING) // ps2_isr 为 C 函数指针内部调用类静态成员 processBit() } }其中processBit()是核心 ISR 处理函数伪代码如下// 在 ISR 中执行禁用全局中断 void PS2KeyRaw::processBit() { static uint8_t bitCount 0; static uint8_t dataByte 0; static uint8_t parity 0; if (bitCount 0) { // 起始位 bitCount 1; } else if (bitCount 8) { // 数据位 uint8_t bit digitalRead(dataPin); dataByte | (bit (bitCount - 1)); parity ^ bit; bitCount; } else if (bitCount 9) { // 奇偶校验位 parity ^ digitalRead(dataPin); // 校验位参与计算 bitCount; } else if (bitCount 10) { // 停止位 if (parity 1) { // 奇校验正确 buffer.push(dataByte); // 存入环形缓冲区 } bitCount 0; dataByte 0; parity 0; } }4.3 典型使用模式与代码示例场景一裸机轮询式读取适合简单调试#include PS2KeyRaw.h PS2KeyRaw keyboard; void setup() { Serial.begin(115200); // CLK2, DATA3 for Uno keyboard.begin(3, 2); } void loop() { if (keyboard.available()) { int byte keyboard.read(); Serial.print(Raw: 0x); Serial.println(byte, HEX); // 解析常见事件示例 if (byte 0xFA) Serial.println( -- ACK from keyboard); else if (byte 0xF0) Serial.println( -- Release prefix); else if (byte 0xFF) Serial.println( -- Error!); } delay(10); // 避免串口刷屏 }场景二FreeRTOS 任务中消费数据生产环境推荐#include PS2KeyRaw.h #include FreeRTOS.h #include queue.h PS2KeyRaw keyboard; QueueHandle_t keyQueue; void vPS2Task(void *pvParameters) { uint8_t keyByte; while (1) { // 阻塞等待数据超时 100ms if (xQueueReceive(keyQueue, keyByte, pdMS_TO_TICKS(100)) pdPASS) { // 在此处实现业务逻辑如映射为 ASCII、触发事件等 processKeyEvent(keyByte); } } } void setup() { keyboard.begin(3, 2); keyQueue xQueueCreate(32, sizeof(uint8_t)); // 创建 32 字节队列 // 启动 PS2 数据搬运任务优先级 2 xTaskCreate(vPS2Task, PS2, configMINIMAL_STACK_SIZE * 2, NULL, 2, NULL); } // 在 ISR 中安全入队使用 FromISR 版本 void IRAM_ATTR ps2_ISR_Handler() { if (keyboard.available()) { uint8_t b keyboard.read(); BaseType_t xHigherPriorityTaskWoken pdFALSE; xQueueSendFromISR(keyQueue, b, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }5. 调试技巧与常见故障排除5.1 通信失效诊断树当keyboard.available()始终返回0时按以下顺序排查物理层检查用万用表确认CLK引脚在按键时是否有电平跳变应看到周期性方波测量DATA线空闲电平是否为高≈5V 或 3.3V检查线序CLK是否误接DATA或反接中断配置验证在ps2_ISR_Handler开头添加digitalWrite(LED_BUILTIN, HIGH)观察 LED 是否闪烁若不闪烁确认irqPin是否在平台支持列表中且attachInterrupt()返回成功电平兼容性Due 用户必须验证电平转换器输入/输出电压是否符合规范A 侧 5VB 侧 3.3V使用示波器观测 CLK 信号边沿是否陡峭过缓边沿会导致 ISR 误触发缓冲区溢出若available()返回值持续增长64说明read()调用频率不足需优化主循环或增加任务优先级。5.2 扫描码序列解析指南PS2KeyRaw 输出的原始字节需结合键盘协议解析。常见模式标准按键单字节如0x1CEnter、0x01EscShift/Ctrl/Alt 等修饰键按下0x12Shift释放0xF0 0x12扩展键E0 前缀方向键、Insert、Delete 等格式为0xE0 XX如0xE0 0x48为 ↑Pause 键特殊序列0xE1 0x14 0x77 0xE1 0xF0 0x14 0xF0 0x77Break Code长按Ctrl时键盘发送0xF0 0x14而非0x14表示“Ctrl 断开”。工程提示在SimpleTest示例中若发现0xF0后紧跟0x1C即表示 Enter 键被释放若连续收到0x1C无0xF0则是 Auto-repeat 触发的重复扫描码。6. 与高级库的协同演进路径PS2KeyRaw 并非孤立存在而是 Paul Carpenter PS/2 库生态的基石。其设计天然支持向上演进向 PS2KeyMap 迁移当需支持多语言布局时可将 PS2KeyRaw 的read()输出直接喂入 PS2KeyMap 的parseScanCode()后者根据KEYMAP_LATIN_US等预设表返回 Unicode向 PS2KeyAdvanced 扩展若需 F24、音量调节等多媒体键PS2KeyAdvanced 内部即封装了 PS2KeyRaw 的缓冲区管理用户只需调用getExtendedKey()自定义协议栈集成在 RTOS 环境中可将 PS2KeyRaw 作为输入子系统其keyQueue与 GUI 任务、命令解析任务、日志任务解耦符合分层架构原则。这种“原始字节 → 语义键值 → 应用事件”的三级抽象正是嵌入式输入驱动设计的最佳实践底层库专注时序与可靠性上层库专注业务逻辑开发者可根据项目复杂度自由裁剪。

更多文章