Почему не срабатывает 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 | Вывод ATmega328P | digitalPinToInterrupt() | Прерывание |
|---|---|---|---|
| D2 | PD2 | 0 | INT0 |
| D3 | PD3 | 1 | INT1 |
| любой другой | — | −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 */ }
}
Остальные причины
- Вход висит в воздухе. Без подтяжки уровень на D2 случаен: прерывание то срабатывает от наводок, то не срабатывает от кнопки. Поможет
INPUT_PULLUPили внешний резистор. - Переменная без volatile. Компилятор вправе держать её копию в регистре и не перечитывать в
loop()— флаг из обработчика «не виден». Почему, объясняет термин volatile. - Не тот режим.
RISINGпри кнопке на землю ловит отпускание, а не нажатие; режимы и их опасности разобраны в статье «Прерывание по фронту и по уровню». - Слишком короткий импульс. Даташит гарантирует прерывание по фронту только для импульса длиннее периода тактовой частоты.
Что делает 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().
Источники
- 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 и ждёт освобождения места
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — разд. 13: внешние прерывания INT0, INT1 и PCINT23…0, срабатывают и при выводе, настроенном на выход; разд. 13.2.1, табл. 13-1 и 13-2: низкий уровень, любое изменение, спад, фронт; импульсы длиннее одного периода тактовой частоты гарантированно вызывают прерывание по фронту, более короткие — нет
Нашли ошибку?
Смотрите также
- attachInterrupt и ISREICRA, EIMSK, EIFR, косвенный вызов, свой ISR и PCINT
- volatileКвалификатор C для регистров и переменных прерываний
- Дребезг контактовСерия переключений вместо одного при нажатии кнопки
- Прерывание по изменению вывода (PCINT)Прерывание AVR при любом изменении уровня на выводе
- Прерывание по фронту и по уровнюДва способа запуска внешнего прерывания: перепадом сигнала или его уровнем