Forum: Mikrocontroller und Digitale Elektronik Frage zu atomaren Variablenzugriff bei Interrupts


von Tobias G. (tobi1435)


Angehängte Dateien:

Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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.

von Tobias G. (tobi1435)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@  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

von Falk B. (falk)


Lesenswert?

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
    }

von Seri (Gast)


Lesenswert?

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/

von my2cent (Gast)


Lesenswert?

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.

von Tobias G. (tobi1435)


Lesenswert?

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

von Purzel H. (hacky)


Lesenswert?

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;
 }

von Falk B. (falk)


Lesenswert?

@  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
Noch kein Account? Hier anmelden.