Forum: Mikrocontroller und Digitale Elektronik Datenaustausch zwischen ISRs


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich will Daten zwischen zwei ISRs austauschen. Bislang war es mir immer 
gelungen, die Daten so zu erzeugen, daß ich einen atomaren Zugriff 
sicherstellen konnte, oder erlaubt, die ISRs lange genug 
zurückzustellen.

Jetzt muß ich zum ersten Mal eine "echte" Mutex implementieren.

Mein naiver Ansatz bislang:
1
#include <stdint.h>
2
#include <stdbool.h>
3
4
void doSomeSimpleStuff(uint32_t *, int32_t *, int32_t *);
5
void doSomeComplicatedStuff(uint32_t *, int32_t *, int32_t *);
6
7
static volatile struct
8
{
9
    uint32_t s[2];
10
    int32_t v[2];
11
    int32_t a[2];
12
    uint_fast8_t idx;
13
} Set;
14
15
static volatile struct
16
{
17
    uint32_t s;
18
    int32_t v;
19
    int32_t a;
20
    bool lock;
21
} Act;
22
23
24
void ISR_1MHz_Prio0(void)
25
{
26
    uint_fast8_t i = Set.idx;
27
    uint32_t s = Set.s[i];
28
    int32_t v = Set.v[i];
29
    int32_t a = Set.a[i];
30
31
    doSomeSimpleStuff(&s, &v, &a);
32
33
    if( !Act.lock ) {
34
        Act.s = s;
35
        Act.v = v;
36
        Act.a = a;
37
    }
38
}
39
40
41
void ISR_10_kHz_Prio1(void)
42
{
43
    Act.lock = true;
44
    uint32_t s = Act.s;
45
    int32_t v = Act.v;
46
    int32_t a = Act.a;
47
    Act.lock = false;
48
49
    doSomeComplicatedStuff(&s, &v, &a);
50
51
    uint_fast8_t i = Set.idx;
52
    i = 1-i;
53
    Set.s[i] = s;
54
    Set.v[i] = v;
55
    Set.a[i] = a;
56
    Set.idx = i;
57
}

Daß die langsame Funktion in ihrem Verlauf mit total veralteten Daten 
der schnellen Funktion konfrontiert wird, ist normal und ich kann damit 
leben. Nur die Tupel s, v, a müssen immer konsistent zusammenpassen, 
ansonsten passieren schlimme Dinge.

Funktioniert das so, oder habe ich irgendeine mögliche race condition 
nicht bedacht?

Viele Grüße
W.T.

Beitrag #5191301 wurde vom Autor gelöscht.
von Stefan F. (Gast)


Lesenswert?

> Funktioniert das so

Ich denke, das wird funktionieren. Ich würde aber (wenn es die CPU 
Performance erlaubt) trotz der Prioritäten in beiden ISR prüfen, ob der 
Lock schon gesetzt ist.

Wenn dein Code unabhängig von der Rangfolge der Prio ist, wird es viel 
leichter, als Kopiervorlage für neue Projekte zu dienen.

von MaWin (Gast)


Lesenswert?

Welche Prio ist höher und welche ISR kann welche ISR unterbrechen?

von Walter T. (nicolas)


Lesenswert?

ISR_1MHz_Prio0 hat eine höhere Taktrate als ISR_10_kHz_Prio1 und kann 
diese auch unterbrechen.

von Vincent H. (vinci)


Lesenswert?

1
Act.lock = true;

ist nicht zwangsweise atomar. Sofern du in reinem C bleiben willst 
empfehle ich dir eine Mutex Implementierung für deine spezifische 
Platform zu suchen!

Ansonsten bietet sich eventuell C++ an, dass je nach Platform bereits 
ein Mutex hat. Und wenn nicht, dann mit hoher Wahrscheinlichkeit 
zumindest std::atomic, womit man sich recht einfach selbst ein Mutex 
basteln kann.

von (prx) A. K. (prx)


Lesenswert?

Vincent H. schrieb:
> ist nicht zwangsweise atomar. Sofern du in reinem C bleiben willst
> empfehle ich dir eine Mutex Implementierung für deine spezifische
> Platform zu suchen!

Klar. Ne Mutex im Interrupt-Handler. Atomic ist ok, hier aber unnötig. 
Wichtig ist nur, dass die Reihenfolge der Operationen nicht umoptimiert 
wird. Aber dafür sollten die volatiles sorgen.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Vincent H. schrieb:
> ist nicht zwangsweise atomar.

In C11 wohl schon. Und Plattformen, auf denen es vorher nicht atomar 
war, sind mittlerweile arg selten.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Warum sperrst du die interrupts nicht, statt dieses handgestrickte Mutex 
zu verwenden?

von (prx) A. K. (prx)


Lesenswert?

MaWin schrieb:
> Warum sperrst du die interrupts nicht, statt dieses handgestrickte Mutex
> zu verwenden?

In einem niedrig priorisierten Interrupt während einer Funktion mit dem 
bezeichnenden Namen doSomeComplicatedStuff einen 1MHz Interrupt sperren?

von Stefan F. (Gast)


Lesenswert?


von MaWin (Gast)


Lesenswert?

A. K. schrieb:
> In einem niedrig priorisierten Interrupt während einer Funktion mit dem
> bezeichnenden Namen doSomeComplicatedStuff einen 1MHz Interrupt sperren?

Nein. Nur während des Schreibens und Lesens der 3 Werte.

von Walter T. (nicolas)


Lesenswert?

Vincent H. schrieb:
> Act.lock = true;
>
> ist nicht zwangsweise atomar.

Bei einer globalen Variable? Woher? Gibt es dazu Material?

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
>> Warum sperrst du die interrupts nicht, statt dieses handgestrickte Mutex
>> zu verwenden?
>
> In einem niedrig priorisierten Interrupt während einer Funktion mit dem
> bezeichnenden Namen doSomeComplicatedStuff einen 1MHz Interrupt sperren?

Der hier gezeigte Code ist ja offensichtlich nicht der echte Code. Wenn 
im 1MHz Interrupt wirklich nichts sonst passiert, dann wärs natürlich 
ein Weg.

von (prx) A. K. (prx)


Lesenswert?

Walter T. schrieb:
> Bei einer globalen Variable? Woher? Gibt es dazu Material?

Die erste Generation der DEC Alphas konnte nicht direkt auf Bytes 
zugreifen. Wenn da "bool" ein Byte sein sollte, dann läufts technisch 
auf getrennte
  load
  merge
  store
Befehle raus, und das ist tatsächlich nicht atomar. Ist aber 
mittlerweile recht exotisch. Vielleicht gibts DSPs mit dieser 
Eigenschaft, aber da wird dann "bool" wohl kein Byte sein.

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

A. K. schrieb:
> Vincent H. schrieb:
>> ist nicht zwangsweise atomar. Sofern du in reinem C bleiben willst
>> empfehle ich dir eine Mutex Implementierung für deine spezifische
>> Platform zu suchen!
>
> Klar. Ne Mutex im Interrupt-Handler. Atomic ist ok, hier aber unnötig.
> Wichtig ist nur, dass die Reihenfolge der Operationen nicht umoptimiert
> wird. Aber dafür sollten die volatiles sorgen.

Sagt ja keiner was von blockend...

von Carl D. (jcw2)


Lesenswert?

Wenn man
- lock in changed umbenennt,
- der Leser dieses vor dem Lesen auf false setzt (1Byte;μC, Single 
Core),
- er dann seine Kopie zieht,
- danach das changed-Flag kontrolliert und ggf. nochmal kopiert,
- der Schreiber (höher Prio ISR, Leser sieht Schreiber's Zugriffe 
atomar)
  setzt das changed-Flag, sobald er geschrieben hat.
Nennt sich "lock-free", behindert den Schnelleren nicht und macht dem 
Langsameren nur minimalen Zusatzaufwand.

von M. Н. (Gast)


Lesenswert?

Walter T. schrieb:
> Vincent H. schrieb:
>> Act.lock = true;
>>
>> ist nicht zwangsweise atomar.
>
> Bei einer globalen Variable? Woher? Gibt es dazu Material?

Die Variable wird im RAM des Controller abgelegt und von beiden ISRs 
verwendet. Selbst wenn der Zugriff sofort passiert (volatile), so dauert 
die Prozedur je nach prozessor mehrere Takte und umfasst verschiedene 
Befehle.

Diese Mutex Implementierung sollte funktionieren, da hier nur von einer 
Seite aus geschrieben wird. Das volatile stellt sicher, dass die 
Variable im RAM sicher geschrieben ist, bevor der kritische code 
ausgeführt wird.

Bei einem Lock, auf das von mehreren stellen aus zugegriffen wird, ist 
das Ganze schwieriger und meist sehr architekturabhängig zu lösen.

von Walter T. (nicolas)


Lesenswert?

Die Namen "ISR_1MHz_Prio0" und "ISR_10_kHz_Prio1" sind nicht komplett 
zufällig gewählt.

Normalerweise bin ich niemand, dem es auf den letzten Taktzyklus 
ankommt, aber diesemal will ich den Overhead insbesondere in der ersten 
Funktion minimal halten. Jeden der theoretisch möglichen 72 Taktzyklen, 
den diese ISR nicht benötigt, kann ich an anderer Stelle gut gebrauchen. 
(Andererseits ist die Last momentan noch niedrig genug, die ISR nicht in 
Assembler schreiben zu müssen.)

Was mir auffällt: Die Diskussion dreht sich nur um den lock-Mechanismus 
in "Act". Heißt das, daß im Doppelpuffer-Mechanismus in "Set" kein 
Problem auftreten kann?


Carl D. schrieb:
> Nennt sich "lock-free", behindert den Schnelleren nicht und macht dem
> Langsameren nur minimalen Zusatzaufwand.

Sehe ich das richtig, daß man damit einen langsamen Task, der nur sehr 
spärlich Zyklen abbekommt, komplett in den Stillstand bremsen kann?

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Walter T. schrieb:
> Heißt das, daß im Doppelpuffer-Mechanismus in "Set" kein
> Problem auftreten kann?

Nö. Sondern dass du nicht deutlich genug gemacht hast, dass es dir auch 
um diesen Aspekt geht. ;-)

von MaWin (Gast)


Lesenswert?

Walter T. schrieb:
> Normalerweise bin ich niemand, dem es auf den letzten Taktzyklus
> ankommt, aber diesemal will ich den Overhead insbesondere in der ersten
> Funktion minimal halten. Jeden der theoretisch möglichen 72 Taktzyklen,

Eine ganz normale Interruptsperre verbraucht vermutlich nicht mehr 
Taktzyklen als das lesen des Locks und der bedingte Sprung.

Beitrag #5191417 wurde vom Autor gelöscht.
von Carl D. (jcw2)


Lesenswert?

Walter T. schrieb:
> Carl D. schrieb:
>> Nennt sich "lock-free", behindert den Schnelleren nicht und macht dem
>> Langsameren nur minimalen Zusatzaufwand.
>
> Sehe ich das richtig, daß man damit einen langsamen Task, der nur sehr
> spärlich Zyklen abbekommt, komplett in den Stillstand bremsen kann?

Ich gehe mal davon aus, daß du gerade Grundlagenforschung betreibst und 
nicht wirklich jede μs neue Daten per ISR in den Speicher legen willst 
und erwartest, daß da noch viel Zeit für andere ISR's übrig bleibt. Mein 
Vorschlag ist eher geeignet die schnelle ISR nicht sperren zu müssen, um 
in der Langsamen/Mainloop konsistente Daten zu lesen.

von RAc (Gast)


Lesenswert?

Walter T. schrieb:
> Die Namen "ISR_1MHz_Prio0" und "ISR_10_kHz_Prio1" sind nicht
> komplett
> zufällig gewählt.
>
...
>
> Sehe ich das richtig, daß man damit einen langsamen Task, der nur sehr
> spärlich Zyklen abbekommt, komplett in den Stillstand bremsen kann?

Deine Namensgebung ist verwirrend. Gegenseitiger Ausschluss in ISRs ist 
etwas komplett Anderes als zwischen Tasks, in sofern ist der Begriff 
Mutex i.Z. mit ISRs unglücklich, weil der Begriff Mutex überall so 
verwendet wird, dass die beteiligten Handlungsstränge (= Tasks) 
suspendiert werden können. Das ist bei ISRs selbstverständlich ein 
absolutes Nono.

Ich gehe jetzt mal davon aus, dass es bei Dir nur um ISRs geht (deinen 
letzten Posts nach zu urteilen hast Du kein OS). Dann tritt ein 
asymmetrischer Mechanismus in Kraft: Der höher priorisierte ISR ist per 
Definition nicht durch einen nieder priorisierten ISR unterbrechbar, 
d.h. alle Operationen, die während des höher priorisierten ISR ohne 
Verlassen nacheinander ausgeführt werden, sind immer "atomisch" 
(jedenfalls relativ zum niederer priorisierten).

Umgekehrt ist die einzige Möglichkeit für den nieder priorisierten ISR, 
eine Sequenz "atomisch" gegenüber dem höher priorisierten auszuführen, 
den ISR zu sperren (ausser Du kannst Dich darauf verlassen, dass die 
Instruktionen controllerseitig ununterbrechbar sind. Sich auf sowas wie 
den Exclusive Access Monitor zu verlassen, ist bei ISRs übrigens eine 
schlechte Idee).

Was genau macht doSomeSimpleStuff(&s, &v, &a) mit den Parametern? Nicht 
etwas busy waiting oder andere in ISRs verbotenen Operationen?

von Walter T. (nicolas)


Lesenswert?

MaWin schrieb:
> Eine ganz normale Interruptsperre verbraucht vermutlich nicht mehr
> Taktzyklen als das lesen des Locks und der bedingte Sprung.

Der schnelle Interrupt darf nie gesperrt werden in diesem 
Anwendungsfall und der langsame Interrupt braucht nicht gesperrt zu 
werden, weil er die schnelle ISR nicht unterbricht.

von Stefan F. (Gast)


Lesenswert?

> Die Diskussion dreht sich nur um den lock-Mechanismus in "Act".

Weil das die Einzige Stelle ist, wo du überhaupt so etwas ähnliches wie 
ein Locking eingebaut hast. Dein "Set" Teil braucht sicher auch noch 
eine Anpassung.  Aber eins nach dem anderen ist besser.

> Sehe ich das richtig, daß man damit einen langsamen Task, der nur sehr
> spärlich Zyklen abbekommt, komplett in den Stillstand bremsen kann?

Ja.

1Mhz Interruptrate ist schon extrem hoch, das riecht schon nach einem 
falschen Lösungsansatz. Aber darüber können nichts sagen, weil dein 
Anwendungsfall ja völlig unbekannt ist.

von MaWin (Gast)


Lesenswert?

Walter T. schrieb:
> Der schnelle Interrupt darf nie gesperrt werden in diesem

Und warum nicht? Es ist ja offensichtlich auch Ok, wenn Daten verworfen 
werden, solange dein Mutex gelockt ist.

von Peter D. (peda)


Lesenswert?

MaWin schrieb:
> A. K. schrieb:
>> In einem niedrig priorisierten Interrupt während einer Funktion mit dem
>> bezeichnenden Namen doSomeComplicatedStuff einen 1MHz Interrupt sperren?
>
> Nein. Nur während des Schreibens und Lesens der 3 Werte.

So ist es. Das Sperren erfolgt ja nur für die 3 LOAD-Befehle, ist also 
komplett zu vernachlässigen. Dein doSomeComplicatedStuff() wird bestimmt 
deutlich länger als 3 CPU-Takte brauchen.

von Ralf G. (ralg)


Lesenswert?

Nur mal so nebenbei...
Sind die Taktquellen für die zwei Interrupts verschieden? (Ändert sich 
der Zeitpunkt des Aufrufs dadurch geringfügig/ zufällig?) -> die 
10kHz-ISR nur ein Flag setzen lassen und die Berechnungen in der 
1MHz-ISR ausführen.

von Walter T. (nicolas)


Lesenswert?

RAc schrieb:
> oder andere in ISRs verbotenen Operationen?

Ich mache keine verbotenen Sachen (weder in der ISR noch sonst), und die 
Taktrate der schnellen ISR ist ungewöhnlich hoch, aber das mit sehr 
gutem Grund. Die ISR ersetzt mir eine externe Logik. Dafür ist es 
notwendig, den Jitter in engen Grenzen zu halten.


MaWin schrieb:
> Walter T. schrieb:
>> Der schnelle Interrupt darf nie gesperrt werden in diesem
>
> Und warum nicht?

Ist so. Unverhandelbar. Word of God.


Ralf G. schrieb:
> Sind die Taktquellen für die zwei Interrupts verschieden? (Ändert sich
> der Zeitpunkt des Aufrufs dadurch geringfügig/ zufällig?) -> die
> 10kHz-ISR nur ein Flag setzen lassen und die Berechnungen in der
> 1MHz-ISR ausführen.

Nein, die Interruptquellen sind nicht 100% getrennt. Die 1-MHz-ISR ruft 
die 10-kHz-ISR alle 100 Aufrufe auf, die dann aufgrund der IRQ-Priorität 
hintenangestellt wird. Die 10-kHz-ISR macht dann alle Berechnungen, die 
für die 1-MHz-ISR zu lange dauern. Die 10-kHz-ISR macht nur einfachen 
Kram.


A. K. schrieb:
> Nö. Sondern dass du nicht deutlich genug gemacht hast, dass es dir auch
> um diesen Aspekt geht. ;-)

Also, noch einmal zur Klarstellung:
Ich will zwischen der schnellen und der langsamen ISR insgesamt jeweils 
12 Bytes in beide Richtungen austauschen, die zusammenpassen und nicht 
den Stand unterschiedlicher Zeitpunkte widerspiegeln. Mehr nicht.


Erwartet hätte ich Antworten der Art: "Das kann schiefgehen, wenn ...".
Davon habe ich nur eine bekommen, nämlich daß evtl. eine boolesches 
Variable nicht unbedingt atomar gelesen und geschrieben werden kann. Das 
werde ich überprüfen. Der ARM bietet für genau den Zweck Bitbanding an, 
aber bislang schien mir das nur nötig, wenn ich aus einem Bitfeld 
einzelne Bits setzen oder löschen will. Außerdem wurden ja auch schon 
die entsprechenden Funktionen des GCC vorgeschlagen. Momentan gehe ich 
davon aus, daß eine bool klammheimlich ein uint32 ist. Das kann ich 
später problemlos mit dem Debugger überprüfen.

Die anderen Bedenken gingen in die Richtung "das geht anders aber 
schöner".

Daraus schließe ich erst einmal, daß meine "unschöne" Lösung 
grundsätzlich funktioniert.

Danke für die Diskussion.

von MaWin (Gast)


Lesenswert?

>Ist so. Unverhandelbar. Word of God.

Das ist keine Begründung.
Gehst du mit deinen Kollegen auch so um? Dann bist du wohl sehr beliebt.

von Walter T. (nicolas)


Lesenswert?

MaWin schrieb:
> Gehst du mit deinen Kollegen auch so um? Dann bist du wohl sehr beliebt.

Nur mit denen, die am Ende einer Diskussion, bei der sie nichts 
mitbekommen haben, aber viel zu sagen haben, noch stundenlang 
nachquengeln wollen. Macht mich bei den anderen Kollegen durchaus nicht 
unbeliebter.

von MaWin (Gast)


Lesenswert?

Walter T. schrieb:
> Nur mit denen, die am Ende einer Diskussion, bei der sie nichts
> mitbekommen haben, aber viel zu sagen haben, noch stundenlang
> nachquengeln wollen.

Und was hat das jetzt mit diesem Thread zu tun?

von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Nein, die Interruptquellen sind nicht 100% getrennt. Die 1-MHz-ISR ruft
> die 10-kHz-ISR alle 100 Aufrufe auf, die dann aufgrund der IRQ-Priorität
> hintenangestellt wird.

Nun, dann sind sie doch synchron und Du brauchst garnichts zu sperren.
Die 1MHz-ISR muß nur soviel Luft haben, daß die ersten 3 Load-Befehle 
der 10kHz-Routine erfolgen, bevor die 1MHz-ISR erneut triggert.

Walter T. schrieb:
> Ist so. Unverhandelbar. Word of God.

Jeder, der schon etwas länger programmiert weiß, daß absolut garnichts 
in Stein gemeißelt ist. Es gibt immer mehrere Lösungen für eine Aufgabe. 
Und oft erreicht man viel effektivere Lösungen, wenn man mal über den 
eigenen Tellerrand hinausschaut.

von MaWin (Gast)


Lesenswert?

Peter D. schrieb:
> Jeder, der schon etwas länger programmiert weiß, daß absolut garnichts
> in Stein gemeißelt ist. Es gibt immer mehrere Lösungen für eine Aufgabe.
> Und oft erreicht man viel effektivere Lösungen, wenn man mal über den
> eigenen Tellerrand hinausschaut.

Erkenntnis ist hier nicht gewünscht.
Es ist nur gewünscht sich das "OK" für die existierende Lösung zu holen.
Alternative Vorschläge werden belächelt oder schnippisch beantwortet.

Ich bin froh, nicht der Kollege des OP zu sein.

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Wenn die 1MHz-ISR die 10kHz-ISR laufend stört, dann wird vom 
Haupt-Programm wohl auch nicht wirklich was abgearbeitet.
Eigentlich sollte die 10kHz-ISR mit der Arbeit fertig sein, bevor der 
neue Start-Befehl kommt - somit wäre am Anfang der 10kHz-Routiene 'genug 
Zeit', um die Variablen abzuspeichern, bevor der nächste 1MHz-Hammer 
geschlagen wird.
Vll. bekommt man so nur die Daten der letzten Berechnung gespeichert, 
aber vll. reicht Das ja schon.
Die Daten der jetzigen Berechnung gibt's dann beim nächsten Aufruf.

MfG

von Walter T. (nicolas)


Lesenswert?

Peter D. schrieb:
> Nun, dann sind sie doch synchron und Du brauchst garnichts zu sperren.
> Die 1MHz-ISR muß nur soviel Luft haben, daß die ersten 3 Load-Befehle
> der 10kHz-Routine erfolgen, bevor die 1MHz-ISR erneut triggert.

Also eine memory barrier einziehen, daß die volatile-Variablen auch 
wirklich am Anfang ausgelesen werden, und nicht vom Compiler das 
Einlesen lazy nach hinten geschoben wird?

Irgendwie widerstrebt mir das ein wenig, mich auf das Timing zu 
verlassen, eben auch in Anbetracht der Tatsache, daß die Triggerung der 
ISRs momentan noch nicht in Stein gemeißelt ist.

Aber es ist ein guter Denkanstoß.


Peter D. schrieb:
> Jeder, der schon etwas länger programmiert weiß, daß absolut garnichts
> in Stein gemeißelt ist. Es gibt immer mehrere Lösungen für eine Aufgabe.

Genau. Und wenn ein Teil dieser Lösung (Sperren der Interrupts und 
Variablen so schachteln, daß sie in einem atomaren Zugriff geschrieben 
oder gelesen werden können) ausreichend genau betrachtet wurde, muß man 
auch mal dazu übergehen, die anderen Lösungen zu betrachtet, und nicht 
immer wieder dieses alte Thema aus der Versenkung holen.


Patrick J. schrieb:
> Eigentlich sollte die 10kHz-ISR mit der Arbeit fertig sein, bevor der
> neue Start-Befehl kommt

Das ist kein Problem. Die 10-kHz-ISR ist laaaaaaaange fertig, bevor sie 
das nächste Mal wieder aufgerufen wird. Sie braucht knapp 4 µs.

von MaWin (Gast)


Lesenswert?

Walter T. schrieb:
> und nicht
> immer wieder dieses alte Thema aus der Versenkung holen.

Altes ist schlecht.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Walter T. schrieb:
> Peter D. schrieb:
>> Nun, dann sind sie doch synchron und Du brauchst garnichts zu sperren.
>> Die 1MHz-ISR muß nur soviel Luft haben, daß die ersten 3 Load-Befehle
>> der 10kHz-Routine erfolgen, bevor die 1MHz-ISR erneut triggert.
>
> Also eine memory barrier einziehen, daß die volatile-Variablen auch
> wirklich am Anfang ausgelesen werden, und nicht vom Compiler das
> Einlesen lazy nach hinten geschoben wird?
>

Vorsicht, das Einziehen einer Barriere (ich vermute mal, dass wir hier 
mit der Nomenklatur des Cortex arbeiten, also das Einfügen einer DMB 
oder DSB Instruktion) ändern m.W. nach nichts am vom Compiler 
generierten Code, sie bewirken nur zur Laufzeit einen Abschluss aller 
asynchronen Transfers vor dem nächsten execution Zyklus (was natürlich 
im Kontext der Diskussion auch wichtig ist).

Oder was genau meinst Du mit Memory Barrier?

von (prx) A. K. (prx)


Lesenswert?

Es gibt auch eine gcc memory barrier: asm volatile("":::"memory");

von Walter T. (nicolas)


Lesenswert?

Ruediger A. schrieb:
> Oder was genau meinst Du mit Memory Barrier?

die ISRs sehen natürlich tatsächlich nicht exakt wie in im obigen 
Beispiel aus. Um genau zu sein: Sehen sie schon, nur die Funktion 
doSomeComplicatedStuff ist keine Funktion, sondern ein längeres Stück 
Quelltext (das ich nicht posten werde, weil es noch nicht fertig ist).

Näherungsweise reicht aber das hier:
1
void ISR_10_kHz_Prio1(void)
2
{
3
4
     uint32_t s = Act.s;
5
     int32_t v = Act.v;
6
     int32_t a = Act.a;
7
8
 
9
     f1(&s);     // dauert
10
     f2(&s, &v); // dauert auch
11
12
     f3(&a); // <<< Bis hierhin kann der Compiler das Lesen von a verzögern.
13
 
14
     uint_fast8_t i = Set.idx;
15
     i = 1-i;
16
     Set.s[i] = s;
17
     Set.v[i] = v;
18
     Set.a[i] = a;
19
     Set.idx = i;
20
 }

Wäre die ISR so einfach, könnte ich natürlich f3(&a) an den Anfang 
setzen. Dann müßten aufgrund volatile alle s,v,a gelesen sein.

Aber das sieht nach einer Lösung aus, die mir irgendwann auch wieder auf 
die Füße fallen kann, wenn irgendetwas geändert wurde.

: Bearbeitet durch User
von Ralf G. (ralg)


Lesenswert?

Walter T. schrieb:
> Die 10-kHz-ISR ist laaaaaaaange fertig, bevor sie
> das nächste Mal wieder aufgerufen wird. Sie braucht knapp 4 µs.

Netto oder Brutto ;-) Denn:
Wird die 10kHz-ISR jetzt 4x unterbrochen oder wird die 1MHz-ISR 4x 
verpasst?

von Walter T. (nicolas)


Lesenswert?

Ralf G. schrieb:
> Netto oder Brutto

Wie auch immer das definiert ist.

Es sind knapp 4 µs, d.h. die langsame ISR wird dreimal unterbrochen.

An der Stelle ist es eine wirklich gemütliche Angelegenheit - aber die 
Last wird vermutlich sogar noch etwas sinken.

Beitrag #5191734 wurde vom Autor gelöscht.
von Pandur S. (jetztnicht)


Lesenswert?

> Es sind knapp 4 µs, d.h. die langsame ISR wird dreimal unterbrochen.

Was natuerlich Bullshit ist. Voellig falsches Konzept. Man splittert die 
10kHz ISR in mehrere Bloecke, die man mit einer Zustandsmaschine 
abspult.

von Ralf G. (ralg)


Lesenswert?

Sapperlot W. schrieb:
> Man splittert die 10kHz ISR

Kann man das nicht als ganz normale Funktion in der 'main' alle 100µs 
anschubsen? Dann sind auch alle anderen Berechnungen ringsherum in einem 
definierten Zustand.

von Walter T. (nicolas)


Lesenswert?

Sapperlot W. schrieb:
> Was natuerlich Bullshit ist. Voellig falsches Konzept. Man splittert die
> 10kHz ISR in mehrere Bloecke, die man mit einer Zustandsmaschine
> abspult.

OK, dann zeig mal.

Die Haupt-Zeit geht in diesen paar Zeilen verloren:
1
assert( i >= 1);
2
    if( i < 4096 ) {
3
      // Fix 22:10, davon nur signifikant 6:10
4
      //uint32_t fac = (sqrt(i<<20UL)-sqrt((i-1)<<20UL));
5
      uint32_t fac = (sqrt32(i<<20UL)-sqrt32((i-1)<<20UL));
6
      assert( fac < (1U<<16));
7
      return (uint32_t) ( ((uint64_t) dt_1 * fac)>>10);
8
    }
9
    else if( i < (1UL<<16) ) {
10
      // Fix 24:8, davon nur signifikant 8:8
11
      uint32_t fac = (sqrt32(i<<16UL)-sqrt32((i-1)<<16UL));
12
      assert( fac < (1U<<16));
13
      return (uint32_t) ( ((uint64_t) dt_1 * fac)>>8);
14
    }
15
    else {
16
      error('Resolution error');
17
      return EXIT_FAILURE;
18
    }

Die restlichen Zeilen kriege ich schon selbst hin. Die sind zusammen 
weniger als 1 µs und lassen sich auch sauber abtrennen.

von Pandur S. (jetztnicht)


Lesenswert?

Also.

const uint8 reloadloop =100;
volatile uint8 : loopcount = reloadloop;
volatile uint8 : zsm =0xff;

interrupt 1MHz {
 .. mach was ...
 loopcount--;
 if (loopcount ==0) {
  loopcount = reloadloop;
  zsm =0;
 }
}

main () {
 loop forever {
  if zsm !=0xff {
   switch zsm {
    0: //hier zustand 0 einfuellen !!
     zsm =1;
     break;
    1: //hier zustand 1 einfuellen !!
     zsm =2;
     break;
    2: //hier zustand 2 einfuellen !!
     zsm =3;
     break;
    3: //hier zustand 3 einfuellen !!
     zsm =4;
     break;
    4: //hier zustand 4 einfuellen !!
     zsm =5;
     break;
    5: //hier zustand 5 einfuellen !!
     zsm =0xff;
     break;
   } // switch
  }
  .. weiter mit main ..
  ..
  ..
 } // loop forever
}

Dann schaut man sich die Mathematik etwas an. Was denn wirklich noetig 
ist.

uint32_t fac = (sqrt32(i<<20UL)-sqrt32((i-1)<<20UL));

Also  i <= 1 < 4096 , dann haenge ich noch 20 Nullen an und ziehe die 
Wurzel. Das bedeutet ich kann mir den Assert auf 65536 danach sparen.

.. man muesste mehr davon wissen.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Der assert() ist ohnehin nur Füllmaterial fürs Debugging.

Und wie teilst Du jetzt die Wurzel auf, deren Berechnung allein mehr als 
1 µs kostet?

Ich mache es kurz: Ich halte es für deutlich praktischer, die 
Fähigkeiten einer Nested-Interrupt-MCU zu nutzen, als krampfhaft 
irgendwelche Rechnungen in Stücke zu brechen, wobei ich bei jedem Stück 
einzeln gucken muß, ob es die Zeitbeschränkung reißt.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Eventuell brauchst du keine Wurzel, sondern nur ungefähr. Dann könnte 
ein iterativer Algorithmus schneller sein.

von Pandur S. (jetztnicht)


Lesenswert?

Ich habe das Gefuehl, die Wurzel ist vereinfachbar. Stark vereinfachbar. 
Weil der Faktor 2^20, resp 2^16 abtrennbar ist. Da kann man sich einige 
Zyklen sparen.
Wenn man so eine Berechnung im Main macht, darf sie auch ohne drueber 
nachzudenken unterbrochen werden.

Was soll die Berechnung denn machen ? Allenfalls kann man sie ersetzen 
durch etwas einfacheres

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Sapperlot W. schrieb:
> Was soll die Berechnung denn machen ?

Ich orientiere mich jetzt einfach mal ein Deinem Nutzernamen: "Jetzt 
nicht." Wenn mein Projekt so weit ist, werde ich es, wenn es nicht 
irgendwo scheitert, wahrscheinlich Open Source machen.

Das Skalieren der Wurzel hat den Grund, daß ich ekligerweise an einer 
Stelle in der Rechnung sqrt(i)-sqrt(i-1) machen muß, d.h. ich will und 
muß Rundungsfehler minimieren.

Andererseits ist die Sache auch noch nicht so weit gediehen, daß es 
schon sinnvoll wäre, viel Zeit in die Optimierung (Reihenentwicklung, 
lookuptable etc.) zu stecken. Unter Umständen wird die Wurzel-Funktion 
wieder für immer verschwinden.

Meine Lösung also: Das, was schnell sein muß, in eine schnelle ISR, das, 
was langsam sein darf, in eine langsame ISR. Die MCU kümmert sich um den 
ganzen Rest, ich kann mir "sinnvolle Aufteilung" und Optimierungen erst 
einmal sparen und mich mit den Kernproblemen beschäftigen. Die sind 
kompliziert genug.

Einzige Mehrkosten für mich: Eine ISR und ich muß dann eben 12 Byte 
konsistent zwischen den beiden Routinen austauschen. Den Preis bin ich 
problemlos bereit zu zahlen.

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.