Moin,
wie kann man beim Cortex-M3/4 (STM32) in einem Schritt die Interrupts
aktivieren und den Sleep-Modus (WFI) betreten, ohne dass Interrupts
dazwischen funken?
Das ist relevant bei solchen Konstrukten, wenn man den Sleep-Mode zur
Reduktion der Leistungsaufnahme einsetzen möchte:
1 | volatile int counter = 0;
|
2 |
|
3 | void TIM2_IRQHandler (void) {
|
4 | ++counter;
|
5 | }
|
6 |
|
7 | int main () {
|
8 | HAL_TIM_Base_Start_IT(&htim2);
|
9 | ...
|
10 | while (1) {
|
11 | __disable_irq ();
|
12 | int c = counter;
|
13 | counter = 0;
|
14 | if (c == 0) {
|
15 | __enable_irq ();
|
16 | // Race Condition hier!!
|
17 | __WFI ();
|
18 | } else
|
19 | __enable_irq ();
|
20 |
|
21 | if (c != 0) {
|
22 | doSomething (c);
|
23 | }
|
24 | }
|
25 | }
|
Der Interrupt signalisiert über die Variable, dass etwas zu tun ist. Die
Funktion doSomething kann nicht direkt im Interrupt ausgeführt werden,
weil sie langsam ist, Delays nutzt, Teil einer proprietären Bibliothek
ist welche nicht geändert werden kann o.ä. Die Interruptsperre ist aber
nötig um counter konsistent modifizieren zu können.
Die Tücke ist: Wenn genau zwischen dem __enable_irq() und dem WFI der
Interrupt kommt und den counter inkrementiert, betritt der Controller
trotzdem den Sleep-Modus und wacht ggf. nie wieder/zu spät wieder auf.
Man muss irgendwie sicherstellen dass zwischen Abfragen des counters und
Betreten des Sleep-Mode kein Interrupt auftreten kann - oder die ISR
muss irgendwie das Betreten des Sleepmode verhindern. Ersetzt man die
Interrupt-Sperre durch Atomics, hat man übrigens das gleiche Problem,
aber eben zwischen Abfrage des Atomic-Werts und WFI.
Gibt es dafür eine smarte Lösung?
Eine Möglichkeit wäre es WFE statt WFI zu nutzen und SEV im
Interrupt-Handler bzw. SEVONPEND. Aber eigentlich ist dieser Mechanismus
für Inter-Core-Kommunikation intendiert, in Multicore-Systemen würde man
damit unnötig andere Kerne wecken.
Das Problem tritt sehr ähnlich auf wenn man Task-Queues für "normale"
Multithreading-Systeme implementieren möchte, dort können Condition
Variables helfen. So etwas bräuchte man hier auch, aber eben ohne
Multithreading.
Mir ist eine relativ komplizierte Lösung mit SVC und SLEEPONEXIT
eingefallen. Kennt jemand eine elegante kompakte Lösung? Wie macht man
das bei Controllern welche kein WFE+SEV oder SLEEPONEXIT haben, wie z.B.
AVR?