Interrompere la latenza su un MCU STM32F303


8

Sto lavorando a un progetto che coinvolge un MCU STM32 (sulla scheda STM32303C-EVAL per l'esattezza) che deve rispondere a un interrupt esterno. Voglio che la reazione all'interrupt esterno sia il più veloce possibile. Ho modificato un esempio di libreria periferica standard dalla pagina Web ST e il programma corrente commuta semplicemente un LED su ogni fronte di salita successivo su 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); 
}

Il gestore di interrupt si presenta così:

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 questo caso particolare, gli interrupt vengono creati da un generatore di funzioni programmabile esterno che funziona a 100 Hz. Dopo aver esaminato la risposta della MCU su un oscilloscopio, sono rimasto piuttosto sorpreso dal fatto che ci vogliono quasi 1,32 noi affinché la MCU inizi a elaborare l'interruzione: inserisci qui la descrizione dell'immagine

Con l'MCU a 72 MHz (in precedenza ho verificato l'uscita SYSCLK sul pin MCO) ciò equivale a quasi 89 cicli di clock. La risposta MCU all'interrupt non dovrebbe essere molto più veloce?

PS Il codice è stato compilato con IAR Embedded Workbench e ottimizzato per la massima velocità.


Sei sicuro che sia il ritardo per iniziare a elaborare l'interrupt? Cosa succede quando si rimuove la condizione if e si attiva / disattiva?
BeB00,

@ BeB00 l' if{}istruzione è necessaria perché la routine di interrupt non sa quale sia l'origine dell'interrupt.
Rohat Kılıç,

Se ricordo bene, la latenza dovrebbe aggirarsi intorno ai 10-15 cicli
BeB00

1
Giusto, ma cosa succede quando lo rimuovi nel tuo esperimento? Presumo che tu non abbia un sacco di altri interrupt che lo attivano costantemente, quindi dovresti essere in grado di sentire meglio il ritardo effettivo
BeB00

1
Non dovrebbe davvero essere un mistero. Guarda il codice assemblatore compilato per la tua funzione di interruzione e fai riferimento al manuale di riferimento ARM appropriato per aggiungere il numero di cicli di clock per ogni istruzione ...
brhans,

Risposte:


8

Problema

Bene, devi guardare le funzioni che stai usando, non puoi semplicemente fare ipotesi sulla velocità del codice che non hai visto:

Questa è la funzione EXTI_GetITStatus:

ITStatus EXTI_GetITStatus   (   uint32_t    EXTI_Line    )  
{
  ITStatus bitstatus = RESET;
  uint32_t enablestatus = 0;

  /* Check the parameters */
  assert_param(IS_GET_EXTI_LINE(EXTI_Line));

  enablestatus =  *(__IO uint32_t *) (((uint32_t) &(EXTI->IMR)) + ((EXTI_Line) >> 5 ) * 0x20) & (uint32_t)(1 << (EXTI_Line & 0x1F));

  if ( (((*(__IO uint32_t *) (((uint32_t) &(EXTI->PR)) + (((EXTI_Line) >> 5 ) * 0x20) )) & (uint32_t)(1 << (EXTI_Line & 0x1F))) != (uint32_t)RESET) && (enablestatus != (uint32_t)RESET))
  {
    bitstatus = SET;
  }
  else
  {
    bitstatus = RESET;
  }
  return bitstatus;

}

Come puoi vedere, questa non è una cosa semplice che richiede solo un ciclo o due.

Il prossimo è la tua funzione di commutazione a LED:

void STM_EVAL_LEDToggle (   Led_TypeDef     Led  )  
{
  GPIO_PORT[Led]->ODR ^= GPIO_PIN[Led];
}

Quindi qui hai un po 'di indicizzazione dell'array e una modifica in lettura e scrittura per attivare il LED.

Gli HAL finiscono spesso per creare un buon sovraccarico perché devono occuparsi delle impostazioni errate e dell'uso errato delle funzioni. Il controllo dei parametri necessario e anche la traduzione da un semplice parametro a un bit nel registro possono richiedere una notevole quantità di elaborazione (beh almeno per un interrupt critico nel tempo).

Quindi, nel tuo caso, dovresti implementare il tuo bare metal di interruzione direttamente sui registri e non fare affidamento su alcun HAL.


Soluzione di esempio

Ad esempio qualcosa del tipo:

if (EXTI->PR & EXTI_PR_PR6)
{
    GPIOE->BSRR = GPIO_BSRR_BS_8;
    EXTI->PR = EXTI_PR_PR6;
}

Nota: questo non commuta il LED ma lo imposta semplicemente. Non sono disponibili interruttori atomici sui GPIO STM. Inoltre non mi piace il ifcostrutto che ho usato, ma genera un assemblaggio più veloce del mio preferito if (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6)).

Una variante di attivazione / disattivazione potrebbe essere qualcosa del genere:

static bool LEDstate = false;
if (EXTI->PR & EXTI_PR_PR6)
{
    if (!LEDstate)
    {
        GPIOE->BSRR = GPIO_BSRR_BS_8;
        LEDstate = true;
    }
    else
    {
        GPIOE->BSRR = GPIO_BSRR_BR_8;
        LEDstate = false;
    }
    EXTI->PR = EXTI_PR_PR6;
}

L'uso di una variabile che risiede nella RAM invece di usare il ODRregistro dovrebbe essere più veloce, specialmente quando si usa 72 MHz, perché l'accesso alle periferiche può essere più lento a causa della sincronizzazione tra domini di clock diversi e clock delle periferiche che funzionano semplicemente a una frequenza inferiore. Naturalmente, non è possibile modificare lo stato del LED al di fuori dell'interrupt affinché l'interruttore funzioni correttamente. Oppure la variabile deve essere globale (quindi devi usare la volatileparola chiave per dichiararla) e devi cambiarla ovunque di conseguenza.

Si noti inoltre che sto usando C ++, quindi il boole non un uint8_ttipo o simile per implementare un flag. Sebbene se la velocità è la tua preoccupazione principale, dovresti probabilmente optare per un uint32_tflag poiché questo sarà sempre allineato correttamente e non genererà codice aggiuntivo durante l'accesso.

La semplificazione è possibile perché si spera che tu sappia cosa stai facendo e continui sempre così. Se hai davvero un solo interrupt abilitato per il gestore EXTI9_5 puoi eliminare del tutto il controllo del registro in sospeso, riducendo ulteriormente il numero di cicli.

Questo porta ad un altro potenziale di ottimizzazione: usare una linea EXTI che ha un singolo interrupt come uno da EXTI1 a EXTI4. Lì non è necessario eseguire un controllo per verificare se la linea corretta ha attivato l'interruzione.


1
È difficile dire dal codice C quante istruzioni sarebbero necessarie. Ho visto funzioni più grandi ottimizzate per un paio di istruzioni che non comportavano nemmeno una chiamata reale.
Dmitry Grigoryev il

1
@DmitryGrigoryev come registro sono dichiarati poiché volatileil compilatore non può ottimizzare molto nelle funzioni sopra e se le funzioni non sono implementate in linea nell'intestazione, la chiamata di solito non viene nemmeno ottimizzata.
Arsenal,

5

Seguendo il suggerimento di PeterJ ho omesso l'uso di SPL. L'intero mio codice è simile al seguente:

#include "stm32f30x.h"

void EXTI0_IRQHandler(void)
{
    // I am simply toggling the pin within the interrupt, as I only want to check the response speed.
     GPIOE->BSRR |= GPIO_BSRR_BS_10;
     GPIOE->BRR |= GPIO_BRR_BR_10;
     EXTI->PR |= EXTI_PR_PR0;
}

int main()
{
    // Initialize the HSI:
    RCC->CR |= RCC_CR_HSION;
    while(!(RCC->CR&RCC_CR_HSIRDY));

    // PLL configuration:
    RCC->CFGR &= ~RCC_CFGR_PLLSRC;     // HSI / 2 selected as the PLL input clock.
    RCC->CFGR |= RCC_CFGR_PLLMULL16;   // HSI / 2 * 16 = 64 MHz
    RCC->CR |= RCC_CR_PLLON;          // Enable PLL
    while(!(RCC->CR&RCC_CR_PLLRDY));  // Wait until PLL is ready

    // Flash configuration:
    FLASH->ACR |= FLASH_ACR_PRFTBE;
    FLASH->ACR |= FLASH_ACR_LATENCY_1;

    // Main clock output (MCO):
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER |= GPIO_MODER_MODER8_1;
    GPIOA->OTYPER &= ~GPIO_OTYPER_OT_8;
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR8;
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8;
    GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL0;

    // Output on the MCO pin:
    RCC->CFGR |= RCC_CFGR_MCO_SYSCLK;

    // PLL as the system clock
    RCC->CFGR &= ~RCC_CFGR_SW;    // Clear the SW bits
    RCC->CFGR |= RCC_CFGR_SW_PLL; //Select PLL as the system clock
    while ((RCC->CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL); //Wait until PLL is used

    // LED output:
    RCC->AHBENR |= RCC_AHBENR_GPIOEEN;
    GPIOE->MODER |= GPIO_MODER_MODER10_0;
    GPIOE->OTYPER &= ~GPIO_OTYPER_OT_10;
    GPIOE->PUPDR &= ~GPIO_PUPDR_PUPDR10;
    GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR10;

    // Interrupt on PA0:
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER &= ~(GPIO_MODER_MODER0);
    GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR0);
    GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR0);
    SYSCFG->EXTICR[0] &= SYSCFG_EXTICR1_EXTI0_PA;
    EXTI->RTSR = EXTI_RTSR_TR0;
    EXTI->IMR = EXTI_IMR_MR0; 
    NVIC_SetPriority(EXTI0_IRQn, 1);
    NVIC_EnableIRQ(EXTI0_IRQn);

    while(1)
    {

    }
}

e le istruzioni di assemblaggio si presentano così:

EXTI0_IRQHandler:
        LDR.N    R0,??DataTable1  ;; 0x48001018
        LDR      R1,[R0, #+0]
        ORR      R1,R1,#0x400
        STR      R1,[R0, #+0]
        LDRH     R2,[R0, #+16]
        ORR      R2,R2,#0x400
        STRH     R2,[R0, #+16]
        LDR.N    R0,??DataTable1_1  ;; 0x40010414
        LDR      R1,[R0, #+0]
        ORR      R1,R1,#0x1
        STR      R1,[R0, #+0]
        BX       LR               ;; return

Questo migliora un po 'le cose, dato che sono riuscito a ottenere una risposta in ~ 440 ns a 64 MHz (ovvero 28 cicli di clock).


2
Cambia il tuo BRR |= e BSRR |= in solo BRR = e BSRR = , quei registri sono solo in scrittura, il tuo codice li sta leggendo, ORRinserendo il valore e quindi scrivendo. che potrebbe essere ottimizzato per una singola STRistruzione.
Colin,

Sposta il gestore e i vettori EXTI su CCMRAM
P__J__

3

La risposta è estremamente semplice: ottima libreria HAL (o SPL). Se fai qualcosa di sensibile al tempo, usa invece i registri periferici nudi. Quindi otterrai la latenza corretta. Non riesco a capire qual è il punto di usare questa ridicola libreria per attivare il pin !! o per controllare il registro delle statue.


3

Ci sono alcuni errori nel tuo codice = il registro BSRR è di sola scrittura. Non utilizzare | = operatore, semplicemente "=" semplice. Imposta / ripristina i pin corretti. Gli zeri vengono ignorati.

Ti farà risparmiare un paio di orologi. Un altro suggerimento: sposta la tua tabella vettoriale e interrompi le routine su CCMRAM. Salverai qualche altro segno di spunta (flash waitstates ecc.)

PS Non posso commentare perché non ho abbastanza reputazione :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.