Hallo!
Ich meine einmal irgendwo gelesen zu haben, dass, wenn ein Interrupt
ausgelöst wird und man beispielsweise gerade mitten in einer Aktion mit
z.B. 32-bit Integern ist (auf einem 8-bit µC), dass dann Fehler
auftreten können, weil die Register vermischt werden (können).
Stimmt das?
Und noch eine weitere kleine Frage:
Wenn man am Anfang der Programmschleife mit cli() Interrupts deaktiviert
und am Ende wieder mit sei() aktiviert, werden dann auch in der
Zwischenzeit auftretende Interrupts ausgeführt? Oder verfallen die dann?
Danke!
Im Interrupt muss man ohnehin immer die Register sichern, die man
letztendlich auch nutzt und beim Beenden wieder zurück zu spielen. Dann
macht das nichts mehr aus. Es ist nur darauf zu achten, dass
gleichzeitig (also Interrupt und außerhalb) benutzte Werte atomar
zugegriffen werden oder ansonsten muss man Interrupts sperren. Das muss
man sich vorher genau überlegen, wie die Daten genutzt werden und wie
der Datenaustausch und wann statt finden sollte. Z.b. ist es in einer
Rechnung auch nicht gut, wenn sich plötzlich Werte ändern, etc.
Interrupts an sich verfallen nicht, aber es kann sein, das man dann
einen Überlauf oder Datenverlust bekommt, wenn diese nicht zeitnah
verarbeitet werden. Dann wird z.B. ein Register überschrieben, sie
werden nicht gestapelt, also dann 2 mal getriggert o.ä. Evtl. bekommt
man auch andere Probleme, wenn man versucht über den Interrupt Timings
zu bestimmen. Normalerweise ist es nicht sinnvoll größere Codeblöcke mit
cli und sei zu sichern.
me_and_my_µC schrieb:> dass dann Fehler> auftreten können, weil die Register vermischt werden (können).> Stimmt das?
JEIN.
Es gibt Architekturen da werden die Register durch die CPU gesichert,
dann kann der Interrupt machen was er will. Nachteil: Zeit für das
Sichern geht mit in die Interruptroutine mit ein.
Dann gibt es Architekturen da sichert die CPU von sich aus Garnichts,
dafür macht der Compiler einer Hochsprache das da er ja weiß, welche
Register er benötigt.
Und der letzte Fall ist die Architektur die nicht sichert die du mit
Assembler programmierst, bei der Du selbst das Vergnügen hast über die
Verwendung Deiner Register und die Sicherung dieser die Übersicht zu
behalten.
me_and_my_µC schrieb:> Wenn man am Anfang der Programmschleife mit cli() Interrupts deaktiviert> und am Ende wieder mit sei() aktiviert, werden dann auch in der> Zwischenzeit auftretende Interrupts ausgeführt? Oder verfallen die dann?
Auch das hängt ein Bisschen von den Umständen ab.
Die CPU wird normalerweise einen Interrupt auslösen und sich das anhand
von Flags merken. Wenn du dann die Interrupts wieder zulässt werden die
in der bestimmten Priorität abgearbeitet.
ABER: treten Interrupts auf die nicht unterschieden werden können oder
tritt ein und derselbe Interrupt in der gesperrten Zeit mehrfach auf
geht das unter.
Und: NEIN, Interrupts sind keine Gefahren nur der nachlässige und nicht
verstandene Umgang damit :)
rgds
@ me_and_my_µC (Gast)
>ausgelöst wird und man beispielsweise gerade mitten in einer Aktion mit>z.B. 32-bit Integern ist (auf einem 8-bit µC), dass dann Fehler>auftreten können, weil die Register vermischt werden (können).>Stimmt das?
Nein. Es geht dabei um globale Variablen.
https://www.mikrocontroller.net/articles/Interrupt#Interruptfeste_Programmierung>Wenn man am Anfang der Programmschleife mit cli() Interrupts deaktiviert>und am Ende wieder mit sei() aktiviert, werden dann auch in der>Zwischenzeit auftretende Interrupts ausgeführt?
Natürlich nicht.
> Oder verfallen die dann?
Nur dann, wenn wähend der Sperre mehr als ein Interrupt vom gleichen Typ
auftritt. Siehe Interrupt.
Falk B. schrieb:>>Wenn man am Anfang der Programmschleife mit cli() Interrupts deaktiviert>>und am Ende wieder mit sei() aktiviert, werden dann auch in der>>Zwischenzeit auftretende Interrupts ausgeführt?>> Natürlich nicht.
Ich wittere ein Missverständnis zwischen dem was der Fragesteller fragen
wollte, wie er seine Frage formuliert hat und wie der Antwortende die
Frage verstanden hat.
Tritt nach setzen von cli() eine InterruptANFORDERUNG auf, dann wird
natürlich der Interrupt nicht sofort ausgeführt, denn das ist wegen cli
gesperrt. Aber das Flag wird gesetzt. Durch Aufruf von sei() wird die
Interruptsperre aufgehoben. Jetzt darf die Interrupt Service Routine
ausgeführt werden.
Also eine Interruptanforderung wird durchaus auch registriert während
der Prozessor in einem Block zwischen cli und sei ist. Die Behandlung
dieser Interruptanforderung geschieht aber erst nach Aufruf von sei und
wird somit verzögert durchgeführt.
6a66 schrieb:> Es gibt Architekturen da werden die Register durch die CPU gesichert,> dann kann der Interrupt machen was er will.
Das ist nicht der Punkt.
Es geht um den Zugriff auf Variablen, die sowohl von der Interrupt-
Serviceroutine (ISR) als auch dem Hauptprogramm genutzt werden. Hier muß
sichergestellt werden, daß der Zugriff atomar erfolgt. Wenn z.B. ein AVR
eine 16-Bit Variable aus dem RAM liest, dann sind dazu mindestens zwei
Maschinenbefehle notwendig. Der Zugriff ist nicht atomar, wenn zwischen
dem Lesen des Low- und des High-Teils ein Interrupt stattfinden könnte.
Wenn nun die ISR genau diese 16-Bit Variable verändert, dann liest das
Hauptprogramm die eine Hälfte der Variable vom alten Wert und die zweite
Hälfte vom neuen Wert. Und dann kann in der Folge alles mögliche schief
gehen. Ganz abgesehen davon, daß solche Fehler extrem schwer zu finden
sind.
Innerhalb der ISR besteht das Problem nicht, weil innerhalb einer ISR
standardmäßig keine weiteren Interrupts erlaubt sind. Außerhalb der ISR
ist die einfachste Lösung, Interrupts vor dem Lesen der Variable zu
sperren und danach wieder zu erlauben.
Die AVR-libc hat dafür Makros:
1
#include<util/atomic.h>
2
...
3
volatileuint16_tfoo;
4
...
5
uint16_tbar;
6
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
7
bar=foo;
8
}
9
...
Zu Beginn des ATOMIC_BLOCK werden Interrupts gesperrt, alles was
innerhalb des Blocks geschieht, kann also nicht unterbrochen werden.
Beim Verlassen des Blocks wird der alte Zustand der Interrupts
(erlaubt/gesperrt) wiederhergestellt.
siehe
http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
Wenn du auf einem AVR eine uint64_t verwendest und während des
Schreibvorganges dieser variable ein Interrupt auftritt, kann es zu
Schwulitäten kommen.
Denn eine 64bit Zahl wird durch acht 8Bit Register/Variablen
ausgedrückt. Nun kann es sein, dass du vier Bytes geschrieben hast, vier
folgen noch - doch jetzt kommt ein Interrupt.
Wenn du dann im Interrupt diese int64_t verwendest steht da mist drin,
da genau im Schreibvorgang dieser Variable unterbrochen wurde.
Nach dem Interrupt wird der Schreibvorgang ganz normal fortgesetzt.
Du kannst hier z.B. atomatre Hilfsvariablen verwenden, die
signalisieren, dass neue Daten in der uint64_t vorhanden sind. Oder eine
cli() sei() gemisch.
Aber vermischt wird nichts. Nach dem Interrupt arbeitet das Programm
weiter, als ob nichts war. Es sei denn, du machst im Interrupt selber
mist.
Die AVR sichern nicht mal das Flagregister, das muss man zu Fuß retten.
Der 6502 beispielsweise legte das automatisch auf den Stack und
restaurierte es beim Return-from-interrupt.
Okay, vielen Dank für eure Antworten!
Ich hätte allerdings noch eine andere kleine Frage:
ich habe einen Timer, der im 0.5µs Takt zählt und einen, der im 1s Takt
zählt.
Allerdings wird in der Interruptroutine von dem 1s-Timer eine Variable
erhöht, die in der Programmschleife ziemlich oft gelesen und teilweise
auch neu beschrieben wird. Es ist ein uint32_t.
Was ist der sicherste Weg, nur die Interrupts von dem 1s-Timer zu
deaktivieren (die Verzweigung dauert sicher keine Sekunde) und danach
wieder zuzulassen, ohne dass der 0.5µS-Timer dadurch behindert wird?
Die Interrupt-Freigabe (enable) für den 1 s Timer abzuschalten und
anschließend wieder einzuschalten. Dadurch werden alle anderen
Interrupts weiterhin bedient.
me_and_my_µC schrieb:> ohne dass der 0.5µS-Timer dadurch behindert wird?
Vergiß es, der AVR kann keinen Interrupt in nur 10 Zyklen (@20MHz)
ausführen.
Rechne je Interrupt wenigstens 100 Zyklen, damit auch das Main etwas
Rechenzeit bekommt.
me_and_my_µC schrieb:> Was ist der sicherste Weg, nur die Interrupts von dem 1s-Timer zu> deaktivieren (die Verzweigung dauert sicher keine Sekunde) und danach> wieder zuzulassen, ohne dass der 0.5µS-Timer dadurch behindert wird?
Auf den 1sec-Timer verzichten und im 0,5µS-Timer den Sekunden-Zähler um
1 inkrementieren, wenn ein interner Zähler den Stand 2000000 erreicht
hat.
Blöd nur, dass dadurch der 0,5µS-Timer um ein ganzes Stück länger in der
Ausführungszeit wird.
m.n. schrieb:> Die Interrupt-Freigabe (enable) für den 1 s Timer abzuschalten und> anschließend wieder einzuschalten.
Während eines Interrupts können sowieso standardmäßig erstmal keine
weiteren ISRs abgefackelt werden. Aber das ist ja auch gar nicht das
Problem.
Sondern:
Wenn die 1sec-ISR bereits läuft und es wird der 0,5µs-Timer-Interrupt
ausgelöst, kommt die ISR für den 0,5µs-Timer erst dann dran, wenn die
1sec-ISR sich beendet hat.
Wenn der (leider ungenannte) µC Interrupt-Prioritäten kennt, ließe sich
das leicht lösen. Bei einem 8-Bit-AVR wirds eklig, insbesondere dann,
wenn in der 1-Sek-ISR sowieso nur ein Statement steht, nämlich das
Inkrementieren des 1-Sekunden-Zählers. Jedes weitere Statement zur
Interrupt-Priorisierung kann dann das Timer-Verhalten vom 0,5µs-Timer
noch weiter verzerren.
EDIT:
Nach Peters Beitrag ist dieser jetzt sowieso obsolet, wenn es sich
tatsächlich um einen AVR handelt. ;-)
Frank M. schrieb:> m.n. schrieb:>> Die Interrupt-Freigabe (enable) für den 1 s Timer abzuschalten und>> anschließend wieder einzuschalten.>> Während eines Interrupts können sowieso standardmäßig erstmal keine> weiteren ISRs abgefackelt werden. Aber das ist ja auch gar nicht das> Problem.
Die Frage war, den 1 s Timer Interrupt zu deaktivieren ohne andere
Interruptquellen zu sperren, wie es mit cli() der Fall wäre.
Frank M. schrieb:> Nach Peters Beitrag ist dieser jetzt sowieso obsolet, wenn es sich> tatsächlich um einen AVR handelt. ;-)
Oder es sind einfach nur 0,5 ms gemeint ;-)
m.n. schrieb:> Die Frage war, den 1 s Timer Interrupt zu deaktivieren ohne andere> Interruptquellen zu sperren, wie es mit cli() der Fall wäre.
Ja, schon klar. Aber überlege mal: wann und wo, ohne den 0,5µs-Timer
(oder 0,5ms) zu beeinflussen?
Den 1-Sekunden-Timer generell zu deaktivieren dürfte keine Lösung sein -
außer Du zählst den 1-Sekunden-Zähler in der 0,5µs-ISR hoch - wie oben
beschrieben.
me_and_my_µC schrieb:> Allerdings wird in der Interruptroutine von dem 1s-Timer eine Variable> erhöht, die in der Programmschleife ziemlich oft gelesen und teilweise> auch neu beschrieben wird. Es ist ein uint32_t.> Was ist der sicherste Weg,...
Also, der sicherste Weg in ALLEN solchen Fällen ist der, erstmal
generell auf das temporäre Sperren von Interrupst zu verzichten. Also
kein cli und sei. Da man unter solcher Prämisse eben zu allen Zeiten
damit rechnen muß, daß einem ein Interrupt dazwischenkommt, besteht die
Hauptaufgabe eben darin, daß man sich irgend eine saubere
Synchronisation zwischen Grundprogramm und ISR ausdenkt.
An deiner Stelle würde ich so verfahren:
1. Beide Instanzen (Grundprogramm und ISR) besitzen jeweils ein
Kennbyte. Am Anfang sind beide Kennbytes gleich, z.B. Null.
2. Wenn nun die ISR dran ist, inkrementiert sie ihr Kennbyte.
3. Das Grundprogramm vergleicht in jedem chleifendurchlauf sein eigenes
Kennbyte mit dem Kennbyte der ISR und wenn beide ungleich sind, dann
macht sie folgendes:
3a) sie inkrementiert den besagten 1s-Timer
3b) sie inkrementiert ihr eigenes Kennbyte (ob die Kennbytes überlaufen,
ist dabei völlig egal)
So, das war's. Beide Instanzen kommen sich niemals gegenseitig ins
Gehege, weil keine von beiden schreibend auf ne Ressource der anderen
zugreift. Sicher gegenüber 8 Bit Systemen ist das auch, denn der lesende
Zugriff auf das Kennbyte der Gegenseite ist atomar. Und das Ganze
braucht auch nur 2 poplige Bytes RAM. Dafür spart man sich die 32 Bit
Inkrementierung in der ISR ein.
Nochwas:
me_and_my_µC schrieb:> ich habe einen Timer, der im 0.5µs Takt zählt..
Einen Timer oder etwas, das alle 500 ns einen Interrupt auslöst? Schreib
das mal klarer. Für einen µC, der im besten Falle 50 ns pro Befehl
braucht, halte ich den 500 ns Interrupt für astronomisch.
W.S.
W.S. schrieb:> Beide Instanzen kommen sich niemals gegenseitig ins> Gehege, weil keine von beiden schreibend auf ne Ressource der anderen> zugreift.
Das ist richtig, aber: Es setzt voraus, dass allerspätestens nach 256
ISR-Aufrufen das Hauptprogramm zum Zug kommt. Ist das nicht gesichert,
wirds schon wieder schwierig. 16-bit-Variable? Dann hast erst wieder das
non-atomic Problem...
Stefan U. schrieb:> Kleiner Hinweis: Dass es um einen AVR geht, ist bislang nur eine> Vermutung. Also verrennt euch da mal nicht zu weit in die falsche> Richtung.AVR ist Mutmaßung, aber "8-Bit-uC" steht im Eröffnungsposting. Und mir
wäre kein 8Bitter bekannt, der 16- oder 32-bit-Werte atomar lesen
könnte...
Entschuldigung, ich habe mich tatsächlich ziemlich doof ausgedrückt. Das
gebe ich zu. Da kam ich leider mit einem anderen Projekt durcheinander,
bei dem es 0.5ms Sekunden sind.
Der gemeinte Timer löst alle 50µS ein Interruptroutine aus.
Der verwendete µC ist nur ein kleiner ATmega8A.
Und ja, natürlich. Es handelt sich um einen volatile uint32_t.
Aber aus euren Antworten schließe ich, dass sich das wohl nicht so
einfach umsetzen lässt.
Dann werde ich mir noch einmal etwas überlegen.
Die 50µs Routine würde ich nur ungern erweitern. Die ist für meinen
Geschmack sowieso schon zu lang.
Ich werde mir noch etwas ausdenken.
Eventuell eine Kopie der Variable während der ich Interrupts verhindere.
Nicht optimal, aber vermutlich fast das beste. Oder wie seht ihr das?
Manchmal hat man die Möglichkeit, in der ISR nur einen "kleinen" 8bit
Counter zu benutzen. Dann kann das Hauptprogramm diesen auf 32bit oder
gar 64bit erweitern. Vorausgesetzt natürlich, dass der 8bit Zähler nicht
überläuft, bevor das Hauptprogramm ihn ausliest.
W.S. schrieb:> Also, der sicherste Weg in ALLEN solchen Fällen ist der, erstmal> generell auf das temporäre Sperren von Interrupst zu verzichten. Also> kein cli und sei.
Der sicherste Weg ist, genau das zu tun. Man muß ja nur den einen
Zugriff atomar kapseln, d.h. die Sperre dauert etwa 10 Zyklen und so ein
kurzes Delay muß jeder Interrupt verkraften können.
Eine spezielle Quelle zu sperren, ist aufwendiger und kann Seiteneffekte
haben. Z.B. kann sich ein geringer priorisierter Interrupt vordrängeln
und die Pause wird dann erheblich größer als 10 Zyklen. Ein großer
Jitter ist aber bei Timerinterrupts oft nicht erwünscht. Das kann z.B.
eine Multiplexanzeige sichtbar flackern lassen.
Peter D. schrieb:> Ein großer> Jitter ist aber bei Timerinterrupts oft nicht erwünscht. Das kann z.B.> eine Multiplexanzeige sichtbar flackern lassen.
Dazu muß man sich aber schon sehr ungeschickt anstellen. Falls der
Jitter tatsächlich einmal stören sollte, nimmt man sowieso einen
Compare-Ausgang für jitterfreies Timing.
m.n. schrieb:> Falls der> Jitter tatsächlich einmal stören sollte, nimmt man sowieso einen> Compare-Ausgang für jitterfreies Timing.
Wie willst Du damit eine Anzeige multiplexen?
W.S. schrieb:> Beide Instanzen (Grundprogramm und ISR) besitzen jeweils ein> Kennbyte. Am Anfang sind beide Kennbytes gleich, z.B. Null.
Hi,
das nennt man wohl auch "Job-Flag".
Realisierbar mit "Job"-Register.
Beispiel für AVR:
; ISR wird durchlaufen und setzt Job-Flag am Ende:
;....
ldi temp, 0x01
mov job, temp
clr temp
;
ISR_1: ; ohne ISR-Durchlauf
sbrc job, 0
rjmp no_sync1
rcall char2lcd
clr job
ret
;
no_sync1: ; wieder vorbereiten für nächsten ISR-Durchlauf
clr job
ret
;
und im Analogieverfahren andere Bits desselben Registers "Job".
ciao
gustav
P.S.:
Dient zum Beispiel als Umschaltung einer Funkuhr auf "Gangreserve"-
interne Uhr
Peter D. schrieb:> m.n. schrieb:>> Falls der>> Jitter tatsächlich einmal stören sollte, nimmt man sowieso einen>> Compare-Ausgang für jitterfreies Timing.>> Wie willst Du damit eine Anzeige multiplexen?
Auf das gestörte Multiplexen von Anzeigen bezog sich, daß man sich sehr
ungeschickt anstellen muß, um dies zu erreichen: Beispielsweise
delay_ms() in anderen Interrupts regelmäßig und häufig verwenden.
Das Verwenden von vom Timer synchronisierten Ausgängen ist allgemein
gemeint, daß man damit Jitter durch vorübergehende Interrupts vermeiden
kann. Wenn man allerdings seine Anzeigen (spezieller Fall) per
Schieberegister ansteuert, kann man diese beschreiben und das
Übernahmesignal (strobe) per Compare-Ausgang erzeugen. Damit läuft die
Anzeige völlig ungestört.
Peter D. schrieb:> Der sicherste Weg ist, genau das zu tun.
Ach wo. Du bist mental gar sehr auf dem AVR-Niveau. Auf anderen
Architekturen läuft der größte Teil der Firmware im User-Modus und von
dort aus kannst du nichtmal den geringsten Interrupt sperren, wenn du
nicht sofort eine Exception auslösen willst, die dich schlichtweg aus
dem Rennen nimmt. Da gilt solchartiges Programmieren nämlich als
"Elefant im Porzellanladen".
Nein, es ist wirklich die beste und sauberste Art, erst garnicht auf
den Gedanken an Generalsperren von Interrupts zu denken, sondern sich
ein vernünftiges Protokoll zwischen den konkurrierenden Instanzen
auszudenken.
W.S.
W.S. schrieb:> Auf anderen> Architekturen läuft der größte Teil der Firmware im User-Modus und von> dort aus kannst du nichtmal den geringsten Interrupt sperren, wenn du> nicht sofort eine Exception auslösen willst, die dich schlichtweg aus> dem Rennen nimmt.
Ach komm, Du mußt doch keine riesen float Tabellen unter der Sperre
berechnen, sondern nur für den einen Zugriff auf die Variable sperren.
Wenige Zyklen Verzögerung muß jede Applikation abkönnen.
Natürlich gibt es auf komplexeren CPUs noch andere Methoden, einen
atomaren Zugriff abzusichern. Die sind auch notwendig, da ja auch noch
ein Cache mit ins Spiel kommt.
W.S. schrieb:> Auf anderen> Architekturen läuft der größte Teil der Firmware im User-Modus und von> dort aus kannst du nichtmal den geringsten Interrupt sperren,
Welche 8-Bit Architektur (um die geht es hier ja wohl) hat denn sowas?
Und wie sähe Deine Lösung für einen simplen 16-Bit Millisekunden-Zähler
aus, der in einem Systick-Interrupt hochgezählt wird, d.h. was wäre
Deine Alternative, um im Hauptprogramm eines 8-Bitters die ms-Systemzeit
in eine 16-Bit Variable zu lesen?
Thomas E. schrieb:> W.S. schrieb:>> Auf anderen>> Architekturen läuft der größte Teil der Firmware im User-Modus und von>> dort aus kannst du nichtmal den geringsten Interrupt sperren,>> Welche 8-Bit Architektur (um die geht es hier ja wohl) hat denn sowas?>> Und wie sähe Deine Lösung für einen simplen 16-Bit Millisekunden-Zähler> aus, der in einem Systick-Interrupt hochgezählt wird, d.h. was wäre> Deine Alternative, um im Hauptprogramm eines 8-Bitters die ms-Systemzeit> in eine 16-Bit Variable zu lesen?
Was ich mir eventuell überlegt habe, wäre, so etwas in der Art:
1
#include<stdint.h>
2
3
uint8_t*counter=NULL;
4
uint32_treal_counter;
5
6
intmain()
7
{
8
/* start timer */
9
10
while(1)
11
{
12
if(counter!=NULL&&*counter>0)
13
{
14
real_counter+=*counter;
15
16
if(*counter>=255)
17
{
18
uint8_ti=1;
19
for(;*(counter+i)>=255;++i)
20
real_counter+=*(counter+i);
21
real_counter+=*(counter+i);
22
23
free(counter);
24
}
25
}
26
}
27
}
28
29
ISR(TIMER_INTR_NAME)
30
{
31
if(counter==NULL)
32
{
33
counter=calloc(1,sizeof(uint8_t));
34
}
35
elseif(*counter>=255)
36
{
37
uint8_ti=1;
38
for(;*(counter+i)>=255;++i)
39
;
40
counter=realloc(counter,(i+1)*sizeof(uint8_t));
41
++*(counter+i);
42
}
43
else
44
{
45
*counter+=1;
46
}
47
}
Habe ich darin irgendeinen Denkfehler oder geht das so in etwa?
(Code ungetestet, nur eben als grobe Skizze geschrieben)
me_and_my_µC schrieb:> if(counter == NULL)> {> counter = calloc(1, sizeof(uint8_t));> }
Oh, oh, oh!
Das kann man eigentlich nur noch steigern, indem man das Byte bitweise
anfordert ;-)
Just in dem Moment, in dem ich es abgeschickt habe, fällt mir auf, dass
gar nicht gewährleistet ist, dass in der Schleife nicht die Größe von
counter verändert wird.
Auch das calloc in der ersten if-Verzweigung in ISR ist natürlich
Schwachsinn. Muss malloc mit einer folgenden Initalisierung mit 1 sein.
Könnte das jetzt so klappen?
1
#include<stdint.h>
2
3
uint8_t*counter=NULL;
4
uint8_tchanged;
5
6
intmain()
7
{
8
uint32_treal_counter=0;
9
10
/* start timer */
11
12
while(1)
13
{
14
while(changed)
15
{
16
retry=0;
17
18
if(counter!=NULL&&*counter>0)
19
{
20
real_counter+=*counter;
21
if(changed)
22
continue;
23
*counter=0;
24
25
if(*counter>=255)
26
{
27
uint8_ti=1;
28
for(;*(counter+i)>=255;++i)
29
real_counter+=*(counter+i);
30
real_counter+=*(counter+i);
31
32
if(changed)
33
continue;
34
free(counter);
35
counter=NULL;
36
}
37
}
38
}
39
40
changed=0;
41
}
42
}
43
44
ISR(TIMER_INTR_NAME)
45
{
46
changed=1;
47
48
if(counter==NULL)
49
{
50
counter=malloc(sizeof(uint8_t));
51
*counter=1;
52
}
53
elseif(*counter>=255)
54
{
55
uint8_ti=1;
56
for(;*(counter+i)>=255;++i)
57
;
58
counter=realloc(counter,(i+1)*sizeof(uint8_t));
59
++*(counter+i);
60
}
61
else
62
{
63
*counter+=1;
64
}
65
}
Verbesserungsvorschläge und Kritik sind ausdrücklich erwünscht.
Gerade eure Beiträge gesehen.
Meint ihr dynamische Speicheranforderung allgemein oder nur calloc?
calloc ist natürlich auch Schwachsinn. Ist schließlich nur einer und
muss dazu noch mit 1 initialisiert werden.
Dass dyn. Speicherverwaltung langsamer ist als der Stack ist klar. Aber
so langsam sollte das doch auch nicht sein, oder?
me_and_my_µC schrieb:> Meint ihr dynamische Speicheranforderung allgemein oder nur calloc?
Generell. ISRs sollte man einfach und kurz halten.
Diese Funktionen sind evtl nicht in Interrupts einsetzbar, wenn sie auch
ausserhalb davon eingesetzt werden, wenn nicht reentrant.
me_and_my_µC schrieb:> Meint ihr dynamische Speicheranforderung allgemein
Ja.
Alles, was man zur Compilezeit abschätzen kann, sollte auch dort
definiert werden.
Da malloc selber auch Speicher zur Verwaltung benötigt, lohnt es sich
für Blöcke <16Byte in der Regel nicht.
Auf kleinen MCs hat man oft zur Laufzeitoptimierung nur eine einfache
Verwaltung (keine Defragmentierung), daher kann realloc schnell gegen
die Wand laufen.
Ansonsten Vorschläge?
Oder soll ich sonst einfach nur einen einzelnen uint8_t hochzählen in
der Hoffnung, dass spätestens nach 255 Interrupts die Programmschleife
den richtigen Counter hochgezählt hat?
Also dann:
Hm, also so ganz blicke ich nicht durch, was Dein Programm da so
kompliziertes macht und warum.
Frage: was spricht nun eigentlich gegen das direkte Hochzählen des
32-Bit Counters in der ISR und eine kurze Interrupt-Sperre beim Zugriff
auf den Counter im Hauptprogramm? Ist die ISR so zeitkritisch, daß es
nicht für die Verarbeitung von 4 Bytes reicht? Oder sind die
ISR-Latenzen so kritisch, daß Du die Interrupts nicht für die Zeitdauer
des Zugriffs auf die 32-Bit Zählervariable sperren kannst? Oder läuft
Dein ATMega im Supervisor-Modus, weshalb Du keine CLI- und SEI-Befehle
in Deinem im User-Mode ablaufenden Programm benutzen kannst (ich wusste
gar nicht, daß der ATMega das kann...)?
P.S.: vergiss nicht, Variablen, die durch die ISR verändert werden und
auf die auch im Hauptprogramm zugegriffen wird, als "volatile" zu
deklarieren!
me_and_my_µC schrieb:> Könnte das jetzt so klappen?
ich verstehe zwar nicht warum das Programm so kompliziert sein muss,
aber schon beim Überfliegen sehe ich:
volatile fehlt
Zugriff auf counter ist nicht atomar!
me_and_my_µC schrieb:> uint8_t i = 1;> for(; *(counter + i) >= 255; ++i)> ;
und was ist denn das, du lässt dir ein Byte allozieren und rödelst dann
fröhlich auf den darauf folgenden Bytes rum?
Anstatt Interrupts zu sperren kann man auch einfach den Counter 2x
lesen. Wenn diese Beiden Werte nicht gleich sind, liest man noch einmal,
dann hat man den richtigen Wert.
So macht man das bei der RTC im STM32F1 (allerdings nicht wegen ISR,
sondern weil der Counter asynchron in HW implementiert ist).
Thomas E. schrieb:> Oder läuft> Dein ATMega im Supervisor-Modus, weshalb Du keine CLI- und SEI-Befehle> in Deinem im User-Mode ablaufenden Programm benutzen kannst (ich wusste> gar nicht, daß der ATMega das kann...)?
Kann er auch nicht. Es gibt beim AVR8 nur zwei Instruktionen, für die es
sowas wie Berechtigungsebenen für die Ausführung gibt. Das sind: lpm und
spm.
Stefan U. schrieb:> So macht man das bei der RTC im STM32F1 (allerdings nicht wegen ISR,> sondern weil der Counter asynchron in HW implementiert ist).
Klar, da würde ja das Sperren der Interrupts auch nichts nützen.
Zweimal lesen und vergleichen gibt wahrscheinlich etwas mehr Code, aber
man blockiert dafür die interrupts nicht - das muss man halt abwägen, ob
einem die Codegröße oder die Interrupt-Latenz wichtiger ist.
Wenn sicher ist, daß es um einen simplen Counter geht, reicht es evtl.
aus, nur das niederwertige Byte zu vergleichen - das müsste etwas
kürzer/schneller sein.
Walter schrieb:> me_and_my_µC schrieb:>> Könnte das jetzt so klappen?>> ich verstehe zwar nicht warum das Programm so kompliziert sein muss,> aber schon beim Überfliegen sehe ich:>> volatile fehlt>> Zugriff auf counter ist nicht atomar!
volatile habe ich vergessen, das stimmt.
counter sind nur mehrere unabhängige uint8_t, auf die doch eigentlich
atomar zugegriffen werden kann.
Ob sich in der Zwischenzeit die Größe von counter verändert hat wird ja
auch immer wieder über changed überprüft. Sollte es sich verändert
haben, beginnt die Schleife von vorne. Der Timer muss dafür natürlich
ziemlich langsam zählen.
Aber dadurch, dass malloc in ISR wohl nicht zu empfehlen ist, klappt das
sowieso nicht.
Walter schrieb:> me_and_my_µC schrieb:>> uint8_t i = 1;>> for(; *(counter + i) >= 255; ++i)>> ;>> und was ist denn das, du lässt dir ein Byte allozieren und rödelst dann> fröhlich auf den darauf folgenden Bytes rum?
Es wird gezählt, wie viele Bytes schon voll sind. Die mehreren Bytes
sind ja nur dafür da, um sicherzustellen, dass, falls die if-Abfrage in
der Programmschleife nicht nach 255 Interrupts drankam.
Das letzte (noch freie) Byte wird dann hochgezählt.
Wobei das tatsächlich nicht so klappt. Das müsste man noch einmal ein
wenig umschreiben.
Ist aber ja, wie gesagt, anscheinend sowieso nicht sinnvoll.
Stefan U. schrieb:> Anstatt Interrupts zu sperren kann man auch einfach den Counter 2x> lesen. Wenn diese Beiden Werte nicht gleich sind, liest man noch einmal,> dann hat man den richtigen Wert.>> So macht man das bei der RTC im STM32F1 (allerdings nicht wegen ISR,> sondern weil der Counter asynchron in HW implementiert ist).
Diese Lösung gefällt mir in der Tat am Besten und der Aufwand hält sich
in Grenzen. Wobei in dem Fall vermutlich auch einfach der uint8_t
counter reicht, weil es in dem Programm eigentlich keine großen
Verzögerungen geben kann, die 256 Sekunden lang den Programmfluss
behindern.
Danke.
Ich meine als Zwischenzähler, der dann in der Programmschleife kopiert
wird. Die Kopie wird dann anschließend zu dem richtien Zähler gezählt,
der 32-bit groß ist.
Ich meine, dass der Interrupt quasi unmöglich 255 Mal auftreten kann,
ohne dass er zu dem richtigen Zähler gezählt und anschließend wieder
zurückgesetzt wurde.
Peter D. schrieb:> Ach komm, Du mußt doch keine riesen float Tabellen unter der Sperre> berechnen,
Ich glaub, ich krieg hier ne Krise...
Versuche du doch bloß endlich, mal nicht alles kleinzureden, was du im
Moment nicht überschaust. Ich geb dir mal ein Beispiel: Kommunikation
des µC mit dem PC via USB-VCP. Wenn du da mit Interrupt-Sperren im
verkehrten Moment daherkommst, klinkt dich der Host ganz einfach
gnadenlos raus.
Nein, das ist alles 8 Bit AVR-Denke. Wenn man bei diesen µC zu bleiben
gedenkt, dann kann man das ja lustig so weitermachen, aber wer vom AVR
in Richtung 32 Bitter guckt (wie der TO), der sollte sowas eben
bleibenlassen und stattdessen andere Strategien sich aneignen. Jaja, der
32 Bit Zugriff ist dort atomar, weswegen das hier gekäute Problem
gegenstandslos wird, aber hier geht's mir um das Wegkommen von der
archaischen AVR-Denke.
W.S.
W.S. schrieb:> Wenn du da mit Interrupt-Sperren im> verkehrten Moment daherkommst, klinkt dich der Host ganz einfach> gnadenlos raus.
Mit Verlaub - das ist Quatsch! Weil der USB-Interrupt im µC mal 2-3
Mikrosekunden später bedient wird, schmeißt der Host das Gerät nicht
'raus. Wir reden hier ja wohl nicht von V-USB oder solchen Krücken (da
könnte ich mir das sogar vorstellen), sondern von einer "richtigen"
USB-Hardware im µC?
W.S. schrieb:> aber wer vom AVR> in Richtung 32 Bitter guckt (wie der TO),
Ähm - wo guckt er denn in diese Richtung? Ich sehe es nicht...
W.S. schrieb:> Kommunikation> des µC mit dem PC via USB-VCP. Wenn du da mit Interrupt-Sperren im> verkehrten Moment daherkommst, klinkt dich der Host ganz einfach> gnadenlos raus.
Schon beim AVR sind Interfaces (UART, CAN, USB) gepuffert, bzw. das I2C
kann den Master bremsen. Es besteht also immer genügend Zeit, Interrupts
abzuarbeiten und das kostet selber ja auch Zeit.
Ich kann mir nicht vorstellen, daß 32Bitter da rückständiger sein
sollen.
Auch muß jede Interruptquelle damit klarkommen, daß sie durch andere
Interrupts verzögert wird. Und ein gerade laufender Interrupthandler
kostet deutlich mehr Zyklen, als nur ein simpler atomarer Zugriff.
In der Praxis bevorzugt man daher die globale Sperre für atomare
Zugriffe, 1. weil es keine Nachteile hat und 2. weil es keine
Seiteneffekte hat (wie z.B. Prioritätsinversion).
Peter D. schrieb:> Ich kann mir nicht vorstellen, daß 32Bitter da rückständiger sein> sollen.
Ist auch nicht. Zumal man bei den Cortex M nicht zwingend alle
Interrupts abschalten muss, sondern über den aktuellen Prio-Level auf
sehr einfache Art alle höher priorisierten Interrupts weiterhin
durchlassen kann.
> In der Praxis bevorzugt man daher die globale Sperre für atomare> Zugriffe, 1. weil es keine Nachteile hat und 2. weil es keine> Seiteneffekte hat (wie z.B. Prioritätsinversion).
Prioritätsinversion ist eine Eigenheit von RTOS. Einfache verschachtelte
Interrupts sind davon nicht betroffen, wenn man über den
Prio-Mechanismus geht, statt über einzelne Int-Enables.
A. K. schrieb:> Prioritätsinversion ist eine Eigenheit von RTOS. Einfach verschachtelte> Interrupts sind davon m.W. nicht betroffen.
Wenn man wie von W.S. postuliert, nur eine Interruptquelle sperrt, kann
wärend dessen jeder andere Interrupt niederer Priorität ausgeführt
werden und damit die Sperre unerwartet lange verlängern. Solche
Seiteneffekte sind gefährlich, da nur schwer zu debuggen.
Die globale Sperre kann aber nicht durch andere Interrupts verlängert
werden, d.h. sie bleibt immer wie erwartet kurz. Und bei deren Ende
wirkt immer die eingestellte Prioritätskette. Es kann sich kein
Interrupt niederer Priorität vordrängeln.
Peter D. schrieb:> Wenn man wie von W.S. postuliert, nur eine Interruptquelle sperrt, kann> wärend dessen jeder andere Interrupt niederer Priorität ausgeführt> werden und damit die Sperre unerwartet lange verlängern.
Weshalb ich ja auch auf den Prio-Mechanismus rauswollte. Damit sperrt
man alle Interrupts bis zum betroffenen Level und vermeidet so die
Prio-Inversion:
1
uint32_t save = __get_BASEPRI();
2
__set_BASEPRI_MAX(level);
3
...
4
__set_BASEPRI(save);
Die Variante über das eigens dafür definierte MSR BASEPRI_MAX ist auch
dann sicher, wenn sie aus bereits höher priorisiertem Code heraus
aufgerufen wird.
PS: Das ist übrigens ein ganz alter Hut und bereits in den Frühversionen
des Unix-Quellcodes zu finden, da die PDP-11 ebenfalls Int-Prios hatte.
Nur die clevere _MAX Version der Cortex M ist m.E. eine neue Idee.
Peter D. schrieb:> Wenn man wie von W.S. postuliert, nur eine Interruptquelle sperrt,
Wie kommst du denn darauf?
Meine Position ist ganz klar:
Interrupts werden überhaupt nicht gesperrt, weder dediziert noch global.
Stattdessen soll man sich sein Zeugs so ausdenken, daß man mit
Interrupts ohne Probleme zurechtkommt. Das geht, und zwar immer und auf
allen Architekturen. Auch dort, wo es sowas wie den NMI gibt und wenn
man diesen in Verwendung hat. Gelle?
W.S.
W.S. schrieb:> Peter D. schrieb:>> Wenn man wie von W.S. postuliert, nur eine Interruptquelle sperrt,>> Wie kommst du denn darauf?>> Meine Position ist ganz klar:> Interrupts werden überhaupt nicht gesperrt, weder dediziert noch global.>> Stattdessen soll man sich sein Zeugs so ausdenken, daß man mit> Interrupts ohne Probleme zurechtkommt. Das geht, und zwar immer und auf> allen Architekturen. Auch dort, wo es sowas wie den NMI gibt und wenn> man diesen in Verwendung hat. Gelle?>> W.S.
Mal wieder ein typischer W.S.
He, Du Chuck Norris der Embedded Welt - wenn es tatsächlich ohne Sperren
gehen würde, warum gibt es denn keinen einzigen Controller, der
Interrupts Ausmaskieren NICHT implementiert? Wäre doch bares Geld, das
man da herauswirft.
Klar, wenn man in der Nanowelt eines W.S. lebt, in der man jedes
einzelne Bit mit Namen kennt, kriegt man es vielleicht irgendwie hin.
Aber das ist ja nicht das Mass der Dinge. Und den daraus resultierenden
Code möchte ich aber einer gewissen Komplexität nicht mehr sehen
wollen...
Wenn man sich z.B. mal den Standard 08/15 Code wirklich JEDEN
Inputtreibers ansieht, sieht man so etwas wie das folgende (Pseudo
Code):
ISR:
- Vorverarbeitung (z.B. Vorpufferung eines Zeichens)
- Signalisierung einer task, die die Zeichen weniger zeitkritisch
wegschafft
Task:
- Warten auf das Signal
- Wegschaffen der Zeichen
Das ist Jahrzehntelang bewährte Praxis. Man KANN es Anders machen, aber
mit dem Kontrollfluss funktionieren gefühlt 90% aller Softwarebasen, und
das ist m.E. nach der beste Kompromiss zwischen Systemausbremsen und
keine Zeichen zu verlieren riskieren.
Und nun erzähl uns doch mal, lieber C.N. (alias W.S.), wie man so etwas
OHNE Ausmaskieren von Interrupts hinkriegen soll. Das Signalisieren und
Warten auf ein Signal sind nämlich in sich (zumindestens in wenn auch
nur sehr kurzen Codeabschnitten) gegenseitig ausschliessende
Operationen. Und da sich ja ISRs nicht suspendieren lassen dürfen,
bleibt nix Anderes als das Ausmaskieren. In jedem RTOS gibt es deswegen
des Konzept von Exklusiven Codeabschnitten, die sich darauf verlassen
müssen, dass sie eben exklusiv ablaufen. Und eben durch das Ausmaskieren
implementiert sind.
W.S. schrieb:> Meine Position ist ganz klar:> Interrupts werden überhaupt nicht gesperrt, weder dediziert noch global.>> Stattdessen soll man sich sein Zeugs so ausdenken, daß man mit> Interrupts ohne Probleme zurechtkommt.
Das mag oft möglich sein, aber mit dem Ausdenken ist das so eine Sache.
Je komplizierter der Code dadurch wird, umso mehr besteht die Gefahr,
daß man dabei einen Fehler macht. Und Interruptfehler sind extrem schwer
zu debuggen.
Meine Position ist auch ganz klar:
Die CPU kann immer nur einen Interrupt zur Zeit abarbeiten. Es gibt also
bei mehreren Interruptquellen immer den Fall, daß ein Interrupt durch
einen anderen verzögert werden kann. Im Worst-Case sogar um die Summe
aller Handlerausführungszeiten aller anderen Interrupts. Und das muß das
Programm abkönnen, sonst hat man falsch geplant.
Dagegen sind wenige Zyklen Sperre durch einen atomaren Zugriff nur
Pillepalle. Die merkt man garnicht. Also gibt es keinen Grund, etwas
unnötig zu verkomplizieren.
KISS: Keep it short and simple.
Als ob es bei Interrupts immer nur um atomare Zugriffe ginge. Das ist
doch alles nur Theorie.
Thomas E. schrieb:> W.S. schrieb:>> Interrupts werden überhaupt nicht gesperrt, weder dediziert noch global.>> Ich halte mehr von Pragmatismus, als von Dogmatismus.
So sieht es aus!