forked from jappel/VersaMCU
Harden firmware state and transfer handling
This commit is contained in:
parent
50dbf8fbee
commit
ce5db617a1
27 changed files with 558 additions and 231 deletions
|
|
@ -54,12 +54,17 @@ static void matrix_cb(uint8_t key, bool pressed)
|
|||
ev.type = pressed ? EventType::KEY_DOWN : EventType::KEY_UP;
|
||||
ev.key_id = key;
|
||||
ev.payload = 0;
|
||||
|
||||
// Encoder-ISRs benutzen dieselbe Queue. Den Loop-Producer kurz gegen einen
|
||||
// dazwischenlaufenden ISR-Push schützen; Encoder-Pushes selbst laufen
|
||||
// bereits mit maskierten gleichpriorisierten Interrupts.
|
||||
noInterrupts();
|
||||
s_queue->push(ev);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// Wird von handle_encoder() aufgerufen – läuft im ISR-Kontext (EIC-Interrupt).
|
||||
// Die Queue vermeidet den Heap, schützt den gemischten Matrix-/ISR-Producerfall
|
||||
// aber aktuell nicht mit einer Critical Section.
|
||||
// Der Matrix-Producer maskiert Interrupts während seines Queue-Pushs.
|
||||
static void encoder_cb(uint8_t enc, int8_t dir)
|
||||
{
|
||||
if (!s_queue) return;
|
||||
|
|
@ -75,8 +80,10 @@ static void encoder_cb(uint8_t enc, int8_t dir)
|
|||
CMainController::CMainController()
|
||||
: m_cfg_chunks_expected(0)
|
||||
, m_cfg_receiving(false)
|
||||
, m_cfg_transfer_valid(false)
|
||||
, m_macro_chunks_expected(0)
|
||||
, m_macro_receiving(false)
|
||||
, m_macro_transfer_valid(false)
|
||||
, m_factory_left_held(false)
|
||||
, m_factory_right_held(false)
|
||||
, m_factory_reset_armed(false)
|
||||
|
|
@ -84,10 +91,21 @@ CMainController::CMainController()
|
|||
, m_factory_hold_started_ms(0)
|
||||
{
|
||||
memset(m_cfg_buf, 0, sizeof(m_cfg_buf));
|
||||
memset(m_cfg_received, 0, sizeof(m_cfg_received));
|
||||
memset(m_macro_buf, 0, sizeof(m_macro_buf));
|
||||
memset(m_macro_received, 0, sizeof(m_macro_received));
|
||||
memset(&m_macros, 0, sizeof(m_macros));
|
||||
}
|
||||
|
||||
bool CMainController::all_chunks_received(
|
||||
const uint8_t* received, uint8_t count)
|
||||
{
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
if (received[i] == 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CMainController::setup()
|
||||
{
|
||||
macro_config_load(m_macros); // Makro-Tabelle aus NVM laden (oder leere Tabelle)
|
||||
|
|
@ -148,6 +166,11 @@ void CMainController::init_buttons()
|
|||
// Phase gleichmäßig verteilen → stehender Regenbogen dreht sich
|
||||
uint16_t phase = (uint16_t)((uint32_t)mx_idx * period / 20);
|
||||
m_buttons[key].set_anim(LEDAnim::COLOR_CYCLE, period, phase);
|
||||
} else if (anim == LEDAnim::COLOR_FADE) {
|
||||
// Config enthält nur eine Ziel-/Base-Farbe. COLOR_FADE wird beim
|
||||
// Laden daher eindeutig als einmaliges Schwarz→Base interpretiert.
|
||||
m_buttons[key].set_base(RGB(0, 0, 0));
|
||||
m_buttons[key].set_color_fade(base, period);
|
||||
} else {
|
||||
m_buttons[key].set_anim(anim, period);
|
||||
}
|
||||
|
|
@ -214,17 +237,30 @@ void CMainController::poll_vendor()
|
|||
// Neuen Empfang starten – bisherige Daten verwerfen
|
||||
m_cfg_chunks_expected = pkt.key_id();
|
||||
m_cfg_receiving = true;
|
||||
m_cfg_transfer_valid = (m_cfg_chunks_expected == CONFIG_CHUNKS);
|
||||
memset(m_cfg_buf, 0, sizeof(m_cfg_buf));
|
||||
memset(m_cfg_received, 0, sizeof(m_cfg_received));
|
||||
break;
|
||||
|
||||
case USB_CMD_CONFIG_DATA:
|
||||
if (m_cfg_receiving) {
|
||||
// 6 Nutzbytes ab Puffer-Offset (chunk_index × 6) eintragen
|
||||
uint16_t offset = (uint16_t)pkt.key_id() * 6;
|
||||
if (offset < sizeof(m_cfg_buf)) {
|
||||
uint8_t chunk = pkt.key_id();
|
||||
uint16_t offset = (uint16_t)chunk * SERIAL_PAYLOAD_BYTES;
|
||||
if (m_cfg_transfer_valid &&
|
||||
chunk < CONFIG_CHUNKS &&
|
||||
m_cfg_received[chunk] == 0 &&
|
||||
offset < sizeof(m_cfg_buf))
|
||||
{
|
||||
uint16_t remaining = (uint16_t)(sizeof(m_cfg_buf) - offset);
|
||||
uint8_t count = (uint8_t)(remaining > 6 ? 6 : remaining);
|
||||
uint8_t count = (uint8_t)(
|
||||
remaining > SERIAL_PAYLOAD_BYTES
|
||||
? SERIAL_PAYLOAD_BYTES
|
||||
: remaining);
|
||||
memcpy(m_cfg_buf + offset, &pkt.data[2], count);
|
||||
m_cfg_received[chunk] = 1;
|
||||
} else {
|
||||
m_cfg_transfer_valid = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -237,8 +273,8 @@ void CMainController::poll_vendor()
|
|||
nvm_config_load(cfg); // ungültige NVM → Defaults
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(&cfg);
|
||||
const uint16_t sz = sizeof(SDeviceConfig); // 740
|
||||
const uint8_t payload = 6;
|
||||
uint8_t chunks = (uint8_t)((sz + payload - 1) / payload); // 124
|
||||
const uint8_t payload = SERIAL_PAYLOAD_BYTES;
|
||||
const uint8_t chunks = CONFIG_CHUNKS;
|
||||
|
||||
usb_serial_send(USB_EVT_CONFIG_BEGIN, chunks);
|
||||
|
||||
|
|
@ -258,14 +294,17 @@ void CMainController::poll_vendor()
|
|||
}
|
||||
|
||||
case USB_CMD_CONFIG_COMMIT:
|
||||
if (m_cfg_receiving) {
|
||||
m_cfg_receiving = false;
|
||||
{
|
||||
bool complete =
|
||||
m_cfg_receiving &&
|
||||
m_cfg_transfer_valid &&
|
||||
all_chunks_received(m_cfg_received, CONFIG_CHUNKS);
|
||||
m_cfg_receiving = false;
|
||||
|
||||
if (complete) {
|
||||
SDeviceConfig cfg;
|
||||
memcpy(&cfg, m_cfg_buf, sizeof(cfg));
|
||||
if (cfg.magic == NVM_CONFIG_MAGIC &&
|
||||
cfg.version == NVM_CONFIG_VERSION &&
|
||||
cfg.crc == nvm_config_crc(cfg))
|
||||
{
|
||||
if (nvm_config_validate(cfg)) {
|
||||
if (nvm_config_save(cfg)) {
|
||||
init_buttons();
|
||||
usb_serial_send(USB_EVT_CONFIG_ACK, 0); // Erfolg melden
|
||||
|
|
@ -277,46 +316,76 @@ void CMainController::poll_vendor()
|
|||
{
|
||||
usb_serial_send(USB_EVT_CONFIG_NACK, 0); // CRC/Magic-Fehler
|
||||
}
|
||||
} else {
|
||||
usb_serial_send(USB_EVT_CONFIG_NACK, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ── Makro-Übertragung: BEGIN → n×DATA → COMMIT ──────────────────
|
||||
case USB_CMD_MACRO_BEGIN:
|
||||
m_macro_chunks_expected = pkt.key_id();
|
||||
m_macro_receiving = true;
|
||||
m_macro_transfer_valid = (m_macro_chunks_expected == MACRO_CHUNKS);
|
||||
memset(m_macro_buf, 0, sizeof(m_macro_buf));
|
||||
memset(m_macro_received, 0, sizeof(m_macro_received));
|
||||
break;
|
||||
|
||||
case USB_CMD_MACRO_DATA:
|
||||
if (m_macro_receiving) {
|
||||
uint16_t offset = (uint16_t)pkt.key_id() * 6;
|
||||
if (offset < sizeof(m_macro_buf)) {
|
||||
uint8_t chunk = pkt.key_id();
|
||||
uint16_t offset =
|
||||
(uint16_t)chunk * SERIAL_PAYLOAD_BYTES;
|
||||
if (m_macro_transfer_valid &&
|
||||
chunk < MACRO_CHUNKS &&
|
||||
m_macro_received[chunk] == 0 &&
|
||||
offset < sizeof(m_macro_buf))
|
||||
{
|
||||
uint16_t remaining = (uint16_t)(sizeof(m_macro_buf) - offset);
|
||||
uint8_t count = (uint8_t)(remaining > 6 ? 6 : remaining);
|
||||
uint8_t count = (uint8_t)(
|
||||
remaining > SERIAL_PAYLOAD_BYTES
|
||||
? SERIAL_PAYLOAD_BYTES
|
||||
: remaining);
|
||||
memcpy(m_macro_buf + offset, &pkt.data[2], count);
|
||||
m_macro_received[chunk] = 1;
|
||||
} else {
|
||||
m_macro_transfer_valid = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_CMD_MACRO_COMMIT:
|
||||
if (m_macro_receiving) {
|
||||
m_macro_receiving = false;
|
||||
memcpy(&m_macros, m_macro_buf, sizeof(m_macros));
|
||||
if (macro_config_save(m_macros)) {
|
||||
usb_serial_send(USB_EVT_MACRO_ACK, 0);
|
||||
} else {
|
||||
usb_serial_send(USB_EVT_MACRO_NACK, 0); // NVM-Timeout
|
||||
}
|
||||
{
|
||||
bool complete =
|
||||
m_macro_receiving &&
|
||||
m_macro_transfer_valid &&
|
||||
all_chunks_received(m_macro_received, MACRO_CHUNKS);
|
||||
m_macro_receiving = false;
|
||||
|
||||
SMacroTable incoming;
|
||||
if (complete) {
|
||||
memcpy(&incoming, m_macro_buf, sizeof(incoming));
|
||||
}
|
||||
|
||||
if (complete &&
|
||||
macro_config_validate(incoming) &&
|
||||
macro_config_save(incoming))
|
||||
{
|
||||
m_macros = incoming;
|
||||
usb_serial_send(USB_EVT_MACRO_ACK, 0);
|
||||
} else {
|
||||
usb_serial_send(USB_EVT_MACRO_NACK, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ── Makro-Dump anfordern ─────────────────────────────────────────
|
||||
case USB_CMD_MACRO_READ:
|
||||
{
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(&m_macros);
|
||||
const uint16_t sz = sizeof(SMacroTable); // 512
|
||||
const uint8_t payload = 6;
|
||||
uint8_t chunks = (uint8_t)((sz + payload - 1) / payload); // 86
|
||||
const uint8_t payload = SERIAL_PAYLOAD_BYTES;
|
||||
const uint8_t chunks = MACRO_CHUNKS;
|
||||
|
||||
usb_serial_send(USB_EVT_MACRO_BEGIN, chunks);
|
||||
|
||||
|
|
@ -348,7 +417,7 @@ void CMainController::poll_vendor()
|
|||
//
|
||||
// KEY_DOWN: execute_action_down() – HID-Taste wird gedrückt, bleibt aktiv bis KEY_UP.
|
||||
// KEY_UP: execute_action_up() – HID-Taste wird losgelassen.
|
||||
// Encoder CW/CCW: execute_action_down() + execute_action_up() für atomare TAP-Sequenz.
|
||||
// Encoder CW/CCW: Host-Event mit Richtung oder HID-Tap-Sequenz.
|
||||
|
||||
void CMainController::processEvents()
|
||||
{
|
||||
|
|
@ -378,17 +447,15 @@ void CMainController::processEvents()
|
|||
|
||||
case EventType::ENC_CW:
|
||||
if (ev.key_id < 4) {
|
||||
execute_action_down(m_enc_cw[ev.key_id], ev.key_id);
|
||||
delay(10);
|
||||
execute_action_up(m_enc_cw[ev.key_id], ev.key_id);
|
||||
execute_encoder_action(
|
||||
m_enc_cw[ev.key_id], ev.key_id, USB_EVT_ENC_CW);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType::ENC_CCW:
|
||||
if (ev.key_id < 4) {
|
||||
execute_action_down(m_enc_ccw[ev.key_id], ev.key_id);
|
||||
delay(10);
|
||||
execute_action_up(m_enc_ccw[ev.key_id], ev.key_id);
|
||||
execute_encoder_action(
|
||||
m_enc_ccw[ev.key_id], ev.key_id, USB_EVT_ENC_CCW);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -483,8 +550,8 @@ void CMainController::perform_factory_reset()
|
|||
// Laufzeit-Zustand immer an die Defaults angleichen – selbst wenn NVM gerade
|
||||
// nicht geschrieben werden konnte, sieht das Gerät sofort wieder "frisch" aus.
|
||||
m_macros = macros;
|
||||
usb_hid_release_key();
|
||||
usb_hid_release_consumer();
|
||||
usb_hid_release_all_keys();
|
||||
usb_hid_release_all_consumers();
|
||||
init_buttons();
|
||||
show_factory_reset_feedback();
|
||||
|
||||
|
|
@ -526,7 +593,7 @@ void CMainController::show_factory_reset_feedback()
|
|||
// execute_action_up(): Taste wird losgelassen (Hold-Ende).
|
||||
// HID_KEY: sendet Key-Up.
|
||||
// HID_CONSUMER: sendet Consumer-Up.
|
||||
// HOST_COMMAND: aktuell keine Ausgabe auf Release.
|
||||
// HOST_COMMAND: sendet KEY_UP mit Command-ID.
|
||||
// MACRO/NONE: keine Aktion.
|
||||
|
||||
void CMainController::execute_action_down(SAction action, uint8_t key_id)
|
||||
|
|
@ -551,8 +618,12 @@ void CMainController::execute_action_down(SAction action, uint8_t key_id)
|
|||
}
|
||||
|
||||
case ActionType::HOST_COMMAND:
|
||||
// Windows-App übernimmt Ausführung; KEY_DOWN-Event senden
|
||||
usb_serial_send(USB_EVT_KEY_DOWN, key_id);
|
||||
// Command-ID little-endian in Byte 2/3 übertragen.
|
||||
usb_serial_send(
|
||||
USB_EVT_KEY_DOWN,
|
||||
key_id,
|
||||
static_cast<uint8_t>(action.data & 0xFF),
|
||||
static_cast<uint8_t>(action.data >> 8));
|
||||
break;
|
||||
|
||||
case ActionType::MACRO:
|
||||
|
|
@ -566,7 +637,7 @@ void CMainController::execute_action_down(SAction action, uint8_t key_id)
|
|||
if (s.keycode == 0) break;
|
||||
usb_hid_send_key(s.keycode, s.modifier);
|
||||
delay(10);
|
||||
usb_hid_release_key();
|
||||
usb_hid_release_key(s.keycode, s.modifier);
|
||||
delay(20); // Kurze Pause zwischen Steps damit der Host mitkommt
|
||||
}
|
||||
break;
|
||||
|
|
@ -599,15 +670,23 @@ void CMainController::execute_action_up(SAction action, uint8_t key_id)
|
|||
switch (action.type) {
|
||||
|
||||
case ActionType::HID_KEY:
|
||||
usb_hid_release_key();
|
||||
{
|
||||
uint8_t keycode = static_cast<uint8_t>(action.data & 0xFF);
|
||||
uint8_t modifier = static_cast<uint8_t>(action.data >> 8);
|
||||
usb_hid_release_key(keycode, modifier);
|
||||
break;
|
||||
}
|
||||
|
||||
case ActionType::HID_CONSUMER:
|
||||
usb_hid_release_consumer();
|
||||
usb_hid_release_consumer(action.data);
|
||||
break;
|
||||
|
||||
case ActionType::HOST_COMMAND:
|
||||
// USB_EVT_KEY_UP ist definiert, wird aktuell aber nicht gesendet.
|
||||
usb_serial_send(
|
||||
USB_EVT_KEY_UP,
|
||||
key_id,
|
||||
static_cast<uint8_t>(action.data & 0xFF),
|
||||
static_cast<uint8_t>(action.data >> 8));
|
||||
break;
|
||||
|
||||
case ActionType::MACRO:
|
||||
|
|
@ -619,6 +698,23 @@ void CMainController::execute_action_up(SAction action, uint8_t key_id)
|
|||
}
|
||||
}
|
||||
|
||||
void CMainController::execute_encoder_action(
|
||||
SAction action, uint8_t enc_id, uint8_t host_event)
|
||||
{
|
||||
if (action.type == ActionType::HOST_COMMAND) {
|
||||
usb_serial_send(
|
||||
host_event,
|
||||
enc_id,
|
||||
static_cast<uint8_t>(action.data & 0xFF),
|
||||
static_cast<uint8_t>(action.data >> 8));
|
||||
return;
|
||||
}
|
||||
|
||||
execute_action_down(action, enc_id);
|
||||
delay(10);
|
||||
execute_action_up(action, enc_id);
|
||||
}
|
||||
|
||||
// ─── LED-Rendering ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Fragt alle CButton-Instanzen ab. Jede Instanz mit dirty-Flag schreibt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue