STM32F303 MCU 上的中断延迟

电器工程 stm32 中断 潜伏 stm32f3 gpio-外部中断
2022-01-18 00:07:30

我正在研究一个涉及必须响应外部中断的 STM32 MCU(确切地说是在 STM32303C-EVAL 板上)的项目。我希望对外部中断的反应尽可能快。我从 ST 网页修改了一个标准外设库示例,当前程序只是在 PE6 上的每个连续上升沿切换一个 LED:

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

中断处理程序如下所示:

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

在这种特殊情况下,中断由以 100 Hz 运行的外部可编程函数发生器创建。在示波器上检查了 MCU 的响应后,我很惊讶 MCU 需要将近 1.32 us 才能开始处理中断: 在此处输入图像描述

MCU 以 72 MHz 运行(我事先检查了 MCO 引脚上的 SYSCLK 输出),这相当于将近 89 个时钟周期。MCU对中断的响应不应该快得多吗?

PS 代码使用 IAR Embedded Workbench 编译并针对最高速度进行了优化。

4个回答

问题

那么你必须看看你正在使用的功能,你不能只对你没有看过的代码的速度做出假设:

这是 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;

}

正如你所看到的,这不是一件简单的事情,只需要一两个周期。

接下来是您的 LED 切换功能:

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

所以在这里你有一些数组索引和一个读取修改写入来切换 LED。

HAL 通常最终会产生大量开销,因为它们必须注意错误的设置和函数的错误使用。所需的参数检查以及从简单参数到寄存器中的位的转换可能需要大量的计算(至少对于时间关键的中断来说是这样)。

因此,在您的情况下,您应该直接在寄存器上实现中断裸机,而不是依赖任何 HAL。


示例解决方案

例如:

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

注意:这不会切换 LED,而只是设置它。STM GPIO 上没有可用的原子切换。我也不喜欢if我使用的构造,但它生成的组装速度比我喜欢的要快if (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))

切换变体可能是这样的:

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

使用驻留在 RAM 中的变量而不是使用ODR寄存器应该会更快,尤其是当您使用 72 MHz 时,因为不同时钟域之间的同步以及仅以较低频率运行的外围时钟,对外围设备的访问可能会更慢。当然,您可能不会在中断之外更改 LED 的状态以使切换正常工作。或者变量必须是全局的(然后你必须volatile在声明它时使用关键字)并且你必须相应地在任何地方更改它。

另请注意,我使用的是 C++,因此bool不是某种uint8_t类型或类似的实现标志。尽管如果速度是您最关心的问题,您可能应该为标志选择 a uint32_t,因为它总是正确对齐并且在访问时不会生成额外的代码。

简化是可能的,因为您希望知道自己在做什么并始终保持这种状态。如果您真的只为 EXTI9_5 处理程序启用了一个中断,您可以完全摆脱挂起的寄存器检查,从而进一步减少周期数。

这导致了另一个优化潜力:使用具有单个中断的 EXTI 线,例如 EXTI1 到 EXTI4 中的一个。在那里,您不必检查正确的线路是否触发了您的中断。

按照 PeterJ 的建议,我省略了 SPL 的使用。我的整个代码如下所示:

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

    }
}

汇编指令如下所示:

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

这大大改善了问题,因为我设法在 ~440 ns @ 64 MHz(即 28 个时钟周期)内得到响应。

您的代码中有一些错误 = BSRR 寄存器是只写的。不要使用 |= 运算符,只需简单的“=”。它将设置/重置正确的引脚。零被忽略。

它将为您节省几个时钟。另一个提示:将您的向量表和中断例程移动到 CCMRAM。您将保存一些其他滴答声(闪存等待状态等)

PS我不能发表评论,因为我没有足够的声誉:)

答案非常简单:很棒的 HAL(或 SPL)库。如果您做一些时间敏感的事情,请改用裸外设寄存器。然后你会得到正确的延迟。我不明白使用这个荒谬的库来切换引脚有什么意义!!或检查雕像登记。