ADC mal ohne BASCOM-"GetADC"

Wer es sich leisten kann, in seinem Microcontroller-Programm unnötig Zeit mit dem Auslesen des ADC zu vertrödeln, bleibt bei dem BASCOM-Befehl GetADC. Ist ja auch schnell hingeschrieben. GETADC wird wohl mit jedem Aufruf den Multiplexer auf den jeweiligen ADC-Port einstellen, den Vorteiler einstellen, den ADC starten, warten, bis die Messung ausführt ist und schließlich den ADC auslesen. Das braucht Zeit. Überhaupt – was versteckt sich hinter solchen Hochsprachenbefehlen? Führen sie tatsächlich das aus, was beabsichtigt ist? Ohne Assemblerkenntnisse steht man da im Dunkeln.

Zeit also, sich mit den ADC-Registern im Datenblatt zu beschäftigen, um den ADC direkt über die Register zu steuern und auszulesen, ausprobiert mit einem ATmega16. Controller neuerer Baureihen haben weitere ADC-Register. Im Internet findet man dazu jede Menge Problemschilderungen, aber kaum funktionierende Lösungen. Das jeweilige Datenblatt zu Rate zu ziehen, ist auf alle Fälle eine gute Idee.

Nachtrag in Abschnitt 6: Funktion "ADCOneShot" als mehr als doppelt so schneller Ersatz für "GetADC".

Zum Test diente folgende kleine Schaltung, zur Demonstration mit Tiefpass an ADC2:

ADC-Testschaltung

Vor dem Programmieren steht das Studieren (des AVR-Datenblatts).

1  Register ADMUX – ADC Multiplexer Selection Register

Erstmal vorweg: Ein AVR mit ADC0-...ADC7-Pins hat keine 8 ADC (Analog-Digital-Converter). Er hat nur einen. Die ADC0-...ADC7-Pins werden über einen Multiplexer (MUX) im Umlaufverfahren an den einzigen ADC geschaltet.
Mit diesem Register ADMUX werden die Referenzspannung, die Datenablage und der Multiplexer eingestellt, Schreib- und Lesezugriff. Die Default-Werte gelten, wenn das jeweilige Register nicht beschrieben wird.

Bit 7 6 5 4 3 2 1 0
Name REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0
Default 0 0 0 0 0 0 0 0

Bit 7-6: REFS0, REFS1 (Reference Selection Bits 0,1)
Diese Bits bestimmen, welche Referenzspannung der ADC verwenden soll. Eine Änderung wird erst nach Abschluss der laufenden Wandlung vorgenommen.

REFS1 REFS2 Referenzspannungsquelle
0 0 Extern an AREF anliegend, internes Vref deaktiviert
0 1 Extern an AVCC anliegend mit externen Kondensator am AREF Pin
1 0 Interne 1,1V Referenz mit externen Kondensator am AREF Pin
1 1 Interne 2,56V Referenz mit externen Kondensator am AREF Pin

Vielleicht noch eine Erklärung dazu:

  1. "Extern an AREF anliegend" heißt, es wird eine Referenzspannung an den Pin AREF angelegt, z.B. ein LM336 mit 2,49V nominal.
  2. "Extern an AVCC anliegend" heißt, es wird keine zusätzliche Referenzspannung an den Pin AREF gelegt. Die Referenz wird Chip-intern der Spannungsversorgung am Pin AVCC entnommen.
  3. Interne Referenzen, je nach Controller z.B. wie hier 1,1 bzw. 2,56V.

In allen Fällen sind
der Pin AREF mit einem externen Kondensator (100nF) nach AGND abgeblockt
und der Pin AVCC über einen Tiefpass 10µH/100nF mit VCC (+5V) verbunden.

Für die Kalibrierung des ADC zur massebezogenen Spannungsmessung (single ended)
mit U = ADC-counts * VREF / 1024
sollte das jeweilige VREF gemessen werden. Gerade die internen Referenzspannungen sind nie ganz genau. Die vom 5V-Spannungsregler an AVCC angelegte Referenz im Fall der zweiten Tabellenzeile auch nicht.

Grundsätzlich ist die Referenzspannung VREF den an den ADC-Eingängen zu messenden Spannungen anzupassen, um die doch begrenzte Auflösung von maximal 10Bit voll auszunutzen.
Bei VREF = 5V ist die Auflösung 5/1024 ≈ 5mV,
bei VREF = 2,5V ist sie 2,5/1024 ≈ 2,5mV, wenn die Eingangsspannung unterhalb von 2,5V bleibt.

Bit 5: ADLAR (ADC Left Adjust Result)
Dieses Bit legt fest, wie der ermittelte 10bit-Messwert im ADC Data Register ADCL und ADCH abgelegt wird. Eine Änderung wird sofort, also auch bei laufender Wandlung vorgenommen.
ADLAR=0: Ausrichtung rechts, (ADCH_ADCL) = (000000xx_xxxxxxxx)
ADLAR=1: Ausrichtung links (ADCH_ADCL) = (xxxxxxxx_xx000000).

Bit 4-0: MUX4:0 (Analog Channel and Gain Selection Bits)
Diese Bits legen fest, welche Analogeingänge über den Multiplexer an den ADC (es gibt nur einen ADC) gelegt werden. Für Differentialeingänge können sie auch die Verstärkung einstellen. Eine Änderung wird erst nach Abschluss der laufenden Wandlung vorgenommen.

Hier nur die Einfacheingänge, Differentialeingänge im Datenblatt.

MUX4...0 00000 00001 00010 00011 00100 00101 00110 00111
Eingang ADC0 ADC1 ADC2 ADC3 ADC4 ADC5 ADC6 ADC7

2  ADCSRA - ADC Control and Status Register A

Dieses Register steuert den ADC und stellt Kontrollsignale zur Verfügung, Schreib- und Lesezugriff.

Bit 7 6 5 4 3 2 1 0
Name ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0
Default 0 0 0 0 0 0 0 0

Bit 7: ADEN (ADC Enable)
ADEN = 0: ADC wird deaktiviert,
ADEN = 1: ADC wird aktiviert
Wird der ADC während einer Wandlung deaktiviert, wird die Wandlung abgebrochen.

Bit 6: ADSC (ADC Start Conversion)
ADSC = 0: Kein Effekt,
ADSC = 1: Wandlung wird gestartet.
In der Einfachwandlung (Single Conversion Mode) muss ADSC vor jeder Wandlung auf 1 gesetzt werden.
Im Freilaufmodus (Free Running Mode) wird die fortlaufende Wandlung mit ADSC=1 erstmalig angestoßen. Die erste Wandlung nach Start des ADC mit ADEN=1 und anschließendem Setzen von ADSC=1 ist eine mit 25 ADC-Takten lange Initialisierungs-wandlung. Anschließende Wandlungen brauchen nur 13 ADC-Takte.
ADSC behält der Wert 1, solange die Wandlung läuft. Nach deren Ende geht ADSC auf 0. Damit kann also das Ende einer Wandlung abgefragt werden.

Bit 5: ADATE (ADC Auto Trigger Enable)
ADATE = 0: Kein Auto Trigger
ADATE = 1: Aktivierung des Auto Trigger Modes. Eine Wandlung wird durch die positive Flanke des ausgewählten Triggersignals ausgelöst. Die Triggersignalquelle wird mit den Bits ADTS2-ADTS0 im Register SFIOR (s. unten) festgelegt, bei neueren Controllern im Register ADCSRB.
ADATE muss für den Free Running Mode gesetzt werden. Ebenso im Single Conversion Mode, wenn Interrupts zum Anstoßen einer neuen Wandlung verwendet werden.

Bit 4: ADIF (ADC Interrupt Flag)
Dieses Bit stellt ein Interruptflag zur Verfügung (ADC Complete Interrupt). Während der laufenden Wandlung ist sein Wert 0. Er wechselt auf 1, wenn eine Wandlung erfolgt ist und das Ergebnis in den Data Registern ADCH, ADCL verfügbar ist. Der ADC Complete Interrupt wird ausgelöst, wenn das ADIE-Bit (s.u.) gesetzt ist und die Interrupts global aktiviert sind. Das Bit wird gelöscht, wenn die entsprechende Interrupt Service Routine verlassen wird.
Mit Schreiben einer 1 in das ADIF-Bit kann das Flag gelöscht werden.

Bit 3: ADIE (ADC Interrupt Enable)
ADIE = 1: Der ADC Complete Interrupt (s.o. ADIF) wird aktiviert. Er wird aber nur dann auch ausgelöst, wenn die globalen Interrupts aktiviert sind. Darauf kann mit einer zugehörigen Interrupt Service Routine reagiert werden.
ADIE = 0: Triggerquelle ist ein anderer Interrupt.

Bit 2-0: ADPS2:0 (ADC Prescaler Select Bits)
Der ADC wird mit einer geringeren Frequenz als der Controller selbst getaktet. Diese lässt sich mit einem einstellbaren Teilerfaktor aus der Crystal-Frequenz des Controllers herunterteilen. Die resultierende ADC-Taktfrequenz Crystal / Teilerfaktor sollte bei Messungen mit 10 bit Auflösung zwischen 50kHz und 200kHz liegen, z.B. Crystal=16 MHz ⇒ Teilerfaktor=128 ⇒ ADC-Takt 125 kHz. Falls nur 8 Bit ausgewertet werden, kann der ADC-Takt bis zu 1 MHz betragen. Zu 8/10 Bit-Auflösung s.u. zu ADC Data Register.
ADPS2 bis ADPS0 bestimmen den Teilerfaktor zwischen der Crystal -Frequenz und dem Eingangstakt des ADC. Die ersten zwei Teilerfaktoren 2 sind kein Schreibfehler!

ADPS2 ADPS1 ADPS0 Teilerfaktor
0 0 0 2
0 0 1 2
0 1 0 4
0 1 1 8
1 0 0 16
1 0 1 32
1 1 0 64
1 1 1 128

3  SFIOR – Special Function IO Register B

Dieses Register legt die Auto Trigger-Signalquelle fest, Schreib- und Lesezugriff (bis auf Bit 4). Bei neueren Controllern ist SFIOR durch ADCSRB (ADC Control and Status Register B) ersetzt.

Bit 7 6 5 4 3 2 1 0
Name ADTS2 ADTS1 ADTS0 Reserved ACME PUD PSR2 PSR10
Default 0 0 0 0 0 0 0 0

Bit 7-5: ADTS2:0 (ADC Auto Trigger Source)
Falls ADATE im Register ADCSRA auf 1 gesetzt ist, wird hiermit die Triggerquelle für eine ADC-Wandlung festgelegt. Wenn ADATE=0 gesetzt wird, werden ADTS2:0 ignoriert.
Eine Wandlung wird durch die positive Flanke des ausgewählten Interrupt Flags angestoßen.

ADTS2 ADTS1 ADTS0 Triggerquelle
0 0 0 Free Running Mode
0 0 1 Analog Comparator
0 1 0 External Interrupt Request 0
0 1 1 Timer/Counter0 Compare Match
1 0 0 Timer/Counter0 Compare Overflow
1 0 1 Timer/Counter1 Compare Match B
1 1 0 Timer/Counter1 Compare Overflow
1 1 1 Timer/Counter1 Capture Event

Bit 4: Reserviert, mit 0 beim Schreiben in das Register SFIOR vorbesetzten.

4  ADC Data Register ADCL / ADCH

In den Registern ADCL und ADCH stehen die Ergebnisse einer ADC-Wandlung, nur Lesezugriff. Die Anordnung der Daten (10 Bit) ist abhängig vom Bit ADLAR im Register ADMUX (s.o.).

ADLAR = 0 (rechtsbündig, Right adjust):

  ADCH ADCL
Bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Wert --- --- --- --- --- --- AD9 AD8 AD7 AD6 AD5 AD4 AD3 AD2 AD1 AD0

ADLAR = 1 (linksbündig, Left adjust):

  ADCH ADCL
Bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Wert AD9 AD8 AD7 AD6 AD5 AD4 AD3 AD2 AD1 AD0 --- --- --- --- --- ---

Das ADC Data Register wird erst dann mit Daten aus einer neuen Wandlung beschrieben, wenn ADCH gelesen wurde. Wenn also alle 10 Bit aus ADCL / ADCH zu lesen sind, muss zuerst ADCL, dann ADCH gelesen werden. Wichtig im Free running Mode.

Ist ADLAR = 0 (rechtsbündig), befinden sich die niederwertigsten Bits ADC7 bis ADC0 in ADCL, und die höchsten beiden ADC9 und ADC8 in ADCH. Die restlichen Bits sind 0. Damit kann unmittelbar eine 16Bit-Word-Variable mit dem 10 Bit-Maximalwert 2^10-1 = 1023 beschrieben werden.

Ist ADLAR=1 (linksbündig), werden die höherwertigsten 8 Bits ADC9 bis ADC2 in ADCH abgelegt, und die ersten beiden ADC1 und ADC0 in ADCL. Die restlichen Bits sind 0. Damit steht in ADCH das Ergebnis um 2 bits nach rechts verschoben. Eine Verschiebung um 2 binäre Stellen bedeutet aber eine Division durch 2^2=4. Wenn also eine 8 Bit-Auflösung ausreichend ist, ist ADLAR=1 (linksbündig) zu setzen und nur das Register ADCH auszulesen. Der Werteumfang ist dann nicht mehr 0...1023 (10 Bit), sondern nur noch 0...255 (8 Bit).

5  ADC-Einstellungen und ADC-Steuerung

Die Bits in den o.a. Registern können einzeln oder im Ganzen gesetzt werden:

  • alle 8 Bits des kompletten Registers, z.B. "ADCSRA = &B10001111"
  • einzelne Bits, z.B. "ADCSRA.ADSC = 1" bzw. "ADCSRA.6 = 1" (Register 6 in ADCSRA ist ADSC).

Praktisch sind dabei bitweise Und-/Oder-Operationen, z.B.

  • Bitweises "And": "bytTmp = ADMUX And &B11110000" ergibt z.B.
    ADMUX     &B01000011 (MUX-Kanal "0011" = 3)
    "And"     &B11110000
    Ergebnis: &B01000000 (MUX-Kanal "0000" = 0, Bits 7-4 sind unverändert) 
    Das Ergebnis ist nur dann 1, wenn beide Operanden gleichzeitig 1 sind.
    
  • Bitweises "Or": "bytTmp = ADMUX Or &B00000011" ergibt z.B.
    ADMUX     &B01000000 (MUX-Kanal "0000" = 0)
    "Or"      &B00000011
    Ergebnis: &B01000011 (MUX-Kanal "0011" = 3, Bits 7-4 sind unverändert)
    Das Ergebnis ist immer dann 1, wenn einer oder beide Operanden 1 sind.
    Das "Or" ist ein nicht ausschließendes "entweder oder oder beide".
    

Mit den im vorherigen Abschnitt gezeigten Möglichkeiten wurden zwei Varianten getestet: Free running und Single Mode. Im Free running Mode war ein beträchtliches Rauschen der Messwerte festzustellen, was sich im Single Mode deutlich reduzierte. Also wurde der Single Mode mit programmiertem Start der einzelnen ADC-Wandlungen gewählt.

A propos Rauschen: Bei langsam veränderlichen Eingangssignalen kann es sinnvoll sein, vor die ADC-Eingänge Tiefpässe anzuordnen, z.B. Serienwiderstände 1...10k und Kondensatoren 10...100n zwischen ADC-Eingang und analog GND (s.o. in der Testschaltung an ADC2).

Nachfolgend nur die die ADC-Steuerung betreffenden Codeschnipsel. Das komplette Programm ist unten im Download zu finden.

$regfile = "m16def.dat"                           'ATmega16
$crystal = 16000000                               '16 MHz xtal
...
'ADC Variables ----------------------------------------------------------------
Dim bytADMUX As Byte
Dim wrdADC As Word
Dim bytADCLo As Byte At wrdADC Overlay
Dim bytADCHi As Byte At wrdADC + 1 Overlay
Dim wrdADCVal(3) As Word                          '1=FWD, 2=REV, 3=keys
Dim sngADCVal(3) As Single                        '1=FWD, 2=REV, 3=keys
Dim bytADChannel As Byte                          '0=FWD, 1=REV, 2=keys
Dim bytADCValIdx As Byte                          'Current ADC value index
Dim bytADCRun As Byte                             'ADC runs for mean value
Dim bitADCFinished As Bit                         '=1: Conversion finished
Const bytADCRep = 5                               'Repetitive ADC runs for mean value

'Configure ADC, Reference = AVCC (ATmega16/32) --------------------------------
bytADMUX = &B01000000                             'Reference = AVCC, ADLAR=0, ADC0
ADMUX = bytADMUX                                  'ADC Multiplexer Selection Reg.
ADCSRA = &B10001111                               'ADC Control&Status Reg A
                                
'                                                  Enable ADC, div. factor 128
'SFIOR = &B00000000                                'Is default setting

Mit der Initialisierung ADMUX = &B01000000 wird eingestellt:

Bit Einstellung
7-6 01 Referenzspannung extern an AVCC anliegend mit externen Kondensator am AREF Pin
5 0 ADLAR=0: ADCL/ADCH rechtsbündig
4-0 00000 ADC0 wird gemessen

Mit der Initialisierung ADCSRA = &B10001111 wird eingestellt:

Bit Einstellung
7 1 ADEN = 1: ADC einschalten
6 0 ADSC = 0: ADC (noch) nicht starten
5 0 ADATE = 0: Kein Auto Trigger (free running mode)
4 0 ADIF wird von außen nicht gesetzt, ist das Conversion completion Interrupt flag
3 1 ADIE = 1: Conversion completion interrupt wird aktiviert
2-0 000 ADPSx = 111: Teilerfaktor 128 (Crystal 16 MHz / 128 = 125 kHz ADC-Takt)

Die Initialisierung SFIOR = &B00000000 ist der Vollständigkeit halber aufgeführt, aber auskommentiert, da der Default 0 so bleiben kann.

On ADC ADC_isr                                    'Go to interrupt service routine
SREG.7 = 1                                        'Enable interrupts
For bytTmp0 = 1 To 3
   sngADCVal(bytTmp0) = 0
Next bytTmp0

bytADCRun = 0
bytADChannel = 0                                  'First ADC channel
bitADCFinished = 0
ADCSRA.ADSC = 1                                   'Start ADC

Mit On ADC ADC_isr wird die Interrupt Service Routine definiert, in die mit abgeschlossener Wandlung gesprungen wird.
Unmittelbar vor der Do...Loop wird mit
bytADChannel = 0 der erste ADC-Kanal (ADC0) für den ersten Sprung in die ADC_isr initialisiert
und mit
ADCSRA.ADSC = 1 der ADC gestartet.
Für die erste ADC-Wandlung wurde weiter oben mit
bytADMUX = &B01000000 und ADMUX = bytADMUX
der erste ADC-Kanal (ADC0) bereits gesetzt.

Do                                                'Start of main program ******

   If bitADCFinished = 1 Then                     'Conversion complete
      bitADCFinished = 0

      'Accumulate ADC values --------------------------------------------------
      If bytADCRun < bytADCRep Then               'Accumulate bytADCRep times
         For bytTmp0 = 1 To 3
            sngADCVal(bytTmp0) = sngADCVal(bytTmp0) + wrdADCVal(bytTmp0)
         Next bytTmp0
         Incr bytADCRun
      Else
         bytADCRun = 0
         For bytTmp0 = 1 To 3
            sngADCVal(bytTmp0) = sngADCVal(bytTmp0) / bytADCRep
         Next bytTmp0

         'Show ADC values -----------------------------------------------------
         bytCol = 1
         For bytTmp0 = 1 To 3
            wrdTmp0 = Int(sngADCVal(bytTmp0))
            strTmp6 = Str(wrdTmp0)
            strTmp6 = Format(strTmp6 , "   0")
            Locate 2 , bytCol
            LCD strTmp6
            bytCol = bytCol + 5
            sngADCVal(bytTmp0) = 0
         Next bytTmp0

      End If
   End If

Loop                                              'End of main program *******

In der Do...Loop wird mit jeder vollendeten Wandlung (bitADCFinished = 1) in die Auswertung und Anzeige verzweigt. Zur Verminderung des ADC-Rauschens wird der Mittelwert aus bytADCRep Einzelmessungen gebildet. Wegen des begrenzten Platzes auf dem LCD kommen nur drei ADC-Werte zur Anzeige.

Mit Beendigung einer Wandlung verzweigt das Programm in die Interrupt Service Routine ADC_isr, da ADIE = 1 gesetzt ist. Dort werden die Messwerte ADCL und ADCH ausgelesen und mit den Overlay-Variablen bytADCLo und bytADCHi der Word-Variablen wrdADC zugeordnet. Diese wird in die Variable wrdADCVal(bytADCValIdx), bytADCValIdx = 1...3, geschrieben.

ADC_isr:
   'Interrupt Service Routine for ADC, conversion complete
   'Read 10bit ADC Data Registers ADCL & ADCH
   'bytADCLo and bytADCHi are overlayed with wrdADC
   'Set next Multiplexer channel and start ADC again

   bytADCLo = ADCL                                'Read ADCL first...
   bytADCHi = ADCH                                '... then ADCH
   bytADCValIdx = bytADChannel + 1
   wrdADCVal(bytADCValIdx) = wrdADC               '10bit ADC value
   Incr bytADChannel                              'Next MUX channel
   If bytADChannel > 3 Then                       'Only ADC0...ADC2
      bytADChannel = 0
   End If
   Admux = bytADMUX Or bytADChannel               'Set next MUX channel
   bitADCFinished = 1
   ADCSRA.ADSC = 1                                'Start ADC again
Return

Mit Hochzählen von bytADChannel (hier nur 0, 1, 2) wird der nächste Multiplexerkanal festgelegt mit Admux = bytADMUX Or bytADChannel. Anschließend wird der ADC wieder gestartet.

Mag es dem eingefleischten BASCOM-Nutzer anfangs umständlich erscheinen, statt mit "Config" und Konsorten sich mit Registern und Bits zu beschäftigen, so gewinnt man damit auch ohne Assemblerkenntnisse viel bessere Kontrollmöglichkeiten mit Hilfe der jeweiligen AVR-Datenblätter. Was allerdings darunter leiden könnte, ist die unbeschränkte Portierbarkeit zwischen Controllern verschiedener Generationen. Die "alten" ATmega16/32 haben z.B. das Register "SFIOR", bei neueren Controllern wie dem ATmega1284P heißt das neben anderen Änderungen "ADCSRB". Wir programmieren also etwas näherer an der Controller-Hardware. Ob das uns BASCOM-Fuzzies einen Achtungserfolg gegenüber den "wahren" C-Könnern bringt, bleibt offen. Ist mir aber auch egal.

6  Ergänzung vom 29.11.2015

In einem anderen Projekt waren zwei Versorgungsspannungen für eine analoge Messwertverarbeitung mit dem AVR-ADC zu überwachen. Da dies in der Do...Loop-Endlosschleife geschieht, wollte ich wissen, wie viel Zeit dafür von der eigentlichen Messwerterfassung abgezweigt wird. Zunächst wurde der Einfachheit halber, auf die Schnelle hingeschrieben, das BASCOM GetADC verwendet. Nachdem die Messwerterfassung lief, wollte ich es genau wissen. Herausgekommen ist eine Eigenbaulösung, die um mehr als den Faktor 2 schneller ist als GetADC.

GetADC wurde mit Bordmitteln als One Shot-ADC-Messung unter Verwendung der o.g. ADC-Register nachgebildet. Um das Zeitverhalten in einem kleinen Testprogramm vergleichen zu können, wird der Timer0 als 1 msec-Zeitgeber verwendet.

'Configure Timer0-Interrupt (Timer0 = 8bit, 2^8=256 counts)
'Overflow time: Overflow-Counts * Prescale / crystal frequency
'Overflow counts = 256 - PresetTimer0 = 256 - 6 = 250
'= 250 * 64 / 16.000.000 = 1 msec (crystal 16 MHz)
'Possible Prescales: 8, 64, 256, 1024, here 64

Config Timer0 = Timer , Prescale = 64
Const PresetTimer0 = 6                            'Initialize Timer0 (shorten time, >0)
On Timer0 Timer0_isr                              'On Overflow go to Timer0_isr

Die Timer0 Service Routine Timer0_isr wird damit alle 1 msec angesprungen und zählt den 1 msec-Zähler wrdTmp0 hoch.

Timer0_isr:
'Timer0 interupt service routine
   Timer0 = Presettimer0
   Incr wrdTmp0                                   '1 msec counter
Return

In der Do...Loop werden die ADC-Ausleseroutinen BASCOM "GetADC" und Eigenbau "ADCOneShot" 100 Mal aufgerufen, um im Millisekundenbereich überhaupt etwas zu sehen.

Stop Timer0
Timer0 = Presettimer0                             'initialize Timer0
wrdTmp0 = 0                                       '1 msec counter

Do                                                'Start of main program ******

   Start Timer0                                   'Start the 1 msec clock
   For bytTmp0 = 1 to 100
      wrdTmp1 = GetADC(0)                         'Test ADC1 (GetADC)
   Next bytTmp0
   Stop Timer0                                    'Stop the clock, wrdTmp0=time elapsed
   Locate 1 , 1
   LCD "100xADC1 " ; wrdTmp0 ; "msec"

   Timer0 = Presettimer0                          'initialize Timer0
   wrdTmp0 = 0

   Start Timer0                                   'Start the 1 msec clock
   For bytTmp0 = 1 to 100
      wrdTmp1 = ADCOneShot(0)                     'Test ADC2 (ADCOneShot)
   Next bytTmp0
   Stop Timer0                                    'Stop the clock, wrdTmp0=time elapsed
   Locate 2 , 1
   LCD "100xADC2 " ; wrdTmp0 ; "msec"
   Timer0 = Presettimer0                          'initialize Timer0
   wrdTmp0 = 0

Loop                                              'End of main program *******

Bevor der ADC angesprochen wird, sind der Timer0 und der 1 msec-Zähler auf "Anfang" zu setzen.
Dann wird der Timer0 gestartet, bis die ADC-Leseschleife durchgelaufen ist, und sodann wieder gestoppt, um den Zähler wrdTmp0 auszuwerten. Mit einer solchen Mimik können natürlich auch andere Zeitmessungen vorgenommen werden. Nimmt man es genau, sind damit jedoch nur relative Zeitvergleiche möglich. Die Zeit, die bei jedem Timer0-Interrupt zur Sicherung von Speicherinhalten gebraucht wird, geht immer mit ein.

Die Eigenbauroutine zum einmaligen Auslesen des ADC analog GetADC arbeitet direkt mit den ADC-Registern wie oben beschrieben.

Function ADCOneShot(byVal bytChannel As Byte) As Word
'Get internal ADC conversion result per one shot
'Input:  bytChannel  ADC channel 0, 1...7
'        bitADCdone  =1: ADC conversion complete, flag from ADC_isr
'Output: ADCOneShot  ADC conversion result for bytChannel

   ADMUX = bytADMUX Or bytChannel                 'Set MUX channel
   ADCSRA.ADSC = 1                                'Start ADC, one shot
   'Do                                             'It works this way...
   '   Nop
   'Loop Until bitADCdone = 1
   BitWait bitADCdone , Set                       '...or this way
   ADCOneShot = wrdADC
   ADCSRA.ADSC = 0                                'Stop ADC
   bitADCdone = 0
End Function

Die Vorbesetzung der ADC-Register ADMUX, ADCSRA und SFIOR ist identisch mit der in Abschnitt 5. Mit "ADMUX = bytADMUX Or bytChannel" wird der gewünschte ADC-Kanal 0...7 eingestellt.
Dies sind die ADC-Register des gerade auf dem Testboard eingesteckten ATMega32, auch gültig z.B. für ATmega8. Neuere AVR haben zusätzliche und z.T. andere Register.

Die Routine wartet auf das "Konversion fertig"-Bit der ADC_isr. Diese übernimmt die Registerinhalte ADCL und ADCH und meldet mit "bitADCdone = 1" ich habe fertig. Mehr passiert hier nicht.

ADC_isr:
'Interrupt Service Routine for internal ADC, conversion complete
'Read 10bit ADC Data Registers ADCL & ADCH
'bytADCLo and bytADCHi are overlayed with wrdADC

   bytADCLo = ADCL                                'Read ADCL first...
   bytADCHi = ADCH                                '... then ADCH
   bitADCdone = 1                                 'Accumulation complete
Return

Ergebnis für je 100 ADC-Konversionen:

Routine Zeit (msec)
GetADC 24
ADCOneShot 11

Also: Das direkte Arbeiten mit den ADC-Registern mit der ADC_isr und ADCOneShot ist mehr als doppelt so schnell wie GetADC.
Das komplette Programm gibt es im Download (TestGetADC_M32_001.zip).
 

Einordung: