Аппаратные прерывания


Забавную картинку к этому уроку я найти не смог, нашёл только какую-то лекцию по программированию, и вот самое начало этой лекции отлично объясняет нам, что такое прерывание. Прерывание в Ардуино можно описать абсолютно точно так же: микроконтроллер “всё бросает”, переключается на выполнение блока функций в обработчике прерывания, выполняет их, а затем возвращается ровно к тому месту основного кода, в котором остановился.

Прерывания бывают разные, то есть не сами прерывания, а их причины: прерывание может вызвать аналогово-цифровой преобразователь, таймер-счётчик или буквально пин микроконтроллера. Такие прерывания называются внешними аппаратными, и именно о них мы сегодня поговорим.

External hardware interrupt – это прерывание, вызванное изменением напряжения на пине микроконтроллера. Основная суть состоит в том, что микроконтроллер (вычислительное ядро) не занимается опросом пина и не тратит на это время, пином занимается другая “железка”. Как только напряжение на пине изменяется (имеется в виду цифровой сигнал, +5 подали/+5 убрали) – микроконтроллер получает сигнал, бросает все дела, обрабатывает прерывание, и возвращается к работе. Зачем это нужно? Чаще всего прерывания используются для детектирования коротких событий – импульсов, или даже для подсчёта их количества, не нагружая основной код. Аппаратное прерывание может поймать короткое нажатие кнопки или срабатывание датчика во время сложных долгих вычислений или задержек в коде, т.е. грубо говоря – пин опрашивается параллельно основному коду. Также прерывания могут будить микроконтроллер из режимов энергосбережения, когда вообще практически вся периферия отключена. Посмотрим, как работать с аппаратными прерываниями в среде Arduino IDE.

Прерывания в Arduino


Начнём с того, что не все пины “могут” в прерывания. Да, есть такая штука, как pinChangeInterrupts, но о ней мы поговорим в продвинутых уроках. Сейчас нужно понять, что аппаратные прерывания могут генерировать только определённые пины:

МК / номер прерывания 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

Как вы поняли из таблицы, прерывания имеют свой номер, который отличается от номера пина. Есть кстати удобная функция digitalPinToInterrupt(pin), которая принимает номер пина и возвращает номер прерывания. Скормив этой функции цифру 3 на Ардуино нано, мы получим 1. Всё по таблице выше, функция для ленивых.

Подключается прерывание при помощи функции attachInterrupt(pin, handler, mode):

  • pin – номер прерывания
  • handler – имя функции-обработчика прерывания (её нужно создать самому)
  • mode – “режим” работы прерывания:
    • LOW (низкий) – срабатывает при сигнале LOW на пине
    • RISING (рост) – срабатывает при изменении сигнала на пине с LOW на HIGH
    • FALLING (падение) – срабатывает при изменении сигнала на пине с HIGH на LOW 
    • CHANGE (изменение) – срабатывает при изменении сигнала (с LOW на HIGH и наоборот)

Также прерывание можно отключить при помощи функции detachInterrupt(pin), где pin – опять же номер прерывания.

А ещё можно глобально запретить прерывания функцией noInterrupts() и снова разрешить их при помощи interrupts(). Аккуратнее с ними! noInterrupts() остановит также прерывания таймеров, и у вас “сломаются” все функции времени и генерация ШИМ.

Давайте рассмотрим пример, в котором в прерывании считаются нажатия кнопки, а в основном цикле они выводятся с задержкой в 1 секунду. Работая с кнопкой в обычном режиме, совместить такой грубый вывод с задержкой – невозможно:

volatile int counter = 0;  // переменная-счётчик

void setup() {
  Serial.begin(9600); // открыли порт для связи

  // подключили кнопку на D2 и GND
  pinMode(2, INPUT_PULLUP); \

  // D2 это прерывание 0
  // обработчик - функция buttonTick
  // FALLING - при нажатии на кнопку будет сигнал 0, его и ловим
  attachInterrupt(0, buttonTick, FALLING);
}

void buttonTick() {
  counter++;  // + нажатие
}

void loop() {
  Serial.println(counter);  // выводим
  delay(1000);              // ждём
}

Итак, наш код считает нажатия даже во время задержки! Здорово. Но что такое volatile? Мы объявили глобальную переменную counter, которая будет хранить количество нажатий на кнопку. Если значение переменной будет изменяться в прерывании, нужно сообщить об этом микроконтроллеру при помощи спецификатора volatile, который пишется перед указанием типа данных переменной, иначе работа будет некорректной. Это просто нужно запомнить: если переменная меняется в прерывании – делайте её volatile.

Ещё несколько важных моментов:

  • Переменные, изменяемые в прерывании, должны быть объявлены как volatile
  • В прерывании не работают задержки типа delay()
  • В прерывании не меняет своё значение millis() и micros()
  • В прерывании некорректно работает вывод в порт (Serial.print()), также не стоит там его использовать – это нагружает ядро
  • В прерывании нужно стараться делать как можно меньше вычислений и вообще “долгих” действий – это будет тормозить работу МК при частых прерываниях! Что же делать? Читайте ниже.

Если прерывание отлавливает какое-то событие, которое необязательно обрабатывать сразу, то лучше использовать следующий алгоритм работы с прерыванием:

  • В обработчике прерывания просто поднимаем флаг
  • В основном цикле программы проверяем флаг, если поднят – сбрасываем его и выполняем нужные действия
volatile boolean intFlag = false;   // флаг

void setup() {
  Serial.begin(9600); // открыли порт для связи

  // подключили кнопку на D2 и GND
  pinMode(2, INPUT_PULLUP);

  // D2 это прерывание 0
  // обработчик - функция buttonTick
  // FALLING - при нажатии на кнопку будет сигнал 0, его и ловим
  attachInterrupt(0, buttonTick, FALLING);
}

void buttonTick() {
  intFlag = true;   // подняли флаг прерывания
}

void loop() {
  if (intFlag) {
    intFlag = false;    // сбрасываем
    // совершаем какие-то действия
    Serial.println("Interrupt!");
  }  
}

Это в принципе всё, что нужно знать о прерываниях, более конкретные случаи мы разберём в продвинутых уроках.

Видео


Важные страницы


  • Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
  • Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
  • Полная документация по языку Ардуино, все встроенные функции и макро, все доступные типы данных
  • Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
  • Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
Последнее обновление Июль 27, 2019