单手键盘的灵敏度设置,怎样才算记得住?
把键盘、鼠标和滚轮放到一只手能够到的地方,解决的是“够不够方便”。下一步往往更细:轨迹球刚调到顺手的速度,重新插上 USB,它还能记住吗?一直按“减速”,到了最慢一档,会不会突然朝相反方向变化?
这类问题不需要先换传感器。顺着“按键—运行参数—非易失配置”走一遍,就能找到值得改的地方。下面以一把 STM32G431 单手键盘的左手大尺寸版本为例,完成一个有测试支撑的配置管理改进。

图1:现有项目的左手大键盘。照片用于说明键盘、轨迹球和滚轮的位置,不代表已经刷入本文改版。
先认清这几块板,别急着改数字
这把键盘将弧形按键区、轨迹球和滚轮收在同一个底座里。对于主要依靠一只手操作电脑的人,少一次在键盘与鼠标之间来回移动,就有实际意义。具体键位和曲面是否适合某位使用者,仍要通过试用调整。

图2:左手大键盘的结构示意。键盘主控与下面的输入小板通过排线连接,软件调节灵敏度不需要改变这套结构。
本文选用 ML 版本,主控为 STM32G431CBU6,轨迹球传感器为 ADNS-9800,固件使用 QMK。滚轮有独立编码器信号,不能把滚轮速度与轨迹球分辨率当成同一个参数。

图3:在 EDA 编辑器中重新导出的主板 FPC3 局部。1脚供电,2、3脚接地,其余为 SPI 与运动提示信号。
从主控图与固件配置交叉核对,SCK、MISO、MOSI 分别是 PA5、PA6、PA7;片选 CS 用 PA4,MOTION 用 PA3。它们对应 FPC3 的6、5、4、7、8脚。滚轮编码器使用 PA9、PA10。这里的 PA5 是 MCU 引脚名称,不是排线第5脚。

图4:轨迹球板的接口局部。MOSI、SCK、CS 分别经过 R10、R11、R12,均为100Ω;MISO 和 MOTION 没有照搬这组串联电阻。
接口图能解释数据往哪走,却不能证明实际排线方向、供电噪声或 SPI 波形已经合格。传感器板标注3.3V,附带手册给出的 VDD3 工作范围为2.7~3.3V,VDDIO 为1.65~3.3V;标称值处在上边界,实际电压还要考虑稳压误差和噪声。本文没有完成整板供电、时序或光学验证,因此不提供照图接线后直接上电的硬件验收结论。

图5:ML 主板顶层铜箔、顶层丝印与板框,来自实际 EDA 导出。

图6:同版本底层铜箔与丝印,仍为从顶面观察的方向,未镜像。主板为四层结构,这两幅外层图不展示完整内层回流路径,也不能代替 DRC 或整板审查。
“减一档”为什么需要一个前提
原固件给轨迹球保留了一个16位变量,启动时从32位用户配置读取数值,再直接写给传感器接口。正常情况下,数值在1000~8000之间,每次增减1000。
问题出在“正常情况下”没有被代码检查。若存储中读到0,减1000后再存回无符号16位变量,结果是64536。后面的“是否小于1000”判断已经拦不住它。
这不是靠推测写出来的现象。把固定版本的原始 C 文件编译进测试程序,以替身提供配置读写和传感器接口,实际复现了 0 → 减一档 → 请求64536。底层驱动随后还会钳位,所以不能宣称传感器真的以64536 CPI运行;确定发生的是,上层“减速”向驱动提交了一个很大的值。
也要留一个对照:合法值1000减1000得到0,原代码会将它夹回1000。这条正常路径没有上述问题。缺陷的前提是异常配置进入运行变量,不能写成“每次按到最低档都会溢出”。
新版先检查完整32位值,再缩窄类型:
static bool valid_cpi(uint32_t value) {
return value >= CPI_MIN && value <= CPI_MAX && value % CPI_STEP == 0;
}
其中最小值1000、最大值8000、步长1000;不合格时使用默认3000。为什么要先检查32位?因为 0x00010BB8 的低16位正好是3000,先截断再检查会误把异常高位忽略掉。保留原存储格式,可以接受旧固件保存的合法档位;不在这些档位上的值则明确回退。
运行参数建立有效范围后,减档在运算前判断下界:
void pointing_adns9800_cpi_down(void) {
uint16_t current = user_config.TrackballDPI;
apply_cpi(current <= CPI_MIN ? CPI_MIN : current - CPI_STEP);
}
apply_cpi() 只处理真正改变的值。已经是最低档,再按一次减档,不重复调用传感器设置,也不重新启动保存计时。
手感立即变化,保存可以稍等
原版每次按增减键都调用配置更新接口,哪怕已经卡在端点。改进时容易走向另一个极端:把整个操作推迟,用户按完键却感觉没有响应。
这里把两件事分开。运行参数立即更新,保存等待最后一次变化后的750毫秒。 主循环持续工作,不用 wait_ms(750) 卡住键盘扫描。

图7:实现后的流程与测试时间线。100、200、300毫秒各调一档,最后在1050毫秒调用一次保存接口。
保存钩子的核心如下,正文与附件采用同一份代码:
void housekeeping_task_kb(void) {
if (!sensor_ready || saved_raw == user_config.TrackballDPI) return;
if (timer_elapsed32(changed_at) < SAVE_QUIET_MS) return;
eeconfig_update_user(user_config.TrackballDPI);
saved_raw = user_config.TrackballDPI;
}
在750毫秒内又调了一档,就从最后一次实际变化重新计时;调来调去又回到已保存值,则不必保存。启动时遇到非法值,也通过同一条延迟路径把默认值写回。
这不是“每按一次就擦一次 Flash”的寿命故事。QMK 这里最终调用的是具有 update 语义的存储接口,底层可能跳过相同数据,也可能使用模拟 EEPROM。测试统计的是接口调用,不能直接换算成物理擦除次数或寿命倍数。延迟保存也没有消除底层一次写入可能产生的阻塞。
它的代价很明确:调完立刻拔线,可能还没保存,下次启动就回到旧值。对于手感参数,短暂等待通常可接受;如果是必须立即记账的交易或安全参数,就需要另一种持久化设计。这版没有 CRC、双槽提交和掉电原子保证,恰好落在合法档位上的数据损坏也无法识别。
初始化顺序,比多加几次延时更有用
固定版本 QMK 的调用顺序是:先初始化传感器驱动,再执行键盘级钩子,最后执行用户级钩子。原模块又在键盘钩子里调用了一次 ADNS 初始化。
本次替换文件移除了这次重复初始化,直接加载有效配置并设置参数;也没有在键盘钩子里再手动调用用户钩子,以免执行两遍。保存所用的 housekeeping 键盘钩子与用户钩子同样由该版本框架分别调用。移植到别的 QMK 版本时,应重新核对这个顺序。
恢复默认则走另一条路径:立即保存3000;如果传感器还没初始化,只更新配置,不访问它。默认键图里的 eeconfig_init_user() 会调用这个接口,因此“启动前恢复默认”也必须进入测试。
还有一个不能靠猜补齐的细节:所选驱动把参数按200的步距映射到寄存器,而项目附带的 ADNS-9800 手册第29页写的是约50 CPI一档。两者存在差异,本次没有确定是否来自资料、SROM 或驱动版本。本文保留旧驱动约定,1000、3000等数字指固件的名义 CPI 参数,不宣称已经校准成真实移动距离,也不据此贸然修改寄存器算法。

图8:现有轨迹球装配示意。球、支撑点、透镜和传感器的关系同样影响使用效果;软件参数测试无法替代这部分检查。
先在电脑上,把不顺手的情况跑出来
附件是完整的最小测试工程,包含原版复现、实际替换模块、QMK 接口替身和构建脚本。验证直接调用这份替换 C 文件,没有另写一个相似算法来“证明自己”。

图9:本次运行结果的排版记录,非实物测量截图。既记录通过项,也记录没有覆盖的整机验证。
测试遍历了全部65,536个低16位值,并加入非零高位配置;检查750毫秒边界、32位计时回绕、连续调节、端点连按、回到原值取消保存,以及复位和保存前模拟重启。共262,161条断言通过,其中大部分来自配置值遍历;数量本身不是整机可靠性的证明。
用 Python 3 和 Zig 0.16.0,在解压目录运行:
python build.py --zig C:/tools/zig/zig.exe
将路径换成实际 Zig 位置即可。脚本先执行两个主机测试,再把替换模块编译为 Cortex-M4 对象文件。本次对象编译通过,但使用了测试接口头文件,没有完成完整 QMK 固件链接,也没有连接实体键盘。附件提供固定提交、覆盖路径及后续 QMK 构建说明,不提供可直接刷机的成品固件。
准备上板验证时,先保留旧固件和键位配置,再在固定版本的完整 QMK 环境构建 ML 默认键图。原键图按住 Fn 后,在 [、] 对应位置调节;若使用过 VIA 改键,应以实际映射为准。除了调节和重启,还应回归按键、滚轮、USB枚举和灯效,观察保存时刻是否影响扫描。
让设备“记住设置”,不是加一句写存储就结束了。先保证读进来的值可信,再明确何时生效、何时保存、断电会怎样,用户才不用每天重新跟设备磨合。
创作来源与许可:项目为 HTXStudio 团队《HTXStudio单手键盘》,完整地址:https://oshwhub.com/htx-studio/One-Handed_Keyboard 。硬件与资料仓库:https://github.com/htx-studio/One-Handed-Keyboard ,核对提交 6211e1fc,MIT License。图1、2、8使用其照片/装配示意,做留白裁剪、保真缩放与轻微锐化;图3~6于2026-09-23在已登录的嘉立创 EDA 对应工程重新导出,母图长边3528~8212像素,PCB为4096像素,再做局部裁剪或亮度调整。原硬件未修改,引用资料的 MIT 版权声明随附件保留。
固件参考:https://github.com/htx-studio/qmk_firmware ,固定提交 4ef64784e4d4121a9711880eb92f6c109a6e39ca,涉及 one_handed_keyboard.c、默认键图、QMK初始化/配置接口与ADNS驱动。固件、本文替换模块与测试按 GPL-2.0-or-later 分享,保留第三方版权。参考手册为资料仓库 Docs/ADNS-9800.PDF,重点核对第10、29页;未在附件重分发该手册。
本文独立组织分析并完成配置校验、饱和步进和延迟保存改动,图7、9由实际代码与验证结果绘制;新增文字及这两幅图按 CC BY 4.0 分享。验证范围为主机测试与模块对象编译,不包含完整固件链接、物理存储寿命、分辨率校准或实体硬件测试。
完整源码、许可与验证报告见本文附件 CPI-Guard-20260923.zip。
技术交流,欢迎关注微信公众号:美男子玩编程。



