Hi,
inspiriert von der millis()-Funktion im Arduino habe ich so etwas in
meinem ATmega8 implementiert:
1
uint32_tmillis;
2
3
uint32_tmillis()
4
{
5
returnmillis;
6
}
7
8
9
intmain()
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_tmillis()
2
{
3
uint32_tcopy;
4
cli();
5
copy=millis;
6
sei();
7
returncopy;
8
}
Oder kann man sogar den Zugriff auf die Variable millis selbst irgendwie
atomar machen?
lg Paul
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;}
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!
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.
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?
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.
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
volatileuint32_tmillis;
2
uint32_tmillis()
3
{
4
uint32_tcopy;
5
6
for(copy=millis;copy!=millis;copy=millis);
7
8
returncopy;
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
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?
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...
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.
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..
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?
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++;
}
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.
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.
• 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.
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.
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.
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...
>> 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
externvolatileuint32_tmillis_cnt;
5
6
staticinlineuint32_tmillis(void)
7
{
8
ATOMIC_BLOCK(ATOMIC_FORCEON)
9
{
10
returnmillis_cnt;
11
}
12
}
Atomare Typen werden von avr-gcc noch nicht unterstützt.
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.
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
volatileunsignedlongTicks;
2
chariieks;
3
4
__isrMyISR(void)
5
{iieks++;
6
Ticks++;
7
}
8
9
...
10
11
// und zur Benutzung
12
unsignedlongGetUhrzeit(void)
13
{unsignedlongL;
14
chargieks;
15
16
gieks=iieks;
17
L=Ticks;
18
if(iieks!=gieks)L=iTicks;
19
returnL;
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.
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 :-)
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?
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.
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.
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.
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?
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
;-)
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.
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.
...
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 ;-)
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.
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
boolhilfmir;
2
unsignedlongTicks,UETicks;
3
voidISR(void)
4
{...
5
Ticks++;
6
if(hilfmir)UETicks=Ticks;
7
hilfmir=false;
8
}
9
10
unsignedlongGetUhrzeit(void)
11
{while(hilfmir);
12
returnUETicks;
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.
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.
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.
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.