View Categories

Внешние прерывания

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); // имитация загруженной программы
}

Полезные страницы #

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх