Forum: Mikrocontroller und Digitale Elektronik Überraschend hohe ISR Latenz auf STM32F3


von Ein Gast (Gast)


Lesenswert?

Ich kann nicht ganz nachvollziehen wie auf einem STM32F302R8 (Cortex M4 
mit FPU) eine ISR-Latenz von etwa 27 Takten mit der Compileroption 
(-Ofast) und etwa 32 Takten mit der Option (-Os) zustandekommt (mit 
arm-atollic-eabi-gcc).
Die Werte sind am Oszilloskop jeweils durch den Unterschied zwischen dem 
Setzten eines Pins mit einer Output Compare Einheit (Hardware) und dem 
entsprechenden Output-Compare-ISR, in welcher als erster Befehl ein 
anderer Pin mit GPIOB->BSRR = LL_GPIO_PIN_13; gesetzt wird.
So wie ich es verstehe, sollte es auf einem Cortex M4 12 Takte dauern um 
die CPU-Register auf den Stack zu sichern und in die ISR zu springen. 
Wenn die FPU-Register zusätzlich gesichert werden müssten, kämen 17 
Takte hinzu. Da ich keine FPU-Befehle in der ISR verwende würde ich 
erwarten, dass es 12 Takte + die Ausführungszeit des Pin-setzt-Befehls 
dauert, bis der Pin high wird. Das "Lazy Context Switching", bei welchem 
die FPU-Register nur bei Bedarf gesichert werden ist nach meinem 
Verständnis standardmäßig aktiv.

Weis jemand wie diese Latenz zustandekommt?
Oder sind Möglichkeiten bekannt wie das Sichern der FPU-Register 
explizit unterbunden werden kann?

von Felix F. (wiesel8)


Lesenswert?

Schon das Assembly angeschaut? Hier solltest du sehen können, was der 
Compiler alles generiert und warum die Zeiten nicht stimmen.

mfg

von (prx) A. K. (prx)


Lesenswert?

Den Assembler-Code der ISR anzuschauen bringt mehr, als darüber zu 
spekulieren.

von Andreas M. (amesser)


Lesenswert?

Mit welcher Frequenz läuft der Cortex? Wie wurde die Taktfrequenz des 
AHB eingestellt? Sind bei den 12 Takten auch die Pipeline Latenz dabei? 
Außerdem hat die ISR möglicherweise noch einen Prolog, Assemblercode? 
Der interne Flash geht max bis 24 Mhz, d.h. es kommen noch zusätzliche 
Latenztakte hinzu wenn der Code aus dem Flash läuft und die Taktfrequenz 
höher ist.

von (prx) A. K. (prx)


Lesenswert?

Es sind 2 nichtsequentielle Flash-Zugriffe dabei, einer für den Vektor 
und einer für die ISR. Die allerdings parallel zum Sichern der Register 
erfolgen. Bei 48-72 MHz gibts pro nonseq access 2 Waitstates oben drauf.

: Bearbeitet durch User
von armel aus dem ice (Gast)


Lesenswert?

Für taktgenaue Echtzeit finde ich die ARMs (mittlerweile) zu komplex.

Neben dem NVIC mit den ganzen Prioritäten gibt es da ja noch ein paar 
weitere Punkte:

* single-cycle access aufs Flash geht nut bei niedrigen Takten, 
wait-states=1 bzw. 2 nötig für höhere Taktraten. Kritische ISRs müssen 
also ins SRAM.
(Das mit den waitstates ist durch das ganze HAL-Gebastle auch wieder 
soweit wegabstrahiert dass ein 0815-Entwickler das gar nicht mehr 
mitbekommt wenn er nicht gezielt tiefer gräbt.)

* Mit den ganzen Bussen (AHBs, APBs) habe ich nicht das Gefühl da noch 
den Überblick über mögliche blocking points zu haben wenn da z.B. gerade 
irgendwo ein DMA-burst passiert.

Und wahrscheinlich gibt es da noch mehr Fallstricke.

von (prx) A. K. (prx)


Lesenswert?

armel aus dem ice schrieb:
> * single-cycle access aufs Flash geht nut bei niedrigen Takten,
> wait-states=1 bzw. 2 nötig für höhere Taktraten. Kritische ISRs müssen
> also ins SRAM.

... um damit die Latenz zu vergrössern, statt sie zu reduzieren: "The 12 
cycle latency requires that a nine cycle stack push can take place on 
one interface (typically the System interface) in parallel with a six 
cycle vector table read and interrupt handler fetch on other interfaces 
(typically I-Code). If these operations cannot be performed in parallel, 
they will have to be performed one after the other, increasing the 
latency."

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka16366.html

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


Lesenswert?

armel aus dem ice schrieb:
> Für taktgenaue Echtzeit finde ich die ARMs (mittlerweile) zu komplex.

Welche µCs erlauben eine auf den einzelnen Takt genau reproduzierbare 
ISR-Latenz?

PS: Grad drüber gestolpert: "Zero jitter support on Cortex-M0/Cortex-M0+ 
processors" als Option bei der Integration dieser Cores.
https://community.arm.com/processors/b/blog/posts/beginner-guide-on-interrupt-latency-and-interrupt-latency-of-the-arm-cortex-m-processors

Ist auch ein hübsches Diagramm zum Timing eines Interrupts drin.

: Bearbeitet durch User
von A. B. (Gast)


Lesenswert?

> anderer Pin mit GPIOB->BSRR = LL_GPIO_PIN_13; gesetzt wird.

Bis das Signal am Pin zu messen ist, dürften etliche (CPU!!!-) Takte 
verstreichen. Diese muss man von der Latenz natürlich abziehen.
Denn Latenz heißt ja: Zeit von Eintreffen des Interrupts (wo genau, wenn 
noch ein paar Synchronisationsstufen durchlaufen werden???) bis zum 
Beginn des ersten Befehl in der ISR.

Versuch dazu: Diesen Pin in einer Endlosschleife togglen und dann messen 
...

von (prx) A. K. (prx)


Lesenswert?

A. B. schrieb:
> Versuch dazu: Diesen Pin in einer Endlosschleife togglen und dann messen

Wenn damit die Latenz ermittelt werden soll, dann setzt dies voraus, 
dass der Befehl erst beendet wird, wenn der Pin den Zustand gewechselt 
hat. Das muss aber nicht zwangsläufig der Fall sein.

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Ein Gast schrieb:
> GPIOB->BSRR = LL_GPIO_PIN_13;

Das sind mindestens 3 Assembler Befehle, auch wenn es nur eine Zeile in 
C ist:
1
LDR R0, [PC+X]  ; Register Addresse laden
2
MOV R1, LL_GPIO_PIN_13
3
STR [R0], R1

Der erste LDR braucht mindestens 2 Takte, bei 2 Flash Waitstates eher 4. 
Der abschließende STR braucht mindestens 2 und arbeitet mit einem 
Schreibpuffer.

Je nach Anbindung der GPIOs (AHB oder APB) kann der eigentliche 
Hardwarezugriff noch weitere Takte kosten - APBs laufen mitunter nicht 
auf vollem Takt.

Der OP könnte mal -mslow-flash-data probieren, falls die GCC Version neu 
genug ist. Bringt aber IMHO höchstens 2 Takte hier.

von (prx) A. K. (prx)


Lesenswert?

Jim M. schrieb:
> Der erste LDR braucht mindestens 2 Takte, bei 2 Flash Waitstates eher 4.

Bei Optimierung auf Tempo sollte die Adresse nicht per Flash-Load, 
sondern mit 2 16-Bit-Immediate Befehlen geladen werden (MOV,MOVT).

von A. B. (Gast)


Lesenswert?

A. K. schrieb:

> Wenn damit die Latenz ermittelt werden soll, dann setzt dies voraus,
> dass der Befehl erst beendet wird, wenn der Pin den Zustand gewechselt
> hat. Das muss aber nicht zwangsläufig der Fall sein.

Logisch. Genau genommen, müsste sogar das Ende des Befehls exakt mit dem 
Wechsel des Pin-Zustandes zusammen fallen (weder früher noch später!).

Und eine Endlos-Schleife reicht genau genommen auch nicht, da einem der 
Prefetch natürlich einen Strich durch die Rechnung macht. Also die 
Schleife abrollen ...

Selbst der Zeitpunkt, wann die CPU oder der NVIC den Interrupt bekommt, 
ist ja nicht präzise bekannt. Da der Timer wohl nicht mit dem CPU-Takt 
läuft, sind zwischen Timer-Ausgang und NVIC auch wieder ein paar weitere 
Stufen ...

Aber es ging schließlich nicht um eine exakte Messung der Latenz (die 
ist von außen schwierig bis unmöglich, da so viele in den Brei spucken), 
sondern nur um einen Plausibilitätsüberlegung, wo diese 27 im Vgl. zu 
dem "Werbe-Slogan" 12 wohl herkommen mag.

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
> Bei Optimierung auf Tempo sollte die Adresse nicht per Flash-Load,
> sondern mit 2 16-Bit-Immediate Befehlen geladen werden (MOV,MOVT).

Auch als Pseudo-Befehl MOV32 verfügbar. Ist 8 Bytes lang, statt summarum 
6 bei LDR, aber unabhängig von der Zugriffszeit vom Flash in 2 Takten 
durch.

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

A. K. schrieb:
> Bei Optimierung auf Tempo sollte die Adresse nicht per Flash-Load,
> sondern mit 2 16-Bit-Immediate Befehlen geladen werden (MOV,MOVT).

Das gilt offenbar nicht mehr für neuere GCC Versionen. Hier wird 
movw/movt nur noch mit -mslow-flash-data generiert, ansonsten nimmt er 
LDR auch mit -O3 Optimierung.

So um GCC 4.5 herum hatte ich noch die movw/movt auch im normalen -O3 
Disassembly gesehen.

von Ein Gast (Gast)


Lesenswert?

Nochmal ein Update in der Sache:
Ich kann es jetzt soweit nachvollziehen wie die Latenz entsteht.
Wie dies hier bereits geäußert wurde, erzeugen die Waitstates des Flash 
den unerwarteten Teil der Latenz. Ich hatte den Controller mit 64MHz 
betrieben, sodass für den Flash, welcher mit maximal 24MHz betrieben 
werden darf 2 Waitstates erforderlich werden. Dementsprechend kommt es 
zu Verzögerungen, wenn die Befehle der ISR aus dem Flash geladen werden.
Testweise habe ich den Controller einmal mit 20MHz getaktet, sodass der 
Flash mit 0 Waitstates betrieben werden kann. Dabei beträgt die 
Verzögerung bis der erste Befehl in der ISR zur Ausführung kommt etwa 
17Takte. Dies liegt etwa im Rahmen dessen, was ich mit einer ISR-Latenz 
von 12Takten + Ausführung des Befehls erwarten würde.

Die "Lazy Context Switch"-Funktion ist auch standardmäßig definitiv 
aktiv. Ich hatte mir die Bits im entsprechenden Register in der ISR 
einmal angesehen.

Falls es interessiert, ist hier noch der C-Code der ISR und das 
Disassembly.
1
void TIM1_CC_IRQHandler(void)
2
{
3
  GPIOB->BSRR = LL_GPIO_PIN_13;
4
  if(LL_TIM_IsActiveFlag_CC4(TIM1) == 1)
5
  {
6
    LL_TIM_ClearFlag_CC4(TIM1);
7
  }
8
  LL_GPIO_ResetOutputPin(GPIOB, LL_GPIO_PIN_13);
9
}
1
          TIM1_CC_IRQHandler:
2
080023bc:   ldr     r3, [pc, #24]   ; (0x80023d8 <TIM1_CC_IRQHandler+28>)
3
4069        return (READ_BIT(TIMx->SR, TIM_SR_CC4IF) == (TIM_SR_CC4IF));
4
080023be:   ldr     r1, [pc, #28]   ; (0x80023dc <TIM1_CC_IRQHandler+32>)
5
185         GPIOB->BSRR = LL_GPIO_PIN_13;
6
080023c0:   mov.w   r2, #8192       ; 0x2000
7
080023c4:   str     r2, [r3, #24]
8
4069        return (READ_BIT(TIMx->SR, TIM_SR_CC4IF) == (TIM_SR_CC4IF));
9
080023c6:   ldr     r0, [r1, #16]
10
188         if(LL_TIM_IsActiveFlag_CC4(TIM1) == 1)
11
080023c8:   lsls    r0, r0, #27
12
4058        WRITE_REG(TIMx->SR, ~(TIM_SR_CC4IF));
13
080023ca:   itt     mi
14
080023cc:   mvnmi.w r0, #16
15
080023d0:   strmi   r0, [r1, #16]
16
926         WRITE_REG(GPIOx->BRR, PinMask);
17
080023d2:   str     r2, [r3, #40]   ; 0x28
18
080023d4:   bx      lr
19
080023d6:   nop     
20
080023d8:   lsls    r0, r0, #16
21
080023da:   ldr     r0, [pc, #0]    ; (0x80023dc <TIM1_CC_IRQHandler+32>)
22
080023dc:   cmp     r4, #0
23
080023de:   ands    r1, r0
24
4003        return (READ_BIT(TIMx->SR, TIM_SR_CC1IF) == (TIM_SR_CC1IF));
Eventuell kann jemand der etwas mehr Ahnung von Assembler hat, das 
Entstehen der verbleibenden Differenz zu den 12 Takten erklären.
Was mich im Disassembly auf den ersten Blick etwas verwundert ist, dass 
die Überprüfung ob das CC4IF-Flag gesetzt ist, anscheinend zum Teil vor 
dem Befehl zum setzten des Pins ausgeführt wird.

Beitrag #5138584 wurde vom Autor gelöscht.
von (prx) A. K. (prx)


Lesenswert?

17/27 Takte bis zum Anfang der ISR, oder bis der Pin wackelt?

Beim STM32 gibts einen kleinen Schmutzeffekt, der die Zählung von Takten 
etwas erschwert. Die haben das Flash nämlich ziemlich auf Kante genäht. 
Das ist bloss 64 Bits breit, liefert die bei 2 Waitstates aber nur alle 
3 Takte ab. Bei in 32 Bits codierten Befehlen kann das knapp werden. 
Weshalb auch die -mslow-flash-data Option u.U. nur ein Problem durch ein 
anderes ersetzt.

Dass der zweite LDR vorgezogen wurde kann eine Pipeline-Optimierung 
darstellen. Das Ergebnis eines Load-Befehls ist bei vielen CPUs nicht 
ohne Verzögerung im nachfolgenden Befehl als Speicheradresse verwendbar 
(address generation interlock). Weshalb ein cleverer Compiler versucht 
sein kann, die Befehle so anzuordnen, dass dies nicht auftritt.

: Bearbeitet durch User
von Lothar (Gast)


Lesenswert?

A. K. schrieb:
> Welche µCs erlauben eine auf den einzelnen Takt genau reproduzierbare
> ISR-Latenz?

- 8051 durch Registerbänke (bei Ausführung im RAM oder Flash ohne 
Waitstates)
- Cortex-R durch FIQ Registerbank

> PS: Grad drüber gestolpert: "Zero jitter support on Cortex-M0/Cortex-M0+

Das soll nur für eine reproduzierbare Latenz sorgen, was aber für 
"normale" Echtzeit ausreicht.

Cortex-M sind wegen ISR-Autostacking grundsätzlich langsam. Das ist der 
Preis für ISR in C ohne ASM-Wrapper und das Einsparen von 
Registerbänken.

von Ein Gast (Gast)


Lesenswert?

@A.K.
Jeweils zwischen dem auf High schalten eines Output-Compare-Pins und dem 
Pin, welcher in der ISR des OC-Kanals auf High gesetzt wird.

von (prx) A. K. (prx)


Lesenswert?

Lothar schrieb:
> - 8051 durch Registerbänke (bei Ausführung im RAM oder Flash ohne
> Waitstates)

Bei Intels Original geht m.W. die Befehlsausführungszeit in die Latenz 
ein. Befehle unterschiedlicher Ausführungszeit führen also zu variabler 
Interrupt-Latenz. Bei neueren Cores mag das anders sein.

>> PS: Grad drüber gestolpert: "Zero jitter support on Cortex-M0/Cortex-M0+
>
> Das soll nur für eine reproduzierbare Latenz sorgen, was aber für
> "normale" Echtzeit ausreicht.

Eben. Ich schrieb deshalb ja auch "taktgenau". Das impliziert eine feste 
Reaktionszeit, nicht unbedingt die kürzeste.

von (prx) A. K. (prx)


Lesenswert?

Ein Gast schrieb:
> Jeweils zwischen dem auf High schalten eines Output-Compare-Pins und dem
> Pin, welcher in der ISR des OC-Kanals auf High gesetzt wird.

Dann erklärt sich ja wohl der Unterschied von 12 zu 17 Takten fast von 
selber.

Von den übrigen 10 Takten bei 2 Waitstates lassen sich 8 direkt durch 
die Waitstates erklären, denn es gibt 4 nichtsequentielle Flashzugriffe. 
Vektor, ISR-Start und die beiden LDRs. Bei den übrigen beiden Takten 
könnte z.B. ein LDR durch einen laufenden Prefetch verzögert ausgeführt 
werden.

Probier mal -mslow-flash-data bei Optimierung für Tempo.

: Bearbeitet durch User
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.