Vorgeschichte
Auf der Suche nach der ultimativen Beleuchtung musste ich leider feststellen, dass es so etwas nicht gibt. Deckenfluter und Schwarzlichtröhren sind ja schon fast Standard, also: Selbst ist der Nerd. Erstmal eine Liste machen. Was macht denn die ultimative Beleuchtung überhaupt aus?
Vorüberlegungen
Eine solche Lampe sollte sein
- vollständig konfigurierbar
- standalone zu betreiben
- natürlich auch dynamisch ansteuerbar
- über einen standard-Bus in grösseren Mengen adressierbar
- nicht übermässig teuer
- ohne grösseren Stress und Hardwareaufwand selbst zu löten
- intelligent
Leuchtmittel
Als erstes galt zu überlegen, womit man eigentlich "Licht machen" will. Leds sind toll für sowas: Sehr hell, geringer Stromverbrauch und günstig zu bekommen. Standard-Leds haben jedoch meistens eine Helligkeit von etwa 20-50 mcd (Milli Candela), das ist für Beleuchtungszwecke arg dunkel (oder man müsste VIELE Leds nehmen, und das wäre dann wieder nicht unbedingt billig und ziemlich aufwändig zu löten.) Mittlerweile gibt es aber auch günstige helle Leds mit 7000-15000 mcd (zB. bei leds.de).
Farbe!
Mit dem RGB-Farbraum und additiver Farbmischung lässt sich ein grosser Teil der für den Menschen wahrnehmbaren (und schönen!) Farben erzeugen. Dabei wird einfach mit den drei Grundfarben Rot, Grün und Blau in jeweils verschiedenen Helligkeiten eine möglichst weisse Oberflächen beleuchtet. Die Farbe Weiss ergibt sich bei gleicher Helligkeit aller drei Farben, Schwarz durch die Abwesenheit von Licht. Dies funktioniert natürlich am besten ohne jegliche Beleuchtung von aussen, sprich: Nachts. Aber das ist ja durchaus gewollt, deckt diese Zeit doch den Hauptlebensrhythmus des gemeinen Nerds zufriedenstellend ab.
Leds lassen sich nicht, wie normale Glühlampen, über die angelegte Spannung dimmen, deshalb lässt man die Leds flimmern und dimmt über das Verhältnis von Einschaltzeit zu Ausschaltzeit (PWM, Pulsweitenmodulation.) Damit dieses Geflacker nicht beim Arbeiten stört, benutzt man einen Basistakt von 100Hz, der über der vom Auge wahrnehmbare Flackerschwelle gelegt. Ein Takt dauert also 1/(100) s=10 ms. Da das Auge ein Problem mit gepulsten Leds hat (diese werden heller empfunden, als sie der Photonenausbeute nach sind), ist das Verhältnis zwischen gewünschter Helligkeit (H) und Einschaltanteil (E) in einem solchen Takt leider nicht linear, sondern H ~ E^g (0<=H<=1, 0<=E<=1), wobei g für die Gamma-Korrektur steht. Probieren hat gezeigt, dass g=2 gut funktioniert.
Prozessor
Ich habe mich entschieden, einen Atmel Atmega8 als Prozessor zu benutzen. Dieser Prozessor hat mehrere Features:
- Systemtakt bis 16MHz
- 8Kb Flashrom
- 1Kb RAM
- 512 Byte EEPROM
- mehrere Timer
- Interrupts für alle Ereignisse
- Hardware für Kommunikation via rs232 und i2c
- geringe Baugrösse (28Pin DIL-S Gehäuse)
- günstig (in der Regel ~2,70EUR bei Reichelt)
Da die in diesem Prozessor eingebaute Hardware-PWM mit 10Bit eine zu geringe Auflösung für 256 "echte" Helligkeitsstufen hat, habe ich mich entschlossen, die PWM in Software selbst zu realisieren.
Software
Taktet man den Atmega8 mit 16MHz, dauert die Ausführung einer Instruktion (also die minimal mögliche Zeit zwischen einer Ein- und einer Ausschaltphase) 1/(16*10^6) Sekunden. Einen PWM-Basistakt von 100Hz (also 100 PWM-Takten pro Sekunde) vorausgesetzt bleiben von diesen 16*10^6 Zyklen pro Sekunde noch 16*10^6/100 = 16*10^4=160000 Zyklen für einen PWM-Takt übrig. Nun zeigt sich das nächste Problem: Der Atmega8 ist eine 8Bit CPU und hat nur einen einzigen 16Bit Timer, aber bereits ab einer Helligkeit von 164/256 ist die Anzahl der Takte, in der die Leds eingeschaltet sein muessen, etwa 66180 ((164/255)^2*160000 ~= 66180), was nicht mehr mit einem 16Bit breiten Register erfassbar ist.
Dieses Problem kann man umgehen, indem nicht die Anzahl der Zyklen vom Einschalten der Leds bis zum Ausschalten gezählt wird, sondern die Anzahl der Takte zwischen zwei Helligkeitsstufen. Man lässt also den Prozessor mit Interrupts von Zeitschlitz zu Zeitschlitz hüpfen. Wegen der Gamma-Korrektur wird diese Zeit immer länger, ist aber zu jeder Zeit kleiner als 65535 (maximale Anzahl Takte, die der 16Bit Zähler erfassen kann.) Die maximale Zeit liegt natürlich zwischen Helligkeit 254 (fast die ganze Zeit eingeschaltet) und Helligkeit 255 (die ganze Zeit eingeschaltet): 160000-(254/255)^2*160000 ~= 1252).
Der Atmega8 kann so programmiert werden, dass der 16Bit Timer ein Register inkrementiert und in einen Interrupt springt, sobald ein voreingestellter Wert erreicht wird. Dies will ich nutzen, um den Status der Leds zu ändern. Nach dem Sprung in einen solchen Interrupt ist also zu testen, ob eine Statusänderung bei einer der drei Farbkanäle notwendig ist und wenn ja, muss sie ausgeführt werden, und anschliessend muss der zu erreichende Wert für den nächsten Timerinterrupt geladen werden. Jetzt haben wir wieder ein Problem: Die Zeit zwischen zwei Zeitschlitzen ist für diese Dinge bis etwa zur Helligkeit 15 zu kurz (Abstand zwischen dem Zeitschlitz für Helligkeit 0 und 1: 2 Takte). Dem kann man zum einen durch vorheriges Berechnen der auszugebenden Werte (so dass im Interrupt dann nur noch ein Byte aus dem Speicher in das Ausgaberegister kopiert werden muss, Zeit genug ist zum Beispiel im letzten Zeitschlitz) und zusätzlich durch Zusammenfassen der ersten 16 Zeitschlitze zu einem einzigen Interrupt.
statische Scripte
Feste Abläufe sind relativ einfach einzubauen, eine Anweisungskette bestehend aus einem Byte zusammengesetzt aus dem Opcode (höherwertiges Nibble) und Flags (niederwertiges Nibble) sowie einem oder drei Byte Daten ist schnell implementiert und so kann das Fnordlicht bereits jetzt einfache Scriptabläufe (zb. "setze Farbkanal R auf Helligkeit X", "fade bis Farbe XYZ und warte darauf, dann springe an den Anfang zurück") aus dem Flash-Rom ausführen.
Ansteuerung
Nach obiger Zielsetzung soll eine Lampe sowohl standalone als auch im Kollektiv zu betreiben sein. Eine einzelne Lampe ist mit dem geringsten Hardwareaufwand seriell anzusteuern, eine Gruppe von Lampen eher per I2C. Der Atmega8 hat da gleich Hardware eingebaut, so dass man sich (von der Lampenseite aus) darauf beschränken kann, die eigene Adresse zu setzen und anschliessend auf den entsprechenden Interrupt zu reagieren.
Im Endausbau soll es möglich sein, die Lampe komplett über I2C fernzusteuern, die serielle Schnittstelle wird zur Zeit nur zum Debuggen der Firmware benutzt. Der Fokus wird auf dem Bus zur Ansteuerung liegen, da ja die Beleuchtung von Räumen, also Installationen von mehreren Lampen, im Vordergrund stehen.
Die Integration der Kommunikationsschnittstellen in die Scriptsprache ("sende Byte X an Lampe Y" oder "warte auf Byte Z") als auch das Ausführen von Scriptbefehlen aus einem Fifo, was von extern befüllt wird sind geplant.