diff --git a/src/gfx_settings.c b/src/gfx_settings.c index f125ba3..d134f29 100644 --- a/src/gfx_settings.c +++ b/src/gfx_settings.c @@ -33,6 +33,7 @@ lv_obj_t *prev_screen = NULL; lv_obj_t *edge_dropdown; lv_obj_t *bias_dropdown; lv_obj_t *debounce_dropdown; +lv_obj_t *release_holdoff_dropdown; lv_obj_t *trigger_dropdown; lv_obj_t *mode_dropdown; lv_obj_t *trigger_output_dropdown; @@ -82,6 +83,10 @@ static void event_handler(lv_event_t* e) default: break; } xlat_gpio_irq_holdoff_us_set(val * 1000); + } else if (obj == release_holdoff_dropdown) { + static const uint32_t vals[] = {0, 20, 50, 100, 200}; + uint16_t sel = lv_dropdown_get_selected(obj); + if (sel < 5) xlat_release_holdoff_us_set(vals[sel] * 1000); } else if (obj == trigger_dropdown) { uint16_t sel = lv_dropdown_get_selected(obj); xlat_auto_trigger_level_set(sel); @@ -188,6 +193,17 @@ void gfx_settings_create_page(lv_obj_t *previous_screen) lv_obj_align_to(bias_dropdown, bias_label, LV_ALIGN_OUT_RIGHT_MID, 10, 0); lv_obj_add_event_cb(bias_dropdown, event_handler, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_t *release_holdoff_label = lv_label_create(tab_detection); + lv_label_set_text(release_holdoff_label, "Release Holdoff:"); + lv_obj_set_width(release_holdoff_label, LABEL_WIDTH); + lv_obj_align_to(release_holdoff_label, bias_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 30); + + release_holdoff_dropdown = lv_dropdown_create(tab_detection); + lv_dropdown_set_options(release_holdoff_dropdown, "Off\n20ms\n50ms\n100ms\n200ms"); + lv_obj_set_width(release_holdoff_dropdown, DROPDOWN_WIDTH); + lv_obj_align_to(release_holdoff_dropdown, release_holdoff_label, LV_ALIGN_OUT_RIGHT_MID, 10, 0); + lv_obj_add_event_cb(release_holdoff_dropdown, event_handler, LV_EVENT_VALUE_CHANGED, NULL); + // Trigger Tab Content // Add explanatory text for Trigger tab first lv_obj_t *trigger_info = lv_label_create(tab_trigger); @@ -266,6 +282,18 @@ void gfx_settings_create_page(lv_obj_t *previous_screen) } lv_dropdown_set_selected(debounce_dropdown, debounce_index); + // Set release holdoff + uint16_t release_index = 2; + switch (xlat_release_holdoff_us_get() / 1000) { + case 0: release_index = 0; break; + case 20: release_index = 1; break; + case 50: release_index = 2; break; + case 100: release_index = 3; break; + case 200: release_index = 4; break; + default: break; + } + lv_dropdown_set_selected(release_holdoff_dropdown, release_index); + // Set input bias uint32_t current_bias = hw_config_input_bias_get(); uint16_t bias_index = 0; diff --git a/src/hardware_config.c b/src/hardware_config.c index c749360..524d6ff 100644 --- a/src/hardware_config.c +++ b/src/hardware_config.c @@ -44,7 +44,7 @@ static void MX_USART1_UART_Init(void); static void MX_USART6_UART_Init(void); static bool rising_edge = false; -static input_bias_t input_bias = INPUT_BIAS_NOPULL; +static input_bias_t input_bias = INPUT_BIAS_PULLUP; /** * @brief The application entry point. @@ -97,6 +97,10 @@ void hw_exti_interrupts_enable(void) { /* EXTI interrupt init */ HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0); + /* Drop any edge latched while the IRQ was disabled (holdoff), otherwise the ISR + * fires immediately on enable with a bogus timestamp. */ + __HAL_GPIO_EXTI_CLEAR_IT(ARDUINO_D12_Pin); + HAL_NVIC_ClearPendingIRQ(EXTI15_10_IRQn); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); } diff --git a/src/xlat.c b/src/xlat.c index de94762..8aa107a 100644 --- a/src/xlat.c +++ b/src/xlat.c @@ -54,6 +54,9 @@ static volatile uint_fast8_t gpio_irq_consumer = 0; // The GPIO edge may still follow (e.g. analog/TMR switches where XLAT's input // threshold is crossed after the mouse already reported the click): that is a // negative latency. Written by the xlat task, consumed by the EXTI ISR. +// USB-side button state, used to reject GPIO chatter while the button is held or right after release +static volatile bool usb_button_held = false; +static volatile uint32_t usb_release_timestamp = 0; static volatile bool usb_event_pending = false; static volatile uint32_t usb_event_pending_timestamp = 0; @@ -334,6 +337,16 @@ void xlat_process_usb_hid_event(void) // FOR BUTTONS/CLICKS: if (xlat_mode_get() == XLAT_MODE_MOUSE_CLICK) { + // Track USB button state for GPIO chatter rejection (see HAL_GPIO_EXTI_Callback) + for (uint8_t i = (xlat_report_id_get() ? 1 : 0); i < hevt->report_size; i++) { + uint8_t changed = (hid_raw_data[i] ^ prev_report[i]) & xlat_button_mask_get()[i]; + if (changed & hid_raw_data[i]) { + usb_button_held = true; + } else if (changed) { + usb_release_timestamp = hevt->timestamp; + usb_button_held = false; + } + } // The correct location of button data is determined by parsing the HID descriptor // This information is available in the button_mask for (uint8_t i = (xlat_report_id_get() ? 1 : 0); i < hevt->report_size; i++) { @@ -408,6 +421,31 @@ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) if (cnt - last_btn_gpio_timestamp < xlat_gpio_irq_holdoff_us_get()) { return; } + // Reject contact chatter that cannot be a new press: edges while USB already reports the button + // held (and nothing is waiting to be paired), or within the release holdoff after a USB release. + // Only GPIO edges are filtered here; USB press reports are never dropped by this. + uint8_t ignore_reason = 0; + if (!usb_event_pending) { + if (usb_button_held) { + ignore_reason = 1; + } else if (cnt - usb_release_timestamp < xlat_release_holdoff_us_get()) { + ignore_reason = 2; + } + } + if (ignore_reason) { + // Chatter: mute the input briefly instead of taking an ISR storm. Re-arm when the release + // holdoff ends (reason 2) or after a short pause while the button is held (reason 1). + uint32_t mute_us = 5000; + if (ignore_reason == 2) { + mute_us = xlat_release_holdoff_us_get() - (cnt - usb_release_timestamp); + } + uint32_t mute_ms = (mute_us + 999) / 1000; + if (mute_ms == 0) mute_ms = 1; + hw_exti_interrupts_disable(); + xTimerChangePeriodFromISR(xlat_timer_handle, pdMS_TO_TICKS(mute_ms), NULL); + xTimerStartFromISR(xlat_timer_handle, NULL); + return; + } last_btn_gpio_timestamp = cnt; if (usb_event_pending) { @@ -533,6 +571,7 @@ void xlat_latency_reset(void) { taskENTER_CRITICAL(); usb_event_pending = false; + usb_button_held = false; gpio_irq_consumer = gpio_irq_producer; taskEXIT_CRITICAL(); for (int i = 0; i < LATENCY_TYPE_MAX; i++) { diff --git a/src/xlat.h b/src/xlat.h index fad8ae6..eb99139 100644 --- a/src/xlat.h +++ b/src/xlat.h @@ -70,6 +70,8 @@ void xlat_print_measurement(void); void xlat_gpio_irq_holdoff_us_set(uint32_t us); uint32_t xlat_gpio_irq_holdoff_us_get(void); +void xlat_release_holdoff_us_set(uint32_t us); +uint32_t xlat_release_holdoff_us_get(void); uint32_t xlat_counter_1mhz_get(void); diff --git a/src/xlat_config.c b/src/xlat_config.c index 9783dc0..1f868d2 100644 --- a/src/xlat_config.c +++ b/src/xlat_config.c @@ -44,6 +44,7 @@ bool keyboard_usage_page_found = false; // Therefore, take a large enough time window to debounce the GPIO interrupt. #define GPIO_IRQ_HOLDOFF_US (100 * 1000) // 100ms; static uint32_t gpio_irq_holdoff_us = GPIO_IRQ_HOLDOFF_US; +static uint32_t release_holdoff_us = 50000; // ignore GPIO edges this long after a USB button release // Mode configuration void xlat_mode_set(enum xlat_mode mode) @@ -103,6 +104,16 @@ void xlat_gpio_irq_holdoff_us_set(uint32_t us) gpio_irq_holdoff_us = us; } +void xlat_release_holdoff_us_set(uint32_t us) +{ + release_holdoff_us = us; +} + +uint32_t xlat_release_holdoff_us_get(void) +{ + return release_holdoff_us; +} + uint32_t xlat_gpio_irq_holdoff_us_get(void) {