From e64048e99b0366c8bace6ac303f6c8be35caff8a Mon Sep 17 00:00:00 2001 From: Manuel Weiser Date: Sat, 19 Jul 2025 15:46:14 +0200 Subject: [PATCH] =?UTF-8?q?Implementiere=20USB-MIDI-Controller=20f=C3=BCr?= =?UTF-8?q?=20Arduino=20Pro=20Micro=20mit=20Hardware-=20und=20Software-Arc?= =?UTF-8?q?hitektur,=20einschlie=C3=9Flich=20Button-=20und=20LED-Steuerung?= =?UTF-8?q?,=20Multiplexer-Integration=20und=20MIDI-Kommunikation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/tasks.json | 15 ++++ README.md | 180 +++++++++++++++++++++++++++++++++++++++ include/button_handler.h | 25 ++++++ include/config.h | 41 +++++++++ include/led_controller.h | 22 +++++ include/midi_handler.h | 23 +++++ include/multiplexer.h | 21 +++++ platformio.ini | 4 + src/button_handler.cpp | 74 ++++++++++++++++ src/led_controller.cpp | 72 ++++++++++++++++ src/main.cpp | 175 ++++++++++++++++++++++++++++++++++--- src/midi_handler.cpp | 106 +++++++++++++++++++++++ src/multiplexer.cpp | 73 ++++++++++++++++ 13 files changed, 820 insertions(+), 11 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 README.md create mode 100644 include/button_handler.h create mode 100644 include/config.h create mode 100644 include/led_controller.h create mode 100644 include/midi_handler.h create mode 100644 include/multiplexer.h create mode 100644 src/button_handler.cpp create mode 100644 src/led_controller.cpp create mode 100644 src/midi_handler.cpp create mode 100644 src/multiplexer.cpp diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..2e4d36a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "PlatformIO: Build", + "type": "shell", + "command": "pio run", + "group": "build", + "problemMatcher": [ + "$gcc" + ], + "isBackground": false + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c119d8c --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# USB-MIDI-Controller für Arduino Pro Micro + +Dieses Projekt implementiert einen vollständigen USB-MIDI-Controller basierend auf einem Arduino Pro Micro (ATmega32u4) mit umfangreicher Hardware-Ausstattung. + +## Hardware-Komponenten + +### Hauptkomponenten +- **Arduino Pro Micro** (ATmega32u4) - Hauptcontroller mit USB-MIDI-Unterstützung +- **2x MCP23017** I/O Expander (I2C) - Erweitert GPIO-Pins für Buttons und LEDs +- **1x CD74HC4067** Multiplexer (16-Kanal, analog) - Für 12 analoge Regler +- **8x Drehpotentiometer** (B10K) - Analoge Eingabe +- **4x Schieberegler** (linear, 10K) - Analoge Eingabe +- **20x Tact Switches** - Digitale Buttons +- **20x LEDs** - Optisches Feedback + +### Verdrahtung + +#### CD74HC4067 Multiplexer +- S0 → Digital Pin 2 +- S1 → Digital Pin 3 +- S2 → Digital Pin 4 +- S3 → Digital Pin 5 +- SIG → Analog Pin A0 +- VCC → 5V +- GND → GND + +#### MCP23017 #1 (Adresse 0x20) +- VCC → 5V +- GND → GND +- SCL → Pin 3 (Arduino Pro Micro) +- SDA → Pin 2 (Arduino Pro Micro) +- A0, A1, A2 → GND (Adresse 0x20) +- Pins 0-15 → Buttons 0-15 (mit Pull-Up-Widerständen) +- Pins 0-15 → LEDs 0-15 (mit Vorwiderständen) + +#### MCP23017 #2 (Adresse 0x21) +- VCC → 5V +- GND → GND +- SCL → Pin 3 (Arduino Pro Micro) +- SDA → Pin 2 (Arduino Pro Micro) +- A0 → 5V, A1, A2 → GND (Adresse 0x21) +- Pins 0-3 → Buttons 16-19 (mit Pull-Up-Widerständen) +- Pins 4-7 → LEDs 16-19 (mit Vorwiderständen) + +## Software-Architektur + +### Modularer Aufbau + +#### 1. `config.h` +- Zentrale Konfigurationsdatei +- Hardware-Pin-Definitionen +- MIDI-Mapping-Tabellen +- Timing-Parameter + +#### 2. `multiplexer.h/.cpp` +- Klasse für CD74HC4067-Multiplexer +- Analog-Wert-Erfassung mit Änderungs-Erkennung +- Schwellenwert-basierte Filterung + +#### 3. `button_handler.h/.cpp` +- Button-Erfassung mit Debouncing +- Verwendung beider MCP23017-Chips +- Event-basierte Erkennung (Pressed/Released) + +#### 4. `led_controller.h/.cpp` +- LED-Steuerung über beide MCP23017-Chips +- Zustandsverwaltung und Hardware-Synchronisation + +#### 5. `midi_handler.h/.cpp` +- USB-MIDI-Kommunikation (bidirektional) +- Note-On/Off und Control-Change-Nachrichten +- Callback-System für eingehende MIDI-Daten + +#### 6. `main.cpp` +- Hauptprogramm mit Setup und Loop +- Integration aller Module +- Timing-Management für optimale Performance + +## MIDI-Implementierung + +### Ausgehende MIDI-Nachrichten + +#### Control Change (CC) +- **Potis 1-8**: CC1 - CC8 +- **Schieberegler 1-4**: CC9 - CC12 +- **Wertebereich**: 0-127 +- **Kanal**: 1 (konfigurierbar) + +#### Note-On/Off +- **Buttons 1-20**: Noten C2 (36) bis G3 (55) +- **Velocity**: 127 (Note-On), 0 (Note-Off) +- **Kanal**: 1 (konfigurierbar) + +### Eingehende MIDI-Nachrichten + +#### Note-On/Off für LED-Steuerung +- Entsprechende LEDs werden basierend auf Note-Nummer ein-/ausgeschaltet +- Synchronisation mit DAW-Software (z.B. Ableton Live) + +## Verwendung + +### Kompilierung und Upload +```bash +# PlatformIO verwenden +pio run --target upload + +# Oder über PlatformIO IDE in VS Code +``` + +### Erste Schritte +1. Hardware gemäß Verdrahtungsplan anschließen +2. Code kompilieren und auf Arduino Pro Micro hochladen +3. USB-Verbindung zum Computer herstellen +4. In DAW/MIDI-Software als USB-MIDI-Gerät auswählen +5. LED-Test beim Start beobachten +6. Regler und Buttons testen + +### Debugging +- Serielle Konsole (115200 Baud) für Debug-Ausgaben +- LED-Test beim Start zeigt Hardware-Funktionalität +- MIDI-Events werden in der Console geloggt + +## Konfiguration + +### MIDI-Mapping anpassen +In `config.h` können folgende Arrays angepasst werden: +```cpp +// CC-Nummern für analoge Regler +const uint8_t ANALOG_CC_MAP[NUM_ANALOG_CONTROLS] = {...}; + +// Note-Nummern für Buttons +const uint8_t BUTTON_NOTE_MAP[NUM_BUTTONS] = {...}; +``` + +### Timing anpassen +In `main.cpp`: +```cpp +const unsigned long ANALOG_READ_INTERVAL = 10; // 100Hz +const unsigned long BUTTON_READ_INTERVAL = 5; // 200Hz +const unsigned long MIDI_READ_INTERVAL = 1; // 1000Hz +``` + +## Troubleshooting + +### MCP23017 nicht erkannt +- I2C-Verkabelung prüfen (SDA, SCL) +- Adressen-Jumper kontrollieren +- Pull-Up-Widerstände an I2C-Bus (4.7kΩ) + +### LEDs funktionieren nicht +- Vorwiderstände prüfen (220-470Ω empfohlen) +- Verdrahtung der MCP23017-Ausgänge kontrollieren +- Pin-Zuordnung in Code überprüfen + +### MIDI wird nicht erkannt +- USB-Kabel prüfen (Datenübertragung muss unterstützt werden) +- Arduino Pro Micro als USB-MIDI-Gerät in DAW auswählen +- MIDI-Kanal-Konfiguration überprüfen + +### Analoge Werte schwanken +- `ANALOG_THRESHOLD` in `config.h` erhöhen +- Netzteil-Qualität prüfen (saubere 5V) +- Verkabelung der analogen Regler kontrollieren + +## Erweiterungen + +### Zusätzliche Features +- Encoder-Unterstützung +- Display-Integration (OLED/LCD) +- Preset-Speicherung (EEPROM) +- Weitere MIDI-Modi (Program Change, etc.) + +### Hardware-Erweiterungen +- Zusätzliche MCP23017 für mehr I/Os +- Mehrere Multiplexer für mehr analoge Eingänge +- RGB-LEDs für erweiterte Rückmeldung + +## Lizenz + +Dieses Projekt steht unter MIT-Lizenz und kann frei verwendet und modifiziert werden. diff --git a/include/button_handler.h b/include/button_handler.h new file mode 100644 index 0000000..676c881 --- /dev/null +++ b/include/button_handler.h @@ -0,0 +1,25 @@ +#ifndef BUTTON_HANDLER_H +#define BUTTON_HANDLER_H + +#include +#include + +class ButtonHandler { +private: + Adafruit_MCP23X17* mcp1; + Adafruit_MCP23X17* mcp2; + + bool buttonStates[20]; + bool lastButtonStates[20]; + unsigned long lastDebounceTime[20]; + +public: + ButtonHandler(Adafruit_MCP23X17* mcp1, Adafruit_MCP23X17* mcp2); + void init(); + void update(); + bool isPressed(uint8_t buttonIndex); + bool wasPressed(uint8_t buttonIndex); + bool wasReleased(uint8_t buttonIndex); +}; + +#endif diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..338f1fc --- /dev/null +++ b/include/config.h @@ -0,0 +1,41 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// Hardware-Konfiguration +#define NUM_POTIS 8 +#define NUM_SLIDERS 4 +#define NUM_BUTTONS 20 +#define NUM_LEDS 20 +#define NUM_ANALOG_CONTROLS (NUM_POTIS + NUM_SLIDERS) + +// MCP23017 I2C Adressen +#define MCP1_ADDRESS 0x20 // Erste MCP23017 (Buttons 0-15 & LEDs 0-15) +#define MCP2_ADDRESS 0x21 // Zweite MCP23017 (Buttons 16-19 & LEDs 16-19) + +// CD74HC4067 Multiplexer Pins +#define MUX_S0 2 +#define MUX_S1 3 +#define MUX_S2 4 +#define MUX_S3 5 +#define MUX_ANALOG_PIN A0 + +// MIDI-Konfiguration +#define MIDI_CHANNEL 1 + +// CC-Mapping für analoge Regler (12 Regler total) +const uint8_t ANALOG_CC_MAP[NUM_ANALOG_CONTROLS] = { + 1, 2, 3, 4, 5, 6, 7, 8, // Potis (CC1-CC8) + 9, 10, 11, 12 // Schieberegler (CC9-CC12) +}; + +// Note-Mapping für Buttons (20 Buttons) +const uint8_t BUTTON_NOTE_MAP[NUM_BUTTONS] = { + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, // Buttons 0-9 (C2-A2) + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55 // Buttons 10-19 (A#2-G3) +}; + +// Debounce-Konfiguration +#define DEBOUNCE_TIME 50 // ms +#define ANALOG_THRESHOLD 4 // Mindeständerung für MIDI-CC + +#endif diff --git a/include/led_controller.h b/include/led_controller.h new file mode 100644 index 0000000..bdb61d8 --- /dev/null +++ b/include/led_controller.h @@ -0,0 +1,22 @@ +#ifndef LED_CONTROLLER_H +#define LED_CONTROLLER_H + +#include +#include + +class LEDController { +private: + Adafruit_MCP23X17* mcp1; + Adafruit_MCP23X17* mcp2; + bool ledStates[20]; + +public: + LEDController(Adafruit_MCP23X17* mcp1, Adafruit_MCP23X17* mcp2); + void init(); + void setLED(uint8_t ledIndex, bool state); + void toggleLED(uint8_t ledIndex); + bool getLEDState(uint8_t ledIndex); + void updateHardware(); +}; + +#endif diff --git a/include/midi_handler.h b/include/midi_handler.h new file mode 100644 index 0000000..1a39ab8 --- /dev/null +++ b/include/midi_handler.h @@ -0,0 +1,23 @@ +#ifndef MIDI_HANDLER_H +#define MIDI_HANDLER_H + +#include +#include + +class MIDIHandler { +private: + static void noteOn(byte channel, byte pitch, byte velocity); + static void noteOff(byte channel, byte pitch, byte velocity); + static void controlChange(byte channel, byte control, byte value); + +public: + static void init(); + static void sendNoteOn(uint8_t note, uint8_t velocity); + static void sendNoteOff(uint8_t note, uint8_t velocity); + static void sendControlChange(uint8_t controller, uint8_t value); + static void processIncomingMIDI(); + static void (*onNoteOnCallback)(uint8_t note, uint8_t velocity); + static void (*onNoteOffCallback)(uint8_t note, uint8_t velocity); +}; + +#endif diff --git a/include/multiplexer.h b/include/multiplexer.h new file mode 100644 index 0000000..74b53fc --- /dev/null +++ b/include/multiplexer.h @@ -0,0 +1,21 @@ +#ifndef MULTIPLEXER_H +#define MULTIPLEXER_H + +#include + +class Multiplexer { +private: + uint8_t s0, s1, s2, s3; + uint8_t analogPin; + int lastValues[16]; + unsigned long lastReadTime[16]; + +public: + Multiplexer(uint8_t s0Pin, uint8_t s1Pin, uint8_t s2Pin, uint8_t s3Pin, uint8_t analogPin); + void init(); + int readChannel(uint8_t channel); + bool hasChanged(uint8_t channel, int threshold = 4); + void selectChannel(uint8_t channel); +}; + +#endif diff --git a/platformio.ini b/platformio.ini index e4c2d7f..4704122 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,3 +12,7 @@ platform = atmelavr board = sparkfun_promicro16 framework = arduino +lib_deps = + arduino-libraries/MIDIUSB@^1.0.5 + adafruit/Adafruit MCP23017 Arduino Library@^2.0.2 + Wire diff --git a/src/button_handler.cpp b/src/button_handler.cpp new file mode 100644 index 0000000..fd3a1a4 --- /dev/null +++ b/src/button_handler.cpp @@ -0,0 +1,74 @@ +#include "button_handler.h" +#include "config.h" + +ButtonHandler::ButtonHandler(Adafruit_MCP23X17* mcp1, Adafruit_MCP23X17* mcp2) { + this->mcp1 = mcp1; + this->mcp2 = mcp2; + + // Initialisiere alle Button-Zustände + for(int i = 0; i < NUM_BUTTONS; i++) { + buttonStates[i] = false; + lastButtonStates[i] = false; + lastDebounceTime[i] = 0; + } +} + +void ButtonHandler::init() { + // Konfiguriere alle Button-Pins als Eingänge mit Pull-Up + // MCP1: Buttons 0-15 (Port A und B) + for(int i = 0; i < 16; i++) { + mcp1->pinMode(i, INPUT_PULLUP); + } + + // MCP2: Buttons 16-19 (Port A, Pins 0-3) + for(int i = 0; i < 4; i++) { + mcp2->pinMode(i, INPUT_PULLUP); + } + + Serial.println("Button Handler initialisiert - 20 Buttons konfiguriert"); +} + +void ButtonHandler::update() { + unsigned long currentTime = millis(); + + // Lese alle Button-Zustände + for(int i = 0; i < NUM_BUTTONS; i++) { + bool currentState; + + // Bestimme welcher MCP und welcher Pin + if(i < 16) { + // Buttons 0-15 auf MCP1 + currentState = !mcp1->digitalRead(i); // Invertiert wegen Pull-Up + } else { + // Buttons 16-19 auf MCP2 + currentState = !mcp2->digitalRead(i - 16); // Invertiert wegen Pull-Up + } + + // Debouncing: Prüfe ob genug Zeit vergangen ist + if(currentTime - lastDebounceTime[i] > DEBOUNCE_TIME) { + if(currentState != lastButtonStates[i]) { + // Zustand hat sich geändert - speichere neuen Zustand + lastButtonStates[i] = buttonStates[i]; + buttonStates[i] = currentState; + lastDebounceTime[i] = currentTime; + } + } + } +} + +bool ButtonHandler::isPressed(uint8_t buttonIndex) { + if(buttonIndex >= NUM_BUTTONS) return false; + return buttonStates[buttonIndex]; +} + +bool ButtonHandler::wasPressed(uint8_t buttonIndex) { + if(buttonIndex >= NUM_BUTTONS) return false; + // Button wurde gedrückt wenn er jetzt gedrückt ist, aber vorher nicht + return buttonStates[buttonIndex] && !lastButtonStates[buttonIndex]; +} + +bool ButtonHandler::wasReleased(uint8_t buttonIndex) { + if(buttonIndex >= NUM_BUTTONS) return false; + // Button wurde losgelassen wenn er jetzt nicht gedrückt ist, aber vorher schon + return !buttonStates[buttonIndex] && lastButtonStates[buttonIndex]; +} diff --git a/src/led_controller.cpp b/src/led_controller.cpp new file mode 100644 index 0000000..3fe03b7 --- /dev/null +++ b/src/led_controller.cpp @@ -0,0 +1,72 @@ +#include "led_controller.h" +#include "config.h" + +LEDController::LEDController(Adafruit_MCP23X17* mcp1, Adafruit_MCP23X17* mcp2) { + this->mcp1 = mcp1; + this->mcp2 = mcp2; + + // Initialisiere alle LED-Zustände als AUS + for(int i = 0; i < NUM_LEDS; i++) { + ledStates[i] = false; + } +} + +void LEDController::init() { + // Konfiguriere alle LED-Pins als Ausgänge + // MCP1: LEDs 0-15 (Port A und B) + // Port A = Pins 0-7, Port B = Pins 8-15 + for(int i = 0; i < 16; i++) { + mcp1->pinMode(i, OUTPUT); + mcp1->digitalWrite(i, LOW); // LEDs initial ausschalten + } + + // MCP2: LEDs 16-19 (Port A, Pins 4-7) + // Pins 0-3 sind für Buttons reserviert, LEDs nutzen Pins 4-7 + for(int i = 4; i < 8; i++) { + mcp2->pinMode(i, OUTPUT); + mcp2->digitalWrite(i, LOW); // LEDs initial ausschalten + } + + Serial.println("LED Controller initialisiert - 20 LEDs konfiguriert"); +} + +void LEDController::setLED(uint8_t ledIndex, bool state) { + if(ledIndex >= NUM_LEDS) return; + + ledStates[ledIndex] = state; + + // Aktualisiere Hardware sofort + if(ledIndex < 16) { + // LEDs 0-15 auf MCP1 + mcp1->digitalWrite(ledIndex, state ? HIGH : LOW); + } else { + // LEDs 16-19 auf MCP2 (Pins 4-7) + mcp2->digitalWrite((ledIndex - 16) + 4, state ? HIGH : LOW); + } +} + +void LEDController::toggleLED(uint8_t ledIndex) { + if(ledIndex >= NUM_LEDS) return; + setLED(ledIndex, !ledStates[ledIndex]); +} + +bool LEDController::getLEDState(uint8_t ledIndex) { + if(ledIndex >= NUM_LEDS) return false; + return ledStates[ledIndex]; +} + +void LEDController::updateHardware() { + // Aktualisiere alle LED-Zustände in der Hardware + // Diese Funktion kann aufgerufen werden, um sicherzustellen, + // dass alle LEDs den gewünschten Zustand haben + + for(int i = 0; i < NUM_LEDS; i++) { + if(i < 16) { + // LEDs 0-15 auf MCP1 + mcp1->digitalWrite(i, ledStates[i] ? HIGH : LOW); + } else { + // LEDs 16-19 auf MCP2 (Pins 4-7) + mcp2->digitalWrite((i - 16) + 4, ledStates[i] ? HIGH : LOW); + } + } +} diff --git a/src/main.cpp b/src/main.cpp index cb9fbba..fb43d72 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,18 +1,171 @@ -#include +/* + * USB-MIDI-Controller für Arduino Pro Micro (ATmega32u4) + * + * Hardware: + * - 2x MCP23017 I/O Expander (I2C) + * - 1x CD74HC4067 Multiplexer (16-Kanal, analog) + * - 8x Drehpotis + 4x Schieberegler (12 analoge Regler total) + * - 20x Buttons (Tact Switches) + * - 20x LEDs (optisches Feedback) + * + * Funktionen: + * - Analoge Regler senden MIDI-CC (CC1-CC12) + * - Buttons senden MIDI Note-On/Off + * - LEDs werden via eingehende MIDI-Nachrichten angesteuert + * - USB-MIDI Kommunikation + */ -// put function declarations here: -int myFunction(int, int); +#include +#include +#include + +// Eigene Module +#include "config.h" +#include "multiplexer.h" +#include "button_handler.h" +#include "led_controller.h" +#include "midi_handler.h" + +// Hardware-Objekte +Adafruit_MCP23X17 mcp1; // Erste MCP23017 (Adresse 0x20) +Adafruit_MCP23X17 mcp2; // Zweite MCP23017 (Adresse 0x21) + +// Modul-Objekte +Multiplexer mux(MUX_S0, MUX_S1, MUX_S2, MUX_S3, MUX_ANALOG_PIN); +ButtonHandler buttons(&mcp1, &mcp2); +LEDController leds(&mcp1, &mcp2); + +// Variablen für Timing +unsigned long lastAnalogRead = 0; +unsigned long lastButtonRead = 0; +unsigned long lastMIDIRead = 0; + +// Timing-Intervalle (in Milliseconds) +const unsigned long ANALOG_READ_INTERVAL = 10; // 100Hz für analoge Regler +const unsigned long BUTTON_READ_INTERVAL = 5; // 200Hz für Buttons (responsive) +const unsigned long MIDI_READ_INTERVAL = 1; // 1000Hz für MIDI (sehr responsive) + +// Callback-Funktionen für eingehende MIDI-Nachrichten +void onMIDINoteOn(uint8_t note, uint8_t velocity) { + // Suche die entsprechende LED für diese Note + for(int i = 0; i < NUM_BUTTONS; i++) { + if(BUTTON_NOTE_MAP[i] == note) { + leds.setLED(i, true); + Serial.print("LED "); + Serial.print(i); + Serial.print(" eingeschaltet für Note "); + Serial.println(note); + break; + } + } +} + +void onMIDINoteOff(uint8_t note, uint8_t velocity) { + // Suche die entsprechende LED für diese Note + for(int i = 0; i < NUM_BUTTONS; i++) { + if(BUTTON_NOTE_MAP[i] == note) { + leds.setLED(i, false); + Serial.print("LED "); + Serial.print(i); + Serial.print(" ausgeschaltet für Note "); + Serial.println(note); + break; + } + } +} void setup() { - // put your setup code here, to run once: - int result = myFunction(2, 3); + // Serielle Kommunikation für Debugging + Serial.begin(115200); + while(!Serial && millis() < 3000); // Warte max. 3 Sekunden auf Serial + + Serial.println("=== USB-MIDI-Controller Start ==="); + Serial.println("Initialisiere Hardware..."); + + // I2C initialisieren + Wire.begin(); + Serial.println("I2C Bus initialisiert"); + + // MCP23017 Chips initialisieren + if (!mcp1.begin_I2C(MCP1_ADDRESS)) { + Serial.println("FEHLER: MCP23017 #1 nicht gefunden!"); + while(1); // Stoppe hier bei Fehler + } + Serial.println("MCP23017 #1 (0x20) initialisiert"); + + if (!mcp2.begin_I2C(MCP2_ADDRESS)) { + Serial.println("FEHLER: MCP23017 #2 nicht gefunden!"); + while(1); // Stoppe hier bei Fehler + } + Serial.println("MCP23017 #2 (0x21) initialisiert"); + + // Module initialisieren + mux.init(); + buttons.init(); + leds.init(); + MIDIHandler::init(); + + // MIDI-Callbacks registrieren + MIDIHandler::onNoteOnCallback = onMIDINoteOn; + MIDIHandler::onNoteOffCallback = onMIDINoteOff; + + // LED-Test: Alle LEDs kurz blinken lassen + Serial.println("LED-Test..."); + for(int i = 0; i < NUM_LEDS; i++) { + leds.setLED(i, true); + delay(50); + leds.setLED(i, false); + } + + Serial.println("=== Initialisierung abgeschlossen ==="); + Serial.println("MIDI-Controller bereit!"); + Serial.println(""); } void loop() { - // put your main code here, to run repeatedly: -} - -// put function definitions here: -int myFunction(int x, int y) { - return x + y; + unsigned long currentTime = millis(); + + // 1. Analoge Regler verarbeiten (Potis & Schieberegler) + if(currentTime - lastAnalogRead >= ANALOG_READ_INTERVAL) { + lastAnalogRead = currentTime; + + for(int i = 0; i < NUM_ANALOG_CONTROLS; i++) { + if(mux.hasChanged(i, ANALOG_THRESHOLD)) { + // Lese den aktuellen Wert + int rawValue = mux.readChannel(i); + + // Konvertiere zu MIDI-Wert (0-127) + uint8_t midiValue = map(rawValue, 0, 1023, 0, 127); + + // Sende MIDI-CC + MIDIHandler::sendControlChange(ANALOG_CC_MAP[i], midiValue); + } + } + } + + // 2. Buttons verarbeiten + if(currentTime - lastButtonRead >= BUTTON_READ_INTERVAL) { + lastButtonRead = currentTime; + + // Button-Zustände aktualisieren + buttons.update(); + + // Prüfe auf Button-Events + for(int i = 0; i < NUM_BUTTONS; i++) { + if(buttons.wasPressed(i)) { + // Button wurde gedrückt - sende Note-On + MIDIHandler::sendNoteOn(BUTTON_NOTE_MAP[i], 127); + } + else if(buttons.wasReleased(i)) { + // Button wurde losgelassen - sende Note-Off + MIDIHandler::sendNoteOff(BUTTON_NOTE_MAP[i], 0); + } + } + } + + // 3. Eingehende MIDI-Nachrichten verarbeiten + if(currentTime - lastMIDIRead >= MIDI_READ_INTERVAL) { + lastMIDIRead = currentTime; + MIDIHandler::processIncomingMIDI(); + } } \ No newline at end of file diff --git a/src/midi_handler.cpp b/src/midi_handler.cpp new file mode 100644 index 0000000..a417cfa --- /dev/null +++ b/src/midi_handler.cpp @@ -0,0 +1,106 @@ +#include "midi_handler.h" +#include "config.h" + +// Statische Callback-Pointer initialisieren +void (*MIDIHandler::onNoteOnCallback)(uint8_t note, uint8_t velocity) = nullptr; +void (*MIDIHandler::onNoteOffCallback)(uint8_t note, uint8_t velocity) = nullptr; + +void MIDIHandler::init() { + // MIDI USB wird automatisch initialisiert + Serial.println("MIDI Handler initialisiert - USB MIDI bereit"); +} + +void MIDIHandler::noteOn(byte channel, byte pitch, byte velocity) { + midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity}; + MidiUSB.sendMIDI(noteOn); +} + +void MIDIHandler::noteOff(byte channel, byte pitch, byte velocity) { + midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity}; + MidiUSB.sendMIDI(noteOff); +} + +void MIDIHandler::controlChange(byte channel, byte control, byte value) { + midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value}; + MidiUSB.sendMIDI(event); +} + +void MIDIHandler::sendNoteOn(uint8_t note, uint8_t velocity) { + noteOn(MIDI_CHANNEL - 1, note, velocity); // MIDI-Kanäle sind 0-basiert + MidiUSB.flush(); + + Serial.print("MIDI Note-On gesendet: Note "); + Serial.print(note); + Serial.print(", Velocity "); + Serial.println(velocity); +} + +void MIDIHandler::sendNoteOff(uint8_t note, uint8_t velocity) { + noteOff(MIDI_CHANNEL - 1, note, velocity); // MIDI-Kanäle sind 0-basiert + MidiUSB.flush(); + + Serial.print("MIDI Note-Off gesendet: Note "); + Serial.print(note); + Serial.print(", Velocity "); + Serial.println(velocity); +} + +void MIDIHandler::sendControlChange(uint8_t controller, uint8_t value) { + controlChange(MIDI_CHANNEL - 1, controller, value); // MIDI-Kanäle sind 0-basiert + MidiUSB.flush(); + + Serial.print("MIDI CC gesendet: Controller "); + Serial.print(controller); + Serial.print(", Wert "); + Serial.println(value); +} + +void MIDIHandler::processIncomingMIDI() { + midiEventPacket_t rx; + + do { + rx = MidiUSB.read(); + + if (rx.header != 0) { + // Extrahiere MIDI-Daten + uint8_t messageType = rx.byte1 & 0xF0; + uint8_t channel = rx.byte1 & 0x0F; + uint8_t data1 = rx.byte2; + uint8_t data2 = rx.byte3; + + // Prüfe auf den konfigurierten MIDI-Kanal + if(channel == (MIDI_CHANNEL - 1)) { + switch(messageType) { + case 0x90: // Note-On + if(data2 > 0) { // Velocity > 0 = echtes Note-On + Serial.print("MIDI Note-On empfangen: Note "); + Serial.print(data1); + Serial.print(", Velocity "); + Serial.println(data2); + + if(onNoteOnCallback != nullptr) { + onNoteOnCallback(data1, data2); + } + } else { // Velocity = 0 = Note-Off + Serial.print("MIDI Note-Off empfangen (via Note-On): Note "); + Serial.println(data1); + + if(onNoteOffCallback != nullptr) { + onNoteOffCallback(data1, 0); + } + } + break; + + case 0x80: // Note-Off + Serial.print("MIDI Note-Off empfangen: Note "); + Serial.println(data1); + + if(onNoteOffCallback != nullptr) { + onNoteOffCallback(data1, data2); + } + break; + } + } + } + } while (rx.header != 0); +} diff --git a/src/multiplexer.cpp b/src/multiplexer.cpp new file mode 100644 index 0000000..190482e --- /dev/null +++ b/src/multiplexer.cpp @@ -0,0 +1,73 @@ +#include "multiplexer.h" + +Multiplexer::Multiplexer(uint8_t s0Pin, uint8_t s1Pin, uint8_t s2Pin, uint8_t s3Pin, uint8_t analogPin) { + this->s0 = s0Pin; + this->s1 = s1Pin; + this->s2 = s2Pin; + this->s3 = s3Pin; + this->analogPin = analogPin; + + // Initialisiere alle Werte mit -1 um erste Änderung zu erkennen + for(int i = 0; i < 16; i++) { + lastValues[i] = -1; + lastReadTime[i] = 0; + } +} + +void Multiplexer::init() { + // Konfiguriere Multiplexer-Steuerpins als Ausgänge + pinMode(s0, OUTPUT); + pinMode(s1, OUTPUT); + pinMode(s2, OUTPUT); + pinMode(s3, OUTPUT); + + // Konfiguriere analogen Eingang + pinMode(analogPin, INPUT); + + Serial.println("CD74HC4067 Multiplexer initialisiert"); +} + +void Multiplexer::selectChannel(uint8_t channel) { + // Stelle sicher dass der Kanal im gültigen Bereich ist + if(channel > 15) return; + + // Setze die Multiplexer-Steuerleitungen entsprechend dem Kanal + digitalWrite(s0, (channel & 0x01) ? HIGH : LOW); + digitalWrite(s1, (channel & 0x02) ? HIGH : LOW); + digitalWrite(s2, (channel & 0x04) ? HIGH : LOW); + digitalWrite(s3, (channel & 0x08) ? HIGH : LOW); + + // Kurze Verzögerung für Multiplexer-Umschaltung + delayMicroseconds(50); +} + +int Multiplexer::readChannel(uint8_t channel) { + if(channel > 15) return -1; + + // Wähle den Kanal aus + selectChannel(channel); + + // Lese den analogen Wert + int value = analogRead(analogPin); + + // Speichere den Wert und die Zeit + lastValues[channel] = value; + lastReadTime[channel] = millis(); + + return value; +} + +bool Multiplexer::hasChanged(uint8_t channel, int threshold) { + if(channel > 15) return false; + + // Lese aktuellen Wert + int currentValue = readChannel(channel); + + // Prüfe ob sich der Wert signifikant geändert hat + if(lastValues[channel] == -1) { + // Erster Aufruf - als Änderung werten + return true; + } + + return abs(currentValue - lastValues[channel]) >= threshold; +}