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 ???
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
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.
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?
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
@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?
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
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...
Hallo
1 | #include <avr/io.h> |
2 | #include <avr/sfr_defs.h> |
3 | |
4 | loop_until_bit_is_clear(ADCSRA, ADSC); |
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.
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
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... !??
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
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.
>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?
@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.
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.
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
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
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?
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...
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.
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
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
Karl Heinz Buchegger schrieb: > Und das Programm ist auch interessant. Bin gestern zuhause nicht mehr dazu gekommen, sorry!
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
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...
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
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)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.