Прерывания в STM32: NVIC, приоритеты и внешние линии EXTI
Как устроены прерывания STM32 на уровне микроконтроллера: сколько каналов у STM32F103 и STM32F407 и где их номера, как работают приоритеты и что выставляет HAL, как подключить вывод к линии EXTI, какие векторы общие для нескольких источников, почему флаг сбрасывают в начале обработчика и чем событие отличается от прерывания.
Обновлено Редакция GAW
Прерывание — запрос периферии, по которому ядро откладывает текущую работу и выполняет обработчик. В STM32 прерываниями управляет контроллер NVIC ядра Cortex-M, а внешние сигналы с выводов попадают в него через контроллер EXTI. Устройство NVIC, приоритеты, маски и кадр стека подробно разобраны в статье об архитектуре Cortex-M3; здесь — то, что зависит от микроконтроллера: номера каналов, линии EXTI, общие векторы, настройки HAL и типичные ошибки на STM32F1 и STM32F4.
Коротко
- Каналы: 43 у STM32F103x8/B, 82 у STM32F405/407; номера — константы xxx_IRQn в заголовке CMSIS.
- Приоритеты: 4 бита, 16 уровней, меньшее число важнее; по умолчанию у всех 0.
- HAL: группа NVIC_PRIORITYGROUP_4 — 16 уровней вытеснения без подприоритетов, SysTick с самым низким приоритетом 15.
- EXTI: 16 линий для выводов (линия n — вывод n одного порта) и линии внутренних событий; у STM32F1 до 20 линий, у STM32F4 — 23.
- Общие векторы: EXTI9_5 и EXTI15_10, у STM32F103 ещё USB и CAN, у STM32F407 — TIM1 и TIM9–TIM11, TIM8 и TIM12–TIM14, TIM6 и ЦАП.
- Флаг источника сбрасывают в начале обработчика — иначе обработчик может вызваться повторно.
Таблица векторов и номера IRQ
После 16 векторов исключений ядра (сброс, NMI, HardFault, SVCall, PendSV, SysTick и другие) в таблице идут каналы периферии. Номер канала IRQn и адрес вектора у семейств совпадают для первых 43 каналов, а дальше у STM32F4 продолжаются новые блоки (RM0008, таблица 63; RM0090, раздел 12.1; заголовки CMSIS stm32f103xb.h и stm32f407xx.h):
| IRQn | STM32F103 | STM32F407 |
|---|---|---|
| 0–5 | WWDG, PVD, TAMPER, RTC, FLASH, RCC | WWDG, PVD, TAMP_STAMP, RTC_WKUP, FLASH, RCC |
| 6–10 | EXTI0–EXTI4 | EXTI0–EXTI4 |
| 11–17 | DMA1_Channel1–7 | DMA1_Stream0–6 |
| 18 | ADC1_2 | ADC (ADC1–ADC3) |
| 19–22 | USB_HP_CAN1_TX, USB_LP_CAN1_RX0, CAN1_RX1, CAN1_SCE | CAN1_TX, CAN1_RX0, CAN1_RX1, CAN1_SCE |
| 23 | EXTI9_5 | EXTI9_5 |
| 24–27 | TIM1_BRK, TIM1_UP, TIM1_TRG_COM, TIM1_CC | TIM1_BRK_TIM9, TIM1_UP_TIM10, TIM1_TRG_COM_TIM11, TIM1_CC |
| 28–30 | TIM2, TIM3, TIM4 | TIM2, TIM3, TIM4 |
| 31–34 | I2C1_EV, I2C1_ER, I2C2_EV, I2C2_ER | то же |
| 35–39 | SPI1, SPI2, USART1, USART2, USART3 | то же |
| 40 | EXTI15_10 | EXTI15_10 |
| 41 | RTC_Alarm | RTC_Alarm |
| 42 | USBWakeUp | OTG_FS_WKUP |
| 43–81 | — | TIM8 и TIM12–TIM14, DMA1_Stream7, FSMC, SDIO, TIM5, SPI3, UART4, UART5, TIM6_DAC, TIM7, DMA2_Stream0–7, Ethernet, CAN2, OTG_FS, USART6, I2C3, OTG_HS, DCMI, RNG, FPU |
Имя обработчика — имя канала с окончанием _IRQHandler: USART1_IRQHandler, EXTI9_5_IRQHandler. Эти имена объявлены в стартовом файле startup_stm32f103xb.s или startup_stm32f407xx.s как «слабые» и по умолчанию ведут в бесконечный цикл. Опечатка в имени не даёт ошибки компиляции — прерывание просто попадает в этот цикл, поэтому имя лучше копировать из стартового файла.
Таблица векторов после сброса лежит в начале Flash-памяти. Загрузчик или программа в другой области переносит её регистром SCB_VTOR (PM0056, раздел 4.4.4).
Общие векторы
Некоторые источники делят один канал, и обработчик должен сам выяснить, кто его вызвал:
- EXTI9_5 и EXTI15_10 — общие для линий 5–9 и 10–15 у обоих семейств; источник показывает регистр EXTI_PR.
- USB и CAN у STM32F103 — каналы 19 и 20 общие. Блоки USB и CAN у STM32F103 к тому же используют одну область памяти на 512 байт и не могут работать одновременно (RM0008, раздел 23.2).
- ADC1_2 у STM32F103 и ADC у STM32F407 — один канал на все АЦП.
- Таймеры у STM32F407: TIM1_BRK с TIM9, TIM1_UP с TIM10, TIM1_TRG_COM с TIM11, TIM8 с TIM12–TIM14, TIM6 с ЦАП.
Приоритеты
У STM32F1 и STM32F4 в байте приоритета реализованы 4 старших бита — 16 уровней, и меньшее значение означает более высокий приоритет. Поле PRIGROUP регистра SCB_AIRCR делит эти биты на приоритет вытеснения и подприоритет: прерывание прерывает обработчик только с более высоким приоритетом вытеснения, а подприоритет решает только очерёдность ожидающих. Если программа ничего не настроила, у всех прерываний приоритет 0 (PM0056, раздел 2.3.5) — они не прерывают друг друга и обслуживаются по номерам.
HAL при инициализации (HAL_Init) выбирает группу NVIC_PRIORITYGROUP_4 — 16 уровней вытеснения без подприоритетов — и настраивает SysTick с приоритетом TICK_INT_PRIORITY, который в шаблонах конфигурации равен 0x0F, то есть самому низкому. Отсюда частая ловушка: обработчик с любым более высоким приоритетом не прерывается SysTick, счётчик тиков HAL стоит, и HAL_Delay или функции с тайм-аутом внутри такого обработчика зависают. Значения для регистров NVIC_IPR и кода CMSIS по заданной группе считает калькулятор приоритетов NVIC.
Функции CMSIS для управления каналами:
| Функция | Что делает |
|---|---|
NVIC_EnableIRQ(IRQn), NVIC_DisableIRQ(IRQn) | разрешить или запретить канал |
NVIC_SetPriority(IRQn, prio) | записать приоритет — число от 0 до 15 при 4 битах |
NVIC_SetPriorityGrouping(group) | записать PRIGROUP |
NVIC_SetPendingIRQ(IRQn), NVIC_ClearPendingIRQ(IRQn) | установить или снять ожидание |
__disable_irq(), __enable_irq() | маска PRIMASK: все прерывания с настраиваемым приоритетом |
__set_BASEPRI(prio << 4) | запрет прерываний с приоритетом prio и ниже |
Для коротких критических секций удобнее BASEPRI, чем полный запрет: важные прерывания, например аварийное отключение ШИМ, продолжают обслуживаться.
Внешние прерывания EXTI
Контроллер EXTI превращает фронт сигнала в прерывание или событие (RM0008, раздел 10.2; RM0090, раздел 12.2). У каждой линии своя маска прерывания (IMR), маска события (EMR), выбор переднего (RTSR) и заднего (FTSR) фронта и бит ожидания в регистре PR.
- Линии 0–15 подключены к выводам: линия n — к выводу n порта, выбранного в регистрах AFIO_EXTICR1–4 у STM32F1 и SYSCFG_EXTICR1–4 у STM32F4 (RM0008, рисунок 21). У STM32F1 перед записью в AFIO_EXTICR нужно включить тактирование AFIO, у STM32F4 — SYSCFG.
- Линии 16 и дальше — внутренние события. У STM32F1: 16 — выход PVD, 17 — будильник RTC, 18 — пробуждение USB, 19 — пробуждение Ethernet (connectivity line). У STM32F4: 16 — PVD, 17 — будильник RTC, 18 — пробуждение USB OTG FS, 19 — Ethernet, 20 — USB OTG HS, 21 — вскрытие и метка времени RTC, 22 — таймер пробуждения RTC.
Порядок настройки линии как прерывания: выбрать порт в EXTICR, выбрать фронты в RTSR и FTSR, открыть маску в IMR и разрешить канал EXTIx в NVIC (RM0008, раздел 10.2.4). Флаг ожидания в EXTI_PR сбрасывают записью единицы. EXTI замечает импульс от 10 нс (DS5319, таблица 38), поэтому механическая кнопка вызывает целую серию прерываний от дребезга — его подавляют программно, например игнорируя фронты в течение нескольких миллисекунд, или RC-фильтром.
Вывод для EXTI остаётся в режиме входа — без подтяжки, с подтяжкой вверх или вниз — и может одновременно использоваться как вход периферии. Регистры описаны на страницах EXTI STM32F1 и STM32F4, выбор порта — на страницах AFIO и SYSCFG, режимы вывода — в статье о GPIO STM32.
События и WFE
Линия EXTI может выдавать не прерывание, а событие (маска EMR). Событие будит ядро из ожидания по команде WFE, но не вызывает обработчик и не ставит бит в EXTI_PR, поэтому после пробуждения ничего сбрасывать не нужно (RM0008, раздел 10.2.3). Второй способ разбудить ядро по WFE — разрешить прерывание в периферии, но не в NVIC, и установить бит SEVONPEND в регистре SCB_SCR; тогда после пробуждения придётся сбросить и флаг периферии, и ожидание в NVIC.
Обработчик: что важно
- Сбрасывайте флаг источника. Прерывание периферии держится, пока установлен её флаг. Ядро снимает состояние ожидания при входе в обработчик, но если флаг остался, после выхода канал снова станет ожидающим, и обработчик выполнится ещё раз (PM0056, раздел 4.3.9). Запись в регистр периферии доходит до неё не мгновенно, поэтому флаг сбрасывают в начале обработчика, а не последней командой; если сброс всё же в конце, после него читают регистр обратно — это дожидается завершения записи.
- Проверяйте, какой источник сработал, если канал общий: несколько флагов одного блока или несколько линий EXTI.
- Не ждите в обработчике. Задержки и опрос флагов надолго блокируют прерывания с меньшим приоритетом; долгую работу переносят в основной цикл по флагу.
- Переменные, общие с основным циклом, объявляют
volatile, а многобайтные значения читают при запрещённых прерываниях или атомарными операциями. - У STM32F4 с FPU обработчик, использующий числа с плавающей точкой, увеличивает кадр стека: ядро сохраняет ещё регистры S0–S15 и FPSCR (PM0214, рисунок 12). По умолчанию сохранение ленивое — место резервируется, а сами регистры записываются, только если обработчик действительно обратится к FPU (бит LSPEN регистра FPCCR). Стек нужно рассчитывать на этот размер.
Примеры на CMSIS
Кнопка на PA0 у STM32F103: замыкает вывод на землю, прерывание по спаду.
#include "stm32f1xx.h"
void button_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN;
/* PA0: input with pull-up (MODE0 = 00, CNF0 = 10, ODR0 = 1) */
GPIOA->CRL = (GPIOA->CRL & ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0)) | GPIO_CRL_CNF0_1;
GPIOA->BSRR = GPIO_BSRR_BS0;
AFIO->EXTICR[0] = (AFIO->EXTICR[0] & ~AFIO_EXTICR1_EXTI0) | AFIO_EXTICR1_EXTI0_PA;
EXTI->FTSR |= EXTI_FTSR_TR0; /* falling edge */
EXTI->IMR |= EXTI_IMR_MR0;
NVIC_SetPriority(EXTI0_IRQn, 5);
NVIC_EnableIRQ(EXTI0_IRQn);
}
volatile uint32_t presses;
void EXTI0_IRQHandler(void)
{
EXTI->PR = EXTI_PR_PR0; /* clear first: write 1 */
presses++;
}
Линия 7 на выводе PB7 у STM32F407, оба фронта, общий вектор EXTI9_5:
#include "stm32f4xx.h"
void pb7_irq_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
/* PB7: input with pull-up (MODER7 = 00 after reset, PUPDR7 = 01) */
GPIOB->PUPDR = (GPIOB->PUPDR & ~GPIO_PUPDR_PUPD7) | GPIO_PUPDR_PUPD7_0;
SYSCFG->EXTICR[1] = (SYSCFG->EXTICR[1] & ~SYSCFG_EXTICR2_EXTI7) | SYSCFG_EXTICR2_EXTI7_PB;
EXTI->RTSR |= EXTI_RTSR_TR7;
EXTI->FTSR |= EXTI_FTSR_TR7;
EXTI->IMR |= EXTI_IMR_MR7;
NVIC_SetPriority(EXTI9_5_IRQn, 6);
NVIC_EnableIRQ(EXTI9_5_IRQn);
}
void EXTI9_5_IRQHandler(void)
{
uint32_t pending = EXTI->PR & (EXTI_PR_PR5 | EXTI_PR_PR6 | EXTI_PR_PR7 | EXTI_PR_PR8 | EXTI_PR_PR9);
EXTI->PR = pending; /* clear exactly the lines being handled */
if (pending & EXTI_PR_PR7) {
/* PB7 changed: read GPIOB->IDR to see the new level */
}
}
Частые ошибки
- Не включено тактирование AFIO или SYSCFG — запись в EXTICR не действует, и линия слушает порт A.
- Сброс флага последней командой обработчика — обработчик вызывается повторно.
- Сброс EXTI_PR записью нуля или через
|=— нули ничего не меняют, а|=сбрасывает все стоящие флаги, включая те, что ещё не обработаны. - Опечатка в имени обработчика — прерывание попадает в обработчик по умолчанию из стартового файла.
- HAL_Delay в прерывании — зависание из-за низкого приоритета SysTick.
- Два вывода с одинаковым номером на EXTI — работает только порт, выбранный в EXTICR.
- USB и CAN одновременно на STM32F103 — блоки делят память и векторы.
Частые вопросы
Сколько прерываний у STM32F103 и STM32F407?
У STM32F103x8/B 43 канала прерываний периферии (номера IRQ 0–42) и 16 уровней приоритета (DS5319). У STM32F405/407 — 82 канала (RM0090, раздел 12.1.1), у STM32F42x/43x — до 91. Номера каналов — константы xxx_IRQn в заголовке CMSIS устройства, например USART1_IRQn = 37 у обоих семейств.
Почему два вывода с одинаковым номером не могут вызывать прерывания одновременно?
Линий EXTI для выводов шестнадцать, и линия n подключена к выводу n только одного порта — какого, выбирают регистры AFIO_EXTICR у STM32F1 и SYSCFG_EXTICR у STM32F4. Поэтому PA3 и PB3 вместе работать не могут, а PA3 и PB4 могут. Кроме того, линии 5–9 и 10–15 делят по одному вектору — EXTI9_5 и EXTI15_10, и обработчик должен проверить, какой бит EXTI_PR установлен.
Почему HAL_Delay зависает в обработчике прерывания?
HAL отсчитывает время прерыванием SysTick, а HAL_Init даёт ему приоритет TICK_INT_PRIORITY = 0x0F — самый низкий при 4 битах приоритета. Обработчик с более высоким приоритетом SysTick не прерывает, счётчик тиков стоит, и HAL_Delay ждёт вечно. В прерываниях задержек избегают; если без неё не обойтись, приоритет SysTick должен быть выше, чем у обработчика.
Почему обработчик прерывания вызывается дважды?
Прерывание периферии держится, пока установлен флаг. Ядро снимает состояние ожидания при входе в обработчик, но если флаг сброшен последней командой, запись может не успеть дойти до периферии к моменту выхода, и NVIC снова увидит запрос (PM0056, раздел 4.3.9). Сбрасывайте флаг в начале обработчика или после сброса прочитайте регистр обратно.
Источники
- STMicroelectronics. RM0008 Rev 21. Reference manual: STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx — 10.1 — NVIC: 68 каналов у старших линеек, 16 уровней, SysTick_CALIB = 9000; таблицы 61 и 63 — векторы; 10.2 — EXTI: линии, события и WFE, порядок настройки, подключение выводов (рисунок 21), линии 16–19; 23.2 — USB и CAN делят память
- STMicroelectronics. RM0090 Rev 22. Reference manual: STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 — 12.1.1 — 82 канала у F405/407, до 91 у F42x/43x; 12.2 — EXTI на 23 линии, линии 16–22
- STMicroelectronics. PM0056 Rev 7. STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3 programming manual — 2.3.5 — приоритеты по умолчанию равны 0; 4.3.9 — прерывания по уровню и по импульсу, повторный вход при несброшенном запросе; 4.4.4 — VTOR
- STMicroelectronics. PM0214 Rev 10. STM32 Cortex-M4 MCUs and MPUs programming manual — рисунок 12 — кадр стека Cortex-M4 с регистрами S0–S15 и FPSCR; регистр FPCCR, биты ASPEN и LSPEN
- STMicroelectronics. DS5319 Rev 20. STM32F103x8, STM32F103xB datasheet — 43 канала прерываний и 16 уровней приоритета; таблица 38 — наименьший импульс для EXTI 10 нс
- STMicroelectronics. STM32CubeF1 и STM32CubeF4: CMSIS-заголовки и HAL — перечни IRQn_Type в stm32f103xb.h и stm32f407xx.h; startup_stm32f103xb.s — слабые обработчики, ведущие в Default_Handler с бесконечным циклом; HAL_Init — NVIC_PRIORITYGROUP_4 и HAL_InitTick(TICK_INT_PRIORITY), TICK_INT_PRIORITY = 0x0F в шаблонах stm32f1xx_hal_conf и stm32f4xx_hal_conf
Нашли ошибку?
Смотрите также
- Приоритеты прерываний NVICPRIGROUP, вытеснение и подприоритет, NVIC_IPR, AIRCR, bit-band
- Cortex-M3Регистры, карта памяти, bit-banding, NVIC, SysTick, MPU, старт, пример
- GPIO STM32Режимы выводов, регистры F1 и F4, BSRR, AF, скорость, токи, 5 В, код
- Таймеры STM32Тактирование, PSC/ARR, ШИМ, мёртвое время, захват, энкодер, OPM, синхронизация
- UART STM32BRR и OVER8, допуск по частоте, флаги, прерывания, DMA с IDLE, полудуплекс, RS-485
- STM32F1Cortex-M3: F100, F101, F102, F103, F105/107 — линейки и распиновки
- STM32F4Cortex-M4 с FPU: от F401 и F411 до F429 и F469 — линейки и распиновки