Software

Das Programm für den Mikrocontroller ist 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 2KByte Programmspeicher bieten, macht es wenig Sinn das Programm in einzelne Module zu unterteilen. Daher besteht es nur aus zwei Quelldateien:

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.

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
    Pin2:  P1.0 Input, int. Pullup, linke Taste
    Pin3:  P1.1 Input, int. Pullup, rechte Taste
    Pin4:  P1.2 Input, int. Pullup, NC
    Pin5:  P1.3 Input, int. Pullup, NC
    Pin6:  P1.4 Input, int. Pullup, NC
    Pin7:  P1.5 Output, TA0, Ultraschallsignal zum Mischer
    
    Pin8:  P1.6 Input, int. Pullup, NC
    Pin9:  P1.7 Input, int. Pullup, NC
    Pin10: RESET
    Pin11: TEST
    Pin12: P2.7 Output, FREQANZ
    Pin13: P2.6 Input, int. Pullup, NC
    Pin14: GND */
Anschlussbelegung der Pistolenelektronik

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.

Seitenanfang

main.c

Das Programm besteht im Wesentlichen aus zwei Teilen: Einer Endlosschleife (Background) in main() wo fortwährend geprüft wird, ob eine Taste gedrückt wurde, und zwei Interruptroutinen, die für die Ultraschall (US) Signalausgabe, die Ansteuerung der Frequenzanzeige und andere Zeitmanagementaufgaben zuständig sind.

Zunächst werden einige Konstanten definiert, die die zeitlichen Zusammenhänge beschreiben. Einige Konstanten werden aus anderen berechnet, um eine leichte Anpassung an andere Verhältnisse zu erleichtern.

 #include "hardware.h"
 #include <signal.h>
 
 #define SYSFREQ 12000000l /* Systemtakt in Hz */
 #define FREQPWM 2000l     /* Taktfrequenz fuer Drehspulanzeige und Tastenabfrage */
 #define PERIODEPWM (SYSFREQ / FREQPWM /2) /* Wert fuer Timer um FREQPWM zu erreichen */
 #define TASTENZEIT (FREQPWM / 10) /* Wartezeit fuer Tastenentprellung */
 #define TASTENZEITREPEAT (FREQPWM / 3) /* Wartezeit fuer Auto-Repeat der Tasten */
 
 #define SCANTEMPO 400
Konstantendefinitionen für Zeitmanagement

Als nächtses sind die 24 Frequenzen definiert, die bei einem Scan von 20kHz bis 100kHz durchlaufen werden. Es wird jeweils um 3,5kHz weitergeschaltet. Statt die Teilerwerte für den Timer zur Laufzeit zu berechnen, sind sie in der Tabelle vorberechnet. Auch die jeweiligen PWM-Werte für die Frequenzanzeige sind in einer Tabelle definiert, da das verwendete VU-Meter nicht linear ist. Die Werte sind durch Ausprobieren ermittelt worden und passen nur auf das Meßgerät das in meiner Schaltung verwendet ist.

 #define STUETZSTELLEN 24
 /* Liste der Zaehlerwerte fuer die gewuenschten Frequenzen, die durchgescannt werden */
 /* Der Wert in Hz ist SYSFREQ / freq[x] / 2, da mit jedem Interrupt der Ausgang die Polaritaet wechselt. */
 /* Die Zaehlerwerte sind gerundet und treffen die gewuenschte Frequenz nicht immer genau. */
 const int freq[STUETZSTELLEN] = {300, 255, 222, 197, 176,
                                  160, 146, 135, 125, 117,
                                  109, 103,  97,  92,  87,
                                   83,  79,  75,  72,  69,
                                   67,  64,  62,  60};
 
 #define DREHSPULMAX 100
 /* Da die Drehspulanzeige nicht linear ist, wird fuer jede Frequenz der enstprechende PWM-
    Wert hier in dieser Liste hinterlegt. */
 /*                                   20,0  23,5  27,0  30,5  34,0 */
 const int anzeige[STUETZSTELLEN] = {   1,    6,    9,   12,   15, 
 /*                                   37,5  41,0  44,5  48,0  51,5 */
                                       17,   20,   22,   25,   27,
 /*                                   55,0  58,5  62,0  65,5  69,0 */
                                       30,   32,   36,   38,   41,
 /*                                   72,5  76,0  79,5  83,0  86,5 */
                                       45,   49,   53,   58,   62,
 /*                                   90,0  93,5  97,0, 100*/
                                       70,   75,   81,  89};
Konstantendefinitionen für US-Signal und Frequenzanzeige Seitenanfang

Für das Programm sind noch einige Variablen zu deklarieren. Da die meisten sowohl in main() als auch im Interrupt verwendet werden, müssen sie als volatile gekennzeichnet sein.

 volatile int findex; /* Index der momentan benutzten Frequenz */
 volatile int anzzaehler; /* Zaehler fuer Drehspul PWM */
 volatile int anzeigenwert; /* Wert, der ans Drehspulwerk ausgegeben werden soll */
 volatile int delayzaehler; /* Fuer Wartezeit */
 volatile int tastenzaehler; /* Fuer Autorepeat bei gedrueckter Taste */
 volatile int tastenwert; /* Wert der gedrueckten Taste */
 volatile int taste_gedrueckt; /* Flag, dass Taste gedrueckt wurde */
 
Variablendeklarationen

Die erste Interruptroutine wird mit der zweifachen Ultraschallfrequenz aufgerufen. Der Timer0, der diesen Interrupt auslöst ist so programmiert (siehe Zeile 133), daß er automatisch jedesmal die Polarität des ihm zugeordneten Ausgangs wechselt. Die Interruptroutine muß nur dafür sorgen, daß der Timer den nächsten Interrupt zur richtigen Zeit erhält.

 /* Dieser Interrupt kommt mit der Folgefrequenz * 2, die fuer den Mischer gebraucht wird
    Der Ausgang wechselt mit jedem Auftreten automatisch. Die Interruptroutine muss nur
    den neuen zu erreichenden Zaehlerwert eingeben. */
 interrupt (TIMERA0_VECTOR) timera0_interrupt(void)
 {
     TACCR0 += freq[findex];
 }
Interrupt für Ultraschallsignalerzeugung Seitenanfang

Die zweite Interruptroutine wird mit 2kHz Folgefrequenz aufgerufen. Zunächst wird der neue Timerwert einprogrammiert, damit der nächste Interrupt zur richtigen Zeit erfolgt. Um die hochfrequente Interruptfolge des US-Signals nicht zu stören, werden als erstes noch alle anderen Interrupts wieder zugelassen. Wenn man das nicht machen würde, könnte es zu Aussetzern beim US-Signal kommen, was man eventuell als Kratzen oder Rauschen hören könnte.

In Zeile 61 wird die Variable delayzaehler hochgezählt. Sie wird in main() benutzt, um eine definierte Zeit zu warten.

In Zeile 63 bis 71 wird das PWM Signal für die Frequenzanzeige erzeugt. Die Variable anzzaehler zählt laufend bis 100 (DREHSPULMAX) was einer Folgefrequenz von 20Hz entspricht. Mit jedem Nulldurchgang von anzzaehler wird der Ausgang eingeschaltet und beim Erreichen vom anzeigenwert wird er wieder abgeschaltet. anzeigenwert wird entsprechend der momentan benutzen Frequenz aus der Tabelle anzeige[STUETZSTELLEN] geladen (Zeile 189).

In Zeile 73 bis 91 werden die Tasten abgefragt. Wenn eine Taste, nach einer gewissen Entprellzeit, als gedrückt erkannt ist, wird dies durch die Variable taste_gedrueckt dem Background mitgeteilt. Im Folgenden wird bei länger gedrückter Taste taste_gedrueckt immer wieder gesetzt, um z.B. eine neue Frequenz bequemer zu erreichen als durch häufiges Tastendrücken (Autorepeat-Funktion).

Da nur Timer 1 diesen Interrupt auslösen kann, ist kein Code für eventuelle andere Interruptquellen vorhanden (Zeile 94 bis 98).

 /* Dieser Interrupt kommt mit der PWM Frequenz von 2kHz. Er wird zur Tastenabfrage, Zeitmessung
    und Drehspulmesswerkansteuerung benutzt. */
 interrupt (TIMERA1_VECTOR) timera1_interrupt(void)
 {
   eint(); /* Interrupts wieder zulassen, damit TIMERA0 weiterhin drankommen kann. */
   switch (TAIV) {
     /* Nur bei Ueberlauf von TACCR1 */
   case 2:
     TACCR1 += PERIODEPWM; /* Neuen Zaehlervergleichswert speichern */
 
     /* Zeitmessung */
     delayzaehler++; /* Fuer Backgroundzeitmessungen hochzaehlen */
 
     /* Anzeige PWM */
     anzzaehler++; /* Zaehler fuer Drehspul PWM hochzaehlen */
     if (anzzaehler >= DREHSPULMAX) { /* Drehspul PWM zaehlt bis DREHSPULMAX */
       anzzaehler=0; 
       FREQANZ_AN; /* Beim Zuruecksetzen wird der Ausgang auf Eins gesetzt */
     }
     if (anzzaehler > anzeigenwert) {
       FREQANZ_AUS; /* Wenn der momentane PWM Wert erreicht ist, wird der Ausgang abgeschaltet */
     }
 
     /* Tastenbehandlung */
     if (TASTE_GEDRUECKT) {
       if (tastenzaehler == (TASTENZEIT - 1)) { /* Kurz vor Ende der Tastenentprellzeit wird der
                                                   Tastenwert gespeichert */
         tastenwert = TASTE_WERT;
       }
       if (tastenzaehler >= TASTENZEITREPEAT) { /* Wenn die Repeatzeit abgelaufen ist, wird wieder
                                                   gemeldet, dass Taste erneut gedrueckt wurde */
         taste_gedrueckt = 1;
         tastenzaehler = 0; /* Zeit beginnt erneut fuer Repeatzeit */
       }
       tastenzaehler++; /* Bei gedrueckter Taste wird diese Zeit gezaehlt */
     }
     else {
       if (tastenzaehler >= TASTENZEIT) { /* Wenn nach TASTENZEIT losgelassen wird, gilt Taste als
                                             gedrueckt. */
         taste_gedrueckt = 1;
       }
       tastenzaehler = 0; /* Fuer Autorepeat Zaehler erneut beginnen */
     }
     break;
   case 4:
     break;
   case 10:
     break;
   default:
     ;
   }
 }
Interrupt für Frequenzanzeige und Tastenabfrage Seitenanfang

main() ist das Hauptprogramm, das nach dem Starten des Prozessors aufgerufen wird. Zunächst werden alle Peripherieeinheiten, wie IO-Pins, Timer und interne Clocks auf die gewünschten Werte eingestellt. Danach werden Variablen mit sinnvollen Werten belegt.

 int main(void) {
 
   int scan;
 
   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;
   P2REN = P2REN_INIT;
 
   P1IES  = P1IES_INIT;                /*init port interrupts*/
   P2IES  = P2IES_INIT;
   P1IE   = P1IE_INIT;
   P2IE   = P2IE_INIT;
 
   DCOCTL = CALDCO_12MHZ; /* 12MHz sind die gewuenschte Systemfrequenz */
   BCSCTL1 = XT2OFF | CALBC1_12MHZ;
   TACTL = TASSEL_SMCLK | ID_DIV1 | MC_CONT | TACLR | TAIE;
   findex = 0;
   TACCR0 = freq[findex];
   TACCR1 = PERIODEPWM;
   TACCTL0 = CM_DISABLE | CCIS_GND | SCS_ASYNC | CAP_COMP | OUTMOD_TOGGLE | CCIE | OUT_LOW;
   TACCTL1 = CM_DISABLE | CCIS_GND | SCS_ASYNC | CAP_COMP | OUTMOD_TOGGLE | CCIE | OUT_LOW;
   eint();
   scan = 1; /* zu Beginn wird automatisch gescannt */
   anzzaehler = 0;
   anzeigenwert = 0;
   taste_gedrueckt = 0; /* zunaechst gilt keine Taste als gedrueckt */
Initialisierung in main() Seitenanfang

Nach der Initialisierung arbeitet das Hauptprogramm als Endlosschleife die Zeilen 140 bis 192 ab.

Es gibt im Wesentlichen zwei Modi: Im Scan-Mode (scan==1) wird automatisch die jeweilige US-Frequenz von 20kHz bis 100kHz durchgestimmt (Zeile 142 bis 162). Die Geschwindigkeit wird durch die Konstante SCANTEMPO bestimmt, gegen die die Variable delayzaehler immer wieder verglichen wird. delayzaehler wird, wie schon beschrieben, im Interrupt mit 2kHz hochgezählt. Wird während des Scans ein Tastendruck erkannt (Zeile 157), so wird der Scan-Mode verlassen und die gerade gewählte Frequenz bleibt stehen.

Im Manuellen Mode (scan==0) ist, wie gerade gesagt, die Frequenz konstant. Sie kann aber durch Drücken einer Taste nach oben oder unten verändert werden. Dies geschieht in den Zeilen 165 bis 180.

Wenn beide Tasten gemeinsam gedrückt werden (Zeile 174), wird wieder in den Scan-Mode gewechselt.

   while (1) {
     if (scan) {
       /* Wenn der automatische Scan eingeschaltet ist */
       /* naechst hoehere Frequenz einstellen */
       if (findex < STUETZSTELLEN - 1) {
         findex++;
       }
       else {
         /*  wieder unten anfangen, wenn hoechste erreicht */
         findex = 0;
       }
       /* Den neuen Frequenzwert anzeigen */
       anzeigenwert = anzeige[findex];
       /* Ein wenig auf dieser Frequenz warten */
       delayzaehler = 0;
       while (delayzaehler < SCANTEMPO); 
       /* Danach nachsehen, ob der Anwender eine Taste gedrueckt hat */
       if (taste_gedrueckt == 1) {
         /* Wenn Taste gedrueckt: Scan stoppen, Tastendruck zuruecknehmen */
         scan = 0;
         taste_gedrueckt = 0;
       }
     }
     else {
       /* Wenn nicht gescannt wird, werden nur noch Tasten abgeprueft */
       if (taste_gedrueckt == 1) {
         /* Es wurde eine Taste gedrueckt */
         if ((tastenwert & TASTE1) == 0) {
           /* TASTE1 stellt niedrigere Frequenz ein, wenn noch nicht die niedrigste erreicht */
           scan = 0;
           if (findex > 0) {
             findex--;
           }
         }
         if ((tastenwert & TASTE2) == 0) {
           /* TASTE2 erhoeht die Frequenz */
           scan = 0;
           if (findex < STUETZSTELLEN - 1) {
             findex++;
           }
         }
         if ((tastenwert & (TASTE1 | TASTE2)) == 0) {
           /* Wenn beide Tasten gemeinsam gedrueckt werden, wird der Scan wieder gestartet */
           scan = 1;
         }
         /* In jedem Falle Tastendruck zuruecknehmen */
         taste_gedrueckt = 0;
       }
       /* Neue Frequenz im Drehspulmesswerk anzeigen */
       anzeigenwert = anzeige[findex];
     }
   }
 }
Endlosschleife in main() Seitenanfang