5.4  Verwendung der A/D-Wandler

Im vorherigen Abschnitt haben wir einen Überblick über die Einstellungsmöglichkeiten und die Initialisierung der A/D-Wandlung kennengelernt. Im Folgenden wollen wir uns etwas mehr mit dem konkreten Einsatz des A/D-Wandlers in der Praxis befassen. Angefangen mit einem einfachen Beispiel wird im zweiten Abschnitt dann eine komplexe Steuerung inklusive Interrupts und Energiesparmodus erklärt.

5.4.1  Einfaches Beispiel für die A/D-Wandlung mit dem ADC12

In diesem einfachen Beispiel werden wir die A/D-Wandlung in einer Endlosschleife ausführen. Daran lässt sich die Funktionalität des A/D-Wandlers sehr gut verdeutlichen. Für energieeffiziente Anwendungen eignet sich dieses Beispiel jedoch nicht, doch dazu mehr in Kapitel 5.4.2.

Zuerst müssen wir den A/D-Wandler und die Eingänge konfigurieren. Unser Beispielprozessor ist wieder der MSP430F1612. Der A/D-Wert soll am Port P6.0 (also Kanal A0) erfasst werden. Dazu muss im P6SEL-Register die Spezialfunktion für P6.0 aktiviert werden. Als Referenzspannung wird die interne 2.5V-Spannung gewählt. Wir dürfen natürlich nicht vergessen, den Referenzspannungsgenerator mit REFON zu aktivieren. Für eine optimierte Auflösung wählen wir die maximale Sample-and-Hold-Time aus (SHT0_15).

#include <msp430x14x.h> 
int main(void) 
{ 
    WDTCTL = WDTPW + WDTHOLD;  // Watchdog-Timer deaktivieren 
    P6SEL=BIT0;                // P6.0 als A/D-Eingang 
    P6IE =0x00;                // Interrupts für P6 deaktivieren 
                               // Initialisierung des ADC12 
 
    // ADC12 deaktivieren, um Einstellungen zu ändern 
    ADC12CTL0 = ~ENC; 
    ADC12CTL0 = ADC12ON + SHT0_15 + REFON + REF2_5V; 
    ADC12CTL1 = SHP;           // SHP benutzen, SH Time = 4 
}
Listing 5.5: Initialisierung der A/D-Wandlung

Bisher haben wir nur den A/D-Wandler initialisiert. Beim ADC12 haben wir insgesamt 15 Ergebnisregister für die A/D-Wandler. Jedem Einzelnen können wir einen anderen Eingangskanal zuordnen.

Mit der Anweisung

    ADC12CTL1 = SHP;           // SHP benutzen

haben wir gleichzeitig die Bits CSTARTADDx = 0 gesetzt. Damit legen wir fest, dass wir bei einer einzigen A/D-Wandlung das Ergebnis in das ADC12MEM0- Register speichern. Wir müssen nun den Kanal des Registers und die dazu gehörende Referenzspannung auswählen:

 // ... 
 
 // Einstellungen für das A/D-Register auswählen 
 ADC12MCTL0 = SREF_1 + INCH_0;
Listing 5.6: Auswahl des A/D-Kanals und der Referenzspannung

Wir wählen dadurch als Referenzspannung die interne Referenzspannung mit den vorher initialisierten 2.5V aus. Mit INCH_0 wird das ADC12MEM0-Register mit dem Kanal A0, also mit dem Port P6.0, verknüpft. Jetzt können wir kontinuierlich einzelne A/D-Werte in einer Endlosschleife erfassen.

#include <msp430x14x.h> 
int main(void) 
{ 
  int ad_wert; 
 
  WDTCTL = WDTPW + WDTHOLD;  // Watchdog-Timer deaktivieren 
  P6SEL=BIT0;                // P6.0 als A/D-Eingang 
  P1IE=0x00;                 // Interrupts für P1 deaktivieren 
 
  // ADC12 deaktivieren, um Einstellungen zu ändern 
  ADC12CTL0 = ~ENC; 
 
  // Initialisierung des ADC12 
  ADC12CTL0 = ADC12ON + SHT0_15 + REFON + REF2_5V; 
  ADC12CTL1 = SHP;           // Pulse-Sample-Mode benutzen 
 
  // Einstellungen für das A/D-Register auswählen 
  ADC12MCTL0 = SREF_1;       // Kanal A0 (P6.0) 
 
  // Einstellungen übernehmen und ADC12 starten 
  ADC12CTL0 |= ENC; 
 
  // Erfassung der A/D-Werte 
  ADC12CTL0 |= ADC12SC; 
  while(1)                   // Endlosschleife 
  { 
   ADC12CTL0 |= ADC12SC;        // Wandlung starten 
   while(ADC12CTL0 & ADC12SC) {}// warte bis fertig 
   ad_wert = ADC12MEM0;         // Ergebnis auslesen 
  } 
}
Listing 5.7: Initialisierung der A/D-Wandlung

Schließen wir jetzt einen Potentiometer, wie in Bild 5.20 gezeigt, an den richtigen Einganspin an, so können wir auf der Basis des eben entwickelten Programms die Spannung am Potentiometerabgriff messen. Da wir die 2.5V-Referenzspannung auswählen, müssen wir die maximale Eingangsspannung durch einen Spannungsteiler auf Umax = 3.3V 10k(10k + 3k3) = 2.48V limitieren.


PIC

Bild 5.20.: Spannungsteiler mit Poti und MSP430


Möchte man die Spannung in Millivolt ausgeben, so muss der Ergebniswert noch zusätzlich skaliert werden, wie in dem nun folgenden Beispiel. Numerisch korrekt müsste man UAD = ADWert4095 * 2500 berechnen. Dies beinhaltet jedoch eine Division, die umfangreich zu berechnen ist. Eine einfache Näherung ist dafür UAD = ADWert * (1016 + 164). Damit erreicht man einen maximalen relativen Fehler von 2 o, der in etwa auch der maximalen Rechengenauigkeit der Integer-Arithmetik entspricht. Gleichzeitig spart man aber viele Rechenschritte, da die Division vom Compiler durch eine Shift-Operation ersetzt wird. Das Ergebnis kann mit dem Debugger durch Auslesen der Variablen U_ad kontrolliert werden.

// .... 
  ad_wert = ADC12MEM0; 
  // Umwandlung AD->Spannungswert [mV] 
  u_ad = (ad_wert * 10) / 16 + (ad_wert) / 64; 
// ....
Listing 5.8: Umrechnung des A/D-Wandler-Ergebnisses in Millivolt

5.4.2  Interruptgesteuerte A/D-Wandlung mit dem ADC12

Im folgenden Beispiel soll über einem Drehpoti die Blinkfrequenz einer an P1.0 angeschlossenen Low-Current-Diode eingestellt werden können. Die Schaltung ist in Bild 5.21 dargestellt.


PIC

Bild 5.21.: Anschluss des Drehpoti-Spannungsteilers an A0 (P6.0) und der Low-Current-LED an P1.0


Das Blinken der LED wird mit Hilfe des Timers A im Up-Mode realisiert. Dafür wird der Wert der A/D-Wandlung in das Register TACCR0 kopiert, mit dem die Überlauf-Schwelle des Timers und damit auch die Blinkfrequenz festgelegt wird. Der Timer inkrementiert das Zählregister, bis der Wert TACCR0 erreicht wurde und ruft dann einen Interrupt auf. In der Interrupt-Service-Routine des Timers A wird die LED dann getoggelt. Um das Programm energiesparend auslegen zu können, wählen wir für den Timer A als Taktquelle ACLK, die wir auch für das ADC12-Modul verwenden. Das komplette Beispielprogramm wird in Listing 5.9 vorgestellt. Bei niedrigen Spannungen über R2 blinkt die LED so schnell, dass das Ergebnis nur mit einem Oszilloskop sichtbar ist.

#include  <msp430x16x.h> 
int led; 
 
#pragma vector=ADC12_VECTOR 
__interrupt void ADC12_measure (void) 
{ 
  TACCR0 = ADC12MEM0 + 1;   // A/D-Wert in TACCR0 speichern 
  ADC12CTL0 |= ADC12SC;     // Konvertierung erneut starten 
} 
 
 
#pragma vector=TIMERA0_VECTOR 
__interrupt void TIMERA_blink (void) 
{ 
  led = ~led;               // LED toggeln 
  P1OUT = led; 
} 
 
void main(void) { 
  WDTCTL = WDTPW + WDTHOLD; // Watchdog ausschalten 
  led = 0;                  // Initialisiere LED-Ausgang 
  P1DIR = BIT0; 
  P1OUT = led; 
 
  BCSCTL1&=~XTS;            // Wähle LF 
  _BIC_SR(OSCOFF);          // Start LFXT1 
 
  TACTL   = TASSEL_1;        // Wähle ACLK 
  TACTL   |= MC_1;           // Up-Mode 
  TACCTL0 = CCIE;           // Interrupt-Enable Cap/Comp 
  TACCR0   = 4096;          // Initialisierung 
 
  P6SEL=BIT0;               // P6.0 als A/D-Eingang 
  P1IE=0x00;                // Interrupts für P1 deaktivieren 
 
  ADC12CTL0 = ~ENC;         // Initialisierung des ADC12 
  ADC12CTL0 = ADC12ON + SHT0_15 + REFON + REF2_5V; 
  ADC12CTL1 = ADC12SSEL_1 + SHP;  // Pulse-Sample-Mode benutzen und 
                            // ACLK verwenden 
  ADC12MCTL0 = SREF_1;      // VREF+ und AVSS als Referenz 
  ADC12IE = 0x01;           // Interrupt für ADC12 aktivieren 
  ADC12CTL0 |= ENC;         // ADC12 starten 
  _enable_interrupts ();    // Interrupts global aktivieren 
 
  ADC12CTL0 |= ADC12SC;     // Konvertierung starten 
 
  while(1) { 
  LPM3;   // LPM3 auswählen Konvertierung läuft im Hintergrund 
          // weiter, weil ACLK aktiv bleibt 
  } 
}
Listing 5.9: Beispielprogramm für das ADC12-Modul

5.4.3  Interruptgesteuerte A/D-Wandlung mit dem SD16_ A

Wir werden im folgenden einfachen Beispiel über den Port P1.2 ein analoges Signal mit einem Interrupt einlesen und auswerten. Dafür definieren wir zunächst die Interrupt-Service-Routine (Listing 5.10).

#pragma vector=SD16_VECTOR 
__interrupt void ADC16_interrupt (void) 
{ 
  if (SD16MEM0 < 54613)     // Eingangsspannung größer 
                            // kleiner 0,5 V? 
    P1OUT &= ~0x01;         // LED aus 
  else 
    P1OUT |= 0x01;          // LED ein 
}
Listing 5.10: Interrupt-Service-Routine für SD16_A

Eine LED am Port P1.0 soll bei einer Spannung über 0.5V leuchten. Bei der Verwendung der internen Referenzspannung beträgt die maximale Eingangsspannung 0.6V, was einem A/D-Wert von 65535 entspricht. Den korrespondierenden A/D-Wert für den Grenzwert von 0.5V berechnen wir durch die Multiplikation der Auflösung 655350.6V mit 0.5V:

65535-
0.6V  ⋅0.5V   ≈  54613                        (5.18)

Nun fehlen noch die Einstellungen für den SD16_ A. Zunächst wählen wir mit dem Register SD16CTL die Taktquelle aus und aktivieren den internen Referenzspannungsgenerator. Als Eingangskanal wird im Register SD16INCTL0 der Kanal A1 (A1+ = P1.2) ausgewählt. Für den unipolaren Betrieb (SD16CCTL0 = SD16UNI) wird A1 automatisch auf Masse gelegt. Gleichzeitig aktivieren wir die Interrupt-Funktion für denSD16_ A im SD16CCTL0-Register. Die Einstellungen sind Listing 5.11 zu entnehmen.

 SD16CTL = SD16REFON + SD16SSEL_1;// Referenzgenerator aktivieren 
                                  // Taktquelle = SMCLK 
 SD16INCTL0 = SD16INCH_1;         // Kanal A1 auswählen 
 SD16CCTL0 =  SD16UNI + SD16IE;   // Interrupt aktiv, Unipolar
Listing 5.11: Einstellungen für den SD16_ A

Die Referenzspannung kann für jeden Kanal unabhängig mit dem Register SD16AE festgelegt werden. Für den Kanal A1 muss daher SD16AE = SD16AE1 gesetzt werden. Alle anderen Kanäle werden darüber hinaus durch diese Zeile deaktiviert. Als komplettes Listing erhalten wir dann für das Interrupt-Beispiel mit dem SD16_ A Listing 5.12.

#include  <msp430x20x3.h> 
 
#pragma vector=SD16_VECTOR 
__interrupt void ADC16_interrupt (void) 
{ 
  if (SD16MEM0 < 54613)          // kleiner 0.5 V? 
    P1OUT &= ~BIT0;              // LED aus 
  else 
    P1OUT |= BIT0;               // LED ein 
} 
 
void main(void) 
{ 
  WDTCTL = WDTPW + WDTHOLD;        // Watchdog ausschalten 
  P1DIR |= BIT0;                   // P1.0 als Augang (LED) 
  SD16CTL = SD16REFON + SD16SSEL_1;// Referenzgenerator ON 
                                   // Taktquelle = SMCLK 
  SD16INCTL0 = SD16INCH_1;         // Kanal A1 auswählen 
  SD16CCTL0 =  SD16UNI + SD16IE;   // Interrupt, unipolar 
  SD16AE = SD16AE1;                // A1 aktivieren, A1- = GND 
  SD16CCTL0 |= SD16SC;             // Nach den Einstellungen kann 
                                   // ADC gestartet werden 
  _BIS_SR(LPM0_bits + GIE);        // LPM0 und Interrupt global 
}
Listing 5.12: Interrupt-Beispiel für den SD16_A