Forum: Mikrocontroller und Digitale Elektronik STM32 Interrupt-Prioritäten - mal wieder


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich versuche gerade, auf meinem STM32F446 den PendSV-Handler vom 
Systick-Handler unterbrechbar zu machen. So sieht mein Quelltext bislang 
aus:

1
#define SPARE0_PIN GPIOC, GPIO_Pin_4
2
#define SPARE1_PIN GPIOB, GPIO_Pin_1
3
4
int main(void)
5
{
6
    SystemInit();
7
    SysTick_Config(SystemCoreClock/500000);
8
9
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
10
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
11
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
12
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
13
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
14
15
    io_setOutput(SPARE0_PIN);
16
    io_setOutput(SPARE1_PIN);
17
18
19
    // Test: Handler konfigurieren
20
    NVIC_InitTypeDef NVIC_InitStruct;
21
22
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
23
24
    // SysTick_Config konfiguriert Prioritaet neu, deshalb erst hier:
25
    NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
26
27
    /* Add IRQ vector to NVIC */
28
    NVIC_InitStruct.NVIC_IRQChannel = PendSV_IRQn;
29
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x20;
30
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
31
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
32
    NVIC_Init(&NVIC_InitStruct);
33
34
    while(1)
35
    {
36
        
37
    }
38
}
39
40
41
void SysTick_Handler(void)
42
{
43
    static uint32_t tmpctr = 0;
44
45
    io_setBit(SPARE0_PIN);
46
47
48
    // PendSV-Handler triggern
49
    tmpctr++;
50
    if( tmpctr == 100) {
51
        SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
52
        tmpctr = 0;
53
    }
54
55
    io_clearBit(SPARE0_PIN);
56
}
57
58
59
void PendSV_Handler(void)
60
{
61
    io_setBit(SPARE1_PIN);
62
    delay_us(10);          // Lastsimulation
63
    io_clearBit(SPARE1_PIN);
64
}

In den PendSV-Handler kommen später einmal etwas längere Berechnungen. 
Zum Testen habe ich sie durch eine Warteschleife ersetzt.

Am Oszilloskop kann ich erkennen, daß der PendSV-Handler aufgerufen 
wird, aber leider auch, daß in dieser Zeit der SysTick-Handler gar 
nichts macht.

Ich hätte erwartet, daß der Systick-Timer, der die höhere 
PreemptionPriority besitzt, den PendSV-Handler jederzeit unterbrechen 
kann.

Wo liegt mein Denkfehler?
W.T.

von Walter T. (nicolas)


Lesenswert?

Merkwürdig... wenn ich mir NVIC->ISER und NVIC->ICER ansehe, wird durch 
die Initialisierung überhaupt nichts geschrieben. Ist die Init-Funktion 
mit negativen Exception Numbers überfordert?

: Bearbeitet durch User
von Christopher J. (christopher_j23)


Lesenswert?

Walter T. schrieb:
> Ich hätte erwartet, daß der Systick-Timer, der die höhere
> PreemptionPriority besitzt, den PendSV-Handler jederzeit unterbrechen
> kann.

Was den NVIC selbst angeht bedeutet ein niedrigerer Priority-Wert eine 
höhere Dringlichkeit, d.h. es ist genau anders herum als man zunächst 
annimmt. D.h. die höchste, vom Benutzer zu vergebende, Dringlichkeit hat 
den Priority-Wert 0. Es gibt meiner Meinung nach absolut keinen Grund 
für die NVIC-Konfiguration irgendeine Hersteller-Library zu verwenden, 
weil der sich zwischen den Herstellern nicht unterscheidet und alles was 
man braucht in den CMSIS-Core Headern steckt. Keine Ahnung was genau STs 
NVIC_Init-Funktion macht. Jedenfalls macht es die Sache unnötig 
kompliziert. Prinzipiell benötigt man eigentlich nur die Funktionen 
NVIC_EnableIRQ und NVIC_SetPriority. Das Priority-Grouping lässt man am 
besten einfach weg, d.h. auf dem Default-Wert ab Reset. Einen ganz 
informativen Blog-Artikel zum Thema findest du hier:
https://embeddedgurus.com/state-space/2014/02/cutting-through-the-confusion-with-arm-cortex-m-interrupt-priorities/

von Walter T. (nicolas)


Lesenswert?

Hallo Christopher,

danke für den Link. Es ist mir schon klar, daß die Prioritäts-Zahl um so 
höher sein muß, je unterbrechbarer die ISR werden soll.

Ich habe mal den Tipp umgesetzt, mich allein auf die ARM-CMSIS ohne 
STM-Herstellercode zu verlassen.

Main sieht jetzt so aus:
1
int main(void)
2
{
3
    SystemInit();
4
    SysTick_Config(SystemCoreClock/500000);
5
    NVIC_DisableIRQ(SysTick_IRQn);
6
7
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
8
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
9
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
10
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
11
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
12
13
    io_setOutput(SPARE0_PIN);
14
    io_setOutput(SPARE1_PIN);
15
16
17
    // Test: Handler konfigurieren
18
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
19
    NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
20
21
    volatile uint32_t priority =  NVIC_GetPriority(PendSV_IRQn);  
22
    printf("priST =%x\n", priority);
23
24
    NVIC_SetPriority(PendSV_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0));
25
    NVIC_EnableIRQ(PendSV_IRQn);
26
    NVIC_EnableIRQ(SysTick_IRQn);
27
28
    priority =  NVIC_GetPriority(PendSV_IRQn);                
29
    printf("priPSV=%x\n", priority);
30
31
    priority =  NVIC_GetPriority(SysTick_IRQn);               
32
    printf("priST =%x\n", priority);
33
34
    while(1)
35
    {
36
        
37
    }
38
}

Das Gute: PendSV wird immer noch korrekt ausgelöst. Das Schlechte: Es 
fängt zwar erst hinter dem Ende von SystickHandler an, läßt sich aber 
von ihm nicht unterbrechen.

Das paßt auch zum Auslesewert "priority". Der ist immer 0.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Wenn ich das Reference Manual richtig verstehe, muß ich, damit ISRs 
andere unterbrechen können, mit unterschiedlichen PriorityGroups 
arbeiten.

NVIC_SetPriority scheint aber in SHP gar nicht zu schreiben.

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


Lesenswert?

Hallo Walter,

sieh Dir mal die Initialisierung der OS Interrupts in FreeRTOS an. Der 
Pending SV muss unter Allen Umständen die niedrigste aller 
Interruptprioritäten haben, damit er nur dann ausgeführt wird, wenn eine 
Task aktiv ist (sonst gibt es keinen sauber definierten Kontext, aus dem 
heraus geswitcht werden kann - der Pending SV ist das Herz des Context 
Switch Mechanismus).

Du kannst Dir von hier:

http://www.springer.com/de/book/9783658148492

unter "Zusatzmaterial" die Beispielapplikationen umsonst und ohne 
Seiteneffekte herunterladen. In Kapitel 3 gibt es dort das 
Beispielprogramm HelloWorld_FreeRTOS, ein Blinky unter FreeRTOS für den 
STM32Fxxx.

Eine genauere Beschreibung darüber, wie sich die beiden Interrupts 
miteinander verhalten, um Context Switches zu realisieren, gibt's in 
Kapitel 3.4.2. des Buches.

Die Priority Groups sind eine Eigenheit von ST. Der ARM Kernel erlaubt 
es, zwischen 8 und 256 Interruptprioritäten zu implementieren, damit 
sind je nach POD zwischen 0 und 5 bits im Prioritätsfeld frei. ST nutzt 
dieses zur Implementation von Subprioritäten. Nein, es muss nicht 
aktiviert werden, um die Interrupts wie gewohnt zu nutzen. Sieh einfach 
im STM port von FreeRTOS nach, wie der Controller initialisiert wird. 
Tipp: Der Kontrollfluss ist nicht immer einfach im Quelltext 
nachvollziehbar. Sieh Dir in der IDE deiner Wahl nach abgeschlossener 
Initialisierung die Registerwerte von SCB und NVIC im SFR Fenster an, 
damit Du weisst, was genau in den Registern drinsteht.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Ruediger A. schrieb:
> Der
> Pending SV muss unter Allen Umständen die niedrigste aller
> Interruptprioritäten haben,

Das gilt nur dann, wenn ich ein OS benutze, das den Pending_SV nutzt. 
Ich habe kein OS. Für mich ist der Pending_SV einfach nur ein beliebiger 
IRQ, der frei ist, weil er sonst nur von einem OS genutzt würde. Die 
Timer-IRQs gehen mir nämlich so langsam aus.

Mein Ziel ist: Pending_SV_Handler soll von SysTick-Handler gestartet und 
beliebig oft von diesem unterbrochen werden.

von Walter T. (nicolas)


Lesenswert?

Soo....ich habe es gefunden. Die Konfiguration in Beitrag 
Beitrag "Re: STM32 Interrupt-Prioritäten - mal wieder"
war schon so weit in Ordnung, bis auf die PriorityGroupConfig. Hier 
gehört

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

hinein. Der M3 Guide von Joseph Yiu hat es besser erklärt als das ARM 
Reference Manual.

Was ich noch nicht verstanden habe ist, warum

NVIC_GetPriority(PendSV_IRQn);

Null zurückliefert.

: Bearbeitet durch User
von Christopher J. (christopher_j23)


Lesenswert?

Wie gesagt ist das Priority-Grouping meiner Meinung nach unnötig. Lies 
dir noch mal den Abschnitt zu den Subpriorities in dem Blog-Artikel 
durch.

So sollte es eigentlich auch funktionieren:
1
NVIC_SetPriorityGrouping(0);
2
3
NVIC_SetPriority(PendSV_IRQn, 1);
4
NVIC_SetPriority(SysTick_IRQn, 0);
5
6
NVIC_EnableIRQ(PendSV_IRQn);
7
NVIC_EnableIRQ(SysTick_IRQn);

von Walter T. (nicolas)


Lesenswert?

Christopher J. schrieb:
> So sollte es eigentlich auch funktionieren:
> NVIC_SetPriorityGrouping(0);
>
> NVIC_SetPriority(PendSV_IRQn, 1);
> NVIC_SetPriority(SysTick_IRQn, 0);

Das verstehe ich nicht. Die 1 müßte doch irgendwo in den fünf hinteren, 
beim STM32 nicht implementierten Bits des Priority Registers 
verschwinden. Und selbst wenn alle 8 Bit da wären, wäre sie an Bit 0, wo 
selbst bei NVIC_SetPriorityGrouping(0) noch nicht-preemptive Priorität 
stünde.

Edit: Die MCU versteht es auch nicht.

: Bearbeitet durch User
von Christopher J. (christopher_j23)


Lesenswert?

Nene, NVIC_SetPriority schiebt die 1 automatisch an die niedrigste 
Stelle, die noch implementiert ist, d.h. bei drei Prio-Bits an die 5. 
Stelle. Ich kann nur nochmal auf den Blog-Artikel verweisen, weil auch 
dieser Zusammenhang dort anschaulich erklärt wird.

von Christopher J. (christopher_j23)


Lesenswert?

Walter T. schrieb:
> Edit: Die MCU versteht es auch nicht.

Da verstehe ich wiederum nicht warum. Hast du den restlichen NVIC-Kram 
von ST aus deinem Source-Code entfernt?

Im folgenden mal ein Ping-Pong-Beispiel mit PendSV und SysTick.
Den Systick triggere ich wie PendSV per Software. Natürlich würde das 
normalerweise der Systick-Timer übernehmen. Jedenfalls triggert der 
Systick-Handler, je nachdem ob eine Bedingung erfüllt ist, den 
PendSV-Handler. In diesem Beispiel triggert der PendSV-Handler wiederum 
den Systick-Handler um einen Systick-Interrupt zu simulieren. Wenn der 
Systick keine höhere Dringlichkeit (d.h. niedrigeren Prioritätswert) 
hat, dann wird das while(cnt) im PendSV-Handler zur Dauerschleife. Das 
ganze habe ich getestet auf einem F411RE, wobei das so grundsätzlich - 
von der unterschiedlichen Headerbezeichnung abgesehen - auf jedem 
Cortex-M laufen sollte.
1
#include <stm32f4xx.h>
2
3
volatile int cnt;
4
#define MAX_CNT 5
5
6
void trigger_pendsv(void)
7
{
8
  SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
9
}
10
11
void trigger_systick(void)
12
{
13
  SCB->ICSR |= SCB_ICSR_PENDSTSET_Msk;
14
}
15
16
void SysTick_Handler(void)
17
{
18
  if (cnt == MAX_CNT) {
19
    trigger_pendsv();
20
  }
21
22
  cnt--;
23
}
24
25
void PendSV_Handler(void)
26
{
27
  while(cnt) {
28
    trigger_systick();
29
  }
30
}
31
32
int main(void) 
33
{
34
  cnt = MAX_CNT;
35
36
  NVIC_SetPriorityGrouping(0);
37
38
  NVIC_SetPriority(PendSV_IRQn, 1);
39
  NVIC_SetPriority(SysTick_IRQn, 0);
40
41
  NVIC_EnableIRQ(PendSV_IRQn);
42
  NVIC_EnableIRQ(SysTick_IRQn);
43
44
  trigger_systick();
45
46
  while(1) {
47
    // LED toggeln, printf, etc.
48
    // wird bei gleicher Priorität nie erreicht
49
  }
50
}

von Walter T. (nicolas)


Angehängte Dateien:

Lesenswert?

OK, Dein Beispiel kann ich auf einem STM32F446 nachvollziehen.

Bei meinem Beispiel ebenfalls: Wenn ich auf die Makros verzichte und die 
Zahlen hart eintippe, funktioniert es: Der PensSV_Handler wird vom 
SysTick_Handler unterbrochen.

Jetzt habe ich auch meinen Fehler gefunden: Das Makro 
"NVIC_PriorityGroup_0" ist noch ein letzter Rest aus der misc.h und ist 
von der Bedeutung genau invers zu dem, was man aus der ARM-Doku erwarten 
würde.

Der Compiler hat deswegen keine Warnung wegen der fehlenden Definition 
des Makros ausgegeben, weil die misc.h indirekt über die 
stm32f4xx_conf.h eingebunden war.

Wegen einer Abbildung im obengenannten Buch bin ich davon ausgegangen, 
daß "Priority Group = 0" bedeutet, daß es ein Sub-Priority Bit gibt. 
Dann hätte eine Priorität 1 eine Priorität 0 nie unterbrechen können, 
weil sie die gleiche Preemption Priority gehabt hätten.

Es klart sich auf!

Danke für Deine Hilfe!

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.