Weckruf

Der c't-Netzschalter weiß, wie spät es ist und wann er das Licht einschalten soll. Seine Programmierung erfolgt per Webbrowser auch vom Hotel aus. Kommt man doch mal früher nach Hause, reagiert er auf die Signale einer IR-Fernbedienung.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 24 Min.
Von
  • Benjamin Benz
Inhaltsverzeichnis

Der c't-Netzschalter, wie wir ihn im vorletzten Heft vorgestellt haben, schaltet bis zu sechs 230-Volt-Steckdosen und hört auf Befehle, die der Administrator per Webbrowser übermittelt [1]. Nun ist es an der Zeit, die Software zu verfeinern und ihm neue Funktionen beizubringen: Eine Zeitschaltuhr steuert alle Endgeräte einzeln nach einem frei konfigurierbaren Zeitplan an. Direkten Zugriff - beispielsweise um das Licht anzuschalten, sobald man nach Hause kommt - bietet ein Empfänger für die Signale von Infrarotfernbedienungen. Die noch brachliegenden Messschaltungen für die Leistungsaufnahme der Verbraucher lassen sich ebenfalls praktisch einsetzen: Eine Master-Slave-Schaltung schaltet auch den Monitor aus, sobald der PC sich schlafen legt.

Um alle Möglichkeiten der Hardware des c't-Netzschalters auszuschöpfen, lohnt es ein wenig Zeit in die Programmierung der Firmware zu investieren. Dieser Artikel zeigt, wie man eine präzise Uhr programmiert und sich die aktuelle Zeit aus dem Internet holt. Das Ablegen von Daten im nichtflüchtigen Speicher und das Erfassen der Leistungsaufnahme über die Analog-Digital-Umsetzer (ADU) des Mikrocontrollers sind genau wie die Abfrage einer IR-Fernbedienung kein Hexenwerk. Fast alle vorgestellten Routinen laufen übrigens auch auf dem Schwesterprojekt c't-Mikrocontroller-im-LAN [2] - dann allerdings ohne 230-Volt-Steckdosen. Umgekehrt lassen sich alle in [3] vorgestellten Sensoren auch an den c't-Netzschalter anschließen.

Für die ATmega-Mikrocontroller existiert eine ganze Reihe von Cross-Compilern für diverse Programmiersprachen - teils kommerziell, teils kostenlos. Wir haben uns zur Realisierung der Beispielprogramme für die Programmiersprache C entschieden und lassen ein gcc-Derivat den Code übersetzen. Unter Windows bildet das kostenlose WinAVR-Kit (Soft-Link) eine komplette Entwicklungsumgebung rund um den gcc. Alle benötigten C-Libraries sowie die Header-Dateien für viele Atmel-Bausteine bringt es auch gleich mit. Wer Linux einsetzt, muss den Compiler und die Bibliotheken getrennt installieren, alle benötigten Dateien finden sich aber unter dem Soft-Link. Atmel selbst bietet mit dem kostenfreien AVRStudio ebenfalls eine Entwicklungsumgebung, die seit kurzem auch den gcc integriert. Der Debugger des AVRStudios hilft übrigens auch bei der Fehlersuche in Programmen, die mit anderen gcc-Versionen übersetzt wurden.

Die fertigen Binaries (.hex-Dateien) überträgt entweder WinAVR oder das ebenfalls kostenlose PonyProg über die ISP-Schnittstelle (In System Programmable) in den Mikrocontroller. Dabei ist, wie schon in [1] beschrieben, auf die korrekte Einstellung der Fuse-Bits zu achten, denn diese legen fest, woher der Prozessor seinen Systemtakt bezieht und ob er weitere Schreib-/Lesezugriffe über die ISP-Schnittstelle akzeptiert.

Der ATmega8535 besitzt drei Arten von Speicher: Der Programmcode und die Werte von Konstanten liegen im Flash-Speicher (8192 Byte). Das EEPROM (512 Byte) nimmt zur Laufzeit Daten auf, die auch nach einem Stromausfall noch benötigt werden und im SRAM (512 Byte) liegen alle Variablen.

Die Ausgaben, die der Linker des gcc nach getaner Arbeit liefert, zeigen, wie viel Platz das Programm in den einzelnen Bereichen benötigt (siehe Bild S. 223). Die .text- (Programmcode) und .data-Sektion (Konstanten, Startwerte der Variablen) landen beide im Flash-Speicher und dürfen daher zusammen nicht größer als 8192 Byte sein - der Linker warnt leider erst, wenn die .text-Sektion zu groß wird und vergisst dabei den für die Konstanten benötigten Teil. Die Daten in der .eeprom-Sektion landen direkt im EEPROM. Sie überschreiben alle bereits im Controller gespeicherten Werte, wenn man das komplette Hex-Image (PonyProg: WriteAll) überträgt.

Die Schätzung des benötigten Arbeitsspeichers ist etwas komplizierter: Dort müssen alle initialisierten globalen Variablen aus .data, die nicht initialisierten aus .bss, der Stack (inklusive lokaler Variablen) und der Heap (dynamische Variablen) Platz finden. Überschreitet das eigene Programm eine dieser Grenzen, kommt es zu merkwürdigem, unvorhersehbarem Verhalten und zu Abstürzen.

Damit unsere Beispiel-Firmware noch Platz für eigene Erweiterungen im Flash-Speicher lässt, besteht sie aus mehreren Modulen. In der Header-Datei mcu.h legt eine ganze Reihe von #define-Ausdrücken fest, welche Module der Compiler in die Firmware einbauen soll. So kann man beispielsweise den Quelltext, der für Ausgaben auf dem LC-Display sorgt, getrost weglassen, wenn man kein solches angeschlossen hat. Einige Module setzen sich allerdings gegenseitig voraus, aber auch das prüft der Präprozessor. Es reicht leider nicht aus, unbenutzte Funktionen im Hauptprogramm auszukommentieren, denn der Linker der AVR-GCC-Umgebung bindet eine Objektdatei ganz oder gar nicht ein. Sobald eine einzige Funktion aus einer Bibliothek ein einziges Mal im Code auftaucht, macht sich die ganze Library im Binary breit.

Assembler-Freunde könnten nun einwenden, dass alle Funktionen unserer Demo-Firmware spielend in den Flash-Speicher passen würden, hätten wir sie in Maschinensprache implementiert. Der Entwicklungsaufwand wäre jedoch ungemein höher gewesen und der Code längst nicht so übersichtlich und erweiterbar geworden. Seit längerem haben auch in der Mikrocontroller-Welt die Hochsprachen die Vorherrschaft übernommen. Handoptimierte Assembler-Routinen schreibt man eigentlich nur noch für besonders laufzeitkritische Passagen.

Wer keine Lust hat, sein Programm abzuspecken, auf Assembler umzustellen oder auf Funktionen zu verzichten, greift einfach zu einem anderen Prozessor. Der ATmega16 (16 KByte Flash, 512 Bytes EEPROM, 1 KByte SRAM) und der ATmega32 (32 KByte Flash, 1 KByte EEPROM, 2 KByte SRAM) sind beide code- und pinkompatibel zum ATmega8535 und kosten nur unwesentlich mehr. Einzig im Makefile muss man den CPU-Typ ändern (MCU=atmega16 oder MCU=atmega32) und das ganze Projekt neu übersetzen (make clean; make).

Die Firmware des c't-Netzschalters muss eine ganze Reihe unterschiedlicher Aufgaben erledigen: Kommandos, die der Xport aus dem Netzwerk empfängt und an die serielle Schnittstelle des Controllers weiterleitet, sind zu beantworten. Die Taster, LEDs und das Display dienen der Kommunikation mit dem Benutzer und sollen flüssig auf Eingaben reagieren oder Werte anzeigen. Die Messung der Leistungsaufnahme der Verbraucher erfordert viele Abfragen der A/D-Umsetzer pro Sekunde und die Echtzeituhr (Real Time Clock, RTC) darf keine einzige Sekunde verschlafen. Einmal pro Sekunde steht dazu noch die Auswertung der Zeitschaltereignisse an.

Theoretisch wäre es möglich, alle diese Aufgaben dem Hauptprogramm und damit einer Endlosschleife zu übertragen. Ein genaues Timing wäre dann jedoch kaum erreichbar und der Code würde extrem kompliziert. Die Timer und diverse Interrupt-Quellen des ATmega bieten eine viel elegantere Lösung: Jeder Timer läuft völlig unabhängig von den anderen, beansprucht keinerlei CPU-Zeit und generiert in einstellbaren Intervallen Interrupt-Signale. Der Prozessor unterbricht daraufhin seine aktuelle Arbeit und verzweigt in die zugehörige Interrupt-Service-Routine (ISR). Diese aktualisiert beispielsweise die Uhr oder liest den A/D-Umsetzer aus und kehrt dann zum eigentlichen Code zurück. Das Hauptprogramm kümmert sich um den Rest und wertet die von den ISRs gesammelten Daten aus.

Der ATmega8535 bringt gleich drei solcher Timer mit (zwei mit 8- und einen mit 16-Bit-Zähler), von denen jeder mehrere Interrupts auslösen kann. Mit jedem Clock-Signal erhöht der Timer sein Zählregister (TCNTx) um eins, vergleicht es dann mit dem zugehörigen Compare-Register (OCRx) und löst wenn nötig einen Interrupt aus (OCFx bei OCRx gleich TCNTx, TOVx bei Zählerüberlauf). Wie schnell dies geschieht, entscheidet der verwendete Zählertakt.

Ein Vorteiler stellt verschiedene Frequenzen zur Verfügung, die er durch Teilen aus dem Systemtakt (14 745 600 Hz) erzeugt. Im Timer-Control-Register (TCCRx) legen drei Bits (CSx0 bis CSx2) fest, welchen Takt der Zähler verwendet (siehe Tabelle im Datenblatt). Weitere Details zur Timer- und Interrupt-Programmierung finden sich im Quelltext oder unter [3].

In der Beispiel-Firmware aktualisiert der Timer0 alle 5 ms die Echtzeituhr - 5 ms ist der größte gemeinsame Teiler von 1/14 745 600 Hz und 1000 ms. Der Acht-Bit-Timer kann nur bis 255 zählen und so fällt die Wahl auf den höchstmöglichen Vorteiler (1024) und somit einen Takt von 14 400 Hz. Der Vergleichswert ergibt sich dann zu (14 400 Hz · 5 ms -1 = 71):

#define XTAL 14745600
#define CLOCK0
...
OCR0=(XTAL/1024/CLOCK0)-1;
TCCR0= _BV(WGM01) | _BV(CS00) | _BV(CS02);

Das WGM01-Bit sorgt dafür, dass der Zähler nach jedem Erreichen des Vergleichswertes wieder bei Null anfängt zu zählen. Den Anfangswert schreibt man direkt in das Timer-Register und aktiviert zuletzt die Interrupts:

TCNT0 = 0;
TIMSK |= _BV(OCIE0);
sei();

Das OCIE0-Bit im TIMSK-Register schaltet den Vergleichs-Interrupt ein und das Makro sei(), das in avr/interrupt.h definiert ist, aktiviert systemweit alle Interrupts.

Die Interrupt-Service-Routine unterscheidet sich dank weiterer Makros aus avr/signal.h kaum von einer gewöhnlichen C-Funktion. Sie beginnt mit dem Schlüsselwort SIGNAL, gefolgt vom Namen des zu behandelnden Interrupts:

SIGNAL (SIG_OUTPUT_COMPARE0){
rtc_isr();
...
}

Während der Ausführung der Funktion sind alle anderen Interrupts blockiert. Wer das nicht will, nutzt statt SIGNAL das Makro INTERRUPT, muss dann aber selbst dafür sorgen, dass sich Aufrufe nicht gegenseitig überlappen. Grundsätzlich gilt, dass ein Programm möglichst wenig Zeit in ISRs verbringen soll. Aufwendige Operationen gehören dort nicht hin.

Die ISR von Timer0 fragt bei jedem Aufruf außerdem die Zustände der Taster und eines eventuell angeschlossenen Drehgebers ab. Timer1 kümmert sich 1024-mal pro Sekunde um den A/D-Umsetzers und Timer2 wertet die Signale einer Infrarotfernbedienung aus.

Der A/D-Umsetzer (ADU) arbeitet im Auto-Triggered-Modus

SFIOR |= _BV(ADTS0)| _BV(ADTS2);
ADCSRA |= _BV(ADATE);

und beginnt automatisch eine neue Messung, sobald TCNT1 den Wert von OCR1B erreicht. Dazu muss zwar der Timer-Interrupt aktiv sein,

TIMSK |= _BV(OCIE1B); 

seine ISR (SIG_OUTPUT_COMPARE1B) bleibt aber leer. Eine eigene ISR des Wandlers (SIG_ADC) speichert das Ergebnis dann in einem Ringpuffer ab. Das ADIE-Bit im ADCSRA-Register aktiviert sie und die Bits ADEN und ADSC setzen den Umsetzer in Betrieb. Der interne Wandlungstakt des ADU (ADPS0 bis ADPS2) spielt eine untergeordnete Rolle, solange eine Wandlung innerhalb von zwei Timer-Ereignissen möglich ist.

ADCSRA= _BV(ADPS2) | _BV(ADPS1)| _BV(ADPS0) | _BV(ADEN)| _BV(ADSC)| _BV(ADIE);

Welcher der acht analogen Eingangskanäle abgefragt wird, steht im Register ADMUX. Das Hauptprogramm wählt hier einen Kanal aus, darf dabei aber die Bits REFSx und ADLAR nicht verändern, da sie die Referenzspannung und die Ausrichtung der Daten auswählen.

ADMUX &= _BV(REFS0)|_BV(REFS1)|_BV(ADLAR);
ADMUX |= kanal;

Um den Strom durch die Verbraucher zu messen, stehen zwei Varianten zur Wahl: Entweder man erfasst nur die positiven Halbwellen mit hoher Auflösung oder das ganze Signal und büßt dabei Genauigkeit ein.

Auf der Grundplatte des c't-Netzschalters messen zwei induktive Stromfühler den Strom, der die Verbraucher an Buchse 1 und 2 durchfließt (siehe auch [1]). Ein Blick auf deren Beschaltung zeigt, dass zwei verschiedene Messmethoden möglich sind: Verbindet man zwischen den beiden Stiftleisten jeweils Pin 2 und 3, so bleibt die Konstantstromquelle (IC6) außen vor. Der A/D-Wandler erkennt nur die positiven Halbwellen und schneidet die negativen ab. Diese muss die Software rekonstruieren. Dafür lässt sich über den Trimmer TR1 die Verstärkung optimal an den Messbereich anpassen.

Nutzt man hingegen Pin 1 und 3, so hebt die Konstantstromquelle das Signal um 2,5 Volt an, die negativen Halbwellen gehen nun nicht mehr verloren. Allerdings wirkt sich dadurch die Verstärkung auch auf den Offset aus. Um den Messbereich zu ändern, muss man zuerst den 100-Ω-Widerstand auswechseln und dann TR1 wieder so einstellen, dass der Nulldurchgang bei 2,5 Volt liegt.

Die Messung des Stroms allein reicht nur bei Ohmschen Lasten, die keine Phasenverschiebung zwischen Strom und Spannung erzeugen, aus, um die Leistung zu berechnen. Die Spannung greifen wir an der Sekundärwicklung des Trafos ab und führen sie an die Analog-Ports PA2 und PA3. Jeder der beiden Ports erfasst eine Halbwelle - PA2 die positive und PA3 die negative. Da PA2 im ursprünglichen Projekt für einen lichtempfindlichen Widerstand vorgesehen war, muss man vor der ersten Messung dessen Vorwiderstand (R19 auf dem B-Modul) entfernen.

Misst man regelmäßig den Strom I(t) und die Spannung U(t) - zusammengesetzt aus den beiden Halbwellen - lässt sich die elektrische Wirkleistung P bestimmen:

Da die Messungen aber diskret in der Zeit sind, ergibt sich folgende Näherung:

DeltaT ist die Zeit zwischen zwei Messungen, also der Takt des Timers, der die ADC-Funktionen aufruft. T sollte mindestens zwanzig 50-Hz-Wellen umfassen, damit Randeffekte sich nicht allzu stark auswirken. Des weiteren müssen alle drei Messungen kurz hintereinander erfolgen. Am einfachsten geht dies, wenn die ISR des AD-Wandlers die drei Messungen nacheinander anstößt.

Ganz andere Herausforderungen bringt der Ausbau des c't-Netzschalters zur Zeitschaltuhr mit sich: Er benötigt eine relativ genaue Uhr, die Sekunden, Minuten, Stunden, Tage und Co. zählt. Der ATmega8535-Controller besitzt dafür eine eigene Funktionseinheit. Diese würde aber einen zweiten speziellen Uhrenquarz erfordern und dadurch zwei I/O-Pins belegen. Für unsere Zwecke bietet sich eine andere Lösung an. Der Takt, den der Hauptquarz liefert, liegt stabil bei 14 745 600 Hz. Stellt man einen der drei Timer geschickt ein, so lässt sich eine komplette Echtzeituhr (Real Time Clock, RTC) in Software implementieren. Die Auflösung ist dabei allerdings auf 5 ms beschränkt - alles andere würde Teiler voraussetzen, die nicht ganzzahlig sind. Allerdings stören die anderen ISRs, während deren Ausführung die Interrupts deaktiviert sind, das Timing ein wenig. Im schlimmsten Fall verpasst man gelegentlich das eine oder andere 5-ms-Signal und diese kleinen Fehler kumulieren sich im Laufe eines Tages zu ein paar Sekunden.

Hat der Netzschalter aber Zugang zum Internet, kann er bei Bedarf seine Systemzeit über das Netzwerk aktualisieren. Timeserver bieten über relativ komplexe Protokolle (NTP und SNTP) sehr präzise Methoden zum Abgleich von Uhren über das Internet. Diese Verfahren gleichen sogar Verzögerungen bei der Datenübertragung aus. Spielt die letzte Millisekunde keine Rolle, kommt man viel einfacher an die aktuelle Uhrzeit: Jeder Webserver überträgt seine Systemzeit in der Antwort auf einen HTTP-Request.

Der Vorteil des HTTP-Protokolls liegt darin, dass die Daten als lesbare ASCII-Zeichen verschickt werden. Die einfachste Anfrage (Request) an einen Webserver besteht aus nur zwei Zeilen: Die erste beginnt mit dem HTTP-Befehl gefolgt von der angefragten Seite und der Protokollversion. In der zweiten Zeile steht die Adresse des angesprochenen Webservers:

HEAD /index.html HTTP/1.1
Host: www.heise.de

Diese Anfrage beantwortet der Server unter anderem mit seiner Systemzeit und ein paar Informationen über die angeforderte Datei. Der Xport kann nicht nur auf einem TCP-Port lauschen und eingehende Daten an den Mikrocontroller weiterreichen, sondern auch aktiv Verbindungen aufbauen. So kann der Mikrocontroller in regelmäßigen Abständen einen Webserver kontaktieren und seine interne Uhr abgleichen.

Die IP-Adresse des Webservers und den TCP-Port (80) überträgt der Deviceinstaller von Lantronix in den Xport. Sobald dieser ein Zeichen auf der seriellen Leitung empfängt und nicht schon eine TCP-Verbindung besteht, baut er eine neue zu dem angegebenen Rechner auf. Schickt der ATmega nun einen gültigen HTTP-Request, muss er nur noch die Antwort des Webservers nach dem Date-Feld durchsuchen und kann dann die Uhr danach stellen.

Das Format des Date-Feldes ist in der Spezifikation des HTTP-Protokolls seit der Version 1.1 (RFC 2616) festgelegt. Dort sind aus historischen Gründen drei verschiedene Datumsformate aufgeführt, zum Einsatz kommen aber nur noch die 29 Zeichen langen Strings nach RFC 1123:

Fri, 25 May 2006 04:20:42 GMT 

Leider schreibt das HTTP-Protokoll die Angabe der Uhrzeit für die Zeitzone GMT (Greenwich Mean Time) vor, die keine Sommer- und Winterzeit kennt. Das muss die Firmware ausgleichen. Die lokale Zeitzone steht als Präcompilerkonstante in der Datei rtc.c und wird beim Setzen der Zeit (rtc_get_httptime()) zur Variablen rtc_hour addiert - für Deutschland gilt:

#define TIMEZONE 1 

Das Signal einer RC5-Infrarotfernbedienung nutzt eine Trägerfrequenz von 36 kHz (unten). Jedes Datenbit setzt sich aus zwei Halbbits zusammen, für ein Low-Halbbit bleibt das Trägersignal 889 µs aus (mitte). Vierzehn Bits ergeben ein RC5-Kommando und werden erst nach einer längeren Pause wiederholt (oben).

Das Ergebnis der Funktion rtc_summertime() kommt ebenfalls zu rtc_hour hinzu. Sie liefert den Wert „1“, wenn das Datum zwischen dem jeweils letzten Sonntag im März und Oktober liegt. Die Stichzeit ist jeweils zwei Uhr Nachts. Zu beachten ist dabei auch, dass in einer anderen Zeitzone bereits ein anderer Tag sein kann. Es lohnt sich übrigens, per Telnet (telnet WEBSERVER 80) zu überprüfen, ob die Uhr des Webservers korrekt gestellt ist.

Steht eine genaue Systemzeit zur Verfügung fehlt nicht mehr viel zu einer komfortablen Zeitschaltuhr. Das Hauptprogramm prüft in seiner Endlosschleife, ob bereits eine Sekunde vergangen ist (rtc_newsecond==1). Dann ruft es die Funktion event_cron() auf und diese kontrolliert, ob aus einer Liste von Ereignissen eines zutrifft. Ein C-Struct enthält alle benötigten Daten:

typedef struct {
char second, minute, hour;
char day, month;
int year __attribute__ ((packed));
char weekday;
action_t action;
} event_t;

Ein Ereignis tritt auf, wenn die ersten sieben Felder mit dem Systemdatum übereinstimmen. Damit nicht nur einmalige Ereignisse möglich sind, ignoriert die Funktion event_cron() alle Felder, die den Wert DONTCARE (#define DONTCARE 255) enthalten. Week-day wird als Bitfeld interpretiert und kann dadurch für mehrere Tage auf einmal gelten. Bit 0 steht dabei für Sonntage. Ein Ereignis mit:

second=00; minute=00; hour=12;
day=DONTCARE;month=DONTCARE;year=DONTCARE;
weekday=0x3e;

tritt jeden Werktag um 12 Uhr mittags auf. Dann kommt der Inhalt des Action-Feldes zum Tragen. Es setzt sich aus drei einzelnen Parametern zusammen und hat eine Gesamtlänge von 16 Bit - die Länge jedes einzelnen Teilfeldes steht hinter dem Doppelpunkt:

typedef struct {
unsigned char channel:7;
unsigned char special:1;
unsigned char data:8;
} action_t;

Solche Bitfelder sparen Speicherplatz, da jede Variable nur die wirklich benötigten Bits belegt. Der Zugriff erfolgt wie bei Structs üblich über den Punkt- oder den Dereferenzierungs-Operator:

action.special=1; 

Der RC5-Code sieht bis zu 32 Geräte (Ax) und 128 Kommandos (Cx) vor. Das Toggle-Bit (T) zeigt, ob eine Taste dauerhaft gedrückt ist.

Der neue Wert für die Relais steht in action.data und in action.channel eine Maske, die einzelne Relais auswählt. Hat action.special den Wert eins, enthält action.data die auszuführende Aktion wie beispielsweise das Aktualisieren der Systemzeit.

Damit die Liste der Ereignisse nicht bei jedem Neustart des c't-Netzschalters verloren geht, legt die Firmware sie im EEPROM ab. Daten im EEPROM sind zwar dauerhaft gespeichert und einfacher zu überschreiben als im Flash, aber nicht so leicht erreichbar wie im RAM. Die AVR-Bibliotheken bieten eine ganze Reihe von Routinen an, um das EEPROM zu beschreiben und auszulesen. Die Compiler-Direktive __attribute__ ((section („.eeprom“))) platziert eine Variable im EEPROM, die Routine eeprom_read_block(...) kopiert ein ganzes Ereignis in den Arbeitsspeicher. Das Schreiben in das EEPROM übernimmt eeprom_write_block(...):

event_t eventEEPROM[MAXEVENT] __attribute__ ((section (".eeprom")));
event_t event;
unsigned char eventPtr;
...
eeprom_read_block(&event, &eventEEPROM[eventPtr],sizeof(event_t));
eeprom_write_block(&event, &eventEEPROM[eventPtr],sizeof(event_t));

Der C-Compiler kann auch das EEPROM mit Startwerten initialisieren. Diese landen dann in der Datei mcu.eep, die Ponyprog direkt ins EEPROM des Mikrocontrollers überträgt. Folgende Zuweisung lässt den c't-Netzschalter zehn Sekunden nach jedem Neustart und jeden Tag nach Mitternacht das Systemdatum aktualisieren:

event_t eventEEPROM[MAXEVENT]
__attribute__ ((section (".eeprom"))) = {
{10, 0,0,
DONTCARE, DONTCARE, DONTCARE, DONTCARE,
{0,EVENT_SPECIAL,EVENT_GETTIME}}
};

Das Licht per Zeitschaltuhr zu steuern mag zwar für den Urlaub optimal sein, sitzt man aber auf dem Sofa, wäre eine Fernbedienung wünschenswert. Kein Problem, wenn man dem c't-Netzschalter noch einen zusätzlichen Infrarot-Empfänger spendiert. Das Dekodieren der Signale einer handelsüblichen RC5-Fernbedienung erledigt der Mikrocontroller nebenbei.

RC5-Fernbedienungen, die beispielsweise Philips oder Sony für ihre Geräte nutzen, senden ein mit 36 kHz moduliertes Infrarotsignal aus. Ein Halbbit hat eine Länge von 889 µs (32 Schwingungen), ein Ausbleiben des Signals gilt als Low, eine vorhandene Trägerwelle als High.

Die 14 Datenbits werden im Manchester-Code übertragen. Jedes Bit besteht aus zwei Halbbits: Ein Übergang von Low zu High steht für eine Eins, der Wechsel von High nach Low für eine Null. Dadurch gibt es in der Mitte eines Bits immer eine Flanke.

Eine IR-Empfangsdiode vom Typ TSSOP1736 analysiert das IR-Trägersignal und liefert einen kontinuierlichen Strom von Halbbits an den Mikrocontroller - allerdings invertiert. Der Anschluss der Diode ist denkbar einfach: Pin 1 hängt man an Masse und Pin 2 an Vcc - beispielsweise an Pin 4 und 2 der Sockelleiste P4. Der dritte Anschluss des IR-Empfängers füttert einen freien Port des ATmega (IO7, Pin 4 von P10) mit Daten.

Zur Dekodierung ruft die ISR von Timer2 alle 178 µs die Funktion ir_isr() auf. Diese erhöht bei jedem Aufruf einen Zähler (ir_bittimer) um eins und misst so die Zeit zwischen zwei Flanken des Signals am Portpin. Da Flanken an den Bitgrenzen die Nutzdaten nicht beeinflussen, reagiert die Funktion nur auf Pegeländerungen, wenn der Zählerstand zwischen acht und zwölf liegt - ein Bit ist 1,778 ms und damit ziemlich genau zehn Aufrufe von ir_isr() lang. Der Wert des jeweiligen Datenbits entspricht - kurz nach dem Durchgang der Flanke - dem invertierten Pegel am Eingang. Nur das erste Bit, das zur Synchronisation dient, erfordert eine Sonderbehandlung.

Alle 178 µs durchläuft der Mikrocontroller die ISR-Routine zur Dekodierung des Fernbedienungssignals. Erst wenn 14 Bit korrekt empfangen wurden, liefert er den RC5-Code an das Hauptprogramm.

Von den 14 Bit eines RC5-Codes wählen fünf das Gerät über eine Adresse (A0 bis A4) aus und sieben übertragen das Kommando (C0 bis C6). Dabei kam das siebte Kommandobit erst später dazu und steht daher alleine und ist invertiert. Somit sind 32 verschiedene Geräte mit jeweils 128 Kommandos ansprechbar. Das erste Bit hat immer den Wert „1“ und hilft dem Empfänger beim Einstellen des Verstärkungsfaktors. Das Toggle-Bit (T) wechselt seinen Wert mit jedem Drücken einer Taste und erlaubt die Unterscheidung zwischen wiederholtem Drücken und Gedrückthalten einer Taste.

Die Hardware des c't-Netzschalters bietet noch viele weitere Möglichkeiten. Die hier vorgestellte und über den Soft-Link verfügbare Beispiel-Firmware erhebt keinerlei Anspruch auf Vollständigkeit, sondern soll zu eigenen Experimenten anregen. Praktisch wäre es beispielsweise, am Netz hängende PCs zu überwachen und bei einem Absturz neu zu starten (Watchdog). Wertet man die Leistungsmessung geschickt aus, lassen sich Master-Slave-Schaltungen oder gar einfache Messgeräte realisieren.

Das Java-Applet überträgt zwar alle wichtigen Einstellungen an den c't-Netzschalter, lässt aber noch viel Raum für Verbesserungen. Wer den Overhead von Java scheut, könnte dem Mikrocontroller auch beibringen, selbst auf HTTP-Requests zu hören. Ein einfacher GET-Request für eine URL wie http://netz-schalter/relais.html?channel=0x1&data=0x1 könnte so den Verbraucher an Kanal 1 anschalten.

Für Programme von Lesern findet sich auf unserer Projektseite [5] immer ein Plätzchen - Teile des hier vorgestellten Codes für die IR-Fernbedienung stammen beispielsweise aus der Firmware unseres Lesers Oliver Oswald. Die Routinen entstanden ursprünglich für das Schwesterprojekt Mikrocontroller-im-LAN [2], aber die Projekte sind fast völlig Code-kompatibel. Umgekehrt laufen daher die meisten Teile der Netzschalter-Firmware auch dort. Für Diskussionen und den Austausch von Tipps gibt es auf heise online ein Leserforum.

[1] Benjamin Benz, Netz-Schalter, 230-V-Steckdosen aus der Ferne schalten

[2] Benjamin Benz, Fernkontrolle, Mikrocontroller zum Messen und Steuern über das LAN

[3] Benjamin Benz, Sensibelchen, Mikrocontroller-Programmierung: Timer, Sensoren und Drehgeber

[4] Leserforum zum Projekt

[5] Webseite zum Projekt

http://ct.de/0523222 (bbe)