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 periférica estándar de la página web ST y el programa actual simplemente alterna un LED en cada borde 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 manejador 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 externo de funciones programables que funciona a 100 Hz. Después de examinar la respuesta de la MCU en un osciloscopio, me sorprendió bastante que nos tome casi 1.32 para que la MCU comience a procesar la interrupción:
Con la MCU funcionando a 72 MHz (he comprobado la salida SYSCLK en el pin MCO de antemano) esto equivale a casi 89 ciclos de reloj. ¿No debería ser la respuesta de MCU a la interrupción mucho más rápida?
PD El código fue compilado con IAR Embedded Workbench y optimizado para la velocidad más alta.
if{}
declaración es necesaria porque la rutina de interrupción no sabe cuál es la fuente de la interrupción.Respuestas:
Problema
Bueno, tienes que mirar las funciones que estás usando, no puedes simplemente hacer suposiciones sobre la velocidad del código que no has visto:
Esta es la función EXTI_GetITStatus:
Como puede ver, esto no es algo simple que requiere solo un ciclo o dos.
Lo siguiente es su función de alternar LED:
Entonces, aquí tiene una indexación de matriz y una lectura, modificación, escritura para alternar el LED.
Los HAL a menudo terminan creando una buena cantidad de sobrecarga porque deben ocuparse de la configuración incorrecta y el uso incorrecto de las funciones. La verificación de parámetros necesarios y también la traducción de un parámetro simple a un bit en el registro pueden requerir una gran cantidad de cómputo (bueno, al menos por una interrupción crítica de tiempo).
Entonces, en su caso, debe implementar su interrupción de metal desnudo directamente en los registros y no confiar en ningún HAL.
Solución de ejemplo
Por ejemplo algo como:
Nota: esto no cambiará el LED sino que simplemente lo configurará. No hay una palanca atómica disponible en los GPIO STM. Tampoco me gusta la
if
construcción que utilicé, pero genera un ensamblaje más rápido que mi preferidoif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))
.Una variante de alternancia podría ser algo así:
El uso de una variable que reside en la RAM en lugar de usar el
ODR
registro debería ser más rápido, especialmente cuando 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 frecuencia más baja. 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 lavolatile
palabra clave al declararla) y debe cambiarla en todas partes en consecuencia.También tenga en cuenta que estoy usando C ++, por lo tanto,
bool
y no algúnuint8_t
tipo o similar para implementar una bandera. Aunque si la velocidad es su principal preocupación, probablemente debería optar por unuint32_t
indicador, ya que siempre estará alineado correctamente y no generará código adicional al acceder.La simplificación es posible porque esperemos que sepa lo que está haciendo y siempre lo mantenga así. Si realmente solo tiene una interrupción habilitada para el controlador EXTI9_5, puede deshacerse de la verificación de registro pendiente por completo, reduciendo aún más el número 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 activado su interrupción.
fuente
volatile
el compilador no puede optimizar mucho en las funciones anteriores y si las funciones no se implementan en línea en el encabezado, la llamada generalmente tampoco se optimiza.Siguiendo la sugerencia de PeterJ, he omitido el uso de SPL. La totalidad de mi código se ve así:
y la instrucción de montaje se ve así:
Esto mejora bastante las cosas, ya que he logrado obtener una respuesta en ~ 440 ns @ 64 MHz (es decir, 28 ciclos de reloj).
fuente
BRR |=
yBSRR |=
a justBRR =
yBSRR =
, esos registros son solo de escritura, su código los lee, registraORR
el valor y luego escribe. eso podría optimizarse para una solaSTR
instrucción.La respuesta es extremadamente fácil: gran biblioteca HAL (o SPL). Si hace algo sensible al tiempo, utilice registros periféricos desnudos. Entonces obtendrás la latencia correcta. No puedo entender cuál es el punto de usar esta biblioteca ridícula para alternar el pin. o para verificar el registro de la estatua.
fuente
Hay algunos errores en su código = el registro BSRR es solo de escritura. No utilice el operador | =, simplemente "=". Establecerá / restablecerá los pines adecuados. Los ceros son ignorados.
Le ahorrará un par de relojes. Otra pista: mueva su tabla de vectores e interrumpa las rutinas a CCMRAM. Guardará algunas otras marcas (estados de espera flash, etc.)
PD: No puedo comentar porque no tengo suficiente reputación :)
fuente