Günstiger Arduino-Atemcontroller mit USB-MIDI-Integration
Komponenten und Materialien
| Arduino Micro | × | 1 | Nur der Micro (ATmega32U4) funktioniert aufgrund der nativen USB-Fähigkeiten. Ein Clone wäre günstiger, aber das Original wird empfohlen. |
| Lötfreies Breadboard (Halbgröße) | × | 1 | |
| MPS20N0040D-D Drucksensor | × | 1 | Sehr günstig und leicht zu beschaffen. |
| LM358-N Operationsverstärker | × | 1 | Verstärkt das Signals des Sensors. |
| Widerstand 1M Ohm | × | 2 | |
| Draht-Set, 22 AWG | × | 1 | Ein komplettes Set ist nicht nötig, nur wenige cm Massivdraht. |
| Baby-Nasensauger | × | 1 | Richtig gelesen! Wir nutzen das Eltern-Mundstück und den Baby-Sauger. Es gibt Dutzende manuelle Sauger dieses Typs (z. B. Physiomer, Chicco Physioclean). Wählen Sie das Mundstück, das Ihnen am angenehmsten ist. |
| 5mm Aquarium-Luftschlauch | × | 1 | Optional, falls der Schlauch des Saugers nicht lang genug ist. Standard-Aquarium-Schlauch funktioniert. |
| 3-Wege-Luftschlauch-Verbinder | × | 1 | Dieses spezifische chinesische OEM-Teil findet sich leicht in Aquaristik-Läden unter verschiedenen Namen. Er passt direkt auf den Sensor. |
Benötigte Werkzeuge und Maschinen
| Abisolierzange & Schneider, 32-20 AWG / 0.05-0.5mm² |
Apps und Online-Dienste
| Arduino IDE |
Über dieses Projekt
Einführung
In diesem Projekt bauen wir einen einfachen USB-MIDI-Plug-and-Play-Atemcontroller. Er ist mit leicht erhältlichen, günstigen Komponenten konzipiert, damit die Gesamtkosten erschwinglich bleiben und weit unter kommerziellen Äquivalenzen liegen. Dies ist die Basisversion, die nur den Luftdrucksensor nutzt; geplant ist eine Erweiterung um Biss- und Nick-/Neigungssensoren.
Das Projekt setzt grundlegende Elektronik- und Arduino-Kenntnisse voraus, aber auch Einsteiger können es bauen, da kein Löten erforderlich ist. Fortgeschrittene können alles auf eine Lochrasterplatine löten. Die Installation der IDE/Bibliotheken und das Hochladen des Codes werden nicht behandelt, da es dafür im Internet hervorragende Tutorials gibt.
Oversampling wird verwendet, um den Sensor-Eingang sehr gut zu glätten. Sie können den Bereich an Ihre Vorlieben anpassen und sogar benutzerdefinierte Kurven einstellen, um das Verhalten des Controllers zu justieren.
Da der Sensor auch Unterdruck messen kann, gibt es einen zweiten Strom an Controller-Nachrichten, der beim Einatmen (statt Ausblasen) gesendet wird. Beide Nachrichtentypen können vom Benutzer festgelegt werden. Beispiel: Ausblasen = Pitch Bend Up, Einatmen = Pitch Bend Down. Standardmäßig sind beide auf Controller Nr. 2 eingestellt.
Bau-Schritte
- Arduino wie im Foto/Schaltplan gezeigt auf das Breadboard stecken.
- Sensor und Op-Amp an ihren Positionen einstecken, Ausrichtung anhand der kleinen Einkerbungen an den Seiten beachten.
- Widerstände nach Kürzen der Beinchen an ihren Positionen platzieren.
- Massivdraht abschneiden/abisolieren und an den jeweiligen Positionen platzieren. Ich habe Rot für 5V, Schwarz für Ground und Gelb für Signal verwendet, um das Verständnis zu erleichtern – Sie können natürlich verwenden, was Sie haben.
- Mundstück, Schlauch, 3-Wege-Verbinder und Sauger wie im Foto gezeigt verbinden. Sie müssen ein Stück Schlauch für den „Auslass“ zuschneiden.
- 3-Wege-Verbinder auf den Sensor drücken, bis er fest sitzt.
- Arduino IDE installieren und die beiden benötigten Bibliotheken (Oversampling und USB-MIDI) über Werkzeuge → Bibliotheken verwalten installieren. Arduino per USB-Kabel mit dem Computer verbinden. Den beigefügten Code hochladen.
- Fertig! Der Arduino sollte nun in Ihrer DAW/Musiksoftware als MIDI-Gerät erscheinen. Aktivieren Sie ihn und routen Sie ihn zusammen mit Ihrem Keyboard zu einem Plug-in, das Breath Controller unterstützt.
Erweiterte Informationen
Ein physischer Nachteil dieses Designs ist der Speichel, der unweigerlich in den Schlauch fließt und spürbare Luftstromschwankungen verursachen kann. Der 3-Wege-Verbinder löst dieses Problem, indem er den Speichel in den „Auslass“-Schlauch leitet. Um eingeschlossenen Speichel im Schlauch zu minimieren, achten Sie auf einen kontinuierlichen Gefälle vom Mundstück zum 3-Wege-Verbinder, indem Sie die Schlauchlänge anpassen. Wenn der Schlauch unter dem Niveau des 3-Wege-Verbinders hängt, sammelt sich Speichel am tiefsten Punkt und verursacht Schwankungen. Der Baby-seitige Teil des Saugers inklusive Filter wird am Auslass befestigt, um Tropfen und Geräusche zu minimieren und den Luftstrom zum Sensor zu erhöhen.
Im Code gibt es Werte, die Sie anpassen können, einschließlich benutzerdefinierter Kurven. Kommentare beschreiben, wie das geht. Wenn Sie den 127er-Wert schwer erreichen, verringern Sie den maximalen Bereich; wenn es zu leicht ist, erhöhen Sie ihn. Bei jeder Wertänderung müssen Sie den Code neu hochladen.
Die ersten paar Messwerte nach dem Arduino-Start werden gemittelt, um die Ruhelage zu kalibrieren. Blasen Sie nicht in den Schlauch, während Sie das Gerät verbinden oder zurücksetzen.
Sie können den Namen des MIDI-Geräts ändern, indem Sie die Datei boards.txt bearbeiten (Anleitungen dazu für jede Plattform finden sich im Internet).
Code
Atemcontroller-Code (Arduino)
/* Breath Controller */
// Verwendete Bibliotheken - über Tools->Bibliotheken verwalten installieren
#include <Oversampling.h>
#include <USB-MIDI.h>
// Debug-Modus (auskommentieren zum Aktivieren)
//#define DEBUG 1
// Erstellung der USB-MIDI-Schnittstelle
USBMIDI_CREATE_DEFAULT_INSTANCE();
// Oversampling Initialisierung
Oversampling adc(10, 13, 6);
// ***************** Benutzer-Einstellungen *****************
// Werte mit Endung 1 = Ausblasen, Endung 2 = Einatmen
// Pin-Konfiguration
const int sensorPin1 = A0; // Arduino-Eingangspin für Sensor/Op-Amp-Ausgang
// Bereichs-Kalibrierung. Manuell anpassen, damit Maximum erreichbar, aber nicht zu leicht.
int sensorRange1 = 800;
int sensorRange2 = 800;
// Ausgabe-Controllernummer. Auswählen aus untenstehender Tabelle
// 0-127: Normale Control-Change-Nachrichten
// 128: Monophonic Aftertouch
// 129: Pitch Bend Up
// 130: Pitch Bend Down
int controllerNumber1 = 2; // Controller beim Ausblasen
int controllerNumber2 = 2; // Controller beim Einatmen
// Ausgabe-Controller-Kanäle
int controllerChannel1 = 1;
int controllerChannel2 = 1;
// Sicherheits-Schwellen für tiefste/höchste Werte, um Schwankungen in Ruhe oder bei Maximum zu vermeiden.
// Wenn in Ruhe mehrere Nachrichten gesendet werden: lowThreshold erhöhen.
// Wenn bei Maximum mehrere Nachrichten gesendet werden: highThreshold erhöhen.
const int lowThreshold1 = 5;
const int lowThreshold2 = 5;
const int highThreshold1 = 0;
const int highThreshold2 = 0;
// Kurven-Definition. Tabellen können beliebige Länge ≥ 2 haben. Werte 0-127.
// „in“-Tabellen müssen aufsteigend sein.
int in1[] = {0, 127};
int out1[] = {0, 127};
int in2[] = {0, 127};
int out2[] = {0, 127};
// Beispiel-Kurven (Sensornummer entsprechend anpassen)
// Sanft
// int in1[] = {0, 6, 24, 78, 127};
// int out1[] = {0, 32, 64, 96, 127};
// Reduzierter Bereich
// int in1[] = {50, 100};
// int out1[] = {50, 100};
// Aktualisierungszyklus (Millisekunden). Niedrigere Werte = mehr Nachrichten während des Betriebs.
int refreshCycle = 0;
// ***************** Implementierung *****************
// Ab hier nicht ändern, wenn Sie die Sensorfunktion nicht verändern wollen.
// Interne Sensorwerte
int sensorValue1 = 0;
int sensorValue2 = 0;
// Minimale Sensorwerte
int sensorMin1;
int sensorMin2;
// Ausgabe-Controller-Werte
int controllerValue1 = 0;
int controllerValue2 = 0;
// Vorherige Zyklus-Werte zur Vermeidung identischer Nachrichten
int previousControllerValue1 = 0;
int previousControllerValue2 = 0;
// Bereichs-Umrechnungsvariablen
int outputRange1;
int outputRange2;
int sensorLow1;
int sensorLow2;
int sensorHigh1;
int sensorHigh2;
void setup() {
MIDI.begin(1);
#ifdef DEBUG
Serial.begin(115200); // Nur für Debug-Modus
#endif
// Ruhelage des Sensors kalibrieren durch Mittelung der ersten 10 Werte.
// Sensor NICHT benutzen während des Bootens!
sensorMin1 = adc.read(sensorPin1);
sensorMin2 = 0;
// Ausgabe-Bereiche für die gewählten Controller ermitteln
outputRange1 = outputRange(controllerNumber1);
outputRange2 = outputRange(controllerNumber2);
}
void loop() {
// Wert vom Sensor lesen:
sensorValue1 = adc.read(sensorPin1); // Ausblasen
sensorValue2 = sensorMin1 - sensorValue1; // Einatmen
// Vorherige Werte speichern
previousControllerValue1 = controllerValue1;
previousControllerValue2 = controllerValue2;
// Nutzbare Bereichsgrenzen für Sensor rauf/runter
sensorLow1 = sensorMin1 + lowThreshold1;
sensorLow2 = sensorMin2 + lowThreshold2;
sensorHigh1 = sensorLow1 + sensorRange1 - highThreshold1;
sensorHigh2 = min(sensorMin1, sensorRange2) - highThreshold2;
// Interne Werte in Ausgabe-Bereich umrechnen (0..127 für Controller/Aftertouch, 0..+/-8191 für Pitchbend)
// unter Verwendung der in „in“- und „out“-Tabellen definierten Kurven.
controllerValue1 = map(mapToCurve(constrain(sensorValue1, sensorLow1, sensorHigh1), sensorLow1, sensorHigh1, in1, out1, sizeof(in1)/sizeof(int)), sensorLow1, sensorHigh1, 0, outputRange1);
controllerValue2 = map(mapToCurve(constrain(sensorValue2, sensorLow2, sensorHigh2), sensorLow2, sensorHigh2, in2, out2, sizeof(in2)/sizeof(int)), sensorLow2, sensorHigh2, 0, outputRange2);
// MIDI-Nachrichten senden
if (controllerValue1 != previousControllerValue1) sendSensorOutput(controllerNumber1, controllerValue1, controllerChannel1);
if (controllerValue2 != previousControllerValue2) sendSensorOutput(controllerNumber2, controllerValue2, controllerChannel2);
// Debug
#ifdef DEBUG
// Sensor (Eingabe) Werte (für Debug auskommentieren)
// Serial.print(sensorValue1);
// Serial.print(",");
// Serial.print(sensorValue2);
// Serial.print(",");
// Controller (Ausgabe) Werte
Serial.print(controllerValue1);
Serial.print(",");
Serial.println(controllerValue2);
#endif
// Programm für Millisekunden pausieren:
delay(refreshCycle);
}
// Funktion zum Senden von MIDI-Nachrichten gemäß Controllernummer
void sendSensorOutput(int number, int value, int channel) {
if (number < 128) MIDI.sendControlChange(number, value, channel);
else if (number == 128) MIDI.sendAfterTouch(value, channel);
else if (number == 129) MIDI.sendPitchBend(value, channel);
else if (number == 130) MIDI.sendPitchBend(-value, channel);
}
// Funktion zur Bestimmung des Bereichs eines spezifischen Controllers
// (Pitch Bend hat größeren Bereich als normale Controller)
int outputRange(int number) {
if (number > 128) return 8191;
else return 127;
}
// Modifizierte multiMap-Funktion für Kurven. Original von Rob Tillaart.
int mapToCurve(int val, int sensorLow, int sensorHigh, int* _in, int* _out, uint8_t size) {
// Wert in Bereich prüfen
// val = constrain(val, _in[0], _in[size-1]);
if (val <= map(_in[0], 0, 127, sensorLow, sensorHigh)) return map(_out[0], 0, 127, sensorLow, sensorHigh);
if (val >= map(_in[size-1], 0, 127, sensorLow, sensorHigh)) return map(_out[size-1], 0, 127, sensorLow, sensorHigh);
// Rechten Intervall suchen
uint8_t pos = 1; // _in[0] bereits getestet
while(val > map(_in[pos], 0, 127, sensorLow, sensorHigh)) pos++;
// Bereich von ..127 auf Sensor-Bereich anpassen
int inPos = map(_in[pos], 0, 127, sensorLow, sensorHigh);
int outPos = map(_out[pos], 0, 127, sensorLow, sensorHigh);
int inPrv = map(_in[pos-1], 0, 127, sensorLow, sensorHigh);
int outPrv = map(_out[pos-1], 0, 127, sensorLow, sensorHigh);
// Exakte „Punkte“ im _in-Array behandeln
if (val == inPos) return outPos;
// Im rechten Segment interpolieren
return ((long)val - (long)inPrv) * ((long)outPos - (long)outPrv) / ((long)inPos - (long)inPrv) + (long)outPrv;
}
Schaltpläne
Fritzing-Schaltplan
CircuitLab-Schaltplan
Herstellungsprozess
- Arduino Nano Flight Simulator 2020 Kontrollpanel – LCD & CDI Display
- Web-gesteuertes DMX-Lichtsystem – Arduino Master Controller
- DIY Arduino USB Gaming Controller – Bauen Sie Ihren eigenen High-Performance Gamepad
- Unopad: Nahtloser Arduino MIDI-Controller für Ableton Live
- Bluetooth Text-Teleporter: Arduino + Android Projekt
- Arduino-basierter VFD-Ansteuerungs-Controller (Vacuum Fluorescent Display)
- Alexa-fähiger Arduino Home Controller: Smarte Automatisierung leicht gemacht
- Smartes Temperatur-Monitoring für Schulen – Arduino, Raspberry Pi, Sigfox & ThingSpeak
- Isolierter Analogeingang für Arduino: Spannung/Strom galvanisch trennen
- Smartes Indoor-Navigations-Roboter-Kit für Arduino-Projekte