最近折腾 HHKB 的过程中,最难处理的不是配列,而是蓝牙偶发断连:有时突然不能输入,等一会又恢复;有时像按键没松开,一直重复。键盘离电脑很近,问题却并没有因此消失。

最初我主要盯着键盘固件。它刚迁移到 ZMK,睡眠、重连、USB 与蓝牙切换都有值得检查的地方。但真正把范围收窄的,是后来在电脑 Wi-Fi 固件上做的一组对照。

截至 2026 年 10 月 26 日,我已经换用包含新固件共存适配的 rtw89 驱动,再加载 0.27.129.4 Wi-Fi 固件,目前使用反馈正常。这篇记录的是怎样走到这个方案,以及为什么“现在稳定”还不等于可以忘掉后续内核升级。

先分清是哪一层出了问题

这套环境里,不止有一份固件。

部分 本次相关配置
键盘 HHKB-BLE v4,nRF52832 + CH554
键盘固件 ZMK,使用 Zephyr 软件 BLE 控制器
电脑系统 Ubuntu,内核 7.0.0-38-generic
电脑 Wi-Fi Realtek RTL8852CE,rtw89 驱动
电脑蓝牙 Realtek 蓝牙功能,btusb / btrtl 驱动
空中抓包 nRF52840 Dongle,Nordic Sniffer

键盘端的 BLE 协议栈、电脑端的蓝牙驱动、电脑端的 Wi-Fi 驱动和固件,是不同的对象。调整其中一项,不能说成把其他几项也升级了。

“按键没松开”同样不是一个足够具体的诊断。在这次长期排查中,USB 输出切换确实有过另一类松键问题,后来单独修复。它和本文讨论的自然蓝牙断连不能混为一个原因。

RSSI 没有直接回答这个问题

断连时,键盘和电脑可以相距不到半米。后来能正常接收到的包,键盘侧记录过约 -47 到 -54 dBm 的 RSSI。

这使“单纯距离过远”不再是最有力的解释,但也不能据此排除干扰。RSSI 只描述成功测到的信号;没收到的包,不能靠旁边几个强信号包来证明其存在。

另一个容易混淆的点是接收者:Dongle 测到的 RSSI 不等于电脑蓝牙控制器测到的 RSSI。监听器能收到键盘应答,也不代表电脑一定收到了那个应答。

所以后面不再只盯着信号强度,而是把三个位置的记录对齐:主机 HCI、空中包、键盘内部故障记录。主机告诉我连接怎样结束,空中包告诉我链路上看到了什么,键盘记录告诉我收到后做了什么。

一次信道图更新迟到了 315 ms

10 月 19 日的一次自然断连,三路记录终于对上了。

BLE 信道图决定连接后续使用哪些数据信道。更新报文里的 Instant 指定双方应该从哪个连接事件开始使用新图,而不是收到后随时切换。

当时抓到的关键数据是:

项目 观察结果
信道图要求生效的连接事件 31915
键盘实际收到时的连接事件 31936
迟到量 21 个事件,约 315 ms
Radio 收包到 ULL 处理 约 30.5 us
随后到故障记录 约 61 us
键盘原因码 0x28,Instant Passed
主机随后记录 0x08,Connection Timeout

这个控制包的 CRC 和 MIC 校验有效,键盘内部保存的事件号、Instant 和控制类型也与空中记录对应。

因此,信道图到达时已经过期,是这次断连的直接触发点。键盘收到包之后的处理是微秒级,证据不支持“ZMK 把已经收到的包积压了几百毫秒”这个解释。

但到这里还没有找到最上游原因。“信道图迟到”回答的是为什么断,不是为什么迟到。此前约 795 ms 内,键盘和监听器都没有记录到可识别的主机数据包。这让我把重点转向电脑端的收发机会、共存调度与共同干扰,而不是继续围着键盘任务队列打转。

真正缩小范围的是 Wi-Fi 固件对照

从无线联网切到有线、关闭 Wi-Fi 后,使用明显安静下来。这是线索,但网络切换带来的变量太多,不能就此下结论。

接下来做了一个范围更窄的新、旧、新对照,只切换 Wi-Fi 固件:

  • 内核、Wi-Fi 驱动、蓝牙固件、键盘固件保持不变。
  • 同一台电脑、同一个 AP、同一条已加密 BLE 连接。
  • Wi-Fi 保持 5 GHz 关联,不配置 IP;上网仍走有线。
  • 每段 120 秒,每段都重新绑定 Wi-Fi,包括第一段。

最后一点是为了减少“只要重新初始化一下就会暂时好转”对结果的干扰。

同一驱动下三段各 120 秒的 Wi-Fi 固件对照,空包交换停滞观测数为 68、0、103,不是断连次数

阶段 Wi-Fi 固件 全空包确认停滞观测数
新版 0.27.129.4 68
旧版 0.27.56.14 0
恢复新版 0.27.129.4 103

这里统计的是同一连接事件、同一信道、连续有效的“主机 → 键盘 → 主机”空包交换:键盘头部表示确认,但主机下一包的 SN/NESN 序号仍未推进。68、0、103 不是断连次数,也不是丢包率。 空包不携带 MIC,不能把它当作经过内容认证的数据包。

三段短测试本身都没有发生完整断连,而且打字负载并非机械复现。这个对照证明的是:在这台机器、这个驱动组合下,观测到的链路退化随 Wi-Fi 固件版本消失和重现。它比“重启后好了一阵”更有说服力,也还没有定位到 Realtek 固件里的某个具体函数。

还有一点值得保留:测试关联的是 5 GHz AP。不能把所有 Wi-Fi 与蓝牙共存异常都简单解释为双方同时挤在 2.4 GHz。

旧固件缓解有效,但不是终点

我先保留 0.27.56.14,恢复正常无线联网,再做了两小时真实使用观察。

结果是 0 次自然蓝牙断连,2591 次按下与 2591 次松开,457 次长按重复。主机、空中与键盘采集的覆盖检查都通过,结束时没有残留按键。

这给了一个有证据支持的临时方案:旧固件在当时的驱动组合下表现更好。但直接把结论写成“新版固件一定有 bug”,仍然少了一步。

固件与驱动需要配套。旧固件更稳定,也可能是因为旧驱动完整支持旧格式,却没有正确处理新固件的共存信息。

上游确实有新格式适配

继续核对上游后,找到了 RTL8852C 0.27.97.x 共存支持的提交。提交说明指出,新版固件使用新的 TLV 头格式,缺少适配会导致驱动遇到长度不匹配。上游提交 38c58d541cfe

本机当时使用的 Ubuntu 原版驱动缺少对应的新版共存表项;后来选用的社区驱动包含相应格式和配套处理。这里不是简单把固件版本数字改大,而是涉及双方交换的数据结构。

这条线索和前面的版本对照能够互相解释。不过,提交说明没有直接点名我抓到的 Instant Passed,社区驱动也包含不止一项更新。因此现在更准确的表述是:主机 Wi-Fi 驱动与固件的共存兼容性是最有力的原因方向,尚未把本机故障锁定到某一条补丁。

现在换的是驱动模块,不是整个内核

这次使用的是 morrownr/rtw89 社区驱动,固定在提交 3cdbb6b81e00b72cf27bbdd4d4f1b96f9c15be27。它不是 Ubuntu 官方驱动包,也不是另换一套蓝牙协议栈。

先在原内核上编译、签名和加载四个配套 Wi-Fi 模块,保留发行版原模块。新驱动搭配旧固件使用了几天,主观反馈比较稳定。之后才再次选择 0.27.129.4,由我手动重启,确认启动日志里的实际加载版本。

项目 当前处理
内核 保持 7.0.0-38-generic
Wi-Fi 驱动 使用新版 rtw89_8852ce_git 及配套模块
Wi-Fi 固件 实际加载 0.27.129.4 / 3f1a5302
Secure Boot 保持开启,使用已签名模块
主机蓝牙驱动与固件 这次主机升级没有更换
键盘固件 这次主机升级没有烧录

当前新驱动与新固件的使用反馈正常,因此先保留这套组合,不再为寻找一个更漂亮的结论反复中断日常使用。前几天的体验属于“新驱动 + 旧固件”,前面的两小时观察也属于旧组合,不能算作新版组合已经完成几天连续抓包验收。

内核升级是下一次需要检查的地方

驱动通常跟随内核交付,而 Wi-Fi 固件由独立的软件包更新。Ubuntu 稳定内核还要经过上游同步或补丁回移、审核和测试,两者不一定同时具备新的适配。Ubuntu 内核补丁生命周期

我的处理是按当前内核单独编译模块,没有配置 DKMS。启动另一个内核时,不会自动得到这套已验证驱动,而会使用那个内核自己的驱动。

这意味着:即使固件版本一直受保护,升级内核后仍可能重新组成“不匹配的驱动 + 固件”。 文件没有被 APT 覆盖,不代表组合始终正确。

目前保留已知可用的启动内核,只是为了有回退路径,不能为了蓝牙永久放弃内核安全更新。以后切换内核,要确认其驱动是否已带修复;已经包含且验证正常,就优先使用发行版驱动。还没包含,再考虑为目标内核重新编译和签名,而不是直接沿用旧模块。

遇到类似问题,可以先做这些只读检查:

bash
# 当前正在运行的内核,不是仅看已安装的软件包
uname -r
# 查看网卡以及实际绑定的内核驱动
lspci -nnk
# 查看本次启动实际加载的 Wi-Fi 固件版本
sudo journalctl -b -k --no-pager \
--grep='rtw89.*(loaded firmware|Firmware version)'
# 查看模块来自哪个目录,本例使用的是社区模块名
modinfo -n rtw89_8852ce_git
# 检查 Secure Boot 状态
mokutil --sb-state

发行版模块名称可能没有 _git 后缀,应以实际绑定结果为准。modinfo 查看的是磁盘模块信息,不能单独代替运行状态和加载日志。也不要直接照搬别人的固件改名、卸载驱动或关闭 Secure Boot 命令,尤其在电脑只能通过这张无线网卡联网时。

这次最值得留下的排查经验

首先,把触发点和上游原因分开。信道图迟到已经足以解释为什么断连,却不足以解释为什么电脑没能及时把它送到键盘。

其次,抓包工具也需要验收。采集器漏掉建连、失去跟踪或者少了一路记录时,“这半小时没看到异常”不一定是键盘正常。失败的测试应该保留为失败记录,不能拼成一份通过报告。

最后,测试的复杂程度要服务于问题。前面的三路证据和版本对照确实缩小了范围;但到了验证新驱动与新固件这一步,反复热重载和网络切换又引入了新的干扰。最终采用一次明确的文件选择、手动重启、核对实际版本和正常使用观察,更符合当时的目标。

现在暂时没有继续提交新的 Bug 报告,也没有因为这次问题直接换网卡。原始证据和草稿仍保留;如果新版组合再次复现,或者确认发行版仍交付有问题的组合,再向对应维护方提供可复现的信息。

这次留下的维护原则很简单:稳定的是经过验证的驱动与固件组合,不是某个脱离环境的版本号。