Initial commit
This commit is contained in:
commit
b49984b9c0
32 changed files with 2394 additions and 0 deletions
88
src/hal/encoder.cpp
Normal file
88
src/hal/encoder.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "encoder.h"
|
||||
#include <Arduino.h>
|
||||
#include "config/pins.h"
|
||||
|
||||
// Quadratur-Dekodierung via 4-State-Lookup.
|
||||
//
|
||||
// Zustand = (A << 1) | B → 4 mögliche Zustände (00, 01, 10, 11)
|
||||
// Bei jedem Flankenwechsel (CHANGE) auf A oder B wird der neue Zustand
|
||||
// bestimmt und mit dem vorherigen verglichen.
|
||||
//
|
||||
// Lookup-Tabelle [prev<<2 | curr] → +1 (CW), -1 (CCW), 0 (ungültig/Prellen)
|
||||
static const int8_t k_lut[16] = {
|
||||
// curr: 00 01 10 11
|
||||
0, +1, -1, 0, // prev = 00
|
||||
-1, 0, 0, +1, // prev = 01
|
||||
+1, 0, 0, -1, // prev = 10
|
||||
0, -1, +1, 0, // prev = 11
|
||||
};
|
||||
|
||||
// Pro Encoder: vorheriger Zustand + Akkumulator für Halb-Schritte.
|
||||
// Mechanische Encoder erzeugen 4 Flanken pro Raste → Akkumulator zählt
|
||||
// auf ±4 bevor ein Event gefeuert wird (= ein Event pro Klick).
|
||||
static volatile uint8_t s_state[ENCODER_COUNT];
|
||||
static volatile int8_t s_accum[ENCODER_COUNT];
|
||||
|
||||
static encoder_cb_t s_cb = nullptr;
|
||||
|
||||
static const uint8_t k_pin_a[ENCODER_COUNT] = { PIN_ENC0_A, PIN_ENC1_A, PIN_ENC2_A, PIN_ENC3_A };
|
||||
static const uint8_t k_pin_b[ENCODER_COUNT] = { PIN_ENC0_B, PIN_ENC1_B, PIN_ENC2_B, PIN_ENC3_B };
|
||||
|
||||
// Generischer Handler — wird von den 8 ISR-Wrappern unten aufgerufen.
|
||||
static void handle_encoder(uint8_t enc)
|
||||
{
|
||||
uint8_t a = digitalRead(k_pin_a[enc]);
|
||||
uint8_t b = digitalRead(k_pin_b[enc]);
|
||||
uint8_t cur = (a << 1) | b;
|
||||
uint8_t idx = (s_state[enc] << 2) | cur;
|
||||
s_state[enc] = cur;
|
||||
|
||||
int8_t delta = k_lut[idx];
|
||||
if (delta == 0) return;
|
||||
|
||||
s_accum[enc] += delta;
|
||||
|
||||
// 4 Halb-Schritte = 1 vollständige Raste
|
||||
if (s_accum[enc] >= 4) {
|
||||
s_accum[enc] = 0;
|
||||
if (s_cb) s_cb(enc, +1);
|
||||
} else if (s_accum[enc] <= -4) {
|
||||
s_accum[enc] = 0;
|
||||
if (s_cb) s_cb(enc, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// 8 ISR-Wrapper – je einer pro Pin (attachInterrupt braucht void-Funktionszeiger)
|
||||
static void isr_enc0_a() { handle_encoder(0); }
|
||||
static void isr_enc0_b() { handle_encoder(0); }
|
||||
static void isr_enc1_a() { handle_encoder(1); }
|
||||
static void isr_enc1_b() { handle_encoder(1); }
|
||||
static void isr_enc2_a() { handle_encoder(2); }
|
||||
static void isr_enc2_b() { handle_encoder(2); }
|
||||
static void isr_enc3_a() { handle_encoder(3); }
|
||||
static void isr_enc3_b() { handle_encoder(3); }
|
||||
|
||||
void encoder_init(encoder_cb_t cb)
|
||||
{
|
||||
s_cb = cb;
|
||||
|
||||
for (uint8_t i = 0; i < ENCODER_COUNT; i++) {
|
||||
pinMode(k_pin_a[i], INPUT_PULLUP);
|
||||
pinMode(k_pin_b[i], INPUT_PULLUP);
|
||||
|
||||
// Initialen Zustand lesen damit der erste Interrupt korrekt ausgewertet wird
|
||||
uint8_t a = digitalRead(k_pin_a[i]);
|
||||
uint8_t b = digitalRead(k_pin_b[i]);
|
||||
s_state[i] = (a << 1) | b;
|
||||
s_accum[i] = 0;
|
||||
}
|
||||
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC0_A), isr_enc0_a, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC0_B), isr_enc0_b, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC1_A), isr_enc1_a, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC1_B), isr_enc1_b, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC2_A), isr_enc2_a, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC2_B), isr_enc2_b, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC3_A), isr_enc3_a, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_ENC3_B), isr_enc3_b, CHANGE);
|
||||
}
|
||||
10
src/hal/encoder.h
Normal file
10
src/hal/encoder.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define ENCODER_COUNT 4
|
||||
|
||||
// Callback: enc = Encoder-Index (0–3), dir = +1 (CW) oder -1 (CCW)
|
||||
typedef void (*encoder_cb_t)(uint8_t enc, int8_t dir);
|
||||
|
||||
void encoder_init(encoder_cb_t cb);
|
||||
// Kein encoder_scan() – rein interrupt-getrieben
|
||||
68
src/hal/matrix.cpp
Normal file
68
src/hal/matrix.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "matrix.h"
|
||||
#include <Arduino.h>
|
||||
#include "config/pins.h"
|
||||
#include <string.h>
|
||||
|
||||
// Hardware: COL lines have 10k pullups to 3V3 (always HIGH by default).
|
||||
// Diodes between switch DO and ROW line (anode=switch, cathode=row).
|
||||
// Scan: drive ROW LOW → pressed switch pulls COL LOW through diode.
|
||||
|
||||
#define DEBOUNCE_MS 10
|
||||
|
||||
static matrix_cb_t s_cb;
|
||||
static bool s_raw[MATRIX_KEYS];
|
||||
static bool s_debounced[MATRIX_KEYS];
|
||||
static uint32_t s_changed_at[MATRIX_KEYS];
|
||||
|
||||
void matrix_init(matrix_cb_t cb)
|
||||
{
|
||||
s_cb = cb;
|
||||
|
||||
// COLs: INPUT – external 10k pullup holds them HIGH
|
||||
for (uint8_t c = 0; c < MATRIX_COLS; c++) {
|
||||
pinMode(BTN_COLS[c], INPUT);
|
||||
}
|
||||
// ROWs: idle high-Z, driven LOW only during scan
|
||||
for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
|
||||
pinMode(BTN_ROWS[r], INPUT);
|
||||
}
|
||||
|
||||
memset(s_raw, 0, sizeof(s_raw));
|
||||
memset(s_debounced, 0, sizeof(s_debounced));
|
||||
|
||||
uint32_t now = millis();
|
||||
for (uint8_t i = 0; i < MATRIX_KEYS; i++) {
|
||||
s_changed_at[i] = now;
|
||||
}
|
||||
}
|
||||
|
||||
void matrix_scan()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
|
||||
for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
|
||||
// Drive this row LOW
|
||||
pinMode(BTN_ROWS[r], OUTPUT);
|
||||
digitalWrite(BTN_ROWS[r], LOW);
|
||||
delayMicroseconds(10);
|
||||
|
||||
for (uint8_t c = 0; c < MATRIX_COLS; c++) {
|
||||
uint8_t key = c * MATRIX_ROWS + r;
|
||||
bool raw = (digitalRead(BTN_COLS[c]) == LOW);
|
||||
|
||||
if (raw != s_raw[key]) {
|
||||
s_raw[key] = raw;
|
||||
s_changed_at[key] = now;
|
||||
}
|
||||
|
||||
if (raw != s_debounced[key] &&
|
||||
(now - s_changed_at[key]) >= DEBOUNCE_MS) {
|
||||
s_debounced[key] = raw;
|
||||
if (s_cb) s_cb(key, raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Release row back to high-Z
|
||||
pinMode(BTN_ROWS[r], INPUT);
|
||||
}
|
||||
}
|
||||
22
src/hal/matrix.h
Normal file
22
src/hal/matrix.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// 5×5 button matrix
|
||||
// COL_0 × ROW_0–3 = encoder SW buttons
|
||||
// COL_1–4 × ROW_0–4 = 20 Cherry MX buttons
|
||||
// COL_0 × ROW_4 = not connected
|
||||
|
||||
#define MATRIX_COLS 5
|
||||
#define MATRIX_ROWS 5
|
||||
#define MATRIX_KEYS 25 // col * MATRIX_ROWS + row
|
||||
|
||||
// Callback: key index (0–24), pressed = true / released = false
|
||||
typedef void (*matrix_cb_t)(uint8_t key, bool pressed);
|
||||
|
||||
void matrix_init(matrix_cb_t cb);
|
||||
void matrix_scan();
|
||||
|
||||
// Helper: key index from logical position
|
||||
inline uint8_t matrix_key(uint8_t col, uint8_t row) {
|
||||
return col * MATRIX_ROWS + row;
|
||||
}
|
||||
96
src/hal/usb_hid.cpp
Normal file
96
src/hal/usb_hid.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "usb_hid.h"
|
||||
#include <Arduino.h>
|
||||
#include <HID.h>
|
||||
|
||||
// ── HID Report Descriptor: Keyboard + Consumer Control ───────────────────────
|
||||
// Vendor-Kommunikation läuft über CVendorHID (eigenes PluggableUSBModule).
|
||||
|
||||
static const uint8_t k_hid_descriptor[] = {
|
||||
|
||||
// ── Report ID 1: Keyboard ─────────────────────────────────────────────────
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, HID_REPORT_ID_KEYBOARD,
|
||||
0x05, 0x07, // Usage Page (Key Codes)
|
||||
0x19, 0xE0, // Usage Minimum (Left Ctrl)
|
||||
0x29, 0xE7, // Usage Maximum (Right GUI)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1 Bit)
|
||||
0x95, 0x08, // Report Count (8)
|
||||
0x81, 0x02, // Input (Data, Variable, Absolute)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x08, // Report Size (8 Bit)
|
||||
0x81, 0x01, // Input (Constant)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x75, 0x08, // Report Size (8 Bit)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x65, // Logical Maximum (101)
|
||||
0x05, 0x07, // Usage Page (Key Codes)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x29, 0x65, // Usage Maximum (101)
|
||||
0x81, 0x00, // Input (Data, Array)
|
||||
0xC0, // End Collection
|
||||
|
||||
// ── Report ID 2: Consumer Control ─────────────────────────────────────────
|
||||
0x05, 0x0C, // Usage Page (Consumer Devices)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, HID_REPORT_ID_CONSUMER,
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x03, // Logical Maximum (1023)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x2A, 0xFF, 0x03, // Usage Maximum (1023)
|
||||
0x75, 0x10, // Report Size (16 Bit)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x00, // Input (Data, Array)
|
||||
0xC0, // End Collection
|
||||
};
|
||||
|
||||
namespace {
|
||||
struct HIDRegistrar {
|
||||
HIDSubDescriptor node;
|
||||
HIDRegistrar() : node(k_hid_descriptor, sizeof(k_hid_descriptor)) {
|
||||
HID().AppendDescriptor(&node);
|
||||
}
|
||||
} s_hid_registrar;
|
||||
}
|
||||
|
||||
struct KeyboardReport {
|
||||
uint8_t modifier;
|
||||
uint8_t reserved;
|
||||
uint8_t keycodes[6];
|
||||
};
|
||||
|
||||
struct ConsumerReport {
|
||||
uint16_t usage;
|
||||
};
|
||||
|
||||
void usb_hid_init() {}
|
||||
|
||||
void usb_hid_send_key(uint8_t keycode, uint8_t modifier)
|
||||
{
|
||||
KeyboardReport report = {};
|
||||
report.modifier = modifier;
|
||||
report.keycodes[0] = keycode;
|
||||
HID().SendReport(HID_REPORT_ID_KEYBOARD, &report, sizeof(report));
|
||||
}
|
||||
|
||||
void usb_hid_release_key()
|
||||
{
|
||||
KeyboardReport report = {};
|
||||
HID().SendReport(HID_REPORT_ID_KEYBOARD, &report, sizeof(report));
|
||||
}
|
||||
|
||||
void usb_hid_send_consumer(uint16_t usage)
|
||||
{
|
||||
ConsumerReport report = { usage };
|
||||
HID().SendReport(HID_REPORT_ID_CONSUMER, &report, sizeof(report));
|
||||
}
|
||||
|
||||
void usb_hid_release_consumer()
|
||||
{
|
||||
ConsumerReport report = { 0 };
|
||||
HID().SendReport(HID_REPORT_ID_CONSUMER, &report, sizeof(report));
|
||||
}
|
||||
35
src/hal/usb_hid.h
Normal file
35
src/hal/usb_hid.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// ── Report-IDs (Keyboard/Consumer Interface) ──────────────────────────────────
|
||||
#define HID_REPORT_ID_KEYBOARD 1
|
||||
#define HID_REPORT_ID_CONSUMER 2
|
||||
|
||||
// ── Keyboard Modifier-Bits ────────────────────────────────────────────────────
|
||||
#define KEY_MOD_LCTRL 0x01
|
||||
#define KEY_MOD_LSHIFT 0x02
|
||||
#define KEY_MOD_LALT 0x04
|
||||
#define KEY_MOD_LGUI 0x08
|
||||
#define KEY_MOD_RCTRL 0x10
|
||||
#define KEY_MOD_RSHIFT 0x20
|
||||
#define KEY_MOD_RALT 0x40
|
||||
#define KEY_MOD_RGUI 0x80
|
||||
|
||||
// ── Consumer Control Usage IDs (HID Usage Table 1.3, Consumer Page 0x0C) ─────
|
||||
#define CONSUMER_MUTE 0x00E2
|
||||
#define CONSUMER_VOLUME_UP 0x00E9
|
||||
#define CONSUMER_VOLUME_DOWN 0x00EA
|
||||
#define CONSUMER_PLAY_PAUSE 0x00CD
|
||||
#define CONSUMER_NEXT_TRACK 0x00B5
|
||||
#define CONSUMER_PREV_TRACK 0x00B6
|
||||
#define CONSUMER_STOP 0x00B7
|
||||
#define CONSUMER_BRIGHTNESS_UP 0x006F
|
||||
#define CONSUMER_BRIGHTNESS_DN 0x0070
|
||||
|
||||
void usb_hid_init();
|
||||
|
||||
void usb_hid_send_key(uint8_t keycode, uint8_t modifier = 0);
|
||||
void usb_hid_release_key();
|
||||
|
||||
void usb_hid_send_consumer(uint16_t usage);
|
||||
void usb_hid_release_consumer();
|
||||
56
src/hal/usb_serial.cpp
Normal file
56
src/hal/usb_serial.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// usb_serial.cpp
|
||||
// CDC Serial – bidirektionale Kommunikation mit der Windows-App.
|
||||
//
|
||||
// Empfang (PC → Board):
|
||||
// CDC kann Bytes in beliebig kleinen Happen liefern. usb_serial_poll() liest
|
||||
// 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).
|
||||
//
|
||||
// Senden (Board → PC):
|
||||
// Direkt via SerialUSB.write() – kein eigener Puffer nötig, da der Arduino-CDC-
|
||||
// Stack intern puffert. Nur gesendet wenn SerialUSB verbunden ist (USB-Host da).
|
||||
|
||||
#include "usb_serial.h"
|
||||
#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.
|
||||
static uint8_t s_buf[SERIAL_PKT_SIZE * 32];
|
||||
static uint16_t s_head = 0;
|
||||
static uint16_t s_count = 0;
|
||||
|
||||
void usb_serial_init()
|
||||
{
|
||||
SerialUSB.begin(0); // CDC ignoriert Baudrate – Wert egal
|
||||
}
|
||||
|
||||
void usb_serial_send(uint8_t event_type, uint8_t key_id, uint8_t a, uint8_t b)
|
||||
{
|
||||
if (!SerialUSB) return; // Nicht verbunden
|
||||
uint8_t pkt[SERIAL_PKT_SIZE] = { event_type, key_id, a, b, 0, 0, 0, 0 };
|
||||
SerialUSB.write(pkt, SERIAL_PKT_SIZE);
|
||||
}
|
||||
|
||||
bool usb_serial_poll(SerialPacket& out)
|
||||
{
|
||||
// Verfügbare Bytes in internen Buffer lesen
|
||||
while (SerialUSB.available() && s_count < sizeof(s_buf)) {
|
||||
s_buf[(s_head + s_count) % sizeof(s_buf)] = SerialUSB.read();
|
||||
s_count++;
|
||||
}
|
||||
|
||||
// Sobald ein vollständiges Paket da ist, ausgeben
|
||||
if (s_count >= SERIAL_PKT_SIZE) {
|
||||
for (uint8_t i = 0; i < SERIAL_PKT_SIZE; i++) {
|
||||
out.data[i] = s_buf[(s_head + i) % sizeof(s_buf)];
|
||||
}
|
||||
s_head = (s_head + SERIAL_PKT_SIZE) % sizeof(s_buf);
|
||||
s_count -= SERIAL_PKT_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
70
src/hal/usb_serial.h
Normal file
70
src/hal/usb_serial.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// usb_serial.h
|
||||
// Bidirektionale Kommunikation zwischen Board und Windows-App über CDC Serial.
|
||||
//
|
||||
// Das Board erscheint als COM-Port unter Windows (kein Treiber nötig).
|
||||
// Alle Pakete haben feste Größe (SERIAL_PKT_SIZE = 8 Bytes) – kein
|
||||
// Längen-Header nötig, vereinfacht Parsing auf beiden Seiten.
|
||||
//
|
||||
// Byte-Layout aller Pakete:
|
||||
// [0] Command/Event-ID
|
||||
// [1] key_id (Button 0–24 oder Encoder 0–3)
|
||||
// [2] r / Daten-Byte A
|
||||
// [3] g / Daten-Byte B
|
||||
// [4] b
|
||||
// [5..7] reserviert (0x00)
|
||||
//
|
||||
// Richtungen:
|
||||
// PC → Board (Commands, 0x01–0x7F): poll_vendor() in CMainController
|
||||
// Board → PC (Events, 0x81–0xFF): usb_serial_send() in processEvents()
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define SERIAL_PKT_SIZE 8
|
||||
|
||||
// ── Commands: PC → Board ──────────────────────────────────────────────────────
|
||||
#define USB_CMD_SET_LED_OVERRIDE 0x01 // key_id, r, g, b → Override-LED setzen
|
||||
#define USB_CMD_CLEAR_LED_OVERRIDE 0x02 // key_id → Override löschen, zurück zu base
|
||||
#define USB_CMD_SET_LED_BASE 0x03 // key_id, r, g, b → Base-LED dauerhaft ändern
|
||||
|
||||
// Config-Übertragung (mehrteilig, 6 Nutzbytes pro Paket):
|
||||
// BEGIN: Data[1] = Anzahl Chunks die folgen
|
||||
// DATA: Data[1] = Chunk-Index (0-based), Data[2..7] = 6 Bytes Nutzdaten
|
||||
// COMMIT: CRC prüfen + NVM schreiben + Buttons neu laden
|
||||
#define USB_CMD_PING 0x05 // Board antwortet sofort mit USB_EVT_PONG
|
||||
#define USB_CMD_CONFIG_BEGIN 0x10
|
||||
#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
|
||||
|
||||
// ── 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_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
|
||||
#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
|
||||
|
||||
// Paket-Struct mit Accessor-Methoden für lesbareren Code
|
||||
struct SerialPacket
|
||||
{
|
||||
uint8_t data[SERIAL_PKT_SIZE];
|
||||
uint8_t command() const { return data[0]; }
|
||||
uint8_t key_id() const { return data[1]; }
|
||||
uint8_t r() const { return data[2]; }
|
||||
uint8_t g() const { return data[3]; }
|
||||
uint8_t b() const { return data[4]; }
|
||||
};
|
||||
|
||||
void usb_serial_init();
|
||||
|
||||
// Board → PC: 8-Byte-Event-Paket senden (nur wenn SerialUSB verbunden)
|
||||
void usb_serial_send(uint8_t event_type, uint8_t key_id, uint8_t a = 0, uint8_t b = 0);
|
||||
|
||||
// PC → Board: nächstes vollständiges Paket abholen.
|
||||
// Gibt true zurück wenn ein Paket verfügbar war.
|
||||
bool usb_serial_poll(SerialPacket& out);
|
||||
35
src/hal/ws2812.cpp
Normal file
35
src/hal/ws2812.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// WS2812 driver – wraps Adafruit NeoPixel (bit-bang, no SERCOM needed)
|
||||
|
||||
#include "ws2812.h"
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
static Adafruit_NeoPixel s_strip(WS2812_COUNT, WS2812_PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
void ws2812_init()
|
||||
{
|
||||
s_strip.begin();
|
||||
s_strip.clear();
|
||||
s_strip.show();
|
||||
}
|
||||
|
||||
void ws2812_set(uint8_t idx, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
if (idx >= WS2812_COUNT) return;
|
||||
s_strip.setPixelColor(idx, r, g, b);
|
||||
}
|
||||
|
||||
void ws2812_fill(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
s_strip.fill(s_strip.Color(r, g, b));
|
||||
}
|
||||
|
||||
void ws2812_show()
|
||||
{
|
||||
s_strip.show();
|
||||
}
|
||||
|
||||
void ws2812_clear()
|
||||
{
|
||||
s_strip.clear();
|
||||
s_strip.show();
|
||||
}
|
||||
30
src/hal/ws2812.h
Normal file
30
src/hal/ws2812.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// ws2812.h
|
||||
// Thin HAL-Wrapper um Adafruit NeoPixel (bit-bang, kein SERCOM).
|
||||
//
|
||||
// Dirty-Flag-Pattern:
|
||||
// ws2812_set() schreibt nur in den internen NeoPixel-Buffer (RAM).
|
||||
// ws2812_show() überträgt den gesamten Buffer an die LEDs (~600µs, blockierend
|
||||
// via noInterrupts()). Nie aus einer ISR aufrufen!
|
||||
// CButton::render_led() ruft nur ws2812_set() auf; ws2812_show() wird
|
||||
// einmalig von CMainController::updateLEDs() aufgerufen wenn mindestens
|
||||
// ein Button dirty war oder eine Animation läuft.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define WS2812_COUNT 20
|
||||
#define WS2812_PIN 18 // D18 = PB22 = LED_DATA_PIN
|
||||
|
||||
void ws2812_init();
|
||||
|
||||
// Einzelne LED im Buffer setzen (sofort, kein HW-Transfer)
|
||||
void ws2812_set(uint8_t idx, uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
// Alle LEDs im Buffer auf dieselbe Farbe setzen (kein HW-Transfer)
|
||||
void ws2812_fill(uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
// Buffer an Hardware übertragen (~600µs, blockierend via noInterrupts())
|
||||
void ws2812_show();
|
||||
|
||||
// Buffer löschen und sofort anzeigen (LEDs aus)
|
||||
void ws2812_clear();
|
||||
Loading…
Add table
Add a link
Reference in a new issue