Latencia de interrupción en una MCU STM32F303

6

Estoy trabajando en un proyecto que involucra una MCU STM32 (en la placa STM32303C-EVAL para ser exactos) que tiene que responder a una interrupción externa. Quiero que la reacción a la interrupción externa sea lo más rápida posible. He modificado un ejemplo de biblioteca de periféricos estándar desde la página web de ST y el programa actual simplemente enciende un LED en cada flanco ascendente sucesivo en 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); 
}

El controlador de interrupciones se ve así:

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);
  }
}

En este caso particular, las interrupciones son creadas por un generador de funciones programable externo que funciona a 100 Hz. Después de examinar la respuesta de la MCU en un osciloscopio, me sorprendió bastante que nos llevara casi 1.32 para que la MCU comience a procesar la interrupción:

Con la MCU funcionando a 72 MHz (he comprobado previamente la salida de SYSCLK en el pin MCO) esto equivale a casi 89 ciclos de reloj. ¿No debería ser más rápida la respuesta de MCU a la interrupción?

P.S. El código fue compilado con IAR Embedded Workbench y optimizado para la velocidad más alta.

    
pregunta K.R.

4 respuestas

8

Problema

Bueno, tienes que mirar las funciones que estás usando, no puedes hacer suposiciones sobre la velocidad del código que no has visto:

Esta es la función 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;

}

Como puede ver, esto no es una cosa simple que requiere solo un ciclo o dos.

La siguiente es su función de conmutación de LED:

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

Así que aquí tienes un índice de matriz y una escritura de modificación de lectura para encender el LED.

Las HAL a menudo terminan generando una buena cantidad de sobrecarga porque deben cuidar las configuraciones incorrectas y el uso incorrecto de las funciones. La comprobación de parámetros necesaria y también la traducción de un parámetro simple a un bit en el registro puede requerir una gran cantidad de cómputo (por lo menos durante un tiempo crítico de interrupción).

Por lo tanto, en su caso, debe implementar la interrupción de bare metal directamente en los registros y no confiar en ninguna HAL.

Ejemplo de solución

Por ejemplo, algo como:

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

Nota: esto no cambiará el LED sino que simplemente lo configurará. No hay un interruptor atómico disponible en los GPIO de STM. Tampoco me gusta la construcción if que usé, pero genera un ensamblaje más rápido que mi if (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6)) preferido.

Una variante de alternar podría ser algo en este sentido:

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;
}

El uso de una variable que reside en la RAM en lugar de usar el registro ODR debería ser más rápido, especialmente cuando se usa 72 MHz, porque el acceso a los periféricos puede ser más lento debido a la sincronización entre diferentes dominios de reloj y relojes periféricos que simplemente se ejecutan a una menor frecuencia Por supuesto, no puede cambiar el estado del LED fuera de la interrupción para que la palanca funcione correctamente. O la variable debe ser global (entonces debe usar la palabra clave volatile al declararla) y debe cambiarla en todas partes según corresponda.

También tenga en cuenta que estoy usando C ++, por lo tanto, el tipo bool y no un tipo uint8_t o similar para implementar un indicador. Aunque si la velocidad es su principal preocupación, probablemente debería optar por un uint32_t para la bandera, ya que siempre se alineará correctamente y no generará código adicional al acceder.

La simplificación es posible porque esperamos que sepas lo que estás haciendo y siempre lo mantengas así. Si realmente tiene una única interrupción habilitada para el controlador EXTI9_5, puede deshacerse completamente de la verificación del registro pendiente, reduciendo aún más la cantidad de ciclos.

Esto conduce a otro potencial de optimización: use una línea EXTI que tenga una sola interrupción como una de EXTI1 a EXTI4. Allí no tiene que verificar si la línea correcta ha provocado su interrupción.

    
respondido por el Arsenal
4

Siguiendo la sugerencia de PeterJ, he omitido el uso de SPL. La totalidad de mi código se ve así:

#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)
    {

    }
}

y las instrucciones de montaje tienen este aspecto:

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

Esto mejora las cosas bastante, ya que he logrado obtener una respuesta en ~ 440 ns a 64 MHz (es decir, 28 ciclos de reloj).

    
respondido por el K.R.
3

La respuesta es extremadamente fácil: excelente biblioteca HAL (o SPL). Si haces algo sensible al tiempo, usa en su lugar registros periféricos. Entonces obtendrás la latencia correcta. ¡No puedo entender cuál es el punto de usar esta biblioteca ridícula para cambiar el pin !! o para comprobar el registro de la estatua.

    
respondido por el P__J__
2

Hay algunos errores en su código = el registro BSRR es solo de escritura. No utilice el operador | =, simplemente "=". Se establecerá / restablecerá los pines adecuados. Los ceros se ignoran.

Te ahorrará un par de relojes. Otra sugerencia: mueve tu tabla de vectores & Rutinas de interrupción al CCMRAM. Guardará otras marcas (estados de espera flash, etc.)

PS No puedo comentar porque no tengo suficiente reputación :)

    
respondido por el P__J__

Lea otras preguntas en las etiquetas