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

Почему не срабатывает attachInterrupt на Arduino?

Обновлено Редакция GAW

У attachInterrupt() нет кода ошибки: если что-то не так, она просто ничего не делает, и обработчик никогда не вызывается. Исходник ядра 1.8.8 объясняет, почему.

Номер прерывания, а не вывода

Первый аргумент — номер внешнего прерывания. Всё тело функции в WInterrupts.c обёрнуто условием interruptNum < EXTERNAL_NUM_INTERRUPTS, а для ATmega328P эта константа равна 2. Вызов attachInterrupt(2, isr, FALLING) с мыслью «вывод D2» передаёт номер 2 и молча возвращается. Перевод делает макрос из pins_arduino.h:

Вывод UnoВывод ATmega328PdigitalPinToInterrupt()Прерывание
D2PD20INT0
D3PD31INT1
любой другой—−1 (NOT_AN_INTERRUPT)через attachInterrupt недоступно
volatile bool pressed = false;           // changed in the ISR, read in loop()

void onButton() { pressed = true; }

void setup() {
  pinMode(2, INPUT_PULLUP);              // defined level when the button is open
  attachInterrupt(digitalPinToInterrupt(2), onButton, FALLING);
}

void loop() {
  if (pressed) { pressed = false; /* handle the press */ }
}

Остальные причины

  1. Вход висит в воздухе. Без подтяжки уровень на D2 случаен: прерывание то срабатывает от наводок, то не срабатывает от кнопки. Поможет INPUT_PULLUP или внешний резистор.
  2. Переменная без volatile. Компилятор вправе держать её копию в регистре и не перечитывать в loop() — флаг из обработчика «не виден». Почему, объясняет термин volatile.
  3. Не тот режим. RISING при кнопке на землю ловит отпускание, а не нажатие; режимы и их опасности разобраны в статье «Прерывание по фронту и по уровню».
  4. Слишком короткий импульс. Даташит гарантирует прерывание по фронту только для импульса длиннее периода тактовой частоты.

Что делает attachInterrupt() с регистрами EICRA и EIMSK и как написать свой ISR(INT0_vect), показано в уроке «attachInterrupt изнутри». Прерывания на остальных выводах дают PCINT, а лишние срабатывания от кнопки — следствие дребезга контактов.

Похожие вопросы

Можно ли повесить прерывание на любой вывод Uno?

Через attachInterrupt — только на D2 и D3. На остальных выводах ATmega328P есть прерывания по изменению уровня PCINT, но ядро Arduino для них функции не даёт: их настраивают регистрами PCICR и PCMSK или сторонней библиотекой.

Почему обработчик срабатывает несколько раз от одного нажатия кнопки?

Контакты кнопки дребезжат: за миллисекунды до установления они замыкаются и размыкаются много раз, а прерывание по фронту ловит каждый импульс длиннее периода тактовой частоты, при 16 МГц — 62,5 нс. Дребезг подавляют RC-цепочкой или программно, игнорируя повторные фронты в течение нескольких миллисекунд.

Можно ли в обработчике вызывать Serial.print и delay?

delay() внутри обработчика не работает: прерывания запрещены, и отсчёт времени стоит. Serial.print при заполненном буфере передатчика не зависнет — ядро в этом случае само опрашивает UART, — но обработчик будет ждать, пока байты уйдут, а на 9600 бит/с это около 1 мс на байт. В обработчике ставят флаг или копируют данные, а печатают уже в loop().

Источники

  1. Arduino. ArduinoCore-avr 1.8.8: cores/arduino/WInterrupts.c, wiring_private.h, HardwareSerial.cpp, variants/standard/pins_arduino.h — attachInterrupt(interruptNum, userFunc, mode): всё тело выполняется только при interruptNum < EXTERNAL_NUM_INTERRUPTS, иначе функция молча возвращается; для ATmega328P EXTERNAL_NUM_INTERRUPTS = 2 (wiring_private.h); pins_arduino.h (standard): digitalPinToInterrupt(p) — 2 → 0, 3 → 1, остальные NOT_AN_INTERRUPT (−1); режим записывается в биты ISC EICRA, прерывание включается в EIMSK; HardwareSerial.cpp, write(): если буфер передатчика полон, а прерывания запрещены, функция сама вызывает обработчик опустошения UDR и ждёт освобождения места
  2. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — разд. 13: внешние прерывания INT0, INT1 и PCINT23…0, срабатывают и при выводе, настроенном на выход; разд. 13.2.1, табл. 13-1 и 13-2: низкий уровень, любое изменение, спад, фронт; импульсы длиннее одного периода тактовой частоты гарантированно вызывают прерывание по фронту, более короткие — нет

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