Estou trabalhando em um projeto que envolve um STM32 MCU (na placa STM32303C-EVAL para ser exato) que precisa responder a uma interrupção externa. Quero que a reação à interrupção externa seja o mais rápida possível. Modifiquei um exemplo de biblioteca periférica padrão na página da Web ST e o programa atual simplesmente alterna um LED em cada borda ascendente sucessiva do 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);
}
O manipulador de interrupção fica assim:
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);
}
}
Nesse caso em particular, as interrupções são criadas por um gerador de função programável externo funcionando a 100 Hz. Depois de examinar a resposta do MCU em um osciloscópio, fiquei surpreso que levamos quase 1,32 para o MCU começar a processar a interrupção:
Com o MCU rodando a 72 MHz (verifiquei a saída SYSCLK no pino MCO de antemão), isso equivale a quase 89 ciclos de clock. A resposta do MCU à interrupção não deveria ser muito mais rápida?
PS O código foi compilado com o IAR Embedded Workbench e otimizado para maior velocidade.
if{}
declaração é necessária porque a rotina de interrupção não sabe qual é a fonte da interrupção.Respostas:
Problema
Bem, você tem que olhar para as funções que está usando, não pode simplesmente fazer suposições sobre a velocidade do código que não olhou:
Esta é a função EXTI_GetITStatus:
Como você pode ver, isso não é algo simples que requer apenas um ciclo ou dois.
A seguir, sua função de alternância de LED:
Então, aqui você tem alguma indexação de matriz e uma gravação de modificação de leitura para alternar o LED.
Os HALs geralmente acabam criando uma boa quantidade de sobrecarga, porque precisam cuidar de configurações incorretas e do uso incorreto das funções. A verificação de parâmetros necessária e também a conversão de um parâmetro simples para um bit no registro podem levar uma quantidade séria de computação (pelo menos por uma interrupção crítica no tempo).
Portanto, no seu caso, você deve implementar sua interrupção bare metal diretamente nos registros e não confiar em nenhum HAL.
Solução de exemplo
Por exemplo, algo como:
Nota: isso não alterna o LED, mas simplesmente o define. Não há alternância atômica disponível nos GPIOs do STM. Também não gosto da
if
construção que usei, mas gera uma montagem mais rápida que a minha preferidaif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))
.Uma variante de alternância pode ser algo assim:
O uso de uma variável que reside na RAM em vez de o
ODR
registro deve ser mais rápido, especialmente quando você usa 72 MHz, porque o acesso aos periféricos pode ser mais lento devido à sincronização entre diferentes domínios do relógio e relógios periféricos, executando simplesmente em uma frequência mais baixa. Obviamente, você não pode alterar o estado do LED fora da interrupção para que a alternância funcione corretamente. Ou a variável deve ser global (então você precisa usar avolatile
palavra - chave ao declará-la) e precisa alterá-la em qualquer lugar de acordo.Observe também que estou usando C ++, portanto o
bool
e não algumuint8_t
tipo ou similar para implementar um sinalizador. Embora a velocidade seja sua principal preocupação, provavelmente você deve optar por umuint32_t
para o sinalizador, pois ele sempre estará alinhado corretamente e não gerará código adicional ao acessar.A simplificação é possível porque, com sorte, você sabe o que está fazendo e sempre o mantém. Se você realmente tiver apenas uma única interrupção ativada para o manipulador EXTI9_5, poderá se livrar completamente da verificação de registro pendente, reduzindo ainda mais o número de ciclos.
Isso leva a outro potencial de otimização: use uma linha EXTI que tenha uma única interrupção, como uma das EXTI1 a EXTI4. Lá, você não precisa verificar se a linha correta provocou sua interrupção.
fonte
volatile
o compilador não pode otimizar muito nas funções acima e se as funções não forem implementadas em linha no cabeçalho, a chamada também não será otimizada.Seguindo a sugestão de PeterJ, omiti o uso de SPL. A totalidade do meu código fica assim:
e as instruções de montagem são assim:
Isso melhora bastante as coisas, pois eu consegui obter uma resposta em ~ 440 ns a 64 MHz (ou seja, 28 ciclos de clock).
fonte
BRR |=
eBSRR |=
para justBRR =
eBSRR =
, esses registradores são apenas para gravação, seu código os está lendo,ORR
digitando o valor e depois escrevendo. que poderia ser otimizado para uma únicaSTR
instrução.A resposta é extremamente fácil: ótima biblioteca HAL (ou SPL). Se você fizer algo sensível ao tempo, use registros periféricos simples. Então você obterá a latência correta. Eu não consigo entender qual é o objetivo de usar essa biblioteca ridícula para alternar o pino !! ou para verificar o registro da estátua.
fonte
Existem alguns erros no seu código = o registro BSRR é somente gravação. Não use o operador | =, apenas "=". Ele irá definir / redefinir os pinos adequados. Os zeros são ignorados.
Economizará alguns relógios. Outra dica: mova sua tabela de vetores e interrompa as rotinas para o CCMRAM. Você salvará alguns outros carrapatos (flash waitstates etc)
PS Não posso comentar, pois não tenho reputação suficiente :)
fonte