Initial commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
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, …)
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) SAction
|
||||
{
|
||||
ActionType type;
|
||||
uint16_t data; // Keycode (HID_KEY / HID_CONSUMER) oder Command-ID (HOST_COMMAND)
|
||||
// packed: 1B type + 2B data = 3B (kein Alignment-Padding)
|
||||
// Muss packed sein damit sizeof(SDeviceConfig)==163 == C#-Serialisierung
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "nvm_config.h"
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
// ── Flash-Adresse (aus Linkerscript) ─────────────────────────────────────────
|
||||
// Kein separates Linker-Symbol nötig – Adresse ist fix und bekannt.
|
||||
static const uint32_t k_config_addr = 0x1FE00UL;
|
||||
|
||||
// SAMD21 NVMCTRL ──────────────────────────────────────────────────────────────
|
||||
// Row = 256 Bytes = 4 Pages à 64 Bytes
|
||||
// Schreiben: Row löschen (ER), dann seitenweise schreiben (WP)
|
||||
|
||||
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; // NVMCTRL erwartet Wort-Adresse (16-Bit-Worte)
|
||||
nvm_exec(NVMCTRL_CTRLA_CMD_ER);
|
||||
}
|
||||
|
||||
static void nvm_write_page(uint32_t addr, const uint8_t* data)
|
||||
{
|
||||
// Page-Buffer löschen
|
||||
nvm_exec(NVMCTRL_CTRLA_CMD_PBC);
|
||||
|
||||
// 64 Bytes in den Page-Buffer schreiben (32-Bit-Zugriffe)
|
||||
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];
|
||||
}
|
||||
|
||||
// Page programmieren
|
||||
NVMCTRL->ADDR.reg = addr / 2;
|
||||
nvm_exec(NVMCTRL_CTRLA_CMD_WP);
|
||||
}
|
||||
|
||||
// ── CRC16 (CCITT, Poly 0x1021) ────────────────────────────────────────────────
|
||||
uint16_t nvm_config_crc(const SDeviceConfig& cfg)
|
||||
{
|
||||
// CRC über alles nach dem crc-Feld (ab Byte 7)
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(&cfg) + offsetof(SDeviceConfig, mx_actions);
|
||||
uint16_t len = sizeof(SDeviceConfig) - offsetof(SDeviceConfig, mx_actions);
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
crc ^= static_cast<uint16_t>(data[i]) << 8;
|
||||
for (uint8_t b = 0; b < 8; b++) {
|
||||
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
// ── Defaults ─────────────────────────────────────────────────────────────────
|
||||
void nvm_config_defaults(SDeviceConfig& cfg)
|
||||
{
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.magic = NVM_CONFIG_MAGIC;
|
||||
cfg.version = NVM_CONFIG_VERSION;
|
||||
|
||||
// Alle Aktionen: NONE
|
||||
for (uint8_t i = 0; i < 20; i++)
|
||||
cfg.mx_actions[i] = {ActionType::NONE, 0};
|
||||
for (uint8_t e = 0; e < 4; e++)
|
||||
for (uint8_t a = 0; a < 3; a++)
|
||||
cfg.enc_actions[e][a] = {ActionType::NONE, 0};
|
||||
|
||||
// Base-LEDs: warm-weiß
|
||||
for (uint8_t i = 0; i < 20; i++) {
|
||||
cfg.led_r[i] = 80;
|
||||
cfg.led_g[i] = 40;
|
||||
cfg.led_b[i] = 0;
|
||||
}
|
||||
|
||||
cfg.crc = nvm_config_crc(cfg);
|
||||
}
|
||||
|
||||
// ── Laden ─────────────────────────────────────────────────────────────────────
|
||||
bool nvm_config_load(SDeviceConfig& cfg)
|
||||
{
|
||||
memcpy(&cfg, reinterpret_cast<const void*>(k_config_addr), sizeof(cfg));
|
||||
|
||||
if (cfg.magic != NVM_CONFIG_MAGIC) { nvm_config_defaults(cfg); return false; }
|
||||
if (cfg.version != NVM_CONFIG_VERSION) { nvm_config_defaults(cfg); return false; }
|
||||
if (cfg.crc != nvm_config_crc(cfg)) { nvm_config_defaults(cfg); return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Speichern ─────────────────────────────────────────────────────────────────
|
||||
void nvm_config_save(const SDeviceConfig& cfg)
|
||||
{
|
||||
// Config in temporären Buffer kopieren der auf 256B (Row) aufgefüllt ist
|
||||
uint8_t row[256];
|
||||
memset(row, 0xFF, sizeof(row));
|
||||
memcpy(row, &cfg, sizeof(cfg));
|
||||
|
||||
// Automatisches Schreiben deaktivieren (manueller Schreib-Modus)
|
||||
NVMCTRL->CTRLB.bit.MANW = 1;
|
||||
|
||||
// Row 0 der Config löschen und seitenweise schreiben (4 × 64B)
|
||||
nvm_erase_row(k_config_addr);
|
||||
for (uint8_t p = 0; p < 4; p++) {
|
||||
nvm_write_page(k_config_addr + p * 64, row + p * 64);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "action.h"
|
||||
|
||||
// ── NVM-Config-Layout (512 Bytes, ab 0x1FE00) ────────────────────────────────
|
||||
//
|
||||
// Offset Size Inhalt
|
||||
// 0 4 Magic (0x56503202 = 'VP2\x02')
|
||||
// 4 1 Version
|
||||
// 5 2 CRC16 über Bytes 7–162
|
||||
// 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)
|
||||
// 256 256 Reserviert für zukünftige Erweiterungen (zweite Row)
|
||||
//
|
||||
// Gesamt genutzt: 163 Bytes (sizeof SDeviceConfig mit packed SAction)
|
||||
|
||||
#define NVM_CONFIG_MAGIC 0x56503202UL
|
||||
#define NVM_CONFIG_VERSION 1
|
||||
|
||||
// Encoder-Aktions-Indizes (in SDeviceConfig.enc_actions[])
|
||||
// Reihenfolge: [enc][0]=SW, [enc][1]=CW, [enc][2]=CCW
|
||||
#define ENC_ACTION_SW 0
|
||||
#define ENC_ACTION_CW 1
|
||||
#define ENC_ACTION_CCW 2
|
||||
|
||||
struct __attribute__((packed)) SDeviceConfig
|
||||
{
|
||||
uint32_t magic;
|
||||
uint8_t version;
|
||||
uint16_t crc;
|
||||
|
||||
// Aktionen
|
||||
SAction mx_actions[20]; // MX-Buttons 0–19 (key_id 5–24)
|
||||
SAction enc_actions[4][3]; // [Encoder 0–3][SW/CW/CCW]
|
||||
|
||||
// Base-LED Farben
|
||||
uint8_t led_r[20];
|
||||
uint8_t led_g[20];
|
||||
uint8_t led_b[20];
|
||||
};
|
||||
|
||||
// Standardwerte wenn keine gültige Config im NVM
|
||||
void nvm_config_defaults(SDeviceConfig& cfg);
|
||||
|
||||
// Config aus NVM lesen. Gibt false zurück wenn Magic/CRC ungültig → Defaults geladen.
|
||||
bool nvm_config_load(SDeviceConfig& cfg);
|
||||
|
||||
// Config in NVM schreiben (löscht 2 Rows, schreibt neu).
|
||||
void nvm_config_save(const SDeviceConfig& cfg);
|
||||
|
||||
// CRC16 über die Nutzdaten der Config
|
||||
uint16_t nvm_config_crc(const SDeviceConfig& cfg);
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
// VersaPad v2 – Logical pin names
|
||||
// All Arduino pin numbers reference the custom variant (variants/versapad/variant.h)
|
||||
|
||||
// ─── Button Matrix ────────────────────────────────────────────────────────────
|
||||
// Layout (viewed from front):
|
||||
//
|
||||
// COL_0 COL_1 COL_2 COL_3 COL_4
|
||||
// ROW_0 [ENC3] [ ] [ ] [ ] [ ]
|
||||
// ROW_1 [ENC2] [ ] [ ] [ ] [ ]
|
||||
// ROW_2 [ENC1] [ ] [ ] [ ] [ ]
|
||||
// ROW_3 [ENC0] [ ] [ ] [ ] [ ]
|
||||
// ROW_4 --- [ ] [ ] [ ] [ ]
|
||||
//
|
||||
// COL_0 × ROW_0–3 = encoder push buttons
|
||||
// COL_1–4 × ROW_0–4 = 20 Cherry MX buttons
|
||||
// COL_0 × ROW_4 = not connected
|
||||
|
||||
#define BTN_COL_COUNT 5
|
||||
#define BTN_ROW_COUNT 5
|
||||
|
||||
// Column pins: driven OUTPUT LOW during scan, otherwise INPUT (high-Z or HIGH)
|
||||
static const uint8_t BTN_COLS[BTN_COL_COUNT] = {
|
||||
PIN_COL0, // PB10 – encoder SW column
|
||||
PIN_COL1, // PA11 – Cherry MX col 1 (leftmost)
|
||||
PIN_COL2, // PA10 – Cherry MX col 2
|
||||
PIN_COL3, // PA09 – Cherry MX col 3
|
||||
PIN_COL4, // PA08 – Cherry MX col 4 (rightmost)
|
||||
};
|
||||
|
||||
// Row pins: INPUT_PULLUP, read LOW when button pressed
|
||||
static const uint8_t BTN_ROWS[BTN_ROW_COUNT] = {
|
||||
PIN_ROW0, // PB11
|
||||
PIN_ROW1, // PA12
|
||||
PIN_ROW2, // PA13
|
||||
PIN_ROW3, // PA14
|
||||
PIN_ROW4, // PA15
|
||||
};
|
||||
|
||||
// Button index helper: col * ROW_COUNT + row → 0..24
|
||||
#define BTN_INDEX(col, row) ((col) * BTN_ROW_COUNT + (row))
|
||||
|
||||
// Encoder SW buttons are at column 0
|
||||
#define BTN_ENC_SW(enc) BTN_INDEX(0, (enc)) // enc = 0..3
|
||||
|
||||
// ─── Rotary Encoders ──────────────────────────────────────────────────────────
|
||||
// ENC0 = closest to USB connector, ENC3 = furthest
|
||||
|
||||
static const uint8_t ENC_A[4] = { PIN_ENC0_A, PIN_ENC1_A, PIN_ENC2_A, PIN_ENC3_A };
|
||||
static const uint8_t ENC_B[4] = { PIN_ENC0_B, PIN_ENC1_B, PIN_ENC2_B, PIN_ENC3_B };
|
||||
|
||||
// ─── WS2812 LEDs ─────────────────────────────────────────────────────────────
|
||||
#define LED_COUNT 20
|
||||
#define LED_DATA_PIN PIN_SPI_MOSI // PB22, SERCOM5 PAD2
|
||||
|
||||
// LED index: serpentine (even rows L→R, odd rows R→L)
|
||||
// Row 0: 0,1,2,3 Row 1: 7,6,5,4 Row 2: 8,9,10,11 Row 3: 15,14,13,12 Row 4: 16,17,18,19
|
||||
#define LED_COLS (BTN_COL_COUNT - 1) // 4 Cherry MX columns
|
||||
#define LED_INDEX(col, row) \
|
||||
((row) * LED_COLS + (((row) & 1) ? (LED_COLS - (col)) : ((col) - 1)))
|
||||
|
||||
// ─── Faders (ADC) ─────────────────────────────────────────────────────────────
|
||||
#define FADER_COUNT 3
|
||||
static const uint8_t FADER_PINS[FADER_COUNT] = {
|
||||
PIN_FADER0, // PA02 A0
|
||||
PIN_FADER1, // PA03 A1 (also VREFA – analog only, no digitalRead)
|
||||
PIN_FADER2, // PB08 A2
|
||||
};
|
||||
Reference in New Issue
Block a user