Forum: Mikrocontroller und Digitale Elektronik wait condition mit ATmega* vs. Cortex-M4


von Markus G. (grabner)


Lesenswert?

Hallo!

Beim Atmel ATmega* ist für das Interrupt Timing-Verhalten folgendes 
spezifiziert:

"When using the SEI instruction to enable interrupts, the instruction 
following SEI will be executed before any pending interrupts."

"When the AVR exits from an interrupt, it will always return to the main 
program and execute one more instruction before any pending interrupt is 
served."

Damit ist es leicht möglich, das Äquivalent einer "wait condition" zu 
implementieren, die folgenden Zeilen entsprechen einem "wait()":

__enable_interrupt();
__sleep();
__disable_interrupt();

Dabei übernimmt das globale Interrupt-Flag die Rolle des Mutex, und das 
Auslösen eines Interrupts entspricht einem "wakeup()".

Den entsprechende Code für den Cortex-M4 ist:
__enable_irq();
__WFI();
__disable_irq();

Allerdings habe ich für den Cortex-M4 keine vergleichbare Interrupt 
Timing-Spezifikation gefunden. Wie kann man also beim Cortex-M4 
garantieren, dass
*) ein Interrupt, der nach __enable_irq(), aber vor __WFI() ausgelöst 
wird, erst dann bearbeitet wird, wenn sich der Prozessor bereits im 
"sleep mode" befindet?
*) ein Interrupt, der nach __WFI(), aber vor __disable_irq() ausgelöst 
wird, erst dann bearbeitet wird, wenn das Hauptprogramm Interrupts 
wieder erlaubt?

Oder gibt es am Cortex-M4 ganz andere Mechanismen, um Hauptprogramm und 
Interrupts zuverlässig zu synchronisieren? Die exklusiven load/store 
Befehle helfen hier jedenfalls nicht, weil sie den Prozessor nicht in 
den "sleep mode" versetzen.

Danke & schöne Grüße,
Markus

von Peter II (Gast)


Lesenswert?

was willst du denn überhaupt genau machen, was versteht du unter 
"Hauptprogramm und Interrupts zuverlässig zu synchronisieren?"

von Markus G. (grabner)


Lesenswert?

Peter II schrieb:
> was willst du denn überhaupt genau machen
Zeitkritische Operationen (z.B. das Empfangen von Daten über eine 
Schnittstelle) sollen im Interrupt Handler bearbeitet werden, länger 
dauernde Operationen (z.B. das Bearbeiten der empfangenen Daten) im 
Hauptprogramm, etwa im Sinne eines FIFO-Buffers.

> was versteht du unter "Hauptprogramm und Interrupts zuverlässig zu 
synchronisieren?"
Um beim obigen Beispiel zu bleiben: immer nachdem neue Daten empfangen 
wurden, soll das Hauptprogramm die Gelegenheit bekommen, diese zu 
bearbeiten, also aus dem "sleep mode" aufgeweckt werden. Beim ATmega* 
funktioniert das aufgrund des speziellen Interrupt-Timings zu 100%, aber 
ohne dieses spezielle Verhalten wäre es möglich (wenn auch sehr 
unwahrscheinlich), dass neue Daten empfangen wurden, ohne dass das 
Hauptprogramm aufgeweckt wird. Die empfangenen Daten würden also nicht 
oder verspätet bearbeitet, das Programm wäre daher unzuverlässig.

Schöne Grüße,
Markus

von Peter II (Gast)


Lesenswert?

wenn du aber beim Atmel:

> __enable_interrupt();
> __sleep();
> __disable_interrupt();

machst und dann die Daten bearbeitest, dann kannst du es auch gleich in 
der ISR machen. Dann wenn die interupts disabled sind passiert sonst eh 
nichts weiter.

von Markus G. (grabner)


Lesenswert?

Peter II schrieb:
> wenn du aber beim Atmel:
>
>> __enable_interrupt();
>> __sleep();
>> __disable_interrupt();
>
> machst und dann die Daten bearbeitest, dann kannst du es auch gleich in
> der ISR machen. Dann wenn die interupts disabled sind passiert sonst eh
> nichts weiter.
Ja, aber ich möchte die Bearbeitung aus dem Interrupt-Handler 
heraushalten, weil die möglicherweise länger dauert und dann Daten vom 
Interface verloren gehen könnten.

Mittels DMA kann man das Problem am Cortex-M4 in manchen Situationen 
sicher entschärfen, aber nicht vollständig lösen. Abgesehen von dieser 
konkreten Aufgabe interessiert es mich grundsätzlich, wie das am 
Cortex-M4 gelöst ist. Das ist ja doch eine wesentlich neuere 
Architektur, ich kann mir nicht vorstellen, dass so etwas einfach nicht 
geht.

Schöne Grüße,
Markus

von Peter II (Gast)


Lesenswert?

Markus Grabner schrieb:
> Ja, aber ich möchte die Bearbeitung aus dem Interrupt-Handler
> heraushalten, weil die möglicherweise länger dauert und dann Daten vom
> Interface verloren gehen könnten.

die sperrst doch die interupts - damit passiert nichts weiter! Es ist 
damit kein unteschied zur bearbeiten in der ISR.

von Lothar (Gast)


Lesenswert?

Markus Grabner schrieb:
> __enable_irq();
> __WFI();
> __disable_irq();

Es funktioniert genau anders herum:

__disable_irq();  // Interrupts sperren
...;              // testen of jetzt noch Interrupts bearbeitet werden, 
warten
__WFI();          // alle Interrupts bearbeitet -> Sleep
__enable_irq();   // neuer Interrupt -> aufwachen -> bearbeiten

von Markus G. (grabner)


Lesenswert?

Peter II schrieb:
> Markus Grabner schrieb:
>> Ja, aber ich möchte die Bearbeitung aus dem Interrupt-Handler
>> heraushalten, weil die möglicherweise länger dauert und dann Daten vom
>> Interface verloren gehen könnten.
>
> die sperrst doch die interupts - damit passiert nichts weiter! Es ist
> damit kein unteschied zur bearbeiten in der ISR.

Es gibt zwei Situationen, wo es einen Unterschied macht:

*) Ein Interrupt wird zwischen __enable_irq() und __WFI() ausgelöst und 
vollständig bearbeitet, bevor das Hauptprogramm auf weitere Interrupts 
warten kann.

*) Ein Interrupt wird zwischen __WFI() und __disable_irq() ausgelöst und 
vollständig bearbeitet, bevor das Hauptprogramm weitere Interrupts 
sperren kann.

In beiden Fällen wird das folgende __WFI() warten, obwohl soeben ein 
Interrupt ausgeführt wurde, d.h. die empfangenen Daten würden nicht 
bearbeitet werden. Am ATmega* sind durch das spezielle Interrupt-Timing 
diese beiden Situationen ausgeschlossen, wie macht das der Cortex-M4?

Schöne Grüße,
Markus

von Hmm (Gast)


Lesenswert?

Es mag sein, das ich Dein Problem nicht verstehe.

Die nächst-naive Antwort wäre, doch einfach die WFI instruction nicht 
auszuführen, falls soeben ein Interrupt aufgetreten in der 
Interrupt-Routine behandelt wurde. Dann behandelst Du das Ergebnis im 
Hauptprogramm und führst dann erst WIF aus.

Ich muss aber auch sagen, das das Thema im Reference Manual und User 
Guide ein wenig lakonisch behandelt wird.

von Peter II (Gast)


Lesenswert?

Markus Grabner schrieb:
> In beiden Fällen wird das folgende __WFI() warten, obwohl soeben ein
> Interrupt ausgeführt wurde, d.h. die empfangenen Daten würden nicht
> bearbeitet werden. Am ATmega* sind durch das spezielle Interrupt-Timing
> diese beiden Situationen ausgeschlossen, wie macht das der Cortex-M4?

wenn es zeitkritisch ist, dann spricht doch dageben es in der ISR zu 
machen. Damit ist das Problem doch gelöst.

Was ist wenn deine Main gar nicht in Sleep steht weil sie etwas anderes 
macht. Dann werden deine Daten auch später verarbeitet.

von Markus G. (grabner)


Lesenswert?

Lothar schrieb:
> Markus Grabner schrieb:
>> __enable_irq();
>> __WFI();
>> __disable_irq();
>
> Es funktioniert genau anders herum:
>
> __disable_irq();  // Interrupts sperren
> ...;              // testen of jetzt noch Interrupts bearbeitet werden,
> warten
> __WFI();          // alle Interrupts bearbeitet -> Sleep
> __enable_irq();   // neuer Interrupt -> aufwachen -> bearbeiten

Was meinst Du mit "testen of jetzt noch Interrupts bearbeitet werden"? 
Im Cortex-M4 User Guide steht:

"On completion of an exception handler, if there is a pending exception 
that meets the requirements for exception entry, the stack pop is 
skipped and control transfers to the new exception handler."

Da kommt also das Hauptprogramm dazwischen gar nicht an die Reihe.

Zur WFI-Anweisung steht im User Guide:
"WFI is a hint instruction that suspends execution until one of the 
following events occurs:
*) a non-masked interrupt occurs and is taken
*) an interrupt masked by PRIMASK becomes pending..."

D.h. sobald bei gesperrten Interrupts das Programm nach __WFI() 
weitergeht, weiß man, dass ein Interrupt angefordert, aber noch nicht 
bearbeitet wurde. Was passiert denn, wenn man jetzt im Hauptprogramm 
unmittelbar hintereinander

__enable_irq()
__disable_irq()

aufruft? Ist dann sichergestellt, dass alle noch "offenen" 
Interrupt-Anforderungen bearbeitet wurden?

Schöne Grüße,
Markus

von Lothar (Gast)


Lesenswert?

Markus Grabner schrieb:
>> Markus Grabner schrieb:
>>> __enable_irq();
>>> __WFI();
>>> __disable_irq();
>>
>> Es funktioniert genau anders herum:
>>
>> __disable_irq();  // Interrupts sperren
>> ...;              // testen of jetzt noch Interrupts bearbeitet werden,
>> warten
>> __WFI();          // alle Interrupts bearbeitet -> Sleep
>> __enable_irq();   // neuer Interrupt -> aufwachen -> bearbeiten
>
> Was meinst Du mit "testen of jetzt noch Interrupts bearbeitet werden"?
> Im Cortex-M4 User Guide steht:
>
> "On completion of an exception handler, if there is a pending exception
> that meets the requirements for exception entry, the stack pop is
> skipped and control transfers to the new exception handler."
>
> Da kommt also das Hauptprogramm dazwischen gar nicht an die Reihe.

__disable_irq() = CPSID I bewirkt das kein Interrupt Handler mehr 
gestartet werden kann. Sobald also der aktuelle Handler beendet ist, 
kommt das Hauptprogramm, und damit WFI. Das ist eigentlich kein Problem, 
weil bei Pending Interrupts WFI nicht in den Sleep gehen sollte. Es soll 
aber Hersteller geben, bei denen der M4 zuerst in den Sleep geht und 
dann unmittelbar wieder aufwacht. Das könnte dann ein Timing-Problem 
geben.

>
> Zur WFI-Anweisung steht im User Guide:
> "WFI is a hint instruction that suspends execution until one of the
> following events occurs:
> *) a non-masked interrupt occurs and is taken
> *) an interrupt masked by PRIMASK becomes pending..."
>
> D.h. sobald bei gesperrten Interrupts das Programm nach __WFI()
> weitergeht, weiß man, dass ein Interrupt angefordert, aber noch nicht
> bearbeitet wurde. Was passiert denn, wenn man jetzt im Hauptprogramm
> unmittelbar hintereinander
>
> __enable_irq()
> __disable_irq()
>
> aufruft? Ist dann sichergestellt, dass alle noch "offenen"
> Interrupt-Anforderungen bearbeitet wurden?

Wie oben beschrieben, nein. __disable_irq() klemmt praktisch den NVIC 
vom Core ab und Pending Interrupts werden nicht mehr verarbeitet. Es 
macht aber ohnehin keinen Sinn, im Hauptprogramm __disable_irq() zu 
verwenden, ausser zur Konfiguration und für Atomic Blocks.

https://sites.google.com/site/iprinceps/Home/embedded-system-and-operating-systems/hardware-and-firmware/nested-vectored-interrupt-controller-of-arm-cortex-m3

>
> Schöne Grüße,
> Markus

von Markus G. (grabner)


Lesenswert?

Hmm schrieb:
> Es mag sein, das ich Dein Problem nicht verstehe.
>
> Die nächst-naive Antwort wäre, doch einfach die WFI instruction nicht
> auszuführen, falls soeben ein Interrupt aufgetreten in der
> Interrupt-Routine behandelt wurde. Dann behandelst Du das Ergebnis im
> Hauptprogramm und führst dann erst WIF aus.
Das macht man ohnehin, aber irgendwann kommt der Zeitpunkt, wo alle 
Interrupts bearbeitet wurden, und dann müssen neue Interrupts wieder 
zugelassen werden und das Haputprogramm in den "sleep mode" gehen. Und 
genau das ist das Problem, das Zulassen von Interrupts und Betreten des 
"sleep mode" müssen eine "atomare" Operation sein, da darf dazwischen 
nichts passieren (insbesondere kein Interrupt, weil der sonst verloren 
geht).

Das Beispiel war etwas abgekürzt, der vollständige Ablauf im Pseudo-Code 
sieht ca. so aus:

while(true) {  // Endlosschleife für Hauptprogramm
  lock();
  while(!dataAvailable()) {
    unlock();
    wait();
    lock();
  }
  getData();  // zeitkritisch, weil Interrupts gesperrt
  unlock();
  processData();  // nicht zeitkritisch, weil Interrupts zugelassen
}

lock()/unlock() beziehen sich aufs Sperren/Zulassen von Interrupts. 
Während die Verfügbarkeit von Daten geprüft wird bzw. die Daten abgeholt 
werden, müssen Interrupts gesperrt sein, damit die von Hauptpgrogramm 
und Interrupts gemeinsam benutze Datenstruktur konsistent bleibt. 
Während die letzten Daten bearbeitet werden, können schon neue empfangen 
werden. Die Bedingung dataAvailable() muss nach jedem Interrupt erneut 
geprüft werden, denn es könnte ja sein, dass ein anderer Interrupt 
bearbeitet wurde, der nichts mit dem Empfangen von Daten zu tun hat.

Hier sind zum Vergleich ein paar Links mit einem (high-level) Beispiel 
in Qt (die "Consumer Class" entspricht unserem Hauptprogramm):
http://qt-project.org/doc/qt-4.8/threads-waitconditions.html
http://qt-project.org/doc/qt-4.8/qwaitcondition.html#wait

Und hier wird eindrucksvoll gezeigt, wie mühsam es ist, ein 
vergleichbares Verhalten zu erzielen, wenn die entsprechenden Funktionen 
nicht bereits low-level verfügbar sind:
http://www.cs.wustl.edu/~schmidt/win32-cv-1.html

> Ich muss aber auch sagen, das das Thema im Reference Manual und User
> Guide ein wenig lakonisch behandelt wird.
Ja, leider :-(

Schöne Grüße,
Markus

von Oliver S. (oliverso)


Lesenswert?

Das Problem ist aber nur deshalb eines, weil du krampfhaft versuchst, 
Interruptroutinen in den Ablauf des Hauptprogramms hinein zu 
synchronisieren. Der Sinn eines Interrupts und der dazugehörigen 
Interrupt-Routine ist es aber nunmal, asynchron abzulaufen. Dein ganzes 
Konzept ist iergendwie  - seltsam (ich will ja höfich bleiben...)

Wenn irgend etwas im Ablauf des Hauptprogramms passieren soll, dann 
schreib das einfach da rein, und fertig.

Oliver

von Markus G. (grabner)


Lesenswert?

Peter II schrieb:
> Markus Grabner schrieb:
>> In beiden Fällen wird das folgende __WFI() warten, obwohl soeben ein
>> Interrupt ausgeführt wurde, d.h. die empfangenen Daten würden nicht
>> bearbeitet werden. Am ATmega* sind durch das spezielle Interrupt-Timing
>> diese beiden Situationen ausgeschlossen, wie macht das der Cortex-M4?
>
> wenn es zeitkritisch ist, dann spricht doch dageben es in der ISR zu
> machen. Damit ist das Problem doch gelöst.
>
> Was ist wenn deine Main gar nicht in Sleep steht weil sie etwas anderes
> macht. Dann werden deine Daten auch später verarbeitet.
Mit "später" meine ich, dass z.B. jede Minute eine Nachricht in der 
Länge von 16 Bytes übertragen wird, wobei die Übertragung eines Bytes, 
sagen wir mal, 10µs dauert. Wenn jetzt ausgerechnet der letzte Interrupt 
nicht zum Aufwecken des Hauptprogramms führt, würde die erste Nachricht 
erst (fast) eine Minute später, nämlich mit dem ersten Byte der zweiten 
Nachricht, bearbeitet werden. Statt "nicht zeitkritisch" hätte ich 
"weniger zeitkritisch" sagen sollen. Eine Verzögerung von ein paar 
Millisekunden ist in vielen Fällen kein Problem, aber eine beliebig 
lange Verzögerung ist wohl kaum akzeptabel.

Schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Oliver S. schrieb:
> Das Problem ist aber nur deshalb eines, weil du krampfhaft versuchst,
> Interruptroutinen in den Ablauf des Hauptprogramms hinein zu
> synchronisieren. Der Sinn eines Interrupts und der dazugehörigen
> Interrupt-Routine ist es aber nunmal, asynchron abzulaufen. Dein ganzes
> Konzept ist iergendwie  - seltsam (ich will ja höfich bleiben...)
Das sehe ich nicht so. Die Informatik beschäftigt sich seit mindestens 
einem halben Jahrhundert damit, wie man Prozesse parallel und asynchron 
ablaufen lassen kann, und ebenso lange damit, wie man, wo es nötig ist, 
die Prozesse wieder synchronisieren kann. Der ATmega* kann das, warum 
sollte es der Cortex-M4 nicht können?

> Wenn irgend etwas im Ablauf des Hauptprogramms passieren soll, dann
> schreib das einfach da rein, und fertig.
Geht eben nicht, wenn, wie Du oben sagst, Interrupts verwendet werden, 
um zeitkritische Aktionen asynchron zu bearbeiten.

Schöne Grüße,
Markus

von Hmm (Gast)


Lesenswert?

Tut mir leid, aber ich verstehe das Problem immer noch nicht so recht.
Ich denke Du machst gedanklich einige Voraussetzungen die ich/wir nicht 
machen.

Wozu ein unlock zwischen getData () und processData ()? Das processing 
ist einfach ein integraler Vorgang der die Daten holt und verarbeitet. 
Ggf. mit einem Ringbuffer. Das löst das Problem mit: "...Interrupts 
gemeinsam benutze Datenstruktur konsistent bleibt."

Das Processing ist ja nur dann überhaupt zeitkritisch wenn die 
Ereignisse in kürzen oder nahezu gleichen Abständen auftreten wie Du 
brauchts um die Daten zu verarbeiten.

[c]
while(true) {
  while (dataAvailable())
     getNprocessData();  // nicht zeitkritisch, weil Interrupts 
zugelassen
  lock ();
  if (!dataAvailable()) {
    wait();
  unlock ();
}
[\c]

Jedenfalls ist in der Schleife, meiner unmaßgeblichen Ansicht nach

[c]
  while(!dataAvailable()) {
    unlock();
    wait();
    lock();
  }
[\c]

ein logischer Widerspruch. Nach dem Aufwecken sind nämlich garantiert 
Daten vorhanden.

von Peter D. (peda)


Lesenswert?

Oliver S. schrieb:
> Das Problem ist aber nur deshalb eines, weil du krampfhaft
> versuchst,
> Interruptroutinen ind den Ablauf des Hauptprogramms hinein zu
> synchronisieren.

Nein, das versucht er nicht.

Es kann durchaus den Fall geben, daß die Mainloop nichts zu tun hat, 
solange kein Interrupt neue Daten zur Verfügung stellt und man für diese 
Zeit Strom sparen will.
Daran ist nichts ungewöhnliches oder verwerfliches.
Jeder MC sollte das also supporten können.

Da Interrupts aber asynchron zum Programm erfolgen, kann just direkt vor 
dem Sleep ein Interrupt eintreffen und genau dann muß man verhindern, 
daß er vor dem Sleep abgearbeitet wird. Sonst wacht der MC erst beim 
nächsten Interrupt auf.
Angenommen der Interrupt kommt alle Stunde dann wacht man ab und zu eine 
Stunde zu spät auf.
Die Daten sind nicht verloren, der Interrupthandler hat sie ja in eine 
FIFO gesteckt, aber sie werden verspätet bearbeitet.
Und wenn der Absender erst auf die Antwort wartet, bevor er das nächste 
Paket sendet, hat man einen klassischen Deadlock.

von Oliver S. (oliverso)


Lesenswert?

Markus Grabner schrieb:
> Der ATmega* kann das,

Sei mir nicht böse, aber das ein AVR nach der ISR ins Hauptprogramm 
zurückkehrt, und dann dort einen Befehl abarbeitet, hat nichts mit 
Synchronisation zu tun.

Natürlich muss Synchronisation sein, dafür gibt es aber doch nun 
ausreichend programmtechnische Mittel, von einfachen Flags bis hin zu 
den ausgefeilten Mechanismen der Echtzeitbetriebssysteme.

Oliver

von Peter D. (peda)


Lesenswert?

Oliver S. schrieb:
> Natürlich muss Synchronisation sein, dafür gibt es aber doch nun
> ausreichend programmtechnische Mittel, von einfachen Flags bis hin zu
> den ausgefeilten Mechanismen der Echtzeitbetriebssysteme.

Das Flag ist völlig nutzlos. Dann haut der Interrupt eben zwischen Flag 
testen und Sleep. Der Deadlock ist genau der gleiche.

Du brauchst einen atomaren Mechanismus, der entweder nach einen 
Interrupt das Sleep nicht ausführt oder vor dem Sleep keinen pending 
Interrupt.

von Markus G. (grabner)


Lesenswert?

Hmm schrieb:
> Tut mir leid, aber ich verstehe das Problem immer noch nicht so recht.
> Ich denke Du machst gedanklich einige Voraussetzungen die ich/wir nicht
> machen.
>
> Wozu ein unlock zwischen getData () und processData ()? Das processing
> ist einfach ein integraler Vorgang der die Daten holt und verarbeitet.
> Ggf. mit einem Ringbuffer. Das löst das Problem mit: "...Interrupts
> gemeinsam benutze Datenstruktur konsistent bleibt."
Wenn die Bearbeitung schnell geht, kann man sich das extra 
unlock()/lock() sparen. Wenn aber die Bearbeitung wesentlich länger 
dauert als das Abholen der Daten aus dem Ringbuffer, ist es sinnvoll, 
die Daten zuerst abzuholen und dann, sobald der Ringbuffer nicht mehr 
benötigt wird, Interrupts wieder zuzulassen.

> Das Processing ist ja nur dann überhaupt zeitkritisch wenn die
> Ereignisse in kürzen oder nahezu gleichen Abständen auftreten wie Du
> brauchts um die Daten zu verarbeiten.
Genau, und damit das Programm insgesamt korrekt ist, muss es für jede 
beliebige zeitliche Abfolge der Ereignisse ein korrektes Verhalten 
zeigen. Das Gemeine an diesen Dingen ist ja, dass sich Fehler bei der 
Synchronisierung praktisch nicht reproduzieren lassen und deshalb sehr 
schwer zu beheben sind. Man sollte also eine Lösung suchen, deren 
Korrektheit beweisbar ist (ein etwas idealistisches Ziel :-), aber 
zumindest für kurze Synchronisationsblöcke durchaus erreichbar).

> [c]
> while(true) {
>   while (dataAvailable())
>      getNprocessData();  // nicht zeitkritisch, weil Interrupts
> zugelassen
>   lock ();
>   if (!dataAvailable()) {
>     wait();
>   unlock ();
> }
> [\c]
Das geht nicht, weil beim Zugriff auf die Datenstruktur (erstes 
dataAvailable() und getNprocessData()) Interrupts gesperrt sein müssen, 
damit die Daten eben konsistent sind. Ein Ringbuffer, der zum 
Datenaustausch zwischen asynchronen Prozessen geeignet ist, tut intern 
ja auch nichts anderes, als "gleichzeitiges" Lesen und Schreiben zu 
verhindern. Außerdem erzeugt die Sequenz lock(),wait() zumindest bei 
einem Betriebssystem definitiv ein dead lock, wobei der Cortex-M4 hier 
etwas nachsichtiger ist und offenbar auch bei einem "pending interrupt" 
das Warten beendet.

> Jedenfalls ist in der Schleife, meiner unmaßgeblichen Ansicht nach
>
> [c]
>   while(!dataAvailable()) {
>     unlock();
>     wait();
>     lock();
>   }
> [\c]
>
> ein logischer Widerspruch. Nach dem Aufwecken sind nämlich garantiert
> Daten vorhanden.
Nur dann, wenn es nur eine einzige Interrupt-Quelle im System gibt, 
nämlich die, die die Daten vom Interface entgegennimmt. Wenn noch andere 
Dinge interrupt-gesteuert ablaufen, wird bei jedem Interrupt das 
Hauptprogramm aufgeweckt, aber nur wenn es der "passende" Interrupt war, 
sind tatsächlich Daten vorhanden, also muss man das jedes mal 
überprüfen.

Schöne Grüße,
Markus

von Hmm (Gast)


Lesenswert?

>> Das Processing ist ja nur dann überhaupt zeitkritisch wenn die
>> Ereignisse in kürzen oder nahezu gleichen Abständen auftreten wie Du
>> brauchts um die Daten zu verarbeiten.
>Genau, und damit das Programm insgesamt korrekt ist, muss es für jede
>beliebige zeitliche Abfolge der Ereignisse ein korrektes Verhalten
>zeigen.

Lass uns das mal zuerst klären, bitte: "Jede beliebige zeitliche 
Abfolge" ist sicherlich nicht realisierbar (vermutlich meinst Du das 
nicht wortwörtlich). Aber doch eine Reihe von "spezifizierten" Abfolgen.
Das würde dann heissen, das die Spezifikation hier in der Diskussion 
noch fehlt (sofern Du Dich auf eine Diskussion darüber einlassen 
willst).

>Wenn die Bearbeitung schnell geht, kann man sich das extra
>unlock()/lock() sparen. Wenn aber die Bearbeitung wesentlich länger
>dauert als das Abholen der Daten aus dem Ringbuffer, ist es sinnvoll,
>die Daten zuerst abzuholen und dann, sobald der Ringbuffer nicht mehr
>benötigt wird, Interrupts wieder zuzulassen.

Ggf. brauchst Du die Interrupts garnicht sperren. Die Frage ist ja,
a) ob es überhaupt möglich ist das Daten in kürzeren Abständen kommen 
als Du für die Verarbeitung brauchst
b) ob es irgendeinen längeren Zeitraum gibt in der die Summe der 
Zeitabstände der Interrupts kleiner ist als für die Verarbeitung der 
Daten insgesamt gebraucht wird

Falls Du b mit nein beantworten muss, dann geht das sowieso so nicht. 
Dann musst Du die Verarbeitung verkürzen.

>Ein Ringbuffer, der zum Datenaustausch zwischen asynchronen Prozessen >geeignet 
ist, tut intern
>ja auch nichts anderes, als "gleichzeitiges" Lesen und Schreiben zu
>verhindern.

Dieses "tut ... auch nichts anderes" suggeriert als wenn die Lösungen 
gleichwertig sind. Das sind sie aber dann nicht wenn man sich mit dem 
Ringbuffer die ganze Frage überhaupt erspart.

>Nur dann, wenn es nur eine einzige Interrupt-Quelle im System gibt,

Da hast Du natürlich recht. Falls Du das schon geschrieben hast, dann 
habe ich es übersehen; falls nicht, dann habe ich es nicht übersehen. 
;-)

von Bronco (Gast)


Lesenswert?

Ich fürchte, vielen in dieser Diskussion ist gar nicht klar, worum es 
wirklich geht. PeDa hat es richtig erkannt und es steht auch hier:
http://www.mikrocontroller.net/articles/Interrupt#Interrupts_und_Low_Power_Modes_.28Sleep.29

Das Problem ist folgendes:
Angenommen ihr wollt das System zyklisch per Timer wecken (z.B. 1x pro 
s). Ihr müßt also sicherstellen, daß der Timer-Interrupt ein schlafendes 
System unter allen Umständen weckt.
Nun gibt es folgenden Fall (mir selbst so schon untergekommen):
- Es ist kurz vor Timer-Aufweck-Zeit
- Jetzt kommt ein anderer Interrupt (bei mir war's der ADC) und weckt 
das System
- In main() merkt Ihr: ich wurde geweckt, aber es war nicht der Timer, 
also wieder schlafen gehen
- Und nun kommt der Timer-Interrupt ganz genau zwischen InteruptEnable 
und Sleep
1
__enable_interrupt();
2
__sleep();

Was würde ohne die um einen Befehl verzögerte Interruptfreigabe 
passieren?
Die Timer-ISR wird bearbeitet, da ja der Interrupt erlaubt ist.
Danach gehen wir ins Sleep und werden nicht mehr vom Timer geweckt!
=> Timing-Fehler

Wie gesagt, genau dieser Fehler ist mir mal untergekommen und er war 
reichlich schwierig zu finden.

von Lothar (Gast)


Lesenswert?

Markus Grabner schrieb:
> while(true) {  // Endlosschleife für Hauptprogramm
>   lock();
>   while(!dataAvailable()) {
>     unlock();
>     wait();
>     lock();
>   }
>   getData();  // zeitkritisch, weil Interrupts gesperrt
>   unlock();
>   processData();  // nicht zeitkritisch, weil Interrupts zugelassen
> }

while(true) {  // Endlosschleife für Hauptprogramm
  while(!dataAvailable()) {
    lock();
    wait();
    unlock();
  }
  lock();
  getData();  // zeitkritisch, weil Interrupts gesperrt
  unlock();
  processData();  // nicht zeitkritisch, weil Interrupts zugelassen
}

von Hmm (Gast)


Lesenswert?

@ Bronco

Das ist uns schon klar. Danke.

von Markus G. (grabner)


Lesenswert?

Hmm schrieb:
>>> Das Processing ist ja nur dann überhaupt zeitkritisch wenn die
>>> Ereignisse in kürzen oder nahezu gleichen Abständen auftreten wie Du
>>> brauchts um die Daten zu verarbeiten.
>>Genau, und damit das Programm insgesamt korrekt ist, muss es für jede
>>beliebige zeitliche Abfolge der Ereignisse ein korrektes Verhalten
>>zeigen.
>
> Lass uns das mal zuerst klären, bitte: "Jede beliebige zeitliche
> Abfolge" ist sicherlich nicht realisierbar (vermutlich meinst Du das
> nicht wortwörtlich). Aber doch eine Reihe von "spezifizierten" Abfolgen.
> Das würde dann heissen, das die Spezifikation hier in der Diskussion
> noch fehlt (sofern Du Dich auf eine Diskussion darüber einlassen
> willst).
Lieber nicht :-), aber ich sollte noch ergänzen, dass "korrektes 
Verhalten" auch eine wohldefinierte Fehlermeldung einschließt, wenn das 
System z.B. erkennt, dass es zu langsam ist, um die empfangenen Daten zu 
verarbeiten ("buffer overflow" o.ä.).

>>Wenn die Bearbeitung schnell geht, kann man sich das extra
>>unlock()/lock() sparen. Wenn aber die Bearbeitung wesentlich länger
>>dauert als das Abholen der Daten aus dem Ringbuffer, ist es sinnvoll,
>>die Daten zuerst abzuholen und dann, sobald der Ringbuffer nicht mehr
>>benötigt wird, Interrupts wieder zuzulassen.
>
> Ggf. brauchst Du die Interrupts garnicht sperren. Die Frage ist ja,
> a) ob es überhaupt möglich ist das Daten in kürzeren Abständen kommen
> als Du für die Verarbeitung brauchst
Ich möchte mich jedenfalls nicht darauf verlassen, dass das nie der Fall 
sein wird. Dann wird das System irgendwann erweitert, und auf einmal 
kommen sporadisch Fehler.

> b) ob es irgendeinen längeren Zeitraum gibt in der die Summe der
> Zeitabstände der Interrupts kleiner ist als für die Verarbeitung der
> Daten insgesamt gebraucht wird
>
> Falls Du b mit nein beantworten muss, dann geht das sowieso so nicht.
> Dann musst Du die Verarbeitung verkürzen.
Ja, das sollte das System aber selbst bemerken (siehe oben).

>>Ein Ringbuffer, der zum Datenaustausch zwischen asynchronen Prozessen >geeignet
> ist, tut intern
>>ja auch nichts anderes, als "gleichzeitiges" Lesen und Schreiben zu
>>verhindern.
>
> Dieses "tut ... auch nichts anderes" suggeriert als wenn die Lösungen
> gleichwertig sind. Das sind sie aber dann nicht wenn man sich mit dem
> Ringbuffer die ganze Frage überhaupt erspart.
In dem konkret skizzierten Beispiel würde ein Ringbuffer das Problem 
lösen (bzw. in seine interne Implementierung verlagern), eine allgemeine 
"wait condition" kann aber auch für andere Zwecke eingesetzt werden.

>
>>Nur dann, wenn es nur eine einzige Interrupt-Quelle im System gibt,
>
> Da hast Du natürlich recht. Falls Du das schon geschrieben hast, dann
> habe ich es übersehen; falls nicht, dann habe ich es nicht übersehen.
> ;-)
Ja, hier:
Beitrag "Re: wait condition mit ATmega* vs. Cortex-M4"

Nebenbei bemerkt bin ich beeindruckt, dass mein erstes Posting hier, 
noch dazu zu einem nicht ganz trivialen Thema, eine so intensive 
Diskussion auslöst :-)

Schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Oliver S. schrieb:
> Markus Grabner schrieb:
>> Der ATmega* kann das,
>
> Sei mir nicht böse, aber das ein AVR nach der ISR ins Hauptprogramm
> zurückkehrt, und dann dort einen Befehl abarbeitet, hat nichts mit
> Synchronisation zu tun.
Ob alle Interrupt Handler hintereinander abgearbeitet werden oder nicht, 
spielt keine Rolle, aber die Zusicherung, dass zumindest ein Befehl vom 
Hauptprogramm bearbeitet wird, stellt sicher, dass das Hauptprogramm 
nach Beendigung eines Interrupt Handlers weitere Interrupts sperren 
kann, ohne dass dazwischen ein Interrupt ausgeführt worden ist.

Bei einer "wait condition" sind "unlock() + Wartezustand beginnen" sowie 
"Wartezustand beenden + lock()" atomare Operationen, und genau das kann 
der AVR auch.

Schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Bronco schrieb:
> Ich fürchte, vielen in dieser Diskussion ist gar nicht klar, worum es
> wirklich geht. PeDa hat es richtig erkannt und es steht auch hier:
> 
http://www.mikrocontroller.net/articles/Interrupt#Interrupts_und_Low_Power_Modes_.28Sleep.29
>
> Das Problem ist folgendes:
> Angenommen ihr wollt das System zyklisch per Timer wecken (z.B. 1x pro
> s). Ihr müßt also sicherstellen, daß der Timer-Interrupt ein schlafendes
> System unter allen Umständen weckt.
> Nun gibt es folgenden Fall (mir selbst so schon untergekommen):
> - Es ist kurz vor Timer-Aufweck-Zeit
> - Jetzt kommt ein anderer Interrupt (bei mir war's der ADC) und weckt
> das System
> - In main() merkt Ihr: ich wurde geweckt, aber es war nicht der Timer,
> also wieder schlafen gehen
> - Und nun kommt der Timer-Interrupt ganz genau zwischen InteruptEnable
> und Sleep
>
>
1
> __enable_interrupt();
2
> __sleep();
3
>
>
> Was würde ohne die um einen Befehl verzögerte Interruptfreigabe
> passieren?
> Die Timer-ISR wird bearbeitet, da ja der Interrupt erlaubt ist.
> Danach gehen wir ins Sleep und werden nicht mehr vom Timer geweckt!
> => Timing-Fehler
>
> Wie gesagt, genau dieser Fehler ist mir mal untergekommen und er war
> reichlich schwierig zu finden.

Danke für das anschauliche Beispiel! Die große Preisfrage: wie macht das 
der Cortex-M4???

Schöne Grüße,
Markus

von Fritz (Gast)


Lesenswert?

@ Markus:
Wenn man eine neue, andere Architektur verwendet, sollte man sich schon 
im Klaren sein, dass wenn man architekturspezifische Eigenheiten 
benutzt, dass man Techniken, die auf einer Architektur gingen, nicht so 
ohne weiters auf einer anderen funktionieren.
Generell scheint es mir halt äusserst fragwürdig mit WFI sich auf einen 
bestimmten Interrupt zu synchronisieren, wenn es mehre verschiedene ISRs 
im System gibt.
Saubere Lösungen dafür bieten realtime syteme (z.B. FreeRTOS). Praktisch 
jedes vernünftige RTOS hat dafür Funktionen zur Synchronisation mit 
anderen Tasks zur Verfügung. Nur in der Idle-Task ist dann eine endless 
loop in der das WFI ausgeführt werden kann.

von Peter D. (peda)


Lesenswert?

Fritz schrieb:
> Generell scheint es mir halt äusserst fragwürdig mit WFI sich auf einen
> bestimmten Interrupt zu synchronisieren

Nein, das will man nicht.

Man will nur schlafen, wenn es mal nichts zu tun gibt.
Und eben zuverlässig mit dem nächsten Interrupt auch wieder aufwachen, 
der aber rein zufällig direkt vor dem Sleep pending werden könnte.

Es geht wirklich nur um die Verhinderung einer Race-Condition und deren 
Folgen.

Wenn man nicht 100%-ig sicher mit dem nächsten Interrupt aus einem Sleep 
aufwachen kann, dann kann man das Sleep in die Tonne treten, die 
Entwickler haben schlichtweg gepennt.

von Peter D. (peda)


Lesenswert?

Beim AVR gibt es übrigens eine weitere Möglichkeit, das Sleep 
interruptsicher zu machen.

Man setzt erst das Sleep-Enable Bit, dann die Interruptfreigabe und dann 
das Sleep.
Und jeder Interrupthandler löscht das Sleep-Enable.

Wird nun vor dem Sleep ein Interrupt ausgeführt, wandelt er das folgende 
Sleep quasi in ein NOP um.
Vielleicht gibt es ja so ein Bit auch beim ARM, dann kann man das Sleep 
doch noch verwenden.

von Peter II (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Man setzt erst das Sleep-Enable Bit, dann die Interruptfreigabe und dann
> das Sleep.
> Und jeder Interrupthandler löscht das Sleep-Enable.

wozu braucht man da noch die Interruptfreigabe? (am einfang einmal ist 
klar)

von Oliver S. (oliverso)


Lesenswert?

Peter Dannegger schrieb:
> dann kann man das Sleep in die Tonne treten, die
> Entwickler haben schlichtweg gepennt.

Vorher sollte man aber dann doch mal die komplette Doku des Prozessors 
lesen, um zu verstehen, was der so alles bietet.

Nur weil es einen zweckentfremdeten Spezialtrick, der auf einem AVR 
zufällig funktioniert, auf einem ARM nicht gibt, heisst das ja noch 
lange nicht, das man dieses Problem da gar nicht gelöst bekommt.

Oliver

von Markus G. (grabner)


Lesenswert?

Fritz schrieb:
> @ Markus:
> Wenn man eine neue, andere Architektur verwendet, sollte man sich schon
> im Klaren sein, dass wenn man architekturspezifische Eigenheiten
> benutzt, dass man Techniken, die auf einer Architektur gingen, nicht so
> ohne weiters auf einer anderen funktionieren.
Schon klar, und wenn es ganz anders geht, ist es auch recht. Aber dass 
es ein Synchronisierungs-Feature auf einer neueren und insgesamt wohl 
fortgeschritteneren Plattform überhaupt nicht mehr geben soll, kann ich 
mir nicht vorstellen.

> Saubere Lösungen dafür bieten realtime syteme (z.B. FreeRTOS). Praktisch
> jedes vernünftige RTOS hat dafür Funktionen zur Synchronisation mit
> anderen Tasks zur Verfügung. Nur in der Idle-Task ist dann eine endless
> loop in der das WFI ausgeführt werden kann.
Das habe ich auch schon überlegt, aber die zusätzliche Komplexität durch 
ein RTOS würde ich gerne vermeiden, vor allem wenn die Synchronisierung 
so einfach geht (bzw. ginge) wie beim ATmega*.

Wenn ich mal etwas mehr Zeit habe, kann ich in den FreeRTOS Quellcode 
schauen, wie das dort gemacht wird, vielleicht lässt sich daraus was 
ableiten...

Schöne Grüße,
Markus

von Fritz (Gast)


Lesenswert?

Markus Grabner schrieb:
> Das habe ich auch schon überlegt, aber die zusätzliche Komplexität durch
> ein RTOS würde ich gerne vermeiden, vor allem wenn die Synchronisierung
> so einfach geht (bzw. ginge) wie beim ATmega*.

Natürlich hat ein RTOS eine gewisse Komplexität, aber du brauchst nur 
einen Bruchteil davon zu verwenden. In Bezug auf RAM- und Flashgrösse 
ist das für einen Cortex M4 ein Klacks.
Es mag ja bei einfachen Applikationen so mit dem ATmega gut 
funktionieren, aber wenn die Applikation mit der Zeit koplexer wird, 
wirst du mit dieser Art von Synchronisation nicht glücklich werden.

Markus Grabner schrieb:
> Wenn ich mal etwas mehr Zeit habe, kann ich in den FreeRTOS Quellcode
> schauen, wie das dort gemacht wird, vielleicht lässt sich daraus was
> ableiten...

Du musst dich von deinem "Hauptprogramm <--> Interrupts" Vorstellungen 
lösen. In einem RTOS hast du halt vrschiedene Tasks (in sich 
abgeschlssene Programmteile) die bestimmte Aufgaben übernehmen und über 
die definierten Funtionen mit anderen Tasks oder ISR kommunizieren. Ein 
Interrupt kommuniziert dann mit einer Task1 Z.B. UART-ISR. Ein anderer 
kommuniziert aber mit Tsk2 Z. B. ADC-ISR. Tasks kann man Prioritäten 
geben, sodass eine Aufgabe Vorrang hat. Hauptprogramm gibt es eigentlich 
nicht, wenn das System nichts zu tun hat, läuft eben die Idle-Task mit 
Z. B. nur sleep in einer loop. Kommt dann ein Interrupt, kann man in der 
ISR die zugehörige Task anstossen um weiterzumachen. Timeouts werden 
ähnlich behandelt.

von Markus G. (grabner)


Lesenswert?

Peter II schrieb:
> Peter Dannegger schrieb:
>> Man setzt erst das Sleep-Enable Bit, dann die Interruptfreigabe und dann
>> das Sleep.
>> Und jeder Interrupthandler löscht das Sleep-Enable.
>
> wozu braucht man da noch die Interruptfreigabe? (am einfang einmal ist
> klar)
Jedes mal, wenn auf die gemeinsame Datenstruktur zugegriffen wird, 
müssen Interrupts vorher gesperrt (und daher nachher wieder freigegeben) 
werden.

Hier habe ich eine kompakte Abhandlung zu dem Thema gefunden:

http://people.cs.umass.edu/~emery/classes/cmpsci377/current/notes/lecture_8_synch.pdf

Schöne Grüße,
Markus

von Peter II (Gast)


Lesenswert?

Markus Grabner schrieb:
> Jedes mal, wenn auf die gemeinsame Datenstruktur zugegriffen wird,
> müssen Interrupts vorher gesperrt (und daher nachher wieder freigegeben)
> werden.

schon klar, aber das ist hier ja nicht der fall.

von Lothar (Gast)


Lesenswert?

Markus Grabner schrieb:
> Jedes mal, wenn auf die gemeinsame Datenstruktur zugegriffen wird,
> müssen Interrupts vorher gesperrt (und daher nachher wieder freigegeben)
> werden.

Aber nicht beim Cortex-M dafür gibt es ja wie schon erwähnt:

LDREXW/STREXW und DMB (Data Memory Barrier)

von Markus G. (grabner)


Lesenswert?

Oliver S. schrieb:
> Peter Dannegger schrieb:
>> dann kann man das Sleep in die Tonne treten, die
>> Entwickler haben schlichtweg gepennt.
>
> Vorher sollte man aber dann doch mal die komplette Doku des Prozessors
> lesen, um zu verstehen, was der so alles bietet.
Ja, RTFM ist oft hilfreich, aber wer tut das schon, darum habe ich halt 
einfach mal hier gefragt :-)

> Nur weil es einen zweckentfremdeten Spezialtrick, der auf einem AVR
> zufällig funktioniert, auf einem ARM nicht gibt, heisst das ja noch
> lange nicht, das man dieses Problem da gar nicht gelöst bekommt.
Also, ich unterstelle den Atmel-Entwicklern einfach mal so, dass sie im 
Vollbesitz ihrer geistigen Kräfte die um einen Taktzyklus verzögerte 
Interruptfreigabe mit voller Absicht genau für den hier diskutierten 
Zweck implementiert haben, sodass damit weder zweckentfremdet noch 
zufällig das Synchronisierungsproblem gelöst werden kann. Diese Theorie 
wird unterstützt durch das folgende Code-Beispiel aus dem Manual:

_SEI(); /* set global interrupt enable */
_SLEEP(); /* enter sleep, waiting for interrupt */
/* note: will enter sleep before any pending interrupt(s) */

Beim Cortex-M4 geht es offenbar anders, aber wie?

Schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Peter II schrieb:
> Markus Grabner schrieb:
>> Jedes mal, wenn auf die gemeinsame Datenstruktur zugegriffen wird,
>> müssen Interrupts vorher gesperrt (und daher nachher wieder freigegeben)
>> werden.
>
> schon klar, aber das ist hier ja nicht der fall.

Wenn dataAvailable() feststellt, dass keine Daten vorhanden sind, und 
unmittelbar danach (noch vor dem sleep()) ein Interrupt kommt, geht das 
Programm trotzdem schlafen, obwohl Daten verfügbar sind.

Schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Lothar schrieb:
> Markus Grabner schrieb:
>> Jedes mal, wenn auf die gemeinsame Datenstruktur zugegriffen wird,
>> müssen Interrupts vorher gesperrt (und daher nachher wieder freigegeben)
>> werden.
>
> Aber nicht beim Cortex-M dafür gibt es ja wie schon erwähnt:
>
> LDREXW/STREXW und DMB (Data Memory Barrier)

Ja, wenn man den Zugriff auf die Datenstruktur so formulieren kann, dass 
nur eine einzige Speicheradresse betroffen ist, kann man so die 
Konsistenz der Daten sicherstellen (aus dem Manual: "The result of 
executing a Store-Exclusive instruction to an address that is different 
from that used in the preceding Load-Exclusive instruction is 
unpredictable"). Bei einer Queue hat man typischerweise neben den 
eigentlichen Daten noch einen Pointer/Index auf die aktuelle Schreib- 
bzw. Lese-Position, das ist also nicht ganz offensichtlich, wie das mir 
LDREX/STREX geht.

Das sichere Aufwecken des Prozessors aus dem "sleep mode" ist ein davon 
unabhängiges Problem.

Schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Fritz schrieb:
> Markus Grabner schrieb:
>> Das habe ich auch schon überlegt, aber die zusätzliche Komplexität durch
>> ein RTOS würde ich gerne vermeiden, vor allem wenn die Synchronisierung
>> so einfach geht (bzw. ginge) wie beim ATmega*.
>
> Natürlich hat ein RTOS eine gewisse Komplexität, aber du brauchst nur
> einen Bruchteil davon zu verwenden. In Bezug auf RAM- und Flashgrösse
> ist das für einen Cortex M4 ein Klacks.
> Es mag ja bei einfachen Applikationen so mit dem ATmega gut
> funktionieren, aber wenn die Applikation mit der Zeit koplexer wird,
> wirst du mit dieser Art von Synchronisation nicht glücklich werden.
>
> Markus Grabner schrieb:
>> Wenn ich mal etwas mehr Zeit habe, kann ich in den FreeRTOS Quellcode
>> schauen, wie das dort gemacht wird, vielleicht lässt sich daraus was
>> ableiten...
>
> Du musst dich von deinem "Hauptprogramm <--> Interrupts" Vorstellungen
> lösen. In einem RTOS hast du halt vrschiedene Tasks (in sich
> abgeschlssene Programmteile) die bestimmte Aufgaben übernehmen und über
> die definierten Funtionen mit anderen Tasks oder ISR kommunizieren. Ein
> Interrupt kommuniziert dann mit einer Task1 Z.B. UART-ISR. Ein anderer
> kommuniziert aber mit Tsk2 Z. B. ADC-ISR. Tasks kann man Prioritäten
> geben, sodass eine Aufgabe Vorrang hat. Hauptprogramm gibt es eigentlich
> nicht, wenn das System nichts zu tun hat, läuft eben die Idle-Task mit
> Z. B. nur sleep in einer loop. Kommt dann ein Interrupt, kann man in der
> ISR die zugehörige Task anstossen um weiterzumachen. Timeouts werden
> ähnlich behandelt.

Hört sich interessant an und ist sicher einen Versuch wert. Wobei eine 
einzelne "main loop", die gleichzeitig auf mehrere Ereignisse warten 
kann, und dann sequentiell die entsprechenden Aktionen anstößt, den 
Vorteil hat, dass die dazugehörigen Programmteile untereinander implizit 
synchronisiert sind. So funktionieren z.B. das select() unter 
Unix/Linux, bzw. darauf aufbauend auch das signal/slot Konzept in Qt. 
Wenn es keinen zwingenden Grund gibt, Tasks parallel laufen zu lassen, 
ist eine sequentielle Bearbeitung meistens einfacher.

Schöne Grüße,
Markus

von Lothar (Gast)


Lesenswert?

Markus Grabner schrieb:
> Bei einer Queue hat man typischerweise neben den
> eigentlichen Daten noch einen Pointer/Index auf die aktuelle Schreib-
> bzw. Lese-Position, das ist also nicht ganz offensichtlich, wie das mir
> LDREX/STREX geht.

Es reicht doch, die Pointer-Adresse als Mutex zu behandeln. Den 
Datenbereich kann man per MPU schützen. Alternativ dazu könnte man auch 
das zugehörige Area-Bit der MPU als Mutex nehmen.

von Bronco (Gast)


Lesenswert?

Oliver S. schrieb:
> Peter Dannegger schrieb:
>> dann kann man das Sleep in die Tonne treten, die
>> Entwickler haben schlichtweg gepennt.
>
> Vorher sollte man aber dann doch mal die komplette Doku des Prozessors
> lesen, um zu verstehen, was der so alles bietet.
>
> Nur weil es einen zweckentfremdeten Spezialtrick, der auf einem AVR
> zufällig funktioniert, auf einem ARM nicht gibt, heisst das ja noch
> lange nicht, das man dieses Problem da gar nicht gelöst bekommt.

Das Problem ist schon lange bekannt, schließlich hat schon der gute alte 
8051 eine um einen Befehl verzögerte Interruptfreigabe.
Also muß der ARM etwas vergleichbares besitzen.

von Oliver S. (oliverso)


Lesenswert?

Markus Grabner schrieb:
> Ja, RTFM ist oft hilfreich, aber wer tut das schon, darum habe ich halt
> einfach mal hier gefragt :-)

Wenn man sich schon in solche Niederungen eines Prozessors begibt, dann 
ist alles andere ausser RTFM völlig sinnlos. Ohne vollständiges 
Verständnis der Funktion des Interruptsystems geht es halt nicht.

Oliver

von Peter D. (peda)


Lesenswert?

Oliver S. schrieb:
> Vorher sollte man aber dann doch mal die komplette Doku des Prozessors
> lesen, um zu verstehen, was der so alles bietet.

Das ist beim ARM leider nicht trivial, da es viele verschiedene 
Dokumente sind, die oft auch für verschiedene ARMs zusammen gewürfelt 
sind, d.h. nicht alles darin muß auf den speziellen ARM auch zutreffen.

Beim AVR hat man nur ein Datenblatt, wo alles drinsteht. Warum 
umständlich, wenn es auch einfach geht.


Vielleicht hat schon jemand das Dokument und die Stelle gefunden, ob es 
eine Möglichkeit gibt, daß das Wait 100% deterministisch funktioniert 
oder ob man es in die Tonne treten muß.
Wie es beim AVR geht, ist nebensächlich, es muß einfach nur überhaupt 
gehen.

Ein Wait muß garantiert immer mit dem 1. Interrupt beendet werden und 
nicht erst mit dem 2. oder 3.
Man kann sich nicht damit herausreden, daß es nur selten passiert und 
man dafür ja einen Watchdog aufsetzen kann.
Wenn man erstmal mit dem Schludern anfängt, gibt es kein Halten mehr.

von Fritz (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Ein Wait muß garantiert immer mit dem 1. Interrupt beendet werden und
> nicht erst mit dem 2. oder 3.
> Man kann sich nicht damit herausreden, daß es nur selten passiert und
> man dafür ja einen Watchdog aufsetzen kann.
> Wenn man erstmal mit dem Schludern anfängt, gibt es kein Halten mehr.

Warum machst du nicht eine Schleife im Hauptprogramm die nur WFI 
ausführt. Den verschiedenen Interrupts gibst du halt entsprechende 
Prioritäten und machst alles in den ISRs.

P:S: Was man unter Schludern versteht, ist halt Ansichtsache. Für mich 
zählt die Methode WFI Interrupt_diablen Interrupt_enablen .. egal ob 
beim ATmega oder ARM eher zum Schludern. Softwaretechnisch sauber ist 
halt eine Synchronisation wie in einem RTOS!

von Oliver S. (oliverso)


Lesenswert?

10 Sekunden Googlen findet das hier:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHBFEIB.html

Vielleicht hilft das ja weiter...

Oliver

von Markus G. (grabner)


Lesenswert?

Oliver S. schrieb:
> 
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHBFEIB.html
Ah, sehr gut, die haben sich ja doch was dabei gedacht :-)

Schöne Grüße,
Markus

von Peter D. (peda)


Lesenswert?

Oliver S. schrieb:
> 
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHBFEIB.html

Das beschreibt leider das genaue Gegenteil:
"If it is necessary to ensure a pended interrupt is recognized before 
subsequent operations"

Benötigt wird aber "after subsequent operations".

von Markus G. (grabner)


Lesenswert?

Markus Grabner schrieb:
> Oder gibt es am Cortex-M4 ganz andere Mechanismen, um Hauptprogramm und
> Interrupts zuverlässig zu synchronisieren?
Ich habe inzwischen auch im ARM-Forum gefragt:

http://forums.arm.com/index.php?/topic/16975-cortex-m4-guaranteed-wakeup-from-wfi

Aus den Beiträgen hier und dort ergibt sich als Lösung die folgende 
Code-Sequenz:

__WFI();
__enable_irq();
__ISB();
__disable_irq();

Nach allem, was ich bisher darüber erfahren habe, bin ich 
zuversichtlich, dass das funktioniert, also werde ich es mal 
ausprobieren...

Danke für die Hilfe & schöne Grüße,
Markus

von Markus G. (grabner)


Lesenswert?

Peter Dannegger schrieb:
> Oliver S. schrieb:
>>
> 
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHBFEIB.html
>
> Das beschreibt leider das genaue Gegenteil:
> "If it is necessary to ensure a pended interrupt is recognized before
> subsequent operations"
>
> Benötigt wird aber "after subsequent operations".

Doch, das passt schon:
*) Zuerst sind Interrupts gesperrt, ein "pending interrupt" weckt den 
Prozessor aber trotzdem auf, ohne dass gleich der Interrupt Handler 
aufgerufen wird.
*) Wenn dann Interrupts wieder zugelassen werden, hat der Prozessor die 
Gelegenheit, den Handler auszuführen.
*) Durch die Synchronisation mit __ISB() ist garantiert, dass der 
Prozessor diese Gelegenheit auch wahrnimmt.
*) Wenn die Interrupts danach sofort wieder gesperrt werden, können die 
im Interrupt Handler empfangenen Daten entgegengenommen werden.

Die Lösung ist ganz anders als beim ATmega*, erfüllt aber den gleichen 
Zweck.

Schöne Grüße,
Markus

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.