STM32 как и микроконтроллеры других производителей поддерживает прерывания. Каждому прерыванию можно назначить приоритет, что позволяет реализовать вложенные прерывания когда при выполнении одного прерывания возникает другое, более приоритетное, которое приостанавливает выполнение кода текущего прерывания с переходом к выполнению более приоритетного. Спецификация ядра ARM Cortex-M в состав которого входит контроллер прерываний NVIC, допускает до 256 приоритетов, но в STM32 в зависимости от версии ядра их гораздо меньше. Например в Cortex-M3 доступны 16 приоритетов, а в Cortex-M0 только 4. Приоритеты нумеруются от 0 и выше. При этом чем больше число тем меньше приоритет, т. е. приоритет 0 самый высокий.
Рассмотрим возможные источники прерываний STM32 Cortex-M0, список которых можно найти в файле startup_stm32f0xx.s.
.long __StackTop /* Top of Stack */ .long Reset_Handler /* Reset Handler */ .long NMI_Handler /* NMI Handler */ .long HardFault_Handler /* Hard Fault Handler */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long SVC_Handler /* SVCall Handler */ .long 0 /* Reserved */ .long 0 /* Reserved */ .long PendSV_Handler /* PendSV Handler */ .long SysTick_Handler /* SysTick Handler */ // External Interrupts .long WWDG_IRQHandler // Window Watchdog .long PVD_IRQHandler // PVD through EXTI Line detect .long RTC_IRQHandler // RTC through EXTI Line .long FLASH_IRQHandler // FLASH .long RCC_IRQHandler // RCC .long EXTI0_1_IRQHandler // EXTI Line 0 and 1 .long EXTI2_3_IRQHandler // EXTI Line 2 and 3 .long EXTI4_15_IRQHandler // EXTI Line 4 to 15 .long TS_IRQHandler // TS .long DMA1_Channel1_IRQHandler // DMA1 Channel 1 .long DMA1_Channel2_3_IRQHandler // DMA1 Channel 2 and Channel 3 .long DMA1_Channel4_5_IRQHandler // DMA1 Channel 4 and Channel 5 .long ADC1_COMP_IRQHandler // ADC1, COMP1 and COMP2 .long TIM1_BRK_UP_TRG_COM_IRQHandler // TIM1 Break, Update, Trigger and Commutation .long TIM1_CC_IRQHandler // TIM1 Capture Compare .long TIM2_IRQHandler // TIM2 .long TIM3_IRQHandler // TIM3 .long TIM6_DAC_IRQHandler // TIM6 and DAC .long TIM7_IRQHandler // Not all devices!! .long TIM14_IRQHandler // TIM14 .long TIM15_IRQHandler // TIM15 .long TIM16_IRQHandler // TIM16 .long TIM17_IRQHandler // TIM17 .long I2C1_IRQHandler // I2C1 .long I2C2_IRQHandler // I2C2 .long SPI1_IRQHandler // SPI1 .long SPI2_IRQHandler // SPI2 .long USART1_IRQHandler // USART1 .long USART2_IRQHandler // USART2 .long USART3_4_IRQHandler // Not all devices!! .long CEC_IRQHandler // CEC .long USB_IRQHandler // Not all devices!! .long BootRAM // @0x108. This is for boot in RAM mode for // STM32F0xx devices.
Этот список можно разделить на две части, те что до комментария "External Interrupts" и после него. Первые это прерывания ядра, а вторые периферии.
Рассмотрим работу с прерываниями на примере таймера TIM14, который будет генерировать прерывание по переполнению. А в прерывании будем мигать светодиодом.
Схема тестового устройства.
Код функции настройки таймера TIM14.
void TimConfg(void) // Настройка таймера. { TIM_TimeBaseInitTypeDef Tim; NVIC_InitTypeDef NVIC_I; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE); // Включаем тактирование TIM14. Tim.TIM_ClockDivision = TIM_CKD_DIV1; // Выключаем предварительный делитель частоты таймера. Tim.TIM_CounterMode = TIM_CounterMode_Up; // Значение в счетном регистре увеличивается. Tim.TIM_Prescaler = (SystemCoreClock / 1000) - 1; // Делим так чтобы на таймер шел 1 КГц. Tim.TIM_Period = 499; // Таймер будет генерирвать событие 2 раза в секунду. Tim.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM14, &Tim); // Инициализация таймера. // Настройка прерывания от таймера. NVIC_I.NVIC_IRQChannel = TIM14_IRQn; NVIC_I.NVIC_IRQChannelPriority = 3; // Приоритет прерывания (0 - 3. 0 - наивысший, 3 - наименьший). NVIC_I.NVIC_IRQChannelCmd = ENABLE; // Разрешаем прерывание. NVIC_Init(&NVIC_I); TIM_ITConfig(TIM14, TIM_IT_Update, ENABLE); // Разрешаем прерывание таймера по переполнению. TIM_Cmd(TIM14, ENABLE); // Запуск таймера. }
Сперва подается тактирование на таймер. Без этого он работать не будет. Затем значение предделителя устанавливается таким чтобы после него на таймер шла частота 1 КГц. А после значение периода задается равным 499 чтобы таймер переполнялся 2 раза в секунду.
Затем настраивается прерывание. Его приоритет устанавливается самым низким (3), но в данном проекте это не имеет особого значения т. к. нет других прерываний от периферии. Функция NVIC_Init() применяет параметры прерывания заданные в структуре NVIC_I.
Далее разрешается в качестве источника прерываний переполнение таймера и запускается таймер. На этом настройка таймера и прерываний от него завершена и два раза в секунду будет происходить прерывание от таймера. Его обработчиком является функция TIM14_IRQHandler.
void TIM14_IRQHandler(void) // Обработчик прерывания от таймера. { GPIO_TogglePin(GPIOB, GPIO_Pin_1); // Мигаем светодиодом. TIM_ClearITPendingBit(TIM14, TIM_IT_Update); // Сброс флага прерывания по переполнению таймера. }
Почему у нее именно такое имя? Если взглянуть на список прерываний выше, то можно найти TIM14_IRQHandler, которое и определяет имя обработчика. Если ошибиться в имени функции или вообще забыть написать ее в коде, то вместо нее будет использован обработчик прерывания по умолчанию (его код в файле startup_stm32f0xx.s) в котором зациклится работа программы.
В функции TIM14_IRQHandler() вызывается функция GPIO_TogglePin() инвертирующая состояние вывода PB1, к которому подключен светодиод. После чего вызывается функция TIM_ClearITPendingBit() сбрасывающая флаг прерывания по переполнению таймера. Если этого не сделать, то прерывание считается необработанным и оно будет происходить постоянно, т. е. по факту работа программы зациклится в этом прерывании.