ZMK split parts talk to each other over a Bluetooth link that, by default, runs at a 7.5 ms connection interval, the minimum the Bluetooth spec allows. It turns out that for a split keyboard (preferably dongle) setup, we can drop that to 1 ms (or even 500 µs) using a feature that's already in the Zephyr Bluetooth controller. No code changes or external modules are needed.
I’d like to have this issue as a place to collect test results from anyone who tries this.
Couple of important caveats first:
- This mode is not Bluetooth specs-compliant and therefore it’s not possible to have a ZMK device connect directly to a host device on 1ms connection interval. It’s only viable between two Zephyr devices running the LL_SW Bluetooth controller, which is exactly the case in a ZMK split setup.
- 1ms connection interval would massively increase power consumption (7.5x more frequent wake-ups) on the central device, therefore this is only recommended for ZMK dongle setups, where the central is usually powered by USB.
- It’s currently not possible to actually achieve actual 1000Hz input over a ZMK split connection, the highest I’ve seen is 600-700 reports per second on a 1ms interval (1000Hz). I’m still not sure what the reason behind this is.
- There are still many cases where either the central or peripheral would crash during my limited testing.
- Performance is very likely worse than ESB if you’re already using that with a Zephyr module. The upside is that this is built-in to Zephyr with no code changes or external modules required.
- It’s possible on a dongle that the BLE advertising to host devices may interrupt the split connection, connecting over BLE to the same host device the dongle is wired via USB to might help, as an established connection takes up less radio time than advertising.
To test this:
- On stock ZMK
main, no BLE/ESB related external modules
- Add the following Kconfigs to your central:
CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CONN_INTERVAL_LOW_LATENCY=y
CONFIG_BT_CONN_PARAM_ANY=y
CONFIG_BT_CTLR_ASSERT_OVERHEAD_START=n
- Then for the peripheral(s):
CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CONN_INTERVAL_LOW_LATENCY=y
CONFIG_BT_CONN_PARAM_ANY=y
CONFIG_BT_CTLR_ASSERT_OVERHEAD_START=n
CONFIG_BT_PERIPHERAL_PREF_MIN_INT=1
CONFIG_BT_PERIPHERAL_PREF_MAX_INT=1
CONFIG_BT_PERIPHERAL_PREF_LATENCY=100
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=y
CONFIG_BT_CONN_PARAM_UPDATE_TIMEOUT=1000
CONFIG_BT_PERIPHERAL_PREF_MIN_INT and CONFIG_BT_PERIPHERAL_PREF_MAX_INT control the actual connection interval that will be used after a connection parameter update. 1 appears to work okay on a single peripheral setup (dongle + mouse), but if you have a two or more peripheral setup (like dongle + pair of splits with one trackball on either side), you may want to bump those two values up to 3, so 500Hz, to leave more radio time for two simultaneous connections.
- Interval value below 6 start at 500µs at 0, in 500µs increments; therefore 0 is 500µs, 1 is 1ms, 2 is 1.5ms, 3 is 2ms, and so on. 6 and above stay at the original 1.25ms increment.
What I’ve measured so far:
- On a test rig with two nRF54L15 DKs, Zephyr 4.1 (not running ZMK), round-trip latency:
- Stock 7.5 ms interval = ~14.9 ms
- 1 ms interval = ~2.8 ms
- Same test rig, one way latency by toggling GPIOs, measured on a Nordic PPK2:
- XIAO BLE ZMK dongle central and another XIAO BLE peripheral with a PAW3950 mouse sensor:
If you try this, please share:
- Board used
- Interval used and full Kconfig you added
- Subjective feel of pointing devices
- Especially if you have a high refresh rate monitor and/or commercial gaming mice with 1k+ polling to compare against. Or versus ESB split transport if you have experience with that.
- Measurement results
- Ideally with MouseTester as I’ve found it to be more indicative of exactly what’s going on vs web-based polling rate testers
- Stability: any disconnects, reconnect issues, crashes?
ZMK split parts talk to each other over a Bluetooth link that, by default, runs at a 7.5 ms connection interval, the minimum the Bluetooth spec allows. It turns out that for a split keyboard (preferably dongle) setup, we can drop that to 1 ms (or even 500 µs) using a feature that's already in the Zephyr Bluetooth controller. No code changes or external modules are needed.
I’d like to have this issue as a place to collect test results from anyone who tries this.
Couple of important caveats first:
To test this:
main, no BLE/ESB related external modulesCONFIG_BT_PERIPHERAL_PREF_MIN_INTandCONFIG_BT_PERIPHERAL_PREF_MAX_INTcontrol the actual connection interval that will be used after a connection parameter update. 1 appears to work okay on a single peripheral setup (dongle + mouse), but if you have a two or more peripheral setup (like dongle + pair of splits with one trackball on either side), you may want to bump those two values up to 3, so 500Hz, to leave more radio time for two simultaneous connections.What I’ve measured so far:
If you try this, please share: