External hardware interrupt - это прерывание, вызванное изменением напряжения на пине МК. Суть в том, что ядро МК не занимается опросом пина и не тратит на это время. Но как только напряжение на пине меняется (цифровой сигнал) - МК получает сигнал, бросает выполнение текущей инструкции, обрабатывает прерывание и возвращается к работе. Подробнее о самом механизме прерываний читай в общем уроке про прерывания.
Чаще всего внешние прерывания используются для детектирования коротких событий - импульсов, или даже для подсчёта их количества, не нагружая основной код. Аппаратное прерывание может поймать короткое нажатие кнопки, поворот энкодера или срабатывание датчика во время сложных долгих вычислений или задержек в коде, т.е. грубо говоря - пин опрашивается параллельно основному коду. Также прерывания могут будить МК из режимов энергосбережения, когда практически вся периферия отключена.
attachInterrupt #
Для подключения внешних прерываний служит функция attachInterrupt(uint8_t interruptNum, void (*handler)(void), uint8_t mode) - подключить внешнее прерывание под номером interruptNum на функцию handler в режиме mode:
- Номер прерывания
interruptNumможет быть получен из номера пина при помощиdigitalPinToInterrupt(). Нумерация пинов и прерываний зависит от платы/МК, см. информацию по конкретной модели handler- функция-обработчик прерывания видаvoid f(), будет вызвана при срабатывании прерыванияmode- режим прерывания. У разных плат/МК могут быть и другие режимы, см. информацию по конкретной моделиLOW- срабатывает всегда, когда на пине низкий сигналCHANGE- срабатывает при смене сигнала на противоположныйRISING- срабатывает при смене сигнала с низкого (LOW) на высокий (HIGH)FALLING- срабатывает при смене сигнала с высокого (HIGH) на низкий (LOW)
AVR Arduino #
На классических Arduino (UNO, Nano, Mega..) номер прерывания для attachInterrupt() не совпадает с номером пина. Более того, не на всех пинах прерывания вообще поддерживаются:
| МК / номер прерывания | INT 0 | INT 1 | INT 2 | INT 3 | INT 4 | INT 5 |
|---|---|---|---|---|---|---|
| ATmega 328/168 (Nano, UNO, Mini) | D2 | D3 | - | - | - | - |
| ATmega 32U4 (Leonardo, Micro) | D3 | D2 | D0 | D1 | D7 | - |
| ATmega 2560 (Mega) | D2 | D3 | D21 | D20 | D19 | D18 |
В то же время, на всех пинах поддерживаются PCINT - Pin Change Interrupts, но фреймворк Arduino не имеет для них удобного инструмента. Можно использовать стороннюю библиотеку PinChangeInterrupt, либо настроить эти прерывания вручную через регистры, как описано например у меня в этом уроке.
// пример для Arduino Nano
void isr1() {}
void isr2() {}
void setup() {
attachInterrupt(0, isr1, CHANGE); // пин 2
attachInterrupt(digitalPinToInterrupt(3), isr2, FALLING); // пин 3 (прерывание номер 1)
}
ESP8266 #
У ESP8266 почти все GPIO (кроме 16) поддерживают attachInterrupt() по номеру GPIO. Функция-обработчик должна быть объявлена с атрибутом IRAM_ATTR:
IRAM_ATTR void myIsr() {}
void setup() {
attachInterrupt(1, myIsr, RISING); // GPIO1
}
На старых версиях ядра - с ICACHE_RAM_ATTR:
void ICACHE_RAM_ATTR myIsr() {}
void setup() {
attachInterrupt(2, myIsr, RISING); // GPIO2
}
Примеры #
Счётчик #
Рассмотрим пример, в котором в прерывании считаются нажатия кнопки (без гашения дребезга), а в основном цикле выводятся с задержкой в 1 секунду, т.е. опрос и счёт нажатий будет выполняться во время задержки. Работая с кнопкой в обычном режиме, совместить такую задержку с опросом кнопки не получится:
volatile int counter = 0; // переменная-счётчик
void btnIsr() {
counter++; // + нажатие
}
void setup() {
Serial.begin(115200);
// подключили кнопку на D3 и GND
pinMode(3, INPUT_PULLUP);
// FALLING - при нажатии на кнопку будет сигнал 0
attachInterrupt(1, btnIsr, FALLING);
}
void loop() {
Serial.println(counter); // выводим
delay(1000); // имитация загруженной программы
}
Прерывание может произойти в любой момент выполнения программы, даже во время передачи переменной в функцию. В нашем случае counter состоит из нескольких байт (тип int), поэтому прерывание может произойти даже посреди передачи частей переменной! Хорошей практикой является отключение прерываний на период чтения переменной, которая может меняться в прерывании, например так:
void loop() {
int buf;
noInterrupts();
buf = counter;
interrupts();
Serial.println(buf); // выводим
delay(1000); // имитация загруженной программы
}
Флаг #
Если событие необязательно обрабатывать сразу, то лучше использовать следующий алгоритм работы с прерыванием:
- В обработчике прерывания просто поднимаем флаг
- В основном цикле программы проверяем флаг, если поднят - сбрасываем его и выполняем нужные действия
volatile bool intFlag = false; // флаг
void btnIsr() {
intFlag = true; // подняли флаг прерывания
}
void setup() {
Serial.begin(115200);
// подключили кнопку на D3 и GND
pinMode(3, INPUT_PULLUP);
// FALLING - при нажатии на кнопку будет сигнал 0
attachInterrupt(1, btnIsr, FALLING);
}
void loop() {
if (intFlag) {
intFlag = false; // сбрасываем
// совершаем какие-то действия
Serial.println("Interrupt!");
}
delay(500); // имитация загруженной программы
}
Дополнительный контент доступен владельцам набора GyverKIT и по подписке, подробнее читай здесь. Блок содержит:
- Дополнительно, Задания
- 1 блоков кода
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками