Compare commits
10 Commits
V2.6
...
99c2542890
| Author | SHA1 | Date | |
|---|---|---|---|
| 99c2542890 | |||
| 855fd01821 | |||
|
|
f86db9c917 | ||
| b106859252 | |||
| dac90a977b | |||
| ce4e6cb536 | |||
|
|
ae7b6f6114 | ||
|
|
aae20f701c | ||
|
|
173c031371 | ||
|
|
9705029f0a |
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(129).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(130).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(131).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(132).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(133).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(134).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(135).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(136).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(137).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(138).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(139).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(140).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(141).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(142).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(143).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(144).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(145).SchDoc.Zip
Normal file
BIN
dev/analog/ETOTH-Amp_LM386/History/Amp_LM386.~(146).SchDoc.Zip
Normal file
@@ -1,98 +1,32 @@
|
||||
ETOTH-Amp_LM386
|
||||
*SPICE Netlist generated by Advanced Sim server on 21.11.2025 08:22:05
|
||||
*SPICE Netlist generated by Advanced Sim server on 28.11.2025 11:11:11
|
||||
.options MixedSimGenerated
|
||||
|
||||
*Schematic Netlist:
|
||||
CC1 NetC1_1 NetC1_2 47nF
|
||||
CC_VCM1 NetC_VCM1_1 GND 47uF
|
||||
CC_VCM2 VAP NetC_VCM1_1 47uF
|
||||
CCblock NetC1_2 NetCblock_2 220uF
|
||||
CCblock1 NetCblock1_1 NetCblock1_2 47uF
|
||||
XIC1C NetIC1_10 NetC_VCM1_1 VAP GND NetIC1_8 TL074
|
||||
XIC2 NetIC2_1 GND NetCblock1_2 GND NetC1_2 VAP NetIC2_7 NetIC2_8 LM386
|
||||
LL_Speaker GND NetL_Speaker_2 0.1mH
|
||||
RR1 NetC1_1 GND 10R
|
||||
CC_DCBLOCK_IN IN NetC_DCBLOCK_IN_2 10uF
|
||||
CC_DCBLOCK_OUT NetC_DCBLOCK_OUT_1 OUT 220uF
|
||||
XIC1A NetIC1_1 0 NetIC1_3 0 NetC_DCBLOCK_OUT_1 VAP NetIC1_7 NetIC1_8 lm386
|
||||
XIC1B NetIC1_1 0 NetIC1_3 0 NetC_DCBLOCK_OUT_1 VAP NetIC1_7 NetIC1_8 lm386
|
||||
LL_Speaker 0 NetL_Speaker_2 0.1mH
|
||||
RR_POTA 0 NetR_POT_2 {10k * {POS}}
|
||||
RR_POTB NetR_POT_2 NetR_POT_3 {10k - (10k * {POS})}
|
||||
RR_rs1 NetIC1_10 VAP 470k
|
||||
RR_rs2 GND NetIC1_10 470k
|
||||
RR_Speaker NetL_Speaker_2 NetCblock_2 8R
|
||||
RR_static1 NetCblock1_1 NetR_POT_2 9400R
|
||||
RR_static2 0 NetCblock1_1 600R
|
||||
QT_rsN GND NetIC1_8 NetC_VCM1_1 2N2907
|
||||
QT_rsP VAP NetIC1_8 NetC_VCM1_1 2N2222
|
||||
VU_q VAP GND 10V
|
||||
VU_VCM_CURRENT 0 NetC_VCM1_1 0
|
||||
VUin NetR_POT_3 0 DC 0 SIN(0 2 440Hz 0 0 0) AC 1 0
|
||||
RR_POTB NetR_POT_2 NetC_DCBLOCK_IN_2 {10k - (10k * {POS})}
|
||||
RR_Speaker NetL_Speaker_2 OUT 8R
|
||||
RR_static1 NetIC1_3 NetR_POT_2 100k
|
||||
RR_static2 0 NetIC1_3 10k
|
||||
VU_q VAP 0 10V
|
||||
VUin IN 0 DC 0 SIN(5 2 220 0 0 0) AC 1 0
|
||||
|
||||
.PLOT TRAN {v(R_Speaker)+v(L_Speaker)} =PLOT(1) =AXIS(1) =NAME(U_speaker) =UNITS(V) =RGB(0, 255, 0)
|
||||
.PLOT TRAN {i(R_Speaker)} =PLOT(2) =AXIS(1) =NAME(I_speaker) =UNITS(A)
|
||||
.PLOT TRAN {p(R_Speaker)} =PLOT(3) =AXIS(1) =NAME(P_speaker) =UNITS(W)
|
||||
.PLOT TRAN {i(U_VCM_CURRENT)} =PLOT(4) =AXIS(1) =NAME(I_VCM) =UNITS(A)
|
||||
.PLOT TRAN {v(VAP)} =PLOT(1) =AXIS(1) =RGB(255, 0, 0)
|
||||
.PLOT TRAN {v(GND)} =PLOT(1) =AXIS(1) =RGB(0, 0, 255)
|
||||
.PLOT TRAN {i(Cblock1)} =PLOT(5) =AXIS(1) =NAME(I_C_in) =UNITS(A)
|
||||
.PLOT TRAN {v(Cblock1)} =PLOT(5) =AXIS(2) =NAME(U_C_in) =UNITS(V)
|
||||
.PLOT TRAN {i(Cblock1)} =PLOT(5) =AXIS(1) =NAME(I_C_out) =UNITS(A)
|
||||
.PLOT TRAN {v(Cblock1)} =PLOT(5) =AXIS(2) =NAME(U_C_out) =UNITS(V)
|
||||
.PLOT TRAN {v(IN)} =PLOT(1) =AXIS(1) =NAME(U_IN) =UNITS(V) =RGB(0, 0, 255)
|
||||
.PLOT TRAN {v(OUT)} =PLOT(2) =AXIS(1) =NAME(U_OUT) =UNITS(V) =RGB(255, 153, 0)
|
||||
|
||||
.OPTIONS METHOD=GEAR MAXORD=2
|
||||
*Selected Circuit Analyses:
|
||||
.TRAN 45.45u 22.73m 0 45.45u
|
||||
.TRAN 90.91u 22.73m 0 90.91u
|
||||
|
||||
*Global Parameters:
|
||||
.PARAM POS={1}
|
||||
|
||||
*Models and Subcircuits:
|
||||
*TL074
|
||||
*Quad LoNoise JFETInput OpAmp pkg:DIP14
|
||||
*+ (A:3,2,4,11,1)(B:5,6,4,11,7)(C:10,9,4,11,8)(D:12,13,4,11,14)
|
||||
* Connections:
|
||||
* Non-Inverting Input
|
||||
* | Inverting Input
|
||||
* | | Positive Power Supply
|
||||
* | | | Negative Power Supply
|
||||
* | | | | Output
|
||||
* | | | | |
|
||||
.SUBCKT TL074 1 2 3 4 5
|
||||
C1 11 12 3.498E-12
|
||||
C2 6 7 15E-12
|
||||
DC 5 53 DX
|
||||
DE 54 5 DX
|
||||
DLP 90 91 DX
|
||||
DLN 92 90 DX
|
||||
DP 4 3 DX
|
||||
BGND 99 0 V=V(3)*.5 + V(4)*.5
|
||||
BB 7 99 I=I(VB)*4.715E6 - I(VC)*5E6 + I(VE)*5E6 +
|
||||
+ I(VLP)*5E6 - I(VLN)*5E6
|
||||
GA 6 0 11 12 282.8E-6
|
||||
GCM 0 6 10 99 8.942E-9
|
||||
ISS 3 10 DC 195E-6
|
||||
HLIM 90 0 VLIM 1K
|
||||
J1 11 2 10 JX
|
||||
J2 12 1 10 JX
|
||||
R2 6 9 100E3
|
||||
RD1 4 11 3.536E3
|
||||
RD2 4 12 3.536E3
|
||||
RO1 8 5 150
|
||||
RO2 7 99 150
|
||||
RP 3 4 2.143E3
|
||||
RSS 10 99 1.026E6
|
||||
VB 9 0 DC 0
|
||||
VC 3 53 DC 2.2
|
||||
VE 54 4 DC 2.2
|
||||
VLIM 7 8 DC 0
|
||||
VLP 91 0 DC 25
|
||||
VLN 0 92 DC 25
|
||||
.MODEL DX D(IS=800E-18)
|
||||
.MODEL JX PJF(IS=15E-12 BETA=270.1E-6 VTO=-1)
|
||||
.ENDS TL074
|
||||
|
||||
*TopSPICE library: Models\MISCSEMI.MDB
|
||||
*PART NUMBER: LM386
|
||||
*MODEL NAME: LM386
|
||||
*SYMBOL: X8PIN
|
||||
*
|
||||
*LM386 Audio power amplifier
|
||||
* /*
|
||||
* 1. The following model behavior shows good agreement with the
|
||||
@@ -174,18 +108,4 @@ q14 out 10018 gnd ddnpn 100
|
||||
+ Tf=1n Itf=1 Xtf=0 Vtf=10)
|
||||
.ends LM386
|
||||
|
||||
*2N2907 MCE 5-27-97
|
||||
*Ref: Motorola Small-Signal Device databook, Q4/94
|
||||
*Si 400mW 40V 600mA 250MHz GenPurp pkg:TO-18 3,2,1
|
||||
.MODEL 2N2907 PNP (IS=60.9F NF=1 BF=260 VAF=114 IKF=0.36 ISE=30.2P NE=2
|
||||
+ BR=4 NR=1 VAR=20 IKR=0.54 RE=85.8M RB=0.343 RC=34.3M XTB=1.5
|
||||
+ CJE=27.6P VJE=1.1 MJE=0.5 CJC=15.3P VJC=0.3 MJC=0.3 TF=636P TR=442N)
|
||||
|
||||
*2N2222 MCE 5-20-97
|
||||
*Ref: Motorola Small-Signal Device Databook, Q4/94
|
||||
*Si 400mW 30V 800mA 300MHz GenPurp pkg:TO-18 3,2,1
|
||||
.MODEL 2N2222 NPN (IS=81.2F NF=1 BF=195 VAF=98.6 IKF=0.48 ISE=53.7P NE=2
|
||||
+ BR=4 NR=1 VAR=20 IKR=0.72 RE=64.4M RB=0.258 RC=25.8M XTB=1.5
|
||||
+ CJE=89.5P VJE=1.1 MJE=0.5 CJC=28.9P VJC=0.3 MJC=0.3 TF=530P TR=368N)
|
||||
|
||||
.END
|
||||
@@ -7,4 +7,4 @@ From : Project [ETOTH-Amp_LM386.PrjPcb]
|
||||
Files Generated : 1
|
||||
Documents Printed : 0
|
||||
|
||||
Finished Output Generation At 08:00:57 On 21.11.2025
|
||||
Finished Output Generation At 11:00:20 On 28.11.2025
|
||||
|
||||
@@ -7,4 +7,4 @@ From : Project [TRI-SQR-VCO_OTA_SS.PrjPcb]
|
||||
Files Generated : 1
|
||||
Documents Printed : 0
|
||||
|
||||
Finished Output Generation At 21:27:47 On 23.10.2025
|
||||
Finished Output Generation At 12:23:27 On 28.11.2025
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
TRI-SQR-VCO_OTA_SS
|
||||
*SPICE Netlist generated by Advanced Sim server on 18.11.2025 14:07:43
|
||||
*SPICE Netlist generated by Advanced Sim server on 28.11.2025 12:29:53
|
||||
.options MixedSimGenerated
|
||||
|
||||
*Schematic Netlist:
|
||||
@@ -29,6 +29,8 @@ RR_A 0 U_SQR_OTA 3.63k
|
||||
RR_CV NetR_CV_1 NetIC2_9 59.941k
|
||||
RR_E NetC_an_2 NetR_E_2 10k
|
||||
RR_lambda_T NetIC2_9 U_C 1.1k
|
||||
RR_offset_1 NetR_CV_1 GND 10k
|
||||
RR_offset_2 VAP NetR_CV_1 10k
|
||||
RR_PWM_a GND NetIC3_6 15k
|
||||
RR_PWM_b NetIC3_6 VAP 10k
|
||||
RR_PWM_c U_PWM NetIC3_7 1k
|
||||
@@ -55,15 +57,17 @@ VU_var NetR_CV_1 0 1
|
||||
.PLOT TRAN {v(U_TRI)} =PLOT(2) =AXIS(1) =NAME(U_TRI) =UNITS(V)
|
||||
.PLOT TRAN {v(U_SAW)} =PLOT(3) =AXIS(1) =NAME(U_SAW) =UNITS(V)
|
||||
.PLOT TRAN {v(U_PWM)} =PLOT(4) =AXIS(1) =NAME(U_PWM) =UNITS(V)
|
||||
.PLOT TRAN {i(U_mess)} =PLOT(5) =AXIS(1) =NAME(I_GND) =UNITS(A)
|
||||
.PLOT TRAN {v(U_in)} =PLOT(2) =AXIS(1) =NAME(U_in) =UNITS(V)
|
||||
.PLOT TRAN {v(U_C)} =PLOT(5) =AXIS(1) =NAME(U_C) =UNITS(V)
|
||||
|
||||
.OPTIONS ABSTOL=1e-10 RELTOL=1e-2 VNTOL=1e-4 METHOD=GEAR MAXORD=2
|
||||
*Selected Circuit Analyses:
|
||||
.TRAN 25u 20m 5m 25u UIC
|
||||
.CONTROL
|
||||
SWEEP R_offset_2 LIST 10k 20k 30k
|
||||
.ENDC
|
||||
|
||||
*Global Parameters:
|
||||
.PARAM POS=0
|
||||
.PARAM POS={0}
|
||||
|
||||
*Models and Subcircuits:
|
||||
* A dual opamp ngspice model
|
||||
|
||||
1
dev/digital/ESP32-S3/ESP32-S3.Harness
Normal file
@@ -0,0 +1 @@
|
||||
SB=SB1.R,SB1.P,SB2.R,SB2.P
|
||||
@@ -66,6 +66,23 @@ DItemRevisionGUID=
|
||||
GenerateClassCluster=0
|
||||
DocumentUniqueId=QMXNWCKL
|
||||
|
||||
[Document2]
|
||||
DocumentPath=ESP32-S3.Harness
|
||||
AnnotationEnabled=1
|
||||
AnnotateStartValue=1
|
||||
AnnotationIndexControlEnabled=0
|
||||
AnnotateSuffix=
|
||||
AnnotateScope=All
|
||||
AnnotateOrder=-1
|
||||
DoLibraryUpdate=1
|
||||
DoDatabaseUpdate=1
|
||||
ClassGenCCAutoEnabled=1
|
||||
ClassGenCCAutoRoomEnabled=0
|
||||
ClassGenNCAutoScope=None
|
||||
DItemRevisionGUID=
|
||||
GenerateClassCluster=0
|
||||
DocumentUniqueId=
|
||||
|
||||
[Parameter1]
|
||||
Name=ProjectTitle
|
||||
Value=ESP32-S3
|
||||
|
||||
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(20).SchDoc.Zip
Normal file
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(21).SchDoc.Zip
Normal file
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(22).SchDoc.Zip
Normal file
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(3).PrjPcb.Zip
Normal file
456
dev/digital/Firmware_TEST/README.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 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 (keine 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
|
||||
@@ -12,13 +12,11 @@
|
||||
#ifndef FIRMWARE_H
|
||||
#define FIRMWARE_H
|
||||
|
||||
#define N_MAX_QUEUE 10
|
||||
#define N_MAX_ROWS 8
|
||||
#define N_MAX_COLS 8
|
||||
#define MS_DEBOUNCE 20
|
||||
|
||||
#define N_MAX_DAC_CH 4
|
||||
#define N_MAX_SEQUENCE_STEPS 128
|
||||
#define N_MAX_QUEUE 10
|
||||
#define N_MAX_ROWS 8
|
||||
#define N_MAX_COLS 8
|
||||
#define MS_DEBOUNCE 20
|
||||
#define N_MAX_DAC_CH 4
|
||||
|
||||
struct Key
|
||||
{
|
||||
@@ -26,9 +24,10 @@ struct Key
|
||||
int col;
|
||||
};
|
||||
|
||||
struct voltageDurationPair
|
||||
struct DualVoltageDurationPair
|
||||
{
|
||||
uint16_t voltage;
|
||||
uint16_t voltage_ch1;
|
||||
uint16_t voltage_ch2;
|
||||
uint16_t duration;
|
||||
};
|
||||
|
||||
@@ -93,18 +92,18 @@ class CV
|
||||
class SequencerBlock
|
||||
{
|
||||
public:
|
||||
SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS);
|
||||
SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount);
|
||||
|
||||
// Aufnahme-Funktionen
|
||||
void startRecord();
|
||||
void stopRecord();
|
||||
void addStep(uint16_t voltage);
|
||||
void addStep(uint16_t voltage_ch1, uint16_t voltage_ch2);
|
||||
bool isRecording();
|
||||
|
||||
// Wiedergabe-Funktionen
|
||||
void startPlay();
|
||||
void stopPlay();
|
||||
void update(); // Muss regelmäßig aufgerufen werden
|
||||
void update();
|
||||
bool isPlaying();
|
||||
|
||||
// Sequenz-Verwaltung
|
||||
@@ -113,33 +112,44 @@ class SequencerBlock
|
||||
|
||||
// Status-Abfragen
|
||||
bool timeLimitReached();
|
||||
uint8_t getStepCount();
|
||||
uint16_t getCurrentVoltage();
|
||||
bool stepLimitReached();
|
||||
uint16_t getStepCount();
|
||||
uint16_t getCurrentVoltageCh1();
|
||||
uint16_t getCurrentVoltageCh2();
|
||||
uint16_t getTotalDuration();
|
||||
|
||||
private:
|
||||
// Sequenz-Speicher
|
||||
voltageDurationPair _sequence[N_MAX_SEQUENCE_STEPS];
|
||||
uint8_t _stepCount;
|
||||
uint8_t _currentStep;
|
||||
/*!
|
||||
* @brief Memory limiting
|
||||
* @return (uint16_t) 1024
|
||||
* @attention Increasing the value might lead to an overflow
|
||||
* @note sizeOf(DualVoltageDurationPair) = 6 Byte ==> 6 Byte * 1024 = 6144 Byte
|
||||
*/
|
||||
const static uint16_t _MAX_SEQUENCE_STEPS = 1024;
|
||||
|
||||
// Zeitverwaltung
|
||||
// Sequenz memory
|
||||
DualVoltageDurationPair _sequence[_MAX_SEQUENCE_STEPS];
|
||||
uint16_t _stepCount;
|
||||
uint16_t _currentStep;
|
||||
|
||||
// Time management
|
||||
uint16_t _maxDurationMS;
|
||||
uint16_t _timeoutMS;
|
||||
uint16_t _maxStepCount;
|
||||
unsigned long _recordStartTime;
|
||||
unsigned long _lastStepTime;
|
||||
unsigned long _playStartTime;
|
||||
unsigned long _stepStartTime;
|
||||
|
||||
// Status-Flags
|
||||
// Status flags
|
||||
bool _isRecording;
|
||||
bool _isPlaying;
|
||||
bool _loop;
|
||||
|
||||
// Letzte aufgenommene Spannung
|
||||
uint16_t _lastVoltage;
|
||||
// Last recorded Voltage: at n-th step minus one
|
||||
uint16_t _lastVoltageCh1;
|
||||
uint16_t _lastVoltageCh2;
|
||||
|
||||
// Hilfsfunktionen
|
||||
// helper functions
|
||||
void _finishCurrentStep();
|
||||
bool _canAddStep();
|
||||
};
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
// CONSTANTS DEFINITONS
|
||||
#define N_KEYBOARD_ROW 4
|
||||
#define N_KEYBOARD_COL 3
|
||||
#define N_KEYBOARD_ROW 4 // for PROD. change to 5
|
||||
#define N_KEYBOARD_COL 3 // for PROD. change to 5
|
||||
#define N_CV_GATES 2
|
||||
#define N_SB 2
|
||||
#define BAUDRATE 115200
|
||||
#define N_MAX_SEQ_STEPS 512
|
||||
// PIN DEFENTITIONS
|
||||
// I2C PINS
|
||||
#define PIN_SDA 15
|
||||
@@ -25,15 +26,22 @@
|
||||
#define PIN_K_R1 8
|
||||
#define PIN_K_R2 9
|
||||
#define PIN_K_R3 10
|
||||
#define PIN_K_R4 // NOT IN USE
|
||||
#define PIN_K_R4 11 // DEV. not in use
|
||||
#define PIN_K_C0 1
|
||||
#define PIN_K_C1 2
|
||||
#define PIN_K_C2 4
|
||||
#define PIN_K_C3 // NOT IN USE
|
||||
#define PIN_K_C4 // NOT IN USE
|
||||
#define PIN_K_C3 5 // DEV. not in use
|
||||
#define PIN_K_C4 6 // DEV. not in use
|
||||
// SEQUENCER BUTTON PINS
|
||||
#define PIN_SB_1_REC 0
|
||||
#define PIN_SB_1_PLAY 0
|
||||
#define PIN_SB_2_REC 0
|
||||
#define PIN_SB_2_PLAY 0
|
||||
#define PIN_SB_1_REC 37 // for PROD. change to 33 / not available on dev board
|
||||
#define PIN_SB_1_PLAY 38 // for PROD. change to 34 / not available on dev board
|
||||
#define PIN_SB_2_REC 35
|
||||
#define PIN_SB_2_PLAY 36
|
||||
// MISC/INFO PINS
|
||||
#define PIN_ACTIVE -1 // TODO: if any key is played return HIGH
|
||||
#define PIN_REC -1 // TODO: if any sb is recording return HIGH
|
||||
#define PIN_BPM -1 // TODO: get bpm through potentiometer analog value
|
||||
#define PIN_B_METRONOME -1 // TODO: button activates/deactivates bpm led output
|
||||
#define PIN_L_METRONOME -1 // TODO: led blinks according to bpm value
|
||||
|
||||
#endif
|
||||
@@ -177,15 +177,6 @@ void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
|
||||
|
||||
// ==================== CV ====================
|
||||
|
||||
/*!
|
||||
* @param dac Adafruit_MCP4728 object
|
||||
* @param wire TwoWire object
|
||||
* @param nCV Number of CV-Gates
|
||||
* @param cvChannelMap Maps CV-Gate-Index to a MCP4728 output-channel
|
||||
* @param keyToVoltage One dimensional array of size row times col
|
||||
* @param row Keyboard matrix rows
|
||||
* @param col Keyboard matrix cols
|
||||
*/
|
||||
CV::CV(Adafruit_MCP4728 *dac, TwoWire *wire, uint8_t nCV, MCP4728_channel_t *cvChannelMap, uint16_t *keyToVoltage, uint8_t row, uint8_t col)
|
||||
{
|
||||
_dac = dac;
|
||||
@@ -242,31 +233,27 @@ uint8_t CV::_getKeyToVoltageIndex(Key k)
|
||||
|
||||
// ==================== SequencerBlock ====================
|
||||
|
||||
|
||||
/*!
|
||||
* @param maxDurationMS maximum loop duration of recording in milliseconds
|
||||
* @param timeoutMS stops recording after timeout in milliseconds
|
||||
* @brief TODO
|
||||
* @param maxStepCount maximum number of steps that can be recorded
|
||||
*/
|
||||
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS)
|
||||
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount)
|
||||
{
|
||||
_maxDurationMS = maxDurationMS;
|
||||
_timeoutMS = timeoutMS;
|
||||
_maxStepCount = maxStepCount;
|
||||
_stepCount = 0;
|
||||
_currentStep = 0;
|
||||
_isRecording = false;
|
||||
_isPlaying = false;
|
||||
_loop = false;
|
||||
_lastVoltage = 0;
|
||||
_lastVoltageCh1 = 0;
|
||||
_lastVoltageCh2 = 0;
|
||||
_recordStartTime = 0;
|
||||
_lastStepTime = 0;
|
||||
_playStartTime = 0;
|
||||
_stepStartTime = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief starts sequence block recording
|
||||
*/
|
||||
void SequencerBlock::startRecord()
|
||||
{
|
||||
if(_isPlaying) stopPlay();
|
||||
@@ -275,12 +262,10 @@ void SequencerBlock::startRecord()
|
||||
_isRecording = true;
|
||||
_recordStartTime = millis();
|
||||
_lastStepTime = _recordStartTime;
|
||||
_lastVoltage = 0;
|
||||
_lastVoltageCh1 = 0xFFFF; // Ungültiger Wert zum Triggern des ersten Steps
|
||||
_lastVoltageCh2 = 0xFFFF;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief stops sequence block recording and saves it
|
||||
*/
|
||||
void SequencerBlock::stopRecord()
|
||||
{
|
||||
if(!_isRecording) return;
|
||||
@@ -289,36 +274,44 @@ void SequencerBlock::stopRecord()
|
||||
_isRecording = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief adds step to sequencer block
|
||||
* @param voltage voltage step for CV-Gate in millivolts
|
||||
*/
|
||||
void SequencerBlock::addStep(uint16_t voltage)
|
||||
void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
|
||||
{
|
||||
if(!_isRecording) return;
|
||||
if(!_canAddStep()) return;
|
||||
if(timeLimitReached())
|
||||
{
|
||||
stopRecord();
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
// Wenn sich die Spannung geändert hat, vorherigen Schritt abschließen
|
||||
if(voltage != _lastVoltage && _stepCount > 0)
|
||||
{
|
||||
_finishCurrentStep();
|
||||
}
|
||||
// Hat sich die Spannung geändert?
|
||||
bool voltageChanged = (voltage_ch1 != _lastVoltageCh1) || (voltage_ch2 != _lastVoltageCh2);
|
||||
|
||||
// Neuen Schritt beginnen oder vorhandenen aktualisieren
|
||||
if(voltage != _lastVoltage || _stepCount == 0)
|
||||
if(voltageChanged)
|
||||
{
|
||||
// Vorherigen Step abschließen (wenn vorhanden)
|
||||
if(_stepCount > 0)
|
||||
{
|
||||
_finishCurrentStep();
|
||||
}
|
||||
|
||||
// Neuen Step beginnen
|
||||
if(_canAddStep())
|
||||
{
|
||||
_sequence[_stepCount].voltage = voltage;
|
||||
_sequence[_stepCount].voltage_ch1 = voltage_ch1;
|
||||
_sequence[_stepCount].voltage_ch2 = voltage_ch2;
|
||||
_sequence[_stepCount].duration = 0;
|
||||
_stepCount++;
|
||||
|
||||
_lastStepTime = now;
|
||||
_lastVoltage = voltage;
|
||||
_lastVoltageCh1 = voltage_ch1;
|
||||
_lastVoltageCh2 = voltage_ch2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gleiche Spannung - Duration des aktuellen Steps aktualisieren
|
||||
if(_stepCount > 0)
|
||||
{
|
||||
_sequence[_stepCount - 1].duration = now - _lastStepTime;
|
||||
@@ -326,18 +319,11 @@ void SequencerBlock::addStep(uint16_t voltage)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief checks if sequencer block is recording
|
||||
* @return true or false
|
||||
*/
|
||||
bool SequencerBlock::isRecording()
|
||||
{
|
||||
return _isRecording;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief starts playing sequencer block
|
||||
*/
|
||||
void SequencerBlock::startPlay()
|
||||
{
|
||||
if(_stepCount == 0) return;
|
||||
@@ -349,19 +335,12 @@ void SequencerBlock::startPlay()
|
||||
_stepStartTime = _playStartTime;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief stops playing sequencer block
|
||||
*/
|
||||
void SequencerBlock::stopPlay()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_currentStep = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief updates sequencer block
|
||||
* @attention Has to be called every cycle!
|
||||
*/
|
||||
void SequencerBlock::update()
|
||||
{
|
||||
if(!_isPlaying || _stepCount == 0) return;
|
||||
@@ -395,44 +374,31 @@ void SequencerBlock::update()
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief checks if sequencer block is playing
|
||||
* @return true or false
|
||||
*/
|
||||
bool SequencerBlock::isPlaying()
|
||||
{
|
||||
return _isPlaying;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief clears recording of sequencer block
|
||||
*/
|
||||
void SequencerBlock::clear()
|
||||
{
|
||||
_stepCount = 0;
|
||||
_currentStep = 0;
|
||||
_lastVoltage = 0;
|
||||
_lastVoltageCh1 = 0;
|
||||
_lastVoltageCh2 = 0;
|
||||
|
||||
for(uint8_t i = 0; i < N_MAX_SEQUENCE_STEPS; i++)
|
||||
for(uint8_t i = 0; i < _MAX_SEQUENCE_STEPS; i++)
|
||||
{
|
||||
_sequence[i].voltage = 0;
|
||||
_sequence[i].voltage_ch1 = 0;
|
||||
_sequence[i].voltage_ch2 = 0;
|
||||
_sequence[i].duration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief sets configuation for looping over the recording
|
||||
* @param loop if set to true, saved recording gets played in a loop
|
||||
*/
|
||||
void SequencerBlock::setLoop(bool loop)
|
||||
{
|
||||
_loop = loop;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief checks if the recording time limit has been reached
|
||||
* @return true of false
|
||||
*/
|
||||
bool SequencerBlock::timeLimitReached()
|
||||
{
|
||||
if(!_isRecording) return false;
|
||||
@@ -443,31 +409,32 @@ bool SequencerBlock::timeLimitReached()
|
||||
return (elapsed >= _maxDurationMS);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief returns the currently recoreded steps
|
||||
* @return uint8_t between 0 and 128
|
||||
*/
|
||||
uint8_t SequencerBlock::getStepCount()
|
||||
bool SequencerBlock::stepLimitReached()
|
||||
{
|
||||
return (_stepCount >= _maxStepCount);
|
||||
}
|
||||
|
||||
uint16_t SequencerBlock::getStepCount()
|
||||
{
|
||||
return _stepCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief if sequencer is playing, returns the current voltage level
|
||||
* @return uint16_t voltage range for CV
|
||||
*/
|
||||
uint16_t SequencerBlock::getCurrentVoltage()
|
||||
uint16_t SequencerBlock::getCurrentVoltageCh1()
|
||||
{
|
||||
if(!_isPlaying || _stepCount == 0) return 0;
|
||||
if(_currentStep >= _stepCount) return 0;
|
||||
|
||||
return _sequence[_currentStep].voltage;
|
||||
return _sequence[_currentStep].voltage_ch1;
|
||||
}
|
||||
|
||||
uint16_t SequencerBlock::getCurrentVoltageCh2()
|
||||
{
|
||||
if(!_isPlaying || _stepCount == 0) return 0;
|
||||
if(_currentStep >= _stepCount) return 0;
|
||||
|
||||
return _sequence[_currentStep].voltage_ch2;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief gets the length of the recording in the block
|
||||
* @return uint16_t time in milliseconds
|
||||
*/
|
||||
uint16_t SequencerBlock::getTotalDuration()
|
||||
{
|
||||
uint16_t total = 0;
|
||||
@@ -483,18 +450,15 @@ void SequencerBlock::_finishCurrentStep()
|
||||
if(_stepCount == 0) return;
|
||||
|
||||
unsigned long now = millis();
|
||||
_sequence[_stepCount - 1].duration = now - _lastStepTime;
|
||||
uint16_t duration = now - _lastStepTime;
|
||||
|
||||
// Timeout prüfen - wenn zu lange keine Änderung, Schritt nicht hinzufügen
|
||||
if(_sequence[_stepCount - 1].duration < _timeoutMS)
|
||||
{
|
||||
_stepCount++;
|
||||
}
|
||||
_sequence[_stepCount - 1].duration = duration;
|
||||
}
|
||||
|
||||
bool SequencerBlock::_canAddStep()
|
||||
{
|
||||
if(_stepCount >= N_MAX_SEQUENCE_STEPS) return false;
|
||||
if(_stepCount >= _maxStepCount) return false;
|
||||
if(_stepCount >= _MAX_SEQUENCE_STEPS) return false;
|
||||
if(timeLimitReached()) return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,35 +1,31 @@
|
||||
/*
|
||||
* Example Code Three
|
||||
* with Sequencer
|
||||
*
|
||||
* Bedienung:
|
||||
* - Keyboard-Tasten: CV-Ausgabe direkt oder Recording
|
||||
* - PIN_SB_1_REC: Sequencer 1 Record Start/Stop
|
||||
* - PIN_SB_1_PLAY: Sequencer 1 Play/Stop
|
||||
* - PIN_SB_2_REC: Sequencer 2 Record Start/Stop
|
||||
* - PIN_SB_2_PLAY: Sequencer 2 Play/Stop
|
||||
* Example Code Three - Dual Channel Sequencer
|
||||
* TODO:
|
||||
- add predefined sequence of voltage (e.g. for usage as startup sound)
|
||||
- implement INFO and MISC pins form file FIRMWARE_DEF.h
|
||||
*/
|
||||
#include "FIRMWARE_DEF.h"
|
||||
#include "FIRMWARE.h"
|
||||
|
||||
static byte pins_keyboard_row[N_KEYBOARD_ROW] = {PIN_K_R0, PIN_K_R1, PIN_K_R2, PIN_K_R3};
|
||||
static byte pins_keyboard_col[N_KEYBOARD_COL] = {PIN_K_C0, PIN_K_C1, PIN_K_C2};
|
||||
byte pins_keyboard_row[N_KEYBOARD_ROW] = {PIN_K_R0, PIN_K_R1, PIN_K_R2, PIN_K_R3};
|
||||
byte pins_keyboard_col[N_KEYBOARD_COL] = {PIN_K_C0, PIN_K_C1, PIN_K_C2};
|
||||
|
||||
Keyboard keyboard(N_KEYBOARD_ROW, N_KEYBOARD_COL, pins_keyboard_row, pins_keyboard_col);
|
||||
|
||||
Adafruit_MCP4728 MCP4728;
|
||||
MCP4728_channel_t cvMap[N_CV_GATES] = {MCP4728_CHANNEL_A, MCP4728_CHANNEL_B};
|
||||
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = { /* 83mV = 1/12V */
|
||||
1*83, 5*83, 9*83,
|
||||
2*83, 6*83, 10*83,
|
||||
3*83, 7*83, 11*83,
|
||||
4*83, 8*83, 12*83
|
||||
1*83, 5*83, 9*83, /* ROW 1: B D Fis */
|
||||
2*83, 6*83, 10*83, /* ROW 2: H Dis G */
|
||||
3*83, 7*83, 11*83, /* ROW 3: C E Gis */
|
||||
4*83, 8*83, 12*83 /* ROW 4: Cis F A' */
|
||||
};
|
||||
|
||||
CV cv(&MCP4728, &Wire, N_CV_GATES, cvMap, keyToVoltage, N_KEYBOARD_ROW, N_KEYBOARD_COL);
|
||||
|
||||
SequencerBlock sb1(30000, 250); // 30 Sekunden max, 250ms Timeout
|
||||
SequencerBlock sb2(30000, 250);
|
||||
// Sequencer 30s max, 512 max Steps
|
||||
SequencerBlock sb1(30000, N_MAX_SEQ_STEPS);
|
||||
SequencerBlock sb2(30000, N_MAX_SEQ_STEPS);
|
||||
|
||||
// Button States
|
||||
struct ButtonState {
|
||||
@@ -45,10 +41,9 @@ ButtonState btn_sb2_play;
|
||||
|
||||
const unsigned long DEBOUNCE_DELAY = 50;
|
||||
|
||||
// Hilfsfunktion zum Lesen eines Buttons mit Debouncing
|
||||
bool readButton(byte pin, ButtonState &state)
|
||||
{
|
||||
bool reading = digitalRead(pin) == LOW; // LOW = gedrückt (mit Pull-Up)
|
||||
bool reading = digitalRead(pin) == HIGH;
|
||||
bool buttonPressed = false;
|
||||
|
||||
if(reading != state.last)
|
||||
@@ -61,7 +56,7 @@ bool readButton(byte pin, ButtonState &state)
|
||||
if(reading != state.current)
|
||||
{
|
||||
state.current = reading;
|
||||
if(state.current == true) // Button wurde gerade gedrückt
|
||||
if(state.current == true)
|
||||
{
|
||||
buttonPressed = true;
|
||||
}
|
||||
@@ -74,10 +69,10 @@ bool readButton(byte pin, ButtonState &state)
|
||||
|
||||
void initButtons()
|
||||
{
|
||||
pinMode(PIN_SB_1_REC, INPUT_PULLUP);
|
||||
pinMode(PIN_SB_1_PLAY, INPUT_PULLUP);
|
||||
pinMode(PIN_SB_2_REC, INPUT_PULLUP);
|
||||
pinMode(PIN_SB_2_PLAY, INPUT_PULLUP);
|
||||
pinMode(PIN_SB_1_REC, INPUT_PULLDOWN);
|
||||
pinMode(PIN_SB_1_PLAY, INPUT_PULLDOWN);
|
||||
pinMode(PIN_SB_2_REC, INPUT_PULLDOWN);
|
||||
pinMode(PIN_SB_2_PLAY, INPUT_PULLDOWN);
|
||||
|
||||
btn_sb1_rec.current = false;
|
||||
btn_sb1_rec.last = false;
|
||||
@@ -111,23 +106,54 @@ void handleSequencerButtons()
|
||||
{
|
||||
if(sb1.isPlaying()) sb1.stopPlay();
|
||||
sb1.startRecord();
|
||||
Serial.printf("\n\r[SEQ1] Recording started...");
|
||||
Serial.printf("\n\r[SEQ1] Recording started (2 channels)...");
|
||||
}
|
||||
}
|
||||
|
||||
// Sequencer 1 Play Button
|
||||
// Sequencer 1 Play Button - 3 Modi: Play / Loop / Stop
|
||||
if(readButton(PIN_SB_1_PLAY, btn_sb1_play))
|
||||
{
|
||||
if(sb1.isPlaying())
|
||||
if(!sb1.isPlaying())
|
||||
{
|
||||
sb1.stopPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback stopped");
|
||||
// Nicht am Spielen -> Starte Playback (ohne Loop)
|
||||
if(sb1.isRecording()) sb1.stopRecord();
|
||||
sb1.setLoop(false);
|
||||
sb1.startPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback started (single)\n\r\tSteps: %i, Duartion: %ims", sb1.getStepCount(), sb1.getTotalDuration());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sb1.isRecording()) sb1.stopRecord();
|
||||
sb1.startPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback started");
|
||||
// Am Spielen -> Prüfe Loop-Status
|
||||
if(!sb1.isPlaying()) // Falls schon gestoppt
|
||||
{
|
||||
// Starte neu
|
||||
sb1.setLoop(false);
|
||||
sb1.startPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback started (single)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ist am Spielen - ermittle ob Loop aktiv ist
|
||||
// Wir testen das indirekt: Wenn ein Sequencer am Ende angekommen ist
|
||||
// und noch spielt, dann muss Loop aktiv sein
|
||||
// Alternative: Wir tracken den Loop-Status selbst
|
||||
static bool seq1_loop_active = false;
|
||||
|
||||
if(!seq1_loop_active)
|
||||
{
|
||||
// 2. Klick: Loop aktivieren
|
||||
sb1.setLoop(true);
|
||||
seq1_loop_active = true;
|
||||
Serial.printf("\n\r[SEQ1] Loop activated");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3. Klick: Stop
|
||||
sb1.stopPlay();
|
||||
seq1_loop_active = false;
|
||||
Serial.printf("\n\r[SEQ1] Playback stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,23 +170,40 @@ void handleSequencerButtons()
|
||||
{
|
||||
if(sb2.isPlaying()) sb2.stopPlay();
|
||||
sb2.startRecord();
|
||||
Serial.printf("\n\r[SEQ2] Recording started...");
|
||||
Serial.printf("\n\r[SEQ2] Recording started (2 channels)...");
|
||||
}
|
||||
}
|
||||
|
||||
// Sequencer 2 Play Button
|
||||
// Sequencer 2 Play Button - 3 Modi: Play / Loop / Stop
|
||||
if(readButton(PIN_SB_2_PLAY, btn_sb2_play))
|
||||
{
|
||||
if(sb2.isPlaying())
|
||||
static bool seq2_loop_active = false;
|
||||
|
||||
if(!sb2.isPlaying())
|
||||
{
|
||||
sb2.stopPlay();
|
||||
Serial.printf("\n\r[SEQ2] Playback stopped");
|
||||
// Nicht am Spielen -> Starte Playback (ohne Loop)
|
||||
if(sb2.isRecording()) sb2.stopRecord();
|
||||
sb2.setLoop(false);
|
||||
seq2_loop_active = false;
|
||||
sb2.startPlay();
|
||||
Serial.printf("\n\r[SEQ2] Playback started (single)");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sb2.isRecording()) sb2.stopRecord();
|
||||
sb2.startPlay();
|
||||
Serial.printf("\n\r[SEQ2] Playback started");
|
||||
if(!seq2_loop_active)
|
||||
{
|
||||
// 2. Klick: Loop aktivieren
|
||||
sb2.setLoop(true);
|
||||
seq2_loop_active = true;
|
||||
Serial.printf("\n\r[SEQ2] Loop activated");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3. Klick: Stop
|
||||
sb2.stopPlay();
|
||||
seq2_loop_active = false;
|
||||
Serial.printf("\n\r[SEQ2] Playback stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,13 +218,16 @@ void setup()
|
||||
sb1.setLoop(false);
|
||||
sb2.setLoop(false);
|
||||
|
||||
Serial.printf("\n\r=== Sequencer System Started ===");
|
||||
Serial.printf("\n\r=== Dual-Channel Sequencer System Started ===");
|
||||
Serial.printf("\n\rControls:");
|
||||
Serial.printf("\n\r PIN_SB_1_REC: SEQ1 Record Start/Stop");
|
||||
Serial.printf("\n\r PIN_SB_1_PLAY: SEQ1 Play/Stop");
|
||||
Serial.printf("\n\r PIN_SB_2_REC: SEQ2 Record Start/Stop");
|
||||
Serial.printf("\n\r PIN_SB_2_PLAY: SEQ2 Play/Stop");
|
||||
Serial.printf("\n\r================================\n\r");
|
||||
Serial.printf("\n\r PIN_SB_1_REC: SEQ1 Record Start/Stop (CH1+CH2)");
|
||||
Serial.printf("\n\r PIN_SB_1_PLAY: SEQ1 Play Mode Toggle:");
|
||||
Serial.printf("\n\r 1st click: Play once");
|
||||
Serial.printf("\n\r 2nd click: Loop mode");
|
||||
Serial.printf("\n\r 3rd click: Stop");
|
||||
Serial.printf("\n\r PIN_SB_2_REC: SEQ2 Record Start/Stop (CH1+CH2)");
|
||||
Serial.printf("\n\r PIN_SB_2_PLAY: SEQ2 Play Mode (same as SEQ1)");
|
||||
Serial.printf("\n\r==============================================\n\r");
|
||||
}
|
||||
|
||||
void loop()
|
||||
@@ -195,71 +241,54 @@ void loop()
|
||||
|
||||
int n = keyboard.getQueueLength();
|
||||
|
||||
// Keyboard-Tasten verarbeiten
|
||||
// Aktuelle Spannungen für beide Kanäle ermitteln
|
||||
uint16_t voltage_ch1 = 0;
|
||||
uint16_t voltage_ch2 = 0;
|
||||
|
||||
if(n > 0)
|
||||
{
|
||||
// Alle Keyboard-Tasten für CV-Ausgabe verwenden
|
||||
int cvIndex = 0;
|
||||
for(int i = 0; i < n && cvIndex < N_CV_GATES; i++)
|
||||
Key k1 = keyboard.getQueue(0);
|
||||
if(!isNotKey(k1))
|
||||
{
|
||||
Key k = keyboard.getQueue(i);
|
||||
if(!isNotKey(k))
|
||||
{
|
||||
uint16_t voltage = keyToVoltage[k.row * N_KEYBOARD_COL + k.col];
|
||||
|
||||
// Bei Recording: Spannung aufnehmen
|
||||
if(sb1.isRecording())
|
||||
{
|
||||
sb1.addStep(voltage);
|
||||
}
|
||||
if(sb2.isRecording())
|
||||
{
|
||||
sb2.addStep(voltage);
|
||||
}
|
||||
|
||||
// Live-Ausgabe nur wenn nicht gerade wiedergegeben wird
|
||||
if(!sb1.isPlaying() && !sb2.isPlaying())
|
||||
{
|
||||
cv.setVoltage(cvIndex++, k);
|
||||
}
|
||||
}
|
||||
voltage_ch1 = keyToVoltage[k1.row * N_KEYBOARD_COL + k1.col];
|
||||
}
|
||||
}
|
||||
|
||||
// Restliche CV-Ausgänge auf 0 setzen wenn live gespielt wird
|
||||
if(!sb1.isPlaying() && !sb2.isPlaying())
|
||||
if(n > 1)
|
||||
{
|
||||
Key k2 = keyboard.getQueue(1);
|
||||
if(!isNotKey(k2))
|
||||
{
|
||||
for(int i = cvIndex; i < N_CV_GATES; i++)
|
||||
{
|
||||
cv.setVoltage(i, 0);
|
||||
}
|
||||
voltage_ch2 = keyToVoltage[k2.row * N_KEYBOARD_COL + k2.col];
|
||||
}
|
||||
}
|
||||
|
||||
// Bei Recording: Beide Kanäle aufnehmen
|
||||
if(sb1.isRecording())
|
||||
{
|
||||
sb1.addStep(voltage_ch1, voltage_ch2);
|
||||
}
|
||||
if(sb2.isRecording())
|
||||
{
|
||||
sb2.addStep(voltage_ch1, voltage_ch2);
|
||||
}
|
||||
|
||||
// CV-Ausgabe: Priorität hat Sequencer-Wiedergabe
|
||||
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
|
||||
{
|
||||
// Keine Tasten gedrückt
|
||||
if(sb1.isRecording())
|
||||
{
|
||||
sb1.addStep(0);
|
||||
}
|
||||
if(sb2.isRecording())
|
||||
{
|
||||
sb2.addStep(0);
|
||||
}
|
||||
|
||||
if(!sb1.isPlaying() && !sb2.isPlaying())
|
||||
{
|
||||
cv.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Sequencer-Wiedergabe auf CV-Ausgänge
|
||||
if(sb1.isPlaying())
|
||||
{
|
||||
cv.setVoltage(0, sb1.getCurrentVoltage());
|
||||
}
|
||||
if(sb2.isPlaying())
|
||||
{
|
||||
cv.setVoltage(1, sb2.getCurrentVoltage());
|
||||
// Live-Ausgabe wenn kein Sequencer spielt
|
||||
cv.setVoltage(0, voltage_ch1);
|
||||
cv.setVoltage(1, voltage_ch2);
|
||||
}
|
||||
|
||||
// Time-Limit Warnung
|
||||
@@ -267,12 +296,16 @@ void loop()
|
||||
{
|
||||
sb1.stopRecord();
|
||||
Serial.printf("\n\r[SEQ1] Time limit reached! Recording stopped.");
|
||||
Serial.printf("\n\r[SEQ1] Final: Steps: %i, Duration: %ims",
|
||||
sb1.getStepCount(), sb1.getTotalDuration());
|
||||
}
|
||||
if(sb2.isRecording() && sb2.timeLimitReached())
|
||||
{
|
||||
sb2.stopRecord();
|
||||
Serial.printf("\n\r[SEQ2] Time limit reached! Recording stopped.");
|
||||
Serial.printf("\n\r[SEQ2] Final: Steps: %i, Duration: %ims",
|
||||
sb2.getStepCount(), sb2.getTotalDuration());
|
||||
}
|
||||
|
||||
delay(10); // Kürzeres Delay für bessere Sequencer-Auflösung
|
||||
delay(10);
|
||||
}
|
||||
@@ -54,7 +54,7 @@ Die eigentliche __Diplomarbeit__ und die dazugehörigen (finalen) Fertigungsunte
|
||||
## TO-DO Liste
|
||||
- [x] Literaturrechreche
|
||||
- [x] Schaltungsentwurf
|
||||
- [ ] Simulationen
|
||||
- [x] Simulationen
|
||||
- [ ] Gesamt System zusammengeführt
|
||||
- [ ] Prototyp entwickeln und bestellen (Deadline: Bis Weihnachtsferien)
|
||||
- [ ] Start Diplomarbeit dokumentieren
|
||||
|
||||
BIN
lab/OutputStage/IMG_9659.jpeg
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
lab/OutputStage/IMG_9660.jpeg
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
lab/OutputStage/PIC1.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
lab/OutputStage/PIC2.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
lab/OutputStage/PIC3.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
lab/OutputStage/PIC4.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
lab/OutputStage/PIC5.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
1
lab/VCF/Aufbau_Steckbrett.brd
Normal file
BIN
lab/VCF/Aufbau_Steckbrett.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
lab/VCF/TEK0000.JPG
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
lab/VCF/TEK0001.JPG
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
lab/VCF/VCF.xlsx
Normal file
BIN
lab/VCO/IMG_9639.jpeg
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
lab/VCO/IMG_9640.jpeg
Normal file
|
After Width: | Height: | Size: 4.1 MiB |