Hey Leute,
wollte mal fragen, ob jemand eine atomic-lib, wie es sie für den AVR-GCC
gibt
(http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html),
auch für die STM32 kennt. Compiler soll auch hier der GCC sein falls das
wichtig ist.
Ich konnte leider nichts passendes im Internet finden, was mich
allerdings schon ein wenig wundert. Als ob beim STM32 keiner mehr
atomare Operationen durchführen möchte...
Vielen Dank
Gruß Leo
Da der AVR nur 8 Bit hat und ein int 16 Bit sind Operationen wie int i++
nicht atomar da sie 2 Speicher Zugriffe erfordern.
Der STM32 (ARM) ist ein 32 Bitter und ein int hat 32 Bit und somit ist
int i++ atomar.
Außerdem hat der ARM einen Befehlssatz der modify Operationen auf
Assembler Ebene atomar ausführt.
Mit dem STM32F10x_StdPeriph_Driver werden auch I/O Zugriffe atomar.
Reiner S. schrieb:> Der STM32 (ARM) ist ein 32 Bitter und ein int hat 32 Bit und somit ist> int i++ atomar.
Diese Folgerung stimmt nicht notwendigerweise, ist aber im Falle des ARM
korrekt. Was allerdings nicht korrekt ist, ist die Annahme dass
"Variable lesen => Variable inkrementieren => Variable zurückspeichern"
atomar ist! Dazu braucht man schon Locks. Das geht aber auf
Multithreading-freundlichen CPU's wie eben dem ARM wohl recht effizient.
Reiner S. schrieb:> Außerdem hat der ARM einen Befehlssatz der modify Operationen auf> Assembler Ebene atomar ausführt.
Aber eben nur auf Registern.
> Mit dem STM32F10x_StdPeriph_Driver werden auch I/O Zugriffe atomar.
Ach? Und wie sieht es mit folgendem aus (aus eben dieser Library,
gekürzt):
> Der STM32 (ARM) ist ein 32 Bitter und ein int hat 32 Bit und somit ist> int i++ atomar.>>Diese Folgerung stimmt nicht notwendigerweise, ist aber im Falle des ARM>>korrekt. Was allerdings nicht korrekt ist, ist die Annahme dass>>"Variable lesen => Variable inkrementieren => Variable zurückspeichern">>atomar ist! Dazu braucht man schon Locks. Das geht aber auf>>Multithreading-freundlichen CPU's wie eben dem ARM wohl recht effizient.
Beim AVR ist int i++ schon auf Registerebene nicht atomar und das meinte
ich.
Dr. Sommer schrieb:>> Mit dem STM32F10x_StdPeriph_Driver werden auch I/O Zugriffe atomar.> Ach? Und wie sieht es mit folgendem aus (aus eben dieser Library,> gekürzt):void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState)> {> if (NewState != DISABLE) {> ADCx->CR2 |= (uint32_t)ADC_CR2_ADON; // <-- (mindestens) 3> Instruktionen, überhaupt nicht atomar> } else {> ADCx->CR2 &= (uint32_t)(~ADC_CR2_ADON); // dito> }> }
Da habe ich mich unklar ausgedrückt denn ich meine die Zugriffe auf Bit
Ebene. Das Beispiele mit kann nicht atomar seinen und ST überlässt es
dem Anwender bei bedarf vor Aufruf der Funktion die Interrups zu
sperren.
Das halte ich für eine gute Idee.
Reiner S. schrieb:> Beim AVR ist int i++ schon auf Registerebene nicht atomar und das meinte> ich.
Jo. Dass das beim ARMv7M auf Registerebene atomar ist bringt aber
praktisch nix, da es auf Speicherebene nicht atomar ist (außer
Geschwindigkeit).
Reiner S. schrieb:> Da habe ich mich unklar ausgedrückt denn ich meine die Zugriffe auf Bit> Ebene.
Ja aber das bringt nix, da es immer noch load-modify-store ist! Ob die
Funktion intern mit 1 oder mehreren Operationen auf dem temporären
Register arbeitet macht doch überhaupt keinen Unterschied.
> Das Beispiele mit kann nicht atomar seinen und ST überlässt es> dem Anwender bei bedarf vor Aufruf der Funktion die Interrups zu> sperren.> Das halte ich für eine gute Idee.
Ja, ist es auch. Aber zu behaupten die StdPeriphal Library wäre "atomar"
ist gefährlich ;-)
Entweder ist meine Frage unpräziese gestellt, oder das Thema hat sich
ein wenig vom Ursprünglich angesprochenen/gefragten entfernt.
Klar bietet die ARM-Cortex nen haufen Atomarer Operationen aber bereits
ein einfaches togglen eines Port-Pins (nur als Beispiel) gehört meines
Wissens schon nicht mehr dazu...
Da ich aber das Interrupts sperren und freigeben mit 2 Funktionen nicht
halb so übersichtlich finde wie einen ATOMIC_BLOCK hätte ich mich
gefreut wenn bereits jemand so etwas für den STM32 (in meinem Fall
konkret den STM32F100) geschrieben und veröffentlicht hätte. Ich selbst
stehe noch sehr am Anfang des Verständnisses der ARM-Rechenkerne und
könnte soetwas vermutlich nicht halb so elegant und performant
implementieren wie ein erfahrener Programmierer.
Zum Vergleich:
1
[...]
2
someNonAtomicCode();
3
__disable_irq();
4
doSomeThing();
5
// some comment
6
doAtomicStuff();
7
// another comment
8
lastAtomicOperation();
9
__enable_irq();
10
someNonAtomicCode();
11
[...]
1
[...]
2
someNonAtomicCode();
3
ATOMIC_BLOCK(...)
4
{
5
doSomeThing();
6
// some comment
7
doAtomicStuff();
8
// another comment
9
lastAtomicOperation();
10
}
11
someNonAtomicCode();
12
[...]
Darf jetzt jeder für sich entscheiden was übersichtlicher ist aber meine
Meinung dazu steht fest...
es fehlt wie gesagt nur die Atomic-lib. Hoffentlich finde ich noch eine.
Danke
Gruß Leo
Es geht einfach nur darum __disable_irq(); und __enable_irq(); um einen
Code-Block zu packen? Das ist doch supereasy. In C++ geht es elegant mit
lambdas:
1
template<typenameF>inlinevoiddoAtomic(Ff){
2
__disable_irq();
3
f();
4
__enable_irq();
5
}
6
7
intmain(){
8
doAtomic([](){
9
// ... geschützte Operationen ...
10
});
11
}
Wenn man aus irgendwelchen obskuren Gründen kein C++ verwenden darf muss
man sich mit ekligen C-Makros behelfen, muss hier mal ein C-Frickler
helfen :o)
Dr. Sommer schrieb:> man sich mit ekligen C-Makros behelfen, muss hier mal ein C-Frickler> helfen :o)
In C benutzt man einen Trick.
Die Frage ist, wie kriege ich den Compiler dazu
* den { } Block so zu verpacken, dass
* ich am Anfang des Blocks eine Anweisung einschummeln kann
* ich am ende des Blocks eine Anweisung einschummeln kann
Dazu bietet sich wieder mal eine for-Schleife an.
Ihre 3 Teile benutze ich
* um die Initialisierungsphase dazu zu benutzen, eine Anweisung vor den
Block zu platzieren
* die Laufbedingung dazu, damit der Block nur 1 mal ausgeführt wird
* den Inkrementteil, um damit die abschliessende Anweisung an das
Blockende zu setzen und gleichzeitig die Laufbedingung ungültig zu
machen.
Wie würde sich das in Langform schreiben?
Ich würde zb schreiben
was jetzt noch fehlt, sind diverse Argumente an das Makro mit denen man
das Verhalten steuern kann. Ob man die braucht ist eine andere Frage,
zur Not kann man ja auch 'verschiedene' ATOMIC_BLOCK Makros machen.
(Ja ich weiß, der Name der Hilfsvariablen __cont ist nicht ganz koscher.
Ich rechne allerdings so ein Makro schon mehr der Implementierung zu.
Von daher nehme ich mir das Recht raus, derartige Namen verwenden zu
dürfen.)
Dr. Sommer schrieb:> Karl Heinz Buchegger schrieb:>> In C benutzt man einen Trick.> Das ist ja mal wieder maximal bösartig... ;-)
:-)
Ich habs mir von der avr-libc abgeschaut.
Ich hab mich nämlich auch gefragt, wie kriegen die das hin.
Wobei die noch einen zusätzlichen Trick verwenden, der aber nicht
portabel ist. Die benutzen __cleanup, welches als eine Art Destruktor
fungiert.
Aber so müsste es auch gehen, so lange sich keiner aus der for-Schleife
raus-breaked. Womit dann auch der 'schwache' Punkt dieser Lösung
angesprochen wäre.
ich steig ja mit den GCC-Compilern noch nicht so recht durch, aber ist
GCC nicht gleich GCC? also Dinge die dieses cleanup-attribut die beim
AVR-GCC offenbar funktionieren, sollten doch auch beim ARM-GCC
funktionieren oder nicht?
wenn nicht, kann mir jemand sagen wo ich ein entsprechendes Manual
finde, wo solche tricks drin stehen? sowohl für AVR als auch für den
ARM.
Ich habe bisher nur diese online-Doku
http://gcc.gnu.org/onlinedocs/
gefunden und die unterscheidet nicht zwischen arm und avr...
passender Link zum Thema:
http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
cleanup müsste dann beim STM32 genauso funktionieren. Auch wenn nicht
ganz klar wird was der cleanup-function als Parameter übergeben wird,
nehme ich doch an, dass es ein Pointer auf entsprechende Variable sein
wird.
wäre ja sehr angenehm wenn man den AVR-Code da portieren könnte.
Danke euch
Gruß Leo
Leo B. schrieb:> ich steig ja mit den GCC-Compilern noch nicht so recht durch, aber ist> GCC nicht gleich GCC? also Dinge die dieses cleanup-attribut die beim> AVR-GCC offenbar funktionieren, sollten doch auch beim ARM-GCC> funktionieren oder nicht?
Für diese Attribute: Ja.
Wäre noch anzumerken, dass man diverse atomare Operationen im RAM auf
ARMs ab ARMv6 auch ohne Abschaltung der Interrupts erledigen kann, indem
man LDREX/STREX bzw. die entsprechenden Funktionen vom CMSIS verwendet.
Da dabei eine Datenabhängigkeit enthalten ist, gibts auch keine Probleme
mit der Reihenfolge und Reordering.
Wenn man Interrupts abschaltet muss man immer drauf achten, dass der
Compiler keine komplexe Operation vom Code davor zwischen rein mogelt.
Das kann einem bei LDREX/STREX zwar auch passieren, aber da spielt es
kaum eine Rolle.
die Implementierung des ATOMIC_BLOCK im der avrlibc ist schon eine große
Sache. Speziell weil man auch heraus break-en und return-en kann.
Um nicht immer glabel die Interrupts abzuschalten, modifiziere ich beim
STM32 die BASEPRIO nach Bedarf. Dann werden nur gewisse Interrupts
ausgeblendet.
HTH, Adib.
--
In einem STM32-Programm für einen Versuchsaufbau habe ich auch ein paar
Codezeilen, die nicht durch einen Interrupt unterbrochen werden dürfen.
Das ATOMIC_BLOCK - Konstrukt vom AVR kenne ich auch und finde es
nützlich.
Da sich die Lösung von Karl Heinz bei mir mit GCC nicht kompilieren
lässt, habe ich etwas gebastelt. Mit den Informationen von
http://stm32f4-discovery.com/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
komme ich auf folgende Lösung, welche die Funtionalität von
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) für den AVR nachbildet:
Das ist zwar ein alter Thread aber das Update lässt mir keine Wahl:
Ihr baut die tollsten Konstrukte aber das ganze Konzept hat einen
eklatanten systematischen Fehler:
Man darf auf keinen Fall am Ende eines atomaren Blocks den Interrupt
unkontrolliert wieder aktivieren! Sonst passiert es, dass in einem
Programmteil, in dem der Interrupt explizit ausgeschaltet sein muss,
durch Aufruf einer Funktion mit dem Konstrukt ungewollte Interrupts
aktiviert werden!
Die korrekte Vorgehensweise ist:
1. Zustand der Interrupts in einer Variablen sichern
2. Interrupt deaktivieren (einen oder alle…)
3. Atomarer Block…
4. Ursprünglichen Zustand der Interrupts wieder herstellen!
Man kann ja noch argumentieren, dass man als einsamer Entwickler weiß,
wann die Interrupts an oder aus sind. Aber bei einem System mit vielen
Entwicklern und Betriebszuständen (Bootloader, Calibrationmode,
Errormode usw…) überblickt keiner mehr, wann wo Interrupts an/aus sind
und dass dann bestimmte Funktionen (oder Unterfunktionen von
Unterfunktionen….) nicht genutzt werden dürfen, da sie von sich aus
Interrupts aktivieren…
Für IAR gibt es die folgende Intrinsic Funktionen auch für ARM
1. __istate_t interruptstate = __get_interrupt_state();
2. __disable_interrupt();
3. // Atomarer Block…
4. __set_interrupt_state(interruptstate);
Im Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors
empfehlen sie die Verwendung von __get_PRIMASK() und __set_PRIMASK().
Eine Info dazu findet sich in
https://devzone.nordicsemi.com/question/47493/disable-interrupts-and-enable-interrupts-if-they-where-enabled/
Was mich wundert: ist von Euch noch niemand über dieses Problem
gestolpert?
Ich hatte diesen Fehler schon mehrfach entdeckt und halte ihn für sehr
gefährlich.
@ Marc (Gast)
>Man darf auf keinen Fall am Ende eines atomaren Blocks den Interrupt>unkontrolliert wieder aktivieren!
Doch, wenn man sicher ist, daß er vorher aktiv war ;-)
Die atomic.h des avr gcc bietet dafür 2 Möglichkeiten
1
22.26.2.2 #define ATOMIC_FORCEON
2
This is a possible parameter for ATOMIC_BLOCK. When used, it will cause the
3
ATOMIC_BLOCK to force the state of the SREG register on exit, enabling the Global
4
Interrupt Status flag bit. This saves on flash space as the previous value of the SREG
5
register does not need to be saved at the start of the block.
6
Care should be taken that ATOMIC_FORCEON is only used when it is known that
7
interrupts are enabled before the block’s execution or when the side effects of enabling
8
global interrupts at the block’s completion are known and understood.
9
22.26.2.3 #define ATOMIC_RESTORESTATE
10
This is a possible parameter for ATOMIC_BLOCK. When used, it will cause the
11
ATOMIC_BLOCK to restore the previous state of the SREG register, saved before
12
the Global Interrupt Status flag bit was disabled. The net effect of this is to make
13
Generated on Wed Jan 6 12:08:48 2010 for avr-libc by Doxygen
14
22.27 <util/crc16.h>: CRC Computations 292
15
the ATOMIC_BLOCK’s contents guaranteed atomic, without changing the state of the
16
Global Interrupt Status flag when execution of the block completes.
}// bei der Schließenden Klammer erfolgt das entsperren
26
}
Diese Variante mit extra Klasse hat den Vorteil, dass das entsperren
auch funktioniert wenn man Exceptions verwendet (RAII-Style). Das Lambda
braucht man dann nicht mehr unbedingt, wie in test2 zu sehen ist.
Falk B. schrieb:> @ Marc (Gast)> Doch, wenn man sicher ist, daß er vorher aktiv war ;-)
Siehe meinen 3. Absatz...
> Die atomic.h des avr gcc bietet dafür 2 Möglichkeiten
Wir sind hier im Thread: atomic-lib für ***stm32***
Gruß
Marc
Dr. Sommer schrieb:> Marc schrieb:>> Was mich wundert: ist von Euch noch niemand über dieses Problem>> gestolpert?> Doch, seit 2013 schon... Man könnte es so machen:> class IRQLocker {
}
Jetzt bin ich beruhigt, dass ich nicht der Einzige bin.
Und eines muss ich Ihnen Herr Dr. Sommer jetzt offenbaren:
Herr Dr. Sommer:
ich liebe Sie für ihre guten
Tipps & Informationen zu C++
auf embedded systemen.
Nachtrag: zum Thema allgemein gab es schon einen Thread mit C
Beispielcode:
Beitrag "Interrupts ein-/ausschalten beim ARM cortex-M3"
Marc schrieb:> Ihr baut die tollsten Konstrukte aber das ganze Konzept hat einen> eklatanten systematischen Fehler:> Man darf auf keinen Fall am Ende eines atomaren Blocks den Interrupt> unkontrolliert wieder aktivieren!
Doch, wird berücksichtigt.
A. K. hat es oben zwar schon erwähnt, aber ich finde die Idee wichtig
und schreibe es deswegen nochmal:
Es müssen nicht zwingend die Interrupts (partiell) ein-/ausgeschaltet
werden! LDREX und das Pendant STREX gibt es als intrinsics (__LDREX bzw
__ldrex), womit man super toll selbst atomare Funktionen bauen kann.
Oft läuft die kritische Sektion doch nur auf das Verändern von 1, 2
Variablen (Zähler, Indizes, etc) Variablen hinaus, da kann man sich
genau so gut so etwas in die Richtung schreiben (ungetestet, ob das mit
dem void Zeiger so legitim ist müsste ich nachgucken)
status=__strex(++count,(uint32_t*)(var));// status != 0, if no exclusive access was guaranteed
9
}
10
while(status!=0);// 0 = success
11
__dmb(10);// recommended by arm -> make sure every memory access is done
12
returnATOMIC_SUCCESS;
13
}
Der Vorteil ist, dass man im Fehlerfall auch returnen könnte, dann weiß
man halt, dass die Variable nicht gesetzt wurde oder wie oben
blockieren, bis es funktioniert.
Klar, für den Zugriff auf exklusive Ressourcen muss man anders vorgehen,
aber da hilft auch ein atomic block von oben nicht (man könnte aber
ATOMIC_IncrementVar nehmen, um einen Mutex/Semaphore zu bauen).
Marc schrieb:> Was mich wundert: ist von Euch noch niemand über dieses Problem> gestolpert?
Ähemm..nö.
Bei mir läuft der normale Kram, also main und alles, was darin
aufgerufen wird, im Usermodus mit grundsätzlich allen Interrupts offen
und erlaubt. Und mir ist es noch nie untergekommen, daß ich mal das
Bedürfnis hatte, die Interrupts zeitweise abzuschalten. Eher denke ich
an andere Synchronisier-Mechanismen als an exclusiven Zugriff zweier
oder mehrerer Programmteile auf die selbe Ressource.
Wenn man schon irgendwas exclusiv machen will, dann eher mit nem SVC.
Dessen Inhalt läuft dann im Supervisor-Modus und den kann man von
vornherein so hoch ansetzen, daß er ungestört läuft. Ist beim Keil recht
easy zu machen, aber beim GCC braucht es wieder mal nen
Assembler-Wrapper dafür.
W.S.
Jan K. schrieb:> Es müssen nicht zwingend die Interrupts (partiell) ein-/ausgeschaltet> werden! LDREX und das Pendant STREX gibt es als intrinsics (__LDREX bzw> __ldrex), womit man super toll selbst atomare Funktionen bauen kann.> ATOMIC_IncrementVar(void * var)
Du hast recht, man sollte vor dem Design erst mal alle
Lösungsmöglichkeiten abwägen. Ich setze auch LDREX / STREX ein, je nach
Problemstellung.
Die Idee deiner Funktion ist gut, ich bin allerdings kein Freund von
Parameterübergabe mittels void*
Der Compiler wirft keine Warnungen und mein schlampiger Kollege (oder
ich nach einer langen Nacht…) bauen einen Aufruf mit einem 8 oder 16 Bit
Parameter…
Und das Unheil nimmt seinen Lauf…
Marc schrieb:> Die Idee deiner Funktion ist gut, ich bin allerdings kein Freund von> Parameterübergabe mittels void*> Der Compiler wirft keine Warnungen und mein schlampiger Kollege (oder> ich nach einer langen Nacht…) bauen einen Aufruf mit einem 8 oder 16 Bit> Parameter…>> Und das Unheil nimmt seinen Lauf…
Stimmt stimmt, wollte das zuerst generisch aufziehen. Vielleicht sollte
man das ganze noch kapseln oder halt für jeden Datentyp eine Funktion
erstellen. Und dann den void Pointer natürlich weg machen ;-)
Vermutlich wäre eine Lösung für jeden Datentyp am sichersten.
(Noch schöner in C++ mit Templates...)
Und statt einem counter++ einen zusätzlichen Parameter, dann kann man
fexibel addieren/subtrahieren. Beispielsweise so (habe die
Variablennamen noch etwas angepasst):
1
boolATOMIC_int32Add(int32_t*var,int32_toff)
2
{
3
int32_ttempVal;
4
int32_tlockedOut;
5
do
6
{
7
tempVal=(int32_t)__LDREX((unsignedlong*)var);
8
tempVal+=off;
9
lockedOut=__STREX(tempVal,(unsignedlong*)var);
10
}
11
while(lockedOut);// lockedOut: 0==Success, 1==instruction is locked out
12
__DMB(10);// recommended by arm -> make sure every memory access is done
13
returntrue;
14
}
Den Rückgabewert könnte man noch verwerfen... Die Frage ist, ob man noch
ein Abbruchkriterium einbaut wenn die Schleife X mal durchlaufen wurde.
Dazu eine Frage an die Leute, die dieses Verfahren schon mehrfach
genutzt haben:
In welchen Fällen kam es zu Problemen (z.B. durch DMA Zugriffe in den
Speicher), die dazu führten, dass __STREX immer ein "locked out"
meldete?
Da ist ein __disable_interrupt().... deterministischer. Was ist eure
Erfahrung?
Und wenn ldrex/strex nicht weiterhelfen, dann kann man bei den Cortex M
dennoch um die Abschaltung aller Interrupts herum kommen, indem man die
Priorität nur so weit verändert, dass nur die relevanten Interrupts
gesperrt werden. Dank des recht exakt dafür implementierten Registers
BASEPRI_MAX geht das ausgesprochen einfach.
Marc schrieb:> Da ist ein __disable_interrupt().... deterministischer.
Sehr deterministisch und im Fall von DMA auch sehr sinnlos. Gegen DMA
hilft die Abschaltung von Interrupts überhaupt nicht. Bei mehreren Cores
sieht es ähnlich aus.
Wenn man die Sorge hat, in diese Falle laufen zu können, dann kann man
solche Spinlocks auch ent-determinisieren, indem das Verhalten (Timing)
abhängig von einem Durchlaufzähler variiert.
A. K. schrieb:>> Da ist ein __disable_interrupt().... deterministischer.> Sehr deterministisch und im Fall von DMA auch sehr sinnlos.
Ich denke dass ein Disabling .... Enabling deterministischer sein könnte
als eine Schleife (ich sage nicht, dass es immer so ist...)
Szenario:
An verschiedenen Stellen im Code (auch in Interrupts) müssen mehrere
Variablen modifiziert werden. Beim Disabling kann ich genau bestimmen
wie lange die Interrupts maximal gesperrt sind.
Bei LDREX / STREX geht das nur mit einer zusätzlichen Abbruchbedingung.
Aber die Ausführungszeit schwankt eben (1 ... n Schleifendurchläufe) und
ich muss danach prüfen ob es zum Abbruch kam
Vermutlich bin ich zu pessimistisch, bisher hatte ich keinen kritischen
Spinlock.Daher eben meine Frage oben.
Anders formuliert: hatte jemand bei LDRES / STRED größere Probleme und
was war die Ursache?
Marc schrieb:> Ich denke dass ein Disabling .... Enabling deterministischer sein könnte> als eine Schleife (ich sage nicht, dass es immer so ist...)
Interrupts abzuschalten macht das Zeitverhalten in dieser Phase
deterministisch. Andererseits vergrössert sich der variable Anteil der
Latenz von Interrupts, die Latenz wird also weniger deterministisch.
Letztlich eine Abwägung, wo man den Schwerpunkt setzt.
Bei Forderung von taktgenauem Zeitverhalten im Hauptprogramm bei darin
enthaltenen Zugriffen auf solche Variablen ist die Abschaltung natürlich
obligatorisch. Das ist aber ein eher ungewöhnliches Szenario, zumal in
komplexen Controller-Anwendungen mit etlichen Interrupts und ggf. DMA.
> An verschiedenen Stellen im Code (auch in Interrupts) müssen mehrere> Variablen modifiziert werden.
So lange alle zu einer Variablen gehörenden ISRs die gleiche preemption
priority haben, ist in ISRs kein ldrex/strex-Verfahren erforderlich.
Diese Interrupts sind davon also nicht betroffen.
NB: Wer sich bei dem Cortex M fragt, wieso die Priorisierung des NVIC
nicht durchweg preemptive ist, der hat hier eine Antwort.
> Bei LDREX / STREX geht das nur mit einer zusätzlichen Abbruchbedingung.> Aber die Ausführungszeit schwankt eben (1 ... n Schleifendurchläufe) und> ich muss danach prüfen ob es zum Abbruch kam
Kannst du ein im relevanten Rahmen zeitkritisches Szenario entwerfen, in
dem ein solches pro Durchlauf nur wenige Takte dauernde Spinlock mehr
als einige wenige Versuche benötigt, am Ende gar in ein Deadlock
reinlaufen kann?
Ich kann mir dafür nur Szenarien vorstellen, in denen das Programm fast
die gesamte Zeit in Interrupts verbringt.
Danke A. K. für deine Infos.
>Ich kann mir dafür nur Szenarien vorstellen, in denen das Programm fast
die gesamte Zeit in Interrupts verbringt.
Meine Bedenken gingen eher Richtung DMA.
Szenario: Ein Stück Code wird in einem hoch priorisierten Interrupt
ausgeführt. Die ISR soll möglichst kurz sein (eben um die Latenz für
andere ISRs... nicht unnötig zu erhöhen). Parallel läuft aber ein DMA
Schreibzyklus, der dazu führt, dass LDREX/STREX x Schleifen durchläuft.
Mmmm, ich glaube ich versuche mich mal an Testcode. Kann für's
Verständnis nie schaden...
Marc schrieb:> Parallel läuft aber ein DMA> Schreibzyklus, der dazu führt, dass LDREX/STREX x Schleifen durchläuft.
Die ARMv7-M Referenz erweckt nicht den Eindruck, dass diese Befehle
geeignet sind, sich gegen Zugriffe eines einfachen DMA Controllers für
Peripherie-Transfers auf die gleiche Adresse abzusichern.
Ich gehe davon aus, dass solche DMA Zugriffe auf ausreichend
verschiedene Adressen dem entsprechenden Monitor nicht ins Gehege
kommen. Andernfalls wäre intensives DMA mit LDREX/STREX prinzipiell
unverträglich.
Anders sieht es bei konkurrierenden Zugriffen mehrerer Cores aus. Da
allerdings kommt man mit der Abschaltung von Interrupts nicht weiter und
muss mit solchen Verfahren arbeiten. Da wird es dann auch etwas
komplizierter.
Jan K. schrieb:> Noch ein edit: der Cortex M3 scheint nicht einzelne Speicherbereiche zu> taggen, sondern guckt nur, ob einfach, ob der monitor im exclusive> Zustand ist und verweigert ggf das exklusive Schreiben
Zumindest ist das entsprechend der Fussnote in ARMv7-M zulässig: "The
IMPLEMENTATION DEFINED options for the local monitor are consistent with
the local monitor being constructed so that it does not hold any
physical address, but instead treats any access as matching the address
of the previous LDREX. In such an implementation, the Exclusives
reservation granule defined in Tagging and the size of the tagged memory
block on page A3-75 is the entire memory address range."
Über das konkrete Verhalten vom CM3 Core finde ich grad keine Details.
Beim CM4 und CM7 steht das aber ausdrücklich so drin, also wird es beim
CM3 wohl auch so sein.
In diesem Fall hätte man dann aber doch ein DMA Problem, sofern der DMA
Controller in diesem Sinn als "observer" gilt und beim DMA Zugriff
folglich unabhängig von der Adresse dies zutrifft: "It is UNPREDICTABLE
whether a store to a tagged physical address causes a tag in the local
monitor to be cleared if that store is by an observer other than the one
that caused the physical address to be tagged."
Bei dieser Kaffeesatzleserei bleibt mir vorerst die Erkenntnis, dass
sich daraus keine zweifelsfreie Aussage über DMA ablesen lässt. Und
eigentlich nur die heuristische Logik verbleibt, dass ein DMA Controller
als "observer" arg wenig Sinn ergibt.
Ok, hier wird es klarer:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka16180.html
"For a design containing a single processor and some other agent such as
a DMA controller, it is acceptable to tie EXRESPx = 0 if software which
makes use of semaphore operations can ensure that the other agent is not
accessing that address range while the semaphore operation is in
progress."
M.a.W: Bei Controllern mit einheitlich von DMA ansprechbarem RAM wird
der Zustand des Monitors von DMA wie erwartet nicht beeinflusst.
Bei Controllern mit verschiedenen nur teilweise von DMA ansprechbaren
RAM-Blöcken oder -Adressräumen könnte angebracht sein, diese Variablen
im non-DMA-RAM zu platzieren. Für den Fall, dass der Controller sich
unwahrscheinlicherweise die Mühe macht, die Adressräume beim STREX
auseinander zu halten. Da müsste man sonst bei ST&Co nachschlagen.
Jan K. schrieb:> steht, dass es sein kann, dass der Compiler store Befehle zwischen> ldrex/strex packen kann.
Normale Store-Befehle auf andere Variablen sind im hier betrachteten
Rahmen (single core, local monitor) sicherlich kein Problem. Der Monitor
wird die ebenso wenig mitkriegen wie die DMA Zugriffe.
Ich hab das so verstanden, dass jeder store Befehl den monitor
resetten KANN und dieser eben nicht nur auf eine Adresse guckt:
"The local monitor's Exclusives Reservation Granule is 4GB, meaning that
no address information is stored in the local monitor."
Somit würde selbst bei einem store auf eine andere Speicheradresse nie
ein erfolgreiches exklusives Schreiben resultieren.
"When a processor writes using any instruction other than a
Store-Exclusive:
• if the write is to a physical address that is not covered by its local
monitor the write does not affect
the state of the local monitor
• if the write is to a physical address that is covered by its local
monitor it is IMPLEMENTATION DEFINED
whether the write affects the state of the local monitor.
If the local monitor is in its Exclusive Access state and a processor
performs a Store-Exclusive to any
address other than the last one from which it has performed a
Load-Exclusive, it is IMPLEMENTATION
DEFINED whether the store succeeds, but in all cases the local monitor
is reset to its Open Access state. In
ARMv7-M, the store must be treated as a software programming error.
"
Und "The compiler does not guarantee that it will preserve the state of
the exclusive monitor. It may generate load and store instructions
between the LDREX instruction generated for the __ldrex intrinsic and
the STREX instruction generated for the __strex intrinsic. Because
memory accesses can clear the exclusive monitor, code using the __ldrex
and __strex intrinsics can have unexpected behavior. Where LDREX and
STREX instructions are needed, ARM recommends using embedded assembly."
Bin ich schief gewickelt? =)
Jan K. schrieb:> Ich hab das so verstanden, dass jeder store Befehl den monitor> resetten KANN und dieser eben nicht nur auf eine Adresse guckt:
Ist implementation defined. Da das hier aber ausdrücklich als absolute
Minimalimplementierung ausgeführt ist, um LDREX/STREX in dieser Rolle
überhaupt nutzen zu können, wird es darauf hinauslaufen, dass der
Monitor von anderen Stores als STREX überhaupt nichts mitbekommt. Die
Alternative wäre aufwendiger und hätte keinerlei Mehrwert.
das sollte sich den alten Werk merken, einen neuen setzen (den du mit
"prio" übergeben musst) und anschließend den alten wiederherstellen.
Getestet habe ich es ehrlich gesagt nicht, müsste aber funktionieren.
Wenn man anfangs BASEPRI_MAX statt BASEPRI setzt, dann vermeidet man das
Risiko, die Prio versehentlich zu reduzieren. Das könnte sonst
passieren, wenn man diesen Code in ISRs oder verschachtelt verwendet.
Was du anpassen musst:
sei(): IRQs global zulassen (I-Flag := 1)
cli(): IRQs global deaktivieren (I-Flag := 0)
SREG: Bei AVR ein 8-Bit SFR, über welches das I-Flag gelesen und
geschrieben werden kann; bei STM geht das wohl über Support
Funktionen wie __set_BASEPRI und __get_BASEPRI
uint8_t: Bei STM vermutlich ein 32-Bit Wert, also der Return-Typ von
__get_BASEPRI
#include <avr/interrupt.h>: Wird nicht mehr gebraucht.
#include <avr/io.h>: dito
#include <stdint.h>: Neues Include falls uint32_t o.ä. verwendet werden
sollen.
Und last not least Copyright beachten, d.h. das entsprechende Intro
kopieren, und wenn du willst dich unter Dean Camera verewigen :-)
Michael K. schrieb:> Ich habe das mal wiederverwertbar in einer Header-Datei> zusammengefasst,> vielleicht kann es ja jemand brauchen.
Mein Compiler motzt hier:
Schweregrad Code Beschreibung Projekt Datei Zeile
Unterdrückungszustand
Fehler second operand to the conditional operator is of type 'void',
but the third operand is neither a throw-expression nor of type 'void'
Debounce C:\Users\daniel\Desktop\Debounce\Debounce\Debounce\cm_atomic.h
80
Er schein __disable_irq() und __enable_irq() nicht zu kennen.
Könnt ihr mir weiter helfen?
Nett :) Wenn der Compiler __disable_irq() nicht kennen würde, gäbe es
den Fehler nicht! Unbekannte Funktionen liefern int und nicht void. A.K.
hat das Problem schon ca. 5 Beiträge weiter oben erklärt:
Beitrag "Re: atomic-lib für stm32"
technikus schrieb:> Könnt ihr mir weiter helfen?eagle user schrieb:> A.K. hat das Problem schon ca. 5 Beiträge weiter oben erklärt:> Beitrag "Re: atomic-lib für stm32"
Stimmt, da fehlt was. Mach mal
Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
Groß- und Kleinschreibung verwenden
Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang