Added Macro functionality, updated readme
This commit is contained in:
+88
-12
@@ -31,7 +31,7 @@
|
||||
#include "hal/usb_serial.h"
|
||||
#include "config/pins.h"
|
||||
#include "config/nvm_config.h"
|
||||
#include <string.h>
|
||||
#include "config/macro_config.h"
|
||||
|
||||
// ─── Static Bridge: HAL-Callbacks → EventQueue ───────────────────────────────
|
||||
//
|
||||
@@ -70,13 +70,18 @@ static void encoder_cb(uint8_t enc, int8_t dir)
|
||||
CMainController::CMainController()
|
||||
: m_cfg_chunks_expected(0)
|
||||
, m_cfg_receiving(false)
|
||||
, m_macro_chunks_expected(0)
|
||||
, m_macro_receiving(false)
|
||||
{
|
||||
memset(m_cfg_buf, 0, sizeof(m_cfg_buf));
|
||||
memset(m_cfg_buf, 0, sizeof(m_cfg_buf));
|
||||
memset(m_macro_buf, 0, sizeof(m_macro_buf));
|
||||
memset(&m_macros, 0, sizeof(m_macros));
|
||||
}
|
||||
|
||||
void CMainController::setup()
|
||||
{
|
||||
init_buttons(); // Buttons aus NVM laden (oder Defaults)
|
||||
macro_config_load(m_macros); // Makro-Tabelle aus NVM laden (oder leere Tabelle)
|
||||
init_buttons(); // Buttons aus NVM laden (oder Defaults)
|
||||
s_queue = &m_queue; // Queue-Pointer setzen bevor Callbacks registriert werden
|
||||
usb_hid_init(); // HID-Descriptor registriert sich via globalem Konstruktor,
|
||||
// usb_hid_init() ist hier ein No-Op aber verdeutlicht die Abhängigkeit
|
||||
@@ -102,13 +107,8 @@ void CMainController::init_buttons()
|
||||
}
|
||||
|
||||
// MX-Buttons: LED-Index aus serpentiner Verdrahtung berechnen,
|
||||
// Aktion + Base-Farbe aus NVM.
|
||||
// Aktion + Base-Farbe + Animation aus NVM.
|
||||
// mx_actions[0] ↔ key_id 5 (COL_1/ROW_0), mx_actions[19] ↔ key_id 24 (COL_4/ROW_4)
|
||||
//
|
||||
// Idle-Animation: Regenbogen-Sweep über alle 20 LEDs.
|
||||
// Jede LED bekommt einen gleichmäßigen Hue-Versatz (phase = idx * period / 20),
|
||||
// sodass immer ein voller Regenbogen auf dem Pad liegt und sich langsam dreht.
|
||||
const uint16_t k_rainbow_period = 4000; // 4s pro volle Runde
|
||||
|
||||
for (uint8_t key = 5; key < MATRIX_KEYS; key++) {
|
||||
uint8_t col = key / MATRIX_ROWS;
|
||||
@@ -118,9 +118,16 @@ void CMainController::init_buttons()
|
||||
RGB base(cfg.led_r[mx_idx], cfg.led_g[mx_idx], cfg.led_b[mx_idx]);
|
||||
m_buttons[key].init(key, led, cfg.mx_actions[mx_idx], base);
|
||||
|
||||
// Phase gleichmäßig verteilen: LED 0 = Hue 0, LED 19 = Hue ~242 (fast voll)
|
||||
uint16_t phase = (uint16_t)((uint32_t)mx_idx * k_rainbow_period / 20);
|
||||
m_buttons[key].set_anim(LEDAnim::COLOR_CYCLE, k_rainbow_period, phase);
|
||||
LEDAnim anim = static_cast<LEDAnim>(cfg.led_anim[mx_idx]);
|
||||
uint16_t period = cfg.led_period_ms[mx_idx] > 0 ? cfg.led_period_ms[mx_idx] : 4000;
|
||||
|
||||
if (anim == LEDAnim::COLOR_CYCLE) {
|
||||
// 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 {
|
||||
m_buttons[key].set_anim(anim, period);
|
||||
}
|
||||
}
|
||||
|
||||
// Encoder CW/CCW-Aktionen separat merken – Encoder haben kein CButton-Objekt
|
||||
@@ -246,6 +253,58 @@ void CMainController::poll_vendor()
|
||||
}
|
||||
break;
|
||||
|
||||
// ── Makro-Übertragung: BEGIN → n×DATA → COMMIT ──────────────────
|
||||
case USB_CMD_MACRO_BEGIN:
|
||||
m_macro_chunks_expected = pkt.key_id();
|
||||
m_macro_receiving = true;
|
||||
memset(m_macro_buf, 0, sizeof(m_macro_buf));
|
||||
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 count = (uint8_t)(sizeof(m_macro_buf) - offset);
|
||||
if (count > 6) count = 6;
|
||||
memcpy(m_macro_buf + offset, &pkt.data[2], count);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_CMD_MACRO_COMMIT:
|
||||
if (m_macro_receiving) {
|
||||
m_macro_receiving = false;
|
||||
memcpy(&m_macros, m_macro_buf, sizeof(m_macros));
|
||||
macro_config_save(m_macros);
|
||||
usb_serial_send(USB_EVT_MACRO_ACK, 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); // 256
|
||||
const uint8_t payload = 6;
|
||||
uint8_t chunks = (uint8_t)((sz + payload - 1) / payload); // 43
|
||||
|
||||
usb_serial_send(USB_EVT_MACRO_BEGIN, chunks);
|
||||
|
||||
for (uint8_t i = 0; i < chunks; i++) {
|
||||
uint8_t p[SERIAL_PKT_SIZE] = {};
|
||||
p[0] = USB_EVT_MACRO_DATA;
|
||||
p[1] = i;
|
||||
uint16_t offset = (uint16_t)i * payload;
|
||||
for (uint8_t b = 0; b < payload; b++) {
|
||||
if (offset + b < sz) p[2 + b] = raw[offset + b];
|
||||
}
|
||||
if (SerialUSB) SerialUSB.write(p, SERIAL_PKT_SIZE);
|
||||
}
|
||||
|
||||
usb_serial_send(USB_EVT_MACRO_END, chunks);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -328,6 +387,23 @@ void CMainController::execute_action(SAction action)
|
||||
// Wird in processEvents() über Serial gesendet
|
||||
break;
|
||||
|
||||
case ActionType::MACRO:
|
||||
{
|
||||
// Makro-Slot aus dem RAM ausführen (bei setup() aus NVM geladen).
|
||||
// Steps mit keycode=0 werden übersprungen; erstes leeres Step stoppt.
|
||||
uint8_t slot = static_cast<uint8_t>(action.data);
|
||||
if (slot >= MACRO_SLOTS) break;
|
||||
for (uint8_t i = 0; i < MACRO_MAX_STEPS; i++) {
|
||||
const SMacroStep& s = m_macros.steps[slot][i];
|
||||
if (s.keycode == 0) break;
|
||||
usb_hid_send_key(s.keycode, s.modifier);
|
||||
delay(10);
|
||||
usb_hid_release_key();
|
||||
delay(20); // Kurze Pause zwischen Steps damit der Host mitkommt
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ActionType::NONE:
|
||||
default:
|
||||
break;
|
||||
|
||||
+12
-6
@@ -12,6 +12,7 @@
|
||||
#include "hal/usb_hid.h"
|
||||
#include "hal/usb_serial.h"
|
||||
#include "config/action.h"
|
||||
#include "config/macro_config.h"
|
||||
|
||||
class CMainController
|
||||
{
|
||||
@@ -41,10 +42,15 @@ private:
|
||||
void updateLEDs(); // Dirty-LEDs in WS2812-Buffer schreiben
|
||||
|
||||
// ── Config-Empfangspuffer ─────────────────────────────────────────────────
|
||||
// Mehrteilige Übertragung: BEGIN setzt receiving=true, DATA füllt den Buffer,
|
||||
// COMMIT validiert und schreibt in den NVM.
|
||||
// Puffergröße = sizeof(SDeviceConfig) = 163 Bytes.
|
||||
uint8_t m_cfg_buf[163]; // Empfangspuffer für eingehende Config-Daten
|
||||
uint8_t m_cfg_chunks_expected; // Anzahl erwarteter Chunks (aus BEGIN-Paket)
|
||||
bool m_cfg_receiving; // true wenn Übertragung läuft
|
||||
uint8_t m_cfg_buf[223]; // sizeof(SDeviceConfig) = 223 Bytes
|
||||
uint8_t m_cfg_chunks_expected;
|
||||
bool m_cfg_receiving;
|
||||
|
||||
// ── Makro-Empfangspuffer ──────────────────────────────────────────────────
|
||||
uint8_t m_macro_buf[256]; // sizeof(SMacroTable) = 256 Bytes
|
||||
uint8_t m_macro_chunks_expected;
|
||||
bool m_macro_receiving;
|
||||
|
||||
// Geladene Makro-Tabelle (im RAM – wird beim Start aus NVM geladen)
|
||||
SMacroTable m_macros;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ enum class ActionType : uint8_t
|
||||
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, …)
|
||||
MACRO, // Makro-Slot (data = Slot-Index 0–31) → bis zu 4 HID-Keys sequenziell
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) SAction
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// macro_config.cpp
|
||||
// NVM-Zugriff für die Makro-Tabelle (Row 1, 0x1FF00).
|
||||
// Nutzt dieselben NVMCTRL-Hilfsfunktionen wie nvm_config.cpp (dupliziert,
|
||||
// da static – kein gemeinsamer Header für interne NVM-Helfer).
|
||||
|
||||
#include "macro_config.h"
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
static const uint32_t k_macro_addr = 0x1FF00UL; // Row 1 (256B nach Row 0)
|
||||
|
||||
static void nvm_wait() { while (!NVMCTRL->INTFLAG.bit.READY) {} }
|
||||
static void nvm_exec(uint16_t cmd)
|
||||
{
|
||||
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | cmd;
|
||||
nvm_wait();
|
||||
}
|
||||
|
||||
static void nvm_erase_row(uint32_t addr)
|
||||
{
|
||||
nvm_wait();
|
||||
NVMCTRL->ADDR.reg = addr / 2;
|
||||
nvm_exec(NVMCTRL_CTRLA_CMD_ER);
|
||||
}
|
||||
|
||||
static void nvm_write_page(uint32_t addr, const uint8_t* data)
|
||||
{
|
||||
nvm_exec(NVMCTRL_CTRLA_CMD_PBC);
|
||||
volatile uint32_t* dst = reinterpret_cast<volatile uint32_t*>(addr);
|
||||
const uint32_t* src = reinterpret_cast<const uint32_t*>(data);
|
||||
for (uint8_t i = 0; i < 64 / 4; i++) dst[i] = src[i];
|
||||
NVMCTRL->ADDR.reg = addr / 2;
|
||||
nvm_exec(NVMCTRL_CTRLA_CMD_WP);
|
||||
}
|
||||
|
||||
bool macro_config_load(SMacroTable& tbl)
|
||||
{
|
||||
memcpy(&tbl, reinterpret_cast<const void*>(k_macro_addr), sizeof(tbl));
|
||||
|
||||
// Prüfen ob Row 1 noch gelöscht ist (alle 0xFF = nie beschrieben)
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(&tbl);
|
||||
bool all_ff = true;
|
||||
for (uint16_t i = 0; i < sizeof(tbl); i++) {
|
||||
if (raw[i] != 0xFF) { all_ff = false; break; }
|
||||
}
|
||||
if (all_ff) {
|
||||
memset(&tbl, 0, sizeof(tbl)); // Leere Tabelle als Default
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void macro_config_save(const SMacroTable& tbl)
|
||||
{
|
||||
// Auf 4-Byte-ausgerichteten Puffer kopieren bevor nvm_write_page ihn als uint32_t* liest.
|
||||
// SMacroTable ist __attribute__((packed)) und könnte unaligned liegen →
|
||||
// direkter uint32_t*-Cast würde auf Cortex-M0+ einen HardFault auslösen.
|
||||
uint8_t aligned_buf[256] __attribute__((aligned(4)));
|
||||
memcpy(aligned_buf, &tbl, sizeof(tbl));
|
||||
|
||||
NVMCTRL->CTRLB.bit.MANW = 1;
|
||||
nvm_erase_row(k_macro_addr);
|
||||
for (uint8_t p = 0; p < 4; p++) {
|
||||
nvm_write_page(k_macro_addr + p * 64, aligned_buf + p * 64);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
// macro_config.h
|
||||
// Makro-Tabelle: bis zu 32 Slots, je 4 HID-Key-Steps.
|
||||
// Gespeichert in NVM Row 1 (0x1FF00, 256 Bytes).
|
||||
//
|
||||
// Slot-Zuweisung (vom Windows-App vergeben, Board speichert blind):
|
||||
// Slot 0–19 : MX-Buttons (mx_idx)
|
||||
// Slot 20–31 : Encoder-Aktionen (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).
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MACRO_SLOTS 32
|
||||
#define MACRO_MAX_STEPS 4
|
||||
|
||||
// Ein einzelner HID-Key-Step im Makro
|
||||
struct __attribute__((packed)) SMacroStep
|
||||
{
|
||||
uint8_t keycode; // HID Keyboard Usage (0x00 = leer → Step überspringen)
|
||||
uint8_t modifier; // HID Modifier-Byte (Ctrl=0x01, Shift=0x02, Alt=0x04, GUI=0x08)
|
||||
};
|
||||
|
||||
// Komplette Makro-Tabelle (32 × 4 × 2 = 256 Bytes = eine NVM-Row)
|
||||
struct __attribute__((packed)) SMacroTable
|
||||
{
|
||||
SMacroStep steps[MACRO_SLOTS][MACRO_MAX_STEPS];
|
||||
};
|
||||
|
||||
// Makro-Tabelle aus NVM lesen (Row 1: 0x1FF00).
|
||||
// Gibt false zurück wenn der Flash-Bereich noch gelöscht (0xFF) war → leere Tabelle geladen.
|
||||
bool macro_config_load(SMacroTable& tbl);
|
||||
|
||||
// Makro-Tabelle in NVM schreiben (löscht Row 1, schreibt 4 Pages).
|
||||
void macro_config_save(const SMacroTable& tbl);
|
||||
@@ -82,6 +82,12 @@ void nvm_config_defaults(SDeviceConfig& cfg)
|
||||
cfg.led_b[i] = 0;
|
||||
}
|
||||
|
||||
// LED-Animationen: Regenbogen (COLOR_CYCLE=5) mit 4s Periode als Standard
|
||||
for (uint8_t i = 0; i < 20; i++) {
|
||||
cfg.led_anim[i] = 5; // LEDAnim::COLOR_CYCLE
|
||||
cfg.led_period_ms[i] = 4000;
|
||||
}
|
||||
|
||||
cfg.crc = nvm_config_crc(cfg);
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -7,19 +7,21 @@
|
||||
// Offset Size Inhalt
|
||||
// 0 4 Magic (0x56503202 = 'VP2\x02')
|
||||
// 4 1 Version
|
||||
// 5 2 CRC16 über Bytes 7–162
|
||||
// 5 2 CRC16 über Bytes 7–222
|
||||
// 7 60 mx_actions[20] – 20 × 3B (SAction packed)
|
||||
// 67 36 enc_actions[4][3] – 12 × 3B
|
||||
// 103 20 led_r[20]
|
||||
// 123 20 led_g[20]
|
||||
// 143 20 led_b[20]
|
||||
// 163 93 Padding bis 256 Bytes (erste Row voll)
|
||||
// 163 20 led_anim[20] – LEDAnim-Typ pro Button (uint8_t)
|
||||
// 183 40 led_period_ms[20] – Animationsperiode in ms (uint16_t, little-endian)
|
||||
// 223 33 Padding bis 256 Bytes (erste Row voll)
|
||||
// 256 256 Reserviert für zukünftige Erweiterungen (zweite Row)
|
||||
//
|
||||
// Gesamt genutzt: 163 Bytes (sizeof SDeviceConfig mit packed SAction)
|
||||
// Gesamt genutzt: 223 Bytes (sizeof SDeviceConfig mit packed SAction)
|
||||
|
||||
#define NVM_CONFIG_MAGIC 0x56503202UL
|
||||
#define NVM_CONFIG_VERSION 1
|
||||
#define NVM_CONFIG_VERSION 2 // Version 2: led_anim + led_period_ms hinzugefügt
|
||||
|
||||
// Encoder-Aktions-Indizes (in SDeviceConfig.enc_actions[])
|
||||
// Reihenfolge: [enc][0]=SW, [enc][1]=CW, [enc][2]=CCW
|
||||
@@ -41,6 +43,10 @@ struct __attribute__((packed)) SDeviceConfig
|
||||
uint8_t led_r[20];
|
||||
uint8_t led_g[20];
|
||||
uint8_t led_b[20];
|
||||
|
||||
// LED-Animationen pro MX-Button
|
||||
uint8_t led_anim[20]; // LEDAnim-Typ (0=STATIC, 1=BLINK, 2=PULSE, 5=COLOR_CYCLE)
|
||||
uint16_t led_period_ms[20]; // Animationsperiode in ms (0 = Firmware-Default verwenden)
|
||||
};
|
||||
|
||||
// Standardwerte wenn keine gültige Config im NVM
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
#define USB_CMD_CONFIG_DATA 0x11
|
||||
#define USB_CMD_CONFIG_COMMIT 0x12
|
||||
#define USB_CMD_CONFIG_READ 0x13 // Board sendet aktuelle NVM-Config zurück
|
||||
// Makro-Tabelle übertragen (gleiche Chunk-Struktur wie Config):
|
||||
#define USB_CMD_MACRO_BEGIN 0x20 // Data[1] = Chunk-Anzahl
|
||||
#define USB_CMD_MACRO_DATA 0x21 // Data[1] = Chunk-Index, Data[2..7] = 6B
|
||||
#define USB_CMD_MACRO_COMMIT 0x22 // NVM schreiben + ACK zurück
|
||||
#define USB_CMD_MACRO_READ 0x23 // Board sendet aktuelle Makro-Tabelle zurück
|
||||
|
||||
// ── Events: Board → PC ────────────────────────────────────────────────────────
|
||||
#define USB_EVT_KEY_DOWN 0x81 // key_id → HOST_COMMAND-Button gedrückt
|
||||
@@ -48,6 +53,10 @@
|
||||
#define USB_EVT_CONFIG_BEGIN 0x92 // Beginn Config-Dump: Data[1] = Chunk-Anzahl
|
||||
#define USB_EVT_CONFIG_DATA 0x93 // Config-Chunk: Data[1] = Index, Data[2..7] = 6B
|
||||
#define USB_EVT_CONFIG_END 0x94 // Config-Dump abgeschlossen
|
||||
#define USB_EVT_MACRO_ACK 0x95 // Makro-Tabelle erfolgreich gespeichert
|
||||
#define USB_EVT_MACRO_BEGIN 0x96 // Beginn Makro-Dump: Data[1] = Chunk-Anzahl
|
||||
#define USB_EVT_MACRO_DATA 0x97 // Makro-Chunk: Data[1] = Index, Data[2..7] = 6B
|
||||
#define USB_EVT_MACRO_END 0x98 // Makro-Dump abgeschlossen
|
||||
|
||||
// Paket-Struct mit Accessor-Methoden für lesbareren Code
|
||||
struct SerialPacket
|
||||
|
||||
Reference in New Issue
Block a user