PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 8 ADC-Kanäle schnell auslesen



almic
17.11.2008, 10:12
Hallo,

ich möchte mit einem at90can 8 ADC Kanäle so schnell auslesen:

Der ADC-Takt ist 125 kHz. (16.000.000 Hz / 128 Prescaler)

In dem AVRGCC-Tutorial wird geraten eine Dummy-Messung durchzuführen. Gilt das nur nachdem ich den ADEN gesetzt hab oder auch beim wechseln der Kanäle?


Der Code ist teilweise aus dem AVRGCC-Tutorial


char i=0;

ADMUX = 0x40; // ADC0
ADCSRA |= (1<<ADSC);
/* Test AD-Wandlung durchführen */

while (ADCSRA & (1<<ADSC))
{
; // Auf Abschluss der Konvertierung warten
}
adc_werte[0] += ADCL;
adc_werte[0] += (ADCH<<8);

adc_werte[0] =0;
/* Test-Wert löschen */

for(i=0; i<5; i++) // 5 Wandlungen durchführen
{
ADCSRA |= (1<<ADSC); // eine Wandlung "single conversion "
while (ADCSRA & (1<<ADSC))
{
; // Auf Abschluss der Konvertierung warten
}

adc_werte[0] += ADCL;
adc_werte[0] += (ADCH<<8);
}
adc_werte[0] /= 5; // Summe durch fünf teilen = arithm. Mittelwert
i=0;

ADMUX = 0x41; // ADC1
ADCSRA |= (1<<ADSC);
/* Test AD-Wandlung durchführen */

while (ADCSRA & (1<<ADSC))
{
; // Auf Abschluss der Konvertierung warten
}
adc_werte[1] += ADCL;
adc_werte[1] += (ADCH<<8);

adc_werte[1] =0;
/* Test-Wert löschen */

for(i=0; i<5; i++) // 5 Wandlungen durchführen
{
ADCSRA |= (1<<ADSC); // eine Wandlung "single conversion "
while (ADCSRA & (1<<ADSC))
{
; // Auf Abschluss der Konvertierung warten
}

adc_werte[1] += ADCL;
adc_werte[1] += (ADCH<<8);
}
adc_werte[1] /= 5; // Summe durch fünf teilen = arithm. Mittelwert
i=0;
// usw.

oberallgeier
17.11.2008, 12:17
Lies Dir doch mal den Absatz: Changing Channel or Reference Selection im Doc zu Deinem Controller durch. Dort wird einiges geschrieben zu Vorgehens- und Vorsichtsmaßnahmen beim Multiplexen der Eingänge.

almic
17.11.2008, 13:38
Hallo,

danke für den Hinweis.

Laut Datenblatt sind Dummy Messungen um den ADC "warmlaufen" zu lassen überhaupt nicht nötig - zumindest steht da nichts davon!

Die erste Wandlung dauert zwar doppelt so lang wie die alle folgenden ( so lange der ADC nicht abgeschatet wird) aber ansonsten passiert da laut Datenblatt nichts mehr ungewöhnliches.

oberallgeier
17.11.2008, 14:56
Hi, almic,


... Die erste Wandlung dauert zwar doppelt so lang ...Betrifft das nicht die erste Wandlung nach dem Umschalten des ADC-Eingangs von einem Pin auf den anderen? Das wäre dann ja beim Multiplexen doch wieder häufiger. Aber ich bin mir da nicht gaaanz sicher.

In solchen Fällen probier ich das auch schon mal auf (m)einer Flash- und Experimentierplatine aus.

Besserwessi
17.11.2008, 20:02
Die Längere Wandlungszeit gilt nicht nach dem Kanalwechsel. Nach dem Kanalwechsel macht eine dummymessung sinn, wenn die Quelle einen hohen Widerstand hat (mehr als etwa 20 KOhm). Der AD Wandler hat sonst noch etwas Gedächnis an den vorherigen Kanal.
Man sollte etwas Wartezeit einplanen nach dem einschalten der internen Referenz oder dem umschalten der Referenz, besonders in Richtung kleinerer Spannung. Da reicht dann aber oft auch eine Dummymessung nicht aus.

Zum Programm:
Für eine Schnelle Messung sollte man den AD duchgängig laufen lassen, also nicht jedesmal als single conversion starten. Das geht auch wenn man die Kanäle umschaltet. Wenn es sein muß könnte man wohl auch auf 250 kHz AD Takt gehen, so groß viel größer werden dadurch die Fehler noch nicht. Zur Mittelwertbildung sind 4 oder 8 Werte oft günstiger, denn da geht das Teilen schneller. Dabei macht es aber duchaus Sinn die Summe von 4 Werten nur durch 2 zu Teilen und das Ergebnis dann als Wert mit 11 Bit Auflösung zu sehen. Entsprechend Macht es Sinn von 16 Werten 12 Bit Auflösung übrig zu lassen.
Auf den ADC wert kann man auch gleich als 16 bit wert zugreifen:
adc_werte[0] = ADC;
reicht zum auslesen aus, den Rest erledigt der Compiler.
Das aufsummieren sollte man wohl auch besser in einer localen Variable machen, und nicht unbedingt im array.

Gock
17.11.2008, 21:04
Du musst den Channel >1 ADC Takt vor der Conversion wählen!!!
Bei Dir sind das immerhin 128 Prozessortakte, das geht also nicht gut.
Nebenbei brauchst Du in C nicht High und Low Byte getrennt zu kopieren, "temp = ADC;" geht auch...
Gruß

oberallgeier
17.11.2008, 23:14
... Zum Programm: ...Ach herrje, schauderhaft was ich alles in Nu (merische) Ma (thematik) vergessen habe. Schlimm schlimm.

almic
18.11.2008, 11:00
Hallo,

ich bleibe mal bei der idee mit der Einzelmessung, im zweifelsfall kann ich später noch im freilaufenden Modus wechseln.

Hab jetzt noch eine Warteschleife eingefügt und die Wertzuweisung geändert.



char i=0;
unsigned char volatile j=0;
unsigned short adc_temp=0;

ADMUX = 0x40; // ADC0

for(j=0; j<15; j++) { } // 10us warten (etwas mehr als ein ADC-Takt)

ADCSRA |= (1<<ADSC);
/* Test AD-Wandlung durchführen */

while (ADCSRA & (1<<ADSC))
{
; // Auf Abschluss der Konvertierung warten
}
adc_temp += ADC;
adc_temp =0;
/* Test-Wert löschen */

for(i=0; i<4; i++) // 4 Wandlungen durchführen
{
ADCSRA |= (1<<ADSC); // eine Wandlung "single conversion "
while (ADCSRA & (1<<ADSC))
{
; // Auf Abschluss der Konvertierung warten
}

adc_temp += ADC;
}
adc_temp /= 2; // Summe durch fünf teilen = arithm. Mittelwert
adc_werte[0] = adc_temp;
i=0;


Die Frage ist jetzt:

Ist das Programm schneller wenn ich das in eine Funktion packe und die Funktion 8 mal starte oder ist es schneller wenn es einfach (copy&paste sein dank) 8mal hintereinander steht?

oberallgeier
18.11.2008, 12:51
Hi, almic,


... Programm schneller ... die Funktion 8 mal starte ... Leider bin ich nicht der große C-Freak. Allerdings weiß ich, dass beim Aufruf einer Funktion eine ganze Menge "Verwaltungsarbeit" vom Compiler gemacht wird: Register retten und wieder zurückschreiben. Daher hatte ich mal in einem Programm die "copy&paste"-Variante gemacht und meine Zeitmessung ergab einen klaren Vorteil. Sieht halt unprofessionell aus, aber wenns was bringt . . . .

Du kannst Dir dazu selbst ein Bild dazu machen: schau Dir mal den Code nach dem Übersetzen an. Der steht in einer Datei *.lls im Unterordner "default" Deines Projektordners. Beim Lesen der *.lls macht sich übrigens eine sorgfältige Kommentierung sehr bezahlt.

Viel Erfolg.

Gock
18.11.2008, 13:29
Sorry! Hab mich vertan: Die Sache mit dem einen ADC Takt gilt nur, wenn man den Channel wechselt, nachdem SingleConversion ausgelöst wurde, bei Dir geschieht es ja vorher! Du brauchst also nicht zu warten. Ich hoffe, ich hab keine Verwirrung gestiftet...
Allerdings hilft es die Optimierung im Compiler einzuschalten. Und der würde Deine leere Warteschleife ohnehin rausschmeißen.
Aber wie schon gesagt, Funktiosaufruf kostet viel Zeit, also lieber eine Funktion schreiben.
Du kannst noch ein bisschen Zeit sparen, indem Du die nächste Konvertierung startest, bevor Du das ADC Register ließt, denn das letzte Ergebnis steht solange dort an, bis es vom nächsten überschrieben wurde.
Gruß

almic
18.11.2008, 13:49
wie peinlich...

Ich hab diesen Satz mindestens 10 mal gelesen und doch falsch verstanden:
The user is thus advised not to write new channel or reference selection values
to ADMUX until one ADC clock cycle after ADSC is written. (Datasheet at90can*

Wir sind uns einig, die Schleife ist unnötig.

Ich habs mit volatile geschrieben darum optimiert er das garnichtmehr weg :)

oberallgeier
18.11.2008, 15:01
... Aber wie schon gesagt, Funktiosaufruf kostet viel Zeit, also lieber eine Funktion schreiben ...Muss ich/man das jetzt verstehen?


... bisschen Zeit sparen, ... nächste Konvertierung startest, bevor Du das ADC Register ließt ...Hmmm, eine seltsame Reihenfolge. Das mag ja stimmen, dass das Ergebnis theoretisch drin bleibt, aber ich mache das aber immer genau anders: erst lesen, z.B. so: "inwert = ADC;" und danach sofort die nächste Wandlung starten. Dies ist dann auch unempfindlich gegen laufende ISR. Denn wenn man erst startet und danach ausliest - und genau dazwischen kommt eine ISR . . . . . trallalallala

Gock
18.11.2008, 16:29
... Aber wie schon gesagt, Funktiosaufruf kostet viel Zeit, also lieber eine Funktion schreiben ...Muss ich/man das jetzt verstehen?
Die Betonung liegt auf EINER Funktion, die den Text 8-mal enthält und NICHT 8 mal aufgerufen wird, weil das ja viel Zeit kostet...



... bisschen Zeit sparen, ... nächste Konvertierung startest, bevor Du das ADC Register ließt ...Hmmm, eine seltsame Reihenfolge. Das mag ja stimmen, dass das Ergebnis theoretisch drin bleibt, aber ich mache das aber immer genau anders: erst lesen, z.B. so: "inwert = ADC;" und danach sofort die nächste Wandlung starten. Dies ist dann auch unempfindlich gegen laufende ISR. Denn wenn man erst startet und danach ausliest - und genau dazwischen kommt eine ISR . . . . . trallalallala
Normalerweise mache ich es auch anders, denn das ist sicherer und besser.
Um ganz sicher zu gehen müsste man ohnehin in jedem Fall vorher die IRQs abschalten, weil ADC ein 16BitRegister ist und soweit ich weiß puffert das weder der µC doppelt, noch erledigt das der Compiler für mich. Aber da es in diesem Fall scheinbar um nichts anderes geht, als besonoders schnell zu sein, sollten ohnehin keine anderen IRQs anktiviert sein, sonst ist die Geschwindigkeit schnell wieder dahin und dann kann man diesen Trick schon mal wagen.
Gruß

Besserwessi
18.11.2008, 17:41
Das 16 Bit breite ADC Register wird hardwaremäßig gepufert. Um das richtige auslesen kümmert sich der Compiler schon. Solange da keine ISR dabei ist, der auch noch den AD ausließt, passiert da also nichts.

Die Geschwindigkeit wird ohnehin im wesentlichen vom AD Wandler vorgegeben. Die eigentlichen Rechenzeiten sind dagegen ziehmlich nebensächlich, solange man nicht gerade ne Division oder Fließkomma hat. Da wäre der kontinuierliche AD mode wirklich die einfachste Lösung das zu beschleunigen. Wenn man von Hand startet wird man wohl jeweils mindestens 14 Zyken des AD taktes brauchen, automatisch halt nur 13.

Mit einem zwischenzeitlichen Interrupt sollte man normalerweise keine Probleme haben, selbst wenn man den AD erst startet und dann ausliest. Man hat da immer noch rund 1600 Zyklen (13x128) an Zeit bis der AD-wert überschrieben wird. So lange sollte eine ISR eigentlich nicht sein.

almic
18.11.2008, 17:51
Nächste woche kommt die Leiterplatte und dann kann ich probieren ob die bisherige Lösung schnell genug ist oder nicht.

Falls es nicht, muss ich auf die Tricks zurückgreifen.

Ceos
19.11.2008, 09:22
also von mir noch ein paar tipps um rechenzeit zu sparen, nachteil es geht auflösung flöten 10bit -> 8bit (mit ADLAR flag)

nurnoch das high byte auslesen, spart schonmal satt speicher aus 2 byte mach 1, das verrechnen der werte für die mittelwertbildung muss dann aber geändert werden(!!!)

ADC ruhig etwas übertakten, die auflösung haben wir ja schon auf 8bit reduziert sollte also keinen großen unterschied machen

vermeide arrayzugriffe ala array[index] versuch dir nen pointer anzulegen den du inkrementierst, meine persönliche erfahrung war, bei zeitkritischen abläufen geht der pointer erheblich schneller

vermeide interruptgesteuertes auslesen nach kräften, das kostet noch mehr rechenleistung .... im freerun solltest du die ersten 2 werte nach einem schannelwechsel wegwerfen, weil nciht sicher ist ob der erste wert noch zum alten channel gehört oder schon vom neuen ist und der erste wert nciht unbedingt der stabilste ist, mit single conversion solltest du auch eventuell das erste ergebnis wegwerfen

wenn du mit pointern und einer geschickten schleife arbeitest kannst du rechenzeit im vergleich zum prozeduralen ablauf sparen UND das programm sieht ordentlich aus

Besserwessi
19.11.2008, 17:52
Das Kanalumschalten macht eigentlich nur Probleme, wenn die Signalquelle zu hochohmig ist. Wenn man den AD Wadnler schneller taktet wird man quellen wahrscheinlich noch niederohmiger als die empfohlenen 10 KOhm machen müssen.
Auch im Freilaufenden Modus ist die Kanalumschaltung eigentlich kein Problem, den die Umschaltung wird intern gepuffert. Es gilt immer die Kanaleinstellung die anliegt wenn die Wandlung startet. Wenn man also den Kanal im freilaufenden Modus nach dem AD-ready signal wechselt, Wird auch die laufende Wandlung noch mit der alten Einstellung gemacht. Der Wert im den ADC Registen kann sogar zu noch einem andernen Kanal gehören. Werte auslassen braucht man nur, wenn man den Überblick verloren hat.

Ceos
20.11.2008, 09:08
du solltest im freerun das ADCF flag benutzen, da das ADSC permanent 1 bleibt, wenn ADCF 1 wird musst du dann auch wieder eine 1 reinschreiben um es zu löschen (hab am anfang immer versucht ne 0 reinzuschreiben XD)damit du den nächsten wert mitbekommst mitbekommst

@besserwessi im freerun magts du recht haben aber wenn mans gaaaanz genau nimmt, musst du den ersten wert dennoch wegwerfen, denn
wenn du umschaltest läuft zu 99% noch die wandlung auf dem letzten kanal und erst das 2te ADCF gilt für den neuen kanal! :p

Besserwessi
20.11.2008, 19:45
Wenn man es richtig macht, schaltet man den Kanal um während die letzte Messung für den vorherigen Kanal läuft. Man muß also nur das Kanalumschalten etwas vorher positionieren, dann kann man jede Wandlung nutzen. Wenn man im schnellen Wechsel 3 Kanäle mißt, kann das schon recht verwirrend werden: Im ADC register ist das Ergebnis von Kanal A, und die Wandlung für Kanal B läuft gerade, wenn man auf Kanal C umschaltet.

Bei der ersten Messung halt den Kanal wählen bevor man den Free running mode wählt.