Главная chevron_right Функции chevron_right Внешние прерывания chevron_right attachInterrupt()

attachInterrupt()

attachInterrupt() назначает функцию-обработчик ISR на внешнее прерывание: микроконтроллер вызовет её автоматически при изменении сигнала на выводе, не дожидаясь следующей итерации loop().

Полная статья

Зачем нужны прерывания

Представьте, что вам нужно отслеживать щелчки поворотного энкодера, кнопку, монету в монетоприёмнике или сигнал от инфракрасного датчика — и при этом делать что-то ещё в основном цикле. Если опрашивать вывод вручную в loop(), короткие импульсы легко пропустить: пока выполняется другой код, сигнал уже пришёл и ушёл.

Внешние прерывания решают эту проблему: как только на выводе меняется уровень сигнала, микроконтроллер немедленно прерывает текущий код, вызывает заранее назначенную функцию-обработчик (ISR) и после её завершения возвращается к прерванному месту. Основная программа при этом не занимается постоянным опросом — она просто продолжает свою работу.

Синтаксис

  • attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)рекомендуемый вариант
  • attachInterrupt(interrupt, ISR, mode) — устаревший, не рекомендуется
  • attachInterrupt(pin, ISR, mode) — устаревший; работает только на платах Arduino SAMD, UNO WiFi Rev2, Arduino Due и 101

Для нового кода всегда используйте первый вариант с digitalPinToInterrupt(pin) — он переносим между платами.

Параметры

  • interrupt — номер прерывания, полученный через digitalPinToInterrupt(). Тип данных: int.
  • pin — номер физического вывода Arduino, к которому подключён сигнал.
  • ISR — функция-обработчик (ISR), которая будет вызвана при срабатывании прерывания. Не принимает параметров и ничего не возвращает.
  • mode — условие срабатывания. Допустимые значения:
  • LOW — вывод удерживается на низком уровне
  • CHANGE — любое изменение уровня (LOWHIGH или HIGHLOW)
  • RISING — фронт: переход от LOW к HIGH
  • FALLING — спад: переход от HIGH к LOW

На платах Arduino Due, Zero и MKR1000 дополнительно доступно:

  • HIGH — вывод удерживается на высоком уровне

Возвращаемое значение

Функция ничего не возвращает.

Пример кода

const byte ledPin = 13;
const byte interruptPin = 2;  // входной пин, к которому будет привязано прерывание
volatile byte state = LOW;  // переменная, которая будет обновляться в ISR

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
}

void blink() {
  state = !state;
}

Как правильно указывать вывод

Первый аргумент attachInterrupt() — это не номер физического вывода, а номер прерывания. Чтобы не запоминать таблицу соответствий и не писать код, привязанный к конкретной плате, используйте digitalPinToInterrupt(pin): он сам вернёт нужный номер прерывания для любого поддерживаемого вывода.

Например, для вывода 3 первый аргумент attachInterrupt() записывается как digitalPinToInterrupt(3).

ПлатаЦифровые контакты, используемые для прерыванийПримечания
UNO R3, Nano, Mini, другие платы на базе ATmega3282, 3
UNO R4 Minima, UNO R4 WiFi2, 3
UNO WiFi Rev2, Nano EveryВсе цифровые контакты
Mega, Mega 2560, Mega ADK2, 3, 18, 19, 20, 21контакты 20 и 21 нельзя использовать для прерываний, пока они заняты I²C; на них также есть внешняя подтяжка, которую нельзя отключить
Micro, Leonardo0, 1, 2, 3, 7
Zero0–3, 5–13, A0–A5Контакт 4 нельзя использовать как внешний interrupt
MKR boards0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Nano 33 IoT2, 3, 9, 10, 11, 13, A1, A5, A7
Nano 33 BLE, Nano 33 BLE Sense (версии 1 и 2)все цифровые контакты
Nano RP2040 Connect0–13, A0–A5
Nano ESP32все цифровые контакты
GIGA R1 WiFiвсе цифровые контакты
Dueвсе цифровые контакты
101все цифровые контактыС CHANGE работают только контакты 2, 5, 7, 8, 10, 11, 12, 13.

Таблица выводов с прерываниями

Набор выводов, поддерживающих прерывания, и их номера отличаются от платы к плате. Именно поэтому прямое указание числа вместо digitalPinToInterrupt() — источник трудноуловимых ошибок при переносе скетча.

В старых скетчах можно встретить прямые номера: 0 (вывод 2) и 1 (вывод 3). Это работало на классических AVR-платах, но на других платах эти числа означают совсем другие выводы.

В таблице ниже номера прерываний — это те значения, которые нужно передать в attachInterrupt(). Обратите внимание: по историческим причинам нумерация не всегда совпадает с нумерацией прерываний в самом чипе ATmega (например, int.0 соответствует INT4 на чипе ATmega2560).

Платаint.0int.1int.2int.3int.4int.5
UNO, Ethernet23
Mega 25602321201918
На базе 32u4 (например, Leonardo, Micro)32017

Для плат UNO WiFi Rev2, Arduino Due, Zero, семейства MKR и 101 номер прерывания совпадает с номером вывода.

Правила написания ISR

ISR — особый вид функции с жёсткими ограничениями:

  • Не принимает параметров и ничего не возвращает.
  • Должна быть максимально короткой и быстрой: пока выполняется ISR, другие прерывания ждут своей очереди.
  • Если в скетче несколько ISR, одновременно работает только одна; остальные выполнятся по очереди после её завершения.
  • millis() не увеличивается внутри ISR, потому что сам зависит от прерываний.
  • delay() внутри ISR не работает по той же причине.
  • micros() поначалу работает, но примерно через 1–2 мс начинает давать непредсказуемые результаты.
  • delayMicroseconds() не использует счётчик прерываний и работает корректно.

Чтобы передавать данные между ISR и основным кодом, используйте глобальные переменные. Любую переменную, которую ISR изменяет, а основной код читает (или наоборот), объявляйте как volatile — иначе компилятор может оптимизировать её чтение и основная программа не увидит обновлённого значения.

Где применяется

  • Поворотный энкодер — подсчёт импульсов на RISING или CHANGE без риска пропустить шаг.
  • Кнопка с обработкой нажатия — реакция на FALLING без постоянного опроса в loop().
  • Монетоприёмник или фотопрерыватель — фиксация короткого импульса, который легко пропустить при опросе.
  • Сигнал готовности от внешнего устройства — например, датчик сообщает о готовности данных через вывод INT.

Практические замечания

  • Всегда используйте digitalPinToInterrupt(pin) вместо прямого числа — это единственный переносимый способ.
  • Объявляйте все переменные, которые меняются в ISR, с ключевым словом volatile.
  • Не вызывайте delay() и не рассчитывайте на корректную работу millis() внутри ISR.
  • Если используете режим LOW, ISR будет вызываться непрерывно, пока вывод остаётся низким — это легко приводит к зависанию. Такой режим нужен только для специфических задач.
  • Последовательные данные (Serial), поступающие во время выполнения ISR, могут быть потеряны — не делайте обработчик длиннее необходимого.
  • Для отладки ISR удобно устанавливать флаг volatile bool внутри обработчика, а уже в loop() реагировать на него и выводить информацию через Serial Monitor.

Дополнительную информацию о прерываниях см. Заметки Ника Гэммона.

Полезные материалы