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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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 | volatile uint16_t foo; |
4 | ...
|
5 | uint16_t bar; |
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. ;-)
:
Bearbeitet durch Moderator
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.
:
Bearbeitet durch Moderator
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...
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.
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.
Peter D. schrieb: > Der sicherste Weg ist, genau das zu tun. Der sicherste Weg ist zu verstehen, was man tut. Alles andere ist unsicheres Voodoo.
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
:
Bearbeitet durch User
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?
:
Bearbeitet durch User
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_t real_counter; |
5 | |
6 | int main() |
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_t i = 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 | else if(*counter >= 255) |
36 | {
|
37 | uint8_t i = 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_t changed; |
5 | |
6 | int main() |
7 | {
|
8 | uint32_t real_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_t i = 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 | else if(*counter >= 255) |
54 | {
|
55 | uint8_t i = 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.
:
Bearbeitet durch User
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:
1 | uint8_t temp = timercounter; uint32_t real_counter = temp; |
Das müsste sonst ja eigentlich auch recht sicher sein.
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!
:
Bearbeitet durch User
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?
Mein Vorschlag wäre ungefähr so:
1 | volatile uint32_t count; |
2 | |
3 | uint32_t AtomicRead32 (uint32_t * volatile pt) |
4 | {
|
5 | uint32_t retval; |
6 | |
7 | cli(); |
8 | retval = *pt; |
9 | sei(); |
10 | return retval; |
11 | }
|
12 | |
13 | void AtomicWrite32 (uint32_t * volatile pt, uint32_t data) |
14 | {
|
15 | cli(); |
16 | *pt = data; |
17 | sei(); |
18 | }
|
19 | |
20 | int main() |
21 | {
|
22 | ... Initialisierungen etc. ... |
23 | |
24 | while (1) |
25 | {
|
26 | if(AtomicRead32(&count) > 1234567) // z.B.... |
27 | AtomicWrite32(&count, 0); |
28 | }
|
29 | }
|
30 | |
31 | ISR(TIMER_INTR_NAME) |
32 | {
|
33 | count++; |
34 | }
|
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.
> Wobei in dem Fall vermutlich auch einfach der uint8_t counter reicht
Wenn das so ist, dann brauchst Du Dir um die Interrupts gar keinen Kopf
machen.
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...
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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: > Interrupts werden überhaupt nicht gesperrt, weder dediziert noch global. Ich halte mehr von Pragmatismus, als von Dogmatismus.
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!
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.