Forum: Compiler & IDEs ADC Channelauswahl


von Mario Lutre (Gast)


Lesenswert?

Hallo,
ich steh derzeit vor dem Problem, dass ich 2 ADC Channels (0 und 1) 
auslesen muss.

Mein sieht derzeit so aus (und funktioniert natürlich nicht :-) ):
1
    ADMUX = (1<<MUX0);              //ADC0 auswählen
2
    ADCSRA |= (1<<ADSC);              //Start Conversion
3
    while (!(ADCSRA & (1<<ADIF)))          //Warten bis Wandlung fertig
4
    {
5
    asm volatile ("nop");
6
    }
7
       
8
     x = ADCL;
9
     y = ADCH;
10
     adc0_result = y+x;
11
     
12
    ADMUX = (1<<MUX0);              //ADC1 auswählen
13
    ADCSRA |= (1<<ADSC);              //Start Conversion
14
    while (!(ADCSRA & (1<<ADIF)))          //Warten bis Wandlung fertig
15
    {
16
    asm volatile ("nop");
17
    }
18
    
19
     x = ADCL;
20
     y = ADCH;
21
     adc1_result = y+x;

Die Rechnung (adcx_result) ist erstmal nur eine Art Platzhalter.

Wo liegt hier der Fehler? Ich warte doch jeweils, das die Wandlung 
abgeschlossen ist, dann lese ich die beiden Register (eigtl. ja auch in 
der richtigen Reihenfolge) aus und dann schalte ich den Muxkanal um.

Vielen Dank an euch!

von Karl H. (kbuchegg)


Lesenswert?

Mario Lutre schrieb:

>     ADMUX = (1<<MUX0);              //ADC0 auswählen

....

>     ADMUX = (1<<MUX0);              //ADC1 auswählen

> Wo liegt hier der Fehler?


Ähm.
Wenn du nur den Kommentar änderst, wird das den Compiler bzw. den µC 
nicht sehr beeindrucken.


Ausserdem: Im ADMUX Register sind auch noch andere Bits. Unter anderem 
auch die Auswahl der Referenzspannung. Es wäre daher sehr günstig, wenn 
du beim Setzen der Kanalselektion dies so machst, dass sich diese nicht 
verändern. Oder auf Deutsch: Einfach dem Register komplett nur die 
Kanalwahl zuweisen is nicht.

von Karl H. (kbuchegg)


Lesenswert?

> while (!(ADCSRA & (1<<ADIF)))

Das ADIF Bit abzufragen ist nicht so toll.
Denn: Du musst dieses Bit auch wieder löschen, wenn du keinen Interrupt 
verwendest. Von alleine setzt es sich sonst nicht zurück.

Besser ist es, das ADSC Bit abzufragen. Das verhält sich genau so, wie 
es beim Polling notwendig ist, ohne das man zuviel Aufwand hat: Du setzt 
es, dann beginnt der ADC zu arbeiten und wenn er fertig ist, setzt er es 
wieder zurück.

von Karl H. (kbuchegg)


Lesenswert?

>     x = ADCL;
>      y = ADCH;
>     adc0_result = y+x;

Ähm. Highbyte und Lowbyte werden aber anders zusammengesetzt.
Um eine Analogie im Dezimalsystem zu schaffen: Wenn du die 'Einzelteile' 
2 und 3 hast und daraus 23 entstehen soll, dann ist wohl 2 + 3 der 
falsche Weg. Richtig wäre 2 * 10 + 3

Und hier dann eben y * 256 + x

Oder eben ganz banal: lass den Compiler arbeiten!

    adc0_result = ADCW;

ganz ohne x und y und sonstigem Schnickschnack.


Aber: All das steht doch auch so im ADC-Teil, der im 
AVR-GCC-Tutorial verlinkt ist. Inklusive einer funktionierenden 
Routine, die einen beliebigen Kanal (und damit auch 2 verschiedene 
hintereinander) abfragen kann. Hast du da schon mal reingeschaut?

AVR-GCC-Tutorial/Analoge Ein- und Ausgabe

von Mario (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Ausserdem: Im ADMUX Register sind auch noch andere Bits. Unter anderem
> auch die Auswahl der Referenzspannung. Es wäre daher sehr günstig, wenn
> du beim Setzen der Kanalselektion dies so machst, dass sich diese nicht
> verändern. Oder auf Deutsch: Einfach dem Register komplett nur die
> Kanalwahl zuweisen is nicht.


Hallo Karl Heinz,
vielen Dank erstmal für deine Hilfe.
Das mit dem nur Kommentar ändern -> Oh gott....

Zum von dir zitierten:
Die anderen Bits, für Ref.-Spg., setze ich in meiner adc_init Funktion.
Ich dachte diese gesetzten Bits bleiben dann unberührt, wenn ich nur die 
MUX Bits ändere???

Den Rest probiere ich jetzt mal aus.

von Mario (Gast)


Lesenswert?

Sorry,
hatte nur das ASM Tutorial gesehen. (Es ist heut einfach zu dunkel 
draußen...) :)
Ich schau mal rein :-)

von Karl H. (kbuchegg)


Lesenswert?

Mario schrieb:

> Die anderen Bits, für Ref.-Spg., setze ich in meiner adc_init Funktion.
> Ich dachte diese gesetzten Bits bleiben dann unberührt, wenn ich nur die
> MUX Bits ändere???

Wenn du einem Register mittels

  Register = ....

etwas neues zuweist, wird das komplette Register verändert.

Du erwartest ja auch nicht, dass bei

   i = 7;
   i = 2;

ein paar Bits von der ersten Zuweisung die zweite Zuweisung überleben.

von Mario (Gast)


Lesenswert?

Hm,
ich hab das Tutorial jetzt gesehen, vom Prinzip her ist das Polling ja 
das gleiche, wie bei mir, nur dass ich keine Funktion aufrufen möchte.

Bei mir siehts jetzt so aus:

Init:
1
void adc_init(void)
2
{
3
      ADMUX = (1<<REFS0)|(1<<REFS1);        //interne Ref.-Spg.    
4
      ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1);  //Prescaler auf 64
5
}
1
    ADMUX |= (1<<MUX0);              //ADC0 auswählen
2
    ADCSRA |= (1<<ADSC);              //Start Conversion
3
    while (ADCSRA & (1<<ADSC))          //Warten bis Wandlung fertig
4
    {
5
    asm volatile ("nop");
6
    }
7
       
8
       
9
     adc0_result = ADCW;
10
     
11
    ADMUX |= (1<<MUX1);              //ADC1 auswählen
12
    ADCSRA |= (1<<ADSC);              //Start Conversion
13
    while (ADCSRA & (1<<ADSC))          //Warten bis Wandlung fertig
14
    {
15
    asm volatile ("nop");
16
    }
17
    
18
     adc1_result = ADCW;

Bekomme aber leider nur noch 0 als Ergebnis.

von Stefan E. (sternst)


Lesenswert?

1
    ADMUX |= (1<<MUX0);              //ADC0 auswählen
2
...     
3
    ADMUX |= (1<<MUX1);              //ADC1 auswählen
Das passt in zweifacher Hinsicht nicht.
1) Durch Setzen von MUX0 wird ADC1 ausgewählt, nicht ADC0 (MUX1 
entsprechend).
2) Nach der zweiten Zeile sind beide gesetzt, und damit ist dann ADC3 
ausgewählt.

von Mario (Gast)


Lesenswert?

Hm, stimmt.
Ersetzt durch:

ADMUX = (1<<REFS0)|(1<<REFS1);        //ADC0 auswählen
...
ADMUX = (1<<REFS0)|(1<<REFS1)|(1<<MUX0);    //ADC1 auswählen


Aber er will nicht...
Ach, wie ich Software liebe :-)

von Udo S. (urschmitt)


Lesenswert?

Du solltest noch etwas in Grundlagen der Bitoperationen investieren.
Übe mal mit Papier und Bleistift:
Wie setze / löscht man in einem Byte ein bestimmtes Bit ohne die anderen 
zu verändern?
Wann benutzt man ein binäres "und"?
Wann benutzt man ein binäres "oder"?

von Mario (Gast)


Lesenswert?

Da hast du recht! Ich bin noch nicht wirklich vertraut mit den 
Bitoperationen/Manipulationen...

Aber selbst wenn ich die "einfache" Möglichkeit nehme und sage:

ADMUX = 0b11000000;  //ADC Ch 0
ADMUX = 0b11000001;  //ADC Ch 1

Bekomme ich auf beiden nur eine 0

von Karl H. (kbuchegg)


Lesenswert?

Mario schrieb:
> Hm,
> ich hab das Tutorial jetzt gesehen, vom Prinzip her ist das Polling ja
> das gleiche, wie bei mir, nur dass ich keine Funktion aufrufen möchte.

Warum nicht?
Es würde dir viel Ärger ersparen.

> Bekomme aber leider nur noch 0 als Ergebnis.

Und es würde funktionieren.


Grundregel:
* mach dein Programm zuerst richtig
* dann sieh nach, wo du Zeitprobleme hast
* und dann, wenn feststeht, dass du in einem bestimmten Bereich
  Optimierungen brauchst, dann fang mit Optimierungen an.

Premature optimization is the root of all evil

von Mario (Gast)


Lesenswert?

Klar würde es funktionieren.
Aber mein eigener "Anspruch" möchte schon wissen, warum meine Variante 
derzeit nicht läuft.

von Karl H. (kbuchegg)


Lesenswert?

Mario schrieb:
> Da hast du recht! Ich bin noch nicht wirklich vertraut mit den
> Bitoperationen/Manipulationen...
>
> Aber selbst wenn ich die "einfache" Möglichkeit nehme und sage:
>
> ADMUX = 0b11000000;  //ADC Ch 0
> ADMUX = 0b11000001;  //ADC Ch 1
>
> Bekomme ich auf beiden nur eine 0

Hast du denn den 'einfachen Fall', nur einen einzigen ADC Kanal, schon 
erfolgreich probiert.

Es würde bei der Eingrenzung der Fehler helfen, wenn man wüsste, dass 
der ADC (Einstellung Referenzspannung, Auswertung des Ergebnisses) 
grundsätzlich schon mal funktioniert.

von Mario (Gast)


Lesenswert?

Ja, bevor ich 2 Unterschiedliche Kanäle auslesen wollte, habe ich es 
erfolgreich schon mit einem geschafft und mir die Daten (ADCL und ADCH) 
über die UART an den PC geschickt.

von Karl H. (kbuchegg)


Lesenswert?

Mario schrieb:
> Klar würde es funktionieren.

Was hat das damit zu tun, dass du 'keine Funktion aufrufen' willst.
Du kannst dir ja die Tutorial Funktion mal genau ansehen, an jeder 
Stelle überlegen warum und wieso das genau so gemacht wurde (speziell 
die Einstellung des Kanals) und dann deine eigene Funktion mit dem was 
du dabei gelernt hast schreiben, die dasselbe macht (natürlich dann ohne 
Ansehen der Vorlage, sonst wärs ja geschummelt)

Sowas nennt man 'Codestudium'. Und es ist eine hervorragende 
Möglichkeit, wie man von anderen lernen kann. Denn es zwingt dich, eine 
vorhandene Funktion komplett und in allen Einzelheiten zu verstehen. Und 
beim Selberschreiben kommst du dann auf die Stellen drauf, die du dir im 
Original nicht gut genug angesehen bzw. verstanden hast.

> Aber mein eigener "Anspruch" möchte schon wissen, warum meine Variante
> derzeit nicht läuft.

Zeig mal den ganzen Code.
Das sind jetzt schon ein paar Änderungen in der Zwischenzeit 
aufgelaufen, so dass nicht mehr wirklich klar ist, wie dein Code jetzt 
wirklich aussieht.

von Mario (Gast)


Lesenswert?

Danke erstmal für eure Geduld!!!

Init:
1
void adc_init(void)
2
{  
3
      ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1);  //Prescaler auf 64
4
}

Routine (wird jede Sekunde aufgerufen):
1
    ADMUX = (1<<REFS0)|(1<<REFS1);        //ADC0 auswählen, interne Ref.-Spg.
2
    ADCSRA |= (1<<ADSC);              //Start Conversion
3
    while(ADCSRA & (1<<ADSC));           //Warten bis Wandlung fertig
4
    {
5
    asm volatile ("nop");
6
    }
7
   
8
     adc0_result = ADCW;
9
     
10
    ADMUX = (1<<REFS0)|(1<<REFS1)|(1<<MUX0);    //ADC1 auswählen, interne Ref.-Spg.
11
    ADCSRA |= (1<<ADSC);              //Start Conversion
12
    while (ADCSRA & (1<<ADSC))          //Warten bis Wandlung fertig
13
    {
14
    asm volatile ("nop");
15
    }
16
    
17
     adc1_result = ADCW;

Das mit der Funktion wird sicherlich noch kommen. Aber mir lässt es 
einfach keine Ruhe, dass es so (wie mein Code oben) nicht funktioniert.

von Karl H. (kbuchegg)


Lesenswert?

Da sind zwar noch ein paar Kleinigkeiten, aber ich seh jetzt erst mal 
nichts, das die Funktion behindert.

Welcher µC ist das überhaupt (wegen der Referenzspannung).

Und: wie gehts dann weiter? Was machst du mit den Ergebnissen?
(Daher auch weiter oben: kompletter Code)

von Mario (Gast)


Lesenswert?

Na endlich...
Das AVR Studio hatte einfach nicht kompiliert.
Nach einem Neustart ging es wieder und der Code von oben funktioniert 
erstmal.

Ist ein Mega8, 2,56V int. Ref. und die Daten kommen dann über die UART 
zum PC. (Vorher wird damit noch ein wenig gerechnet)

Aber die Kleinigkeiten, die du noch siehst im Code würde mich schon 
interessieren :-)

von Karl H. (kbuchegg)


Lesenswert?

Mario schrieb:

> Aber die Kleinigkeiten, die du noch siehst im Code würde mich schon
> interessieren :-)


zb die

    asm volatile ("nop");

-> unnötig. Wozu sollen die gut sein?

Weiters:
Ist dir aufgefallen, dass du hier

    while(ADCSRA & (1<<ADSC));           //Warten bis Wandlung fertig
    {
    asm volatile ("nop");
    }

um Ende der while Zeile einen ; hast?

Deine Schleife wird gar nicht über den { } Teil gebildet. Syntaktisch 
hast du das hier

    while(ADCSRA & (1<<ADSC))           //Warten bis Wandlung fertig
      ;

    {
    asm volatile ("nop");
    }

gebaut. Und daher rate ich auch meistens dazu, die Formatiervorschriften 
ernst zu nehmen. Der ; gehört syntaktisch nicht zum while (oder auch if 
oder ...). Die Syntax für eine while-Schleife lautet
1
  while( Expression )
2
    Statement
Da kommt kein ; vor

Der ; bildet für sich eine eigene Anweisung, so wie auch eine Zuweisung 
eine eigene Anweisung wäre. Ein einzelner Strichpunkt ist ganz einfach 
die 'leere Anweisung' (ja, das gibt es wirklich, das denk ich mir jetzt 
nicht einfach aus). Will ich also ausdrücken: solange irgendwas gilt - 
mach nichts, dann schreibt sich das in korrekter Formatierung

   while( irgendwas )
     ;

Der einzelne Strichpunkt ist genau das: mach nichts - die leere 
Anweisung.

Natürlich kannst du das auch noch weiter hervorheben, indem du anstelle 
der leeren Anweisung einen leeren Block benutzt

   while( irgendwas )
   {
   }

geht genausogut und ist optisch etwas hervorstechender.
Aber beides, leere Anweisung und leerer Block, so mischen wie du das 
hast, das geht nicht.

Was gehen würde


   while( irgendwas )
   {
     ;
   }

Ist genau dasselbe. Nur das jetzt die leere Anweisung zusätzlich auch 
noch in einen { } Block eingeschlossen wurde.

von Karl H. (kbuchegg)


Lesenswert?

> ADMUX = (1<<REFS0)|(1<<REFS1)|(1<<MUX0);

Wenn du dir die Anordnung der MUX Bits mal etwas genauer ansiehst, dann 
stellst du fest, dass die MUX Bits genau so angeordnet sind, dass sie in 
Binärschreibweise exakt den Kanal-Zahlen entsprechen.

D.h.

  ADMUX = (1<<REFS0) | (1<<RFEFS1) |  0;
aktiviert den ADC-Kanal 0


  ADMUX = (1<<REFS0) | (1<<RFEFS1) |  1;
aktiviert den ADC-Kanal 1

  ADMUX = (1<<REFS0) | (1<<RFEFS1) |  2;
aktiviert den ADC-Kanal 2

  ADMUX = (1<<REFS0) | (1<<RFEFS1) |  3;
aktiviert den ADC-Kanal 3

etc. etc.

Das ist eine der Ausnahmen, bei der die Verwendung der Bitnamen keinen 
Vorteil bringt. Schreib ich aber die Kanalnummer direkt hin, dann kann 
ich an der Anweisung ablesen, welcher Kanal aktiviert wurde. Und Dinge, 
die ich direkt im Code schreiben kann ohne sie in einem Kommentar näher 
erläutern zu müssen, sind immer gut. Denn: Kommentare sind Schall und 
Rauch. Da kann vieles drinn stehen. Gültig ist immer der Code.

von Mario (Gast)


Lesenswert?

Dank dir!
Das mit den leeren Schleifen kenne ich auch.
Aber z.B. hier:

  while(1){

  while(x == y)
  {
     asm volatile ("nop");
  }
        [...]
        }

Wenn ich hier das nop weglasse, habe ich das Gefühl, dass das AVR Studio 
diese Schleife beim kompilieren wegoptimiert... weil wenn ich die 
einfach leerlasse oder nur das Punktstrich setze, funktioniert es nicht 
mehr

von Karl H. (kbuchegg)


Lesenswert?

Mario schrieb:

>   while(x == y)
>   {
>      asm volatile ("nop");
>   }
>         [...]
>         }
>
> Wenn ich hier das nop weglasse, habe ich das Gefühl, dass das AVR Studio
> diese Schleife beim kompilieren wegoptimiert...

Der Optimizer wird sich ohne dem nop so verhalten: Wenn er nachweisen 
kann, dass x und y schon zu Beginn der Schleife gleich sind (und zwar in 
allen Fällen), dann wird er die Schleife wegoptimieren. Kann er das 
nicht, dann DARF er sie nicht wegoptimieren, egal ob da jetzt ein nop 
drinnen ist oder nicht. Alles andere wäre ein schwerer Fehler des 
Compilers.

> weil wenn ich die
> einfach leerlasse oder nur das Punktstrich setze, funktioniert es nicht
> mehr

Das ist aber ein Hinweis darauf, dass du einen anderen Fehler im 
Programm hast und hier nur die Symptome siehst. Denn dadurch beseitigst 
du das eigentliche Problem nicht.
Bildlich gesprochen klebst du beim Auto ein Pickerl auf das Loch in der 
Motorhaube, anstelle die gebrochene Kurbelwelle reparieren zu lassen, 
die dafür gesorgt hat, dass dir die Kolben die Haube durchschlagen 
haben.

Symptom:       Loch in der Motorhaube
Ursache:       Kolben hat die Haube durchschlagen
Ursache dafür: Zylinderkopf hat abgehoben
Ursache dafür: Zylinderkopfschrauben gerissen
Ursache dafür: Kolben haben dagegen gehämmert
Ursache dafür: gebrochene Kurbelwelle


Du musst dich immer auf die Suche nach den Ursachen machen. Beseitigst 
du die, verschwinden auch die Symptome. Aber nur die Symptome 
kaschieren, ist der falsche Weg. Und ja, Ursachenforschung ist manchmal 
langwierig.

von Uwe (de0508)


Lesenswert?

Guten morgen,

vielleicht habe ich es im Code überlesen, da wir nur Auszüge sehen.
Im Datenblatt eines atMega88 steht die Erste Wandlung dauert länger und 
das ADC-Ergebnis ist zu verwerfen.
Häufig list man auch den Begriff "dummy readout".

Man sollte also das Datenblatt zu seinem µP immer 'offen' haben, darin 
lesen und die Anweisungen ernst nehmen.

Link:
[1] http://www.atmel.com/Images/doc2545.pdf

von Wolfgang (Gast)


Lesenswert?

Mario Lutre schrieb:
> ADMUX = (1<<MUX0);              //ADC1 auswählen
> ADCSRA |= (1<<ADSC);              //Start Conversion

Mal abgesehen von sämtlichen programmiertechnischen Fallstricken, 
Registerbelegungen und C-Grundlagen zu Bitoperationen, solltest man 
nicht vergessen, dass der Multiplexer eine Analogschaltung mit 
parasitären Elementen ist.
Zwischen der Umschaltung des Multiplexers und Start der Wandlung ist 
also etwas Zeit (10-fache Zeitkonstante bei 10 Bit) erforderlich, damit 
sich die Spannungspegel mit der erforderlichen Genauigkeit an den neuen 
gewählten Eingang anpassen können.
Viele Datenblätter empfehlen daher ein Blindwandlung nach der 
MUX-Umschaltung. Guck mal nach, was für deinen Prozessor empfohlen wird 
(evtl. auch in einer AP-Note) oder vergleiche - wenn die Software dann 
läuft - die Wandlungsergebnisse für eine Sequenz

  Ch1 - Ch1 - Ch1 - Ch2 - Ch2 - Ch2

mit unterchiedlichen Zeiten zwischen Mux-Umschaltung und Wandlung.

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.