Ich arbeite an einem Projekt, das eine STM32-MCU (genauer gesagt auf der STM32303C-EVAL-Karte) umfasst, die auf einen externen Interrupt reagieren muss. Ich möchte, dass die Reaktion auf den externen Interrupt so schnell wie möglich erfolgt. Ich habe ein Beispiel für eine Standard-Peripheriebibliothek auf der ST-Webseite geändert und das aktuelle Programm schaltet einfach eine LED bei jeder aufeinanderfolgenden ansteigenden Flanke auf PE6 um:
#include "stm32f30x.h"
#include "stm32303c_eval.h"
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
static void EXTI9_5_Config(void);
int main(void)
{
/* Initialize LEDs mounted on STM32303C-EVAL board */
STM_EVAL_LEDInit(LED1);
/* Configure PE6 in interrupt mode */
EXTI9_5_Config();
/* Infinite loop */
while (1)
{
}
}
// Configure PE6 and PD5 in interrupt mode
static void EXTI9_5_Config(void)
{
/* Enable clocks */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOE, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* Configure input */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Connect EXTI6 Line to PE6 pin */
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource6);
/* Configure Button EXTI line */
EXTI_InitStructure.EXTI_Line = EXTI_Line6;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* Enable and set interrupt to the highest priority */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
Der Interrupt-Handler sieht folgendermaßen aus:
void EXTI9_5_IRQHandler(void)
{
if((EXTI_GetITStatus(EXTI_Line6) != RESET))
{
/* Toggle LD1 */
STM_EVAL_LEDToggle(LED1);
/* Clear the EXTI line 6 pending bit */
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
In diesem speziellen Fall werden die Interrupts von einem externen programmierbaren Funktionsgenerator erzeugt, der mit 100 Hz läuft. Nachdem ich die MCU-Antwort auf einem Oszilloskop untersucht hatte, war ich ziemlich überrascht, dass die MCU fast 1,32 Minuten benötigt, um mit der Verarbeitung des Interrupts zu beginnen:
Bei einer MCU mit 72 MHz (ich habe zuvor den SYSCLK-Ausgang am MCO-Pin überprüft) entspricht dies fast 89 Taktzyklen. Sollte die MCU-Reaktion auf den Interrupt nicht viel schneller sein?
PS Der Code wurde mit IAR Embedded Workbench kompiliert und für höchste Geschwindigkeit optimiert.
if{}
Anweisung wird benötigt, da die Interruptroutine die Quelle des Interrupts nicht kennt.Antworten:
Problem
Nun, Sie müssen sich die Funktionen ansehen, die Sie verwenden. Sie können nicht einfach Annahmen über die Geschwindigkeit des Codes treffen, den Sie nicht betrachtet haben:
Dies ist die EXTI_GetITStatus-Funktion:
Wie Sie sehen können, ist dies keine einfache Sache, die nur ein oder zwei Zyklen erfordert.
Als nächstes kommt Ihre LED-Umschaltfunktion:
Hier haben Sie also eine Array-Indizierung und einen Lese- und Schreibvorgang, um die LED umzuschalten.
HALs verursachen häufig einen hohen Overhead, da sie sich um falsche Einstellungen und die falsche Verwendung der Funktionen kümmern müssen. Die erforderliche Parameterprüfung und auch die Übersetzung von einem einfachen Parameter in ein Bit im Register kann einen erheblichen Rechenaufwand erfordern (zumindest für einen zeitkritischen Interrupt).
In Ihrem Fall sollten Sie Ihr Interrupt-Bare-Metal direkt in den Registern implementieren und sich nicht auf HAL verlassen.
Beispiellösung
Zum Beispiel so etwas wie:
Hinweis: Dadurch wird die LED nicht umgeschaltet, sondern einfach eingestellt. Auf den STM-GPIOs ist kein atomarer Umschalter verfügbar. Ich mag das
if
Konstrukt, das ich verwendet habe, auch nicht , aber es erzeugt eine schnellere Assemblierung als meine bevorzugteif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))
.Eine Umschaltvariante könnte in diese Richtung gehen:
Die Verwendung einer Variablen im RAM anstelle des
ODR
Registers sollte schneller sein, insbesondere wenn Sie 72 MHz verwenden, da der Zugriff auf die Peripheriegeräte aufgrund der Synchronisation zwischen verschiedenen Taktdomänen und Peripherietakten, die einfach mit einer niedrigeren Frequenz ausgeführt werden, langsamer sein kann. Natürlich dürfen Sie den Status der LED außerhalb des Interrupts nicht ändern, damit der Umschalter ordnungsgemäß funktioniert. Oder die Variable muss global sein (dann müssen Sie dasvolatile
Schlüsselwort verwenden, wenn Sie es deklarieren) und Sie müssen es überall entsprechend ändern.Beachten Sie auch, dass ich C ++ verwende, daher der
bool
und keinuint8_t
Typ oder ähnliches, um ein Flag zu implementieren. Wenn Geschwindigkeit Ihr Hauptanliegen ist, sollten Sie sich wahrscheinlich für einuint32_t
Flag entscheiden, da dieses immer korrekt ausgerichtet ist und beim Zugriff keinen zusätzlichen Code generiert.Die Vereinfachung ist möglich, weil Sie hoffentlich wissen, was Sie tun, und es immer so halten. Wenn Sie wirklich nur einen einzigen Interrupt für den EXTI9_5-Handler aktiviert haben, können Sie die ausstehende Registerprüfung vollständig entfernen und die Anzahl der Zyklen noch weiter reduzieren.
Dies führt zu einem weiteren Optimierungspotential: Verwenden Sie eine EXTI-Leitung mit einem einzelnen Interrupt wie EXTI1 bis EXTI4. Dort müssen Sie nicht prüfen, ob die richtige Leitung Ihren Interrupt ausgelöst hat.
quelle
volatile
der Compiler in den obigen Funktionen nicht viel optimieren darf. Wenn die Funktionen nicht inline im Header implementiert sind, wird der Aufruf normalerweise auch nicht wegoptimiert.Auf den Vorschlag von PeterJ habe ich die Verwendung von SPL weggelassen. Der gesamte Code sieht folgendermaßen aus:
und die Montageanleitung sieht folgendermaßen aus:
Dies verbessert die Sache erheblich, da ich es geschafft habe, eine Antwort in ~ 440 ns bei 64 MHz (dh 28 Taktzyklen) zu erhalten.
quelle
BRR |=
undBSRR |=
in nurBRR =
undBSRR =
, diese Register sind nur schreibbar, Ihr Code liest sie, gibtORR
den Wert ein und schreibt dann. das könnte auf einen einzigenSTR
Befehl optimiert werden .Die Antwort ist sehr einfach: großartige HAL- (oder SPL-) Bibliothek. Wenn Sie etwas zeitkritisches tun, verwenden Sie stattdessen bloße Peripherieregister. Dann erhalten Sie die richtige Latenz. Ich kann nicht verstehen, was der Sinn ist, diese lächerliche Bibliothek zu benutzen, um den Stift umzuschalten !! oder um das Statuenregister zu überprüfen.
quelle
Ihr Code enthält einige Fehler = Das BSRR-Register ist nur schreibbar. Verwenden Sie nicht den Operator | =, sondern nur das einfache "=". Die richtigen Pins werden gesetzt / zurückgesetzt. Nullen werden ignoriert.
Sie sparen ein paar Uhren. Ein weiterer Hinweis: Verschieben Sie Ihre Vektortabellen- und Interruptroutinen nach CCMRAM. Sie speichern einige weitere Ticks (Flash-Wartezustände usw.)
PS Ich kann nicht kommentieren, da ich nicht genug Ruf habe :)
quelle