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— любое изменение уровня (LOW→HIGHилиHIGH→LOW)RISING— фронт: переход отLOWкHIGHFALLING— спад: переход от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, другие платы на базе ATmega328 | 2, 3 | |
| UNO R4 Minima, UNO R4 WiFi | 2, 3 | |
| UNO WiFi Rev2, Nano Every | Все цифровые контакты | |
| Mega, Mega 2560, Mega ADK | 2, 3, 18, 19, 20, 21 | контакты 20 и 21 нельзя использовать для прерываний, пока они заняты I²C; на них также есть внешняя подтяжка, которую нельзя отключить |
| Micro, Leonardo | 0, 1, 2, 3, 7 | |
| Zero | 0–3, 5–13, A0–A5 | Контакт 4 нельзя использовать как внешний interrupt |
| MKR boards | 0, 1, 4, 5, 6, 7, 8, 9, A1, A2 | |
| Nano 33 IoT | 2, 3, 9, 10, 11, 13, A1, A5, A7 | |
| Nano 33 BLE, Nano 33 BLE Sense (версии 1 и 2) | все цифровые контакты | |
| Nano RP2040 Connect | 0–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.0 | int.1 | int.2 | int.3 | int.4 | int.5 |
|---|---|---|---|---|---|---|
| UNO, Ethernet | 2 | 3 | ||||
| Mega 2560 | 2 | 3 | 21 | 20 | 19 | 18 |
| На базе 32u4 (например, Leonardo, Micro) | 3 | 2 | 0 | 1 | 7 |
Для плат 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.
Дополнительную информацию о прерываниях см. Заметки Ника Гэммона.