ADC mal ohne BASCOM-"GetADC"
Erstellt: DL6GL, 14.12.2012, letzte Änderung 29.11.2015
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:
Übrigens, werden die ADC-Eingänge ADC0, ADC1... anders als mit dieser Simpelschaltung gefüttert, sollte die Ausgangsimpedanz der speisenden Stufen kleiner als 10kΩ sein. Damit wird der Sample&Hold-Kondensator am ADC-Eingang schnell genug aufgeladen mit optimaler Signalabtastung (sampling time).
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:
- "Extern an AREF anliegend" heißt, es wird eine Referenzspannung an den Pin AREF angelegt, z.B. ein LM336 mit 2,49V nominal.
- "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.
- Interne Referenzen, je nach Controller z.B. wie hier 1,1 bzw. 2,56V beim ATmega16/32.
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.
Das ADC-Konversionsergebnis reicht von 0 bis 1023. Ein Wert 1023 ist also fallweise ein Indiz für einen ADC-Überlauf, also Eingangsspannung größer/gleich Referenzspannung.
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 Initialisierungswandlung. 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 vorbesetzen.
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 | 111 | 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: Ersatz für BASCOM GetADC
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).
7 Spannungsmessung
Sinn der ganzen Übung wird ja sein, Spannungen an den ADC-Eingängen zu messen. Die zu messende Spannung muss auf alle Fälle kleiner als die Versorgungsspannung, i.d.R. 5V, sein. Größere Spannungen muss ein Spannungsteiler reduzieren. Bei deutlich kleineren Spannungen wird es sinnvoll sein, entweder eine vom Controller bereitgestellte interne Referenzspannung, s.o. Register REFS0, REFS1, von z.B. 1,10 oder 2,56V, zu wählen oder eine passende externe Spannungsreferenz am Pin AREF anzuschließen. Damit werden die maximal messbare Spannung und die erzielbare Auflösung bestimmt.
Auflösung = U-Ref / 2^Bits , bei 10Bit = U-Ref / 1024
Die zu messende Spannung ist dann mit 10Bit-Auflösung
U-in = ADC-Wert * U-Ref / 1024
Das kann in BASCOM so aussehen:
Dim wrdADC As Word
Dim sngURef As Single
Dim sngVolt As Single
sngURef = 5 'Reference Voltage VCC
'wrdADC = GETADC(1) 'ADC value
wrdADC = ADCOneShot(1) 'ADC value
sngVolt = wrdADC * sngURef
sngVolt = sngVolt / 1024 'Measured voltage
Einen Rechenschritt kann man sich sparen, wenn sngURef / 1014 ausgerechnet als "Const" definiert wird. Dabei kann man gleich auch noch die tatsächliche Referenzspannung am Pin AREF ausmessen. Weder VCC=5V noch die internen AVR-Referenzspannungen sind ganz genau. Eine Mittelwertbildung aus mehreren Einzelmessungen könnte auch noch zugefügt werden.