- 嵌入式
- 固件
- 驱动开发
- 硬件开发
【免费下载链接】qmk_firmware
Open-source keyboard firmware for Atmel AVR and Arm USB families
导读
本篇文章聚焦 QMK Firmware 仓库中的keyboards/converter/palm_usb/stowaway转换器项目,它专门解决 Palm Pilot 时代 Think Outside Stowaway 串行键盘与现代 USB 主机之间的协议桥接问题。读完本文,你将掌握:如何用一块 Arduino Pro Micro(ATMega32u4)把 Stowaway 键盘接到电脑上、不同版本键盘(Palm / Handspring / Journada / iPaq)的接线与协议差异、负逻辑 9600 波特串行协议的底层实现(软串行、复位握手、矩阵状态机),以及如何通过配置头文件适配不同硬件。
项目定位:把"上古"串行键盘搬进现代 USB 世界
Stowaway 是 Think Outside 公司为 Palm Pilot 时代掌上设备设计的折叠式串行键盘,采用红外与串行两种接口,其中串行型号通过 9~12 芯排线连接 PDA。在 QMK 中,这类"旧外设转 USB"的设备被归入 converter 目录,与 ADB、Sun、XT 等经典键盘转换器并列,而 palm_usb/stowaway 正是其中专门面向 Stowaway 串行键盘的一员。
该转换器大量复用了 cy384 的 Arduino USB HID 适配方案(ppk_usb),由 milestogo 移植到 QMK。从实现上看,它没有沿用"行列扫描 + 键盘矩阵"的传统模式,而是以 自定义矩阵(CUSTOM_MATRIX) 的方式接管了矩阵层:串口收到键盘发来的按键编码后,被解析为 12 行 × 8 列的虚拟矩阵,再交给 QMK 键值处理管线。这意味着你拥有 QMK 全部的键位编排、层切换、宏等能力,只是"扫描"的对象从物理引脚变成了串行字节流。
构建与刷写:一条命令编译固件
在搭建好 QMK 构建环境后,编译本转换器固件只需要一条命令:
make converter/palm_usb/stowaway:default关于环境搭建与 make 的详细说明,可参考仓库内的 make 指南 与 新手指南。
固件的目标信息可以从 keyboard.json 中直接读出:
- 主控:
atmega32u4(即 Arduino Pro Micro / Teensy 2.0 常用的 USB 型 AVR,天然自带 USB 引擎,无需额外转接芯片); - 引导加载程序:
caterina(Pro Micro 的 Arduino Leonardo 风格引导); - USB 标识:VID
0xFEED、PID0x0001、设备版本1.0.0; - 功能开关:默认关闭 bootmagic、mousekey、extrakey,开启 console 与 command——前者便于通过串口调试输出观察协议交互,后者允许用键组合触发 QMK 命令模式。
需要说明的是,readme 指出目标主控虽是 ATMega32u4,但"其他具备 USB 能力的 AVR 也应当可用",因为核心逻辑只依赖 UART 与 GPIO。若你手头是其他 USB AVR,只需按 config.h 中的引脚注释调整引脚即可(见下文)。
硬件接线:Pro Micro 与 Stowaway 的一一对应
cy384 的原版方案有非常特殊的引脚分配,其 Arduino 软串行库使用的引脚与 QMK 不同,直接照搬会"破坏"QMK 的引脚约定,因此本移植版专门为 Pro Micro 重新规划了接线。readme 给出的接线表如下:
| Label | TX0 | RX1 | GND | GND | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|---|---|
| Palm | * | GND | VCC | RX | NC | RTS | NC | DCD | ||
| MCU | D1 | D0 | C6 | E6 |
对应到 config.h 中的宏,即为:
#define VCC_PIN D1 // Pro Micro 的 2 号引脚,给键盘供电 #define RX_PIN D0 // Pro Micro 的 3 号引脚,接收键盘串行数据(原 cy384 方案用 8 号) #define RTS_PIN C6 // Pro Micro 的 5 号引脚(原方案用 D4/4 号),用于复位握手 #define DCD_PIN E6 // Pro Micro 的 7 号引脚,读取键盘的 DCD 电平接线要点:
- VCC(电源):由 MCU 输出驱动键盘供电;
matrix_init()中上电后会将 VCC 拉高,模拟键盘被主机上电。 - RX(数据):键盘的 RXD 是"输出到 MCU 的 RX"方向,接 D0。这是软件串行口,具体实现见后文。
- RTS(请求发送):在复位流程中,MCU 会先读取该引脚电平,再将其切换为输出并"抖动"(toggle)以通知键盘"主机已重启"。这就是
rts_reset()函数做的事。 - DCD(载波检测):键盘上电后会主动拉高 DCD(最多 3 次)表示就绪,MCU 在
matrix_init()中会阻塞等待 DCD 变高才开始握手。 - 下拉电阻:键盘的 RX 线必须通过一个约10KΩ的电阻下拉到地,防止无数据时的浮空电平导致误码:
RX --|--3 10K | GNDreadme 特别注明:目前只有 Palm 3 型号的接线经过实测验证,其余型号的连线与插座参考 cy384 的 ppk_usb 项目文档及其提供的 3D 打印插座模型。
键盘版本与分组:至少 5 种硬件变体
Stowaway 折叠键盘在市面上至少有 5 个版本,readme 将它们归为三组,各自协议与接脚不同:
- Group 1:Palm 3、Palm 5、HP Journada 540、Compaq iPaq 键盘。这些型号共享同一套 RTS 握手协议,但每种设备的排线定义不同。
- Group 2:Handspring 键盘。不使用 RTS/DCD 握手协议,直接使用 TTL 电平信号;需要在 config.h 中把
HANDSPRING置为 1 来切换。 - Group 3:IRDA(红外)型号。未经测试,但理论上同样走串行。
readme 按"键盘处于打字位时从左到右"的视角给出了各型号排线顺序:
| 型号 | 引脚顺序 |
|---|---|
| Palm3 | [NC, VCC, RXD, RTS, NC, NC, DCD, NC, NC, GND] |
| Palm5 | [NC, VCC, RXD, RTS, NC, NC, DCD, NC, NC, GND](顺序相同,连接器不同) |
| Handspring | [VCC/TXD, NC, NC, NC, GND, NC, NC, RXD] |
| Journada | [NC, NC, NC, GND, NC, RTS, NC, DTR/VCC, RXD, DCD, NC] + [GND-IN, VCC-IN] |
| Ipaq | [NC, NC, DTR/VCC, NC, NC, RTS, NC, RXD, DCD, GND, NC, NC] |
可以看到,Palm 3 与 Palm 5 电学定义一致(仅物理连接器不同),而 Journada/iPaq 的排线更长、电源信号(DTR/VCC、VCC-IN)位置各不相同,接错会造成损坏风险,务必按上表核对。
串行协议:负逻辑、9600 波特、无流控
readme 中给出了协议的核心参数,这是整个转换器的"翻译字典":
信号:异步、负逻辑(Negative logic)、9600 波特率、无流控 帧格式:1 起始位 + 8 数据位 + 无校验 + 1 停止位(即 8N1)这里最关键的一点是负逻辑:Stowaway 键盘的空闲电平是低电平、数据位用高电平表示,而 AVR 的 USART 硬件引擎期望的是正逻辑(空闲高电平)。如果要用 AVR 硬件 UART,就必须在 RX/TX 引脚前外接反相器。本转换器选择了纯软件串行方案,因此完全不需要反相器电路,这也是它取名为"软串行"的原因。
从键盘到系统的命令
- 系统 → 键盘:无任何命令(转换器单向接收)。
- 键盘 → 系统:
0xFA:复位/就绪响应(Reset/Ready Response),随后紧跟0xFD。
结合 matrix.c 的源码可以看到更完整的字节流语义:
palm_handshake()会连续监听最多 5 个数据包,期望收到序列FF FA FD:0xFF之后0xFA表示复位就绪,紧随其后的0xFD确认握手完成;- 在
matrix_scan()中,若运行期再次收到0xFA或0xFD,会被当作"意外的复位字节"处理,直接丢弃并返回(源码注释为rstA/rstD); - Handspring 变体走另一条握手:
handspring_handshake()期望0xF9后跟0xFB,且按协议应在键盘上电约 15ms 后发送。
按键编码:一位一位"抠"出矩阵坐标
转换器把串行字节直接映射为虚拟矩阵坐标,matrix.c 中用三组位掩码完成解码:
#define KEY_MASK 0b10000000 // bit7:按下(0)/抬起(1) 标志 #define COL_MASK 0b00000111 // bit0-2:列号(0~7) #define ROW_MASK 0b01111000 // bit3-6:行号(0~11) #define ROW(code) (( code & ROW_MASK ) >> 3) #define COL(code) ((code & COL_MASK)) #define KEYUP(code) ((code & KEY_MASK) >> 7)即一个字节被拆解为:最高位表示键位状态(1 为抬起),bit3~6 为行号(配合MATRIX_ROWS 12),bit0~2 为列号(配合MATRIX_COLS 8)。matrix_scan()据此更新 12 字节的行位图matrix[MATRIX_ROWS],QMK 的键位处理层便能像对待普通矩阵一样读取状态。这也解释了为什么 keyboard.json 里的LAYOUT布局矩阵坐标范围是[0..11] × [0..7]。
软件实现:matrix.c 里的完整状态机
CUSTOM_MATRIX = yes意味着矩阵扫描由 matrix.c 全权接管(见 rules.mk,同时以SRC += matrix.c加入编译、UART_DRIVER_REQUIRED = yes拉入 UART 驱动)。整体流程可分为初始化、复位握手、周期扫描三个阶段。
初始化与复位握手
matrix_init()的流程:
- 开启调试输出(
debug_enable = true); - 调用
uart_init(9600)初始化软件串行; - 非 Handspring 模式下调
pins_init():VCC 输出、DCD/RTS 设为输入(读键盘状态); - 拉高
VCC_PIN给键盘上电; - 按版本分支执行握手:
- Handspring:直接尝试
handspring_handshake()(上电后约 15ms 内应收到0xF9 0xFB); - Palm 系:
while(!gpio_read_pin(DCD_PIN))阻塞等待键盘的 DCD 电平跳变(键盘会主动发出最多 3 次),随后调用rts_reset()抖动 RTS 让键盘认为"主机已重启",最后palm_handshake()验证0xFA 0xFD。
- Handspring:直接尝试
- 清空 12 行矩阵位图,调用
matrix_init_kb()交给用户层。
rts_reset()的实现细节值得一提:上电后 RTS 保持输入态,通过gpio_read_pin读取当前电平,再切换为输出:若读到高电平则先拉低,等待 10ms 后拉高,再等 5ms——这就是 readme 所说"频繁重启键盘"式的电源/复位管理(目前尚未实现真正的低功耗管理)。
周期扫描与睡眠恢复
matrix_scan()是每次扫描周期的主入口:
uart_read()读取一个字节;若无数据(返回 0)且距上次活动超过SLEEP_TIMEOUT(500000ms,见 config.h),则触发palm_reset()或handspring_reset()重新完成复位握手;- 只要有数据到达,就更新
last_activity并把disconnect_counter清零; - 丢弃意外的
0xFA/0xFD复位字节; - 根据
KEYUP(code)判断按下/抬起:按下则置位对应(row, col),抬起则清除;若连续收到相同的抬起码,则视为"全部按键已松开",清空状态并返回(readme 提到的手册要求——此时应释放所有遗留修饰键,但源码注释指出这可能干扰粘滞键功能,故当前实现是丢弃该字节)。
底层 UART 驱动
软件串行的载体是 QMK 平台的 UART 驱动。以 AVR 为例,platforms/avr/drivers/uart.c 提供了uart_init(baud)、uart_read()等接口:uart_init按(F_CPU / 4 / baud - 1) / 2配置波特率寄存器并开启收发中断;uart_read()在接收缓冲(RX_BUFFER_SIZE为 64 字节)为空时阻塞等待字符,这正是matrix_scan()中"无数据时 UART 驱动会让出执行"的行为基础。CHIBIOS 平台则有等价的 uart_serial.c / uart_sio.c 实现,体现了 QMK 跨平台抽象的能力。
配置项速查:如何适配你的键盘
所有关键配置集中在 config.h:
| 宏 | 默认值 | 说明 |
|---|---|---|
VCC_PIN | D1 | 键盘供电引脚(Pro Micro 2 号) |
RX_PIN | D0 | 软串行接收引脚(3 号) |
RTS_PIN | C6 | RTS 复位握手引脚(5 号) |
DCD_PIN | E6 | DCD 电平检测引脚(7 号) |
CY384 | 未定义 | 若沿用 cy384 的 Arduino 引脚布局则定义之,会启用GND_PIN D7与PULLDOWN_PIN B1的 GND/下拉引脚输出 |
HANDSPRING | 0 | 置 1 表示 Handspring 键盘,禁用 RTS/DCD 握手并切换为0xF9/0xFB握手 |
MAXDROP | 10 | 计划用于"每 X 次扫描检查键盘是否断开"的阈值(当前扫描逻辑中该计数分支被注释掉) |
SLEEP_TIMEOUT | 500000 | 无数据超过该毫秒数即触发复位握手(睡眠恢复) |
MATRIX_ROWS | 12 | 虚拟矩阵行数 |
MATRIX_COLS | 8 | 虚拟矩阵列数 |
另外,IS_COMMAND()被定义为"同时按下左右 Alt 或左右 Win/Command 或左右 Shift"触发 QMK 命令模式,与 console/command 功能配合使用。
默认键位:三层布局开箱即用
keymaps/default/keymap.c 提供了完整可用的三层键位:
- _QWERTY(默认层):标准全键盘布局,空格键位被拆分为两个 Space,
TG(_CDH)用于切换到 Colemak-DH 层; - _CDH(Colemak-DH 层):字母区重排为 Colemak-DH 指法,其余键位继承默认层;
- _FN(功能层):由
MO(_FN)临时按住触发,当前仅将 Enter 映射到空格位(KC_ENT),为快捷键预留了扩展空间。
由于矩阵是 12×8 的虚拟网格,keyboard.json 中的LAYOUT把每个键位映射到对应的[row, col],并定义了物理坐标(含 2.25u 空格、导航键簇等),QMK 的 info.json 数据驱动能力使得布局可视化与配置生成都能直接工作。
已知限制与后续路线
readme 与源码共同指出了当前实现的边界与 TODO,读者在自行移植或调试时值得留意:
- 测试覆盖有限:目前仅 Palm 3 型号键盘经过实测,其余型号(Palm 5、Journada、iPaq、Handspring、IRDA)的接线与握手需要自行验证;
- 软串行待重构:TODO 中计划把软串行实现迁移到新版 Helix 风格的代码,以便更容易切换引脚(当前引脚宏集中在 config.h,已为重构留出接口);
- 断开检测未启用:源码注释了"键盘按住删除键后断开"的检测逻辑——按计划应每
MAXDROP次扫描检查键盘是否还在,若不在则清空矩阵,但由于矩阵扫描远快于串行速率,该分支目前被注释,尚未实现; - 电源管理缺失:readme 直言"Power management is not implemented yet",当前只是通过频繁复位握手让键盘保持唤醒。
结语
converter/palm_usb/stowaway是 QMK"万物皆矩阵"思想的典型例证:一段 9600 波特、负逻辑的串行协议,通过自定义矩阵与位运算解码,被无缝纳入 QMK 的键位处理管线,让 20 年前的折叠键盘在现代电脑上重新工作。无论你是想复刻这套转换器,还是研究如何为其他串行/非标准协议外设编写自定义矩阵驱动,readme、config.h 与 matrix.c 三件套都是极佳的参考模板。
- 嵌入式
- 固件
- 驱动开发
- 硬件开发
【免费下载链接】qmk_firmware
Open-source keyboard firmware for Atmel AVR and Arm USB families
相关推荐
Apple M0110/M0110A 键盘转 USB 转换器:QMK 移植版协议实现与接线指南
Apple M0110/M0110A 键盘转 USB 转换器:QMK 移植版协议实现与接线指南 Apple M0110/M0110A 是上世纪 80 年代 Ma
嵌入式固件驱动开发硬件开发Chaos Genius实战案例:电商、音乐、出行等行业的异常监控解决方案
Chaos Genius实战案例:电商、音乐、出行等行业的异常监控解决方案 Chaos Genius作为一款开源的ML驱动的异常检测和根因分析引擎,为各行业提供
DragonflyDB协议转换:多协议适配器深度解析
DragonflyDB协议转换:多协议适配器深度解析 引言:现代数据存储的协议兼容挑战 在当今分布式系统架构中,数据存储服务面临着前所未有的协议兼容性挑战。传统
数据库KV存储缓存
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考