/* ZIEL */ #include "hardware.h" #include #define SYSFREQ 16000000l /* Systemtakt in Hz */ #define SMCLKDIVISOR 8 #define PERIODEFREQ 3000l /* Taktfrequenz fuer Zeitzaehlung und TA0 interrupt */ #define PERIODECOUNT (SYSFREQ / (PERIODEFREQ * SMCLKDIVISOR)) /* Wert fuer Timer um FREQPWM zu erreichen */ #define IRPULSFREQ 38000l /* gewuenschte IR Pulsefrequenz */ #define IRPULSCOUNT (SYSFREQ / (IRPULSFREQ * SMCLKDIVISOR) / 2) /* Wert fuer Timer um IR Pulsefrequenz zu erreichen */ /* Timer A laeuft mit SMCLK und kann auch mal angehalten werden */ #define TIMERA_STOP TACTL = TASSEL_SMCLK | ID_DIV8 #define TIMERA_RUN TACTL = TASSEL_SMCLK | ID_DIV8 | MC_CONT /* Timer 1 wird entweder zum Erzeugen der 38kHz fuer die IR-LED genutzt, oder */ /* fuer die Abtastung der eintreffenden IR-Detektorimpulse */ #define TIMER1_INTERRUPT TACCTL1 = OUTMOD_OUT | CCIE /* Interrupts erzeugen, Output bleibt 0 */ #define TIMER1_NOINTERRUPT TACCTL1 = OUTMOD_OUT /* Keine Interrupts, Output bleibt 0 */ #define TIMER1_TOGGLE_INTERRUPT TACCTL1 = OUTMOD_TOGGLE | CCIE /* Interrupts erzeugen, Output wechseln */ #define TIMER1_RESET_INTERRUPT TACCTL1 = OUTMOD_RESET | CCIE /* Interrupts erzeugen, Output auf 0 setzen */ #define TIMER1_RESET_NOINTERRUPT TACCTL1 = OUTMOD_RESET /* Keine Interrupts erzeugen, Output auf 0 setzen */ #define TIMER1_DET_RELOAD TACCR1 += PERIODECOUNT /* Reloadwert fuer IR-Empfang */ #define TIMER1_IR_RELOAD TACCR1 += IRPULSCOUNT /* Reloadwert fuer IR-Senden */ #define TIMER1_INT_RESET TACCTL1 = TACCTL1 & ~CCIFG /*Timer 0 erzeugt einen periodischen Interrupt zur Zeitzaehlung und IR-Puls-Erzeugung */ #define TIMER0_CCR_RELOAD TACCR0 += PERIODECOUNT #define TIMER0_INTERRUPT TACCTL0 = OUTMOD_OUT | CCIE /* Interrupts erzeugen, Output bleibt 0 */ #define IR_INT_ENABLE P1IE = P1IE | IR_PORT #define IR_INT_DISABLE P1IE = P1IE & ~IR_PORT #define IR_INT_RESET P1IFG = P1IFG & ~IR_PORT #define IRBITS 5 #define PAUSE 6 #define TXPAUSE 10 #define INAKTIVDAUER 30*60 /* Zeit in Sekunden, bis zum Abschalten der Muetze */ #define TREFFERDAUER 30 #define SCHUTZDAUER 10 #define S_WARTEN 0 #define S_GETROFFEN 1 #define S_SCHUTZ 2 #define S_TASTE 3 #define MS(x) (((x) * PERIODEFREQ) / 1000) #define BLINK_AN_LANGSAM ((PERIODEFREQ) / 2) #define BLINK_AUS_LANGSAM ((PERIODEFREQ) / 2) #define BLINK_AN_ZAEHLEND MS(100) #define BLINK_AUS_ZAEHLEND MS(300) #define BLINK_AN_FEHLER MS(50) #define BLINK_AUS_FEHLER MS(50) #define BLINK_AN_TREFFER MS(50) #define BLINK_AUS_TREFFER MS(100) #define BLINKLED_ALLE 0 #define BLINKLED_SCHIRM 1 #define ANZ_TREFFER_MELDER 5 #define FLASHDIVIDER (SYSFREQ / 366000) /* Das Flash-EPROM braucht eine Frequenz von ca. 366kHz */ #define MUETZENNUMMER_ADDR 0x1000 /* Im Flash-EPROM wird diese Adresse fuer die Speicherung der ID benutzt */ sfrw(MUETZENNUMMER, MUETZENNUMMER_ADDR); /* Assemblerfunktion um auf die Adresse MUETZENNUMMER_ADDR wortbreit zugreifen zu koennen */ volatile char treffermeldecount; volatile int irrxbitcount; volatile int irrxpausecount; volatile int irrxdaten; volatile char irempfang; volatile int irtxbitcount; volatile int irtxpausecount; volatile int irtxdaten; volatile unsigned int timer; volatile unsigned int delaytimer; volatile unsigned int sekundenzaehler; volatile unsigned char empfangsmode; volatile unsigned int aktivzeit; unsigned int trefferzeit; unsigned char zustand; volatile unsigned char muetzennummer; /** Verzoegerungsfunktion durch Abwarten bis Interrupt den delaytimer auf 0 heruntergezaehlt hat */void delay(unsigned int d) { delaytimer = d; while (delaytimer) { } } /* Die LED blinkt 'anzahl' mal. Die Leuchtdauer und Pausendauer ist durch 'zeitAn' und 'zeitAus' bestimmt. Bei jedem Blinken wird aber noch der externe Spannungsteiler abgepr[ft und mit der internen Referenz verglichen. Falls die Batteriespannung zu niedrig ist, wird im Anschluss an das normale Blinken noch 20 mal ganz schnell geblinkt. Durch den Parameter ledtyp wird bestimmt, ob alle LEDs oder nur die am Schirm der Muetze blinken soll. */ void blink(unsigned int anzahl, unsigned char ledtyp, unsigned int zeitAn, unsigned int zeitAus) { int i; for (i = 0; i < anzahl; i++) { LED_AN; /* Die LED am Schirm wird auf jeden Fall eingeschaltet */ if (ledtyp == BLINKLED_ALLE) { LEDS_AN; /* Eventuell auch noch die drei LEDs auf der Muetze */ } delay(zeitAn); LED_AUS; /* Abgeschaltet werden einfach alle. */ LEDS_AUS; delay(zeitAus); } } /* Mit dieser Routine wird an 'adresse' das 'datum' ins Flash-EPROM geschrieben. Da hier nur ein einziges Datum, naemlich die Muetzennummer, zu speichern ist, wird einfach immer der gesamter Flash/ */void flashWrite(unsigned int adresse, unsigned int datum) { /* Watchdog disable nicht noetig, da sowieso aus */ dint(); /* Interrupts sperren */ /* Erst mal loeschen */ FCTL1 = 0x0A502; FCTL3 = 0x0A500; *((unsigned int *)(adresse)) = 0xFFFF; while(BUSY & FCTL3); /* Nur wenn nicht Busy weitermachen */ FCTL3 = FWKEY; FCTL1 = FWKEY + WRT; *((unsigned int *)(adresse)) = (datum); FCTL1 = FWKEY; eint(); } interrupt (PORT1_VECTOR) port1_interrupt(void) { if (P1IFG & IR_PORT) { /* Wenn IR-Empfaenger den Interupt ausgeloest hat */ /* Timer stoppen und Timer-Interrupt auf halbe Bithaelfte einstellen */ TIMERA_STOP; TACCR1 = TAR + PERIODECOUNT / 2; /* Naechster Interrupt in der Mitte der Bithaelfte */ TIMERA_RUN; /* Merken, dass der Empfang laeuft */ empfangsmode = 1; /* Jetzt keine Interrupts mehr durch den IR-Empfaenger zulassen */ IR_INT_DISABLE; /* Es werden 2*IRBITS ausgewertet */ /* Solange irbitcount != 0 ist, wird im Timerinterrupt geprueft */ irrxbitcount = IRBITS*2; irrxpausecount = PAUSE; /* Das Datenwort wird erst mal auf 0 gesetzt */ irrxdaten = 0; /* Den Timer auf Interruptausloesung einstellen */ TIMER1_INT_RESET; TIMER1_RESET_INTERRUPT; } else { /* Die Taste muss den Interrupt ausgeloest haben */ VCC_5V_ON; /* DCDC-Wandler fuer die 5V einschalten */ IR_PORT_UP; /* Pullup fuer IR-Empfang einschalten */ _BIC_SR_IRQ(LPM4_bits); /* Background durchlaufen lassen */ } /* Alle anstehenden Interrupts zuruecknehmen */ P1IFG = 0; } interrupt (TIMERA0_VECTOR) Timer_A(void) { eint(); /* Interrupts wieder zulassen, damit TIMERA1 weiterhin drankommen kann. */ /* Dieser Interrupt kommt zyklisch und ohne abgeschaltet zu werden */ TIMER0_CCR_RELOAD; /* Neuen Zaehlervergleichswert speichern */ /* Ab hier kommt die Behandlung des Sendeimpulses */ /* Wenn irtxbitcount != 0 ist, muss das Senden von Bits bearbeitet werden */ /* Pro Bit gibt es eine Puls- und eine Pausenhaelfte. Bei Bit=1 wird zunaechst ein */ /* Puls und dann eine Pause gesendet. Beides wird durch Setzen der MODE-Bits fuer */ /* Timer 0 gesteuert. Bei Bit=0 ist die Pause zuerst. */ if (irtxbitcount != 0) { /* Wenn noch Bits zu senden sind */ if ((irtxbitcount & 0x0001) == 0) { /* Erste Pulshaelfte (Gerade Anzahl von Bithaelften) */ if (irtxdaten & 0x0001) { TIMER1_TOGGLE_INTERRUPT; /* Puls einschalten */ } else { TIMER1_RESET_INTERRUPT; /* Puls abschalten, Pause */ } } else { /* Zweite Pulshaelfte */ if (irtxdaten & 0x0001) { TIMER1_RESET_INTERRUPT; /* Pause einschalten */ } else { TIMER1_TOGGLE_INTERRUPT; /* Pause abschalten, Puls an */ } /* Nach zweiter Bithaelfte wird das nachste Bit vorbereitet */ irtxdaten = irtxdaten >> 1; } /* Jede Bithaelfte mitzaehlen */ irtxbitcount--; } else { if (irtxpausecount) { TIMER1_RESET_INTERRUPT; irtxpausecount--; } else { /* Wenn keine Bits mehr zu senden sind, Sender abstellen, Interrupt wird abgeschaltet */ TIMER1_RESET_INTERRUPT; } } /* Sekundenzaehler fuers Abschalten */ timer++; if (timer >= PERIODEFREQ) { timer = 0; sekundenzaehler++; /* Sekundenzaehler bis ca. 18 Stunden */ /* Es kann davon ausgegangen werdden, dass kein Ueberlauf vor Ende des Spiels erfolgt */ } /* Fuers kurzzeitge Warten gibt es noch den delaytimer, der in diesem Interrupt runtergezaehlt wird */ if (delaytimer) { delaytimer--; } } /* Die TIMER1 Interruptroutine wird mit zwei verschiedenen Betriebsarten verwendet. */ /* Wenn der IR-Empfaenger einen Interrupt ausgeloest hat, wird durch das Flag 'empfangsmode' */ /* bestimmt, dass nun im halben Bitraster das IR-Signal abzutasten ist und das Signal zu decodieren ist. */ /* Der IR-Interrupt hat dafuer gesorgt, dass die Abtastungen jeweils in den Mitten der Bithaelften stattfinden. */ /* Erwartet wird als Ergebnis eine Zahl von 0 bis 7. Wird aber ein Fehler im IR-Datenstrom gefunden, so wird */ /* eine Zahl der Form 0xFFFn ausgegeben, die als ungueltig erkannt werden kann. */ /* Wird nichts empfangen, so wird der TIMER1 mit 2*38kHz ausgeloest, so dass er, gesteuert durch den Timer0- */ /* Interrupt Lichtpulse mit 38kHz ausloesen kann. */ interrupt (TIMERA1_VECTOR) timera1_interrupt(void) { switch (TAIV) { /* Nur bei Ueberlauf von TACCR1 */ case 2: if (empfangsmode) { /* Es wird ein IR-Bitstrom empfangen und er muss mit doppelter Bitrate abgetastet werden */ TIMER1_DET_RELOAD; if (irrxbitcount > 0) { /* Es ist noch was zu empfangen */ if ((irrxbitcount & 0x0001) == 0) { /* Erste Bithaelfte */ if (IR_ERKANNT) { /* Wenn Puls erkannt, eine 1 empfangen */ irrxdaten = (irrxdaten >> 1) | (1 << (IRBITS - 1)); } else { /* Sonst ist es eine 0 */ irrxdaten = (irrxdaten >> 1); } } else { /* Zweite Bithaelfte */ if (IR_ERKANNT) { if ((irrxdaten & (1 << (IRBITS - 1))) != 0) { /* Wenn ein Puls kommt, obwohl in der ersten Haelfte schon */ /* einer da war, wird das Ergebnis auf 0xffff gesetzt und */ /* durch irbitcount der Empfang beendet */ irrxbitcount = 1; irrxdaten = 0xffff; /* Fehler melden */ } } else { if ((irrxdaten & (1 << (IRBITS - 1))) == 0) { /* Beenden bei zweiter Pause in Folge */ irrxbitcount = 1; irrxdaten = 0xffff; /* Fehler melden */ } } /* Nichts machen */ } irrxbitcount--; } else { if (irrxpausecount) { if (IR_ERKANNT) { irrxdaten = 0xfff8; /* Fehler melden, keine Pause erkannt */ } irrxpausecount--; } else { /* Wenn keine Bits mehr zu empfangen sind, Timer Interrupt abschalten */ TIMER1_NOINTERRUPT; /* Die Startbits rausschieben */ irrxdaten = irrxdaten >> 2; /* Melden, dass Empfangsdaten anliegen */ irempfang = 1; empfangsmode = 0; /* Flag fuer Empfangsmode zuruecksetzen */ } } } else { /* Es wird ein 38*2kHz Interrupt erzeugt */ /* Je nach Programmierung wird der Ausgang gepulst oder auf 0 gehalten */ TIMER1_IR_RELOAD; } break; case 4: break; case 10: break; default: ; } } /* ir_out() sendet das datenbyte als RC5-aehnlicher Code aus */ /* Es werden drei Bits Daten aus 'datenbyte' gesendet, also Codes von 0 bis 7 */ /* Eine Pistole sendet als Schuss immer eine 0. */ /* Es werden zwei Startbits hinzugefuegt und mit der TIMER0 Interruptroutine */ /* ausgesendet. Im Anschluss an das Senden wird eine Pause eingefuegt. Die Dauer */ /* wird in 'irtxpausecount' bestimmt. */ /* ir_out() wartet bis die Interruptroutine das Datum vollstaendig gesendet hat. */ /* Dies wird durch das Nullen des Bitzaehlers erkannt. */ void ir_out(int datenbyte) { /* Keine Interrupts des Detektors zulassen */ IR_INT_DISABLE; while (empfangsmode) { /* Einen eventuell stattfindenden Empfang abwarten (das geht auch ohne IR_INT */ } irtxdaten = (datenbyte << 2) | 0x03; /* zwei Startbits hinzufuegen */ TIMERA_STOP; TACCR1 = TAR + IRPULSCOUNT; /* Timer auf baldigen Interrupt programmieren */ TIMERA_RUN; TIMER1_RESET_INTERRUPT; /* Timer erzeugt erst mal nur 0 am Ausgang */ irtxpausecount = TXPAUSE; irtxbitcount = IRBITS*2; /* Es werden insgesamt 3 + 2 Startbits gesendet, */ /* das sind 5 * zwei Bithaelften */ while ((irtxbitcount > 0) || (irtxpausecount > 0)); /* Warten bis alles abgearbeitet ist */ IR_INT_RESET; /* Eventuell aufgelaufene IR_Interrupts loeschen */ IR_INT_ENABLE; /* Jetzt wieder Empfangsbereit sein */ } /********************************************************************/ int main(void) { /* Nach dem Einlegen der Batterien wird hier gestartet */ WDTCTL = WDTCTL_INIT; /*Init watchdog timer */ P1OUT = P1OUT_INIT; /* Init output data of port1*/ P2OUT = P2OUT_INIT; /* Init output data of port2*/ P1SEL = P1SEL_INIT; /*Select port or module -function on port1 */ P2SEL = P2SEL_INIT; /*Select port or module -function on port2 */ P1DIR = P1DIR_INIT; /* Init port direction register of port1*/ P2DIR = P2DIR_INIT; /*Init port direction register of port2*/ P1REN = P1REN_INIT; P1IES = P1IES_INIT; /*init port interrupts*/ P2IES = P2IES_INIT; P1IE = P1IE_INIT; P2IE = P2IE_INIT; FCTL2 = FWKEY | FSSEL_MCLK | FLASHDIVIDER; /* Clock fuer Flash-EPROM auswaehlen */ irempfang = 0; empfangsmode = 0; eint(); /* Interrupts sind jetzt grundsaetzlich zugelassen */ /* Systemfrequenz ist 16MHz */ DCOCTL = CALDCO_16MHZ; BCSCTL1 = XT2OFF | CALBC1_16MHZ; TIMERA_RUN; TIMER1_NOINTERRUPT; TIMER0_INTERRUPT; muetzennummer = MUETZENNUMMER; /* Aktuelle Muetzennummer aus Flash-EPROM holen */ if ((muetzennummer < 1) || (muetzennummer > 7)) { muetzennummer = 1; /* Falls nicht zwischen 1 und 7, wird sie auf 1 gesetzt */ flashWrite(MUETZENNUMMER_ADDR, muetzennummer); /* und im EPROM abgespeichert */ } VCC_5V_ON; /* 5V fuer IR-Empfaenger einschalten */ blink(2, BLINKLED_ALLE, BLINK_AN_LANGSAM, BLINK_AUS_LANGSAM); /* Blinken um anzuzeigen, dass eingeschaltet wurde */ IR_INT_ENABLE; /* IR-Empfang loest ab jetzt Interrupts aus */ sekundenzaehler = 1000; /* Eine Zeit waehlen, die mit der Initialisierung der diversen anderen Zaehler nicht kollidiert */ aktivzeit = sekundenzaehler; /* letzte Zeit mit Aktivitaet merken */ timer = 0; /* Schneller Zaehler fuer Interrupts initialisieren */ zustand = S_WARTEN; /* Der Zustand der Muetze wird hierueber gesteuert */ while (1) { if ((aktivzeit + INAKTIVDAUER) < sekundenzaehler) { /* Wenn die Inaktive Zeit zu lange angedauert hat wird die Muetze ausgeschaltet */ VCC_5V_OFF; /* 5V abschalten */ IR_PORT_DOWN; /* Den Pullup des Eingangs auf 0 schalten um Strom zu sparen */ IR_INT_DISABLE; /* Keine IR-Interrupts zulassen */ LPM4; /* Alles abschalten und nur noch auf den einen Interrupt durch Taste warten */ /* Hier geht es nur weiter, wenn man die Taste gedrueckt hat. */ IR_INT_ENABLE; /* IR-Interrupts wieder zulassen */ /* Die 5V und der Port fuer den IR-Empfang wurden schon in der Interruptroutine aktiv gesetzt */ aktivzeit = sekundenzaehler; /* Aktivitaet festhalten */ } switch(zustand) { /* Je nach Zustand werden verschiedene Aktionen durchgefuehrt */ case S_WARTEN: /* Muetze wartet auf Treffer oder Tastendruck */ if (irempfang) { /* Wenn der Empfaenger was gesehen hat. */ irempfang = 0; if (irrxdaten == 0) { /* Wenn es eine 0 war, dann ist man getroffen */ blink(1, BLINKLED_ALLE, BLINK_AN_TREFFER, BLINK_AUS_TREFFER); /* Treffer durch erstes Blinken anzeigen */ ir_out(muetzennummer); /* Eigene Muetzennummer aussenden, damit Pistole abgeschaltet wird */ trefferzeit = sekundenzaehler; /* Ab hier beginnt die Trefferzeit zu laufen */ aktivzeit = sekundenzaehler; /* Aktivitaet festhalten */ treffermeldecount = 0; /* Zaehler fuer Aussendung der eigenen Muetzennummer */ zustand = S_GETROFFEN; /* Die Statemachine ist hiernach in einem neuen Zustand */ } else { /* Es war kein Treffer, sondern irgend etwas anderes. Wieder auf Empfang gehen */ IR_INT_RESET; IR_INT_ENABLE; } } if (TASTE_GEDRUECKT) { /* Bei Tastendruck geht man in einen eigenen Zustand ueber */ zustand = S_TASTE; } break; case S_GETROFFEN: /* Ist man getroffen, so wird einige Zeit mit allen LEDs geblinkt */ if ((trefferzeit + TREFFERDAUER) > sekundenzaehler) { blink(1, BLINKLED_ALLE, BLINK_AN_TREFFER, BLINK_AUS_TREFFER); /* Am Anfang wird noch ein paar mal versucht die eigene Pistole abzuschalten, falls es nicht sofort funktioniert haben sollte */ if (treffermeldecount < ANZ_TREFFER_MELDER) { treffermeldecount++; ir_out(muetzennummer); } } else { zustand = S_SCHUTZ; /* Nach Ablauf der Trefferzeit geht man in den Zustand des Trefferschutzes ueber */ } break; case S_SCHUTZ: /* Es gibt nun eine Zeit, in der man nicht getroffen werden kann. Nur die LED am Schirm blinkt. */ blink(1, BLINKLED_SCHIRM, BLINK_AN_TREFFER, MS(800)); if ((trefferzeit + TREFFERDAUER + SCHUTZDAUER) < sekundenzaehler) { /* Wenn die Schutzzeit um ist, werden wieder IR-Impulse empfangen und man kann wieder getroffen werden */ irempfang = 0; IR_INT_RESET; IR_INT_ENABLE; zustand = S_WARTEN; } break; case S_TASTE: /* Zunaechst wird zweimal kurz geblinkt, sozusagen zur Einstimmung */ blink(2, BLINKLED_ALLE, BLINK_AN_FEHLER, BLINK_AUS_FEHLER); delay(MS(500)); /* Nun wird sooft zum Mitzaehlen geblinkt wie die Muetzennummer angibt. */ blink(muetzennummer, BLINKLED_ALLE, BLINK_AN_ZAEHLEND, BLINK_AUS_ZAEHLEND); delay(MS(500)); blink(2, BLINKLED_ALLE, BLINK_AN_FEHLER, BLINK_AUS_FEHLER); delay(MS(1000)); /* Wenn man bis jetzt die Taste wieder losgelassen hat, hat man einfach nur die Muetzennummer ausgelesen. */ if (TASTE_GEDRUECKT) { /* Bleibt man weiter auf der Taste, wird die Muetzennumer um eins erhoeht und durch Blinken angezeigt */ while (TASTE_GEDRUECKT) { if (muetzennummer < 7) { /* Die Nummern gehen von 1 bis 7 */ muetzennummer++; } else { muetzennummer = 1; } blink(muetzennummer, BLINKLED_ALLE, BLINK_AN_ZAEHLEND, BLINK_AUS_ZAEHLEND); delay(MS(1000)); } flashWrite(MUETZENNUMMER_ADDR, muetzennummer); /* Die aktuelle Nummer wird im Flash-EPROM gesichert */ } zustand = S_WARTEN; /* Wieder in den normalen Zustand wechseln */ break; default: break; } } }