Forum: Mikrocontroller und Digitale Elektronik Automarer Zugriff auf 32-Bit Variable die in einer ISR verändert wird


von Paul H. (powl)


Lesenswert?

Hi,

inspiriert von der millis()-Funktion im Arduino habe ich so etwas in 
meinem ATmega8 implementiert:
1
uint32_t millis;
2
3
uint32_t millis()
4
{
5
  return millis;
6
}
7
8
9
int main()
10
{
11
  // Timer2: 8Mhz F_CPU, Prescaler: 64 
12
  TCCR2 = (1 << CS22);
13
  TIMSK = (1 << OCIE2);
14
  OCR2 = 125;
15
16
....
17
}
18
19
ISR(TIMER2_COMP_vect)
20
{
21
  TCNT2 = 0;
22
  millis++;
23
}

Die ISR wird jede Millisekunde einmal ausgeführt, zählt die 
millis-Variable hoch und setzt dann den Timer wieder auf 0.

Wenn ich jetzt aber die millis-Variable innerhalb des codes irgendwo 
verwende, kann es ja sein, dass die ISR zwischenreinfunkt und sie 
hochzählt, dann wird daraus Kauderwelsch gelesen.

Mit der Funktion millis(), welche den Wert der Variable millis einfach 
als Rückgabewert liefert, möchte ich den Zugriff kapseln. Der 
Rückgabewert ist ja nur eine Kopie der Variable millis. Die Rückgabe 
dürfte daher atomar bleiben, auch wenn sich die ursprüngliche Inhalt der 
millis-Variable zwischenzeitlich ändert.

Aber reicht das so, wie ich das implementiert habe? vermutlich nicht, 
oder? Da auch beim Anfertigen der Kopie die ISR dazwischenfunken kann.

Ist folgendes zielführender?
1
uint32_t millis()
2
{
3
  uint32_t copy;
4
  cli();
5
  copy = millis;
6
  sei();
7
  return copy;
8
}

Oder kann man sogar den Zugriff auf die Variable millis selbst irgendwie 
atomar machen?

lg Paul

von H.Joachim S. (crazyhorse)


Lesenswert?

so ist es doch richtig.

von ProjektX (Gast)


Lesenswert?

In der main-Funktion fehlt mir eine Interruptfreischaltung in Form von 
sei();

Die uint32_t-Variable als volatile deklarieren bewirkt manchmal Wunder 
:)

Dein Vorschlag mit dem Ausschalten der Interrupts dürfte auch 
durchkommen. Das Gleiche, optisch schöner ist:

ATOMIC_BLOCK(ATOMIC_FORCEON)
{ copy = millis;}

von ProjektX (Gast)


Lesenswert?


von Paul H. (powl)


Lesenswert?

H.Joachim S. schrieb:
> so ist es doch richtig.

Wie nun? :D Ich habe ja 2 Varianten gezeigt.

ProjektX schrieb:
> In der main-Funktion fehlt mir eine Interruptfreischaltung in Form von
> sei();

Stimmt, habe ich hier im Beispiel vergessen, ist im richtigen Code aber 
drin.

ProjektX schrieb:
> Die uint32_t-Variable als volatile deklarieren bewirkt manchmal Wunder
> :)

Das bewirkt doch nur, dass der Compiler die Variable auf jeden Fall 
immer aus dem RAM lädt und nicht irgendwie in den Registern 
zwischenspeichert. Das nützt mir aber für den atomaren Zugriff nichts? 
oder?

ProjektX schrieb:
> ATOMIC_BLOCK(ATOMIC_FORCEON)
> { copy = millis;}

Huch interessant, das kannte ich noch nicht!

von (prx) A. K. (prx)


Lesenswert?

Paul H. schrieb:
> Das bewirkt doch nur, dass der Compiler die Variable auf jeden Fall
> immer aus dem RAM lädt und nicht irgendwie in den Registern
> zwischenspeichert. Das nützt mir aber für den atomaren Zugriff nichts?
> oder?

Korrekt, atomar wird sie dadurch nicht. Aber ohne volatile kann es 
passieren, dass sie im Hauptprogramm in Registern bleibt und von einer 
Änderung im Interrupt nichts mitkriegt.

: Bearbeitet durch User
von Paul H. (powl)


Lesenswert?

Ahhh
1
volatile uint32_t millis_cnt;
2
3
uint32_t millis()
4
{
5
  uint32_t copy;
6
  ATOMIC_BLOCK(ATOMIC_FORCEON)
7
  { copy = millis_cnt;}
8
  return copy;
9
}

Das ist jetzt die finale Version, sollte so funktionieren :) danke!

von ProjektX (Gast)


Lesenswert?

Das #include <util/atomic.h> nicht vergessen, sieht ansonsten für diese 
späte Stund richtig aus, viel Erfolg!

von Peter D. (peda)


Lesenswert?

Kürzere Schreibweise:
1
volatile uint32_t millis_cnt;
2
3
uint32_t millis(void)
4
{
5
  ATOMIC_BLOCK(ATOMIC_FORCEON)
6
    return millis_cnt;
7
}

von Paul H. (powl)


Lesenswert?

Ah, interessant, dass man return da reinpacken kann, aber warum auch 
nicht, ja.

das liefert mir übrigens ein
warning: control reaches end of non-void function

warum macht er das?

von Peter D. (peda)


Lesenswert?

Paul H. schrieb:
> warning: control reaches end of non-void function

Ja, vergiß meinen Beitrag.

von ProjektX (Gast)


Lesenswert?

Klar, das Ganze könnte man natürlich noch weiter vereinfachen, ich würde 
jedoch glatt schätzen, dass es für deine Verhältnisse/Bedürfnisse 
ausreichen wird.

Als logische Weiterentwicklung und falls überlaufsichere Zeitdifferenzen 
gemessen werden sollen, kannst du ja schon mal auf die time_t - Structs 
rüberschielen.

Alternativ auch selbst machen:
typedef struct
{
  uint16_t msec;
  uint8_t  sec;
  uint8_t  min;
  uint16_t  hour;
  uint8_t  year;
}time_t;

ISR(TIMER1_COMPA_vect)
{
  // Millisekunden zählen
  tm.msec++;

  // Sekunden zählen
  if (tm.msec==1000)
  {  tm.sec++;

    tm.msec = 0;
  }

  // Minuten zählen
  if(tm.sec==60)
  {
    tm.min++;
    tm.sec = 0;
  }

  // Stunden zählen
  if (tm.min==60)
  {
    tm.hour++;
    tm.min = 0;
    tm.sec = 0;
    tm.msec = 0;
  }

  // Bei Bedarf um Jahre ergänzen
  if (tm.hour==61320)  // entspricht x Jahren
  {
    tm.year += 7;
    tm.hour = 0;
    tm.min = 0;
    tm.min = 0;
    tm.sec = 0;
    tm.msec = 0;
  }
}

Und dazwischen kannst du dann deine eigenen Flags setzen, z.B. 
Entprellzeit des Tasters, Aktualisierung des Bildschirms, Ende der 
Periode o.ä.
So kannst du einen Timer für mehrere Ereignisse verwenden.

von A. S. (Gast)


Lesenswert?

Nur für den Fall, dass man die Interrupts nicht sperren will, gäbe es 
auch eine Lösung ohne, wenn die Zeit der Zuweisung klein gegenüber der 
Updatezeit ist:
1
volatile uint32_t millis;
2
uint32_t millis()
3
{
4
uint32_t copy;
5
6
   for(copy=millis;copy!=millis;copy=millis);
7
8
   return copy;
9
}
(ich weiss, hier wäre do..while richtig, da mit for 2 identische 
Zuweisungen. Ich fand es hier nur lustiger und kompakter.)

wenn Garantiert ist, dass der Code niemals (ok, gibt es nicht), aber 
wenn er niemals für eine Update-Zeit gesperrt ist, dann geht auch
1
uint32_t millis()
2
{
3
uint32_t copy = millis;
4
5
   if(copy==millis) return copy;
6
   return millis;
7
}

von Paul H. (powl)


Lesenswert?

Peter D. schrieb:
> Paul H. schrieb:
>> warning: control reaches end of non-void function
>
> Ja, vergiß meinen Beitrag.

Ich habe mir das disassembly nicht angeschaut aber die Codegröße bleibt 
bei beiden Varianten exakt gleich, scheinbar kann man die Warnung 
getrost ignorieren?

von c-hater (Gast)


Lesenswert?

Achim S. schrieb:

> Nur für den Fall, dass man die Interrupts nicht sperren will, gäbe es
> auch eine Lösung ohne, wenn die Zeit der Zuweisung klein gegenüber der
> Updatezeit ist:
[...]

Ja, das funktioniert tatsächlich. Ist aber irgendwie doch sowas wie ein 
Offenbarungseid. Nicht wirklich empfehlenswert...

von Einer K. (Gast)


Lesenswert?

Peter D. schrieb:
> ATOMIC_FORCEON

Warum hier kein ATOMIC_RESTORESTATE ?
(welches ich hier verwenden würde)
Hat das einen besonderen Grund?

von Edi R. (edi_r)


Lesenswert?

Mit ATOMIC_RESTORESTATE wird der Zustand des I-Bits vor dem Eintritt in 
den Block gesichert, dann gelöscht und am Ende des Blocks wieder so 
gesetzt, wie es vorher war. Das dauert länger, als wenn man das I-Bit 
vor dem Block löscht und danach immer setzt. Man muss nur sicher sein, 
dass das I-Bit auch wirklich gesetzt sein soll, aber das ist in der 
main() bzw. außerhalb von ISRs praktisch immer der Fall.

von dunno.. (Gast)


Lesenswert?

Arduino F. schrieb:
> Peter D. schrieb:
> ATOMIC_FORCEON
>
> Warum hier kein ATOMIC_RESTORESTATE ?
> (welches ich hier verwenden würde)
> Hat das einen besonderen Grund?

Ich muss dem fan boy zustimmen.. Das kontextlose aktivieren 
irgendwelcher interrupts mache ich mittlerweile nur noch einmal, nämlich 
mach der initialisierung des systems. Alles was danach läuft, darf nur 
noch restoren.

Man stelle sich vor, man ist in einem kritischen block, und braucht die 
zeit. Milis aufgerufen, interrupts ungewollt aktiv. Knallt garantiert, 
und man sucht sich nen wolf..

von Rolf M. (rmagnus)


Lesenswert?

Paul H. schrieb:
> ISR(TIMER2_COMP_vect)
> {
>   TCNT2 = 0;
>   millis++;
> }
>
> Die ISR wird jede Millisekunde einmal ausgeführt, zählt die
> millis-Variable hoch und setzt dann den Timer wieder auf 0.

Manuelles Rücksetzen vom Timer sollte man vermeiden, vor allem, wenn man 
einigermaßen genaue Timings will. Warum nutzt du nicht den CTC-Modus?

von m.n. (Gast)


Lesenswert?

Arduino F. schrieb:
> Warum hier kein ATOMIC_RESTORESTATE ?

Was ist das denn? Ich schreibe vor die Funktion einfach 'monitor'.
So lebt halt jeder in seiner kleinen Welt ;-)
Man kann es aber auch verständlich formulieren:

uint32_t millis()
{
  uint32_t copy;
  uint8_t temp_sreg = SREG;  // sichern
  cli();
  copy = millis;
  SREG = temp_sreg;          // alter Zustand
  return copy;
}

Oder man sperrt nur die ISR-Quelle, die stören könnte:

uint32_t millis()
{
  uint32_t copy;
  TIMSK &= ~(1 << OCIE2);
  copy = millis;
  TIMSK |= (1 << OCIE2);
  return copy;
}

Bei der ISR wird TCNT2 leider immer zerschossen und damit für andere 
Verwendung unbrauchbar. Daher mein Vorschlag:

#define OCR2_NACHLADEWERT 125

ISR(TIMER2_COMP_vect)
{
  OCR2 += OCR2_NACHLADEWERT;
  millis++;
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

ProjektX schrieb:
> Als logische Weiterentwicklung und falls überlaufsichere Zeitdifferenzen
> gemessen werden sollen, kannst du ja schon mal auf die time_t - Structs
> rüberschielen.

Kann man machen, aber ... will man das wirklich im Timerinterrupt 
machen?

Vergleiche mal, wie oft zur Laufzeit eines Programms tatsächlich die 
time_t-Strukturen benötigt werden, und wie oft zur Laufzeit eines 
Programms der Timerinterrupt aktiv wird.

Ich würde eine Konvertierung von ticks in time_t erst dann vornehmen, 
wenn ich time_t wirklich benötige, und nicht im Millisekundentakt.

von Paul H. (powl)


Lesenswert?

Rolf M. schrieb:
> Manuelles Rücksetzen vom Timer sollte man vermeiden, vor allem, wenn man
> einigermaßen genaue Timings will. Warum nutzt du nicht den CTC-Modus?

Der Timer2 hat leider keinen CTC. Aber meinst du nur, es könnte 
Timingprobleme geben beim Rücksetzen des Timers? Der Timer hat ja einen 
Prescaler von 64, d.h. ich habe ab dem Compare-Interrupt 64 Takte Zeit 
um den Timer zu nullen. Das dürfte doch locker reichen, oder?

m.n. schrieb:
> #define OCR2_NACHLADEWERT 125
>
> ISR(TIMER2_COMP_vect)
> {
>   OCR2 += OCR2_NACHLADEWERT;
>   millis++;
> }

Auch eine Methode, die zumindest nicht so zeitkritisch sein dürfte.

von Rolf M. (rmagnus)


Lesenswert?

Paul H. schrieb:
> Der Timer2 hat leider keinen CTC.

Also bei mir sagt das Datenblatt was anderes:
1
Timer/Counter2 is a general purpose, single channel, 8-bit Timer/Counter module. The main
2
features are:
3
• Single Channel Counter
4
• Clear Timer on Compare Match (Auto Reload)
5
• Glitch-free, phase Correct Pulse Width Modulator (PWM)
6
• Frequency Generator
7
• 10-bit Clock Prescaler
8
• Overflow and Compare Match Interrupt Sources (TOV2 and OCF2)
9
• Allows Clocking from External 32kHz Watch Crystal Independent of the I/O Clock

> Aber meinst du nur, es könnte Timingprobleme geben beim Rücksetzen des
>Timers? Der Timer hat ja einen Prescaler von 64, d.h. ich habe ab dem
> Compare-Interrupt 64 Takte Zeit um den Timer zu nullen. Das dürfte doch
> locker reichen, oder?

Ok, das stimmt schon - wenn die Interrupts nicht gesperrt werden. ;-)
Dennoch halte ich manuelles Neuladen des Timers für unschön.

> m.n. schrieb:
>> #define OCR2_NACHLADEWERT 125
>>
>> ISR(TIMER2_COMP_vect)
>> {
>>   OCR2 += OCR2_NACHLADEWERT;
>>   millis++;
>> }
>
> Auch eine Methode, die zumindest nicht so zeitkritisch sein dürfte.

Hat eben auch den Vorteil, dass der Timer gar nicht neu geladen werden 
muss.

von Paul H. (powl)


Lesenswert?

Ups, ja da hab ich nicht richtig geguckt. Dann schalte ich mal den 
CTC-Mode ein :-)

von m.n. (Gast)


Lesenswert?

Rolf M. schrieb:
> Dennoch halte ich manuelles Neuladen des Timers für unschön.

'Unschön' ist dann zu freundlich ausgedrückt, wenn man neuere ATmegas 
wie zum Beispiel den ATmega328 verwendet. Dort können die Timer noch 
einige Interrupts mehr auslösen: 2 x Compare, 1 x Überlauf und bei T1 
noch den Input-Capture Interrupt. Ein Löschen des TCNTx verdirbt die 
Möglichkeiten, den Timer für viele ISRs unabhängig voneinander zu 
verwenden.

von Achim (Gast)


Lesenswert?

c-hater schrieb:
> Nicht wirklich empfehlenswert..

Kann man so allgemein nicht sagen. Es ist portabel und funktioniert bei 
externen devices, die man nicht locken kann.

von ProjektX (Gast)


Lesenswert?

Arduino Fanboy schrieb:
> Warum hier kein ATOMIC_RESTORESTATE ?
>(welches ich hier verwenden würde)
> Hat das einen besonderen Grund?

Im Großen und Ganzen hast du schon Recht, ich habe mir das FORCEON 
angewöhnt, weil ich in den meisten Fällen mit unterbrechbaren 
Interruptprozeduren arbeite - und da programmiere ich sowieso mit dem 
Wissen, dass alles in jedem Augenblick unterbrochen werden kann. Sieh 
das als einen allgemeinen Verweis für den TS zur Verwendung von 
atomic-Biblithek :)


Rufus Τ. Firefly schrieb
> Als logische Weiterentwicklung und falls überlaufsichere Zeitdifferenzen
> gemessen werden sollen, kannst du ja schon mal auf die time_t - Structs
> rüberschielen.

>>Kann man machen, aber ... will man das wirklich im Timerinterrupt
>>machen?

Das war jetzt die optisch schöne Lösung, dass du einen direkten Zugriff 
auf Sekunden, Minuten etc. hast. Bei größeren Zeitdifferenzen kann man 
natürlich auch eine uint128_t-Zählvariable in der ISR inkrementieren 
lassen...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Paul H. schrieb:
> Ahhh
>
>
1
> volatile uint32_t millis_cnt;
2
> 
3
> uint32_t millis()
4
> {
5
>   uint32_t copy;
6
>   ATOMIC_BLOCK (ATOMIC_FORCEON)
7
>   { copy = millis_cnt;}
8
>   return copy;
9
> }
>
> Das ist jetzt die finale Version, sollte so funktionieren :) danke!

Um Overhead zu vermindern, würde ich diese Funktion in einem Header als 
static inline definieren, z.B: so:
1
#include <stdint.h>
2
#include <util/atomic.h>
3
4
extern volatile uint32_t millis_cnt;
5
6
static inline uint32_t millis (void)
7
{
8
   ATOMIC_BLOCK (ATOMIC_FORCEON)
9
   {
10
       return millis_cnt;
11
   }
12
}

Atomare Typen werden von avr-gcc noch nicht unterstützt.

von Einer K. (Gast)


Lesenswert?

ProjektX schrieb:
> Sieh
> das als einen allgemeinen Verweis für den TS zur Verwendung von
> atomic-Biblithek :)
Ja, das hört sich besser an!
:-)

ProjektX schrieb:
> ich habe mir das FORCEON
> angewöhnt, weil ich in den meisten Fällen mit unterbrechbaren
> Interruptprozeduren arbeite - und da programmiere ich sowieso mit dem
> Wissen, dass alles in jedem Augenblick unterbrochen werden kann.
Da haben wir quasi die gleiche Begründung, nur eben invertiert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Das ist wieder so ein schönes, weil einfaches Beispiel, das ist C++ 
wesentlich eleganter geht:
1
{
2
    Scoped<DisableInterrupt> interruptDisabler;
3
    // ...
4
}

Hier ist Scoped eine RAII-Klasse, die den Wrapper EnableInterrupt 
verwendet. Im gesamten umgebenden Block die Iterrupts temporär 
abschaltet.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Das ist wieder so ein schönes, weil einfaches Beispiel, das ist C++
> wesentlich eleganter geht

Es geht doch überhaupt nicht eleganter, sondern es sieht nur nach etwas 
weniger Schreib- und Denk-Arbeit aus.

Also mal gabz prinzipiell: Wenn man Daten benutzt, die nicht in einem 
Wutsch (atomar) lesbar sind und sich während des Lesens ändern können, 
dann muß man sich einen Handshake zwischen dem Leser und dem Veränderer 
ausdenken.

Anders geht es nicht, es sei denn, man sperrt für den Zugriff künstlich 
den Veränderer.

Aber letzteres ist extrem unschön, denn es ist wie der Knüppel im 
Porzellanladen - und es geht auch in ganz vielen Fällen nicht wirklich, 
denn in manchen Architekturen läuft der Usercode eben im Usermodus und 
eine andere Instanz mal eben zu verbieten ist nicht drin. Aber auf einen 
Atmega trifft das wohl nicht zu.

Also sollte der TO lieber darüber nachdenken, wie er die ISR dazu 
bringen kann, ohne viel Aufwand ein Synchronisiersignal zu liefern.
1
volatile unsigned long Ticks;
2
char iieks;
3
4
__isr MyISR(void)
5
{ iieks++;
6
  Ticks++;
7
}
8
9
...
10
11
// und zur Benutzung
12
unsigned long GetUhrzeit (void)
13
{ unsigned long L;
14
  char gieks;
15
16
  gieks = iieks;
17
  L = Ticks;
18
  if (iieks!=gieks) L = iTicks;
19
  return L;
20
}

Das sieht zumindest sparsamer aus auf einer 8 Bit Maschine als ein 
nachträglicher Vergleich von zwei 32 Bit Zahlen. Und es kommt ohne 
Interrupt-Beeinflussung aus.

W.S.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

W.S. schrieb:
> volatile unsigned long Ticks;
> char iieks;

Sollte "iieks" nicht besser auch volatile sein?

von W.S. (Gast)


Lesenswert?

Nachtrag: iieks sollte auch volatile sein, gelle?

W.S.

von m.n. (Gast)


Lesenswert?

W.S. schrieb:
> Das sieht zumindest sparsamer aus auf einer 8 Bit Maschine

Bedenke, daß Deine Abfrage zu jeder Zeit von einem anderen, längeren 
Interrupt unterbrochen werden kann. Bei

if (iieks!=gieks) L = iTicks;

ist nicht sichergestellt, daß iTicks bei der Zuweisung konstant bleibt.

Bezüglich Usermode und Supervisermode gibt es detaillierte Informationen 
im Datenblatt zum ATmega8 :-)

von m.n. (Gast)


Lesenswert?

Supervisor mode sieht besser aus!

von Paul H. (powl)


Lesenswert?

m.n. schrieb:
> Bezüglich Usermode und Supervisermode gibt es detaillierte Informationen
> im Datenblatt zum ATmega8 :-)

Nichts gefunden. Was ist das und wo steht es?

von Peter D. (peda)


Lesenswert?

m.n. schrieb:
> Oder man sperrt nur die ISR-Quelle, die stören könnte:

Davon halte ich garnichts. Dann könnten sich Interrupts mit niederer 
Priorität vordrängeln. Außerdem könnten diese Interrupts viel längere 
Pausen bewirken, als nur wenige Zyklen. Die Seiteneffekte sind also viel 
schwerer abzuschätzen, als bei einer kurzen globalen Sperre.

von Wilhelm M. (wimalopaan)


Lesenswert?

Die atmegas haben keine Prioritäten. Eine isr wird also nicht 
unterbrochen. Sofern man nicht die Interrupts wieder einschaltet.

von Carl D. (jcw2)


Lesenswert?

Wilhelm M. schrieb:
> Die atmegas haben keine Prioritäten. Eine isr wird also nicht
> unterbrochen. Sofern man nicht die Interrupts wieder einschaltet.

Wenn man Int0 sperrt, dann kann sich ein TWIint vordrängeln, das war 
gemeint. Den der nicht laufende Int0 kann leider die "niedriger 
priorisierten", weil mit höherer Nummer, Int's nicht aussperren.

von Walter S. (avatar)


Lesenswert?

....

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Paul H. schrieb:
> d.h. ich habe ab dem Compare-Interrupt 64 Takte Zeit
> um den Timer zu nullen. Das dürfte doch locker reichen, oder?

Wenn noch andere Interrupts verwendet werden, kann ein aktiver Handler 
schnell mal 64 Zyklen Verzögerung überschreiten.

von Carl D. (jcw2)


Lesenswert?

Walter S. schrieb:
> ....

Was jetzt? Walter oder W.? S. Oder S.?

Und wenn schon sparen, warum nicht einfach, wenn man ja eh davon 
ausgeht, das keine 256 Interrupt inzwischen passieren, (uint8_t)Ticks 
vergleichen?

von ProjektX (Gast)


Lesenswert?

Leutchen, Moment mal - geht es uns um die Robustheit und Ästhetik des 
Programms oder betreiben wir gerade (Takte-) Erbsenzählerei?

An erster Stelle steht die Robustheit. Wenn wir, um eine Zählvariable zu 
sichern, die in einer ISR inkrementiert wird, kurzfristig die Interrupts 
ausschalten (oder wirklich nur den dazugehörigen - dies aber von Fall zu 
Fall unterschiedlich) haben wir nun mal ein in erster Linie robustes 
Ding.

Dann suchen wir, fallabhängig, eine Kompromisslösung zwischen leichtem 
Leseverständnis (Ästhetik/Optik). Und erst hier überlegt man sich, ob es 
sich lohnt, paar Takte zu sparen, dafür aber ggf. mit 
nicht-/unterbrechbaren Interruptroutinen, Semaphoren etc. zu arbeiten.

Als eine mögliche Lösung, um Abfragen zu vermeiden: Man führt einen 
künstlichen Interrupt herbei. Sprich, PinChangeInterrupt auf einen Pin 
und diesen Pin (sobald eine Variable aus der ISR gesichert werden muss) 
umschalten. Und in dieser PCINT-ISR sichert man die Variable.

von ProjektX (Gast)


Lesenswert?

> Dann suchen wir, fallabhängig, eine Kompromisslösung zwischen leichtem
> Leseverständnis (Ästhetik/Optik) und Resourcenschonung.

Satzbau und so.

von ProjektX (Gast)


Lesenswert?

Was ich mit dem Ganzen sagen will - ob Abfrage eines Flags oder 
Kopiervorgang einer 32-bit-Variable. Wir kommen von den Takten her auf 
(beinah) das Gleiche.

P.S.: Der PCINT-Fall war nicht auf den ATmega8 bezogen, da er keine 
Interruptvektoren dafür hat.

von Daniel A. (daniel-a)


Lesenswert?

Ich habe gerade keinen avr-gcc 4.9, aber müsste das ganze nicht auch mit 
c11 stdatomic.h gehen?
http://en.cppreference.com/w/c/atomic

von Wilhelm M. (wimalopaan)


Lesenswert?

Compiliert zumindest mal mit avr-gcc >= 6.2. Ich habe es aber noch nicht 
getestet ...

von Rolf M. (rmagnus)


Lesenswert?

Carl D. schrieb:
> Wilhelm M. schrieb:
>> Die atmegas haben keine Prioritäten. Eine isr wird also nicht
>> unterbrochen. Sofern man nicht die Interrupts wieder einschaltet.
>
> Wenn man Int0 sperrt, dann kann sich ein TWIint vordrängeln, das war
> gemeint. Den der nicht laufende Int0 kann leider die "niedriger
> priorisierten", weil mit höherer Nummer, Int's nicht aussperren.

Wenn der TWI-Interrupt ausgelöst wurde und während seiner Abarbeitung 
der INT0 ausgelöst wird, muss dieser so oder so warten, bis der TWI 
fertig ist, also muss der INT0 in dem Fall sowieso mit der Verzögerung 
klar kommen können.

von Peter D. (peda)


Lesenswert?

Rolf M. schrieb:
> also muss der INT0 in dem Fall sowieso mit der Verzögerung
> klar kommen können.

Aber nur für die Zeit eines Interrupts niederer Priorität. Ohne globale 
Sperre können sich jedoch mehrere Interrupts dazwischen drängen.

von Jobst M. (jobstens-de)


Lesenswert?

Paul H. schrieb:
> und setzt dann den Timer wieder auf 0.

Das kann die Timer-Hardware aber auch selber. Viel genauer.


Gruß

Jobst

von Rolf M. (rmagnus)


Lesenswert?

Peter D. schrieb:
> Rolf M. schrieb:
>> also muss der INT0 in dem Fall sowieso mit der Verzögerung
>> klar kommen können.
>
> Aber nur für die Zeit eines Interrupts niederer Priorität. Ohne globale
> Sperre können sich jedoch mehrere Interrupts dazwischen drängen.

Ok. Was ist für dich denn ein Interrupt niederer Priorität? Man kann das 
ja nicht einstellen. Es ist einfach die Reihenfolge aufsteigender 
Interruptvektoren und kann damit höchstens zufällig dem entsprechen, was 
für dich gerade am besten ist. Und auf einem anderen µC ist die 
Reihenfolge ggf. auch anders, also schreibe ich meine Programme in der 
Regel eh so, dass sie wenn's irgendwie geht nicht von einer bestimmten 
Rangfolge abhängen.

von W.S. (Gast)


Lesenswert?

m.n. schrieb:
> Bedenke, daß Deine Abfrage zu jeder Zeit von einem anderen, längeren
> Interrupt unterbrochen werden kann. Bei
>
> if (iieks!=gieks) L = iTicks;
>
> ist nicht sichergestellt, daß iTicks bei der Zuweisung konstant bleibt.

Wie kommst du darauf? Ich hab ja nicht dazwischen geschrieben 
"wait(1ms)" oder so.
Sicher ist, daß immer dann, wenn iieks <> gieks ist, ganz gewiß ein 
Systick-Interrupt dazwischen gewesen ist.

Natürlich ist das alles unter der Annahme konstruiert, daß so ein ATmega 
nicht derart schnarchlangsam ist, daß er sich eine ganze Millisekunde in 
einer andersweitigen ISR aufhält.

Wenn doch, dann ist er für die konkrete Aufgabe ohnehin 
unterdimensioniert und man sollte dann einen 32 Bit µC nehmen, wo das 
ganze Theater von sich aus hinfällig ist.

Jetzt könnte man auf die Idee kommen

while (iieks!=gieks) { gieks = iieks; L = Ticks; }

zu schreiben, aber so langsam wird das Ganze ein bissel irrational. Wer 
sowas tatsächlich zu brauchen vermeint, nehme nen billigen Cortex 
Msonstwas und fertig.

W.S.

von m.n. (Gast)


Lesenswert?

W.S. schrieb:
> Natürlich ist das alles unter der Annahme konstruiert,

Es ist einfach nicht 100% sicher, was zu ganz bösen Fehlern führen kann, 
die alle paar Tage auftreten und kaum zu finden sind. Da ist eine 
globale Sperre mit cli() - sei() transparenter und zuverlässig.

Peter D. schrieb:
> m.n. schrieb:
>> Oder man sperrt nur die ISR-Quelle, die stören könnte:
>
> Davon halte ich garnichts. Dann könnten sich Interrupts mit niederer
> Priorität vordrängeln.

Ich setze meine Prioritäten anders. Daß eine untergeordnete Funktion die 
gesamte Interruptverarbeitung des Prozessors sperren kann, ist 
eigentlich schon heftig. Dieser Auslesefunktion sollte man garkeine 
Priorität geben.
Wenn ich selektiv Interruptquellen abschalte, dann nicht mit dem Ziel, 
sich Interrupts niedriger Priorität - was immer das bei einem ATmega 
bedeuten soll - vordrängeln zu lassen, sondern Interrupts hoher Frequenz 
nicht auszubremsen.

Letztlich hängt das Vorgehen davon ab, ob sich beim Programm diverse 
ISRs um schnellste Bedienung schlagen müssen, oder es sich, wie im 
vorliegenden Fall, nur um einen 'Dreizeiler' handelt. Da kann man es 
dann so machen, wie es gerade passend erscheint.

von Peter D. (peda)


Lesenswert?

m.n. schrieb:
> Daß eine untergeordnete Funktion die
> gesamte Interruptverarbeitung des Prozessors sperren kann, ist
> eigentlich schon heftig.

Nein ist es nicht!
4 Bytezugriffe dauern 8 Zyklen. Mit CLI/SEI sind das 11 Zyklen 
Interruptsperre. Mit solch kurzen Sperrzeiten muß Deine Applikation 
klarkommen, ohne jedes wenn und aber.
Zum Vergleich, z.B. jedes RET sind schon 4 Zyklen Interruptsperre.

Jeder zwischengedrängelter Interrupt kann dagegen leicht 100 Zyklen oder 
länger dauern. Das kann schon heftige Seiteneffekte geben, wenn sich 
davon mehrere kaskadieren.
Viel Spaß dann beim Debuggen, wenn das Programm crasht, sobald 3 
Interrupts pending sind. Derart seltene Fehlerbedingungen muß man schon 
im Ansatz vermeiden.

von Paul H. (powl)


Lesenswert?

Wenn ich nur den Compare-Match Interrupt sperre, dann muss ich halt 
hinterher dafür sorgen, dass der verpasste Aufruf der ISR nachgeholt 
wird. Glücklicherweise ist mein Code nicht so kompliziert, dass ich mir 
darüber Gedanken machen müsste.

von Carl D. (jcw2)


Lesenswert?

Paul H. schrieb:
> Wenn ich nur den Compare-Match Interrupt sperre, dann muss ich halt
> hinterher dafür sorgen, dass der verpasste Aufruf der ISR nachgeholt
> wird. Glücklicherweise ist mein Code nicht so kompliziert, dass ich mir
> darüber Gedanken machen müsste.

Solange CompA nicht alle 11Takte kommt, sprich in der Sperrzeit 2 davon 
kommen, macht der AVR das für dich. Sobald er wieder interrupten darf 
;-)

: Bearbeitet durch User
von Paul H. (powl)


Lesenswert?

Ach die ISR wird dennoch ausgeführt sobald ich den Interrupt wieder 
aktiviere und die Interrupt-Flag noch gesetzt ist?

von Carl D. (jcw2)


Lesenswert?

Paul H. schrieb:
> Ach die ISR wird dennoch ausgeführt sobald ich den Interrupt wieder
> aktiviere und die Interrupt-Flag noch gesetzt ist?

Überleg mal was andernfalls bei CLI .... SEI passieren würde. Alle Int's 
darin untergehen? Man muß nur zeitnah wieder SEIen, damit die 1-Bit 
Zähler namens Interrupt-Flag in TIFR/und wie sie alle heißen, nicht 
überlaufen.

Und man muß natürlich kurz genug sperren, um Zeitvorgaben einzuhalten. 
Bei den (ohne Tricks) me-only ISR's der AVRs hat man damit entweder kein 
Problem oder kennt sich aus.

von Rainer B. (katastrophenheinz)


Lesenswert?

Paul H. schrieb:
> Ach die ISR wird dennoch ausgeführt sobald ich den Interrupt wieder
> aktiviere und die Interrupt-Flag noch gesetzt ist?

Ja. RTFM im Abschnitt "Reset and Interrupt Handling"
...
If an interrupt condition occurs while the corresponding interrupt 
enable bit is cleared, the Interrupt Flag will be set and remembered 
until the interrupt is enabled, or the flag is cleared by software. 
Similarly, if one or more interrupt conditions occur while the global 
interrupt enable bit is cleared, the corresponding Interrupt Flag(s) 
will be set and remembered until the global interrupt enable bit is set, 
and will then be executed by order of priority.
...

von Einer K. (Gast)


Lesenswert?

Ja, natürlich....




Ein einzelnes Statement des Hauptprogramms wird da zwischen ausgeführt.

von m.n. (Gast)


Lesenswert?

Peter D. schrieb:
> Jeder zwischengedrängelter Interrupt kann dagegen leicht 100 Zyklen oder
> länger dauern. Das kann schon heftige Seiteneffekte geben, wenn sich
> davon mehrere kaskadieren.

Ja, die Welt ist böse und wird auch bald untergehen ;-)

von S. R. (svenska)


Lesenswert?

Das Flag sagt ja nur, dass ein Interrupt "aufgetreten, aber noch nicht 
verarbeitet" ist. Warum (selektiv abgeschaltet, cli, gerade langen 
Befehl ausgeführt, andere ISR aktiv) ist ihm egal.

von W.S. (Gast)


Lesenswert?

Yeah... ihr haut euch ja immer noch.

Deswegen nochmal:
Wenn man seinen 32 Bit Wert nicht atomar geladen kriegt, dann muß man 
eben eines von Folgendem tun:
1. die Sache anders lösen ohne Konflikte heraufzubeschwören
2. die zwei Instanzen so schreiben, daß sie sich irgendwie verständigen
3. Mit dem Kriegshammer namens "Interrupts ausschalten" daherkommen.

Letzteres finde ich obersch....
Version 1 wäre mein Favorit
Version 2 hatte ich ja schon skizziert.

Ein Vorschlag zu Version 1: Die ISR soll's richten:
1
bool  hilfmir;
2
unsigned long Ticks, UETicks;
3
void ISR(void)
4
{ ...
5
  Ticks++;
6
  if (hilfmir) UETicks = Ticks;
7
  hilfmir = false;
8
}
9
10
unsigned long GetUhrzeit(void)
11
{ while (hilfmir);
12
  return UETicks;
13
}
Die Grundschleife in main setzt ganz einfach immer mal 'hilfmir' und 
macht ansonsten nix in der Sache. Bei nächstbester Gelegenheit 
aktualisiert die ISR die Variable UETicks und ab da ist UETicks beliebig 
oft lesbar ohne jeglichen Zoff. Allerdings sollte man keine Ewigkeit 
darauf vertrauen, daß sie aktuell ist. Wer dies nur jeweils einmal 
braucht, der kann natürlich auch nach jedem Lesen von UETicks hilfmir 
sofort setzen.

W.S.

von (prx) A. K. (prx)


Lesenswert?

Würde es trotzdem volatile machen.

von Rolf M. (rmagnus)


Lesenswert?

S. R. schrieb:
> Das Flag sagt ja nur, dass ein Interrupt "aufgetreten, aber noch nicht
> verarbeitet" ist. Warum (selektiv abgeschaltet, cli, gerade langen
> Befehl ausgeführt, andere ISR aktiv) ist ihm egal.

Es ist eigentlich ganz einfach: Nach jedem Befehl prüft der AVR quasi 
pro Interrupt drei Bits ab, nämlich die globale Interrupt-Freigabe, das 
Enable-Bit des jeweiligen Interrupts und sein Interrupt-Flag. Sind alle 
drei gesetzt, werden globale Freigabe und das Interrupt-Flag gelöscht 
und dann der Interrupt-Vektor angesprungen. In welcher Reihenfolge diese 
drei Bits gesetzt werden, ist dabei völlig unerheblich. Sobald nach 
einem Befehl alle drei gesetzt sind, kommt der Interrupt. Das 
Interrupt-Flag wird immer dann automatisch gesetzt, wenn die 
Interrupt-Bedingung eingetreten ist - und bleibt solange gesetzt, bis 
entweder der Interrupt auch ausgeführt wird oder das Bit manuell 
gelöscht wird.

W.S. schrieb:
> Yeah... ihr haut euch ja immer noch.
>
> Deswegen nochmal:
> Wenn man seinen 32 Bit Wert nicht atomar geladen kriegt, dann muß man
> eben eines von Folgendem tun:
> 1. die Sache anders lösen ohne Konflikte heraufzubeschwören
> 2. die zwei Instanzen so schreiben, daß sie sich irgendwie verständigen
> 3. Mit dem Kriegshammer namens "Interrupts ausschalten" daherkommen.
>
> Letzteres finde ich obersch....

Es ist der einfachste Weg und in vielen Fällen völlig ausreichend. Man 
kann sich natürlich stattdessen auch verkünsteln, um eine Lösung für ein 
nicht existentes Problem zu implementieren.

> Ein Vorschlag zu Version 1: Die ISR soll's richten:
> bool  hilfmir;
> unsigned long Ticks, UETicks;
> void ISR(void)
> { ...
>   Ticks++;
>   if (hilfmir) UETicks = Ticks;
>   hilfmir = false;
> }
>
> unsigned long GetUhrzeit(void)
> { while (hilfmir);
>   return UETicks;
> }
> Die Grundschleife in main setzt ganz einfach immer mal 'hilfmir' und
> macht ansonsten nix in der Sache. Bei nächstbester Gelegenheit
> aktualisiert die ISR die Variable UETicks und ab da ist UETicks beliebig
> oft lesbar ohne jeglichen Zoff.

Bis dahin ist allerdings das Hauptprogramm komplett blockiert.

A. K. schrieb:
> Würde es trotzdem volatile machen.

Zumindest hilfmir und UETicks müssen selbstverständlich volatile sein, 
sonst wird das nix.

von Axel S. (a-za-z0-9)


Lesenswert?

W.S. schrieb:
> Wenn man seinen 32 Bit Wert nicht atomar geladen kriegt, dann muß man
> eben eines von Folgendem tun:
> 1. die Sache anders lösen ohne Konflikte heraufzubeschwören
> 2. die zwei Instanzen so schreiben, daß sie sich irgendwie verständigen
> 3. Mit dem Kriegshammer namens "Interrupts ausschalten" daherkommen.
>
> Letzteres finde ich obersch....

Über Geschmack soll man ja bekanntlich nicht streiten, aber ich finde 
dein Argument geht hier weit am Ziel vorbei. Interrupts haben immer 
Latenz, weil die CPU ja erst den laufenden Befehl fertig machen muß, 
bevor sie in die ISR springen kann. Wenn man jetzt zwei Ladebefehle in 
ein CLI/SEI Paar einklammert, dann erhöht man die Interrupt-Latenz 
kurzzeitig etwas. So what?

Das ist doch Pillepalle.

Semaphore ergeben erst dann einen Vorteil, wenn der zu schützende 
Codeabschnitt länger ist.

von W.S. (Gast)


Lesenswert?

Axel S. schrieb:
> Das ist doch Pillepalle.

Bei einem simplen ATmega - ja. Hab vor Urzeiten beim Z80 auch so etwa 
programmiert, da allerdings deutlich mehr in Assembler. Wobei der mit HL 
auch echte 16 Bit Zugriffe konnte und nicht viermal zu je 8 Bit.

Hier ging's mir um das Prinzip, denn bei einigen Architekturen (jaja, 
auch ARM7TDMI) kommt es drauf an, in welchem Mode der Prozessor läuft. 
Da kann man nicht so einfach mal eben die Interrupts sperren. Allerdings 
ist dort das Thema 32 Bit Zugriff eben kein Thema. Trotzdem: Für sowas 
hat man beim ARM incl. Cortex den SVC, der solche Dinge erledigen kann. 
Allerdings kann wiederum der GCC keine SVC's...

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