Je travaille sur un projet qui implique un microcontrôleur STM32 (sur la carte STM32303C-EVAL pour être exact) qui doit répondre à une interruption externe. Je veux que la réaction à l'interruption externe soit aussi rapide que possible. J'ai modifié un exemple de bibliothèque périphérique standard à partir de la page Web ST et le programme actuel bascule simplement une LED à chaque front montant successif sur PE6:
#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);
}
Le gestionnaire d'interruption ressemble à ceci:
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);
}
}
Dans ce cas particulier, les interruptions sont créées par un générateur de fonction programmable externe fonctionnant à 100 Hz. Après avoir examiné la réponse du MCU sur un oscilloscope, j'ai été plutôt surpris qu'il nous faut près de 1,32 pour que le MCU commence à traiter l'interruption:
Avec le MCU fonctionnant à 72 MHz (j'ai vérifié la sortie SYSCLK sur la broche MCO au préalable), cela représente près de 89 cycles d'horloge. La réponse du MCU à l'interruption ne devrait-elle pas être beaucoup plus rapide?
PS Le code a été compilé avec IAR Embedded Workbench et optimisé pour la vitesse la plus élevée.
if{}
instruction est nécessaire car la routine d'interruption ne sait pas quelle est la source de l'interruption.Réponses:
Problème
Eh bien, vous devez regarder les fonctions que vous utilisez, vous ne pouvez pas simplement faire des hypothèses sur la vitesse du code que vous n'avez pas regardé:
Il s'agit de la fonction EXTI_GetITStatus:
Comme vous pouvez le voir, ce n'est pas une chose simple nécessitant juste un cycle ou deux.
Vient ensuite votre fonction de bascule LED:
Vous avez donc ici une indexation de tableau et une lecture-écriture-modification pour basculer la LED.
Les HAL finissent souvent par créer une bonne quantité de frais généraux car ils doivent prendre soin de mauvais réglages et d'une mauvaise utilisation des fonctions. La vérification des paramètres nécessaires ainsi que la traduction d'un simple paramètre en un bit du registre peuvent prendre une quantité importante de calcul (enfin pour une interruption critique en temps).
Donc, dans votre cas, vous devez implémenter votre interruption bare metal directement sur les registres et ne compter sur aucun HAL.
Exemple de solution
Par exemple quelque chose comme:
Remarque: cela ne fera pas basculer la LED mais la réglera simplement. Il n'y a pas de bascule atomique disponible sur les GPIO STM. Je n'aime pas non plus la
if
construction que j'ai utilisée, mais elle génère un assemblage plus rapide que mon préféréif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))
.Une variante à bascule pourrait être quelque chose dans ce sens:
L'utilisation d'une variable résidant dans la RAM au lieu d'utiliser le
ODR
registre devrait être plus rapide, surtout lorsque vous utilisez 72 MHz, car l'accès aux périphériques peut être plus lent en raison de la synchronisation entre les différents domaines d'horloge et les horloges périphériques fonctionnant simplement à une fréquence inférieure. Bien sûr, vous ne pouvez pas modifier l'état de la LED en dehors de l'interruption pour que la bascule fonctionne correctement. Ou la variable doit être globale (alors vous devez utiliser levolatile
mot - clé pour la déclarer) et vous devez la changer partout en conséquence.Notez également que j'utilise C ++, d'où le type
bool
et pas unuint8_t
type ou similaire pour implémenter un indicateur. Bien que si la vitesse est votre principale préoccupation, vous devriez probablement opter pour unuint32_t
indicateur car il sera toujours correctement aligné et ne générera pas de code supplémentaire lors de l'accès.La simplification est possible parce que nous espérons que vous savez ce que vous faites et que vous le gardez toujours ainsi. Si vous n'avez vraiment qu'une seule interruption activée pour le gestionnaire EXTI9_5, vous pouvez vous débarrasser complètement de la vérification du registre en attente, ce qui réduit encore plus le nombre de cycles.
Cela conduit à un autre potentiel d'optimisation: utilisez une ligne EXTI qui a une seule interruption comme celle de EXTI1 à EXTI4. Là, vous n'avez pas à vérifier si la ligne correcte a déclenché votre interruption.
la source
volatile
le compilateur n'est pas autorisé à optimiser beaucoup dans les fonctions ci-dessus et si les fonctions ne sont pas implémentées en ligne dans l'en-tête, l'appel n'est généralement pas optimisé non plus.Suite à la suggestion de PeterJ, j'ai omis l'utilisation de SPL. L'intégralité de mon code ressemble à ceci:
et les instructions d'assemblage ressemblent à ceci:
Cela améliore un peu les choses, car j'ai réussi à obtenir une réponse en ~ 440 ns à 64 MHz (soit 28 cycles d'horloge).
la source
BRR |=
etBSRR |=
en justeBRR =
etBSRR =
, ces registres sont en écriture seule, votre code les lit,ORR
ing la valeur puis écrit. qui pourrait être optimisé en une seuleSTR
instruction.La réponse est extrêmement simple: une excellente bibliothèque HAL (ou SPL). Si vous faites quelque chose de sensible au temps, utilisez plutôt des registres périphériques nus. Ensuite, vous obtiendrez la latence correcte. Je ne peux pas comprendre quel est l'intérêt d'utiliser cette bibliothèque ridicule pour basculer la broche !! ou pour vérifier le registre des statues.
la source
Il y a quelques erreurs dans votre code = le registre BSRR est en écriture uniquement. N'utilisez pas l'opérateur | =, juste un simple "=". Il définira / réinitialisera les broches appropriées. Les zéros sont ignorés.
Cela vous fera économiser quelques horloges. Autre conseil: déplacez votre table vectorielle et interrompez les routines vers CCMRAM. Vous économiserez quelques autres ticks (flash waitstates etc)
PS Je ne peux pas commenter car je n'ai pas assez de réputation :)
la source