Hallo, wie ruft man innerhalb einer ISR eine andere Funktion auf, so dass mit dem Funktionsaufruf die ISR auch wieder geschlossen wird? z.B. empfange ich über die serielle Schnittstelle neue Daten -> interrupt ISR wird aufgerufen -> weitere funktion soll innerhalb der ISR aufgerufen werdne, um die empfangenen Daten zu verarbeiten. Gleichzeitig soll mit diesem Aufruf die ISR geschlossen werden, so dass neue Interrupts erkannt werden können. Oder geht man mit Interrupts etc. anders um? Bernd
Ja, man geht anders um. Lese in der ISR die Daten vom UART und speichere sie ine einem buffer, und setzte eine Boolean fuer das main, sodass der buffer naechstens bearbeitet wird.
Bernd Schuster wrote: > Hallo, > > wie ruft man innerhalb einer ISR eine andere Funktion auf, so dass mit > dem Funktionsaufruf die ISR auch wieder geschlossen wird? Du machst den Aufruf. Wenn die Funktion zurückkommt, kommt sie wieder im ISR raus, der dann seinerseits irgendwann zu Ende ist. Und erst dann ist die ISR Abarbeitung abgeschlossen. > > z.B. empfange ich über die serielle Schnittstelle neue Daten -> > interrupt ISR wird aufgerufen -> weitere funktion soll innerhalb der ISR > aufgerufen werdne, um die empfangenen Daten zu verarbeiten. Wie aufwändig ist diese 'weitere Verarbeitung'. Generell: In einer ISR so viel wie unbedingt notwendig aber so wenig wie möglich machen. > Gleichzeitig > soll mit diesem Aufruf die ISR geschlossen werden, so dass neue > Interrupts erkannt werden können. Das könnte man zwar machen, einfach sei() aufrufen. Wenn man aber nicht genau weis was man tut, kann das in Chaos ausarten. > Oder geht man mit Interrupts etc. anders um? Ja. Eine übliche Vorgehensweise sind Jobflags. In main() gibt es die Hauptendlosschleife. Die überprüft ständig, ob ein Jobflag gesetzt ist und bearbeitet einen Job, wenn dem so ist. Die ISR sichert einfach nur die dazu notwendigen Daten in globalen Variablen und setzt das entsprechende Jobflag. Bsp. Du hast einen ADC im System. Der ADC ist so eingestellt, dass er bei fertigem Wandlerergebnis einen Interrupt auslöst. Das Wandlerergebnis muss jetzt beispielseweise noch umgerechnet werden und auf einem LCD ausgegeben werden. Sowohl Umrechnung als auch Ausgabe beanspruchen relativ viel Zeit und du möchtest die Interrupts während dieser Zeit nicht gesperrt haben (weil zb eine UART ebenfalls über Interrupts empfängt). Also setzt der ADC-Interrupt nur ein entsprechendes Jobflag und sichert das Wandlerergebnis. Mehr nicht
1 | volatile uint16_t ADC_Result; |
2 | volatile uint8_t ADC_Job; |
3 | |
4 | ISR( ADC_FIN_vect ) // keine Ahnung ob der wirklich so heist |
5 | {
|
6 | ADC_Result = ADCW; |
7 | ADC_Job = 1; |
8 | }
|
Die Haupschleife in main wartet ständig darauf, dass ADC_Job irgendwann 1 wird und bearbeitet dann den Messwert
1 | int main() |
2 | {
|
3 | |
4 | ...
|
5 | ADC_Job = 0; |
6 | |
7 | while( 1 ) { |
8 | ...
|
9 | |
10 | if( ADC_Job == 1 ) { |
11 | Result = KomplizierteBerechnung( ADC_result ); |
12 | Put_On_LCD( Result ); |
13 | |
14 | ADC_Job = 0; |
15 | }
|
16 | |
17 | ...
|
18 | }
|
19 | }
|
Auf diese Art sind die Interrupts nur minimal gesperrt. Der µC bearbeitet das ISR Ergebnis, sobald Zeit dazu ist.
>Oder geht man mit Interrupts etc. anders um?
Geht man. Das Wesen einer ISR ist, daß sie völlig losgelöst vom üblichen
Programmablauf abläuft.
Die ISR sollte nur die wirklich zeitkritischen Dinge tun, also z.B.
Daten aus dem Hardware-Buffer abholen, dann z.B. ein Flag setzen, und
sich beenden. Das Flag und die Daten werden dann im normalen
Programmablauf ausgewertet.
Oliver
Ich setzte in meinen ISRs nur Flags/Variablen, wie irgenwo in main() angefragt werden. bsp: main(void) { while (1) { if (flag_isr == gesetzt) { flag_isr = zurückSetzen; tuWasWegenISR(); } } } ISR(INT0oderSonstwas) { flag_isr = gesetzt; }
Du kannst in ISRs normale Funktionen aufrufen. Vorbehaltlich notwendiger Sonderbehandlung solcher Funktionen bei Maschinen, die sich wie beispielsweise PIC16 und 8051 mit Stacks schwer tun und daher u.U. irgendwas wie "reentrant" benötigen. Allerdings wird diese Funktion ganz normal in die ISR zurückkehren und erst von dort aus geht es ins normale Programm zurück. Direkt aus der aufgerufenen Funktion henaus geht das nicht.
ok - das hab ich soweit verstanden... d.h. man pollt auch bei der Verwendung von Interrupts - ob ein bestimmtes Ereignis eingetroffen ist (in diesem Fall so ein Flag gesetzt wurde). D.h. aber im Umkehrschluss doch auch, dass man so einen Interrupt gar nicht installieren muss, wenn die Berechnungen im Anschluss eh durch Polling + Flagbit setzen durchgeführt werden, sondern man z.B. einfach den Inhalt eines Registers ständig pollt (wenn das möglich ist). Bernd
Bernd Schuster wrote: > D.h. aber im Umkehrschluss doch auch, dass man so einen Interrupt gar > nicht installieren muss, wenn die Berechnungen im Anschluss eh durch > Polling + Flagbit setzen durchgeführt werden, sondern man z.B. einfach > den Inhalt eines Registers ständig pollt (wenn das möglich ist). Korrekt. Wenn der Interrupt wirklich nur ein Flag setzt, dann kann man sich das ersparen und statt dessen das entsprechende Interrupt-Flag abfragen (und ggf. zurücksetzen). Meist passiert in Interrupts jedoch ein kleines bischen mehr als das.
d.h. im ISR selbst macht man - Flag setzen für main Routine - Buffer (z.B. USART) in internen / externen Speicher schreiben - Register zurücksetzen damit neue Interrupts empfangen werden können welche Sachen werden ansonsten noch in einer ISR erledigt? Bernd
Andreas Kaiser wrote: > Bernd Schuster wrote: > >> D.h. aber im Umkehrschluss doch auch, dass man so einen Interrupt gar >> nicht installieren muss, wenn die Berechnungen im Anschluss eh durch >> Polling + Flagbit setzen durchgeführt werden, sondern man z.B. einfach >> den Inhalt eines Registers ständig pollt (wenn das möglich ist). > > Korrekt. Wenn der Interrupt wirklich nur ein Flag setzt, dann kann man > sich das ersparen und statt dessen das entsprechende Interrupt-Flag > abfragen (und ggf. zurücksetzen). > > Meist passiert in Interrupts jedoch ein kleines bischen mehr als das. Zb. In Bernds Fall könnte der UART Receive Interrupt das empfangene Zeichen in einem Buffer hinten anfügen und bei Empfang eines '\n' das Jobflag setzen um der main() Loop anzuzeigen, dass eine komplette Zeile empfangen wurde. In main() braucht man sich dadurch nicht mehr mit einzelnen Zeichen herumzuschlagen, sondern hat einen kompletten String zur Verfügung. Das Job Flag zeigt dann den exakten Zeitpunkt an, an dem dieser String (die empfangene Zeile) zur Verarbeitung ertig empfangen wurde.
1 | char ReceiveBuffer[80]; |
2 | uint8_t ReceiveCnt; |
3 | uint8_t UART_Job; |
4 | |
5 | ISR( UART_RX_vect ) |
6 | {
|
7 | char c = UDR; |
8 | |
9 | if( c == '\n' ) { |
10 | ReceiveBuffer[ReceiveCnt] = '\0'; |
11 | UART_Job = 1; |
12 | }
|
13 | else
|
14 | ReceiveBuffer[ ReceiveCnt++ ] = c; |
15 | }
|
16 | |
17 | int main() |
18 | {
|
19 | ....
|
20 | |
21 | while( 1 ) { |
22 | if( UART_Job == 1 ) { // eine komplette Zeile wurde |
23 | // von der UART empfangen
|
24 | .... zb. |
25 | strcpy( Command, ReceiveBuffer ); // damit die UART gleich wieder |
26 | // empfangen kann
|
27 | UART_Job = 0; |
28 | |
29 | if( strcmp( Command, "Help" ) == 0 ) { |
30 | // bearbeite Help Kommando
|
31 | }
|
32 | ....
|
33 | }
|
34 | }
|
35 | }
|
Und den berühmten buffer overflow gibt's gratis. Eine Überprüfung von ReceiveCnt sollte schon noch rein.
Andreas Kaiser wrote: > Und den berühmten buffer overflow gibt's gratis. Eine Überprüfung von > ReceiveCnt sollte schon noch rein. :-) Ist schon klar (mir zumindest). Die Fehlerbehandlung hab ich absichtlich weggelassen um das wesentliche, den Zusammenhang zwischen Arbeit in der ISR und Arbeit in der main(), nicht zu verschleiern.
ist es eigenltich eleganter mit Hilfe von define ein solches Flag zu setzen, oder mit einer unsigned char variable? Die Fehlererkennung gehört dann auch in die ISR - und die Bearbeitung der Fehler je nach Größe in die ISR oder in eine andere Funktion, die von main aufgerufen wird. Bernd
Spring doch einfach, nachdem die Funktion zurückkommt, aus dem ISR raus (vorher Variablen restoren, wenn nötig - beim PIC zumindest immer nötig für W und STATUS). Oder statt einer Funktion aufzurufen, kannste auch mit GOTO hinspringen - dann gibt's keine Probleme mit dem direkten Verlassen der ISR direkt in der "Funktion" Du kannst auch in der Funktionen sowas wie einen RC zurückgeben (beim PIC in assembler z.B. retlw x), so daß im ISR dann abhängig vom RC der Funktion aus dem ISR rausgesprungen werden kann, oder auch nicht. In der Funktion selbst einfach den ISR verlassen würde ich nicht, denn ich glaube, dann bleibt der Stack so stehen, wie er im ISR als letztes gesetzt wurde beim Funktionsaufruf (ich glaube, beim PIC wird der Stackpointer nicht gesichert). Wenn dann irgendwann in der main irgendwo zurückgesprungen wird (weil z.B. der ISR mitten in einer von der main aufgerufenen anderen Funktion reinfunkte), landet der Return dann nach dem Funktionsaufruf im ISR statt irgendwo im main.
Bernd Schuster wrote: > ist es eigenltich eleganter mit Hilfe von define ein solches Flag zu > setzen, oder mit einer unsigned char variable? Weder noch. #define und die Deklaration von Variablen sind 2 völlig verschiedene Dinge. Die Frage ist genausowenig beantwortbar, wie die Frage ob bei Halsweh Apfelsaft oder ein Atomkraftwerk besser ist :-)
JensG wrote: > Spring doch einfach, nachdem die Funktion zurückkommt, aus dem ISR raus > (vorher Variablen restoren, wenn nötig - beim PIC zumindest immer nötig > für W und STATUS). Deine Empfehlungen fallen so ziemlich alle in die Kategorie: "Wie schiesse ich mir selbst möglichst elegant ins eigene Bein"
Karl heinz Buchegger wrote: > int main() > { > ADC_Job = 0; > > while( 1 ) { > ... > > if( ADC_Job == 1 ) { > Result = KomplizierteBerechnung( ADC_result ); > Put_On_LCD( Result ); > > ADC_Job = 0; > } > } > } Erm... da koennen Dir allerdings dann wegen Synchronisationsprobleme Ereignisse verloren gehen, das ist Dir klar...?
Michael G. wrote: > Karl heinz Buchegger wrote: >> int main() >> { >> ADC_Job = 0; >> >> while( 1 ) { >> ... >> >> if( ADC_Job == 1 ) { >> Result = KomplizierteBerechnung( ADC_result ); >> Put_On_LCD( Result ); >> >> ADC_Job = 0; >> } >> } >> } > > Erm... da koennen Dir allerdings dann wegen Synchronisationsprobleme > Ereignisse verloren gehen, das ist Dir klar...? Ja, ist mir klar. (Ich hoffe dem Bernd auch). OK. Etwas weiter ausgeholt: Man muss natürlich darauf achten, dass das JobFlag nicht zulange blockiert wird. Weiters sollte die ISR mglw. auch noch prüfen, ob der zuletzt angestossene Job schon bearbeitet wurde. ISR( ADC_fertig ) { if( ADC_Job == 0 ) { ADC_Result = ADCW; ADC_Job = 1; } } int main() { ADC_Job = 0; while( 1 ) { ... if( ADC_Job == 1 ) { // den ADC Job möglichst schnell wieder freigeben Tmp = ADC_Result; ADC_Job = 0; Result = KomplizierteBerechnung( Tmp ); Put_On_LCD( Result ); } } } Auch jetzt können immer noch Ereignisse verloren gehen. Das liegt dann aber daran, dass die Auswertung der Daten zu lange dauert. Dagegen kannst du dann allerdings nichts machen. Wenn die Daten in Packeten ankommen, könnte man sie in der ISR in eine FIFO stellen und in der main() sukzessive abarbeiten. Aber letztendlich wird es immer Fälle geben, wo der µC einfach mit der anfallenden Arbeit nicht mehr mitkommt und Ereignisse unter den Tisch fallen. Sowas muss im Einzelfall entschieden werden.
Hallo Karl, das war jetzt eher weniger gemeint, sondern dass der Zugriff auf die gemeinsam genutzte Variable nicht synchronisiert ist. Tritt der Interrupt nun direkt nach Put_On_LCD() auf, setzt der ISR sein Flag auf eins und will damit signalisieren dass es was zu tun gibt. Dummerweise wird das Hauptprogramm aber mit dem Befehl ADC_Job = 0; fortgesetzt und Dein Flag ist geloescht. Das Problem dass die Bearbeitung zu lange dauert und Interrupts somit nicht mehr abgearbeitet werden koennen ist nochmal ein ganz anderes. Du wirst hier den Part, wo das Flag im Hauptprogramm modifiziert wird, atomar machen muessen. Die ISRs selber sind per Std ja schon nicht unterbrechbar, das Hauptprogramm allerdings schon. Das hilft Dir, dass keine Ereignisse verloren gehen, wenn Zeit vorhanden ist, sie abzuarbeiten... Michael
@ Karl heinz Buchegger (kbuchegg) >Deine Empfehlungen fallen so ziemlich alle in die Kategorie: >"Wie schiesse ich mir selbst möglichst elegant ins eigene Bein" Begründung ?
Du solltest in ner ISR ueberhaupt keine Funktionen aufrufen, wenn es nicht einen verdammt guten Grund dafuer gibt...
>Du wirst hier den Part, wo das Flag im Hauptprogramm modifiziert wird, >atomar machen muessen. @Michael G. Hättest Du ein Beispiel?
@ Michael G. (linuxgeek) > Du solltest in ner ISR ueberhaupt keine Funktionen aufrufen, wenn es > nicht einen verdammt guten Grund dafuer gibt... sorry, aber das klingt mir immer noch nur nach rein formeller Begründung, ohne jegliche fachliche Begründung. Das einzige, worauf man aufpassen muß, ist das Problem, daß ohne weiteres sowas nicht reentrant ist, was zum Überschreiben von Variablen führen kann. Das ist aber für mich jetzt erstmal kein Showstopper, denn für ISR's benutze ich eigene Funktionen. Und die erlaubt Stacktiefe muß man im Blick behalten. Andere fachliche Begründungen? Das kann aber eigentlich Karl heinz Buchegger (kbuchegg) wohl nicht gemeint haben, denn um 06.11.2007 14:49 hat er noch nix gegen Funktionen in ISR's gehabt . Oder doch ?
Nein. Aber ich habe ein Problem damit, wenn anstelle von Funktions- aufrufen mit GOTO gearbeitet wird um in Funktionen hinzukommen, nur damit der Stack (hoffentlich) so steht, dass aus der Funktion heraus die ISR beendet werden kann. Dazu dann noch irgendwelche Registermanipulationen (W bzw. STATUS sind doch irgendwelche PIC Register, oder nicht), und das Chaos ist fertig. Meine Meinung: In dem Moment, in dem man sich Gedanken darüber machen muss, ob der Stack jetzt noch richtig ist, ist irgendetwas faul im Staate Dänemark.
Michael G. wrote: > Hallo Karl, > > das war jetzt eher weniger gemeint, sondern dass der Zugriff auf die > gemeinsam genutzte Variable nicht synchronisiert ist. Tritt der > Interrupt nun direkt nach Put_On_LCD() auf, setzt der ISR sein Flag auf > eins und will damit signalisieren dass es was zu tun gibt. Dummerweise > wird das Hauptprogramm aber mit dem Befehl ADC_Job = 0; fortgesetzt und > Dein Flag ist geloescht. Da hast du recht :-) Daran hab ich jetzt wiederrum nicht gedacht. > Du wirst hier den Part, wo das Flag im Hauptprogramm modifiziert wird, > atomar machen muessen. Jep. Für Torben: if( ADC_Job == 1 ) { cli(); Tmp = ADC_Result; ADC_Job = 0; sei(); .... und weiter gehts }
Michael G. wrote: > Du solltest in ner ISR ueberhaupt keine Funktionen aufrufen, wenn es > nicht einen verdammt guten Grund dafuer gibt... Das halte ich in dieser Konsequenz für ziemlich übertrieben. Man sollte da zwar keine grösseren lang andauernden Aktivitäten veranstalten, aber auf saubere Programmstruktur muss man deshalb nicht verzichten. Wenn man also beispielsweise ein Funktion hat, die ohne zu blockieren ein Byte in den Empfangsspeicher einer seriellen Schnitstelle steckt, warum diese nicht verwenden? Und wenn man es mit Controllern ohne vektorierte Interrupts zu tun hat (z.B. PIC18, ADuC7000), dann geht das kaum anders.
@ Karl heinz Buchegger (kbuchegg) deswegen hatte ich das Wörtchen Funktion in Gänsefüßchen gesetzt, weil ich nicht wirklich eine Funktion meinte, sondern ganz einfach ein Stück Code, wo ich hinspringe, und aus dem ich dann einfach mich aus der ISR ausklinken kann. Stacks manipulieren will ich nicht, und braucht man nicht. W und STATUS sind PIC register - das ist richtig, und die muß man sichern, wenn man in die ISR springt. Das ist einfach eine Notwendigkeit beim PIC. Ist also keine Registermanipulation in Hacker-Manier. Zumindest, wenn man die Sache in Assembler schreibt - wie es bei C aussieht, weiß ich nicht, ob da der Compiler das irgendwie gleich automatisch mit erledigt (um welchen µC/Sprache es geht, ging aus der Initialbeschreibung nicht hervor) Abgesehen vom Mißverständnis, was das Wörtchen "Funktion" + GOTO verursachte, sehe ich meine Vorschläge nicht gerade als Kamikaze-Vorschlag an.
Karl heinz Buchegger wrote: > Da hast du recht :-) > Daran hab ich jetzt wiederrum nicht gedacht. > >> Du wirst hier den Part, wo das Flag im Hauptprogramm modifiziert wird, >> atomar machen muessen. > > Jep. > > Für Torben: > > if( ADC_Job == 1 ) { > cli(); > Tmp = ADC_Result; > ADC_Job = 0; > sei(); > .... und weiter gehts > } Genau so isses ;) Das mit den Funktionsaufrufen ist vielleicht etwas uebertrieben aber Du musst schon aufpassen, wenn man bekannte Gewaesser betritt duerfte es ja kein Problem sein aber so kurz wie moeglich halten, das wurde aber ja schon gesagt. Mit Interrupt-Programmierung handelt man sich typische Multiprogramming-Probleme ein, selbst auf ner Single-Tasking-Architektur ,) Michael
welche Funktion haben die Funktionen cli() und sei()? Hab die jetzt schon öfter gesehen, aber immer ohne jeglicher Defintion im Programmcode. Bernd
Interrupts global ein bzw. ausschalten. Da wird letztlich ein Bit im GICR-Register gesetzt... Datenblaetter koennen bei sowas recht aufschlussreich sein btw ,)
Danke, aber wieso wird hier mit cli(); und sei() gearbeitet? Müssen alle Interrups gesperrt werden oder reicht es nicht nur den ADC Interrupt abzuschalten? Nochmal zum eigentlichen Thema zurück, man sollte Funktionsansprünge vermeiden, weil das auch einige Instructions mehr sind (sprungbefehle)
Du hast Recht im Prinzip wird es reichen den ADC-Interrupt auszuschalten also dort wo schreibend auf das Korpus delikti zugegriffen wird. Ich weiss jetzt ad-hoc nur nicht wie man einzelne Ints abschaltet und sicherstellen kann dass sie noch vorgemerkt werden, muesstest mal ins Datenblatt schauen... oder es weiss jemand hier ;) Wenn Du aber sowieso nur einen Interrupt-Handler installiert hast isses Hund wie Katz...
@ Torben >Nochmal zum eigentlichen Thema zurück, man sollte Funktionsansprünge >vermeiden, weil das auch einige Instructions mehr sind (sprungbefehle) grundsätzlich stimmt es, daß dies ein paar zusätzliche Instructions bedeutet, die dabei ausgeführt werden, aber deswegen würde ich nie die Strukturierung meines Programms einfach so hintenanstellen (vor allem, wenn ich dabei nur den Call und den Return der Funktion einsparen würde) Funktionssprünge bedeuten schließlich auch kompakteren Code, und effizienterer Speicherverbrauch. Wenn mir wirklich aus Timinggründen die paar zusätzlich ausgeführten Instructions weh tun, dann sollte man vielleicht doch langsam auf einen etwas performanteren Prozessor ausweichen.
Michael G. wrote: > Du hast Recht im Prinzip wird es reichen den ADC-Interrupt auszuschalten > also dort wo schreibend auf das Korpus delikti zugegriffen wird. Ich > weiss jetzt ad-hoc nur nicht wie man einzelne Ints abschaltet und > sicherstellen kann dass sie noch vorgemerkt werden, muesstest mal ins > Datenblatt schauen... oder es weiss jemand hier ;) Ich denke für die Vormerkung muss nichts spezielles gemacht werden. In den Statusregistern gibt es ja immer ein Bit welches anzeigt, dass das entsprechende Ereignis aufgetreten ist. Den spezifischen Interrupt abschalten wäre natürlich schöner :-) Allerdings ist mir das 'zuviel Aufwand' im Vergleich mit sei()/cli() Und die paar Takte Verzögerung, wenn es denn wirklich mal zu einer zeitlichen Überschneidung kommen sollte, habe ich normalerweise allemal.
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.