Использование прерываний на Arduino

Автор Deny, 22:23:05

« предыдущая - следующая »

0 Пользователей и 1 гость просматривают эту тему.

Deny

Оптимизируйте ваши программы для Arduino с помощью прерываний – простого способа для реагирования на события в режиме реального времени!

Мы прерываем нашу передачу...


Как выясняется, существует отличный (но недостаточно часто используемый) механизм, встроенный во все Arduino, который идеально подходит для отслеживания событий в режиме реального времени. Данный механизм называется прерыванием. Работа прерывания заключается в том, чтобы убедиться, что процессор быстро отреагирует на важные события. При обнаружении определенного сигнала прерывание (как и следует из названия) прерывает всё, что делал процессор, и выполняет некоторый код, предназначенный для реагирования на вызвавшую его внешнюю причину, воздействующую на Arduino. После того, как этот код будет выполнен, процессор возвращается к тому, что он изначально делал, как будто ничего не случилось!


Что в этом удивительного, так это то, что прерывания позволяют организовать вашу программу так, чтобы быстро и эффективно реагировать на важные события, которые не так легко предусмотреть в цикле программы. И лучше всего это то, что прерывания позволяют процессору заниматься другими делами, а тратить время на ожидание события.


Прерывания по кнопке


Начнем с простого примера: использования прерывания для отслеживания нажатия кнопки. Для начала, мы возьмем скетч, который вы, вероятно, уже видели: пример «Button», включенный в Arduino IDE (вы можете найти его в каталоге «Примеры», проверьте меню Файл → Примеры → 02. Digital → Button).


const int buttonPin = 2;     // номер вывода с кнопкой
const int ledPin =  13;      // номер вывода со светодиодом


int buttonState = 0;         // переменная для чтения состояния кнопки


void setup()
{
  // настроить вывод светодиода на выход:
  pinMode(ledPin, OUTPUT);
  // настроить вывод кнопки на вход:
  pinMode(buttonPin, INPUT);
}


void loop() {
  // считать состояние кнопки:
  buttonState = digitalRead(buttonPin);


  // проверить нажата ли кнопка.
  // если нажата, то buttonState равно HIGH:
  if (buttonState == HIGH)
  {
    // включить светодиод:
    digitalWrite(ledPin, HIGH);
  }
  else
  {
    // погасить светодиод:
    digitalWrite(ledPin, LOW);
  }
}
В том, что вы видите здесь, нет ничего шокирующего и удивительного: всё, что программа делает снова и снова, это прохождение через цикл loop() и чтение значения buttonPin. Предположим на секунду, что вы хотели бы сделать в loop() что-то еще, что-то большее, чем просто чтение состояния вывода. Вот здесь и пригодится прерывание. Вместо того, чтобы постоянно наблюдать за состоянием вывода, мы можем поручить эту работу прерыванию и освободить loop() для выполнения в это время того, что нам необходимо! Новый код будет выглядеть следующим образом:


const int buttonPin = 2;     // номер вывода с кнопкой
const int ledPin =  13;      // номер вывода со светодиодом


volatile int buttonState = 0;         // переменная для чтения состояния кнопки


void setup()
{
  // настроить вывод светодиода на выход:
  pinMode(ledPin, OUTPUT);
  // настроить вывод кнопки на вход:
  pinMode(buttonPin, INPUT);
  // прикрепить прерывание к вектору ISR
  attachInterrupt(0, pin_ISR, CHANGE);
}


void loop()
{
  // Здесь ничего нет!
}


void pin_ISR()
{
  buttonState = digitalRead(buttonPin);
  digitalWrite(ledPin, buttonState);
}
Циклы и режимы прерываний


Здесь вы заметите несколько изменений. Первым и самым очевидным из них является то, что loop() теперь не содержит никаких инструкций! Мы можем обойтись без них, так как вся работа, которая ранее выполнялась в операторе if/else, теперь выполняется в новой функции pin_ISR(). Этот тип функций называется обработчиком прерывания: его работа состоит в том, чтобы быстро запуститься, обработать прерывание и позволить процессору вернуться обратно к основной программе (то есть к содержимому loop()). При написании обработчика прерывания следует учитывать несколько важных моментов, отражение которых вы можете увидеть в приведенном выше коде:


обработчики должны быть короткими и лаконичными. Вы ведь не хотите прерывать основной цикл надолго!
у обработчиков нет входных параметров и возвращаемых значений. Все изменения должны быть выполнены на глобальных переменных.
Вам, наверное, интересно: откуда мы знаем, когда запустится прерывание? Что его вызывает? Третья функция, вызываемая в функции setup(), устанавливает прерывание для всей системы. Данная функция, attachInterrupt(), принимает три аргумента:


вектор прерывания, который определяет, какой вывод может генерировать прерывание. Это не сам номер вывода, а ссылка на место в памяти, за которым процессор Arduino должен наблюдать, чтобы увидеть, не произошло ли прерывание. Данное пространство в этом векторе соответствует конкретному внешнему выводу, и не все выводы могут генерировать прерывание! На Arduino Uno генерировать прерывания могут выводы 2 и 3 с векторами прерываний 0 и 1, соответственно. Для получения списка выводов, которые могут генерировать прерывания, смотрите документацию на функцию attachInterrupt для Arduino;
имя функции обработчика прерывания: определяет код, который будет запущен при совпадении условия срабатывания прерывания;
режим прерывания, который определяет, какое действие на выводе вызывает прерывание. Arduino Uno поддерживает четыре режима прерывания:
RISING – активирует прерывание по переднему фронту на выводе прерывания;
FALLING – активирует прерывание по спаду;
CHANGE – реагирует на любое изменение значения вывода прерывания;
LOW – вызывает всякий раз, когда на выводе низкий уровень.
И резюмируя, наша настройка attachInterrupt() соответствует отслеживанию вектора прерывания 0 (вывод 2), чтобы отреагировать на прерывание с помощью pin_ISR(), и вызвать pin_ISR() всякий раз, когда произойдет изменение состояния на выводе 2.


Volatile


Еще один момент, на который стоит указать: наш обработчик прерывания использует переменную buttonState для хранения состояния вывода. Проверьте определение buttonState: вместо типа int, мы определили его, как тип volatile int. В чем же здесь дело? volatile является ключевым словом языка C, которое применяется к переменным. Оно означает, что значение переменной находится не под полным контролем программы. То есть значение buttonState может измениться и измениться на что-то, что сама программа не может предсказать – в этом случае, пользовательский ввод.


Еще одна полезная вещь в ключевом слове volatile заключается в защите от любой случайной оптимизации. Компиляторы, как выясняется, выполняют еще несколько дополнительных задач при преобразовании исходного кода программы в машинный исполняемый код. Одной из этих задач является удаление неиспользуемых в исходном коде переменных из машинного кода. Так как переменная buttonState не используется или не вызывается напрямую в функциях loop() или setup(), существует риск того, что компилятор может удалить её, как неиспользуемую переменную. Очевидно, что это неправильно – нам необходима эта переменная! Ключевое слово volatile обладает побочным эффектом, сообщая компилятору, что эту переменную необходимо оставить в покое.


Удаление неиспользуемых переменных из кода – это функциональная особенность, а не баг компиляторов. Люди иногда оставляют в коде неиспользуемые переменные, которые занимают память. Это не такая большая проблема, если вы пишете программу на C для компьютера с гигабайтами оперативной памяти. Однако, на Arduino оперативная память ограничена, и вы не хотите тратить её впустую! Даже C компиляторы для компьютеров будут поступать точно так же, несмотря на массу доступной системной памяти. Зачем? По той же причине, по которой люди убирают за собой после пикника – это хорошая практика, не оставлять после себя мусор.


Подводя итоги


Прерывания – это простой способ заставить вашу систему быстрее реагировать на чувствительные к времени задачи. Они также обладают дополнительным преимуществом – освобождением главного цикла loop(), что позволяет сосредоточить в нем выполнение основной задачи системы (я считаю, что использование прерываний, как правило, позволяет сделать мой код немного более организованным: проще увидеть, для чего разработан основной кусок кода, и какие периодические события обрабатываются прерываниями). Пример, показанный здесь, – это самый базовый случай использования прерываний; вы можете использовать для чтения данных с I2C устройства, беспроводных передачи и приема данных, или даже для запуска или остановки двигателя.




Оригинал статьи:


Nash Reilly. Using Interrupts on Arduino
​Не говорите, если это не изменит тишину к лучшему.