Software

Die Programme für die Mikrocontroller sind in C geschrieben und mit dem Gnu Compiler für den MSP430 compiliert. Man bekommt den Compiler als mspgcc von der GNU-Homepage.

Da die Mikrocontroller nur maximal 1KByte Programmspeicher bieten, macht es wenig Sinn das Programm in einzelne Module zu unterteilen. Daher besteht es jeweils nur aus zwei Quelldateien:

Sender: main.c und hardware.h.

Empfänger: main.c und hardware.h.

Dieses Kapitel beschreibt die Software nur sehr grob. Wenn man es genau wissen will, muß man sich durch den Quelltext arbeiten, der extra ausführlich kommentiert ist. Wenn hier Auszüge aus den Quellen angegeben sind, so sind sie mit Zeilennummern versehen, die den Originalen entsprechen. Dadurch können Programmstellen eindeutig angesprochen werden.

Sender

hardware.h

Im File hardware.h sind Definitionen, die die Hardware betreffen untergebracht. Zur Erinnerung und als Referenz ist zu Beginn eine Auflistung aller Pins eingetragen. Man kann dadurch schnell prüfen, ob die unten verwendeten Konstanten mit der Realität übereinstimmen.

 /* Pinbelegung */
 /* Pin1:  VCC MSP430F20x1
    Pin2:  P1.0/TACLK/ACLK/CA0:    Input NIRQ, active Low
    Pin3:  P1.1/TA0/CA1:           Input MSI, Daten vom RFM12
    Pin4:  P1.2/TA1/CA2:           Output MSO, Daten an RFM12
    Pin5:  P1.3/CAOUT/CA3:         Output SCL, Clock
    Pin6:  P1.4/SMCLK/CA4/TCK:     Output CS, active LOW
    Pin7:  P1.5/TA0/CA5/TMS:       NC
    
    Pin8:  P1.6/TA1/CA6/TDI/TCLK:  Output, NC
    Pin9:  P1.7/CAOUT/CA7/TDO/TDI: Output, NC
    Pin10: RESET/NMI/SBWTDIO
    Pin11: TEST/SBWTCK
    Pin12: P2.7/XOUT:              Output LED, active HIGH
    Pin13: P2.6/XIN/TA1:           NC
    Pin14: GND */
Anschlussbelegung der Senderelektronik

Die Defintionen in hardware.h sollten für sich selbst sprechen, damit man im Programm sofort erkennen kann was passiert. Ich hoffe das ist mir gelungen.

main.c

 #define SYSFREQ 8000000l /* Systemtakt in Hz */
 
 #define FREQPWM 100l     /* Taktfrequenz fuer PWM und TA1 interrupt */
 #define PERIODEPWM (SYSFREQ / FREQPWM / 16) /* Wert fuer Timer um FREQPWM zu erreichen */
 #define STARTBURST 4
 #define SENDEPERIODE 59
 
 #define CONFIG_COMMAND           0x8000
 .
 #define OUTPUTPOWERREDUCTION(x) (((x)/3)&0x0007)
 
Definitionen in main.c

In den Zeilen 4 bis 158 werden zunächst diverse Konstanten definiert, die meisten für das Funkmodul.

Seitenanfang
 volatile unsigned int delaytimer;
 volatile unsigned int sekundenzaehler;
 unsigned int sekundenalt;
 unsigned int timer;
Globale Variablen

Es gibt nur sehr wenige globale Variablen. Die, die im Interrupt verwendet werden, müssen als volatile deklariert werden, damit der Compiler nicht zu viel optimiert und den Zugriff auf die Variable unterläßt.

 /**
 Verzoegerungsfunktion durch Abwarten bis Interrupt den delaytimer auf 0 heruntergezaehlt hat
 */
 void delay(unsigned int d) {
   delaytimer = d;
   while (delaytimer) {
   }
 }
Warteroutine

Gewartet wird in einer Schleife, die die Variable delaytimer auf eine Wartezeit setzt und dann solange abfragt bis sie vom Interrupt auf 0 heruntergezählt wurde.

 unsigned int rfm12_spi(unsigned int datenout) {
   unsigned int i;
   unsigned int datenin;
 
   datenin = 0;
   CS_LOW;
   for (i=0; i<16; i++) {
     if ((datenout & (unsigned int)0x8000)==0) {
       MSO_LOW;
     }
     else {
       MSO_HIGH;
     }
     SCL_HIGH;
     datenin = datenin << 1;
     if (MSI_IS_HIGH) {
       datenin = datenin | 0x0001;
     }
     SCL_LOW;
     datenout = datenout << 1;
   }
   CS_HIGH;
   return(datenin);
 }
SPI Routine

Die Routine rfm12_spi sorgt für die Datenübertragung von und zum RFM12-Modul. Wie bei einer SPI-Schnittstelle üblich, wird beim Senden von Daten auch immer ein Datum empfangen. Das macht beim RFM12 nicht immer Sinn. Die Routine liefert trotzdem immer den empfangenen 16-Bit Wert, der vom RFM12-Modul geliefert wurde. Ob dieser gebraucht wird und ob er ausgewertet werden muß, muß vom aufrufenden Programm entschieden werden.

Seitenanfang
 void rfm12_init() {
   rfm12_spi(STATUS_COMMAND);
   rfm12_spi(DEFAULT_CONFIG_VAL|ENABLE_INTERNAL_DATAREG);
   rfm12_spi(DEFAULT_POWER_MAN_VAL);
   rfm12_spi(FREQUENCY_COMMAND|CENTRE_FREQ_433MHZ(FUNKFREQUENZ));
   rfm12_spi(BAUD_RATE_COMMAND|BAUDRATE(2400));
   rfm12_spi(DEFAULT_RECEIVER_CONTROL_VAL);
   rfm12_spi(DATA_FILTER_COMMAND|CLOCK_RECOVERY_AUTO|DATA_QUALITY(4));
   rfm12_spi(DEFAULT_FIFO_VAL);
   rfm12_spi(SYNC_PATTERN_RESET);
   rfm12_spi(AFC_COMMAND | AFC_ENABLE | AFC_OFFSET_ENABLE | AFC_NO_LIMIT | AFC_IF_VDI);
   rfm12_spi(TRANSMITTER_CONTROL_COMMAND|BANDWIDTH_KHZ(90)|OUTPUTPOWERREDUCTION(0));
   rfm12_spi(DEFAULT_WAKEUP);
   rfm12_spi(DEFAULT_LOW_DUTY_CYCLE);
   rfm12_spi(LOW_BATTERIE_RESET);
 }
RFM12 Initialisierung

Die nötige Initialisierung des RFM12-Moduls ist in dieser Routine zusammengefaßt. Die Werte sind z.T. aus Beispielprogrammen des Herstellers und eigenen Experimenten entnommen. Die relativ niedrige Baudrate soll die mögliche Reichweite und Störfestigkeit gegenüber höheren Baudraten erhöhen. Ob dies gelungen ist, kann ich nicht wirklich sagen, da ich auf aufwändige Vergleichtstests verzichtet habe.

 void rfm12_send(unsigned char datenout) {
   
   while (INT_IS_HIGH) {
   }
   rfm12_spi(TRANSMITTER_WRITE_COMMAND + datenout);
 }
Senderoutine

Zum Senden eines Bytes wird diese Routine benutzt. Es wird einfach darauf gewartet, daß das RFM12-Modul über die Leitung NIRQ mitteilt, daß es ein neues Byte zum Senden annehmen kann. Das Byte wird dann mit dem TRANSMITTER_WRITE_COMMAND in den Sendepuffer geschrieben, von wo es dann baldmöglichst ausgesendet wird.

Seitenanfang
 void rfm12_send_kennung(unsigned char datenout) {
   rfm12_spi(STATUS_COMMAND);
   rfm12_spi(DEFAULT_POWER_MAN_VAL);
   //      rfm12_send(0xAA);
   rfm12_send(0xAA);
   rfm12_send(0xAA);
   rfm12_send(0x2D);
   rfm12_send(0xD4);
   rfm12_send(datenout);
   rfm12_send(datenout);
   rfm12_send(datenout);
   rfm12_send(0xAA);
   rfm12_send(0xAA);
   rfm12_send(0xAA);
   rfm12_spi(POWER_MAN_COMMAND | DISABLE_CLOCK_PIN);
 }
Senderoutine der Nachricht zum Einschalten

Zum Senden wird der Sender durch den Befehl DEFAULT_POWER_MAN_VAL eingeschaltet. Das Modul fordert nun mittels NIRQ Byte für Byte zum Senden an. Zu Beginn werden ein paar Dummybytes gesendet, damit sich der Empfänger auf das Signal einstellen kann. Die beiden Bytes 0x2D und 0xD4 sind ein spezielles Muster auf das der Empfänger reagiert. Alle Bytes nach dieser Sequenz werden dort als Nutzdaten erkannt und dem Mikrocontroller gemeldet. Durch dieses Synchronisationsmuster wird vermieden, daß der Empfänger durch Störer mit Daten versorgt wird, die gar keine sind.

Im Anschluß an die Synchronisation folgt die eigentliche Nachricht, die den Funkschalter einschalten soll. Es ist einfach dreimal dasselbe Byte. Danach werden noch drei Dummybytes geschickt, bevor der Sender wieder ausgeschaltet wird. Die Dummybytes sind notwendig, da der RFM12 einen FIFO besitzt, und das wirkliche Aussenden der Datenbytes einige Zeit dauert. Würde man sofort nach dem letzten Datenbyte abschalten, wäre dieses noch nicht über den Sender gegangen.

 interrupt (TIMERA1_VECTOR) timera1_interrupt(void)
 {
   switch (TAIV) {
     /* Nur bei Ueberlauf von TACCR1 */
   case 2:
     eint(); /* Interrupts wieder zulassen, damit TIMERA0 weiterhin drankommen kann. */
     TACCR1 += PERIODEPWM; /* Neuen Zaehlervergleichswert speichern */
     timer++;
     if (timer >= FREQPWM) {
       timer = 0;
       sekundenzaehler++;
     }
     /* Fuers kurzzeitge Warten gibt es noch den delaytimer, der in diesem Interrupt runtergezaehlt wird */
     if (delaytimer) {
       delaytimer--;
     }
     break;
   case 4:
     break;
   case 10:
     break;
   default:
     ;
   }
 }
Interruptroutine

Die Interruptroutine sorgt für das Zeitmanagement. Mit ihr können kurze Zeiten (mit dem delaytimer) und längere Zeiten (mit dem sekundenzaehler) gemessen werden.

Seitenanfang
 int main(void) {
   unsigned char z;
 
   WDTCTL = WDTCTL_INIT;               /*Init watchdog timer */
 .
   TACCTL1 = CM_DISABLE | CCIS_GND | SCS_ASYNC | CAP_COMP | OUTMOD_TOGGLE | CCIE | OUT_LOW;
 
   sekundenzaehler = 0;
   timer = 0;
   
   eint();
   while(sekundenzaehler < 1) {
   }
   rfm12_init();
   z = 0;
   while (z < STARTBURST) {
     if (sekundenalt != sekundenzaehler) {
       rfm12_send_kennung('1');
       sekundenalt = sekundenzaehler;
       TOGGLE_LED;
       delay(30);
       TOGGLE_LED;
       z++;
     }
   }
   while (1) {
     if (sekundenalt + SENDEPERIODE < sekundenzaehler) {
       rfm12_send_kennung('1');
       sekundenalt = sekundenzaehler;
       TOGGLE_LED;
       delay(30);
       TOGGLE_LED;
     }
   }
 }
Hauptprogramm

Das Hauptprogramm beginnt mit der Initalisierung der Mikrocontrollerhardware und der Variablen. Nach einer kurzen Wartezeit (Zeile 309) wird das Funkmodul initialisiert. Da man auch unter widrigen Umständen den Funkschalter einschalten möchte wird nun in kurzer Folge das Einschaltsignal mehrfach ausgesendet (Zeile 313 bis 322). Das Senden wird durch kurzes Aufblinken der LED angezeigt.

Danach bleibt das Hauptprogramm in einer Endlosschleife und sendet ca. jede Minute ein Einschaltsignal. Auch das wird durch die LED signalisiert. Es sind hier keine besonderen Maßnahmen zum Stromsparen getroffen worden. Da das Sendemodul an einer USB-Schnittstelle angeschlossen ist, ist es unerheblich ob das Modul 2mA oder 0,02mA aufnimmt. In jedem Fall ist die Stromaufnahme in Relation zum Computer vernachlässigbar, auch bei batteriebetriebenen Notebooks.

Das Senden der Einschaltsignale hört nur dann auf, wenn der Sender keinen Strom mehr bekommt. Das ist normalerweise der Fall, wenn der Computer ausgeschaltet wird und für diesen dann auch kein Netzwerk mehr benötigt wird.

Seitenanfang

Empfänger

hardware.h

 /* Pinbelegung */
 /* Pin1:  VCC MSP430F20x1
    Pin2:  P1.0/TACLK/ACLK/CA0:    Input MSI, Daten vom RFM12
    Pin3:  P1.1/TA0/CA1:           Input NIRQ, active Low
    Pin4:  P1.2/TA1/CA2:           Output MSO, Daten an RFM12
    Pin5:  P1.3/CAOUT/CA3:         Output SCL, Clock 
    Pin6:  P1.4/SMCLK/CA4/TCK:     Output CS, active LOW
    Pin7:  P1.5/TA0/CA5/TMS:       Input, NC wegen Layout
    
    Pin8:  P1.6/TA1/CA6/TDI/TCLK:  Output, NC
    Pin9:  P1.7/CAOUT/CA7/TDO/TDI: Output, NC
    Pin10: RESET/NMI/SBWTDIO
    Pin11: TEST/SBWTCK
    Pin12: P2.7/XOUT:              Output LED, active HIGH
    Pin13: P2.6/XIN/TA1:           Output Schalter, active HIGH
    Pin14: GND */
Anschlussbelegung der Empfängerelektronik

Die Anschlussbelegung im Empfänger weicht von der des Senders ab. Ansonsten ähnelt der Inhalt natürlich der Datei des Senders.

main.c

 #include "hardware.h"
 .
 
 volatile unsigned int sekundenzaehler;
 unsigned int sekundenalt;
 unsigned char eingeschaltet;
 unsigned int timer;
 volatile unsigned int delaytimer;
 volatile unsigned char empfang;
 volatile unsigned char kennungzaehler;
 volatile unsigned char k[3];
 volatile unsigned int fifo;
 .
 void delay(unsigned int d) {
 .
 unsigned int rfm12_spi(unsigned int datenout) {
 .
 void rfm12_init() {
 .
Variablendeklaration und ähnliche Funktionen wie beim Sender

Die Initialisierung und Datenübertragung zum RFM12 geschieht beim Empfänger in gleicher Weise wie beim Sender. Da hier die Nachricht des Senders analysiert werden muß, sind mehr Variablen für die Zwischenspeicherung nötig. Auch die Delay-Routine des Sender ist wiederzufinden.

Seitenanfang
 unsigned char rfm12_rec(void) {
   unsigned int fifo;
 
   P1IFG = 0;
   empfang = 0;
   P1IE = P1IE | INT_PORT;
   while (!empfang) {
   }
   empfang = 0;
   rfm12_spi(STATUS_COMMAND);
   fifo = rfm12_spi(FIFO_READ_COMMAND);
   return((unsigned char)fifo & 0x00ff);
 }
 
Empfangsroutine wird nie aufgerufen (Historie)
 void rfm12_init_rec_kennung() {
   kennungzaehler = 3;
   P1IFG = 0;
   P1IE = P1IE | INT_PORT;
   rfm12_spi(0xCA83);
 }
 
 unsigned char rfm12_kennung_erkannt() {
   return(empfang == 1);
 }
 
 unsigned char rfm12_read_kennung() {
   unsigned char kennung;
 
   kennung = 0;
   if (empfang) {
     if ((k[0] == k[1]) && (k[1] == k[2])) {
       kennung = k[0];
     }
   }
   empfang = 0;
   return(kennung);
 }
 
Routinen für Datenempfang

Der Empfang neuer Nachrichten wird durch rfm12_init_rec_kennung() gestartet. Hier wird die Variable kennungzaehler auf die zu erwartende Anzahl von Datenbytes eingestellt. Der Interrupt durch den RFM12 wird freigegeben und das RFM12-Modul wird auf Empfang eingestellt. Der eigentliche Empfang der Daten wird in der Interruptroutine geregelt.

Der Interrupt setzt nach der gewünschten Anzahl von Datenbytes die Variable empfang auf 1. Deren Zustand wird durch die Routine rfm12_kennung_erkannt() gemeldet.

In der Routine rfm12_read_kennung() werden die empfangenen Datenbytes überprüft. Wenn alle denselben Wert aufweisen, wird dieser als Rückgabewert an die aufrufende Instanz geliefert. Im Fehlerfall, also wenn nicht alle Datenbytes übereinstimmen, wird der Wert 0 zurückgeliefert. In jedem Falle wird das Flag empfang zurück auf 0 gesetzt um danach erneut mit dem Empfang von Daten zu beginnen.

Seitenanfang
 interrupt (PORT1_VECTOR) wakeup port1_interrupt(void)
 {
   P1IFG = 0;
 //  eint();
   rfm12_spi(STATUS_COMMAND);
   fifo = rfm12_spi(FIFO_READ_COMMAND);
   k[kennungzaehler - 1] = (unsigned char)fifo & 0x00ff;
   kennungzaehler--;
   if (kennungzaehler == 0) {
     P1IE = P1IE & ~INT_PORT;
     empfang = 1;
     rfm12_spi(0xCA81);
   }
 }
 
Interruptroutine ausgelöst durch RFM12

Wenn der RFM12 ein Byte empfangen hat, wird dies durch einen Interrupt des NIRQ-Pins angezeigt. Das empfangene Byte wird im Array k abgelegt um später ausgewertet zu werden. Die Anzahl der zu erwartenden Bytes ist zunächst in der Variable kennungszaehler gespeichert. Dieser Wert wird dann mit jedem empfangenen Byte heruntergezählt. Wenn der Wert 0 erreicht hat, wird der Interrupteingang durch Löschen des IE-Bits gesperrt. Das vollständige Einlesen der Nachricht wird durch empfang = 1 angezeigt. Der Empfänger wird erst mal abgestellt, bis die Nachricht untersucht ist.

 interrupt (TIMERA1_VECTOR) timera1_interrupt(void)
 .
 
 /********************************************************************/
 int main(void) {
 .
   sekundenzaehler = 0;
   timeoutzaehler = RETRIGGERTIMEOUT;
   timer = 0;
   empfang = 0;
   kennungzaehler = 0;
   eint();
   LED_ON;
   delay(20);
   LED_OFF;
   delay(20);
   LED_ON;
   delay(20);
   LED_OFF;
   while(sekundenzaehler < 1) {
   }
   rfm12_init();
   rfm12_spi(0xCA81);
   while (1) {
     if (sekundenalt != sekundenzaehler) {
       sekundenalt = sekundenzaehler;
       if (timeoutzaehler) {
         timeoutzaehler--;
         if (timeoutzaehler == 0) {
           SWITCH_OFF;
           LED_OFF;
         }
       }
     }
     if (kennungzaehler == 0) {
       rfm12_init_rec_kennung();
     }
     if (rfm12_kennung_erkannt()) {
       if (rfm12_read_kennung() == '1') {
         timeoutzaehler = RETRIGGERTIMEOUT;
         SWITCH_ON;
         LED_ON;
         TOGGLE_LED;
         delay(50);
         TOGGLE_LED;
       }
     }
   }
 }
Endlosschleife in main()

Die Interruptroutine (Zeile 275 bis 300) für das Zeitmanagement entspricht der des Senders und deshalb hier nicht noch einmal genauer dargestellt.

Das Hauptprogramm main() initialisiert zunächst die Variablen, blinkt dann ein bißschen mit der LED und initialisiert dann das Funkmodul.

Danach werden dann drei Aktionen immer wieder nacheinander ausgeführt:

  • Jede Sekunde nachsehen, ob die Zeit ohne Nachricht abgelaufen ist.
    Wenn etwa 10 Minuten lang keine neue gültige Nachricht gekommen ist, wird das Relais und die LED abgeschaltet.
  • Prüfen, ob eine Nachricht empfangen wurde.
    Wenn eine Nachricht empfangenn wurde, wird der Empfänger für die nächste Nachricht bereit gemacht.
  • Zeitzähler bei einer gültigen Nachricht zurücksetzen und mit LED blinken.
    Wenn eine Nachricht empfangen wurde und die dann auch noch korrekt war, wird der Zeitzähler zurückgesetzt. Es beginnen wieder die 10 Minuten bis zum Abschalten zu laufen. Das Relais und die LED werden in jedem Falle eingeschaltet, unabhängig davon, ob sie schon von vorher eingeschaltet waren. Damit man erkennen kann, daß der Zähler neu angefangen hat zu zählen, wird danach die LED kurz ausgeschaltet (inverses Blinken).

Obwohl der Prozessor umfangreiche Stromsparmodi unterstützt, habe ich sie hier nicht verwendet. Das Funkmodul muß sowieso die ganze Zeit laufen und auf Nachrichten horchen. Da machen dann die ein, zwei Milliampere des MSP430 auch nicht mehr besonders viel aus. Vielleicht wird das ja später nochmal von mir in Angriff genommen.

Seitenanfang