diff --git a/device/prj.conf.overlays/nrf_shared.conf b/device/prj.conf.overlays/nrf_shared.conf index 057f996e4..1ad6bee13 100644 --- a/device/prj.conf.overlays/nrf_shared.conf +++ b/device/prj.conf.overlays/nrf_shared.conf @@ -29,6 +29,15 @@ CONFIG_BT_SMP=y CONFIG_BT_FILTER_ACCEPT_LIST=y +# 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 + # increase these to make multiple connections more reliable # this is a generic ai advice. CONFIG_BT_ATT_TX_COUNT=10 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); } 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..b61a7335b 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,9 @@ extern "C" errno_t Hid_SendMouseReport(const hid_mouse_report_t *report) break; } Trace_Printf("z22,%d", err); + if (err == 0) { + 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..fa7cea91b --- /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 20 +#define JITTER_TEST_WINDOW_MS 1000 + +bool JitterTest_Active = false; + +typedef struct { + uint8_t dt; + uint8_t x; +} jitter_sample_t; + +static jitter_sample_t samples[JITTER_TEST_SAMPLE_COUNT]; +static uint16_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