fix(core,led,usb): eliminate dual-core race conditions and correct RGB LED indicator behavior

- Unified key_engine_tick and key events on Core 0 with recursive mutex to eliminate stuck key race conditions

- Added HID hardware report mutex lock in usb_composite to ensure 100% reliable report delivery

- Fixed RGB LED logic: solid red on disconnect, solid green on standby, solid blue on voice record, yellow flash on HID press

- Removed UAC endpoint enumeration false blue-light trigger
This commit is contained in:
cuicuiV5 committed 2026-08-30 15:29:24 +08:00
1 parent 3889c9fc77
commit 3198c884a8
5 files changed
+78 -16

No files matched your search

+38 -1
View File
@@ -1,6 +1,25 @@
#include "key_state_machine.h"
#include "app_config.h"
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
static SemaphoreHandle_t s_key_engine_mutex = NULL;
static void key_engine_lock(void) {
if (!s_key_engine_mutex) {
s_key_engine_mutex = xSemaphoreCreateRecursiveMutex();
}
if (s_key_engine_mutex) {
xSemaphoreTakeRecursive(s_key_engine_mutex, portMAX_DELAY);
}
}
static void key_engine_unlock(void) {
if (s_key_engine_mutex) {
xSemaphoreGiveRecursive(s_key_engine_mutex);
}
}
static int find_binding_index(const key_mapper_engine_t *engine, uint8_t raw_key) {
if (!engine) return -1;
@@ -184,37 +203,47 @@ void key_engine_load_defaults(key_mapper_engine_t *engine) {
bool key_engine_set_binding(key_mapper_engine_t *engine, const key_binding_t *binding) {
if (!engine || !binding) return false;
key_engine_lock();
int idx = find_binding_index(engine, binding->source_vk);
if (idx >= 0) {
engine->bindings[idx] = *binding;
key_engine_unlock();
return true;
}
if (engine->binding_count < MAX_KEY_BINDINGS) {
engine->bindings[engine->binding_count++] = *binding;
key_engine_unlock();
return true;
}
key_engine_unlock();
return false;
}
bool key_engine_get_binding(const key_mapper_engine_t *engine, uint8_t source_vk, key_binding_t *out_binding) {
if (!engine || !out_binding) return false;
key_engine_lock();
int idx = find_binding_index(engine, source_vk);
if (idx >= 0) {
*out_binding = engine->bindings[idx];
key_engine_unlock();
return true;
}
key_engine_unlock();
return false;
}
void key_engine_init(key_mapper_engine_t *engine, key_output_callback_t cb) {
if (!engine) return;
key_engine_lock();
memset(engine, 0, sizeof(key_mapper_engine_t));
engine->output_cb = cb;
key_engine_load_defaults(engine);
key_engine_unlock();
}
void key_engine_feed_key(key_mapper_engine_t *engine, uint8_t raw_key_code, bool is_pressed, uint32_t now_ms) {
if (!engine) return;
key_engine_lock();
// Immediately record telemetry on EVERY single button state change
engine->last_telemetry.source_vk = raw_key_code;
@@ -222,7 +251,10 @@ void key_engine_feed_key(key_mapper_engine_t *engine, uint8_t raw_key_code, bool
engine->last_telemetry.timestamp = now_ms;
int idx = find_binding_index(engine, raw_key_code);
if (idx < 0) return;
if (idx < 0) {
key_engine_unlock();
return;
}
key_binding_t *b = &engine->bindings[idx];
key_slot_state_t *s = &engine->states[idx];
@@ -279,10 +311,12 @@ void key_engine_feed_key(key_mapper_engine_t *engine, uint8_t raw_key_code, bool
}
}
}
key_engine_unlock();
}
void key_engine_tick(key_mapper_engine_t *engine, uint32_t now_ms) {
if (!engine) return;
key_engine_lock();
for (size_t i = 0; i < engine->binding_count; i++) {
key_binding_t *b = &engine->bindings[i];
@@ -315,10 +349,12 @@ void key_engine_tick(key_mapper_engine_t *engine, uint32_t now_ms) {
}
}
}
key_engine_unlock();
}
void key_engine_release_all(key_mapper_engine_t *engine, uint32_t now_ms) {
if (!engine) return;
key_engine_lock();
for (size_t i = 0; i < engine->binding_count; i++) {
key_binding_t *b = &engine->bindings[i];
@@ -330,4 +366,5 @@ void key_engine_release_all(key_mapper_engine_t *engine, uint32_t now_ms) {
s->waiting_double = false;
s->press_count = 0;
}
key_engine_unlock();
}
+2
View File
@@ -59,7 +59,9 @@ void led_indicator_init(void) {
void led_indicator_set(led_state_t state) {
if (state == LED_STATE_HID_KEY_PRESS || state == LED_STATE_MIC_KEY_PRESS) return; // Use trigger for flashes
s_is_flashing = false;
s_current_base_state = state;
update_hardware_led(state);
}
void led_indicator_trigger_key(bool is_voice_key) {
+4 -6
View File
@@ -21,6 +21,7 @@ static void ble_task_core0(void* param) {
while (true) {
ble_remote_task();
key_engine_tick(&g_key_engine, millis());
vTaskDelay(pdMS_TO_TICKS(5));
}
}
@@ -80,16 +81,13 @@ void loop() {
// 1. Service TinyUSB & Audio push
usb_composite_task();
// 2. Service Key State Machine timers (long press, double click, repeat)
key_engine_tick(&g_key_engine, now);
// 3. Service Wi-Fi & DNS tasks
// 2. Service Wi-Fi & DNS tasks
wifi_manager_task();
// 4. Service HTTP Web Server
// 3. Service HTTP Web Server
web_server_task();
// 5. Service Serial / WebSerial CLI commands
// 4. Service Serial / WebSerial CLI commands
cli_manager_task();
// No delay here — USB audio task handles its own timing via vTaskDelayUntil
-6
View File
@@ -150,12 +150,6 @@ static bool uac_driver_control_xfer_cb(uint8_t rhport, uint8_t stage,
s_uac_alt = alt;
s_uac_streaming = (alt == 1);
if (s_uac_streaming) {
led_indicator_set(LED_STATE_MIC_STREAMING);
} else {
led_indicator_set(LED_STATE_CONNECTED);
}
// Force release endpoint to clear stuck busy flags from aborted transfers
usbd_edpt_close(rhport, (uint8_t)(s_uac_ep_in | 0x80));
+34 -3
View File
@@ -45,18 +45,41 @@ void usb_composite_task(void) {
uac_microphone_task();
}
static SemaphoreHandle_t s_hid_mutex = NULL;
static void hid_lock(void) {
if (!s_hid_mutex) {
s_hid_mutex = xSemaphoreCreateMutex();
}
if (s_hid_mutex) {
xSemaphoreTake(s_hid_mutex, portMAX_DELAY);
}
}
static void hid_unlock(void) {
if (s_hid_mutex) {
xSemaphoreGive(s_hid_mutex);
}
}
bool usb_hid_keyboard_press(uint8_t modifier, uint8_t keycode) {
if (!s_usb_ready) return false;
hid_lock();
KeyReport report = {0};
report.modifiers = modifier;
report.keys[0] = keycode;
s_keyboard.sendReport(&report);
hid_unlock();
return true;
}
bool usb_hid_keyboard_release(void) {
if (!s_usb_ready) return false;
hid_lock();
KeyReport report = {0}; // All zeroes
s_keyboard.sendReport(&report);
s_keyboard.releaseAll();
hid_unlock();
return true;
}
@@ -70,21 +93,27 @@ bool usb_hid_keyboard_tap(uint8_t modifier, uint8_t keycode) {
bool usb_hid_consumer_press(uint16_t usage_code) {
if (!s_usb_ready) return false;
hid_lock();
s_consumer.press(usage_code);
hid_unlock();
return true;
}
bool usb_hid_consumer_release(void) {
if (!s_usb_ready) return false;
hid_lock();
s_consumer.release();
hid_unlock();
return true;
}
bool usb_hid_consumer_tap(uint16_t usage_code) {
if (!s_usb_ready) return false;
hid_lock();
s_consumer.press(usage_code);
delay(15);
s_consumer.release();
hid_unlock();
return true;
}
@@ -94,10 +123,12 @@ void usb_hid_dispatch_action(const key_action_t *action) {
app_log("USB_HID", "Emit Action: type=%d, mod=0x%02X, key=0x%02X, cons=0x%04X",
action->type, action->modifier, action->key_code, action->consumer_code);
if (action->type == ACTION_VOICE_HOLD || action->type == ACTION_VOICE_RELEASE) {
led_indicator_trigger_key(true); // Voice key
if (action->type == ACTION_VOICE_HOLD) {
led_indicator_set(LED_STATE_MIC_STREAMING); // Solid Blue while voice recording
} else if (action->type == ACTION_VOICE_RELEASE) {
led_indicator_set(LED_STATE_CONNECTED); // Solid Green when voice recording ends
} else {
led_indicator_trigger_key(false); // Other key
led_indicator_trigger_key(false); // Yellow flash on ordinary HID key actions
}
switch (action->type) {