Forum: Mikrocontroller und Digitale Elektronik ATmega32 ADC auslesen


von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Hallo Leute,

bitte verzeiht mir, wenn ich euch mit solch einer Fingerübung belästige.
Aber ich sehe leider den Wald vor lauter Bäumen nicht mehr.

Ich schrieb ein einfaches Programm in C auf einen ATmega32 der lediglich 
den Analogwert am Channel 0 auslesen und die LEDs mit dem Ergebnis am 
Port B ansteuern soll.

Das Programm läuft im Pollingmode und fragt halt das entsprechende 
Statusbit ab:
1
#include <avr/io.h>
2
3
uint16_t Result = 0;
4
uint8_t  Display = 0;
5
6
int main(void)
7
{
8
  DDRB = 0xFF;  // LEDs | pins set as inputs
9
  PORTB = 0xFF;  // all LEDs off
10
  DDRA &= ~0x01;  // Port A Pin 0 set as output
11
  ADCSRA = 0x8E;  // 1000|1110
12
  ADMUX = 0x00;  // 0000|0000
13
  ADCSRA |= 0x40;  // start first conversion
14
  while(1)
15
  {
16
    while( ~(ADCSRA & 0x10));  //wait for conversion
17
    Result = ADCL;
18
    Result += (ADCH << 8);
19
    //  Result = ADCW;
20
    Display = 8 * (0xFFFF / Result);  // convert the result from 0x0000..0xFFFF to 1..8
21
    PORTB = ~(1 << Display);
22
  }
23
}

Und genau da ist das Problem. Aus irgendeinem Grund bleibt er beim 
Auslesen des Statusbits hängen und macht nicht weiter. (Das habe ich mit 
diversen LEDausgaben getestet, die jetzt hier nicht zu sehen sind.)
1
while( ~(ADCSRA & 0x10));  //wait for conversion

Mich wundert es auch, dass ich nirgends den Modus (Free running mode) 
angeben konnte. Wie mache ich das beim ATmega32?

Könnt Ihr mir helfen?


Danke
BrEin

von Krapao (Gast)


Lesenswert?

>    while( ~(ADCSRA & 0x10));  //wait for conversion

Das ~ ist da seltsam. Vergleich das doch mal mit der funktionierenden 
ADC Routine im AVR-GCC-Tutorial. BTW. Deine Schaltung funktioniert mit 
den bekannt funktionierenden Routinen (um Hardwarekäfer 
auszuschliessen)?

Die Bits (0x10 usw.) habe ich nicht kontrolliert. Tipp: Benutze die 
allgemein gängige Schreibweise und die Bitnamen. Dann weiss man direkt 
ob du Bit ADSC oder ADIF... prüfst oder nicht. Wie das geht steht auch 
im Tutorial bzw bei Bitmanipulation

Die 0xFFFF bei der Umrechnung ist IMHO falsch. 1023..0 => 8..1 würde ich 
so machen: Display = (7*Result)/1024+1; Der AVR hat nur einen max. 
10-Bit ADC, benutzt du 10-Bit ADC dann gilt die 1024 in der Formel.

Innerhalb der while(1) Schleife sollte die Single Conversion (wenn du 
die benutzt) auch regelmäßig angestoßen werden.

Freerunning mit ADFR bei älteren ist bei neueren AVRs durch ADATE und 
passende Konfigurationsbits ersetzt.

von MitLeser (Gast)


Lesenswert?

lass mal bei der Initialisierung von ADCSRA das ADIE-Bit weg.

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Krapao schrieb:
>>    while( ~(ADCSRA & 0x10));  //wait for conversion
>
> Das ~ ist da seltsam. Vergleich das doch mal mit der funktionierenden
> ADC Routine im AVR-GCC-Tutorial. BTW. Deine Schaltung funktioniert mit
> den bekannt funktionierenden Routinen (um Hardwarekäfer
> auszuschliessen)?

Genau das Tutorial macht in diesem Punkt wenig Sinn.
1
ADCSRA |= (1<<ADSC);                  // eine ADC-Wandlung 
2
while (ADCSRA & (1<<ADSC) ) {}        // auf Abschluss der Konvertierung warten
Warum das mit dem ADSC - Flag gemacht wird, also dem der eine Messung 
anstößt erschließt sich mir nicht. Ich lesen den Interrupt-Flag aus, 
denn dafür ist er da.

Die Tilde ~ negiert den Ausdruck dahinter. Ich vergleiche ob das Flag 
gesetzt wird. Ist es noch Null, so bleibt der Ausdruck Null und wird 
durch die Tilde so lange Eins, bis die Konvertierung erledigt ist und 
das Flag auf eins gesetzt wird. Denn dann wird der Ausdruck zu Eins und 
durch die Tilde Null. Dann endet wie While-Schleife und eben genau das 
passiert hier nicht.

>
> Die Bits (0x10 usw.) habe ich nicht kontrolliert. Tipp: Benutze die
> allgemein gängige Schreibweise und die Bitnamen. Dann weiss man direkt
> ob du Bit ADSC oder ADIF... prüfst oder nicht. Wie das geht steht auch
> im Tutorial bzw bei Bitmanipulation
>
Ja, werde ich gleich ergänzen.

> Die 0xFFFF bei der Umrechnung ist IMHO falsch. 1023..0 => 8..1 würde ich
> so machen: Display = (7*Result)/1024+1; Der AVR hat nur einen max.
> 10-Bit ADC, benutzt du 10-Bit ADC dann gilt die 1024 in der Formel.
>
Finde ich besser als meine Lösung. Danke, wird auch übernommen.

> Innerhalb der while(1) Schleife sollte die Single Conversion (wenn du
> die benutzt) auch regelmäßig angestoßen werden.
>
> Freerunning mit ADFR bei älteren ist bei neueren AVRs durch ADATE und
> passende Konfigurationsbits ersetzt.
Es soll ja der Free running Mode sein. Ich weiß auch, dass ich ihn schon 
mal auf einem ATmega32 und einem ATmega128 genutzt habe. Warum ich jetzt 
nicht sehe, wo ich ihn einstellen kann ist mir absolut schleierhaft. Das 
war ganz einfach. Leider war das jetzt 2 Jahre her und ich habe alles 
wieder vergessen.

:(

von Jonathan S. (joni-st) Benutzerseite


Lesenswert?

Fabian Hoemcke schrieb:
> Warum das mit dem ADSC - Flag gemacht wird, also dem der eine Messung
> anstößt erschließt sich mir nicht. Ich lesen den Interrupt-Flag aus,
> denn dafür ist er da.

Ähm, das Datenblatt meint da aber was anderes: Das ADSC-Bit bleibt so 
lange gesetzt, wie die Messung andauert. Im Freerunning-Modus bleibt es 
gesetzt, setzt man es auf 0, ist die Messung beendet. Das Interrupt-Flag 
wird (glaube ich) im Freerunning-Modus nicht benutzt. Also: 
Single-Conversion-Modus und ADSC-Bit nehmen.


Gruß
Jonathan

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Jonathan Strobl schrieb:
> Fabian Hoemcke schrieb:
>> Warum das mit dem ADSC - Flag gemacht wird, also dem der eine Messung
>> anstößt erschließt sich mir nicht. Ich lesen den Interrupt-Flag aus,
>> denn dafür ist er da.
>
> Ähm, das Datenblatt meint da aber was anderes: Das ADSC-Bit bleibt so
> lange gesetzt, wie die Messung andauert. Im Freerunning-Modus bleibt es
> gesetzt, setzt man es auf 0, ist die Messung beendet. Das Interrupt-Flag
> wird (glaube ich) im Freerunning-Modus nicht benutzt. Also:
> Single-Conversion-Modus und ADSC-Bit nehmen.
>
>
> Gruß
> Jonathan

OK! Wenn Du das sagst, probiere ich das mal aus!
Finde es aber komisch, dass ich den Free Running Mode nicht mehr hin 
bekomme.

_

Hier im übrigen mal der angepasste Code:
1
#include <avr/io.h>
2
3
uint16_t Result = 0;
4
uint8_t  Display = 0;
5
6
int main(void)
7
{
8
  DDRB = 0xFF;  // LEDs | pins set as inputs
9
  PORTB = 0x0F;  // all LEDs off
10
  DDRA &= ~0x01;  // Port A Pin 0 set as output
11
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);  //0x86;  // 1000|0110
12
  ADMUX = 0x00;  // 0000|0000
13
  ADCSRA |= (1<<ADSC);  // start first conversion
14
  while(1)
15
  {
16
    while( ~(ADCSRA & (1<<ADIF)));  //wait for conversion
17
    Result = ADCL;
18
    Result += (ADCH << 8);
19
    //  Result = ADCW;
20
    Display = 7 * (Result / 1024) + 1;  // convert the result from 0..1024 to 1..8
21
    PORTB = ~(1 << Display);
22
  }
23
}

von Jonathan S. (joni-st) Benutzerseite


Lesenswert?

Du musst die Negierung im while() noch wegmachen. Sonst wartest Du, 
solange keine Messung stattfindet ;)

von Achim M. (minifloat)


Lesenswert?

Fabian Hoemcke schrieb:
> Genau das Tutorial macht in diesem Punkt wenig Sinn.ADCSRA |= (1<<ADSC); 
// eine ADC-Wandlung
> while (ADCSRA & (1<<ADSC) ) {}        // auf Abschluss der Konvertierung warten
> Warum das mit dem ADSC - Flag gemacht wird, also dem der eine Messung
> anstößt erschließt sich mir nicht. Ich lesen den Interrupt-Flag aus,
> denn dafür ist er da.

Im Datenblatt steht jedoch, fast schon als Empfehlung ausgedrückt, dass 
man zum Start einer Wandlung das ADSC-Bit setzen soll. Die Hardware 
setzt es dann selbsttätig nach Abschluss der Wandlung wieder auf 0. Das 
kann man dann pollen. Das Tutorial ergibt also einen Sinn.

Wenn du es Interruptgetrieben machen möchtest, dann fehlt mir im 
geposteten Code irgendwie die Interruptroutine.

mfg mf

von Krapao (Gast)


Lesenswert?

> Die Tilde ~ negiert den Ausdruck dahinter. Ich vergleiche ob das Flag
> gesetzt wird. Ist es noch Null, so bleibt der Ausdruck Null und wird...

durch ~ zu einem 0xFF! Die Tilde ~ ist der Bitoperator NOT. Du hast eine 
Endlosschleife gebaut. Nimm für diesen Zweck den Logikoperator NOT (!).

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Jonathan Strobl schrieb:
> Du musst die Negierung im while() noch wegmachen. Sonst wartest Du,
> solange keine Messung stattfindet ;)

Nein! Das ist so schon richtig. Das habe ich weiter oben beschrieben.
Ich frage ja ab, wann das Flag und somit der Ausdruck 1 wird. Und dass 
muss ich halt negieren.

Mini Float schrieb:
> Fabian Hoemcke schrieb:
>> Genau das Tutorial macht in diesem Punkt wenig Sinn.ADCSRA |= (1<<ADSC);
> // eine ADC-Wandlung
>> while (ADCSRA & (1<<ADSC) ) {}        // auf Abschluss der Konvertierung warten
>> Warum das mit dem ADSC - Flag gemacht wird, also dem der eine Messung
>> anstößt erschließt sich mir nicht. Ich lesen den Interrupt-Flag aus,
>> denn dafür ist er da.
>
> Im Datenblatt steht jedoch, fast schon als Empfehlung ausgedrückt, dass
> man zum Start einer Wandlung das ADSC-Bit setzen soll. Die Hardware
> setzt es dann selbsttätig nach Abschluss der Wandlung wieder auf 0. Das
> kann man dann pollen. Das Tutorial ergibt also einen Sinn.
>
> Wenn du es Interruptgetrieben machen möchtest, dann fehlt mir im
> geposteten Code irgendwie die Interruptroutine.
>
> mfg mf

Danke, aber im Datenblatt steht aber auch, dass das ADIF auf 1 gesetzt 
wird, sobald die Messung beendet und das Ergebnis im DATA REGISTER 
geschrieben ist.
Es bleibt sich also gleich, ob ich jetzt nun warte bis ADIF auf 1 
gesetzt wurde oder ADSC auf 0. Oder etwa nicht?

Die Version mit dem Abfragen des ADSC auf 0 habe ich auch ausprobiert. 
Das Ergebniss ist immer 2. Auch wenn ich VTG anlege. Dabei spielt es 
keine Rolle ob ich die Konvertierung nur einmal starte (also das ADSC 
bit setze) oder es jedes Mal in der Schleife starte. Es scheint sich 
hier also schon um den Free Running Mode zu handeln.
Durch eine Ausgabe an den LEDS (1 für „vor der Flagabfrage” und 2 für 
„nach der Flagabfrage” - beide waren an!) konnte ich sehen, das bei 
dieser Version also die Schleife ständig durchlaufen wird. Ich muss also 
was an den Einstellungen des ADCs oder des Muxers falsch gemacht haben, 
dass er einfach nicht die Werte vom PA0 auslesen will.

Krapao schrieb:
>> Die Tilde ~ negiert den Ausdruck dahinter. Ich vergleiche ob das Flag
>> gesetzt wird. Ist es noch Null, so bleibt der Ausdruck Null und wird...
>
> durch ~ zu einem 0xFF! Die Tilde ~ ist der Bitoperator NOT. Du hast eine
> Endlosschleife gebaut. Nimm für diesen Zweck den Logikoperator NOT (!).

Probiere ich gleich aus!

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Krapao schrieb:
>> Die Tilde ~ negiert den Ausdruck dahinter. Ich vergleiche ob das Flag
>> gesetzt wird. Ist es noch Null, so bleibt der Ausdruck Null und wird...
>
> durch ~ zu einem 0xFF! Die Tilde ~ ist der Bitoperator NOT. Du hast eine
> Endlosschleife gebaut. Nimm für diesen Zweck den Logikoperator NOT (!).

Jup! Das hat geholfen! Blöd von mir die anderen Nullen auch ständig auf 
1 zu setzen! **Facepalm**.

OK, das war der erste Baum!

Warum zeigt mir der ADC nun ständig immer das selbe Ergebnis an???
Lese ich falsch aus? Habe ich falsche Settings?

Der Prescaler soll 64 sein. Bei einer Frequenz von 3.686.400Hz sind das 
57.600Hz. Das müsste im Grunde doch passen.

**Dummguck**

Danke aber für die Hilfe bisher!
BrEin

von Stefan E. (sternst)


Lesenswert?

Fabian Hoemcke schrieb:
> Warum zeigt mir der ADC nun ständig immer das selbe Ergebnis an???

Weil du überhaupt nur eine Messung machst. Du stößt ja keine neue an. 
Und das Warten funktioniert nach dem ersten Mal auch nicht mehr, weil du 
das Flag ja nicht zurücksetzt.
(letzteres ist auch der Grund, warum es einfacher ist, auf ADSC zu 
warten)

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Stefan Ernst schrieb:
> Fabian Hoemcke schrieb:
>> Warum zeigt mir der ADC nun ständig immer das selbe Ergebnis an???
>
> Weil du überhaupt nur eine Messung machst. Du stößt ja keine neue an.
> Und das Warten funktioniert nach dem ersten Mal auch nicht mehr, weil du
> das Flag ja nicht zurücksetzt.
> (letzteres ist auch der Grund, warum es einfacher ist, auf ADSC zu
> warten)

Was habt Ihr denn nur mit dem ADSC?
Das habe ich so nie gelernt. Auch im Pollingmode mutze ich doch den 
dafür vorgesehenen ADIF. Aber sei es drum. Egal ob ich den ADIF mit 1 
zurücksetzte oder gleich den ADSC auslese, das Ergebnis ist immer gleich 
*2*:
1
#include <avr/io.h>
2
3
uint16_t Result = 0;
4
uint8_t  Display = 0;
5
6
int main(void)
7
{
8
  DDRB = 0xFF;  // LEDs | pins set as inputs
9
  PORTB = 0x0F;  // all LEDs off
10
  DDRA &= ~0x01;  // Port A Pin 0 set as output
11
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);  //0x86;  // 1000|0110
12
  ADMUX = 0x00;  // 0000|0000
13
  while(1)
14
  {
15
    ADCSRA |= (1<<ADSC);  // start conversion
16
    while( (ADCSRA & (1<<ADSC)));  //wait for conversion
17
      Result = ADCL;
18
      Result += (ADCH << 8);
19
    //  Result = ADCW;
20
      Display = 7 * (Result / 1024) + 1;  // convert the result from 0..1024 to 1..8
21
      PORTB = ~(1 << Display);
22
  }
23
}

Habt Ihr da noch ein Lösungsangebot?

Danke!

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Ich wollte mich nur mal bei euch bedanken und sagen, dass Ihr super 
seid.
Zu so später Stunde schon so viel Hilfe bekommen.
Danke!

Nur verstehe ich nicht, warum ich mich so harte anstelle bei einem ADC!
Naja! Vielleicht bekommen wir das noch hin. ;)

Danke euch!

von Stefan E. (sternst)


Lesenswert?

Fabian Hoemcke schrieb:
> das Ergebnis ist immer gleich
> *2*:

Logisch, da (Result / 1024) immer 0 ist, ist Display immer 1.

von Krapao (Gast)


Lesenswert?

> Display = (7*Result)/1024+1

Die Klammern brauchst du! Und ich die 7 ist eigentlich eine 8 :-)

max. Result = 1023
8*1023 = 8184
8184/1024 (= 7,9921875 mathematisch) = 7 (Ganzzahlrechnung!)
7+1 = 8

von Stefan E. (sternst)


Lesenswert?

Fabian Hoemcke schrieb im Beitrag #2384166:
> Kann ich das mit nem floatcast verbessern?

Du könntest mit Float arbeiten, oder aber einfach die Rechnung etwas 
umstellen (wie von Krapao schon gezeigt).

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Stefan Ernst schrieb:
> Fabian Hoemcke schrieb:
>> das Ergebnis ist immer gleich
>> *2*:
>
> Logisch, da (Result / 1024) immer 0 ist, ist Display immer 1.

Krapao schrieb:
>> Display = (7*Result)/1024+1
>
> Die Klammern brauchst du! Und ich die 7 ist eigentlich eine 8 :-)
>
> max. Result = 1023
> 8*1023 = 8184
> 8184/1024 (= 7,9921875 mathematisch) = 7 (Ganzzahlrechnung!)
> 7+1 = 8

Danke euch beiden!
Ich habe die Passage aber mal umgeändert:
1
Display = (8*Result)/1024;
Da ich ja noch:
1
PORTB = ~(1 << Display);
;)

Aber vielen lieben Dank!
Jetzt kann ich endlich beruhigt schlafen gehen.

Danke
BrEin

von Fabian H. (Firma: Technische Universität Berlin) (brein)


Lesenswert?

Abschließend nochmal der fertige Code, so wie er auch bei mir 
funktioniert:
1
#include <avr/io.h>
2
3
uint16_t Result = 0;
4
uint8_t  Display = 0;
5
6
int main(void)
7
{
8
  DDRB = 0xFF;  // LEDs | pins set as inputs
9
  PORTB = 0x0F;  // all LEDs off
10
  DDRA &= ~0x01;  // Port A Pin 0 set as output
11
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);  //0x86;  // 1000|0110
12
  ADMUX = 0x00;  // 0000|0000
13
  while(1)
14
  {
15
    ADCSRA |= (1<<ADIF);  // clear interrupt flag
16
    ADCSRA |= (1<<ADSC);  // start conversion
17
    while(! (ADCSRA & (1<<ADIF)));  //wait for conversion
18
    Result = ADCW;      // read out the result
19
    Display = (8*Result)/1024;  // convert the result from 0..1024 to 1..8
20
    PORTB = ~(1 << Display);
21
  }
22
}

Warum der Free Running Mode hier nicht funktioniert ist mir zwar absolut 
schleierhaft, aber ist jetzt auch nicht so wichtig.

Vielen Dank nochmal an alle Helfer!

Gute Nacht!
BrEin

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.