Kontakt

µC-Steuerung der LED-Leuchte

Funktionsumfang

  • Simulation von Sonnenaufgang und Sonnenuntergang, sinusförmig, Dauer jeweils einstellbar
  • Standard PWM-Level einstellbar
  • Maximaler PWM-Level und Dauer einstellbar, Übergang vom/zum Standard PWM-Level sinusförmig innerhalb 10s
  • Wolkensimulation zuschaltbar, wolkig = fest 10% des simulierten Tageslichts, linearer Übergang
  • Wochen- und Tageszeitschaltur, 14 Zeiträume für Wochentage (Priorität vor täglich), drei tägliche Zeiträume
  • Voneinander unabhängige Helligkeitseinstellung der COB-Module relativ zum simulierten Tageslicht in 1%-Schritten
  • Fünf fest verdrahtete Gruppierungen der COB-Module: alle, vordere 7, mittlere 7, hintere 7, vordere/hintere 7, durchschaltbar
  • Manuelles Ein/Ausschalten, De/Reaktivierung der Tageslichtsimulation
  • Manuelles Dimmen in 2%-Schritten, relativ zum simulierten Tageslicht, bei Nacht und deaktivierter Tageslichtsimulation relativ zum physischen Maximum
  • Weiche Übergänge von Aus/Ein, Ein/Aus und der Gruppenansteuerung über 2s dauernde Exponential-Rampen
  • Datum und Uhrzeit einstellbar, Datum und Uhrzeit laufen dabei im Dialog weiter
  • LCD-On durch Tastendruck, LCD-Off nach 30s Nichtbenutzung außer bei Einstellung von Datum/Uhrzeit
  • Funkfernauslösen einer Kamera, akustische Ankündigung des Auslösens, optional MirrorUp mit 2s Abstand, 100% Licht beim Auslösen, Lichtverzögerung und -dauer einstellbar, manuelles Auslösen, drei Intervallmodi: Langintervall nur bei Tageslicht, Langintervall rund um die Uhr, Kurzintervall, Lang- und Intervalle einstellbar.

Hardware

  • Arduino Mega 2560 R3
  • Adafruit 16-Channel 12-bit PWM/Servo Driver, PCA9685 (12 Bit), I2C, 2x
  • Adafruit ChronoDot - Ultra-precise Real Time Clock, I2C
  • 20x4 LCD, I2C
  • P82B96P, I2C Bustreiber, 2x
  • 2x XBee XB24-AWI-001 und Adafruit XBee Adapter kit
  • Pololu Basic SPDT Relay Carrier with 5VDC Relay (Assembled)
  • Pololu Logic Level Shifter, 4-Channel, Bidirectional, XBee verlangt 3.3V Signale, Arduino aber liefert 5V
  • 5V 1,6A Steckernetzteil, versorgt den Arduino über USB
  • 9 Taster
  • USB-Kabel, 1,5m, zweckentfremdet
  • Gehäuse für die Steuerbox
  • Diverses Installationsmaterial

I2C Bustreiber

Zusammen mit der RTC und dem LCD befindet sich der µC in einer mobilen Steuerbox, die beiden PWM-Driver dagegen in der Leuchte. Dazwischen sorgen die beiden P82B96P für ein sauberes I2C-Signal auf dem USB-Kabel. Beide werden vom Arduino mit 5V versorgt, der in der Leuchte über das USB-Kabel.

Schaltplan I2C Bustreiber

Steuerbox

Sie gewinnt sicher keinen Schönheitswettbewerb, soll sie auch nicht.

Steuerbox

Tastenfunktionen

  • Kreuz: bedient das Menü; reaktiviert auch die Tageslichtsimulation
  • Rot: Ein/Ausschalten des Lichts; deaktiviert die Tageslichtsimulation
  • Gelb/grün: Hoch/Runterdimmen in 2%-Schritten
  • Blau: schaltet von einer der fünf COB-Modul-Gruppen zur nächsten

Funkfernauslöser

Dieser ist mit zwei XBee Modulen und einem SPDT-Relais realisiert. Die Software schaltet zum Auslösen ein Arduino-Pin timergesteuert für 100ms auf High, das über einen Pegelwandler an D0 des XBee-Senders angeschlossen ist. Der Empfänger schaltet mit D0 ein Relais, an dem das Auslösekabel der Kamera angeschlossen ist. Das Relais wird mit 5V versorgt. Der Steueranschluß aktiviert das Relais ab ca. 2,5V, ist also für die 3,3V eines XBees geeignet.

Zur Belichtungszeit wird die Aquarienbeleuchtung auf 100% Leistung eingeschaltet. Somit sind auch Nachtaufnahmen möglich. Die Verzögerung, mit der das Licht eingeschaltet wird sowie die Dauer der Beleuchtung ist in 20ms-Schritten einstellbar. Die z.Z. verwendete Nikon P7700 löst recht spät aus. Um die Beleuchtungsdauer während einer Aufnahme möglichst kurz zu halten, ist zusammen mit der Funkverbindung und dem Relais eine Verzögerungsdauer von 400ms notwendig. Beleuchtet wird 300ms lang. Die Verschlußzeit der Kamera beträgt 1/5s (=200ms) bei f/5.6 und ISO 100.

Das Langintervall ist derzeit auf 10min. rund um die Uhr eingestellt, also inkl. Nachtaufnahmen. Das Kurzintervall von 1min. nutze ich für die Zeitrafferaufnahme eines Wasserwechsels.

Mehr zur verwendeten XBee-Konfiguration:

Software

Entwicklungsumgebung

  • Eclipse Luna IDE for C/C++ Developers 4.4.0.20140612-0500
  • Arduino eclipse extensions 2.2.0.1
  • Arduino SDK 1.5.2
  • CollabNet Subversion Edge
  • Externe Libraries/Klassen: m2tklib, Liquid Crystal, Adafruit_PWMServoDriver, RTC_DS3231

Architektur/Design

Die Anwendung ist objektorientiert in C++ implementiert, Event-getrieben und Timer-getriggert. Sie basiert somit auf Event-Producer, Event-Consumer, einem Event-Dispatcher und einem Timer. Der Timer ist das alles zur Ausführung bringende Element. Er triggert alle 10ms.

Timer-Callbacks

Der Timer ist das das zentrale Steuerelement der Anwendung. Er leitet von seiner ISR sein Signal alle 10ms an registrierte Callback-Handler weiter. Diese sind:

  • TimerEventGenerator: erzeugt aus jedem zweiten Timer-Signal einen Timer-Event.
  • ButtonDeBouncer: liest von digitalen Input-Pins, entprellt diese im 10ms-Takt und erzeugt Key-Events.

Event-Producer

Folgende Objekte erzeugen Events:

  • TimerEventGenerator: erzeugt alle 20ms einen Timer-Event.
  • ButtonDeBouncer: erzeugt Key-Events aus entprellten Tastendrücken.
  • DimmerStorage: erzeugt LightSettings-Events bei Änderungen an der Tageslichtsimulation.
  • LightTimerStorage: erzeugt LightSettings-Events bei Änderungen an der Tages/Wochenzeitschaltuhr.
  • GUI: erzeugt LightSettings-Events bei einer Änderung von Datum/Uhrzeit.
  • LightController: erzeugt Display-Events zur Anzeige von Text auf dem LCD (z.B. den Grad der manuellen Dimmung).
  • Timelapse: erzeugt LightEvents (on, reset), um für Licht für die Zeitrafferaufnahmen zu sorgen.

Event-Consumer

In meiner Lichtsteuerung werden Event-Handler von den folgenden Klassen implementiert:

  • LightController:
    Verarbeitet Key-Events für das manuelle Dimmen und Durchschalten der LED-Gruppen.
    Verarbeitet LightSettings-Events für die Synchronisation bei Änderungen von Datum/Uhrzeit, der Tages/Wochenzeitschaltuhr und der Tageslichtsimulation.
    Verarbeitet LightEvents (on, off, reset), 100% Licht, Aus, zurück zur Tageslichtsimulation.
    Verarbeitet Timer-Events für
    • das Dimmen der LEDs zur Tageslichtsimulation
    • die Übergänge von Ein/Aus und des Durchschaltens der LED-Gruppen.
  • GUI:
    Verarbeitet Key-Events zum Einschalten des LCDs, wenn dieses nach 30s ausgeschaltet wurde.
    Verarbeitet DisplayEvents zum Anzeigen von nicht menüspezifischem Text auf dem LCD, z.B. den Level der manuellen Dimmung.
    Verarbeitet Timer-Events für
    • den sekündlichen Heart Beat (LED in der Steuerbox blinkt im Sekundentakt)
    • die Synchronisation der internen Softwareuhr mit der RTC alle 30 Minuten
    • das Abschalten des LCDs nach 30s Nichtbenutzung der Steuerbox.
  • KeyEventListener:
    Bindet das Menüsystem (m2tklib) an das Event Driven Framework an, indem es Key-Events in Befehle des Menüsystems transformiert und diese an das Menüsystem weiterleitet.

Event-Dispatcher

Der Event-Dispatcher ist Teil eines Frameworks und besitzt folgende Aufgaben:

  • Er nimmt Registrieranfragen für Event-Handler entgegen. Bei der Registrierung wird für den Event-Handler eine Event-Mask angegeben, die bestimmt, für welche Events sich der Event-Handler interessiert. Ohne die Event-Mask müßte der Event-Handler für jeden auftretenden Event aufgerufen werden, was nicht besonders effizient wäre. Die Registrierung erfolgt in der Initialisierungsphase der Anwendung.
  • Er nimmt Events entgegen und stellt sie in in den Anfang der Event-Queue ein. Die Queue ist limitiert auf 20 Events. Überzählige Events werden verworfen.
  • Er dispatcht Events, d.h., er ruft für einen Event alle Event-Handler auf, die sich für diesen Event-Typ interessieren, und das in der Reihenfolge ihrer Registrierung. Der nächste zu dispatchende Event wird aus dem Ende der Event-Queue gezogen. Ein Event kann von einem Handler als consumed markiert werden. In diesem Fall wird der Event nicht weiter dispatcht (die nachfolgenden Handler werden nicht aufgerufen). Dieser Fall tritt z.B. dann ein, wenn ein Tastendruck das LCD einschaltet und dieser Tastendruck nicht die ihm ansonsten zugedachte Funktion erfüllen darf (verhindert blindes Eintippen).

Der Event-Dispatcher wird zum Dispatchen von Events permanent in der Main-Loop aufgerufen.

Die Anwendung ist dahingehend getrimmt, die Event-Queue nicht zum Überlaufen zu bringen, da die Anwendung ansonsten teilweise ausfällt. Die eingestellten 50 Timer-Events pro Sekunde sind wenig genug, damit genau das nicht passiert, und viel genug, damit das Auge die Dimmer-Schritte nicht wahrnehmen kann, da die Änderungen des PWM-Levels (12 bit Auflösung) klein und schnell genug ausfallen. Bei sehr niedrigen PWM-Leveln sieht das Auge trotz der vergleichsweise hohen PWM-Auflösung dennoch Helligkeitsänderungen schrittweise. Das liegt am Auge und Gehirn, das Helligkeit logarithmisch wahrnimmt und bei geringer Helligkeit empfindlicher reagiert.

Die Anwendung darf sich nicht auf eine Reihenfolge verlassen, mit der Events auftreten. Events treten in einer willkürlichen Reihenfolge auf, so wie sie der Benutzer am User Interface oder die Anwendung selbst erzeugt.

Dimmen

Alle abrupten Helligkeitsänderungen werden durch timergesteuerte Übergänge vermieden, außer bei Zeitrafferaufnahmen. Auch das Dimmen durch die Tageslichtsimulation ist timergesteuert. Über den Timer - genauer den TimerEventGenerator, der am Timer hängt - wird alle 20ms ein Timer-Event ausgelöst, für den die Lichtsteuerung beim Event-Dispatcher registriert ist. Die Lichtsteuerung übergibt so 50x in der Sekunde einen Timestamp in 20ms-Genauigkeit einem Dimmerobjekt, das für den Timestamp den PWM-Level errechnet. Dieser PWM-Level wird mit dem globalen und dem LED-spezifischen Helligkeitsgrad (0-100%) verrechnet und über I2C an den jeweiligen PWM-Driver ausgegeben.

Das Dimmerobjekt kann variieren. Für die Tageslichtsimulation ist es ein aus Sinus-Rampe, Wolkensimulation und max. PWM-Level zusammengesetztes Composite-Objekt, wobei die Wolkensimulation eine Linear-Rampe und der max. PWM-Level wiederum eine Sinus-Rampe nutzt. Für das manuelle Ein/Ausschalten und das Gruppenumschalten ist es eine einfache Exponential-Rampe.

Rampenfunktionen für's Dimmen

Die jeweiligen Hell/Dunkelübergänge mögen einer mathematischen Funktion entsprechen:

  • Exponentiell: f(x) = 1.0384 * (33*x-3 - 0.037)
  • Sinus: f(x) = 0.5 + sin(x*pi-pi/2)/2
  • Linear: f(x) = x

Sie liefern für 0 ≤ x ≤ 1 (Zeit) Werte zwischen 0 und 1 (PWM-Level). Entsprechend muß die Uhrzeit für das Zeitintervall, für das diese Funktionen angewendet werden sollen, in den Bereich von x skaliert werden. Dito f(x) für den PWM-Level.

Jede dieser Funktionen ist in eine eigene Klasse gekapselt, die von einer gemeinsamen Basisklasse erbt, die wiederum ein Interface implementiert. Die Composite-Klasse, die diese Interface-Implementierer und auch sich selbst aufnehmen kann und sie der Reihe nach aufruft, implementiert dasselbe Interface.
Über dieses Interface sind sie alle gleichgeartet und beliebig austauschbar: polymorph. Geil.

Klassendiagramm