Forum: Compiler & IDEs Funktionsaufruf aus ISR


von Peter Q. (peq-man)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe leider keine Loesung fuer mein Problem im Forum gefunden.
Sollte ich etwas uebersehen haben, sorry fuer den doppelten Post!
Hier zu meinem Problem:

Wenn ich aus einer ISR in meinem Programm (siehe Anhang) eine Funktion
get_adc aufrufen moechte, bekomme ich vom Compiler (gestartet mit
AVR-Studio, Versionen siehe Anhang) immer folgende Fehlermeldung:

Build started 16.5.2006 at 00:44:08
avr-gcc.exe  -mmcu=atmega32 -Wall -gdwarf-2 -O0 -MD -MP -MT
Drehzahlregler.o -MF dep/Drehzahlregler.o.d  -c  ../Drehzahlregler.c
../Drehzahlregler.c: In function `__vector_11':
../Drehzahlregler.c:20: warning: implicit declaration of function
`get_adc'
../Drehzahlregler.c: At top level:
../Drehzahlregler.c:28: error: conflicting types for 'get_adc'
../Drehzahlregler.c:20: error: previous implicit declaration of
'get_adc' was here
../Drehzahlregler.c:42: warning: return type of 'main' is not `int'
make: *** [Drehzahlregler.o] Error 1
Build failed with 2 errors and 2 warnings...

Wenn ich die Funktion get_adc aber aus der main aufrufe (siehe
auskommentierte Zeile im Source) und den Aufruf in der ISR
auskommentiere ist der Fehler wieder weg.

Da ich annehme, dass es sich um einen stupiden Anfaengerfehler handelt
moechte ich um Nachsicht fuer die verschwendete Zeit bitten! ;-)

MfG

PeQ-Man

von Matthias B. (fluchtpunkt)


Lesenswert?

du versuchst get_adc aufzurufen bevor es dem Compiler bekannt gemacht
wurde.
Entweder du verschiebst deine Funktion get_adc vor die ISR, oder du
teilst deinem Compiler durch einen Prototypen(=Deklaration) mit dass
diese Funktion existiert und sie diese und jene Parameter besitzt.
Das geht indem du in deinem Fall "unsigned int get_adc(int kanal);"
in deine Datei einfügst, noch vor den Funktionsaufrufen; bei mir finden
sich Funktionsprototypen meist direkt hinter den globalen Variablen.
Alternativ kannst du die Deklaration auch in einer Headerdatei
erledigen.

HTH

von peter dannegger (Gast)


Lesenswert?

Da Du keine 256 Kanäle und auch keinen negativen Kanäle hast, solltest
Du besser:

unsigned int get_adc(unsigned char kanal);

schreiben.

Man muß ja den Compiler nicht unnötig zwingen, toten Code zu erzeugen.


Peter

von johnny.m (Gast)


Lesenswert?

Funktionsaufrufe aus ISRs sind schlechter Programmierstil, v.a. dann,
wenn sie noch Warteschleifen enthalten. Du willst das Programm ja
vielleicht mal erweitern, und dann kanns Probleme geben. Grundsätzlich:
ISRs so kurz wie möglich.

Da Du anscheinend mit nem Mega32 arbeitest: Machs Dir doch nicht
unnötig kompliziert. Beschäftige Dich mal mit den ADC Auto
Trigger-Quellen (Einstellung über die Bits ADTSx in SFIOR) und nimm
Timer0 Overflow als Triggerquelle. Dann den ADC-Interrupt aktivieren
und fast alles läuft automatisch. Musst nur dran denken: Wenn Du den
Overflow Interrupt dann deaktivierst (eigentlich brauchst Du dann
nämlich nur noch den ADC-Interrupt), musst Du das Overflow-Flag in der
ADC-ISR manuell löschen, sonst wandelt der nur einmal.

Gruß

Johnny

von Peter Q. (peq-man)


Lesenswert?

Hallo!

Erstmal danke fuer die schnellen Antworten!

@Matthias Bauch: Danke, hab da wohl gestern den Wald vor lauter Baeumen
nicht gesehen... Jetzt geht es ;-)

@peter dannegger: Das ist ein gutes Argument, soweit hatte ich noch gar
nicht gedacht (stehe da noch ziemlich an den Anfaengen...). Ich habe das
jetzt geandert.

@johnny.m:
Das das mit dem Funktionsaufruf aus der ISR nicht gerade elegant ist,
ist mir bewusst. Das mit dem Timing fasse ich als nicht so kritisch
auf, da ich in der ISR 2 Messungen machen will mit jeweils einer Dauer
von 0,1ms. Oder ist das dann schon zu viel?

Der Workaround den ich mir ueberlegt habe, dass ich eine Flag-Variable
in der ISR nutze und in der main nach diesem Flag polle, erscheint mir
aus energietechnischer Sicht nicht sehr gut. Ich hatte vor den µC
schlafen zu schicken und nur auf den Interrupt zu reagieren. Wenn ich
polle is er ja quasi immer beschaeftigt oder sehe ich das falsch?

Den ADC automatisch anwerfen zu lassen finde ich eine gute Idee. ICh
sehe jedoch keine Moeglichkeit da bei jedem Overflow dann spaeter mal 2
Messungen von zwei verschiedenen Kanaelen zu machenn. Oder?

MfG

PeQ-Man

von Wolfram (Gast)


Lesenswert?

>Das das mit dem Funktionsaufruf aus der ISR nicht gerade elegant ist,
>ist mir bewusst
du solltest dir auch bewusst sein, dass das eine Menge Ram auf dem
Stack verbraucht, da alle Register gesichert werden.
>Ich hatte vor den µC schlafen zu schicken und nur auf den Interrupt zu
>reagieren.
und bei einem Interrupt, wacht er auf und macht dann in der Mainroutine
weiter, wo du die Flags überprüfst und dich wieder schlafen legst.

mainroutine:
1:flags pollen
2:tue was
3.leg dich schalfen
zu 1.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Ich hatte vor den µC schlafen zu schicken und nur auf den Interrupt
> zu reagieren.

Dann ist die Variante, die Messung anzuwerfen, den Controller
schlafen zu legen, und den ADC mit einem Interrupt den Controller
wieder aufzuwecken sicher ohnehin die sinnvollste.

> ICh sehe jedoch keine Moeglichkeit da bei jedem Overflow dann
> spaeter mal 2 Messungen von zwei verschiedenen Kanaelen zu
> machenn. Oder?

Jede Messung braucht sowieso ihre Zeit, also ,,gleichzeitig''
geht's
ohnehin nicht.  Entweder schiebst du die erste Messung bei deinem
Overflow an, legst den Controller schlafen, speicherst nach dem Ende
der Wandlung den Wert ab und schiebst die zweite Messung gleichermaßen
an.  Oder aber, du misst einfach kontinuierlich abwechselnd beide
Kanäle nach diesem Prinzip, und spuckst beim Overflow-Interrupt sofort
die letzten beiden Messwerte aus.

von Peter Q. (peq-man)


Lesenswert?

@Wolfram:

>mainroutine:
>1:flags pollen
>2:tue was
>3.leg dich schalfen
>zu 1.

Wie kann ich dann aus dem Schlaf wieder zu 1 zurueckspringen? Bin da
noch recht neu auf dem Gebiet, wie man sicher schon gemerkt hat ;-)

@Jörg Wunsch:

>Jede Messung braucht sowieso ihre Zeit, also ,,gleichzeitig''
>geht's ohnehin nicht.

Das hatte ich wohl falsch ausgedrueckt. Ich wollte die Messungen in der
ISR nacheinander anwerfen. Ich ja schliesslich zwischendrin den Kanal
wecheln.

>Entweder schiebst du die erste Messung bei deinem
>Overflow an, legst den Controller schlafen, speicherst nach dem Ende
>der Wandlung den Wert ab und schiebst die zweite Messung
gleichermaßen
>an.  Oder aber, du misst einfach kontinuierlich abwechselnd beide
>Kanäle nach diesem Prinzip, und spuckst beim Overflow-Interrupt
sofort
>die letzten beiden Messwerte aus.

Das klingt recht vernuenftig. ;-) Aber wie schalte ich dann zwischen
den Overflows zwischen den Kanaelen um? Die Messung wird ja dann
automatisch angeworfen?! Kann ich das dann einfach ueber eine parallele
ISR in der ich jeweils den Kanal wechsle machen?

MfG

PeQ-Man

von Michael Wilhelm (Gast)


Lesenswert?

Da hilft eine Bitvariablr. Ist sie 0 wird Messwert 1 erfasst und die
Bitvariable auf 1 gesetzt. Ist die Bitvariable 1 wird Messwert 2
erfasst und die Variable wird auf 0 gesetzt. So wird jedesmal zwischen
Messwert 1 und 2 unterschieden. Zwischen den Overflows wird gar nichts
gemacht, sondern nach prüfen der Bitvariable wird das ADMUX-Register
gesetzt. Wenn der ADC-ISR kommt, wird anhand der Bitvariable deutlich,
welcher Wert gewandelt wurde. Danach zwischen 0 und 1 unterscheiden,
Wandlung starten und schlafen.

MW

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Aber wie schalte ich dann zwischen
> den Overflows zwischen den Kanaelen um?

Der ADC-Interrupt vermeldet dir das Ende der Messung (und
weckt den Prozessor aus dem Schlaf auf).  Danach speicherst
du den aktuellen Messwert in einer Variablen ab (aus der
ihn der Timer-Interrupt dann abholen kann), wechselst den
Kanal, schiebst eine neue Messung an und legst den Prozessor
wieder schlafen.

Guck mal ins Datenblatt, es gibt eine minimale Zeit, die man
verstreichen lassen muss nach dem Beschreiben des ADMUX bis
zum Anwerfen der neuen Messung, sonst wird die Messung noch
auf dem alten Kanal angeschoben.  Im Prinzip kannst du die
Umschaltung auf den anderen Kanal auch sofort nach dem
Aktivieren der Messung anwerfen (also noch vor dem Schlafen-
legen), da sie ohnehin bis zum Ende der laufenden Messung
verzögert wird.

von johnny.m (Gast)


Lesenswert?

...eine Bitvariable brauchste eigentlich nicht. Einfach nach jeder
Messung abfragen, was in ADMUX grad drinsteht, dann weißt Du, welchen
Kanal Du grad gemessen hast. Dann das Ergebnis in der entsprechenden
Variablen speichern und den Kanal umschalten. Nach der nächsten Messung
das gleiche Spiel, nur umgekehrt...

von Peter Q. (peq-man)


Angehängte Dateien:

Lesenswert?

Hallo,

also nochmals vielen Dank fuer eure Hilfe!
Ich habe jetzt mal versucht den ADC ueber den TIMER0_OVF zu starten und
die Auswertung in der ISR(ADC_vect) zu machen. Ich habe das schon so
weit wie moeglich abgespeckt um meinen Fehler zu finden nur leider
scheint der ADC nichts zu tun?!

MfG

PeQ-Man

PS: Ich hoffe, das wird jetzt nicht etwas zu off-topic fuer diese
Ueberschrift?!

von Wolfram (Gast)


Lesenswert?

Der Programmcode liest sich gut weiter so,
Wo ist deine Timer ISR Routine?

von johnny.m (Gast)


Lesenswert?

Ich hatte doch oben geschrieben: Wenn Du für den Timer Overflow den
Interrupt nicht aktivierst und die entsprechende ISR weglässt, musst Du
in der ADC-ISR das Overflow Flag manuell löschen. Sonst wandelt der nur
ein einziges Mal und macht dann gar nix mehr. Also: Irgendwo in der
ADC-ISR 'TIFR = 1 << TOV0;' reinschreiben.

von johnny.m (Gast)


Lesenswert?

Hab grad noch gesehen, dass Du den Overflow Interrupt aktiviert hast,
den ADC-Interrupt jedoch nicht! Das haut dann natürlich daneben.
Entweder den Overflow Interrupt deaktivieren und das Flag manuell
löschen (wie oben beschrieben) oder den Interrupt aktivieren und eine
(evtl. leere) Overflow-ISR deklarieren, wobei letzteres allerdings
Laufzeit kostet (was in Deinem Programm allerdings wahrscheinlich
nichts ausmacht...). Und Du musst natürlich den ADC-Interrupt
aktivieren, sonst passiert gar nix...

von Peter Q. (peq-man)


Lesenswert?

Hallo,

also jetzt funktioniert alles. Vielen Dank nochmal!

Ich bin aber im Handbuch noch auf einen Satz gestossne den ich nicht so
ganz verstehe (ATMEGA32 Handbuch, Seite 210, "Starting a
Conversion"):
[...]When a positive edge occurs on the selected trigger signal, the
ADC prescaler is reset and a conversion is started.[...]

Muss ich das so verstehen, dass ich auch nach jeder Messung den
Prescaler neu starten muss oder ist das nur von Belang, wenn ich einen
externen Trigger benutze?

MfG

PeQ-Man

von johnny.m (Gast)


Lesenswert?

Das bezieht sich nur auf das externe Triggersignal. Heißt auf deutsch:
Wenn eine positive Flanke auf dem Triggersignal auftritt (heißt z.B.
bei Verwendung eines Timer Overflow oder Compare als Autotrigger, dass
das entsprechende Flag von 0 auf 1 wechselt, also gesetzt wird), wird
der Prescaler zurückgesetzt und ohne Rücksicht auf Verluste wird eine
neue Wandlung gestartet. Der Prescaler selbst läuft eigentlich immer
durch. Wenn Du die Wandlung manuell startest (durch Setzen von ADSC)
passiert genau dasselbe.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ich halte das für ein Implementierungsdetail.  Du hast ja sowieso
keine Möglichkeit, den Prescaler direkt zurückzusetzen.

von Peter Q. (peq-man)


Lesenswert?

Hallo,

man, ihr seit ja schneller als mein Mailintervall ;-)

Es handelte sich hier wohl eher um ein Missverstaendnis meinerseits.
Ich dachte der Prescaler an sich is nur das Teilverhaeltnis zum
CPU-Takt.

MfG

PeQ-Man

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.