Files
audio-synth/dev/digital/Firmware_TEST
2025-12-03 13:19:34 +01:00
..
2025-10-29 08:58:43 +01:00
2025-10-29 08:58:43 +01:00
2025-10-29 08:58:43 +01:00
2025-10-29 08:58:43 +01:00
2025-10-29 08:58:43 +01:00
2025-12-03 13:19:34 +01:00

Firmware

Inhaltsverzeichnis

  1. Projektübersicht
  2. Systemarchitektur
  3. Komponenten
  4. Speicheranalyse
  5. Installation
  6. Verwendung
  7. Hauptablauf

Projektübersicht

Features

  • Dual-Channel CV-Sequencer - 2 unabhängige Control Voltage Ausgänge
  • 4×3 Tastaturmatrix - Echtzeit-Tasten-Eingabe mit Entprellung
  • Recording & Playback - Speichere Sequenzen bis 30 Sekunden
  • Loop-Funktion - Endlose Wiederholung oder Einmaledition
  • Live-Modus - Direkte Tastatur-zu-CV Ausgabe
  • Multi-Key Support - Bis zu 10 gleichzeitig aktive Tasten

Hardware

Komponente Modell Funktion
Microcontroller ESP32 Hauptprozessor
DAC MCP4728 4-Kanal 12-Bit DAC
I2C Bus - Kommunikation MCU ↔ DAC
Tastatur 4×3 Matrix Benutzereingabe
Buttons 4× Push-Buttons Record/Play Steuerung

Systemarchitektur

3-Schicht-Modell

┌─────────────────────────────────────────────────────┐
│                  INPUT LAYER (📥)                   │
├──────────────────────┬──────────────────────────────┤
│   Keyboard Matrix    │   Sequencer Buttons          │
│   (4×3 Tasten)       │   (Record/Play × 2)          │
└──────────────┬───────┴──────────────┬───────────────┘
               │                      │
┌──────────────▼─────────────────────▼───────────────┐
│              PROCESSING LAYER (⚙️)                 │
├──────────────────────┬──────────────────────────────┤
│  Keyboard Klasse     │  SequencerBlock (2×)         │
│  (Queue-Management)  │  (Recording & Playback)      │
└──────────────┬───────┴──────────────┬───────────────┘
               │                      │
┌──────────────▼─────────────────────▼───────────────┐
│              OUTPUT LAYER (📤)                     │
├──────────────────────┬──────────────────────────────┤
│    CV Klasse (DAC)   │   CV Ausgänge                │
│   MCP4728 I2C        │   (A=Ch1, B=Ch2)             │
└──────────────┬───────┴──────────────┬───────────────┘
               │                      │
               └──────────────┬───────┘
                              ▼
                    Externe Synthesizer / Module

Datenfluss

TASTATUR → KEYBOARD → SEQUENCER/LIVE → DAC → CV AUSGÄNGE
                         ↑
                      BUTTONS

Komponenten

1. Keyboard Klasse

Funktion: Verwaltet die 4×3 Matrix-Tastatur mit Entprellung

class Keyboard {
    public:
        Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol);
        void begin();
        void update();
        int getQueueLength();
        Key getQueue(uint8_t index);
    // ...
};

Merkmale:

  • Debounce-Zeit: 20ms
  • Max. 10 gleichzeitig aktive Tasten
  • FIFO-Queue für Tastenreihenfolge
  • Rückmeldung als Key(row, col) Struktur

2. CV Klasse

Funktion: Verwaltet DAC-Ausgänge über I2C (MCP4728)

class CV {
    public:
        CV(Adafruit_MCP4728 *dac, TwoWire *wire, uint8_t nCV, 
           MCP4728_channel_t *cvChannelMap, uint16_t *keyToVoltage, 
           uint8_t row, uint8_t col);
        bool begin(uint8_t pinSDA, uint8_t pinSCL);
        void setVoltage(uint8_t cvIndex, Key k);
        void setVoltage(uint8_t cvIndex, uint16_t mV);
        void clearAll();
    // ...
};

Merkmale:

  • 2 CV-Kanäle (A, B)
  • I2C-Kommunikation (Pins 15, 16)
  • Voltage-Mapping von Tasten
  • Range: 0-4096mV (12-Bit)

3. SequencerBlock Klasse

Funktion: Speichert und spielt Sequenzen auf 2 Kanälen

class SequencerBlock {
    public:
        SequencerBlock(uint16_t maxDurationMS, uint16_t minStepDurationMS);
        
        // Recording
        void startRecord();
        void stopRecord();
        void addStep(uint16_t voltage_ch1, uint16_t voltage_ch2);
        
        // Playback
        void startPlay();
        void stopPlay();
        void update();  // Must be called regularly
        void setLoop(bool loop);
        
        // Status
        uint8_t getStepCount();
        uint16_t getCurrentVoltageCh1();
        uint16_t getCurrentVoltageCh2();
    // ...
};

Merkmale:

  • Max. 128 Steps pro Sequenz
  • Max. 30 Sekunden Aufnahmetime
  • Dual-Channel Recording
  • Loop-Funktion
  • Automatisches Time-Limit

4. Button-Verarbeitung

3-Mode Play-Button-System:

Click Aktion Zustand
1x Play (kein Loop) Spielt einmalig ab
2x Loop aktivieren Endlosschleife
3x Stop Stoppt Wiedergabe

Record-Button: Toggle zwischen Recording starten/stoppen


Speicheranalyse

RAM:   [=         ]   6.5% (used 21176 bytes from 327680 bytes)
Flash: [=         ]   8.5% (used 283165 bytes from 3342336 bytes)

RAM-Verbrauch

Komponente Größe Menge Gesamt Notizen
SequencerBlock #1 ~550 B 1x 550 B 128 Steps × 6 Bytes + Variablen
SequencerBlock #2 ~550 B 1x 550 B 128 Steps × 6 Bytes + Variablen
Keyboard Objekt ~130 B 1x 130 B 8×8 Bool Arrays + Pointer
CV Objekt ~50 B 1x 50 B DAC-Pointer + Config
keyToVoltage Array 24 B 1x 24 B 12 Keys × uint16_t
Button States ~50 B 1x 50 B 4 Buttons × ~12 Bytes
Lokale Variablen ~100 B 1x 100 B Loop-Variablen
GESAMT (Schätzung) - - ~1.5 KB -

Flash-Verbrauch

Komponente Größe
Arduino/Wire Libraries ~150 KB
Adafruit_MCP4728 ~20 KB
Firmware Code ~80 KB
Bootloader ~60 KB
GESAMT (Schätzung) ~310 KB

Sequenzen-Speicher Detail

struct DualVoltageDurationPair {
    uint16_t voltage_ch1;  // 2 Bytes
    uint16_t voltage_ch2;  // 2 Bytes
    uint16_t duration;     // 2 Bytes
};  // = 6 Bytes pro Step

// Berechnung:
// - N_MAX_SEQUENCE_STEPS = 128
// - 128 Steps × 6 Bytes = 768 Bytes pro Sequenz
// - 2 Sequenzer = 1536 Bytes (1.5 KB)

Optimierungspotential

Feature Größenänderung Status
256 Steps statt 128 +768 B Problemlos möglich
60s statt 30s Limit 0 B Kostenloses Upgrade
4 Sequenzer statt 2 +3.3 KB Problemlos möglich
8×8 Tastatur statt 4×3 ~ +30 B Kaum Mehraufwand

Verwendung

Grundlegende Konfiguration

include/FIRMWARE_DEF.h:

#define N_KEYBOARD_ROW  4      // Keyboard Reihen
#define N_KEYBOARD_COL  3      // Keyboard Spalten
#define N_CV_GATES      2      // CV-Ausgänge
#define N_SB            2      // Sequencer

// I2C Pins
#define PIN_SDA         15
#define PIN_SCL         16

// Keyboard Pins (Reihen)
#define PIN_K_R0        7
#define PIN_K_R1        8
#define PIN_K_R2        9
#define PIN_K_R3        10

// Keyboard Pins (Spalten)
#define PIN_K_C0        1
#define PIN_K_C1        2
#define PIN_K_C2        4

Spannung-Mapping

src/main.cpp:

// Voltage für jede Tastaturposition (in 1/12V = 83mV Schritten = 1 Halbtonschritt)
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
    1*83, 5*83, 9*83,    // Row 0: C, E, G
    2*83, 6*83, 10*83,   // Row 1: D, F, A
    3*83, 7*83, 11*83,   // Row 2: E, G, B
    4*83, 8*83, 12*83    // Row 3: F, A, C (Oktave)
};

Hauptablauf

Main Loop Flowchart

START
  ↓
SETUP (Initialisierung)
  ↓
┌─── MAIN LOOP ───────────────────────┐
│                                     │
├─ Keyboard Update                    │
│  (Tasten auslesen)                  │
│                                     │
├─ Button Handler                     │
│  (Record/Play Buttons)              │
│                                     │
├─ Sequencer Update                   │
│  (sb1 & sb2 Playback)               │
│                                     │
├─ Spannungen bestimmen               │
│  voltage_ch1 = Queue[0]             │
│  voltage_ch2 = Queue[1]             │
│                                     │
├─ Recording Check                    │
│  IF Recording: addStep()            │
│                                     │
├─ Output Priority                    │
│  IF sb1.playing() → Output SEQ1     │
│  ELSE IF sb2.playing() → Output SEQ2│
│  ELSE → Output Live                 │
│                                     │
├─ Time-Limit Check                   │
│  IF Limit reached: Stop Record      │
│                                     │
├─ Delay 10ms                         │
│                                     │
└─────────────────────────────────────┘
     ↓
  [Loop zurück]

State Machine

State Beschreibung Aktion
IDLE Leerlauf Liest Tasten, gibt Live-Spannungen aus
REC Recording aktiv Speichert Sequenzen, überwacht Zeit-Limits
PLAY Playback aktiv Gibt Sequenzen aus, verwaltet Step-Übergänge
LOOP Endlosschleife Wiederholt Sequenz nahtlos

Ausgabe-Prioritätssystem

1. Sequencer 1 Playing?
   ├─ JA → Gebe SEQ1 Voltages aus (höchste Priorität)
   └─ NEIN ↓

2. Sequencer 2 Playing?
   ├─ JA → Gebe SEQ2 Voltages aus (zweite Priorität)
   └─ NEIN ↓

3. Live Input
   └─ Gebe Tasten-Voltages aus (Standard)

Dies ermöglicht nahtlose Übergänge und verhindert Konflikte.


Code-Beispiele

Beispiel 1: Live-Modus (main.cpp.1)

Direkte Tastatur-zu-CV Verbindung ohne Sequencer:

void loop() {
  keyboard.update();
  
  int n = keyboard.getQueueLength();
  
  if(n > 0) {
    for(int i = 0; (i < N_CV_GATES) && (i < n); i++) {
      Key k = keyboard.getQueue(i);
      cv.setVoltage(i, k);  // Taste direkt auf CV ausgeben
    }
  } else {
    cv.clearAll();  // Keine Taste → 0V
  }
  
  delay(50);
}

Beispiel 2: Dual-Channel Sequencer (main.cpp)

Vollständiger Sequencer mit 2 unabhängigen Kanälen:

void loop() {
  keyboard.update();
  handleSequencerButtons();
  
  sb1.update();
  sb2.update();
  
  // ... Voltage determination ...
  
  // Recording
  if(sb1.isRecording()) {
    sb1.addStep(voltage_ch1, voltage_ch2);
  }
  if(sb2.isRecording()) {
    sb2.addStep(voltage_ch1, voltage_ch2);
  }
  
  // Output mit Priorität
  if(sb1.isPlaying()) {
    cv.setVoltage(0, sb1.getCurrentVoltageCh1());
    cv.setVoltage(1, sb1.getCurrentVoltageCh2());
  }
  else if(sb2.isPlaying()) {
    cv.setVoltage(0, sb2.getCurrentVoltageCh1());
    cv.setVoltage(1, sb2.getCurrentVoltageCh2());
  }
  else {
    cv.setVoltage(0, voltage_ch1);
    cv.setVoltage(1, voltage_ch2);
  }
  
  delay(10);
}

Dateistruktur

project/
├── include/
│   ├── FIRMWARE.h          # Klassen-Definitionen
│   └── FIRMWARE_DEF.h      # Konstanten & Pin-Definitionen
│   
└── src/
    ├── main.cpp            # Dual-Channel Sequencer Beispiel
    ├── main.cpp.1          # Live-Modus Beispiel
    ├── main.cpp.2          # Dual-Channel ohne Sequencer
    └── FIRMWARE.cpp        # Implementierungen


Technische Spezifikationen

Timings

Parameter Wert Funktion
Keyboard Debounce 20ms Anti-Prellen Verzögerung
Button Debounce 50ms Button Anti-Prellen
Main Loop Delay 10ms Update-Rate
Max Recording 30s Zeit-Limit pro Sequenz
Min Step Duration 50ms Minimale Step-Länge

Voltage-Mapping

Verwendet gleichmäßige 83mV Schritte (1/12 Oktave):

Key (1,0) = 1 × 83mV  = 83mV   (C)
Key (1,1) = 5 × 83mV  = 415mV  (E)
Key (1,2) = 9 × 83mV  = 747mV  (G)
... etc
Key (4,2) = 12 × 83mV = 996mV  (C')

Zuletzt aktualisiert: 2025-11-30