STM32F303 MCU的中断等待时间


8

我正在一个涉及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我们的时间: 在此处输入图片说明

当MCU以72 MHz运行时(我已经事先检查了MCO引脚上的SYSCLK输出),这总计接近89个时钟周期。MCU对中断的响应是否应该更快?

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


您确定这是开始处理中断的延迟吗?删除if条件并仅切换时会发生什么?
BeB00

@ BeB00该if{}语句是必需的,因为中断例程不知道中断源是什么。
RohatKılıç17年

如果我没记错的话,延迟应该在10-15个周期左右
BeB00

1
是的,但是在实验中将其删除时会怎样?我假设您没有其他不断触发该中断的负载,因此您应该能够更好地了解实际延迟
BeB00

1
确实不应该是一个谜。查看为您的中断函数编译的汇编代码,并参考适当的ARM参考手册,为每条指令
累加

Answers:


8

问题

好吧,您必须查看所使用的功能,不能仅仅假设未查看的代码的速度:

这是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中的一个。在那里,您不必执行检查是否正确的行触发了中断。


1
从C代码很难判断将需要多少指令。我已经看到针对一些指令进行了优化的更大功能,这些指令甚至都没有涉及实际的调用。
德米特里·格里戈列耶夫

1
@DmitryGrigoryev被声明为寄存器,因为volatile上面的函数不允许编译器进行太多优化,并且如果未在标头中内联实现这些函数,则调用通常也不会得到优化。
阿森纳

5

根据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

由于我设法在64 MHz(约28个时钟周期)下在约440 ns的时间内获得了响应,因此这大大改善了问题。


2
将您的BRR |= 和更改BSRR |= 为just BRR = BSRR = ,这些寄存器是只写的,您的代码正在读取它们,ORR读取值,然后写入。可以针对单个STR指令进行优化。
科林

将您的EXTI处理程序和向量移动到CCMRAM
P__J__

3

答案非常简单:出色的HAL(或SPL)库。如果您对时间敏感,请改用裸机外设寄存器。然后,您将获得正确的延迟。我不明白使用这个荒谬的库来切换图钉的意义是什么!或检查雕像登记册。


3

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

这将节省您几个时钟。另一个提示:将向量表和中断例程移至CCMRAM。您将另存一些滴答声(闪存等待状态等)

附注:我没有足够的声誉,所以我无法发表评论:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.