Перейти к содержанию

Прерывания в 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):

IRQnSTM32F103STM32F407
0–5WWDG, PVD, TAMPER, RTC, FLASH, RCCWWDG, PVD, TAMP_STAMP, RTC_WKUP, FLASH, RCC
6–10EXTI0–EXTI4EXTI0–EXTI4
11–17DMA1_Channel1–7DMA1_Stream0–6
18ADC1_2ADC (ADC1–ADC3)
19–22USB_HP_CAN1_TX, USB_LP_CAN1_RX0, CAN1_RX1, CAN1_SCECAN1_TX, CAN1_RX0, CAN1_RX1, CAN1_SCE
23EXTI9_5EXTI9_5
24–27TIM1_BRK, TIM1_UP, TIM1_TRG_COM, TIM1_CCTIM1_BRK_TIM9, TIM1_UP_TIM10, TIM1_TRG_COM_TIM11, TIM1_CC
28–30TIM2, TIM3, TIM4TIM2, TIM3, TIM4
31–34I2C1_EV, I2C1_ER, I2C2_EV, I2C2_ERто же
35–39SPI1, SPI2, USART1, USART2, USART3то же
40EXTI15_10EXTI15_10
41RTC_AlarmRTC_Alarm
42USBWakeUpOTG_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). Сбрасывайте флаг в начале обработчика или после сброса прочитайте регистр обратно.

Источники

  1. 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 делят память
  2. 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
  3. STMicroelectronics. PM0056 Rev 7. STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3 programming manual — 2.3.5 — приоритеты по умолчанию равны 0; 4.3.9 — прерывания по уровню и по импульсу, повторный вход при несброшенном запросе; 4.4.4 — VTOR
  4. STMicroelectronics. PM0214 Rev 10. STM32 Cortex-M4 MCUs and MPUs programming manual — рисунок 12 — кадр стека Cortex-M4 с регистрами S0–S15 и FPSCR; регистр FPCCR, биты ASPEN и LSPEN
  5. STMicroelectronics. DS5319 Rev 20. STM32F103x8, STM32F103xB datasheet — 43 канала прерываний и 16 уровней приоритета; таблица 38 — наименьший импульс для EXTI 10 нс
  6. 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

Нашли ошибку?