Задержка прерывания на MCU STM32F303


8

Я работаю над проектом, включающим микроконтроллер STM32 (точнее, на плате STM32303C-EVAL), который должен реагировать на внешнее прерывание. Я хочу, чтобы реакция на внешнее прерывание была максимально быстрой. Я изменил пример стандартной периферийной библиотеки с веб-страницы ST, и текущая программа просто включает светодиод на каждом последующем фронте на 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); 
}

Обработчик прерываний выглядит так:

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 Гц. Изучив ответ MCU на осциллографе, я был довольно удивлен, что MCU требуется почти 1,32 мегапикселя для начала обработки прерывания: введите описание изображения здесь

Когда MCU работает на частоте 72 МГц (я предварительно проверил вывод SYSCLK на выводе MCO), это составляет почти 89 тактов. Разве ответ MCU на прерывание не должен быть намного быстрее?

PS Код был скомпилирован с помощью IAR Embedded Workbench и оптимизирован для максимальной скорости.


Вы уверены, что это задержка для начала обработки прерывания? Что происходит, когда вы удаляете условие if и просто переключаетесь?
BeB00

@ BeB00 if{}оператор необходим, потому что подпрограмма прерывания не знает, каков источник прерывания.
Рохат

Если я правильно помню, задержка должна быть около 10-15 циклов
BeB00

1
Правильно, но что происходит, когда вы удаляете это в своем эксперименте? Я предполагаю, что у вас нет множества других прерываний, которые постоянно вызывают это, так что вы сможете лучше понять фактическую задержку
BeB00

1
Это действительно не должно быть загадкой. Посмотрите на скомпилированный ассемблерный код для вашей функции прерывания и обратитесь к соответствующему руководству по ARM, чтобы сложить количество тактов для каждой инструкции ...
brhans

Ответы:


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;

}

Как видите, это не простая вещь, требующая всего один или два цикла.

Следующая функция переключения светодиодов:

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

Итак, здесь у вас есть некоторая индексация массива и запись с изменением чтения для переключения светодиода.

HAL часто заканчивают тем, что создают много накладных расходов, потому что они должны заботиться о неправильных настройках и неправильном использовании функций. Проверка необходимого параметра, а также перевод из простого параметра в бит в регистре могут потребовать значительных вычислительных ресурсов (по крайней мере, для прерывания, критичного по времени).

Таким образом, в вашем случае вы должны реализовывать свое прерывистое «голое железо» непосредственно в регистрах и не полагаться на какой-либо HAL.


Пример решения

Например что-то вроде:

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

Примечание: это не переключит светодиод, а просто установит его. На 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;
}

Использование переменной, находящейся в ОЗУ, вместо использования ODRрегистра должно быть быстрее, особенно когда вы используете 72 МГц, потому что доступ к периферийным устройствам может быть медленнее из-за синхронизации между разными тактовыми доменами, а периферийные часы просто работают на более низкой частоте. Конечно, вы не можете изменить состояние светодиода вне прерывания, чтобы тумблер работал правильно. Или переменная должна быть глобальной (тогда вы должны использовать volatileключевое слово при ее объявлении), и вы должны изменить ее везде соответственно.

Также обратите внимание, что я использую C ++, следовательно, boolа не какой-то uint8_tтип или аналог для реализации флага. Хотя, если скорость является вашей основной задачей, вы, вероятно, должны выбрать 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

Это немного улучшает ситуацию, так как мне удалось получить ответ в ~ 440 нс при 64 МГц (то есть, 28 тактов).


2
Измените ваш BRR |= и BSRR |= просто, BRR = и BSRR = эти регистры только для записи, ваш код читает их, ORRвводит значение и затем записывает. это можно оптимизировать под одну STRинструкцию.
Колин

Переместите ваш обработчик и векторы EXTI в CCMRAM
P__J__

3

Ответ очень прост: отличная библиотека HAL (или SPL). Если вы делаете что-то чувствительное ко времени, используйте вместо этого открытые периферийные регистры. Тогда вы получите правильное время ожидания. Я не могу понять, какой смысл использовать эту нелепую библиотеку для переключения булавки !! или проверить реестр статуй.


3

В вашем коде есть некоторые ошибки = BSRR регистр только для записи. Не используйте оператор | =, просто "=". Это установит / сбросит правильные контакты. Нули игнорируются.

Это сэкономит вам пару часов. Еще один совет: переместите таблицу векторов и подпрограммы прерывания в CCMRAM. Вы сохраните некоторые другие галочки (флэш-состояния и т. Д.)

PS Не могу комментировать, так как мне не хватает репутации :)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.