Update firmware documentation and agent guidance

This commit is contained in:
2026-07-24 09:27:07 +02:00
parent ac3b2aa90f
commit 50dbf8fbee
26 changed files with 592 additions and 196 deletions
+2 -2
View File
@@ -45,7 +45,7 @@ struct RGB
enum class LEDAnim : uint8_t
{
STATIC = 0, // Sofort, keine Animation (Standardzustand)
BLINK, // Binäres An/Aus period_ms = Halbperiode (An-Zeit = Aus-Zeit)
BLINK, // Binäres An/Aus period_ms = Vollperiode (50 % an, 50 % aus)
PULSE, // Lineares Fade-In/Fade-Out in Schleife period_ms = Vollperiode
FADE_IN, // Einmalig: schwarz → volle Helligkeit über period_ms
FADE_OUT, // Einmalig: volle Helligkeit → schwarz über period_ms
@@ -76,7 +76,7 @@ public:
// ── LED-Animation ─────────────────────────────────────────────────────────
// set_anim(): für STATIC, BLINK, PULSE, FADE_IN, FADE_OUT, COLOR_CYCLE.
// period_ms: Halbperiode (BLINK), Vollperiode (PULSE/COLOR_CYCLE), Dauer (FADE_*).
// period_ms: Vollperiode (BLINK/PULSE/COLOR_CYCLE), Dauer (FADE_*).
// phase_offset_ms: Zeitversatz in die Vergangenheit verschiebt den Startpunkt der
// Animation. Nützlich für COLOR_CYCLE um LEDs versetzt starten zu
// lassen (Regenbogen-Wellen-Effekt über mehrere Buttons).
+4 -5
View File
@@ -7,11 +7,10 @@
// Leer: m_head == m_tail
// Voll: (m_tail + 1) % SIZE == m_head → ein Slot bleibt immer frei
//
// Interrupt-Sicherheit (Cortex-M0+):
// push() wird aus Encoder-ISR aufgerufen, pop() aus dem Loop.
// Auf M0+ sind uint8_t-Lese/Schreibzugriffe atomar (single-cycle LDR/STR)
// solange nur ein Producer (ISR) und ein Consumer (Loop) existieren, ist kein
// Mutex nötig. Bei mehreren Producern müsste noInterrupts() verwendet werden.
// Nebenläufigkeit:
// Encoder-ISRs und der Matrixcallback im Loop können beide push() aufrufen.
// Dieser gemischte Producerfall ist aktuell nicht durch eine Critical
// Section geschützt; siehe doc/09_known_limitations.md.
#include "CEventQueue.h"
+4 -5
View File
@@ -7,11 +7,10 @@
// Kapazität: QUEUE_SIZE - 1 = 16 Events (ein Slot bleibt leer damit
// is_full() und is_empty() ohne extra Zähler unterscheidbar sind).
//
// Thread-Sicherheit:
// push() wird aus ISR-Kontext aufgerufen (encoder_cb).
// pop() wird aus Loop-Kontext aufgerufen (processEvents).
// Auf Cortex-M0+ sind 8-Bit-Lese/Schreibzugriffe atomar → kein Mutex nötig
// solange nur ein Producer (ISR) und ein Consumer (Loop) existieren.
// Nebenläufigkeit:
// push() wird aus Encoder-ISRs und aus dem Matrixcallback im Loop aufgerufen.
// pop() läuft ebenfalls im Loop. Es gibt aktuell keine Critical Section für
// den Fall, dass ein Encoderinterrupt einen Matrix-Push unterbricht.
#pragma once
#include "SEvent.h"
+5 -4
View File
@@ -58,7 +58,8 @@ static void matrix_cb(uint8_t key, bool pressed)
}
// Wird von handle_encoder() aufgerufen läuft im ISR-Kontext (EIC-Interrupt).
// CEventQueue::push() ist interrupt-sicher (kein Heap, atomare Indizes auf M0+).
// Die Queue vermeidet den Heap, schützt den gemischten Matrix-/ISR-Producerfall
// aber aktuell nicht mit einer Critical Section.
static void encoder_cb(uint8_t enc, int8_t dir)
{
if (!s_queue) return;
@@ -168,7 +169,7 @@ void CMainController::work()
poll_vendor(); // 2. Eingehende Serial-Pakete (PC→Board) verarbeiten
processEvents(); // 3. Queue leeren, Aktionen ausführen
check_factory_reset();// 4. Long-Press-Kombination für Werksreset prüfen
updateLEDs(); // 4. Geänderte LED-Zustände in WS2812-Buffer schreiben + show()
updateLEDs(); // 5. Geänderte LED-Zustände in WS2812-Buffer schreiben + show()
}
// ─── Vendor-Kommunikation (PC → Board) ───────────────────────────────────────
@@ -525,7 +526,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: kann USB_EVT_KEY_UP senden.
// HOST_COMMAND: aktuell keine Ausgabe auf Release.
// MACRO/NONE: keine Aktion.
void CMainController::execute_action_down(SAction action, uint8_t key_id)
@@ -606,7 +607,7 @@ void CMainController::execute_action_up(SAction action, uint8_t key_id)
break;
case ActionType::HOST_COMMAND:
// Optional: USB_EVT_KEY_UP senden (aktuell nicht implementiert)
// USB_EVT_KEY_UP ist definiert, wird aktuell aber nicht gesendet.
break;
case ActionType::MACRO:
+5 -4
View File
@@ -6,15 +6,16 @@ enum class ActionType : uint8_t
NONE, // Keine Aktion
HID_KEY, // Standard-Keyboard-Keycode (direkt in Firmware gesendet)
HID_CONSUMER, // Consumer-Control-Keycode (Volume, Media, …)
HOST_COMMAND, // Command-ID → Windows-App führt aus (URL, Programm, …)
HOST_COMMAND, // Host-Event; data wird vom aktuellen Controller nicht übertragen
MACRO, // Makro-Slot (data = Slot-Index 031) → bis zu 8 HID-Keys sequenziell
PROFILE_SWITCH, // Profil wechseln (data = Profil-Index 02); speichert in NVM
PROFILE_SWITCH, // Profil 02 oder 0xFF = nächstes Profil; speichert in NVM
};
struct __attribute__((packed)) SAction
{
ActionType type;
uint16_t data; // Keycode (HID_KEY / HID_CONSUMER) oder Command-ID (HOST_COMMAND)
uint16_t data; // Typabhängige Nutzdaten; für HOST_COMMAND aktuell ungenutzt
// packed: 1B type + 2B data = 3B (kein Alignment-Padding)
// Muss packed sein damit sizeof(SDeviceConfig)==163 == C#-Serialisierung
// Muss packed sein, damit sizeof(SDeviceConfig)==740 und die
// hostseitige Serialisierung bytegenau übereinstimmen.
};
+1 -1
View File
@@ -5,7 +5,7 @@
//
// Slot-Zuweisung (vom Windows-App vergeben, Board speichert blind):
// Slot 019 : MX-Buttons (mx_idx)
// Slot 2031 : Encoder-Aktionen (enc*3 + act_idx, 0=SW/1=CW/2=CCW)
// Slot 2031 : Encoder-Aktionen (20 + enc*3 + act_idx, 0=SW/1=CW/2=CCW)
//
// Ein Step mit keycode=0 gilt als leer → Ausführung stoppt dort.
// Delay zwischen Steps: 20 ms (hardcoded).
+2 -2
View File
@@ -19,7 +19,7 @@
#define BTN_COL_COUNT 5
#define BTN_ROW_COUNT 5
// Column pins: driven OUTPUT LOW during scan, otherwise INPUT (high-Z or HIGH)
// Column pins: INPUT; external 10k pull-ups hold them HIGH.
static const uint8_t BTN_COLS[BTN_COL_COUNT] = {
PIN_COL0, // PB10 encoder SW column
PIN_COL1, // PA11 Cherry MX col 1 (leftmost)
@@ -28,7 +28,7 @@ static const uint8_t BTN_COLS[BTN_COL_COUNT] = {
PIN_COL4, // PA08 Cherry MX col 4 (rightmost)
};
// Row pins: INPUT_PULLUP, read LOW when button pressed
// Row pins: idle INPUT (high-Z), driven OUTPUT LOW one at a time during scan.
static const uint8_t BTN_ROWS[BTN_ROW_COUNT] = {
PIN_ROW0, // PB11
PIN_ROW1, // PA12
+1 -1
View File
@@ -3,7 +3,7 @@
#include <HID.h>
// ── HID Report Descriptor: Keyboard + Consumer Control ───────────────────────
// Vendor-Kommunikation läuft über CVendorHID (eigenes PluggableUSBModule).
// Host-Kommunikation außerhalb von HID läuft separat über USB CDC (SerialUSB).
static const uint8_t k_hid_descriptor[] = {
+4 -3
View File
@@ -6,7 +6,7 @@
// alle verfügbaren Bytes in einen internen Ring-Buffer und gibt ein vollständiges
// 8-Byte-Paket zurück sobald genug Bytes akkumuliert sind.
// Der Ring-Buffer (256 Bytes = 32 Pakete) verhindert Datenverlust wenn mehrere
// Pakete auf einmal ankommen (Config-Transfer: 30 Pakete).
// Pakete auf einmal ankommen. Größere Transfers werden fortlaufend geleert.
//
// Senden (Board → PC):
// Direkt via SerialUSB.write() kein eigener Puffer nötig, da der Arduino-CDC-
@@ -16,8 +16,9 @@
#include <Arduino.h>
// Ring-Buffer für eingehende Bytes CDC kann jederzeit Bytes liefern.
// Größe: 32 Pakete × 8 Bytes = 256 Bytes reicht für eine vollständige
// Config-Übertragung (30 Pakete) ohne Überlauf.
// Größe: 32 Pakete × 8 Bytes = 256 Bytes. Der Puffer ist nur ein
// Zwischenpuffer; ein vollständiger Config-Transfer umfasst 126 Pakete
// einschließlich BEGIN und COMMIT.
static uint8_t s_buf[SERIAL_PKT_SIZE * 32];
static uint16_t s_head = 0;
static uint16_t s_count = 0;
+5 -7
View File
@@ -8,10 +8,8 @@
// Byte-Layout aller Pakete:
// [0] Command/Event-ID
// [1] key_id (Button 024 oder Encoder 03)
// [2] r / Daten-Byte A
// [3] g / Daten-Byte B
// [4] b
// [5..7] reserviert (0x00)
// [2..7] kommandospezifische Daten
// LED-Kommandos nutzen [2..4] als RGB; Config-/Makro-DATA nutzt [2..7].
//
// Richtungen:
// PC → Board (Commands, 0x010x7F): poll_vendor() in CMainController
@@ -44,9 +42,9 @@
// ── Events: Board → PC ────────────────────────────────────────────────────────
#define USB_EVT_KEY_DOWN 0x81 // key_id → HOST_COMMAND-Button gedrückt
#define USB_EVT_KEY_UP 0x82 // key_id → HOST_COMMAND-Button losgelassen
#define USB_EVT_ENC_CW 0x83 // enc_id → Encoder Schritt CW (HOST_COMMAND)
#define USB_EVT_ENC_CCW 0x84 // enc_id → Encoder Schritt CCW (HOST_COMMAND)
#define USB_EVT_KEY_UP 0x82 // Reserviert; vom Controller aktuell nicht gesendet
#define USB_EVT_ENC_CW 0x83 // Reserviert; vom Controller aktuell nicht gesendet
#define USB_EVT_ENC_CCW 0x84 // Reserviert; vom Controller aktuell nicht gesendet
#define USB_EVT_PONG 0x85 // Antwort auf USB_CMD_PING
#define USB_EVT_CONFIG_ACK 0x90 // Config erfolgreich in NVM geschrieben
#define USB_EVT_CONFIG_NACK 0x91 // Config CRC/Magic ungültig nicht geschrieben