Hallo,
ich bin seit ein paar Tagen dabei einen Atmega 16 zu programmieren, mit
welchem ich die Spannungen und Ströme in einem Labornetzteil messe und
die Werte anschließend auf einem LCD ausgeben möchte. Zum vorläufigen
Test möchte ich auf dem LCD in der ersten Zeile den aktuellen ADC-Wert
von Kanal 1 und in der zweiten Zeile den aktuellen ADC-Wert von Kanal 2
ausgeben.
Hier ist mein vorläufiges Programm:
ADMUX=admux_init;//left adjusted für 10 bit ADC- Wandlung, ADC1 wählen
76
lcd_init();
77
sei();//global interrupts enable
78
}
79
80
81
82
main()
83
{
84
init();
85
86
while(1)
87
{
88
//calculation();
89
display();
90
}
91
}
Leider hänge ich seit zwei Tagen an einem recht seltsamen Problem. Auf
dem LCD wird nämlich in der ersten Zeile nicht der Wert von Kanal 1,
sondern der von Kanal 4 angezeigt. Ähnlich sieht es mit der zweiten
Zeile aus. Da wird nämlich nicht der Wert von Kanal 2, sondern der von
Kanal 4 angezeigt.
Aufgrund dieses Fehlverhaltens habe ich probiert ich mir das Programm
mit dem AVR Dragon im JTAG-Debugmodus angeschaut und dabei folgendes
festgestellt:
Wenn ich mit F11 durchsteppe werden alle ADC-Werte in den korrekten
Variablen abgespeichert, das ganze funktioniert auch über lange Zeit
(längeres Drücken der F11 Taste). Allerdings wird die Funktion Display()
nie richtig aufgerufen, immer wenn der Debugger reinspringen müsste ruft
er erneut die ISR auf.
Wenn ich mit F10 durchsteppe bestätigt sich meine fehlerhafte
LCD-Ausgabe. Im Watch-Fenster sieht man, dass der ADC-Wert von Kanal 4
in der Variablen adc_1_voltage und der ADC-Wert von Kanal 1 in der
Variablen adc_2_ref abgespeichert werden. Daran ändert sich auch nichts
wenn ich lang auf F10 bleibe. Interessant ist auch, dass direkt nach
Aufrufen der init-Funktion und anscheinend direkt vor aufrufen der
display() Funktion zwei Interrupts korrekt ausgeführt werden (richtige
Werte in den Variablen) und dann direkt nach dem ersten Aufruf der
display() Funktion die richtigen Werte auf dem LCD erscheinen. Diese
beiden Interrupts sind komischerweise beim Debuggen nicht sichtbar. Auf
jeden Fall wird direkt nach dem ersten Aufrufen der display()-Funktion
fälschlicherweise der Wert von Kanal 4 in der Variablen adc_1_voltage
gespeichert und von da an funktioniert das Programm fehlerhaft.
Ich hoffe, dass meine Schilderungen verständlich waren und mir jemand
helfen kann.
Vielen Dank im Voraus fürs Durchlesen und Mitdenken
Andi
Andi schrieb:> Leider hänge ich seit zwei Tagen an einem recht seltsamen Problem. Auf> dem LCD wird nämlich in der ersten Zeile nicht der Wert von Kanal 1,> sondern der von Kanal 4 angezeigt.
Tja.
Eine Änderung des MUX wirkt sich erst beim Start der nächsten Messung
aus. Zu dem Zeitpunkt an dem du in der ISR den MUX änderst, läuft aber
schon die nächste Messung. D.h. der Interrupt für diese Messung benutzt
noch die alte MUX Einstellung. Oder anders gesagt: du änderst gerade den
MUX, dessen Ergebnis du erst bei der übernächsten Messung bekommen
wirst.
Ganz ehrlich:
Ich würd da nicht wirklich mit dem Free Running Mode und Interrupt
rumwerken. D.h. Interrupt - ja - vielleicht. Aber nicht Free Running.
Bringt ja sowieso nichts. So schnell wie du sampelst, kannst du eh das
Display nicht updaten, weil kein Mensch so schnell schauen kann.
Karl Heinz Buchegger schrieb:> Tja.> Eine Änderung des MUX wirkt sich erst bei der nächsten Messung aus. Zu> dem Zeitpunkt an dem du in der ISR den MUX änderst, läuft aber schon die> nächste Messung. D.h. der Interrupt für diese Messung benutzt noch die> alte MUX Einstellung.
Das mit der Änderung der MUX ist mir schon klar. Das hab ich im Code
schon berücksichtigt. Beim Studieren haben wir das auch schon mal so
ähnlich gemacht. Drum sollte es prinzipiell funktionieren.
Karl Heinz Buchegger schrieb:> anz ehrlich:> Ich würd da nicht wirklich mit dem Free Running Mode und Interrupt> rumwerken. D.h. Interrupt - ja - vielleicht. Aber nicht Free Running.
Ich könnte es auch mit single conversion in einer Funktion ganz ohne
Interrupt machen. Aber irgendwie fand ich die Lösung vom Studium mit
free running mode und Interrupt am Elegantesten.
Karl Heinz Buchegger schrieb:> Bringt ja sowieso nichts. So schnell wie du sampelst, kannst du eh das> Display nicht updaten, weil kein Mensch so schnell schauen kann.
So isses!
Ergonomisch ist eine Anzeigerate von 200..500ms.
Für 4 Meßwerte also einen Timerinterrupt alle 50ms die Messung starten
lassen und dann nach 200ms auf das LCD ausgeben.
Peter
Andi schrieb:> Ich könnte es auch mit single conversion in einer Funktion ganz ohne> Interrupt machen. Aber irgendwie fand ich die Lösung vom Studium mit> free running mode und Interrupt am Elegantesten.
Find ich ehrlich gesagt nicht.
Wenn man bedenkt, dass der ADC nur eine Handvoll Takte zum Wandeln
braucht, macht dein µC (fast) nix anderes mehr, als nur ADC-Interrupt
bearbeiten. Und das aus keinem guten Grund.
Ist er mit einer ISR fertig, hat auch der ADC schon wieder ein Ergebnis
und schon wieder gehts in die nächste ISR rein.
Und ehrlich gesagt: Ich bin mir nicht sicher, ob dann die Zuordnung von
MUX zu deiner Zählvariable noch stimmt.
Karl Heinz Buchegger schrieb:> Und ehrlich gesagt: Ich bin mir nicht sicher, ob dann die Zuordnung von> MUX zu deiner Zählvariable noch stimmt.
Deshalb im Interrupt den ADC auslesen, den MUX umschalten und dann erst
die nächste Messung starten. Dann kann auch mal ein anderer Interrupt
dazwischen kommen und nichts geht schief.
Free-Running ist nur für einen Kanal richtig geeignet. Dann braucht man
keinen Interrupt und das Main liest den ADC, wann es will.
Peter
Hmmm, vielleicht eine andere Optimierung benutzt?
Die Variablen, die in der ISR verändert werden sind nicht als volatile
gekennzeichnet!
Die free-running Konfiguration sollte kein Problem darstellen, wenn man
der Versatz -1 berücksichtigt.
Jetzt hab ich gerade nochmal in meinen Studienunterlagen nachgekuckt.
Mein Programmaufbau entspricht genau dem, was unser Professor uns damals
gezeigt hatte. Allerdings muss ich dazu sagen, dass er uns das nur
theoretisch an der Tafel gezeigt hat, ich es also praktisch noch nie so
funktionieren gesehen habe.
Karl Heinz Buchegger schrieb:> Find ich ehrlich gesagt nicht.> Wenn man bedenkt, dass der ADC nur eine Handvoll Takte zum Wandeln> braucht, macht dein µC (fast) nix anderes mehr, als nur ADC-Interrupt> bearbeiten. Und das aus keinem guten Grund.> Ist er mit einer ISR fertig, hat auch der ADC schon wieder ein Ergebnis> und schon wieder gehts in die nächste ISR rein.> Und ehrlich gesagt: Ich bin mir nicht sicher, ob dann die Zuordnung von> MUX zu deiner Zählvariable noch stimmt.
Deine Erklärung macht dann schon Sinn. Ich bin beim Verstehen der
Vorlesungslösung immer davon ausgegangen, dass nach einer
abgeschlossenen AD-Wandlung das Ergebnis ausgelesen wird und
anschließend das ADMUX Register für den nächsten Kanal verändert wird
und erst danach der AD-Wandler mit der nächsten Wandlung beginnt. Bin
also davon ausgegangen, dass zwischen dem Ende einer AD-Wandlung und dem
Beginn einer neuen immer eine kleine Pause ist, dass das mit dem Updaten
des ADMUX Registers es so funktioniert.
Dass dem nicht so ist und auch Professoren nur Menschen sind wäre
hiermit mal wieder belegt.
Peter Dannegger schrieb:> Deshalb im Interrupt den ADC auslesen, den MUX umschalten und dann erst> die nächste Messung starten. Dann kann auch mal ein anderer Interrupt> dazwischen kommen und nichts geht schief.>> Free-Running ist nur für einen Kanal richtig geeignet. Dann braucht man> keinen Interrupt und das Main liest den ADC, wann es will.>>> Peter
Das sehe ich jetzt, nachdem ich nun weiß, dass sich ein free running
mode mit mehreren Kanälen mit nur einem Interrupt nicht bewerkstelligen
lässt genauso. Darum werd ich jetzt das ganze einfach mit Single
Conversion in einer Funktion lösen.
Danke vielmals für eure Hilfe. Ohne euch würde ich noch ewig versuchen
eine nicht funktionierende Lösung zum Laufen zu bringen.
Hi
> Wenn man bedenkt, dass der ADC nur eine Handvoll Takte zum Wandeln> braucht, macht dein µC (fast) nix anderes mehr, als nur ADC-Interrupt> bearbeiten. Und das aus keinem guten Grund.
Na ja nicht ganz. Mit seiner Einstellung dauert eine AD-Wandlung 13x128
Controllertakte. Also ist der Controller maximal zu 5...10% mit dem
AD-Wandler beschäftigt. Das ganze lässt sich reduzieren, wenn man statt
Free-Running den Autotrigger-Mode benutzt, also mit einen Timer
triggern.
MfG spess
Andi schrieb:> Das sehe ich jetzt, nachdem ich nun weiß, dass sich ein free running> mode mit mehreren Kanälen mit nur einem Interrupt nicht bewerkstelligen> lässt genauso. Darum werd ich jetzt das ganze einfach mit Single> Conversion in einer Funktion lösen.
Selbstverständlich lässt sich das bewerkstelligen.
Wenn der Interrupt ausgelöst wird und du bspw. auf Kanal drei
umschaltest, so weisst du automatisch das der gerade von dir gelesene
Wert zu Kanal zwei gehört!
0 - > 3
1 - > 0
2 - > 1
3 - > 2
Noch etwas detaillierter beschrieben:
- Interrupt löst aus
- Der ADC befindet sich bereits in der Konvertierphase und nutz dazu den
MUX Wert der von dir beim letzten Interrupt gesetzt wurde.
- ADC Wert wird gelesen
- Du setzt den neuen MUX Kanal (der aber erst beim nächsten Mal genutzt
wird)
- ISR wird verlassen
Hi
>Wenn der Interrupt ausgelöst wird und du bspw. auf Kanal drei>umschaltest, so weisst du automatisch das der gerade von dir gelesene>Wert zu Kanal zwei gehört!
Einfacher ist es, ADMUX vorher auszulesen. Dann weiß man gleich, von
welchem Kanal der Wert stammt.
MfG Spess
121212qw schrieb:> Einfacher ist es, ADMUX vorher auszulesen. Dann weiß man gleich, von> welchem Kanal der Wert stammt.
Das stimmt, jedoch hat man dann noch eine volatile globale Variable die
den identischen Informationsgehalt wie (count_step) nur um eins
verschoben vorhält.
Wenn man nicht mit Prozessorzyklen oder Speicher haushalten muss ist es
allerdings am Bequemsten.
121212qw schrieb:> Einfacher ist es, ADMUX vorher auszulesen. Dann weiß man gleich, von> welchem Kanal der Wert stammt.
Jetzt hab ich es mal nach deinem Vorschlag programmiert:
Andi schrieb:> So habt ihr es doch gemeint, oder?> Das Programm hat aber genau die selbe Fehlfunktion wie vorher.
1) Die Variablen, die in der ISR modifiziert und im Hauptprogramm
gelesen werden, sind immer noch nicht volatile.
2) Seh' ich das richtig,
ADC_Kanal0 wird nicht benutzt,
ADC_Kanal1-4 entsprechen den Variablen:
adc_1_voltage, adc_2_ref, adc_3_current, adc_4_current_limit
ADC_Kanal5-7 sind auch ungenutzt?
Dann wäre die ISR so richtig.
3) An Punkt 1) denken!
Norbert schrieb:> 2) Seh' ich das richtig,> ADC_Kanal0 wird nicht benutzt,> ADC_Kanal1-4 entsprechen den Variablen:> adc_1_voltage, adc_2_ref, adc_3_current, adc_4_current_limit> ADC_Kanal5-7 sind auch ungenutzt?>> Dann wäre die ISR so richtig.
Ja genau so ist es. Nur Kanal 1 bis 4 hab ich benutzt.
Norbert schrieb:> 1) Die Variablen, die in der ISR modifiziert und im Hauptprogramm> gelesen werden, sind immer noch nicht volatile.
Habe ich jetzt gerade auch mal ausprobiert. Die globale Variablen
einfach mal mit dem Namenszusatz volatile deklariert. Die Fehlfunktion
des Programms bleibt aber weiterhin bestehen.
Aus diesem Grund habe ich mich zur klassischen Lösung mit Funktion und
Single Conversion Mode entschieden, welche auch ohne Probleme
funktioniert:
Hi
>Die Fehlfunktion des Programms bleibt aber weiterhin bestehen.
Das kann aber nur an deinen C-Kenntnissen liegen. Ich benutze den ADC im
Free-Running-Mode oder Autotrigger mit 2 bis 4 Kanälen seit über 12
Jahren in verschiedenen Geräten. Allerdings in Assembler. Am ADC kann es
also nicht liegen.
MfG Spess
@Spess:
Weil es unsinnig ist, einen Interrupt auszulösen wenn die Wandlung
fertig ist, wenn der ADC im Freerun ist. Dann lieber dann messen wenn
man einen Messwert braucht. Du löst sicherlich keinen Interrupt aus wenn
die Wandlung fertig ist, oder? Sollte die CPU Auslastung deutlich
reduzieren. Ich trigger immer mittels Timer in einem bestimmten
Zeitraster eine Messung und schalte den MUX nach Belieben um, wenn eine
Messung fertig ist.
Hat man DMA kann man natürlich Freerun machen, der DMA schaufelt dann
die Werte in dieVariable ohne CPU Auslastung.
Ingo
Hi
>Du löst sicherlich keinen Interrupt aus wenn>die Wandlung fertig ist, oder?
Selbstverständlich. Ich hatte schon weiter oben geschrieben, das die
CPU-Belastung bei den ADC-Einstellungen von Andi relativ gering ist. Bei
angenommenen 100 Takten für den Interrupt sind es ca. 6% im
Free-Running-Mode. Mit Autotrigger durch einen Timer lässt sich diese
fast beliebig verkleinern. Also wo ist das Problem?
MfG Spess
Ingo schrieb:> Weil es unsinnig ist, einen Interrupt auszulösen wenn die Wandlung> fertig ist, wenn der ADC im Freerun ist.
Darum haben sich die Atmel Leute auch gedacht:
Ach komm', ist zwar unsinnig aber lass uns doch trotzdem einen Interrupt
Pfad für den ADC einbauen. Dann kann er immer schön einen Interrupt
auslösen wenn eine Konvertierung fertig ist, ist zwar Quatsch weil
Polling viel eleganter ist aber wir machen's doch mal... Haben ja gerade
sowieso nichts zu tun und Chipfläche kostet auch kein Geld...
Und jetzt noch mal deutlich:
GERADE im free-running mode ist der ADC-IRQ Gold wert, erlaubt er doch
zum Beispiel mit geringstmöglichem Overhead die maximale Anzahl an
Konvertierungen reinzuholen und zu verarbeiten während die Hauptroutine
nur minimal gebremst wird.
Hi
>Wie ihr meint.
Du machst es doch auch so:
>Ich trigger immer mittels Timer in einem bestimmten>Zeitraster eine Messung und schalte den MUX nach Belieben um, wenn eine>Messung fertig ist.
Das ist das gleiche wie Autotrigger. Und Autotrigger über einen Timer
benutze ich auch seit dem die AVRs das haben. Bei den ersten Geräten
wurde ein ATMega103 eingesetzt der nur Free-Runnig-Mode kannte.
Und den ADC kann man auch abschalten, wenn man ihn nicht braucht. Dann
gibt es auch keinen Interrupt.
MfG Spess