Files
audio-synth/dev/digital/Firmware_TEST/README.md
2025-12-03 13:19:34 +01:00

456 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Firmware
## Inhaltsverzeichnis
1. [Projektübersicht](#projektübersicht)
2. [Systemarchitektur](#systemarchitektur)
3. [Komponenten](#komponenten)
4. [Speicheranalyse](#speicheranalyse)
5. [Installation](#installation)
6. [Verwendung](#verwendung)
7. [Hauptablauf](#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
```cpp
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)
```cpp
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
```cpp
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
```bash
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
```cpp
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`:
```cpp
#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`:
```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:
```cpp
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:
```cpp
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