Внешние прерывания
Давайте разберёмся с таким важным механизмом работы микроконтроллера как прерывания. Каких видов они бывают, как ими пользоваться и на практике попробуем использовать внешнее прерывание.
Прерывания — это механизм который реагирует на событие, которое происходит с микроконтроллером. После возникновения события программа запоминает место из основного цикла и уходит в подпрограмму прерывания, так называемый вектор. После выполнения подпрограммы, идёт возврат в точку основного цикла, где она была до прерывания. Тут важно разделять понятия прерывание и событие. Событие вызывает прерывание, в котором в свою очередь происходят какие-то действия. Прерывания используются во множестве мест микроконтроллера, например для реакции на нажатие кнопки,в таймерах, интерфейсах и т.д.
Виды прерываний
В STM32 существует: внутренние, внешние и системные прерывания. Внешние прерывания (EXTI) срабатывают при изменении состояния пинов ввода-вывода, например, нажатии кнопки или активации датчика. Внутренние прерывания генерируются внутренними модулями микроконтроллера, такими как таймеры, UART, ADC и другие, и сигнализируют о завершении операции или наступлении важного события. Системные это встроенные прерывания ядра Cortex-M обеспечивающие базовую работу самой системы и операционной среды микроконтроллера.
Модуль NVIC
В STM32 за управление прерываниями отвечает специальный модуль — NVIC (Nested Vectored Interrupt Controller). Он позволяет не только включать и отключать нужные прерывания, но и задавать им приоритеты, чтобы важные события могли прерывать менее важные. Благодаря NVIC микроконтроллер умеет обрабатывать прерывания «вложено» — одно может прерывать другое, если оно важнее. Настройка происходит через удобные функции HAL, такие как HAL_NVIC_SetPriority() и HAL_NVIC_EnableIRQ(), а если нужна более точная настройка, можно использовать CMSIS или LL-библиотеку.
Понятие “Вектор прерывания”
У каждого прерывания есть свой вектор прерывания. Вектор прерывания — это указатель на функцию, которую микроконтроллер должен вызвать при срабатывании конкретного прерывания. Когда происходит прерывание, STM32 «смотрит» в специальную таблицу — векторную таблицу прерываний, чтобы узнать, куда перейти для обработки прерывания. Для примера возьмём наш микроконтроллер STM32F407VGT6 у него 82 возможных вектора прерываний. Все они есть в таблице в reference manual. Я приведу небольшую часть этой таблицы.

В колонках указан номер, название,описание, адрес по которому находится вектор и тип приоритета. Прерывания имеют приоритет. от 15 до 0. 15 самый слабый приоритет. Если вызывается прерывание с приоритетом выше текущего, то программа уходит в новое прерывание. Если вызывается с приоритетом ниже, ожидает выполнения. В нашем случае мы можем программно задать тип приоритета от 0 до 15.
Шаги для работы с прерываниями.
- Разрешаем глобальные прерывания. Если вы используете HAL то это происходит в функции HAL_Init()
- Настраиваем само устройство ( например таймер ) на работу с прерываниями
- Разрешаем нужное прерывания для этого конкретного устройства
- Создаём функцию обработчик прерывания в самом коде программы
Внешние прерывания при нажатии на кнопку
Рассмотрим работу с внешними прерываниями на примере платы STM32F4 Discovery, построенной на микроконтроллере STM32F407VGT6. У неё на борту уже есть встроенный светодиод и кнопка , что удобно для нашего примера. Логика работы следующая. При нажатии кнопки будет срабатывать внешнее прерывание. В нём мы запоминаем состояние кнопки. Затем запускаем счетчик таймера на 10 мс. Как только произойдет срабатывание таймера, сравниваем состояние кнопки и если оно такое же как и было то переключаем светодиод. Это гарантирует защиту от случайных нажатий и дребезга.
По шагам разберёмся с настройками. Делаем всё в CubeIDE. Открываем наш файл .ioc
1.Будем использовать внутренний кварцевый генератор.

2.По схеме кнопка подключена на пин PA0. Настроим его для работы с внешними прерываниями. Для этого кликнем по нему и выберем GPIO_EXTI0(работа с прерываниями).

3.Во вкладке GPIO выберем нашу строку с пином. Включим срабатывание по возрастающему фронту. GPIO mode — External Interrupt Mode with Rising edge trigger detection.

4.Вкладка NVIC. Разрешим прерывания по нашей линии Exti line 0.
![]()
5.Настроим встроенный на плате светодиод на выход. Пускай это будет PD12,светодиод зелёного цвета.

6.Настроим наш таймер. Подробнее про таймеры есть в этой статье (ссылка на статью про таймер). Я выбрал таймер 2, в нашем случае не критично, можете выбирать любой.

Такие настройки гарантирую что при частоте 16МГц переполнение произойдет через 10мс.
Не забудьте разрешить прерывания таймера.
![]()
С настройками закончили. Перейдём к коду. Зададим переменную состояния кнопки .
/* USER CODE BEGIN PV */ volatile uint8_t button_state_snapshot = 0; /* USER CODE END PV */
Далее нас интересуют две функции. Функция вызова при внешнем прерывании.
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_0) // Проверяем кнопку PA0
{
button_state_snapshot = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
__HAL_TIM_SET_COUNTER(&htim2, 0); // Сброс счётчика
HAL_TIM_Base_Start_IT(&htim2); // Запуск таймера с прерыванием
}
}
В ней мы проверяем действительно ли нажата именно наша кнопка, запоминаем состояние и запускаем таймер в работу.
Функция обработчик прерывания таймера.
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
uint8_t current_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
if (current_state == button_state_snapshot && current_state == GPIO_PIN_SET)
{
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}
HAL_TIM_Base_Stop_IT(&htim2); // Остановить таймер
}
}
В ней мы считываем текущее состояние кнопки и если оно такое же как и было то переключаем светодиод. Если вам необходимо более быстрое срабатывание кнопки, можно уменьшить время вашего таймера.
На этом мы завершаем. В этом примере мы разобрались с принципом работы прерываний и применили их для управления светодиодом.