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):
1 | void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState) |
2 | {
|
3 | if (NewState != DISABLE) { |
4 | ADCx->CR2 |= (uint32_t)ADC_CR2_ADON; // <-- (mindestens) 3 Instruktionen, überhaupt nicht atomar |
5 | } else { |
6 | ADCx->CR2 &= (uint32_t)(~ADC_CR2_ADON); // dito |
7 | }
|
8 | }
|
> 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 <typename F> inline void doAtomic (F f) { |
2 | __disable_irq (); |
3 | f (); |
4 | __enable_irq (); |
5 | }
|
6 | |
7 | int main () { |
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
1 | for( __disable_irq(), uint8_t cont = 1; cont == 1; __enable_irq(), cont = 0 ) |
2 | {
|
3 | Anweisungen .... |
4 | }
|
Anstelle des 'umständlichen' for, wird der Teil noch in ein Makro verpackt und nähere mich damit dem Ziel
1 | #define ATOMIC_BLOCK() for( __disable_irq(), uint8_t __cont = 1; __cont == 1; __enable_irq(), __cont = 0 )
|
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.)
Karl Heinz Buchegger schrieb: > In C benutzt man einen Trick. Das ist ja mal wieder maximal bösartig... ;-)
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
Bissel was in der Hinsicht gibts im GCC plattformübergreifend schon von Haus aus, z.B. result = __sync_add_and_fetch(&var, 1); für atomare Addition einer Variablen. http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/_005f_005fsync-Builtins.html#_005f_005fsync-Builtins http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/_005f_005fatomic-Builtins.html#_005f_005fatomic-Builtins
:
Bearbeitet durch User
Ja, ist alles "legacy" :-) Wenn man es wirklich protabel halten will, dann mit C++11 und den Memory Models und GCCs libatomic, die m.W. für ARM implementiert ist. http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/_005f_005fatomic-Builtins.html#g_t_005f_005fatomic-Builtins
:
Bearbeitet durch User
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:
1 | #define ATOMIC_BLOCK for( uint32_t __cond = 1, __prim = __get_PRIMASK(); __cond != 0 ? __disable_irq() : 0, __cond != 0; __prim == 0 ? __enable_irq() : 0, __cond = 0 )
|
Damit man es nachvollziehen kann:
1 | #include <stdio.h> |
2 | |
3 | int Funktion1(void) |
4 | {
|
5 | printf("Funktion1\n"); |
6 | return 0; |
7 | }
|
8 | |
9 | void Funktion2(void) |
10 | {
|
11 | printf("Funktion2\n"); |
12 | }
|
13 | |
14 | void Funktion3(void) |
15 | {
|
16 | printf("Funktion3\n"); |
17 | }
|
18 | |
19 | void Funktion4(void) |
20 | {
|
21 | printf("Funktion4\n"); |
22 | }
|
23 | |
24 | int main() |
25 | {
|
26 | for( int __cond = 1, __prim = Funktion1(); __cond != 0 ? Funktion2() : 0, __cond != 0; __prim == 0 ? Funktion4() : 0, __cond = 0 ) { |
27 | Funktion3(); |
28 | }
|
29 | return 0; |
30 | }
|
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. |
Marc schrieb: > Was mich wundert: ist von Euch noch niemand über dieses Problem > gestolpert? Doch, seit 2013 schon... Man könnte es so machen:
1 | class IRQLocker { |
2 | private:
|
3 | uint32_t m; |
4 | public:
|
5 | inline IRQLocker () { m = __get_PRIMASK (); __disable_irq (); } |
6 | inline ~IRQLocker () { if (m == 0) __enable_irq (); } |
7 | };
|
8 | |
9 | template <typename F> |
10 | inline void doAtomic (F f) { |
11 | IRQLocker l; |
12 | f (); |
13 | }
|
14 | |
15 | void test1 () { |
16 | doAtomic ([] () { |
17 | /* tu was atomisches */
|
18 | });
|
19 | }
|
20 | |
21 | void test2 () { |
22 | {
|
23 | IRQLocker l; |
24 | /* tu was atomisches */
|
25 | } // 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.
1 | ... __prim = __get_PRIMASK() ... |
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)
1 | int ATOMIC_IncrementVar(void * var) |
2 | {
|
3 | int32_t count; |
4 | int32_t status; |
5 | do
|
6 | {
|
7 | count = (int32_t) __ldrex( (uint32_t *)(var) ); // load variable address exclusively |
8 | 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 | return ATOMIC_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 | bool ATOMIC_int32Add(int32_t *var, int32_t off) |
2 | {
|
3 | int32_t tempVal; |
4 | int32_t lockedOut; |
5 | do
|
6 | {
|
7 | tempVal = (int32_t) __LDREX((unsigned long *)var); |
8 | tempVal += off; |
9 | lockedOut = __STREX(tempVal,(unsigned long *)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 | return true; |
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
Es gibt hier z.B. einige Infos zu den Exclusiven Monitoren: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/CJAGCFAF.html Und da steht konkret, wie er im M3 und M4 implementiert ist: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka16180.html Und das ist hier das Manual zu der ARMv7-M Architektur, alles ab A3.4 wird interessant. https://www.google.de/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwiKwrae_6fMAhVUGsAKHZpUAGkQFgggMAA&url=https%3A%2F%2Fweb.eecs.umich.edu%2F~prabal%2Fteaching%2Feecs373-f10%2Freadings%2FARMv7-M_ARM.pdf&usg=AFQjCNHNAdnUhWjznYcJGCLNNdzgMGQmgw&sig2=bu77UAoozA51G2cblKD8Bg Guckt man sich das state machine diagram in figure A3-2 an, steht da nur was von Store und StoreExcl instructions, werden diese vom DMA benutzt? Wenn nein, sollte der DMA kein Problem machen. Edit: Oder meinst du echt einen Zugriff auf gleiche Adressen? Ich dachte eher du meintest eine Endlosschleife, die auf Grund von false negatives des LDREX Befehls entstehen. 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
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
Noch eine andere Sache: In http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0375g/chr1359124997286.html steht, dass es sein kann, dass der Compiler store Befehle zwischen ldrex/strex packen kann. Das wäre eine blöde Sache, weil das exklusive Schreiben dann nie funktionieren würde. Ich vermute, dass die Quintessenz daraus ist, die Funktion vermutlich doch wieder als Assembler umzusetzen... Darüberhinaus ist der intrinsic Befehl als deprecated markiert, weiß da jmd was drüber? Die Passage tritt erst bei armcc v5.06 auf, vorher nicht: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0376d/CJAECCJJ.html (v5.05)
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
Leo B. schrieb: > 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. Philipp S. schrieb: > #define ATOMIC_BLOCK for( uint32_t __cond = 1, __prim = __get_PRIMASK(); > __cond != 0 ? __disable_irq() : 0, __cond != 0; __prim == 0 ? > __enable_irq() : 0, __cond = 0 ) Der Vollständigkeit halber:
1 | #define ATOMIC_BLOCK(type) for( uint32_t __cond = 1, __prim = __get_PRIMASK(); \
|
2 | __cond != 0 ? __disable_irq() : 0, __cond != 0; \
|
3 | (type & __prim) == 0 ? __enable_irq() : 0, __cond = 0 )
|
4 | #define ATOMIC_FORCEON 0
|
5 | #define ATOMIC_RESTORESTATE 1
|
sollte sich Verhalten wie das AVR-Pendant. Alternativ hat sich schonmal jemand die Mühe gemacht die ganze atomic.h zu portieren: https://github.com/PaulStoffregen/cores/blob/master/teensy3/util/atomic.h
:
Bearbeitet durch User
1 | #define ATOMIC_BLOCK(type) for( uint32_t __cond = 1, __prim = __get_PRIMASK(); \
|
2 | __cond != 0 ? __disable_irq() : 0, __cond != 0; \
|
3 | (type && __prim) == 0 ? __enable_irq() : 0, __cond = 0 )
|
4 | #define ATOMIC_FORCEON 0
|
5 | #define ATOMIC_RESTORESTATE 1
|
bietet dem Compiler noch die Möglichkeit zu optimieren. Man beachte "(type && __prim)" statt "(type & __prim)".
:
Bearbeitet durch User
Hallo Leute, das sieht allles richtig gut aus. Wie muss man das schreiben, um die BASEPRIO temporär zu setzen? Danke und Grüße, Adib.
Adib schrieb: > Wie muss man das schreiben, um die BASEPRIO temporär zu setzen? Falls du das im Zusammenhang mit den ATOMIC_BLOCKs meinst, versuch mal
1 | #define ATOMIC_BLOCK(prio) for( uint32_t __cond = 1, __prim = __get_BASEPRI(); \
|
2 | __cond != 0 ? __set_BASEPRI(prio) : 0, __cond != 0; \
|
3 | __set_BASEPRI(__prim), __cond = 0 )
|
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.
:
Bearbeitet durch User
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.
1 | #define ATOMIC_BLOCK(prio) for( uint32_t __cond = 1, __prio = __get_BASEPRI(); \
|
2 | __cond != 0 ? __set_BASEPRI_MAX(prio) : 0, __cond != 0; \
|
3 | __set_BASEPRI(__prio), __cond = 0 )
|
:
Bearbeitet durch User
Hallo Leute, mein Keil Compiler kommt offensichtlich nicht mit dem Konstrukt hier klar:
1 | __cond != 0 ? __set_BASEPRI_MAX(prio) : 0 |
Es gibt eine komische Fehlermeldung:
1 | TOMIC .... |
Also das "A" wird vom "TOMIC" getrennt ??? Es hat wohl was mit der Anweisung ohne return __set_BASEPRI_MAX zu tun. Danke und Grüße, Adib. --
Yep, dieser Ausdruck ist kein korrektes C, weil links vom : vom Typ void. Probier mal: __cond != 0 ? (__set_BASEPRI_MAX(prio), 0) : 0
Ich habe das mal wiederverwertbar in einer Header-Datei zusammengefasst, vielleicht kann es ja jemand brauchen.
Leo B. schrieb: > 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, Na du HAST doch bereits was gefunden, nämlich util/atomic.h der AVR-LibC: Es ist nämlich lediglich ein Header und KEINE Bibliothek. http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/util/atomic.h?root=avr-libc&view=markup Der Header ist dann für deine Maschine anzupassen, insbesondere die weiter unten im Headetr verwendeten
1 | /* Internal helper functions. */
|
2 | static __inline__ uint8_t __iSeiRetVal(void) |
3 | {
|
4 | sei(); |
5 | return 1; |
6 | }
|
7 | |
8 | static __inline__ uint8_t __iCliRetVal(void) |
9 | {
|
10 | cli(); |
11 | return 1; |
12 | }
|
13 | |
14 | static __inline__ void __iSeiParam(const uint8_t *__s) |
15 | {
|
16 | sei(); |
17 | __asm__ volatile ("" ::: "memory"); |
18 | (void)__s; |
19 | }
|
20 | |
21 | static __inline__ void __iCliParam(const uint8_t *__s) |
22 | {
|
23 | cli(); |
24 | __asm__ volatile ("" ::: "memory"); |
25 | (void)__s; |
26 | }
|
27 | |
28 | static __inline__ void __iRestore(const uint8_t *__s) |
29 | {
|
30 | SREG = *__s; |
31 | __asm__ volatile ("" ::: "memory"); |
32 | }
|
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:
1 | #define ATOMIC_BLOCK(type) for( uint32_t __cond = 1, __prim = __get_PRIMASK(); \
|
2 | __cond != 0 ? __disable_irq() : 0, __cond != 0; \
|
3 | (type && __prim) == 0 ? __enable_irq() : 0, __cond = 0 )
|
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
1 | #define ATOMIC_BLOCK(type) for( uint32_t __cond = 1, __prim = __get_PRIMASK(); \
|
2 | __cond != 0 ? (__disable_irq(), 0) : 0, __cond != 0; \
|
3 | (type && __prim) == 0 ? __enable_irq() : 0, __cond = 0 )
|
draus. Habe grad keinen Compiler zur Hand, ist also ungetestet. Vermutlich braucht es das bei __enable_irq() dann auch noch, also:
1 | (__enable_irq(), 0) |
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.