
Прерывания – это аппаратный механизм, позволяющий микроконтроллеру (МК) мгновенно реагировать на внешние или внутренние события без постоянного опроса регистров. В отличие от программного поллинга, где процессор тратит такты на проверку флагов, прерывания освобождают ресурсы для выполнения основной задачи. Например, в STM32F407 тактовая частота ядра достигает 168 МГц, но при поллинге UART на скорости 115200 бод процессор теряет до 10% производительности на ожидание данных. Прерывания решают эту проблему, активируясь только при поступлении байта в буфер.
Каждое прерывание имеет вектор – адрес в памяти, где хранится указатель на соответствующую функцию-обработчик (ISR). В ARM Cortex-M векторная таблица начинается с адреса 0x00000000 и содержит до 240 векторов для периферии и системных событий. При срабатывании прерывания процессор сохраняет контекст (регистры PC, LR, xPSR) в стеке, выполняет ISR и восстанавливает состояние за 12–16 тактов. Для сравнения: переключение контекста в RTOS занимает 100–300 тактов, что делает прерывания критически важными для задач реального времени.
Типичные источники прерываний: таймеры, UART, SPI, ADC, внешние пины (EXTI). Например, в AVR ATmega328P прерывание по изменению состояния пина INT0 срабатывает за 4 такта после события, тогда как программный опрос требует минимум 10 тактов на одну проверку. Однако неконтролируемое использование прерываний приводит к джиттеру – задержкам в выполнении основного кода. Чтобы минимизировать джиттер, рекомендуется:
- Ограничивать длительность ISR до 50–100 тактов (например, в STM32 – до 10 мкс при 100 МГц).
- Использовать буферы FIFO для данных (например, HAL_UART_Receive_IT в STM32).
- Отключать вложенные прерывания (PRIMASK в Cortex-M) для критичных секций.
- Приоритизировать прерывания: в Cortex-M4 доступно 8–256 уровней приоритета (регистр NVIC_IPR).
Неправильная настройка приоритетов – частая причина зависаний. Например, если прерывание UART (низкий приоритет) будет обрабатываться дольше, чем срабатывает таймер SysTick (высокий приоритет), система потеряет тики системного времени. В таких случаях помогает группировка приоритетов (регистр SCB_AIRCR в Cortex-M) или перенос обработки в основной цикл с помощью флагов. Для отладки рекомендуется использовать инструменты трассировки, такие как SEGGER SystemView или встроенный ETM в Cortex-M7.
Прерывания в микроконтроллерах: как работают и зачем нужны
Зачем нужны прерывания? Без них МК вынужден постоянно опрашивать периферийные устройства (кнопки, датчики, UART), что приводит к неэффективному расходу ресурсов и задержкам. Допустим, датчик температуры обновляет данные каждые 10 мс. При опросе в цикле МК тратит до 90% времени на проверку, тогда как с прерыванием он реагирует только в момент изменения данных, освобождая процессор для других задач. Это критично для систем реального времени, где задержка в 1 мс может нарушить работу.
Типы прерываний делятся на внешние (например, нажатие кнопки на пине GPIO) и внутренние (переполнение таймера, завершение передачи по SPI). В AVR-микроконтроллерах (например, ATmega328) внешние прерывания настраиваются через регистры EICRA и EIMSK, где указывается фронт сигнала (нарастающий, спадающий или оба). Внутренние прерывания генерируются периферией автоматически: например, модуль ADC завершает преобразование и вызывает ISR, где можно считать результат без ожидания в цикле.
При написании обработчиков прерываний важно соблюдать два правила: минимизировать время выполнения ISR и избегать блокирующих операций. Длительная работа в ISR приводит к пропуску других прерываний или зависанию системы. Например, если в обработчике прерывания от UART выполнять сложные вычисления, МК может пропустить следующий байт данных. Решение – ставить флаг в ISR и обрабатывать данные в основном цикле. Также критично правильно настраивать приоритеты прерываний: в ARM Cortex-M (STM32) используется NVIC, где прерываниям назначаются уровни приоритета от 0 (высший) до 255.
Как микроконтроллер реагирует на внешние сигналы без постоянного опроса
Для настройки прерывания необходимо сконфигурировать несколько регистров. В микроконтроллерах STM32, например, требуется задать источник прерывания (GPIO, таймер, UART), чувствительность к фронту (EXTI_RTSR для нарастающего, EXTI_FTSR для спадающего) и приоритет (регистр NVIC_IPR). Пример кода для STM32CubeIDE:
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn);
Ошибка в конфигурации приоритетов может привести к пропуску критических событий или зависанию системы.
Обработчик прерывания должен быть максимально коротким. Его задача – зафиксировать событие и выставить флаг для основной программы. Например, при нажатии кнопки обработчик считывает состояние GPIO и записывает его в глобальную переменную, избегая длительных операций вроде работы с файлами или сложных вычислений. В ARM Cortex-M для этого используют инструкцию __disable_irq() перед критическими секциями, чтобы предотвратить вложенные прерывания.
В системах реального времени критически важно учитывать задержку обработки прерываний. На рисунке ниже показаны временные диаграммы для двух сценариев: с опросом и с прерываниями. При частоте опроса 1 кГц максимальная задержка реакции составит 1 мс, тогда как прерывание обеспечивает реакцию в пределах микросекунд. Однако при высокой частоте внешних событий (например, 100 кГц) может возникнуть насыщение контроллера прерываний, что требует оптимизации или использования DMA.
При проектировании системы с прерываниями необходимо учитывать конфликты доступа к общим ресурсам. Если основная программа и обработчик прерывания работают с одной переменной, используйте атомарные операции или механизм семафоров. В FreeRTOS для этого применяют функции taskENTER_CRITICAL() и taskEXIT_CRITICAL(). Без синхронизации возможны гонки данных, приводящие к некорректным состояниям системы.
Какие события могут вызывать прерывания в типовых микроколлерах
В микроконтроллерах прерывания чаще всего инициируются аппаратными событиями, связанными с периферийными модулями. Например, в STM32 (семейство Cortex-M) прерывания генерируют таймеры при достижении заданного значения счётчика (события *Update*, *Capture*, *Compare*), UART при приёме или передаче байта (флаги *RXNE*, *TC*), а также АЦП по завершении преобразования. В AVR (ATmega328P) аналогичные функции выполняют таймеры T0/T1/T2 (переполнение, совпадение), внешние прерывания INT0/INT1 (по фронту/спаду сигнала) и модули USART/SPI/I2C. Для эффективной работы критически важно правильно настроить регистры управления прерываниями: в STM32 это *NVIC* и *EXTI*, в AVR – *EIMSK* и *PCICR*.
Внешние сигналы – ещё один распространённый источник прерываний. Микроконтроллеры поддерживают как детектирование фронтов (восходящий, нисходящий, оба) на выделенных пинах (например, *PA0* в STM32 или *PD2* в ATmega328P), так и изменение состояния группы пинов (*Pin Change Interrupts* в AVR). В промышленных применениях это позволяет реагировать на нажатия кнопок, срабатывание датчиков (конечные выключатели, датчики Холла) или сигналы от других устройств без постоянного опроса портов. Для минимизации ложных срабатываний рекомендуется использовать фильтрацию шумов: аппаратную (в STM32 – *EXTI* с настройкой *Rising/Falling Trigger*) или программную (проверка стабильности сигнала в обработчике прерывания).
Системные события, такие как сброс, ошибки тактирования или сбои питания, также могут вызывать прерывания. В Cortex-M (STM32, NXP LPC) немаскируемое прерывание *NMI* срабатывает при критических сбоях, например, при обнаружении ошибки контрольной суммы памяти (*CRC Fail*). В AVR аналогичную роль играет сброс по сторожевому таймеру (*WDT Reset*), который можно перехватить через вектор прерывания *WDT_vect*. Для отладки полезно использовать прерывания по точкам останова (*Breakpoint Exception* в Cortex-M) или по событию *HardFault*, которое возникает при некорректной работе с памятью или стеком. Настройка приоритетов таких прерываний (через *SCB->SHCSR* в Cortex-M) позволяет обеспечить стабильную работу системы даже в аварийных ситуациях.
Специализированные периферийные модули расширяют список событий для прерываний. В STM32 контроллер DMA генерирует прерывание по завершении передачи данных, что ускоряет обмен с памятью без участия CPU. Модули CAN и Ethernet (в старших моделях) вызывают прерывания при приёме пакета или ошибке связи. В микроконтроллерах с поддержкой USB (например, STM32F103) прерывания возникают при подключении/отключении устройства (*USB_LP_CAN_RX0_IRQn*) или приёме данных по конечной точке. Для работы с такими событиями необходимо учитывать задержки обработки: например, в обработчике прерывания DMA следует сбросить флаг *TCIF* и перезапустить передачу, если требуется циклическая работа.
Как настроить регистры для активации прерываний по таймеру
Настройка прерываний по таймеру требует последовательного конфигурирования нескольких регистров. В большинстве микроконтроллеров (например, STM32, AVR, PIC) процесс начинается с выбора тактового источника. Для STM32 в регистре RCC_APB1ENR или RCC_APB2ENR активируется тактирование таймера (например, бит TIM2EN для TIM2). Без этого шага таймер не получит тактовый сигнал и не будет функционировать.
Далее настраивается предделитель (PSC) и регистр автоперезагрузки (ARR). Формула расчёта частоты прерываний: f_interrupt = f_clock / ((PSC + 1) * (ARR + 1)). Например, при тактовой частоте 80 МГц и значениях PSC = 7999, ARR = 9999 прерывания будут генерироваться каждые 1 секунду. Ошибка в расчётах приведёт к неверной периодичности.
В AVR-микроконтроллерах (например, ATmega328P) настройка таймера отличается. Для таймера Timer1 используются регистры TCCR1A, TCCR1B и TIMSK1. В TCCR1B биты CS10, CS11, CS12 задают предделитель (001 – без предделителя, 101 – делитель на 1024). Бит TOIE1 в TIMSK1 разрешает прерывания по переполнению.
Для активации прерываний необходимо также настроить вектор прерываний. В STM32 вектор таймера TIM2 находится по адресу 0x0000_00B0 (в таблице векторов), а обработчик прерывания должен быть объявлен с атрибутом __attribute__((interrupt)) или макросом IRQHandler. В AVR вектор прерывания по переполнению Timer1 – TIMER1_OVF_vect. Пропуск этого шага приведёт к игнорированию прерываний.
Некоторые микроконтроллеры требуют явного разрешения глобальных прерываний. В AVR это делается установкой бита I в регистре SREG с помощью инструкции sei(). В STM32 используется регистр CPSR (для Cortex-M) или функция __enable_irq(). Без этого прерывания не будут обрабатываться, даже если локальные разрешения установлены.
В регистре управления таймером (CR1 в STM32, TCCR1B в AVR) часто присутствуют дополнительные параметры. Например, в STM32 бит URS (Update Request Source) определяет, будет ли прерывание генерироваться только при переполнении или также при других событиях. Бит CEN (Counter Enable) запускает счётчик. В AVR бит WGM13:0 задаёт режим работы таймера (например, Fast PWM или CTC).
Типичные ошибки при настройке:
- Неправильный расчёт предделителя или
ARR, что приводит к неверной частоте прерываний. - Забытое разрешение тактирования таймера в регистре
RCC(STM32) или неправильный выбор источника тактирования. - Отсутствие глобального разрешения прерываний (
sei()в AVR,__enable_irq()в STM32). - Некорректное объявление обработчика прерывания (например, пропуск атрибута
__attribute__((interrupt))). - Игнорирование флагов прерываний. В STM32 необходимо сбрасывать флаг
UIFв регистреSRвручную, иначе прерывание будет генерироваться повторно.
Почему прерывания снижают нагрузку на процессор по сравнению с поллингом
Поллинг требует постоянного опроса периферийных устройств или регистров состояния, что заставляет процессор выполнять циклы проверки даже в отсутствие изменений. Например, при опросе кнопки с частотой 1 кГц процессор тратит до 10% ресурсов на 8-битном микроконтроллере с тактовой частотой 8 МГц, выполняя бесполезные инструкции. Прерывания же активируются только при возникновении события, освобождая процессор для выполнения полезной работы или перехода в режим энергосбережения.
В системах с поллингом задержка реакции на событие зависит от частоты опроса. Если устройство опрашивается раз в 10 мс, максимальная задержка составит 10 мс, а средняя – 5 мс. Прерывания сокращают эту задержку до микросекунд, так как процессор реагирует немедленно. Для задач с жесткими временными требованиями, например, обработки сигналов энкодера с частотой 10 кГц, поллинг просто неэффективен.
Прерывания позволяют оптимизировать использование тактов процессора. В типичном сценарии обработки UART-данных при скорости 115200 бод поллинг потребует проверки регистра приема каждые 86 мкс. На микроконтроллере STM32 с тактовой частотой 72 МГц это означает потерю ~6200 тактов на каждую проверку. Прерывание же занимает ~50 тактов на вход и выход из обработчика, освобождая остальное время для других задач.
Энергопотребление при поллинге остается высоким, так как процессор постоянно активен. В режиме сна с прерываниями микроконтроллер может снизить потребление с 10 мА до 1 мкА, просыпаясь только при необходимости. Для устройств с батарейным питанием, таких как датчики IoT, это критично: поллинг сокращает срок службы батареи в десятки раз по сравнению с прерываниями.
Прерывания упрощают многозадачность. В системах с RTOS или простыми планировщиками задач поллинг блокирует выполнение других процессов, так как требует выделенного цикла. Прерывания же позволяют обрабатывать события асинхронно, не прерывая основной поток выполнения. Например, в системе сбора данных с АЦП и передачей по SPI прерывания позволяют совмещать оцифровку и передачу без потери производительности.
Для реализации эффективных прерываний рекомендуется минимизировать время выполнения обработчика, вынося сложную логику в основной цикл. Используйте флаги событий или очереди для передачи данных между обработчиком и основной программой. Настройте приоритеты прерываний так, чтобы критичные по времени задачи (например, обработка таймера ШИМ) имели высший приоритет, а менее срочные (например, UART) – низший.
Как правильно сохранять и восстанавливать контекст при обработке прерывания
При возникновении прерывания микроконтроллер должен сохранить текущее состояние процессора – контекст – чтобы вернуться к выполнению основной программы без потерь данных. Контекст включает регистры общего назначения (R0–R15 в ARM Cortex-M), регистр состояния (xPSR), указатель стека (SP) и специальные регистры, такие как LR (Link Register). В большинстве архитектур часть контекста сохраняется автоматически аппаратно: например, в Cortex-M при входе в прерывание на стек помещаются xPSR, PC, LR, R12 и R0–R3. Однако остальные регистры (R4–R11) требуют ручного сохранения, если они используются в обработчике.
Для сохранения контекста применяют два подхода: полное и частичное сохранение. Полное сохранение подразумевает запись всех регистров на стек в начале обработчика и их восстановление перед выходом. Это надежно, но увеличивает задержку обработки. Частичное сохранение оптимизирует процесс: компилятор (например, GCC с флагом `-fomit-frame-pointer`) анализирует используемые регистры и сохраняет только их. В ARM Cortex-M для этого используют инструкции `PUSH {R4–R11}` и `POP {R4–R11}` в начале и конце обработчика соответственно. Важно: если обработчик вызывает функции, компилятор может использовать дополнительные регистры, что требует их включения в список сохраняемых.
Восстановление контекста должно происходить в обратном порядке: сначала извлекаются регистры, сохраненные вручную, затем аппаратно восстановленные. В ARM Cortex-M выход из прерывания осуществляется инструкцией `BX LR` или `POP {PC}`, которая автоматически восстанавливает xPSR, PC и другие регистры. Критическая ошибка – изменение регистра LR внутри обработчика без его восстановления: это приведет к неправильному возврату из прерывания. Для обработчиков с вложенными прерываниями (например, в Cortex-M4 с поддержкой приоритетов) необходимо дополнительно сохранять и восстанавливать регистр BASEPRI, чтобы избежать потери приоритета текущего прерывания.
В системах с ограниченными ресурсами стека (например, в микроконтроллерах с 1–2 КБ RAM) глубина сохраняемого контекста критична. Использование контекстных переключателей (как в RTOS) требует сохранения не только регистров, но и указателя стека задачи. В таких случаях применяют ассемблерные вставки или специальные макросы, например, `portSAVE_CONTEXT` в FreeRTOS для Cortex-M. Для минимизации накладных расходов рекомендуется размещать часто используемые данные в регистрах, не требующих сохранения (например, R0–R3 в Cortex-M), и избегать вызовов функций с большим количеством локальных переменных.
Отладка ошибок контекста требует анализа содержимого стека и регистров. В ARM Cortex-M для этого используют отладчик (например, J-Link) с точкой останова на инструкции возврата из прерывания. Проверяют последовательность сохраненных значений: если регистр R4 на стеке не совпадает с его значением до прерывания, значит, обработчик не восстановил контекст корректно. Для предотвращения таких ошибок рекомендуется использовать компиляторные атрибуты, например, `__attribute__((interrupt))` в GCC, который автоматически генерирует код сохранения/восстановления для всех используемых регистров.
Какие ошибки возникают при неправильной приоритизации прерываний

Неправильная приоритизация прерываний приводит к блокировке критически важных процессов. Например, если прерывание от таймера системных часов (SysTick) получает низкий приоритет, а обработчик UART – высокий, задержки в обновлении системного времени могут достигать сотен микросекунд. Это нарушает работу планировщика задач в RTOS, вызывая джиттер в выполнении потоков или даже пропуск тактов. В системах с жесткими временными требованиями (например, управление двигателями) такие задержки приводят к нестабильной работе или аварийным остановкам.
Гонки данных возникают, когда прерывание с низким приоритетом модифицирует общие ресурсы, а высокоприоритетное прерывание пытается их использовать. Допустим, обработчик SPI с приоритетом 3 обновляет буфер, а обработчик DMA с приоритетом 1 читает его. Если DMA прервет SPI, он получит частично обновленные данные, что приведет к искажению пакетов или потере синхронизации. Решение – либо повысить приоритет SPI, либо использовать атомарные операции или критические секции.
Приоритетные инверсии происходят, когда низкоприоритетное прерывание захватывает мьютекс, а высокоприоритетное блокируется на нем. В худшем случае среднеприоритетное прерывание может бесконечно откладывать выполнение высокоприоритетного. В STM32 с NVIC это решается протоколом приоритетного наследования, но только если он реализован на уровне ОС или драйверов. Без него задержки могут достигать миллисекунд, что недопустимо для систем реального времени.
Несбалансированные приоритеты вызывают перегрузку процессора. Если обработчик внешнего прерывания (EXTI) с высоким приоритетом выполняется слишком долго, он может заблокировать обработку других событий. Например, в контроллере с частотой 80 МГц обработчик, занимающий 10 мкс, при частоте прерываний 100 кГц займет 100% процессорного времени. Рекомендация: разбивать длинные обработчики на части с помощью отложенных прерываний (например, через флаги или очереди) и назначать им разные приоритеты.
Ошибки конфигурации регистров приоритетов – частая причина неочевидных сбоев. В Cortex-M приоритеты задаются 8-битными значениями, но используются только старшие биты (например, 4 бита в STM32F4). Если разработчик установит приоритет 0xFF вместо 0xF0, прерывание получит нулевой приоритет, что эквивалентно блокировке всех остальных. Проверка конфигурации с помощью отладчика (например, чтение регистра NVIC_IPR) должна быть обязательным этапом отладки.
Как обрабатывать вложенные прерывания без потери данных

Вложенные прерывания возникают, когда высокоприоритетное прерывание прерывает выполнение обработчика низкоприоритетного. Чтобы избежать потери данных, микроконтроллер должен сохранять контекст (регистры, флаги, указатель стека) перед входом в новый обработчик. Например, в ARM Cortex-M для этого используется автоматическое сохранение регистров R0–R3, R12, LR, PC и PSR в стеке при входе в прерывание. Однако критические данные, не входящие в этот набор (например, пользовательские переменные), требуют ручного сохранения в начале обработчика и восстановления перед выходом.
Основные правила безопасной обработки вложенных прерываний:
- Отключай прерывания с равным или низшим приоритетом на время работы с разделяемыми данными (например, через
__disable_irq()в CMSIS). - Используй атомарные операции для модификации общих переменных – например,
std::atomicв C++ или инструкции типаLDREX/STREXв ARM. - Минимизируй время выполнения обработчика, вынося длительные операции в основной цикл или планировщик задач.
- Проверяй переполнение стека: вложенные прерывания увеличивают его использование. Для STM32 глубину стека можно оценить через
__get_MSP()и сравнение с границей области.
В системах с динамическим изменением приоритетов (например, FreeRTOS) применяй мьютексы или семафоры с приоритетным наследованием. Это предотвращает инверсию приоритетов, когда низкоприоритетная задача блокирует ресурс, а высокоприоритетная задача (или прерывание) ждёт его освобождения. Для аппаратной поддержки используй контроллеры прерываний с группировкой приоритетов (NVIC в Cortex-M), где регистры IPR позволяют задать до 256 уровней приоритета, а бит PRIMASK глобально маскирует все прерывания, кроме немаскируемых (NMI).
