www.radonmaster.de/dancingwater/dmx-extension/         Stand: 31.10.2016

home             DMX und Erweiterungen

Um zu einer fertigen Licht-Choreographie zu kommen, erscheint DMX der schnellste Weg zu sein. Insbesondere weil es kostenlose Editor-Programme dafür gibt (PC als Master). Springbrunnen werden leider nicht direkt unterstützt. Hauptproblem ist die zeitliche Verzögerung, mit der Fontänen auf die Kommandos reagieren. Anfangs deutete alles darauf hin, dass ich mich mit dem kostenlosen Programm DMXControl behelfen werde. Behelfen bedeutet, dass ich vom Idealfall eines Fontäneneditors, der eine Simulation des Brunnens auf dem Bildschirm anzeigt, weit entfernt bin. Der Show Control Editor von ID-AL kommt meinen Vorstellungen wesentlich näher, weil er speziell für die Lichtgestaltung (Verlichtung) von Musik konzipiert ist. Jetzt fehlt nur noch die Simulation des Brunnens, um während langer Winterabende Choreographien für den nächsten Sommer zu bearbeiten.

DMX Übertragungstechnik

Beschäftigen wir uns zunächst mit grundlegenden Eigenschaften von DMX (Digital Multiplex), bevor es um Tricks und Erweiterungen geht. DMX wurde ursprünglich für die Lichtsteuerung in Theatern konzipiert. Dabei wurde auf die leichte Realisierbarkeit der Gerätekomponenten geachtet, die mit einfachster Schaltungstechnik auskommen. Zum Verständnis sehen wir uns die Struktur eines Datenwortes (Bytes) während der Übertragung an:



Das Übertragungsverfahren stammt aus der amerikanischen, mechanischen Fernschreibtechnik. Das erklärt auch einige der auftauchenden Begriffe. Im Ruhezustand liegt die Übertragungsleitung auf logisch 1, (idle = müßig). Eingeleitet wird die Übertragung eines jedes Datenworts (data-frame, bei uns Byte) mit einem Start-Bit, dem alle Datenbit mit gleicher Länge Folgen. Den Abschluss bilden zwei Stop-Bit. Zwischen den Datenworten sind beliebig lange Pausen erlaubt. Weil die Übertragung außerhalb der einzelnen Worte keinem bestimmten Zeitrahmen folgt, nennen wir sie asynchron. Die Bitfrequenz (innerhalb eines Datenwortes) beträgt bei DMX 250 kHz. Für andere Zwecke gibt es andere Frequenzen und andere Bitzahlen.

Das folgende Bild zeigt den Aufbau eines DMX-Empfängers mit Schaltkreisen, die ab den 1970er Jahren weit verbreitet waren. Mit Rücksicht auf deren Möglichkeiten wurde das DMX-Protokoll entwickelt.



Die Daten gelangen vom DMX-Bus (data line) in einen Empfänger für asynchrone Übertragungen (Empfangsteil eines UART, Asynchronous Universal Receiver Transmitter). Der schiebt alle empfangenen Bit der Reihe nach in sein Ausgaberegister. Ist ein Zeichen vollständig, liefert sein ready-Ausgang das Signal zum Weiterschalten des Adresszählers. Ein Vergleichsschaltkreis (comparator) vergleicht die aktuelle Adresse im Zähler mit einer eingestellten Adresse z.B. aus einem Schalterfeld. Sind eingestellte und aktuelle Adresse gleich, erhält das Datenregister einen Impuls zur Übernahme der Daten aus dem Schieberegister des UART. Erst jetzt steht das übertragene Byte für die Nutzung durch einen Helligkeitsregler zur Verfügung. Der Vorgang bis hier her muss während der zwei Stop-Bit abgeschlossen sein, weil an den Ausgängen des UART sonst bereits Teile des folgenden Zeichens anstehen.

Der Empfangsvorgang im Detail: Der Vorgang beginnt mit dem ersten Erkennen des Signalpegels 0 auf der Datenleitung, also eines Start-Bit. Ab jetzt werden in festen Zeitabständen neunmal die Zustände der Datenleitung in das interne Register des UART übernommen und jeweils einen Schritt weiter geschoben. Steht zum 9. Zeittakt der Pegel logisch 1 an, liefert das UART das ready-Signal. Steht logisch 0 an, liefert es ein error-Signal. Nur bei DMX ist das error-Signal Bestandteil einer einwandfreien Übertragung. Vor jedem Datenpaket aus 512 Bytes gibt es ein Break (logisch 0), das länger ist als ein Datenwort. Damit bleibt in dem Fall das Stop-Bit aus. Das error-Signal wird nicht zur Fehlerbehandlung sondern zum Rücksetzen des Adresszählers verwendet.

In neu entwickelten DMX-Empfängern stecken Mikrocontroller, die weit umfangreichere Funktionen haben als der oben beschriebene historische Empfänger. Ich verwende einen Arduino Controller und eine DMX-Programmbibliothek von www.mathertel.de , die empfangen und senden kann. Das Bild zeigt das zugehörige Funktionsschema.



Der Controller enthält ein weit komplexeres UART als die oben beschriebene Hardware-Lösung. Es wird von einem Programmteil bedient, der im Hintergrund läuft. Es ist eine sogenannte Interrupt-Routine, die das selbst programmierte Anwenderprogramm immer kurz unterbricht, wenn ein neues Datenbyte eingegangen ist. Die Interrupt-Routine schreibt das Byte in einen Pufferspeicher, der alle übertragenen DMX-typischen 512 Bytes umfasst. Das bedeutet, dass jeder Empfänger dieser Art neben den Werten des von ihm gesteuerten Gerätes auch über alle anderen Daten seines DMX-Universums verfügt. Das selbst programmierte Anwenderprogramm merkt von all dem nichts. Es entnimmt per Anweisung DMXSerial.read(adr) den zum DMX-Kanal (adr) übermittelten Einstellwert und kann ihn z.B. einer Controller-internen PWM-Einheit (pulse width modulation) übergeben. Damit wird dann direkt eine Lampenhelligkeit eingestellt. Weil der hier verwendete Arduino Uno Controller über 6 PWM-Einheiten verfügt lassen sich bis zu 6 einfache Lichtquellen steuern.

Um 6 Bytes aus dem Puffer zu lesen und in die PWM-Einheiten zu schreiben, ist nur minimalster Programmaufwand erforderlich. Eine Endlosschleife, die das erledigt sieht so aus:

void loop()
{
  analogWrite(ledpin1, DMXSerial.read(ad1));
  analogWrite(ledpin2, DMXSerial.read(ad2));
  analogWrite(ledpin3, DMXSerial.read(ad3));
  analogWrite(ledpin4, DMXSerial.read(ad4));
  analogWrite(ledpin5, DMXSerial.read(ad5));
  analogWrite(ledpin6, DMXSerial.read(ad6));
}

Zusätzlich zu den 6 Programmzeilen (für jede angeschlossene LED eine) müssen vor dieser Schleife lediglich die Anschluss-Pins der LEDs (ledpin1,...ledpin6) und die DMX-Adressen (ad1, ...ad6) vereinbart werden. Ein Durchlauf der Schleife benötigt um die 0,1 ms. Mein DMX-Interface benötigt 60 ms um einen Datensatz von 512 Byte zu übertragen. Die Daten werden also 600 Mal aus dem Puffer in die PWM-Einheiten geschrieben bis ein neuer Datensatz eingeht, obwohl ein einziges Mal ausreichen würde.

Tricks und Erweiterungsmöglichkeiten

Mit dem Programmcode weiter oben erledigt der Controller sämtliche Aufgaben eines typischen DMX-Empfängers. Damit ist er natürlich kaum gefordert, und es sind noch jede Menge Kapazitäten für weit umfangreichere Aufgaben frei.

Synchronisation mit der DMX-Übertragung

Das Füllen des DMX Pufferspeichers und das Entnehmen einzelner Bytes erfolgen im einfachsten Fall (wie oben) völlig unabhängig voneinander. Es ist also offen, wann ein ausgelesenes Byte vom Master über den DMX-Bus übertragen wurde. Damit einem kein Byte entgeht, wird meistens wesentlich öfter zugegriffen als erforderlich ist. Das DMX-Verfahren bringt keine Möglichkeit mit, um die Zugriffe auf den Pufferspeicher mit der DMX-Übertragung zu synchronisieren. Es geht aber mit einem Trick:

Nur in den seltensten Fällen werden tatsächlich alle 512 DMX-Adressen verwendet. Für nicht verwendete Adressen übertragen die DMX-Master eine Null. Weil der Anwender in den DMX-Puffer auch schreiben kann, bietet es sich an, ein spezielles Muster in den Puffer zu schreiben. Eine DMX-Übertragung vom Master ist fertig, wenn das Muster durch eine Null ersetzt ist. Nach dem Auswerten der übertragenen Daten schreiben wir erneut unser Muster in den Puffer. Im Vergleich mit dem Programmauszug weiter oben wird der Programmcode damit um zwei Zeilen länger:

void loop()
{
  if(!DMXSerial.read(512))
  {
    analogWrite(ledpin1, DMXSerial.read(ad1));
    analogWrite(ledpin2, DMXSerial.read(ad2));
    analogWrite(ledpin3, DMXSerial.read(ad3));
    analogWrite(ledpin4, DMXSerial.read(ad4));
    analogWrite(ledpin5, DMXSerial.read(ad5));
    analogWrite(ledpin6, DMXSerial.read(ad6));
    DMXSerial.write(512,255)
  }
}

Die IF-Abfrage möchte wissen, ob der letzte DMX-Kanal 512 eine Null enthält oder nicht. Enthält der Kanal keine Null, werden die folgenden Zeilen übersprungen. Enthält er eine Null (die der DMX-Master gerade übertragen hat) werden die Programmzeilen ausgeführt und zum Schluss wieder das von Null verschiedene Muster 255 in den Puffer auf Platz 512 geschrieben. Damit ist das Auslesen des Puffers blockiert, bis wieder ein neues DMX-Paket eigetroffen ist. Es wird also nach jedem DMX-Paket nur einmal zugegriffen, um die frisch eingetroffenen Daten zu verwerten.

Netter Trick, aber nützt er uns etwas? Der DMX-Empfänger ist damit in der Lage, den zeitlichen Abstand aufeinanderfolgender Datenpakete zu ermitteln. Zusammen mit dem Übertragungszeitpunkt eröffnet das zumindest für mich ein riesiges Potential an Möglichkeiten. Eine naheliegende Anwendung wäre feinstufiges Dimmen, wenn lediglich grobe Daten einlaufen. Auch ließen sich von verschiedenen Controllern zur gleichen Zeit eingeleitete Vorgänge etwas entzerren, z.B. das Einschalten kräftiger Halogenlampen oder Motoren.

So ganz nebenbei lassen sich damit grobe Übertragungsfehler erkennen. Bleibt z.B. das Muster im letzten Kanal bestehen, ist unterwegs ein Byte verloren gegangen. Wenn wir das mit mehreren Kanälen machen und dem DMX-Master außerdem beibringen, nicht nur eine Null sondern bestimmte Codes zu übermitteln, ist unsere Fehlererkennung schon einigermaßen passabel.

Kalibrier- und Korrekturkurven

Wenn wir ein logarithmisches Wahrnehmungsvermögen für Helligkeiten haben und trotzdem eine Lampe linear steuern, ist das nicht überzeugend. Deshalb habe ich in alle Slave-Controller logarithmische Kalibrierkurven eingebaut. Damit sieht das schon besser aus. Im dunklen Bereich ist die Abstufung feiner und im hellen Bereich lassen sich noch Stufen erkennen. Der Einbau einer Kalibriertabelle ändert den gesamten Programmcode nur minimal. Das ist im Programm DMX-Kalibtriertabelle durch den Vergleich der Programmzeilen für unkalibrierte direkte Ausgabe (mit // als Kommentar gekennzeichnet) mit der Ausgabe mit Kalibriertabelle sehr schön sichtbar. Das kurze Beispielprogramm ist für einen Arduino Mega 2560 zur Ausgabe der empfangenen Helligkeitswerte an 12 LEDs geschrieben. Alternativ lässt es sich auch mit Arduino Uno Controllern für je 6 LEDs verwenden. Eigentlich sollten alle Beleuchtungsgeräte über eine Kalibrierkurve verfügen, damit sie beim Dimmen einer standardisierten Charakteristik folgen. Tatsächlich gibt es das nur in sehr wenigen und teuren Geräten.

Viel wichtiger als für Licht ist eine individuelle Kalibrierung für die Fontänenhöhen meines Springbrunnens. Wir können kaum wahrnehmen, ob ein Strahler 10% heller oder dunkler leuchtet. Ob eine Fontäne 10 cm höher oder niedriger ist, fällt dagegen deutlich auf. Hinzu kommt, dass ein DMX-Steuerbyte ohne weitere Maßnahmen die Pumpenspannung einstellt. Der Zusammenhang zwischen Fontänenhöhe und Austrittsgeschwindigkeit an der Düse ist bereits quadratisch. Um doppelt so hoch zu spritzen, müssen wir also die Strahlgeschwindigkeit vervierfachen. In der Praxis sieht es so aus, dass wir die Pumpenspannung noch drastischer erhöhen müssen.

Die DMX-Prozessoren oder auch Slave-Controller betreiben einen recht großen Aufwand zur Kalibrierung.
Erstens gibt es eine Kalibriertabelle für die Helligkeit der Lampen/Strahler.
Zweitens gibt es eine Kalibriertabelle für die Fontänenhöhe. Doppelt so viel DMX-Signal soll zumindest ungefähr doppelt so hoch spritzen. Es war recht aufwendig die dafür nötige Tabelle zu erstellen. Das ging experimentell mit DMX-Editor und Metermaß.
Drittens liefern nicht alle Pumpen die gleiche Fontänenhöhe bei gleicher Spannung. Bei gleichem DMX-Steuersignal sollen sie es aber. Deshalb ist eine individuelle Höhenjustierung erforderlich. Das passiert mit Korrekturpolynomen (Parabeln), die für niedrige, mittlere und hohe Fontänen Korrekturen anbringen.

Den Aufwand zur Anpassung der Fontänen auf gleiche Höhe hatte ich anfangs völlig unterschätzt. Dabei geht es weniger um eine Controller-Programmierung sondern um eine ausgeklügelte Prozedur zum Einjustieren der Höhen, Ermittlung der Korrekturgrößen und individueller Speicherung dieser während des Brunnenbetriebs. Für diesen Zweck reagiert jeder Slave-Controller auf gemeinsame zusätzliche Kommandos.

Die Höhen verschiedener Fontänengruppen (im Extremfallfall alle) werden auf gleiche Höhen justiert. Dabei erhalten alle Controller naturgemäß unterschiedliche DMX-Steuergrößen. Eine bestimmte Folge spezieller DMX-Kommandos (auf sonst unbenutzten Kanälen) veranlasst alle ausgewählten Controller gleichzeitig, sich ihre individuellen und aktuellen Höhen-Steuergrößen zu merken. Das passiert insgesamt drei Mal für unterschiedliche Fontänenhöhen.
Anschließend veranlasst eine zweite Kommandofolge die Controller, Korrekturpolynome durch die drei gespeicherten Kalibrierpunkte zu berechnen. Diese Korrektur geht natürlich verloren, wenn der Brunnen ausgeschaltet wird. Deshalb gibt es noch einen dritten Schritt, in dem jeder Controller seine Polynomparameter in seinem internen nichtflüchtigen Speicher (EEPROM) ablegt. Beim erneuten Einschalten wird der nichtflüchtige Speicher ausgelesen und sein Inhalt zur Höhenkorrektur bereit gestellt.

home