Forum: Mikrocontroller und Digitale Elektronik Initialisierung des ADC


von Martin T. (Gast)


Lesenswert?

Hallo,

ich habe momentan ein Problem bei der Initialisierung des ADC beim 
AT90PWM3B. Es wäre schön, wenn einer bei meinem lauten denken versucht 
mir zu folgen.

Ich initialisiere wie folgt:
1
...
2
ADMUX = 2;
3
ADCSRA = (1<<ADSC)|(1<<ADPS2)|(1<<ADPS1);
4
ADCSRA = (1<<ADEN)                         // start
5
6
while (bit_is_set(ADCSRA,6));
7
ADC_Val = result;
8
...

Mein Problem ist, dass im Handbuch zum Bit 6 folgendes steht:

"Set this bit to start a conversion in single conversion mode or to 
start the first conversion in free
running mode.Cleared by hardware when the conversion is complete."

Nun möchte ich aber gerne den free running mode nutzen um nicht jedes 
mal von vorne Initialisieren zu müssen. Wie macht man das? Ich vermute, 
dass es mit dem ADC Auto trigger Enable Bit zu tun hat. Wenn man das Bit 
setzt und "ADTS3 ADTS2 ADTS1 ADTS0" löscht müsste er doch im free 
running mode laufen.

Dann müsste man nur einmal initialisieren und könnte dann den Wert wann 
man möchte abholen:
1
ADMUX = 2;
2
ADCSRA = (1<<ADSC)|(1<<ADATE)|(1<<ADPS2)|(1<<ADPS1);
3
ADCSRA = (1<<ADEN)                         // start
4
5
int main (void) {
6
while(1) {
7
  while (bit_is_set(ADCSRA,6));
8
  ADC_Val = result;
9
};
10
}

Liege ich damit richtig?

von Hubert G. (hubertg)


Lesenswert?

Hab mir die Register nicht angeschaut, aber löscht du mit ADCSRA = 
(1<<ADEN) nicht die anderen Bit im Register.
Sollte doch eher ADCSRA |= (1<<ADEN); heissen.
Strichpunkt danach sollte auch sein.

von Martin T. (Gast)


Lesenswert?

Oh das kann sein. Ich bin (wie viele hier) noch recht neu auf dem Gebiet 
der uc's. Habe den Code jetzt so editiert, aber es funktioniert 
immernoch nicht mit dem kontinuierlichen ADC.
1
ADMUX = 2;
2
ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADPS2)|(1<<ADPS1); // init und Start
3
4
int main (void) {
5
6
while(1) {
7
8
  while (bit_is_set(ADCSRA,6));
9
10
  ADC_Val = result;
11
12
}; 
13
}

Woran liegt das? Sobald ich das ADATE Bit setze geht nix mehr.

von Hubert G. (hubertg)


Lesenswert?

Die Abfrage passt auch nicht.
result = ADC;

von Martin T. (Gast)


Lesenswert?

So ein dummer Fehler. Ich komme hier irgendwie ständig mit der 
Portierung ins Straucheln -.- .

Danke, dass du mitdenkst, aber leider geht es nach wie vor nicht und da 
du ja auch keinen weiteren Tip geben konntest, verstehe ich erst recht 
nicht, warum der ADC nicht im kontinuierlichen Modus arbeiten will. Aber 
vielleicht hat ja noch wer ne Idee...

Hier der "richtigste" Code :-) :
1
ADMUX = 2;
2
ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADPS2)|(1<<ADPS1); // init und Start
3
4
int main (void) {
5
  
6
while(1) {
7
 
8
while (bit_is_set(ADCSRA,6));
9
 
10
result = ADC;
11
 
12
}; 
13
}

von Hubert G. (hubertg)


Lesenswert?

Wie stellst du eigentlich fest das nichts geht, bekommst du keine oder 
falsche Werte?

von Martin T. (Gast)


Lesenswert?

folgendermaßen:

Bei folgendem Code halte ich eine Lampe an den Helligkeitssensor und die 
LED geht aus. Lampe weg -> LED geht an. Lampe hin -> LED geht aus.
1
int main (void) {
2
  
3
while(1) {
4
5
ADMUX = 2;
6
ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1); // init und Start
7
 
8
while (bit_is_set(ADCSRA,6));
9
 
10
result = ADC;
11
 
12
}; 
13
}

wenn ich jetzt die Initialisierung rausziehe funktioniert gar nix mehr. 
Die LED geht nicht mehr aus. Und ich habe definitiv nur die 
Initialisierung geändert.
1
int main (void) {
2
3
ADMUX = 2;
4
ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADPS2)|(1<<ADPS1); // init und Start
5
  
6
while(1) {
7
 
8
while (bit_is_set(ADCSRA,6));
9
 
10
result = ADC;
11
 
12
}; 
13
}

von Hannes L. (hannes)


Lesenswert?

> Aber vielleicht hat ja noch wer ne Idee...

Wenn ich den ADC im Free-Run-Mode (adate in adcsra gesetzt, adcsrb=0) 
laufen lasse, dann lesee ich ihn zyklisch in einem Timer-Interrupt aus, 
der nebenher noch andere Dinge des Programms synchronisiert (z.B. 
Tasten-Entprellung, Byte-Transfer zum LCD, Zeitbasis für Blinken, usw.). 
Dabei achte ich darauf, dass das Timer-Intervall größer ist als das 
Abtastintervall des ADCs.

Wenn ich schnell samplen muss, dann nutze ich den Interrupt des ADC. Da 
dieser auch eine Art "Timer" ist, bekommt er ggf. auch noch andere Jobs 
mit aufgebrummt, wie z.B. das Multiplexing von 
7-Segment-Lichtschachtanzeigen.

Wenn ich mehrere Kanäle brauche, dann lege ich mir je Array für Messwert 
und MUX-Wert der nächsten Messung an und lasse in der ISR den Index 
rotieren. Also Messwert auslesen und in Array legen, Index erhöhen und 
begrenzen, MUX-Wert aus Array holen und in ADMUX schreiben, Messung 
starten, fertig. Die Mainloop (oder einer ihrer Jobs) holt sich dann den 
entsprechenden Messwert (atomar) aus dem Array.

Diese Varianten haben den Vorteil, dass in der Mainloop kein Busywait 
stattfindet, der Controller in dieser Zeit also etwas Sinvolles tun 
kann. Denn meist muss das Programm ja noch ein paar Dinge mehr tun als 
nur den ADC auslesen.

Mit C-Code kann ich nicht dienen, ich werkele in ASM.

...

von Karl H. (kbuchegg)


Lesenswert?

Gleich vorweg, ich hab mich mit dem Free running Mode noch nicht 
beschäftigt.

Aber welchen Sinn soll es haben, wenn du das Bit 6 abfrägst um dann doch 
wieder auf den ADC zu warten. Wird dieses Bit überhaupt im Free Running 
Modus bedient? Und wenn der ADC es tatsächlich setzt, während eine 
Wandlung läuft, was denkst du, wie gross sind die Chancen, dass dein 
Code das Bit genau in dem Moment erwischt, wenn eine Wandlung fertig ist 
und die nächste noch nicht begonnen hat. Auch ein
while (bit_is_set(ADCSRA,6));
kann keine Bitabfrage in 0-Zeit machen.

Nimm die Warterei mal raus oder starte alternativ den ADC jedesmal neu 
wenn du unbedingt warten willst
1
int main (void) {
2
3
  ADMUX = 2;
4
  ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1); // init und Start
5
 
6
  while(1) {
7
 
8
    while (bit_is_set(ADCSRA,6));
9
10
    result = ADC;
11
12
    ADCSRA = 1<<ADSC;    // nächste Wandlung starten
13
  }
14
}

von Justus S. (jussa)


Lesenswert?

Martin T. schrieb:
> Bei folgendem Code halte ich eine Lampe an den Helligkeitssensor und die
> LED geht aus. Lampe weg -> LED geht an. Lampe hin -> LED geht aus.

Bin ich der einzige, der in dem angeblich funktionierenden Code keine 
LED sieht, die irgendwie geschaltet werden könnte?

von Karl H. (kbuchegg)


Lesenswert?

Justus Skorps schrieb:
> Martin T. schrieb:
>> Bei folgendem Code halte ich eine Lampe an den Helligkeitssensor und die
>> LED geht aus. Lampe weg -> LED geht an. Lampe hin -> LED geht aus.
>
> Bin ich der einzige, der in dem angeblich funktionierenden Code keine
> LED sieht, die irgendwie geschaltet werden könnte?

Das nehme ich mal als:
Ist während des Copy&Paste im Bithimmel verloren gegangen.

von Hannes L. (hannes)


Lesenswert?

;-)

...

von Martin T. (Gast)


Lesenswert?

Hannes Lux schrieb:
> Wenn ich den ADC im Free-Run-Mode (adate in adcsra gesetzt, adcsrb=0)
>
> laufen lasse, dann lesee ich ihn zyklisch in einem Timer-Interrupt aus,
>
> der nebenher noch andere Dinge des Programms synchronisiert (z.B.
>
> Tasten-Entprellung, Byte-Transfer zum LCD, Zeitbasis für Blinken, usw.).
>
> Dabei achte ich darauf, dass das Timer-Intervall größer ist als das
>
> Abtastintervall des ADCs.

Meine Anwendung ist nicht soo zeitkritisch und ich hatte mir gedacht, 
dass ich ihn "frei laufen" lasse und wenn er halt 50 mal pro Sekunde nen 
Wert bekommt ist das genauso gut wie 500 mal pro Sekunde.

Das mit den Interrupts ist ne spannende Sache, da ich momentan noch kein 
Gefühl habe was zu lange und was OK für ein Interrupt wäre. Mein uc 
läuft mit 8MHz und die ISR dauert 8MHz/1024 also 128us. Nun habe ich bis 
jetzt nur ein paar kleinere if's drin für ne Zeitbasis. Wieviel kann ich 
da reinpacken? Wie geht man da vor? Es ist klar, dass ich bis zum 
nächsten Interrupt alles fertig abgearbeitet haben muß, aber was ist mit 
den Prozessen in der main? Ich kann nur sehr rudimentär Assembler und 
weiß daher nicht wie lange ne for (x=0;x<50;x++) oder ne if (x == 0) x = 
1 dauern. Im Tutorial findet man hierzu auch nichts. Und debuggen im AVR 
Studio ist die reinste Qual. ;-)

Karl heinz Buchegger schrieb:
> Aber welchen Sinn soll es haben, wenn du das Bit 6 abfrägst um dann doch
>
> wieder auf den ADC zu warten.


Das Problem ist glaube ich, dass ich die Arbeitsweise des ADC im 
Allgemeinen nicht richtig verstanden habe. Im Datenblatt steht

"When a positive edge occurs on the selected trigger signal,
the ADC prescaler is reset and a conversion is started. This provides a 
method of starting conversions
at fixed intervals. If the trigger signal is still set when the 
conversion completes, a new
conversion will not be started."

Somit werden nur positive und keine negativen Flanken erkannt, da mein 
Wert aber auch mal absacken kann sind negative Flanken essentiell. 
Außerdem warum sollte ich in nem frei laufenden ADC exakte Intervalle 
haben wollen? Dafür habe ich doch dann den single mode?!

Ich werde das ganze nochmal von vorne überdenken und dann, falls mich 
keiner haut, nochmal ne Frage dazu stellen.

von Karl H. (kbuchegg)


Lesenswert?

Martin T. schrieb:

> Somit werden nur positive und keine negativen Flanken erkannt, da mein
> Wert aber auch mal absacken kann sind negative Flanken essentiell.

Das hat damit nichts zu tun.
Da geht es darum, was den ADC dazu bringt eine Konvertierung zu machen.

zb. ein regelmässiges Taktsignal, welches jede 1 Sekunde eine ADC 
Wandlung anstößt.
Dieses Signal hat aber nichts mit dem Eingang zu tun, der vom ADC 
ausgemessen wird.

von Hannes L. (hannes)


Lesenswert?

Martin T. schrieb:
> Meine Anwendung ist nicht soo zeitkritisch und ich hatte mir gedacht,
> dass ich ihn "frei laufen" lasse und wenn er halt 50 mal pro Sekunde nen
> Wert bekommt ist das genauso gut wie 500 mal pro Sekunde.

Ja, mache ich auch so, besonders wenn nur ein Kanal gebraucht wird. 
Allerdings brauchen fast alle meiner Programme eine Zeitbasis, die 
mittels Timer sehr "billig" zu haben ist. Daher lese ich den ADC in 
einem vom Timer synchronisierten Job (es muss ja nichtmal in der ISR 
sein) aus.

>
> Das mit den Interrupts ist ne spannende Sache, da ich momentan noch kein
> Gefühl habe was zu lange und was OK für ein Interrupt wäre.

Da ich in ASM werkele, kann ich "Takte zählen" (vorhersagen, wie lange 
ein Stück Code braucht). Der Programmierstil richtet sich dabei etwas 
nach den zu erledigenden Aufgaben.

Es kann (bei einfachen Programmen) durchaus sein, dass ich das gesamte 
Programm in die ISR lege, dann weiß ich aber, dass das 
Timer-Interrupt-Intervall groß genug ist und dass keine anderen 
Interrupts gebraucht werden.

Meist erledige ich in den ISRs aber nur zeitkritische Dinge, wie das 
Retten (sichern) flüchtiger Werte (also Werte, die bereits überschrieben 
sein könnten, ehe die Mainloop (oder einer ihrer Jobs) Zeit dafür hat) 
und setze der Mainloop einen Merker, also einen Jobauftrag zum 
Weiterverarbeiten des gesicherten Wertes.

Sind mehrere verschiedene Interrupts aktiv, dann kommt es darauf an, die 
ISRs möglichst kurz (schnell) zu machen, damit sie sich nicht 
gegenseitig blockieren bzw. verzögern. Verzweigungen (Fallabfragen) 
sollten dann schon kritisch gesehen werden. Und Warteschleifen haben in 
ISRs ja sowiso nichts zu suchen, es sei denn, es geht um wenige µs.

> Mein uc
> läuft mit 8MHz

Mir reichen oftmals die 1MHz der Grundeinstellung.

> und die ISR dauert

Du meinst vermutlich, die ISR wird im Abstand von ... aufgerufen? Denn 
wie lange sie dauert, hängt davon ab, wie schnell der darin liegende 
Code abgearbeitet wird.

> 8MHz/1024 also 128us.

Warum erst in Zeit umrechnen? Es ist doch einfacher, die Rechenzeit in 
Takten zu betrachten. Und in 1024 Takten kann man schon allerhand tun.

> Nun habe ich bis
> jetzt nur ein paar kleinere if's drin für ne Zeitbasis. Wieviel kann ich
> da reinpacken? Wie geht man da vor? Es ist klar, dass ich bis zum
> nächsten Interrupt alles fertig abgearbeitet haben muß, aber was ist mit
> den Prozessen in der main?

Deshalb teilt man ja die zu erledigenden Aufgaben auf. Die ISR macht 
das, was bei jedem Durchlauf/Aufruf nötig ist. Für Aufgaben, die 
seltener nötig sind, setzt die ISR nur einen Merker. Die Mainloop 
erledigt dann den zugehörigen Job und löscht den Merker (die Arbeit ist 
ja getan). Dabei kann sie ohne Weiteres von weiteren Interrupts 
unterbrochen werden, sie muss nur fertig sein, ehe dieser Job erneut 
fällig wird.

Ich organisiere das meist so, dass die Mainloop alle Job-Merker abfragt 
und ggf. zum Job verzweigt und dann, wenn alle Jobs erledigt sind, den 
in den Controller in den Sleep schickt, aus dem er vom nächsten 
Interrupt wieder geweckt wird. Bei umfangreicheren Programmen nutze ich 
zum Ermitteln der gebrauchten Rechenzeit einen I/O-Pin (Ausgang), der 
von jeder ISR auf H gesetzt wird und von der Mainloop beim Erreichen von 
Sleep auf L. Ein Oszi zeigt mir dann sehr genau die "Lastkurve" des 
Prozessors. ;-)

> Ich kann nur sehr rudimentär Assembler und
> weiß daher nicht wie lange ne for (x=0;x<50;x++)

Das sind 50 bedingte Rücksprünge je 2 Takte + 50 mal Abarbeitung des 
Schleifeninhalts (des Codes in der Schleife). Dazu noch 50 mal die 
RAM-Zugriffe, da Hochsprachen ja ihre Variablen im SRAM halten.

> oder ne if (x == 0) x = 1 dauern.

Um das einschätzen zu können, ist es hilfreich, die Architektur des 
Controllers zu kennen (das erfordert nun wieder etwas ASM-Wissen, denn 
der Controller kann nunmal nur Maschinencode und der ist nur in ASM 1 zu 
1 notierbar) und auch etwas Hintergrundwissen zur Arbeitsweise des 
Compilers. C-Programmierer, die guten hardwarenahen Code schreiben 
können, kennen sich auch mit ASM aus. Ansonsten wären sie nicht in der 
Lage, den C-Code so zu formulieren, dass genau der gewünschte ASM-Code 
erzeugt wird. Mir persönlich ist das alles zu kryptisch, deshalb 
schreibe ich in ASM.

> Im Tutorial findet man hierzu auch nichts. Und debuggen im AVR
> Studio ist die reinste Qual. ;-)

In C kann ich es nicht beurteilen, in ASM ist der Debugger (Simulator) 
aber recht brauchbar. Das Hardware-Debugging (mittels AVR-Dragon) ist 
zwar nicht ganz mein Geschmack, das liegt aber nur daran, dass meine 
Programme oft Impulsfolgen verarbeiten müssen und das HW-Debugging nicht 
richtig echtzeitfähig ist. Da bekomme ich mit LED oder UART oft 
sinnvollere Debug-Ausgaben.

>
> Karl heinz Buchegger schrieb:
>> Aber welchen Sinn soll es haben, wenn du das Bit 6 abfrägst um dann doch
>>
>> wieder auf den ADC zu warten.
>
>
> Das Problem ist glaube ich, dass ich die Arbeitsweise des ADC im
> Allgemeinen nicht richtig verstanden habe. Im Datenblatt steht
>
> "When a positive edge occurs on the selected trigger signal,
> the ADC prescaler is reset and a conversion is started. This provides a
> method of starting conversions
> at fixed intervals. If the trigger signal is still set when the
> conversion completes, a new
> conversion will not be started."
>
> Somit werden nur positive und keine negativen Flanken erkannt, da mein
> Wert aber auch mal absacken kann sind negative Flanken essentiell.
> Außerdem warum sollte ich in nem frei laufenden ADC exakte Intervalle
> haben wollen? Dafür habe ich doch dann den single mode?!
>
> Ich werde das ganze nochmal von vorne überdenken und dann, falls mich
> keiner haut, nochmal ne Frage dazu stellen.

Bei konkreten Fragen wird Dich kaum jemand "verhauen".

...

von Martin T. (Gast)


Lesenswert?

Ich danke dir vielmals für deine Umfangreiche Antwort. Ich sehe es gibt 
viel zu tun :-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.