Implementiere USB-MIDI-Controller für Arduino Pro Micro mit Hardware- und Software-Architektur, einschließlich Button- und LED-Steuerung, Multiplexer-Integration und MIDI-Kommunikation.
This commit is contained in:
15
.vscode/tasks.json
vendored
Normal file
15
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "PlatformIO: Build",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pio run",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$gcc"
|
||||||
|
],
|
||||||
|
"isBackground": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
180
README.md
Normal file
180
README.md
Normal file
@@ -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.
|
25
include/button_handler.h
Normal file
25
include/button_handler.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef BUTTON_HANDLER_H
|
||||||
|
#define BUTTON_HANDLER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Adafruit_MCP23X17.h>
|
||||||
|
|
||||||
|
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
|
41
include/config.h
Normal file
41
include/config.h
Normal file
@@ -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
|
22
include/led_controller.h
Normal file
22
include/led_controller.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef LED_CONTROLLER_H
|
||||||
|
#define LED_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Adafruit_MCP23X17.h>
|
||||||
|
|
||||||
|
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
|
23
include/midi_handler.h
Normal file
23
include/midi_handler.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef MIDI_HANDLER_H
|
||||||
|
#define MIDI_HANDLER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <MIDIUSB.h>
|
||||||
|
|
||||||
|
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
|
21
include/multiplexer.h
Normal file
21
include/multiplexer.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef MULTIPLEXER_H
|
||||||
|
#define MULTIPLEXER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
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
|
@@ -12,3 +12,7 @@
|
|||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
board = sparkfun_promicro16
|
board = sparkfun_promicro16
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
arduino-libraries/MIDIUSB@^1.0.5
|
||||||
|
adafruit/Adafruit MCP23017 Arduino Library@^2.0.2
|
||||||
|
Wire
|
||||||
|
74
src/button_handler.cpp
Normal file
74
src/button_handler.cpp
Normal file
@@ -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];
|
||||||
|
}
|
72
src/led_controller.cpp
Normal file
72
src/led_controller.cpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
src/main.cpp
175
src/main.cpp
@@ -1,18 +1,171 @@
|
|||||||
#include <Arduino.h>
|
/*
|
||||||
|
* 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:
|
#include <Arduino.h>
|
||||||
int myFunction(int, int);
|
#include <Wire.h>
|
||||||
|
#include <Adafruit_MCP23X17.h>
|
||||||
|
|
||||||
|
// 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() {
|
void setup() {
|
||||||
// put your setup code here, to run once:
|
// Serielle Kommunikation für Debugging
|
||||||
int result = myFunction(2, 3);
|
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() {
|
void loop() {
|
||||||
// put your main code here, to run repeatedly:
|
unsigned long currentTime = millis();
|
||||||
}
|
|
||||||
|
// 1. Analoge Regler verarbeiten (Potis & Schieberegler)
|
||||||
// put function definitions here:
|
if(currentTime - lastAnalogRead >= ANALOG_READ_INTERVAL) {
|
||||||
int myFunction(int x, int y) {
|
lastAnalogRead = currentTime;
|
||||||
return x + y;
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
106
src/midi_handler.cpp
Normal file
106
src/midi_handler.cpp
Normal file
@@ -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);
|
||||||
|
}
|
73
src/multiplexer.cpp
Normal file
73
src/multiplexer.cpp
Normal file
@@ -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;
|
||||||
|
}
|
Reference in New Issue
Block a user