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
asmvolatile("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
asmvolatile("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!
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.
> 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.
> 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
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.
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.
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
voidadc_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
asmvolatile("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
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.
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 :-)
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"?
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
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
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.
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.
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.
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)
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 :-)
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.
> 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.
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
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.
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
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.