Moin! Kann mir hier vielleicht irgendwer sagen, ob es irgendwo Konventionen bzw. Codebeispiele zum sauberen/übersichtlichen programmieren mit Interrupts anzusehen gibt? Ich habe immer das Problem das ich zwar kleine Programme schreiben kann, welche auch funktionieren, jedoch habe ich immer das Problem das ich bei steigenden Anforderungen teilweise selbst nicht mehr ohne weiteres durch meinen Code durchblicke. Es kommen je nach Anzahl der ISRs "Massen" an globalen Variablen vor und wenn ich Funktionen schreibe, die mit mehreren Dingen zusammenspielen (Beispiel: Timer und Externer Interrupt für Software-Tasterentprellung), bin ich mir auch immer unsicher, wohin ich die entsprechenden Funktionen auslagern soll. Ich würde mich freuen wenn mir jemand ganz einfach eine Beispiel-Codesammlung, bzw. Beispielprojekte empfehlen könnte, um sich einfach mal eine gute Programmierweise anzusehen und sich ein bisschen abschauen könnte, wie andere an so etwas rangehen. Grüße Jan
Danke für die schnelle Antwort!!! Denke in Kap5 (Interrupts) sind ein paar hilfreiche Tipps drin. Das andere werde ich mir auch mal in ruhe ansehen. Grüße Jan
Jan S. schrieb: > Es kommen je nach Anzahl der ISRs "Massen" an > globalen Variablen vor und wenn ich Funktionen schreibe, die mit > mehreren Dingen zusammenspielen (Beispiel: Timer und Externer Interrupt > für Software-Tasterentprellung), bin ich mir auch immer unsicher, wohin > ich die entsprechenden Funktionen auslagern soll. Mach für jedes "Thema" ein Modul: - Jede Hardwarekomponente kriegt ihr Modul - Jeder andere sinnvoll abkapselbare Teil auch Variablen werden den zuständigen Modulen zugeordnet, der Zugriff erfolgt nur innerhalb des Moduls, globale Variablen gibt es nicht. Jedes Modul kriegt eine Initialisierung für Daten und evtl. Hardware, sowie weitere Funktionen nach Bedarf. Hier z.B.: Timer, externer Interrupt, Taster-Entprellung, Hauptprogramm
Jürgen S. schrieb: > Variablen werden den zuständigen Modulen zugeordnet, der Zugriff erfolgt > > nur innerhalb des Moduls, globale Variablen gibt es nicht. Und Semaporen?
ich gehe jetzt mal von AVR-Assembler aus Wichtig ist es den Interrupt-Vectortabelle komplett auszufüllen notfalls mit einen Rücksprung, dann kann selbst ein unerwarteter Interrupt die Programmausführung nicht fehlleiten. Interruptroutinen möglichst kurz halten, also nur das wichtigste darin ausführen z.B. einen Wert sichern, die Verarbeitung erfolgt dann später im Programmablauf. Überlegen welche Register von der einzelnen Interrupt-Routine benötigt werden, damit du diese vorher auf den Stack legen und danach wieder vom Stack abholst.
Thomas O. schrieb: > Wichtig ist es den Interrupt-Vectortabelle komplett auszufüllen Das sehe ich zwar auch so, die Profis bevorzugen aber .org-Orgien. > notfalls > mit einen Rücksprung, Habe ich anfangs auch gemacht, inzwischen bevorzuge ich dafür eine Endlosschleife, in die man bei Bedarf noch eine Alarm-Ausgabe einbauen kann. > dann kann selbst ein unerwarteter Interrupt die > Programmausführung nicht fehlleiten. Unerwartete Interrupts sind Programmierfehler. Ein Rücksprung (reti) vertuscht diese, eine Endlosschleife deckt sie auf. Thomas O. schrieb: > Interruptroutinen möglichst kurz halten, also nur das wichtigste darin > ausführen z.B. einen Wert sichern, die Verarbeitung erfolgt dann später > im Programmablauf. Das kommt auf die Umstände an. Gibt es nur einen Interrupt (z.B. Timer), so kann es durchaus sinnvoll sein, den gesamten Code in der ISR abzuarbeiten. Bei mehreren Interrupts stimmt Deine Aussage natürlich. Thomas O. schrieb: > Überlegen Überlegen ist immer richtig! Blind nach Schema F arbeiten ist nicht immer richtig... > welche Register von der einzelnen Interrupt-Routine benötigt > werden, damit du diese vorher auf den Stack legen und danach wieder vom > Stack abholst. Prinzipiell nicht falsch, aber der AVR hat 32 Register. Davon lassen sich ohne weiteres einige exklusiv für ISRs reservieren. Das spart dann viele Push/Pop-Orgien und macht die ISRs recht schnell. Wichtig ist, dass man versteht was man macht und nicht einfach irgendwelche aufgeschnappte Phrasen nachplappert. ...
@Hannes: Irgendwie fühle ich mich immer persönlich von dir angegriffen. "Überlegen" "Blind schreiben" "Phrasen nachplappern" usw. kannst du es nicht ohne? Ich persönlich nutze neben den Registern auch definierte Bytes im SRAM, damit mir möglichst vieles "exclusiven" Register übrig bleiben mit denen es etwas schneller zu arbeiten geht. Aber wenn ein Anfänger hier schon fragt, das sag ich ihm halt die generelle Vorgehensweise wie sie in jedem Leerbuch steht. Also benötigte Register vor der Bearbeitung der Interruptroutine sichern und danach wiederherstellen, zusätzlich schrieb ich ja hier etwas zu überlegen um nicht alles sichern zu müssen. Mir ist auch ein Programm ohne Fehler lieber, aber mein Vorschlag richtete sich gerade an Anfänger. Denn dort wird sicherlich ein Programm das trotz eines kleinen Programmierfehlers (falscher Interrupt freigegeben) richtig läuft einem das nicht läuft vorgezogen. Ich simuliere meine Programme sowieso erst durch und wenn ich da plötzlich auf dem falschen reti stehe sehe ich das dann auch gleich. Ist ja nicht mein Programmierziel aber mal ein falsches Bit gesetzt und schon ist der falsche Interrupt aktiviert. ja solche .ORGien meinte ich, nennt sich das etwa nicht Interruptvector?
1 | .ORG 0x00 |
2 | rjmp RESET ; Reset handler |
3 | reti; rjmp EXT_INT0 ; IRQ0 handler |
4 | reti; rjmp PIN_CHANGE ; Pin change handler |
5 | reti; rjmp TIM1_CMP1A ; Timer1 compare match 1A |
6 | reti; rjmp TIM1_CMP1B ; Timer1 compare match 1B |
7 | reti; rjmp TIM1_OVF ; Timer1 overflow handler |
8 | reti; rjmp TIM0_OVF ; Timer0 overflow handler |
9 | reti; rjmp USI_STRT ; USI Start handler |
10 | reti; rjmp USI_OVF ; USI Overflow handler |
11 | reti; rjmp EE_RDY ; EEPROM Ready handler |
12 | reti; rjmp ANA_COMP ; Analog Comparator handler |
13 | reti; rjmp ADC ; ADC Conversion Handler |
Wenn man Assembler programmiert muss man doch nicht die Nachteile des C-Compilers übernehmen, wie z.B. den Overhead bei den Interruptvektoren. Es mag vielleicht für einen Anfänger Fehler abfangen - ein guter Assemblerprogrammierer aktiviert jedoch keine Interrupts ohne den zugehörigen Vektor zu bearbeiten. Falls doch findet sich der Fehler mit dem Simulator sehr schnell.
Hallo zusammen, mit Interesse verfolge ich die Diskussion, da ich selbst auch sehr daran interessiert bin meinen Stil zu verbessern. Um die Sache zu systematisieren würde ich mir wünschen, dass mal eine Liste mit Nachteilen aufgeführt wird, die Globale Variablen inne haben. Ich selbst bin nämlich auch ein globale-variablen-nutzer :-(
Hi >Wenn man Assembler programmiert muss man doch nicht die Nachteile des >C-Compilers übernehmen, wie z.B. den Overhead bei den Interruptvektoren. >Es mag vielleicht für einen Anfänger Fehler abfangen - ein guter >Assemblerprogrammierer aktiviert jedoch keine Interrupts ohne den >zugehörigen Vektor zu bearbeiten. Falls doch findet sich der Fehler mit >dem Simulator sehr schnell. Nun, so ganz verstehe ich nicht, was du sagen willst. Es sollte klar sein, das ein Anfänger kein guter Assemblerprogrammierer ist. Das mit dem Overhead ist mir auch nicht ganz klar. Ist doch nur C&P, oder schreibst du die IVT jedes mal ab. Den Controller belastet es nicht, da die nicht genutzten Interrupts ja auch nicht angesprungen werden. Sollte der "Anfänger" doch mal einen Interrupt ungewollt aktivieren, so sucht er jedenfalls nicht stundenlang in einem merkwürdig ablaufenden Programm..... Und ist er dann soweit, das er durchschaut was er macht, wird er sowieso seinen eigenen Stil umsetzen. Dann interessieren die hier niedergeschriebenen Worte nicht mehr. Wésentlich wichtiger ist die modulare Programmierung. Arbeiten mit Funktionsblöcken, die helfen, den Überblick nicht zu verlieren. Dazu kann ich auch die Nutzung von Skizzen empfehlen. Wenn nix besseres verfügbar, leisteet Paint auch gute Dienste. Gruß oldmax
im oberen Fall sind das wenige Bytes die im Flash belegt werden Overhead ist da was ganz anderes.
@ balli (Gast) >Um die Sache zu systematisieren würde ich mir wünschen, dass mal eine >Liste mit Nachteilen aufgeführt wird, die Globale Variablen inne haben. Man ist immer bestrebt, möglich viele interne Datails, so auch Variablen, in den Funktion zu platzieren und damit zu "verstecken", damit man nicht von Unmengen an Informationen erschlagen wird. Darum nimmt man so wenig wie möglich globale Variablen. Ein weiteres Problem ist, dass globale Variablen während der gesammten Laufzeit Speicher belegen, während lokale Variablen nur während des Aufrufs einer Funktion Speicher benötigen. Das kann aber ggf. auch ein Vorteil sein, dann damit wei0 man direkt nach dem Compilieren, wieviel Speicher man braucht, während man mit dynamisch zugewiesenen Variablen schön auf's Maul fallen kann. Hmmm, ansonsten fällt mir dazu nix ein.
balli schrieb: > Um die Sache zu systematisieren würde ich mir wünschen, dass mal eine > Liste mit Nachteilen aufgeführt wird, die Globale Variablen inne haben. > Ich selbst bin nämlich auch ein globale-variablen-nutzer :-( Globale Variablen sind die, vor denen uns 'unsere Mütter immer gewarnt haben'. Ohne Flax. Eiegntlich ist es so, dass man in der Programmierung einen großen Bogen um globale Variablen macht. Solange es sich um Variablen mit einem ordentlichen Namen handelt, deren Verwendung eindeutig ist, mag es noch angehen, aber auch dann wird man eher Zugriffsfunktionen benutzen und dafür lieber die Variablen Modul-lokal machen. Diese Zugriffsfunktionen können nämlich dann auch Dinge wie zb Bereichsprüfungen übernehmen. Zusätzlich hat man dann auch noch das Problem, dass nicht einfach jeder dahergelaufene Code sich an Variablen zuschaffen machen kann, wie er lustig ist. Eine Analogie: Da gibt es eine Werkstatt mit einem Bereich in dem die Werkzeuge gelagert werden. Globale Variablen wären jetzt so, wie wenn jeder einfach in das Lager geht und sich nimmt was er braucht. Das funktioniert auch - bei kleinen Werkstätten. Bei großen Werkstätten mit vielen Mitarbeitern geht das aber nicht mehr. Da gibt es dann einen Mann der an der Theke steht und zu dem du gehst um den Spezial - zöllischen - Gewindebohrer zu holen, den du gerade brauchst. Der holt ihn aus dem Lager, trägt ihn in sein Buch ein und gibt ihn dir. Bringst du ihn zurück, dann trägt er wieder aus, dass du den Bohrer hast und legt ihn wieder an seinen Platz zurück. Braucht wer anderer diesen Bohrer, dann kann er nachsehen wer ihn gerade hat und gezielt bei dir nachfragen. So ähnlich ist das auch mit globalen Variablen. Bei kleinen Programmen ist das noch ok. Aber sobald die Programme größer werden, wird es immer schwieriger den Überblick zu behalten, welche Code-Teile welche Variablen verändern. Zudem ist die Gefahr recht groß, dass man dann zu schlampen anfängt und einfach wild Variablen beschreibt ohne sich um Zusammenhänge zu kümmern. Du magst vielleicht eine Variable haben, die den Radius eines bestimmten Kreises beschreibt. Das ist gut so und jeder der will kann einen neuen Wert für diesen Radius festlegen. Bis du dann eines Tages in das Problem läufst, dass dir auffällt, dass du von diesem Kreis nicht nur den Radius benutzt sondern auch seine Fläche. Also kommst du auf die glorreiche Idee, anstatt diese ständig neu auszurechnen, ganz einfach eine weitere Variable dafür zu spendieren. Nur musst du jetzt auch sicher gehen, dass wenn immer du den Radius neu setzt (die Variable beschreibst), auch die Fläche neu berechnet werden muss. Du musst also deinen ganzen Code durchgehen und alle Schreiboperationen auf den Radius finden. Irgendwann bist du damit fertig und dein Kollege baut ein neues Modul dazu. Er kommt drauf, dass wenn in seinem Konstruktionsprogramm ein bestimmtes Rohr benutzt wird, dann müsste man auch diesen Kreisradius ändern. Von der zusätzlichen Variablen für die Fläche weiß er nichts. Also vergisst er darauf. Und damit hat er dir eine schöne Debug-Session eingebrockt, denn du musst jetzt rausfinden warum da plötzlich Radius und Fläche nicht mehr zusammenstimmen. Natürlich nicht an der Stelle an der du das Problem bemerkst, sondern 30 Bildschirmseiten vorher, wo dein Kumpel die Erweiterung gemacht hat - von der allerdings du wieder nichts weißt. Von den 'trivialen' Problemen, die sich durch int i; void foo() { for( i = 0; i < 8; ++i ) bar( i ); } void bar( int j ) { for( i = 0; i < j; ++i ) batz( i ); } also einer Doppelverwendung einer Variablen, die sich durch die Aufrufhierarchie von Funktionen ergibt, reden wir erst mal nicht. Kurz und gut: Eigentlich will man überhaupt keine globalen Variablen haben. Aber für einen kleinen AVR gilt insofern eine Sonderregel, dass man aus praktischen und resourcespezifischen Gründen da auch schon mal eine Ausnahme macht, WENN man einen guten Grund dafür hat. Alles was besser als lokale Variable aufgehoben wäre, wie zb void foo() { int i; for( i = 0; i < 8; ++i ) bar( i ); } void bar( int j ) { int i; for( i = 0; i < j; ++i ) batz( i ); } ist übrigens kein guter Grund. Derartige Schleifenvariablen bzw. Argumente in Funktionen können NICHT vernünftig durch globale Variablen ersetzt werden. Das wäre ein Spiel mit dem Feuer. Das größte Problem in der ganzen Prorgrammierung ist es den Überblick zu bewahren bzw. Querbeeinflussungen von einem Codemodul auf ein anderes Codemodul entweder zu minimieren oder ganz auszuschliessen. Globale Variablen sind das genaue Gegenteil zu diesen Bestrebungen.
In C kann man im Prinzip zwischen folgenden vier Arten von Variablen unterscheiden:
1 | int var_global; |
2 | static int var_modul_global; |
3 | |
4 | void meine_funktion(void) |
5 | {
|
6 | int var_stack; |
7 | static int var_funktions_lokal; |
8 | }
|
Globale Variablen wie var_global sollte man strikt vermeiden. Sie sind im gesamten Programm sichtbar, brauchen also einen eindeutigen Namen, und können von überall aus verändert werden. Man kann sie immer durch eine modul-globale Variable und eine get- und set-Funktion ersetzen. Der einzige Grund für eine globale Variable ist, dass man sich den Overhead des Funktionsaufruf und Rücksprungs spart. Das spielt aber nur bei extremst zeitkrischen Operationen eine Rolle, die normalerweise dann sowieso in ein Modul gebündelt gehören. Gegen modul-globale Variablen ist dagegen nichts einzuwenden. Sie sind nur für Funktionen innerhalb des Moduls (der C-Datei) sichtbar. Sie brauchen also keinen programmweit eindeutigen Namen und man kann überblicken, wer darauf zugreift. Modul-globale Variablen nimmt man, wenn mehrere Funktionen innerhalb des Moduls auf die gleichen Daten zugreifen müssen. Für alle Daten, die nur temporär innerhalb einer Funktion gebraucht werden, gibt es die automatischen Variablen, die auf dem Stack angelegt werden. Sie sind nur innerhalb der Funktion (genauer: innerhalb des Blocks) gültig, in der sie angelegt sind. Sie benötigen nur Speicher, während man sich innerhalb der Funktion befindet. Wenn die Funktion verlassen wird, kann der Speicher wieder für andere Stackvariablen verwendet werden. Das ist also Mittel der Wahl für alles, was nicht über den Funktionsaufruf hinaus überdauern muss. Die letzte Art von Variablen sind funktions-lokal: Sie sind nur innerhalb der Funktion sichtbar, allerdings behalten sie ihren Wert auch, wenn die Funktion verlassen wird. Beim nächsten Betreten der Funktion steht der zuletzt darin abgelegte Wert wieder zur Verfügung. Sie belegen allerdings entsprechend dauerhaft den Speicher. Im Prinzip sind sie also nichts anderes als modul-globale Variablen, nur dass ihre Gültigkeit auf genau eine Funktion und nicht alle Funktionen im Modul beschränkt ist. Imo kann sie eher selten gebrauchen. Zusammengefasst kommt man also in den allermeisten Fällen mit modul-globalen Variablen für dauerhaft zu speichernden Daten (ggf. mit set- und get-Funktionen) und Stack-Variablen für alle temporären Werte wunderbar aus.
Noch zum eigentlichen Thema "Sauber Programmieren mit Interrupts": Das ganze "Geheimnis" ist, sein Programm in sinnvolle Module zu zerlegen, die jeweils eine Aufgabe in sich kapseln. Jedes Modul muss eine geeignete Schnittsstelle anbieten, über die mit ihm kommuniziert wird. Das Modul selber darf auch mit anderen Modulen kommunizieren. Allerdings sollte man möglichst gegenseitige oder zyklische Abhängigkeiten vermeiden, sonst erhält man Spaghetti-Code. Ein guter Ansatz ist, sein Programm hierarchisch oder in Schichten zu unterteilen. Auf oberster Ebene steht das Hauptmodul mit der main-Funktion. Von dort aus werden die Untermodule initialisiert und aufgerufen. Die Untermodule rufen wiederum Unteruntermodule oder andere Untermodule (aber wie gesagt möglichst nur in eine Richtung) auf. Auf unterster Ebene stehen Module, die bestimmte Teile der Hardware kapseln. Und in genau diesen befinden sich die ISRs. Die ISRs greifen nur auf Daten innerhalb des Moduls zu. Im UART-Modul könnte zum Beispiel ein Puffer liegen, den die UART-ISR befüllt. Das Modul, das die UART-Daten weiterverarbeitet, weiß von dem Puffer nichts. Es fragt lediglich ab und zu per uart_getc() nach, ob ein neues Zeichen angekommen ist. Was dahinter passiert, geht außerhalb der UART-Implementierung niemanden etwas an. So kommt auch niemand auf die Idee, sich die Zeichen selber direkt aus dem Puffer zu holen und die Indizes zu verändern. Das vermeidet Fehler und ermöglicht, die UART-Implementierung jederzeit auszutauschen. Zum Beispiel gegen eine andere Art von Puffer oder vielleicht sogar gegen eine ganz ohne Puffer. Ganz nebenbei kann man sein Programm so auch gut auf andere Hardware portieren, da man nur die untersten, hardwareabhängigen Module austauschen muss.
@ Karl Heinz & Fabian, erstmal herzlichen Dank für die ausführlichen Ratschläge. Ich habe diesbezüglich eine Frage: Nehmen wir an ich binde eine c-Datei "uart.c" in mein Projekt ein und in dieser c-Datei stehen nur Funktionen die ich benötige um den UART anzusprechen (also nicht die main-Funktion). Ferner steht in dieser c-Datei ganz oben (außerhalb der Funktionen) eine Variable "buffer" in die ein empfangener String hineinkommt. Diese Variable ist ja innerhalb der c-datei "uart.c" global angelegt, kann aber in einer anderen c-Datei "main.c" in der dann mein main-programm steht gar nicht angesprochen werden (weil ich sie nicht als extern deklariert habe). Ist das dann auch noch sauber, wenn ich den String dann mit einer getstring-funktion die einen Zeiger übergibt in der main-funktion abhole? Oder gilt die Variable buffer schon als unsauber, da sie innerhalb der uart.c ja global definiert ist? lg balli
oldmax schrieb: > Wenn nix besseres > verfügbar, leistet Paint auch gute Dienste. > Gruß oldmax PapDesigner ist da sehr nützlich. Gerade wenn es umfangreich wird, wird "die alte Schule" wohl unerlässlich. Früher hab ich mal Datenbanken unter Access gemacht, da hatte ich nichts in "meiner eigenen" Datenbank dokumentiert (weil ich ja so ein tolles Gedächtnis habe*lol*), nach drei Jahren habe ich etliche Stunden gebraucht meinen eigenen Kram zu verstehen. Dieser PapDesigner ist kostenlos und gute Dokumentation gibt es auch. Ist aber nicht nötig, geht quasi alles von selbst.
Also erst einmal danke für die vielen Kommentare!!! Hab mir gerade alles mal in ruhe durchgelesen. Mit den Assemblersachen kann ich als "Anfänger" wirklich noch nicht viel anfangen, die danach folgenden Kommentare haben mich jedoch echt weitergebracht, denke ich... Ausprobiert habe ich es mit den ISRs in extra Modulen noch nicht. Funktionen in Module auslagern versuche ich sowieso immer. Ich werde in Zukunft mal versuchen die Modul-spezifische ISR auch in das Passende Modul zu legen. Dort dann Modul-globale variablen für die Arbeit damit verwenden. Denke damit wird auf jeden Fall etwas mehr Struktur in mein Gedöns kommen. Aber wie ich sehe is das Struktorierte Programmieren besonders mit Interrupts eine interessante Sache an der ich in jedem Fall dranbleiben will. Grüße Jan.
Karl Heinz Buchegger schrieb: > Globale Variablen wären jetzt so, wie wenn jeder einfach in das Lager > geht und sich nimmt was er braucht. > Das funktioniert auch - bei kleinen Werkstätten. schöner Vergleich, und ein AVR ist eine eine kleine Werkstätte Jürgen S. schrieb: > globale Variablen gibt es nicht. kannst Du mir erklären wie Du Informationen mit dem Hauptprogramm bzw. anderen Modulen austauscht?
Ein Informationsaustausch kann denk ich wie folgt geschehen: Ins Hauptprogramm: Eine Funktion mit rückgabewert (return ...) welche im modul definiert wird. Oder mit übergabe eines Pointers. In die Module: Übergabewerte von Funktionen, welche auch im Modul definiert werden -> void funktion(uebergabewert1, uebergabewert2) Grüße Jan.
balli schrieb: > Nehmen wir an ich binde eine c-Datei "uart.c" in mein Projekt ein und in > dieser c-Datei stehen nur Funktionen die ich benötige um den UART > anzusprechen.. Nun, wenn wir hier über C reden, dann reden wir auch über Header-Dateien. Dort sollte man sogenannte Prototypen reinschreiben, damit auch andere Programmteile in anderen Quellen auf diese Funktionen richtig zugreifen können. z.B. in uart.h extern bool IsCharAvailable (int uartnummer); Wie man das im Falle eines UART-Anschlusses macht, kannst du hier im Forum in der "LernBetty" (Codesammlung) dir anschauen. Dort kannst du auch sehen, wie man einen Ringpuffer organisiert, um z.B. die Zusammenarbeit zwischen einem Interrupt-Programm und dem Rest der Firmware zu organisieren. W.S.
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.