From ced9be00df8e5b400a0ed19e7ce9b80d9c555ac8 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Thu, 28 May 2026 17:53:58 +0200 Subject: [PATCH 1/4] Add jitter test dump. --- device/src/shell/shell_commands.c | 12 +++++ right/src/CMakeLists.txt | 1 + right/src/hid/transport.cpp | 6 +++ right/src/jitter_test.c | 77 +++++++++++++++++++++++++++++++ right/src/jitter_test.h | 12 +++++ 5 files changed, 108 insertions(+) create mode 100644 right/src/jitter_test.c create mode 100644 right/src/jitter_test.h diff --git a/device/src/shell/shell_commands.c b/device/src/shell/shell_commands.c index 0a2d79270..e7dc72e32 100644 --- a/device/src/shell/shell_commands.c +++ b/device/src/shell/shell_commands.c @@ -25,6 +25,7 @@ #include "slave_drivers/kboot_driver.h" #include "i2c_addresses.h" #include "test_suite/test_suite.h" +#include "jitter_test.h" #include #include #include @@ -422,6 +423,16 @@ static int cmd_uhk_testSuite(const struct shell *shell, size_t argc, char *argv[ return 0; } +static int cmd_uhk_jitterTest(const struct shell *shell, size_t argc, char *argv[]) +{ + if (argc == 1) { + shell_fprintf(shell, SHELL_NORMAL, "%i\n", JitterTest_Active ? 1 : 0); + } else { + JitterTest_SetActive(argv[1][0] == '1'); + } + return 0; +} + void InitShellCommands(void) { @@ -472,6 +483,7 @@ void InitShellCommands(void) SHELL_CMD_ARG(shells, NULL, "list available shell backends", cmd_uhk_shells, 1, 0), SHELL_CMD_ARG(irqs, NULL, "list enabled IRQs and their priorities", cmd_uhk_irqs, 1, 0), SHELL_CMD_ARG(testSuite, NULL, "run test suite [module] [test]", cmd_uhk_testSuite, 1, 2), + SHELL_CMD_ARG(jitterTest, NULL, "get/set mouse jitter test mode", cmd_uhk_jitterTest, 1, 1), SHELL_SUBCMD_SET_END); SHELL_CMD_REGISTER(uhk, &uhk_cmds, "UHK commands", NULL); diff --git a/right/src/CMakeLists.txt b/right/src/CMakeLists.txt index a1692751f..0f7040344 100644 --- a/right/src/CMakeLists.txt +++ b/right/src/CMakeLists.txt @@ -28,6 +28,7 @@ target_sources(${PROJECT_NAME} PRIVATE host_connection.c i2c.c i2c_error_logger.c + jitter_test.c $<$:${CMAKE_CURRENT_SOURCE_DIR}/i2c_watchdog.c> $<$:${CMAKE_CURRENT_SOURCE_DIR}/init_clock.c> $<$:${CMAKE_CURRENT_SOURCE_DIR}/init_peripherals.c> diff --git a/right/src/hid/transport.cpp b/right/src/hid/transport.cpp index 02cbebaf4..aa17f02df 100644 --- a/right/src/hid/transport.cpp +++ b/right/src/hid/transport.cpp @@ -16,6 +16,7 @@ extern "C" { #include "trace.h" #include "usb_report_updater.h" #include "led_display.h" +#include "jitter_test.h" } #include "command_app.hpp" #include "controls_app.hpp" @@ -233,6 +234,11 @@ extern "C" errno_t Hid_SendMouseReport(const hid_mouse_report_t *report) break; } Trace_Printf("z22,%d", err); + if (err == 0) { + // Record once per logical report: a retrying caller keeps re-invoking + // this until a send is accepted, so only the accepted attempt counts. + JitterTest_RecordMouseX(report->x); + } return err; } diff --git a/right/src/jitter_test.c b/right/src/jitter_test.c new file mode 100644 index 000000000..56038fa3d --- /dev/null +++ b/right/src/jitter_test.c @@ -0,0 +1,77 @@ +#include "jitter_test.h" +#include "timer.h" +#ifdef __ZEPHYR__ +#include +#endif + +#define JITTER_TEST_SAMPLE_COUNT 50 +#define JITTER_TEST_WINDOW_MS 1000 + +bool JitterTest_Active = false; + +typedef struct { + uint32_t dt; + int16_t x; +} jitter_sample_t; + +static jitter_sample_t samples[JITTER_TEST_SAMPLE_COUNT]; +static uint32_t count; +static uint32_t lastSampleTime; +static uint32_t windowStart; +static uint32_t nextWindowStart; +static bool waiting; + +void JitterTest_SetActive(bool active) +{ + JitterTest_Active = active; + count = 0; + waiting = false; + nextWindowStart = 0; +} + +static void startWindow(uint32_t now) +{ + waiting = false; + count = 0; + windowStart = now; + lastSampleTime = now; +} + +static void dumpSamples(void) +{ +#ifdef __ZEPHYR__ + printk("-----\n"); + for (uint32_t i = 0; i < count; i++) { + printk("%u %d\n", samples[i].dt, samples[i].x); + } +#endif +} + +void JitterTest_RecordMouseX(int16_t x) +{ + if (!JitterTest_Active) { + return; + } + + uint32_t now = Timer_GetCurrentTime(); + + if (waiting) { + if (now < nextWindowStart) { + return; + } + startWindow(now); + } else if (count == 0) { + startWindow(now); + } + + samples[count].dt = now - lastSampleTime; + samples[count].x = x; + lastSampleTime = now; + count++; + + if (count >= JITTER_TEST_SAMPLE_COUNT) { + dumpSamples(); + waiting = true; + nextWindowStart = windowStart + JITTER_TEST_WINDOW_MS; + } +} diff --git a/right/src/jitter_test.h b/right/src/jitter_test.h new file mode 100644 index 000000000..fc1472a6f --- /dev/null +++ b/right/src/jitter_test.h @@ -0,0 +1,12 @@ +#ifndef __JITTER_TEST_H__ +#define __JITTER_TEST_H__ + +#include +#include + +extern bool JitterTest_Active; + +void JitterTest_SetActive(bool active); +void JitterTest_RecordMouseX(int16_t x); + +#endif From aaf2b349f0d63b6fbd623a61a79e34f4ff0eccde Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Fri, 29 May 2026 19:28:22 +0200 Subject: [PATCH 2/4] Make ble actually send in 7.5ms multiples. --- device/prj.conf.overlays/nrf_shared.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/device/prj.conf.overlays/nrf_shared.conf b/device/prj.conf.overlays/nrf_shared.conf index 057f996e4..e2129ed7d 100644 --- a/device/prj.conf.overlays/nrf_shared.conf +++ b/device/prj.conf.overlays/nrf_shared.conf @@ -29,6 +29,13 @@ CONFIG_BT_SMP=y CONFIG_BT_FILTER_ACCEPT_LIST=y +# Experiment: shrink the SoftDevice controller's per-event reservation and +# central ACL event spacing so a 7.5ms connection interval has scheduling +# headroom (default 7500us leaves none). Trades NUS bulk throughput for lower +# mouse latency/jitter. +CONFIG_BT_CTLR_SDC_MAX_CONN_EVENT_LEN_DEFAULT=2500 +CONFIG_BT_CTLR_SDC_CENTRAL_ACL_EVENT_SPACING_DEFAULT=2500 + # increase these to make multiple connections more reliable # this is a generic ai advice. CONFIG_BT_ATT_TX_COUNT=10 From d34bfe49c027e2942e897c421a52af7b3a6e7a29 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Fri, 29 May 2026 19:35:14 +0200 Subject: [PATCH 3/4] Advertise slow when there is no reason to advertise fast. --- device/src/bt_advertise.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/device/src/bt_advertise.c b/device/src/bt_advertise.c index 4ee0e6c32..263b141b3 100644 --- a/device/src/bt_advertise.c +++ b/device/src/bt_advertise.c @@ -60,6 +60,19 @@ static const struct bt_data sdHid[] = {SD_HID_DATA("UHK 80 BLE")}; #define BY_SIDE(LEFT, RIGHT) LEFT #endif +// Fast advertisement makes mouse key movement jittery as it makes us miss transports +static void applyAdvInterval(struct bt_le_adv_param* param) { + bool fast = BtConn_UnusedPeripheralConnectionCount() == ACTUAL_PERIPHERAL_CONNECTION_COUNT + || SelectedHostConnectionId != ConnectionId_Invalid; + if (fast) { + param->interval_min = BT_GAP_ADV_FAST_INT_MIN_1; + param->interval_max = BT_GAP_ADV_FAST_INT_MAX_1; + } else { + param->interval_min = BT_GAP_ADV_SLOW_INT_MIN; + param->interval_max = BT_GAP_ADV_SLOW_INT_MAX; + } +} + #define BT_LE_ADV_START(PARAM, AD, SD) bt_le_adv_start(PARAM, AD, ARRAY_SIZE(AD), SD, ARRAY_SIZE(SD)); static const char * advertisingString(uint8_t advType) { @@ -144,6 +157,7 @@ uint8_t BtAdvertise_Start(adv_config_t advConfig) LOG_DBG("Adv: advertise nus+hid."); /* our devices don't check service uuids, so hid advertisement effectively advertises nus too */ advParam = *BT_LE_ADV_CONN_ONE_TIME; + applyAdvInterval(&advParam); err = BT_LE_ADV_START(&advParam, adHid, sdHid); break; @@ -155,10 +169,12 @@ uint8_t BtAdvertise_Start(adv_config_t advConfig) advParam = *BT_LE_ADV_CONN_ONE_TIME; advParam.options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME | BT_LE_ADV_OPT_FILTER_CONN | BT_LE_ADV_OPT_USE_IDENTITY; + applyAdvInterval(&advParam); err = BT_LE_ADV_START(&advParam, BY_SIDE(adNusLeft, adNusRight), sdNus); } else { LOG_DBG("Adv: advertise nus, without allow list."); advParam = *BT_LE_ADV_CONN_ONE_TIME; + applyAdvInterval(&advParam); err = BT_LE_ADV_START(&advParam, BY_SIDE(adNusLeft, adNusRight), sdNus); } break; @@ -170,10 +186,12 @@ uint8_t BtAdvertise_Start(adv_config_t advConfig) advParam = *BT_LE_ADV_CONN_ONE_TIME; advParam.options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME | BT_LE_ADV_OPT_FILTER_CONN | BT_LE_ADV_OPT_USE_IDENTITY; + applyAdvInterval(&advParam); err = BT_LE_ADV_START(&advParam, BY_SIDE(adNusLeft, adNusRight), sdNus); } else { LOG_DBG("Adv: direct advertise nus, without allow list."); advParam = *BT_LE_ADV_CONN_ONE_TIME; + applyAdvInterval(&advParam); err = BT_LE_ADV_START(&advParam, BY_SIDE(adNusLeft, adNusRight), sdNus); } From 24f799d5f48627aaf35b6547f2c0d0dd9fdd4927 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Mon, 1 Jun 2026 13:37:49 +0200 Subject: [PATCH 4/4] Clean up the jitter PR. --- device/prj.conf.overlays/nrf_shared.conf | 4 +++- right/src/hid/transport.cpp | 2 -- right/src/jitter_test.c | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/device/prj.conf.overlays/nrf_shared.conf b/device/prj.conf.overlays/nrf_shared.conf index e2129ed7d..1ad6bee13 100644 --- a/device/prj.conf.overlays/nrf_shared.conf +++ b/device/prj.conf.overlays/nrf_shared.conf @@ -29,10 +29,12 @@ CONFIG_BT_SMP=y CONFIG_BT_FILTER_ACCEPT_LIST=y -# Experiment: shrink the SoftDevice controller's per-event reservation and +# Shrink the SoftDevice controller's per-event reservation and # central ACL event spacing so a 7.5ms connection interval has scheduling # headroom (default 7500us leaves none). Trades NUS bulk throughput for lower # mouse latency/jitter. +# +# Without this, dongle operates on 11ms instead of 7.5ms interval. CONFIG_BT_CTLR_SDC_MAX_CONN_EVENT_LEN_DEFAULT=2500 CONFIG_BT_CTLR_SDC_CENTRAL_ACL_EVENT_SPACING_DEFAULT=2500 diff --git a/right/src/hid/transport.cpp b/right/src/hid/transport.cpp index aa17f02df..b61a7335b 100644 --- a/right/src/hid/transport.cpp +++ b/right/src/hid/transport.cpp @@ -235,8 +235,6 @@ extern "C" errno_t Hid_SendMouseReport(const hid_mouse_report_t *report) } Trace_Printf("z22,%d", err); if (err == 0) { - // Record once per logical report: a retrying caller keeps re-invoking - // this until a send is accepted, so only the accepted attempt counts. JitterTest_RecordMouseX(report->x); } return err; diff --git a/right/src/jitter_test.c b/right/src/jitter_test.c index 56038fa3d..fa7cea91b 100644 --- a/right/src/jitter_test.c +++ b/right/src/jitter_test.c @@ -4,18 +4,18 @@ #include #endif -#define JITTER_TEST_SAMPLE_COUNT 50 +#define JITTER_TEST_SAMPLE_COUNT 20 #define JITTER_TEST_WINDOW_MS 1000 bool JitterTest_Active = false; typedef struct { - uint32_t dt; - int16_t x; + uint8_t dt; + uint8_t x; } jitter_sample_t; static jitter_sample_t samples[JITTER_TEST_SAMPLE_COUNT]; -static uint32_t count; +static uint16_t count; static uint32_t lastSampleTime; static uint32_t windowStart; static uint32_t nextWindowStart;