Hallo, ich habe in C eine Steuerung für eine Warmwasser-Umwälzpumpe programmiert für einen ATmega8. Diese funktioniert prinzipeill auch, ist aber noch nicht ganz fertig. Nach einer Änderung der Software hatte ich große Probleme (z. B. Anzeige im LCD ist unvollständig, Controller wird langsam oder stürzt ab). Mir ist dabei insbesondere aufgefallen, dass es beim Anzeigen längerer Strings im LCD Probleme gibt, sodass ich nun nur noch Abkürzungen verwende. Allerdings ist der Programmspeicher erst zu ca. 90% voll, sodass es daran nicht liegen kann. Nach dem Studium diverser Anleitungen stellte ich fest, dass ich nicht korrekt (atomar) auf Variablen zugreife, die sowohl im Hauptprogramm als auch in der Interrupt-Routiene verwendet werden (Variablen waren also weder mit volatile deklariert noch waren die Zugriffe durch cli(); ... sei(); atomar). Mit dem neuen Wissen habe ich diese Fehler korrigiert, allerdings funktioniert es immer noch nicht, sodass ich einige Fragen habe: Alle Variablen, die in der Interrpt-Routine und im Hauptprogramm verwendet werden, habe ich als volatile ... deklariert. Alle anderen nicht. Ich hoffe das ist so richtig. Ich verwende auch ein struct. Dieses habe ich als volatile struct {...} ... deklariert. Ich hoffe, das geht so. Es soll ja insbesondere Probleme geben, wenn man auf Variablen mit mehr als 1 Byte nicht atomar zugreift. Gilt das auch für solche, die ausschließlich im Hauptprogramm verwendet werden? Müssen auch Lesezugriffe bzw. if-Abfragen von 1 Byte Variablen atomar sein? (Habe ich vorsichtshalber mal so gemacht). Müssen EEPROM-Zugriffe atomar sein? Müssen ADC-Abfragen atomar sein? Ich habe für eventuelles Interesse mal die aktuelle (nicht stabile) Version angehängt, allerdings will ich euch damit eigentlich nicht belasten, denn es sind über 1000 Codezeilen. ;-) Ich danke vielmals im Voraus für eure Antworten und eure Hilfe. Viele Grüße, Tobi
Was heißt bei dir denn "verwenden"? Also, die Regel zum störungsfreien Verwenden von Variablen lautet, daß immer nur eine Instanz schreibend darauf zugreifen darf. Lesen darf hingegen jeder. wenn du das beherzigst, dann wird bei dir auch nix mehr abstürzen. Und von ellenlangem Sperren von Interrupts halte ich GARNICHTS. Sowas ist schlichtweg ein Denkfehler des Programmierers, wenn es nicht genau so vom Hersteller vorgegeben ist. (kleines Beispiel: Programmieren von Flashroms im System zur Laufzeit. Da MUSS dafür gesorgt werden, daß eine ganz bestimmte Datensequenz unterbrechungsfrei ins ROM geschrieben werden) Aber SOWAS hast du ja garnicht vor. Also denk mal drüber nach, wie sich zwei verschiedene Prozesse miteinander unterhalten können, ohne sich gegenseitig zu stören. Ich geb dir mal nen Tip: char merk; char oldmerk; double Measurementresult; // Interrupt-Routine void MyInterrupt(void) { Measurementresult = CalculateMyNewResult(); ++merk; } void main (void) { double temp; blablabla... immerzu: if (oldmerk!=merk) { clearinterrupt(); temp = Measurementresult; enableinterrupt(); VerarbeiteResult(temp); oldmerk = merk; } goto immerzu; } W.S.
Hallo, W.S. schrieb: > Was heißt bei dir denn "verwenden"? Ich meine hiermit lesend oder schreibend zugreifen. > Und von ellenlangem Sperren von Interrupts halte ich GARNICHTS. Warum? Wenn die atomaren Operationen nicht sehr lange dauern, dürfte doch eigentlich kein Interrupt ausbleiben, oder? Leider werde ich aus Deinem Beispiel-Programm nicht ganz schlau. Willst Du mir damit sagen, dass ich immer nur entweder im Hauptprogramm oder in der Interrupt-Routine schreibend auf eine Variable zugreifen soll - d. h. in meinem Fall beispielsweise die Änderung der Uhrzeit NUR im Hauptprogramm machen soll? Wenn das sinvoll ist und weniger Probleme macht, könnte ich es ja mal versuchen. Wenn ich dies konsequent umsetze, bräucht ich, wenn ich es richtig verstehe, keine atomaren Zugriffe mehr, oder? Viele Grüße, Tobi
@ Tobias G. (tobi1435) >ich habe in C eine Steuerung für eine Warmwasser-Umwälzpumpe >programmiert für einen ATmega8. Diese funktioniert prinzipeill auch, ist >aber noch nicht ganz fertig. Solche Aussagen sind mit Vorsicht zu genießen ;-) >Alle Variablen, die in der Interrpt-Routine und im Hauptprogramm >verwendet werden, habe ich als volatile ... deklariert. Schom mal gzut, reicht aber nicht. >Ich verwende auch ein struct. Dieses habe ich als volatile struct {...} >... deklariert. Ich hoffe, das geht so. Ja, aber. Du machst das alles voel zu kompliziert. ALlein die Tatsache, dass es "nur" um eine Pumpensteuerung geht macht klar, dass man den Code nicht mit Millionen sei() und cli() zuballern muss. Dir fehlt das richtige Konzept. Siehe Interrupt. Im Interrupt macht man möglichst wenig, zählt ein paar Zähler hoch und setzt Flags, das alles volatile. Der Rest passiert in der Hauptschleife. dann muss man auch nicht dauernd atomare Zugriffe machen. Tasten werden auch dort entprellt und abgefragt, siehe Entprellung. >Müssen auch Lesezugriffe bzw. if-Abfragen von 1 Byte Variablen atomar >sein? (Habe ich vorsichtshalber mal so gemacht). Ja, aber siehe oben. Wenn es um Structs geht, so kopiert man die EINMAL in eine lokale Variable und kann dann völlig relaxt drauf zugreifen. Beim nächsten Durchlauf der Hauptschleife gibt es eine neue Kopie. Deine Hauptscheife ist viel zu konfus, chaotisch und aufgeblasen, dass kann man um Längen einfacher machen. Und dadurch wird es nicht nur eleganter, sondern auch sicherer und überschaubarer. >Müssen EEPROM-Zugriffe atomar sein? Ja, aber die haben in der ISR nix zu suchen. Dauern viel zu lange. >Müssen ADC-Abfragen atomar sein? Dito. Die macht man EINMAL, entweder in der ISR oder in der Hauptschleife. >Version angehängt, allerdings will ich euch damit eigentlich nicht >belasten, denn es sind über 1000 Codezeilen. ;-) Naja, wir haben schon mehr und schlimmeres gesehen. MfG Falk
Einfaches Beispiel. Deine Variablen für Sekunden, minuten und Stunden Kommen ins Hauptprogramm, ohne Volatile. In der ISR werden nur die 125tel Sekunden Hochgezählt und im Fall 1 S ein Flag gesetzt. Damit wird das um Längen einfacher und robuster.
1 | volatile uint8_t flag_1s; |
2 | |
3 | main() { |
4 | |
5 | while(1) { |
6 | |
7 | if (flag_1s==1) { |
8 | flag1s=0; |
9 | // Zeit hochzählen
|
10 | // Aktionen ableiten
|
11 | }
|
12 | }
|
13 | |
14 | |
15 | ISR(TIMER2_COMP_vect) |
16 | static uint8_t cnt125; |
17 | |
18 | cnt125++; |
19 | if (cnt125==125) { |
20 | cnt125=0; |
21 | flag_1s=1; |
22 | }
|
So wie du die Sache schilderst, wäre es evtl. sinnvoll wenn du dir mal dieses Video anschaust. Mir hat das sehr geholfen und ist ungemein interessant, speziell der Teil zu beginn wo es auch um Atomarität geht. http://greenteapress.com/semaphores/
W.S. schrieb: > Also, die Regel zum störungsfreien Verwenden von Variablen lautet, daß > immer nur eine Instanz schreibend darauf zugreifen darf. Lesen darf > hingegen jeder. Die Einhaltung dieser Regel führt aber nicht sicher zu richtiger Funktion: Es passiert trotzdem noch Unfug, wenn die eine Instanz (1) die Variable halb gelesen hat, dann die zweite Instanz (2) eine Änderung macht und (1) beim Fortsetzen z.B. nach einem Übertrag in (2) nicht zusammenpassende Bytes liest.
Hallo, vielen Dank für die zahlreichen Antworten! Ich habe allerdings noch ein paar Verständnisfragen: Falk Brunner schrieb: > @ Tobias G. (tobi1435) >>Müssen auch Lesezugriffe bzw. if-Abfragen von 1 Byte Variablen atomar >>sein? (Habe ich vorsichtshalber mal so gemacht). > > Ja, aber siehe oben. Das heißt, in Falks Beispiel müsste ich wie folgt mit cli(); ... sei(); vor Interrupts schützen (da zuerst die if-Abfrage und dann - wenn zutreffend - die Änderung von flag_1s)?
1 | main() { |
2 | |
3 | while(1) { |
4 | |
5 | cli(); |
6 | if (flag_1s==1) { |
7 | flag1s=0; |
8 | sei(); |
9 | // Zeit hochzählen
|
10 | // Aktionen ableiten
|
11 | }
|
12 | sei(); |
13 | }
|
> Wenn es um Structs geht, so kopiert man die EINMAL > in eine lokale Variable und kann dann völlig relaxt drauf zugreifen. > Beim nächsten Durchlauf der Hauptschleife gibt es eine neue Kopie. Das verstehe ich leider nicht ganz. Wenn ich einen Struct kopiere, ändere ich dann nur noch die Kopie? Dann habe ich aber doch nicht mehr den "Komort", dass ich struct_name.bit_name schreien kann, sodern müsste dann ggf. ((variablenname && (1 << bitnummer)) >> bitnummer) schreiben, oder? In diesem Fall könnte ich ja ganz ohne struct arbeiten. Falls meine atomaren struct-Zugriffe Probleme bereiten, kann ich mal versuchen alle Zugriffe auf meinen struct in die Hauptroutine verlagern. Aber wenn es grundsätzlich auch so gehen würde, würde ich - um Arbeitsspeicher zu sparen - gerne mit dem struct auch in der Interrupt-Routine arbeiten. >>Müssen EEPROM-Zugriffe atomar sein? > > Ja, aber die haben in der ISR nix zu suchen. Dauern viel zu lange. Der Aufruf von EEPROM_Daten_speichern(); ist nur eine Ausnahme, damit bei einem Stromausfall beim nächsten Interrupt sofort die wichtigen Daten gespeicher werden. Dei Versorgungsspannung wird nämlich nur kurz durch einen Elko gepuffert. Da dürfen dann auch mal ein paar Interrupts ausfallen, da alschließend die Spannung sowieso weg ist. ;-) >>Müssen ADC-Abfragen atomar sein? > > Dito. Die macht man EINMAL, entweder in der ISR oder in der > Hauptschleife. Aha, das habe ich dann wohl falsch gemacht. Also auch wieder cli(); ... sei();. Viele Grüße, Tobi
Nicht ganz. Den ADC liest man sinnvollerweise im ADC Interrupt. Kopiert die Werte in eine Variable. Dabei braucht man kein cli & sei. etwa so ADC Interrupt { ADCData:=ReadADC(); ADCready:=true; } und dann im Main if (ADCready==true) { calc.... verwenden von ADCData ... ADCready:=false; }
@ Tobias G. (tobi1435) >Das heißt, in Falks Beispiel müsste ich wie folgt mit cli(); ... sei(); >vor Interrupts schützen (da zuerst die if-Abfrage und dann - wenn >zutreffend - die Änderung von flag_1s)? Nein. Sie Abfrage von flag_1s ist von allein atomar, da es nur 8 Bit beit ist. Da braucht man kein cli() sei(). >> Wenn es um Structs geht, so kopiert man die EINMAL >> in eine lokale Variable und kann dann völlig relaxt drauf zugreifen. >> Beim nächsten Durchlauf der Hauptschleife gibt es eine neue Kopie. >Falls meine atomaren struct-Zugriffe Probleme bereiten, kann ich mal >versuchen alle Zugriffe auf meinen struct in die Hauptroutine verlagern. Gute Idee! >Aber wenn es grundsätzlich auch so gehen würde, würde ich - um >Arbeitsspeicher zu sparen - gerne mit dem struct auch in der >Interrupt-Routine arbeiten. Du sparst keinen nennenswerten Speicher, du verkmplizierst es nur unnötig. >Der Aufruf von EEPROM_Daten_speichern(); ist nur eine Ausnahme, damit >bei einem Stromausfall beim nächsten Interrupt sofort die wichtigen >Daten gespeicher werden. Stimmt, hab ich erst später gesehen. Dann braucht man aber dort keinen atomaren Zugiff. >Aha, das habe ich dann wohl falsch gemacht. Also auch wieder cli(); ... >sei();. Was soll denn das? Du hast Paranoia. Vorsicht ist schön und gut, aber man kann es übertreiben. Du greifst doch nicht auf den ADC parallel in der ISrt und im Hauptprogramm zu. Zumal deine Steurung bestenfalls einen 1s Takt braucht. Da kann man alles in die Hauptschleife packen und tippel tappel Tour abarbeiten. Mit normalen Funktionen, auch für den ADC. Fettig. MFG Falk
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.