Forum: Mikrocontroller und Digitale Elektronik ATmega16: ADC-Multiplexing geht nicht


von frederik_u (Gast)


Lesenswert?

Hallo,

habe folgendes Problem:

Möchte mit einem AVR ATmega16 8 Spannungen überwachen.

Mit einer Spannung funktioniert es, aber sobald ich es mit Multiplexing 
versuche, um auch die anderen 7 ADC-Kanäle auslesen zu können, gibt es 
Probleme.

Meine Vermutung ist, dass ich die Bits für die Kanalwahl im 
ADMUX-Register möglicherweise zum falschen Zeitpunkt update.

Im Datenblatt steht dazu:
"If these bits [MUX0 bis 3] changed during conversion, the change will 
not take effect until this conversion is complete (it means while the 
ADIF bit in ADCSRA register is set)."

Die AD-Konvertierung ist ja eigentlich abgeschlossen, wenn das ADSC 
-Flag im ADCSRA-Register gelöscht wurde?!!!

Vielleicht also am besten erst mal die Frage, woran man nun das 
Konvertierungsende "besser" erkennt:

an ADSC = 0

oder

an ADIF = 1

???

von spess53 (Gast)


Lesenswert?

Hi

>Mit einer Spannung funktioniert es, aber sobald ich es mit Multiplexing
>versuche, um auch die anderen 7 ADC-Kanäle auslesen zu können, gibt es
>Probleme.

Welche?

>Vielleicht also am besten erst mal die Frage, woran man nun das
>Konvertierungsende "besser" erkennt:

>an ADSC = 0
>oder
>an ADIF = 1

An dem Wechsel von 1 nach 0.

MfG Spess

von nobi (Gast)


Angehängte Dateien:

Lesenswert?

Hi,

ich hab mal was ähnliches gemacht mit 4 Kanälen,

Hab einfach eine Einzelmessung gestarted, und im ADC Interrupt den Kanal 
weitergeschaltet und die nächste Messung gestartet.

Funktioniert so problemlos.

von frederik_u (Gast)


Lesenswert?

spess53 schrieb:
>>Probleme.
>
> Welche?

Es gibt nur jeder zweite Kanal ein Ergebnis (das aber selber richtig zu 
sein scheint).

spess53 schrieb:
>>an ADIF = 1
>
> An dem Wechsel von 1 nach 0.

Also ADSC setzen, um die Konvertierung im Single Conversion Mode zu 
starten und warten, bis ADSC von der Hardware wieder gelöscht ist, um 
das Ende der Konvertierung zu erkennen?

von Ingo (Gast)


Lesenswert?

Das hier ist auch eher Sub-Optimal:
1
uint16 AdcGetVoltage(uint8 x)
2
{
3
4
   uint16 val =0;
5
   if(x ==0)
6
   {
7
      val= AdcVoltage(0);
8
   }
9
   else if(x ==1)
10
   {
11
      val= AdcVoltage(1);
12
   }
13
   else if(x ==2)
14
   {
15
      val= AdcVoltage(2);
16
   }
17
   else if(x ==3)
18
   {
19
      val= AdcVoltage(3);
20
   }
21
   return val;
22
}

Warum vorher noch abfragen?
Besser:
1
uint16_t val = ADCVoltage(x);

Oder nicht?


Ingo

von frederik_u (Gast)


Lesenswert?

@nobi: Danke für den Code!!!

Zurück zur Hauptfrage:

frederik_u schrieb:
> Also ADSC setzen, um die Konvertierung im Single Conversion Mode zu
> starten und warten, bis ADSC von der Hardware wieder gelöscht ist, um
> das Ende der Konvertierung zu erkennen?

Wann ist denn eigentlich der günstigste Zeitpunkt, den neuen ADC-Kanal 
durch Update der MUX-Register zu definieren?

von spess53 (Gast)


Lesenswert?

Hi

>Es gibt nur jeder zweite Kanal ein Ergebnis (das aber selber richtig zu
>sein scheint).

Dann musst du halt mal dein Programm zeigen.

>Also ADSC setzen, um die Konvertierung im Single Conversion Mode zu
>starten und warten, bis ADSC von der Hardware wieder gelöscht ist, um
>das Ende der Konvertierung zu erkennen?

Ja. Oder einfach den ADC Complete Interrupt benutzen. Dann entfällt die 
Warterei.

MfG Spess

von Ingo (Gast)


Lesenswert?

spess53 schrieb:
> Ja. Oder einfach den ADC Complete Interrupt benutzen. Dann entfällt die
> Warterei.

Wobei man eigentlich nur das Flag pollen muss, der ganze Overhead für 
die ISR lohnt meist nicht, wenn man nur den MUX eins weiter setzt und 
den Wert abholt...

von flo (Gast)


Lesenswert?

Ingo schrieb:
> Wobei man eigentlich nur das Flag pollen muss

was bedeutet "flag pollen"?

von Uwe (de0508)


Lesenswert?

Hallo
1
#include <avr/io.h>
2
#include <avr/sfr_defs.h>
3
4
 loop_until_bit_is_clear(ADCSRA, ADSC);

von Eumel (Gast)


Lesenswert?

flo schrieb:
> Ingo schrieb:
>> Wobei man eigentlich nur das Flag pollen muss
>
> was bedeutet "flag pollen"?

Flagsoolange abfragen bis eine Änderung eingetreten ist und dann 
entsprechend reagieren.

von spess53 (Gast)


Lesenswert?

Hi

>Wobei man eigentlich nur das Flag pollen muss, der ganze Overhead für
>die ISR lohnt meist nicht, wenn man nur den MUX eins weiter setzt und
>den Wert abholt...

Und das ist keine Warterei? Bei z.B. 8MHz sind das zwischen 500 und 2000 
Takte.

MfG Spess

von frederik_u (Gast)


Lesenswert?

Der Code ist leider auf einem anderen Rechner, könnte ihn aber heute 
Nachmittag übertragen.

Ich würde auch ganz gerne einzelne ADC-Kanäle gezielt auslesen können, 
also nicht immer nur der Reihe nach.

Flag pollen ist eine gute Idee, das Programm kann seine anderen Aufgaben 
erledigen und dazwischen wird hin und wieder das Flag gepollt.


Wobei mit "Flag pollen" das Pollen des ADSC-Flags auf Null gemeint ist, 
nehme ich an... !??

von Ingo (Gast)


Lesenswert?

spess53 schrieb:
> Und das ist keine Warterei? Bei z.B. 8MHz sind das zwischen 500 und 2000
> Takte.
Nein, nicht wenn man das Flag in einem festen Zeitraster pollt, man muss 
ja nicht unbedingt darauf warten das er fertig wird, sondern man kann 
das auch geschickter machen, wundert mich das du das nicht kennst...

Ingo

von Karl H. (kbuchegg)


Lesenswert?

spess53 schrieb:
> Hi
>
>>Wobei man eigentlich nur das Flag pollen muss, der ganze Overhead für
>>die ISR lohnt meist nicht, wenn man nur den MUX eins weiter setzt und
>>den Wert abholt...
>
> Und das ist keine Warterei? Bei z.B. 8MHz sind das zwischen 500 und 2000
> Takte.

natürlich ist das Warterei.

*) die Frage ist, wie weh das in einer bestimmten Situation tut
*) weiters ist es oft möglich, das Programm etwas umzugestalten, so
   dass der ADC sampelt, während das Programm etwas anderes macht.
   Wichtig ist ja nur, dass das Ergebnis vorliegt, wenn es benötigt
   wird, bzw. alle Zwischenarbeiten erledigt wurden.


Speziell letzteres:
Man kann ja den ADC bereits mit dem neuen Kanal schon wieder starten, 
während noch das vorhergehende Ergebnis aufgearbeitet wird.

also nicht (Prinzipskizze)

  while( 1 ) {

   starte ADC
   warte auf Ergebnis
   bearbeite Ergebnis
   Ergebnis ausgeben
  }

sondern

  starte ADC

  while( 1 ) {
    x = warte auf Ergebnis
    starte ADC
    bearbeite x
    x ausgeben
  }

oder irgendein anderes Schema um das Pollen durchzuführen.

von Vuvuzelatus (Gast)


Lesenswert?

>Wann ist denn eigentlich der günstigste Zeitpunkt, den neuen ADC-Kanal
>durch Update der MUX-Register zu definieren?

Unmittelbar vor dem Starten einer neuen Wandlung?

von nobi (Gast)


Lesenswert?

@Ingo

>Das hier ist auch eher Sub-Optimal:
1
uint16 AdcGetVoltage(uint8 x)
2
{
3
4
   uint16 val =0;
5
   if(x ==0)
6
   {
7
      val= AdcVoltage(0);
8
   }
9
   else if(x ==1)
10
   {
11
      val= AdcVoltage(1);
12
   }
13
   else if(x ==2)
14
   {
15
      val= AdcVoltage(2);
16
   }
17
   else if(x ==3)
18
   {
19
      val= AdcVoltage(3);
20
   }
21
   return val;
22
}

AdcVoltage(x) ist ein Makro in meinem Headerfile definiert, dass mir 
fuer den jeweiligen Kanal die Spannung berechnet, unter Berücksichtigung 
eines Fehlerkorrekturfaktors, der die Hardwaretolleranzen meiner 
speziellen Eingangsbeschaltung berücksichtigt.

Da Dies aber zur Auslesung der ADC Werte nichts zur Sache tut, hab ich 
das Makro hier nicht gepostet.

von Karl H. (kbuchegg)


Lesenswert?

nobi schrieb:

> AdcVoltage(x) ist ein Makro


Dann halte dich an die einzige freiwillge Konvention, die es tatsächlich 
schafft, dass Millionen Programmierer weltweit sie einhalten:

Makronamen werden ausschliesslich in Grossbuchstaben geschrieben und 
auch umgekehrt sind Namen komplett in Grossbuchstaben ausschliesslich 
für Makros reserviert.
Schreibst du

   val = ADCVOLTAGE(0);

dann kann jeder C-Programmierer im Dschungel des Amazonas erkennen, dass 
ADCVOLTAGE (oder ADC_VOLTAGE um es lesbarer zu machen) ein Makro ist.

Im übrigen sehe ich nicht, was dir hier ein Makro bringen würde. Ich 
sehe aber, dass dir ein Makro unter Umständen eine Menge Mehrarbeit 
anstelle einer Funktion aufbürdet.

von frederik_u (Gast)


Lesenswert?

Also, habe den Fehler gefunden. Es muss nach jedem ADC-Multiplexing 
(ADC-Kanal-Umschaltung) eine "Leerlauf"-ADC-Konvertierung durchgeführt 
und verworfen werden, bevor die eigentliche Messung stattfinden kann.

Viele Grüße

von Spess53 (Gast)


Lesenswert?

Hi

>Also, habe den Fehler gefunden. Es muss nach jedem ADC-Multiplexing
>(ADC-Kanal-Umschaltung) eine "Leerlauf"-ADC-Konvertierung durchgeführt
>und verworfen werden, bevor die eigentliche Messung stattfinden kann.

Habe ich noch nie gemacht. Ging immer ohne. Da ist etwas anderes faul.

MfG Spess

von Ingo (Gast)


Lesenswert?

Spess hat recht, nur der XMega hat (oder hatte?) dieses Problem, normale 
Tinys und Megas eigentlich nicht. Wie hochohmig gehst du an den ADC ran?

von frederik_u (Gast)


Lesenswert?

Ingo schrieb:
> Spess hat recht, nur der XMega hat (oder hatte?) dieses Problem, normale
> Tinys und Megas eigentlich nicht. Wie hochohmig gehst du an den ADC ran?

10k gegen Masse. Werde es noch mal genauer untersuchen...

von Uwe (de0508)


Lesenswert?

Hallo Ingo,

das war nicht gemeint, der Innenwiderstand deiner Stromquelle soll <10kΩ 
betragen.

Ich habe deshalb bei einem Messgerät einen präzisions OPA im SOT-23 
Gehäuse mit Vu=1 eingesetzt.

Die externe Spannugsreferenz hat VRef = 2,500V.

von Spess53 (Gast)


Lesenswert?

Hi

>10k gegen Masse.

Das ist nicht die Quellimpedanz.

> Werde es noch mal genauer untersuchen...

Dann schreib gleich noch dazu, wie hoch dein ADC-Takt ist. Also die ADPS 
Bits.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

Und das Programm ist auch interessant.

von frederik_u (Gast)


Lesenswert?

Danke für die Antworten!

Kleine Zwischenfrage, wie müssen eigentlich die I/O-Pins für ADC-Betrieb 
definiert sein?

Sie sind momentan als Eingänge konfiguriert per

DDRX &= ~(1<<DDXy)

wobei X = Port und y = jeweiliger Pin



An den ADC-Ports sitzt ein Spannungsteiler:

Messspannung (max. 8,4V)
 |
10k
 |
ADC-Pin
 |
10k
 |
GND


ADC-Spannungsteiler so wie im Buch "AVR" von Florian Schäffer

von frederik_u (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und das Programm ist auch interessant.

Bin gestern zuhause nicht mehr dazu gekommen, sorry!

von spess53 (Gast)


Lesenswert?

Hi

>Kleine Zwischenfrage, wie müssen eigentlich die I/O-Pins für ADC-Betrieb
>definiert sein?

>Sie sind momentan als Eingänge konfiguriert per

>DDRX &= ~(1<<DDXy)

>wobei X = Port und y = jeweiliger Pin

So wie du es gemacht hast. Allerdings solltest du auch vermeiden den 
internen Pull-Up-Widerstand einzuschalten.

>ADC-Spannungsteiler so wie im Buch "AVR" von Florian Schäffer

wenn du keine schnellen Spannungsänderungen messen willst, kann ein 100n 
Kondensator zwischen AD-Pin und GND nicht schaden.

MfG Spess

von frederik_u (Gast)


Lesenswert?

spess53 schrieb:
>>DDRX &= ~(1<<DDXy)
>
>>wobei X = Port und y = jeweiliger Pin
>
> So wie du es gemacht hast. Allerdings solltest du auch vermeiden den
> internen Pull-Up-Widerstand einzuschalten.

Selbstverständlich!

spess53 schrieb:
> wenn du keine schnellen Spannungsänderungen messen willst, kann ein 100n
> Kondensator zwischen AD-Pin und GND nicht schaden.

Gute Idee! Die Spannungen könnten im Prinzip sogar im Sekundentakt oder 
noch langsamer ausgelesen werden...

von spess53 (Gast)


Lesenswert?

Hi

>Selbstverständlich!

Nicht ganz. Andere IOs, wie z.B. USART oder TWI, übernehmen die volle 
Kontrolle über die entsprechenden Pins. Da kannst du die Portregister 
setzen wie du lustig bist.

MfG Spess

von frederik_u (Gast)


Lesenswert?

spess53 schrieb:
> Hi
>
>>Selbstverständlich!
>
> Nicht ganz. Andere IOs, wie z.B. USART oder TWI, übernehmen die volle
> Kontrolle über die entsprechenden Pins. Da kannst du die Portregister
> setzen wie du lustig bist.
>
> MfG Spess

Aber nur, wenn sie aktiviert werden, schätze ich... ?!!


Ansonsten könnte man vielleicht hinter das

DDRX = 0x00

zur Sicherheit noch ein

PORTX = 0x00

setzen, damit die internen Pullups wirklich deaktiviert sind...
(wenn das wirklich Sinn macht)

von spess53 (Gast)


Lesenswert?

Hi

>Aber nur, wenn sie aktiviert werden, schätze ich... ?!!

Natürlich, nur wen die passenden Enable-Bits gesetzt sind.

>(wenn das wirklich Sinn macht)

Mcht nur Sinn wenn du vorher schon mal an dem Portregister rumgefummelt 
hast. Nach einem Reset/Power on ist das Register automatisch mit 0x00 
initialisiert.

MfG Spess

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.