Я работаю над проектом, включающим микроконтроллер 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{}
оператор необходим, потому что подпрограмма прерывания не знает, каков источник прерывания.Ответы:
проблема
Что ж, вы должны взглянуть на функции, которые вы используете, вы не можете просто делать предположения о скорости кода, на который вы не обращали внимания:
Это функция EXTI_GetITStatus:
Как видите, это не простая вещь, требующая всего один или два цикла.
Следующая функция переключения светодиодов:
Итак, здесь у вас есть некоторая индексация массива и запись с изменением чтения для переключения светодиода.
HAL часто заканчивают тем, что создают много накладных расходов, потому что они должны заботиться о неправильных настройках и неправильном использовании функций. Проверка необходимого параметра, а также перевод из простого параметра в бит в регистре могут потребовать значительных вычислительных ресурсов (по крайней мере, для прерывания, критичного по времени).
Таким образом, в вашем случае вы должны реализовывать свое прерывистое «голое железо» непосредственно в регистрах и не полагаться на какой-либо HAL.
Пример решения
Например что-то вроде:
Примечание: это не переключит светодиод, а просто установит его. На STM GPIO нет атомарного переключения. Мне также не нравится
if
конструкция, которую я использовал, но она генерирует более быструю сборку, чем моя предпочтительнаяif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))
.Вариант переключения может быть чем-то вроде этого:
Использование переменной, находящейся в ОЗУ, вместо использования
ODR
регистра должно быть быстрее, особенно когда вы используете 72 МГц, потому что доступ к периферийным устройствам может быть медленнее из-за синхронизации между разными тактовыми доменами, а периферийные часы просто работают на более низкой частоте. Конечно, вы не можете изменить состояние светодиода вне прерывания, чтобы тумблер работал правильно. Или переменная должна быть глобальной (тогда вы должны использоватьvolatile
ключевое слово при ее объявлении), и вы должны изменить ее везде соответственно.Также обратите внимание, что я использую C ++, следовательно,
bool
а не какой-тоuint8_t
тип или аналог для реализации флага. Хотя, если скорость является вашей основной задачей, вы, вероятно, должны выбратьuint32_t
флаг, поскольку он всегда будет правильно выровнен и не будет генерировать дополнительный код при доступе.Упрощение возможно, потому что вы, надеюсь, знаете, что делаете, и всегда придерживаетесь этого. Если вы действительно просто включили одно прерывание для обработчика EXTI9_5, вы можете полностью избавиться от ожидающей проверки регистра, еще больше сократив количество циклов.
Это приводит к другому потенциалу оптимизации: используйте линию EXTI, которая имеет одно прерывание, например, от EXTI1 до EXTI4. Там вам не нужно проверять правильность строки, вызвавшей ваше прерывание.
источник
volatile
компилятор, которому не разрешено много оптимизировать в вышеприведенных функциях, и если функции не реализованы встроенными в заголовке, вызов обычно тоже не оптимизируется.Следуя предложению PeterJ, я исключил использование SPL. Весь мой код выглядит так:
и инструкция по сборке выглядит так:
Это немного улучшает ситуацию, так как мне удалось получить ответ в ~ 440 нс при 64 МГц (то есть, 28 тактов).
источник
BRR |=
иBSRR |=
просто,BRR =
иBSRR =
эти регистры только для записи, ваш код читает их,ORR
вводит значение и затем записывает. это можно оптимизировать под однуSTR
инструкцию.Ответ очень прост: отличная библиотека HAL (или SPL). Если вы делаете что-то чувствительное ко времени, используйте вместо этого открытые периферийные регистры. Тогда вы получите правильное время ожидания. Я не могу понять, какой смысл использовать эту нелепую библиотеку для переключения булавки !! или проверить реестр статуй.
источник
В вашем коде есть некоторые ошибки = BSRR регистр только для записи. Не используйте оператор | =, просто "=". Это установит / сбросит правильные контакты. Нули игнорируются.
Это сэкономит вам пару часов. Еще один совет: переместите таблицу векторов и подпрограммы прерывания в CCMRAM. Вы сохраните некоторые другие галочки (флэш-состояния и т. Д.)
PS Не могу комментировать, так как мне не хватает репутации :)
источник