Как использовать прерывания на Arduino

Как использовать внешние прерывания в Arduino

Начиная работать с Arduino, пройдет не много времени, как вы обнаружите необходимость использования аппаратных прерываний или прерываний по таймеру. Прерывания могут помочь, когда вы хотите, чтобы Arduino выполнял несколько операций одновременно. Прерывания заставят плату Arduino прекратить то, что она делает, для выполнения другой задачи. Как только задача будет завершена, Arduino возобновит то, что делала до того, как была прервана.

В этой статье мы узнаем, как использовать два разных типа прерываний: аппаратные прерывания и прерывания по таймеру. Аппаратные прерывания запускаются внешним событием, таким как нажатие кнопки или сигнал от датчика. Прерывания по таймеру запускаются одним из внутренних таймеров Arduino.

Но прежде чем мы начнем в этом разбираться, давайте сначала рассмотрим пример того, почему прерывания полезны.

Мигающие светодиоды без прерываний

Давайте создадим пример проекта, который пытается управлять мигающим светодиодом с помощью кнопки. В приведенной ниже схеме синий светодиод будет включаться и выключаться постоянно каждые 300 миллисекунд. Желтый светодиод загорится тогда, когда вы нажмете на кнопку.

Для этого нам понадобятся следующие компоненты:

  • Arduino Nano (или другая плата Arduino)
  • Макетная плата
  • Перемычки
  • Кнопка
  • Резистор 330 Ом — 2 шт.
  • Светодиод — 2 шт.

Схема подключения в программе Proteus:

Схема для проверки внешних прерываний Arduino

Собираем на макетной плате:

Вот код для схемы:

int btnPin = 7;      // Кнопка подключена к выводу D7
int btnLED = 11;     // Контакт для желтого светодиода
int blinkLED = 12;   // Контакт для синего светодиода

void setup() {
  pinMode(btnPin, INPUT_PULLUP);   // Настройка входа с подтягивающим резистором
  pinMode(btnLED, OUTPUT);         // Настройка контакта на выход
  pinMode(blinkLED, OUTPUT);       // Настройка контакта на выход
}

void loop() {
  int btnState = digitalRead(btnPin);  // Считывание состояния кнопки

  /* Если кнопка не нажата, то светодиод не должен гореть */
  if (btnState == HIGH) {
    digitalWrite(btnLED, LOW);
  }
  /* Если кнопка нажата, то зажигаем желтый светодиод */
  if (btnState == LOW) {
    digitalWrite(btnLED, HIGH);
  }
  
  /* Помигаем синим светодиодом */
  digitalWrite(blinkLED, HIGH);
  delay(300);
  digitalWrite(blinkLED, LOW);
  delay(300);
}

Если вы соберете эту схему и загрузите код в Ардуино, то увидите, что синий светодиод мигает и гаснет постоянно. А вот когда вы нажимаете кнопку, желтый светодиод иногда загорается, а иногда — нет. Это связано с использованием функции delay(). Когда контроллер вызывает эту функцию, он делает паузу и больше ничего не «хочет» выполнять, до того времени пока не «отсчитает» 300 миллисекунд. Поэтому он пропускает некоторые нажатия на кнопку.

Давайте исправим эту проблему с помощью аппаратного прерывания.

Как создать аппаратное прерывание на Ардуино

Скетч ниже добавляет аппаратное прерывание к приведенному выше скетчу с мигающим светодиодом, так, что каждое нажатие кнопки будет обработано платой Arduino:

int btnPin = 2;          // Контакт для кнопки, который поддерживает прерывания
int btnLED = 11;         // Контакт для светодиода, который будет включаться при нажатии кнопки
int blinkLED = 12;       // Контакт для светодиода, который будет мигать постоянно
volatile int btnState;   // Переменная для хранения состояния кнопки

/* Функция-обработчик прерываний */
void btnInterrupt() {
  btnState = digitalRead(btnPin);   // Считываем состояние кнопки
  if (btnState == HIGH) {           // Если кнопка не нажата
    digitalWrite(btnLED, LOW);      // светодиод выключен
  }
  if (btnState == LOW) {            // Если кнопка нажата
    digitalWrite(btnLED, HIGH);     // светодиод включен
  }
}

void setup() {
  pinMode(btnPin, INPUT_PULLUP);    // Настройка вывода как вход с подтяжкой
  pinMode(btnLED, OUTPUT);          // Настройка вывода как выход
  pinMode(blinkLED, OUTPUT);        // Настройка вывода как выход
  attachInterrupt(digitalPinToInterrupt(btnPin), btnInterrupt, CHANGE);  // Установка внешнего прерывания
}

void loop() {
  /* Мигающий светодиод */
  digitalWrite(blinkLED, HIGH);  
  delay(300);
  digitalWrite(blinkLED, LOW);
  delay(300);
}

Чтобы создать прерывание, вам сначала нужно написать специальную функцию, называемую обработчиком прерываний (ISR). Обработчик прерываний будет содержать весь код, который вы хотите выполнить при срабатывании прерывания. В примере с мигающим светодиодом, приведенном ранее, мы хотели, чтобы нажатия на кнопку управляли желтым светодиодом, в то время как синий светодиод мигал постояно.

В приведенном выше скетче есть функция ISR для нажатия кнопки, называемая btnInterrupt(). Внутри ISR-функции находится код, который считывает состояние кнопки и включает или выключает желтый светодиод.

Обработчик прерываний ISR в Arduino

Функции-обработчики содержат код, который вы хотите выполнить при срабатывании прерывания. Код должен быть максимально коротким и быстрым.

Внимание!


Если вы используете несколько прерываний, имейте в виду, что одновременно может выполняться только одно прерывание.

Функции millis(), micros() и delay() сами зависят от прерываний, поэтому они не будут работать внутри функции-обработчике. Но если вам нужна задержка в ISR-функции, то вы для этого можете использовать функцию delayMicroseconds(). Кроме того, функция Serial.print() не всегда работает внутри ISR-функции, потому что данные передаются на последовательный монитор с использованием прерывания. Обработчики прерываний не могут принимать входные данные или возвращать какие-либо значения.

Если у вас есть какие-либо переменные в обработчике (например, btnState в скетче сверху), перед ними должно стоять ключевое слово volatile. Объявление переменной как volatile предотвращает ее оптимизацию компилятором. В противном случае значение переменной может быть неточным. Ключевое слово volatile гарантирует, что переменная будет обновлена, если она будет изменена в другой части скетча.

Как запустить обработчик прерывания на Ардуино

Чтобы запустить процедуру обслуживания прерываний, необходимо вызвать функцию attachInterrupt() в функции setup(). Функция attachInterrupt() принимает три параметра.

Параметры функции attachInterrupt()

Первый параметр — это номер прерывания. Arduino Nano (также как и Uno) имеет два прерывания: прерывание 0 и прерывание 1. Прерывание 0 подключается к цифровому выводу D2, а прерывание 1 подключается к цифровому выводу D3.

attachInterrupt(0, btnInterrupt, CHANGE);  // Настройка прерывания 0. Жестко указываем его в первом параметре

Чтобы было проще запомнить, какое прерывание к какому выводу подключено, существует функция digitalPinToInterrupt(), которую вы можете использовать вместо номера прерывания. Функция digitalPinToInterrupt() принимает цифровой номер вывода (2 или 3 для вывода 2 или 3 соответственно) вместо номера прерывания в качестве своего параметра.

attachInterrupt(digitalPinToInterrupt(2), btnInterrupt, CHANGE);  // Настройка прерывания 0. В качестве первого параметра передается функция digitalPinToInterrupt с параметром 2 (2 - это контакт D2 платы Arduino)

Таким образом, внешнее прерывание для цифрового контакта D2 устанавливаем вот так:

attachInterrupt(0, btnInterrupt, CHANGE);

Или вот так:

attachInterrupt(digitalPinToInterrupt(2), btnInterrupt, CHANGE);

Для цифрового контакт D3:

attachInterrupt(1, btnInterrupt, CHANGE);

Либо вот так:

attachInterrupt(digitalPinToInterrupt(3), btnInterrupt, CHANGE);

Второй параметр, используемый функцией attachInterrupt(), — это имя функции-обработчика прерывания. В приведенном выше скетче имя функции — btnInterrupt.

Третий параметр функции attachInterrupt() — это режим прерывания. Режим прерывания определяет тип сигнала, который вызовет прерывание. Существует четыре различных режима прерывания:

  • LOW — прерывание будет срабатывать всякий раз, когда на выводе прерывания будет низкий уровень LOW;
  • RISING — прерывание будет срабатывать, когда сигнал переходит из низкого уровня LOW в высокий уровень HIGH;
  • FALLING — прерывание будет вызвано, когда сигнал изменится с высокого уровня HIGH на низкий уровень LOW;
  • CHANGE — прерывание будет срабатывать, когда сигнал переходит либо из высокого уровня HIGH в низкий уровень LOW, либо из низкого уровня LOW в высокий уровень HIGH.

Прерывания по таймеру в Ардуино

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

Таймеры — это электронные схемы, встроенные в микроконтроллер, которые отсчитывают время. Прерывания по таймеру обычно используются для чтения или записи состояния контактов через определенные промежутки времени. Например, можно использовать прерывание по таймеру для получения показаний датчика влажности каждые тридцать секунд.

В Arduino есть три таймера – Timer0, Timer1 и Timer2:

  • Timer0 — 8-битный таймер, используемый для функций delay(), millis() и micros();
  • Timer1 — 16-битный таймер;
  • Timer2 — 8-битный таймер.

Внимание


Не используйте Timer0 для прерываний, иначе это может привести к нарушению функций delay(), millis() и micros(). Вместо этого используйте Timer1 или Timer2.

Все три таймера работают на тактовой частоте Arduino, которая составляет 16 МГц. Каждый такт засчитывается как один тик таймера. Timer1 — это 16-разрядный таймер, что означает, что он может хранить максимум 216 или 65 536 отсчетов, прежде чем он заполнится. Когда он достигает 65 536 отсчетов, таймер сбрасывается на ноль и начинает отсчет снова.

Timer0 и Timer2 являются 8-битными таймерами, поэтому они могут хранить максимум 28 или 256 отсчетов, прежде чем они будут заполнены. Скорость работы таймера можно замедлить, используя предварительно заданное значение — предделитель таймера. Изменяя предварительно значение предделителя, вы можете настроить таймеры на различные отрезки времени.

Использование прерываний по таймеру

Чтобы продемонстрировать, как использовать прерывания таймера, давайте создадим проект с двумя мигающими светодиодами. Зеленый светодиод будет мигать с частотой 500 миллисекунд, а красный светодиод будет мигать с частотой 5 секунд.

Обычный метод мигания светодиодов использует функцию delay(). Но в данном случае мы будем использовать прерывание по таймеру для управления одним из светодиодов.

Вот схема подключения светодиодов к Ардуино для этого эксперимента:

Схема подключения двух светодиодов к ардуино

Чтобы упростить программирование, мы будем использовать библиотеку TimerOne.

Загрузка


Нажмите на кнопку ниже, чтобы загрузить ZIP-архив библиотеки и установить ее на свой компьютер.

Скачать библиотеку TimerOne v.9

Как только библиотека TimerOne будет установлена, можете загрузить этот код на свою плату Arduino:

#include <TimerOne.h>

int ledGreen = 4;
int ledRed = 9;


void setup() {
  pinMode(ledRed, OUTPUT);
  pinMode(ledGreen, OUTPUT);
  
  Timer1.initialize(5000000);    
  Timer1.attachInterrupt(callback);  
}

void callback() {
  digitalWrite(ledRed, !digitalRead(ledRed));
}

void loop() {
  digitalWrite(ledGreen, HIGH);
  delay(500);
  digitalWrite(ledGreen, LOW);
  delay(500);
}

В верхней части скетча мы подключаем библиотеку TimerOne.h. Зеленый светодиод будет мигать каждые 500 миллисекунд. Красный светодиод будет включаться и выключаться каждые 5 секунд. Мы настроим прерывание таймера только для управления красным светодиодом.

В функции setup() мы настраиваем контакты 4 (переменная ledGreen) и 9 (переменная ledRed) в качестве выходов. Контакт 4 (переменная ledGreen) будет использоваться в функции loop() для мигания зеленым светодиодом каждые 500 миллисекунд.

В функции setup() мы используем Timer1.initialize() для инициализации таймера. Аргумент функции initialize() устанавливает время до срабатывания прерывания в микросекундах. В приведенной выше программе аргумент функции initialize() равен 5000000 микросекунд, поэтому прерывание по таймеру будет срабатывать каждые пять секунд. В библиотеке Timer1 минимальный период прерывания составляет 1 микросекунду, а максимальный период прерывания — 8 388 480 микросекунд.

Затем мы указываем функцию-обработчик прерывания с помощью функции Timer1.attachInterrupt(). Функция attachInterrupt() принимает только один аргумент — это имя функции обработчика прерывания. В скетче выше эта функция называется callback(). Таким образом, каждый раз, когда таймер будет достигать пяти секунд, будет запускаться ISR-функция callback().

Код для функции callback() содержит функцию digitalWrite(), которая включает и выключает красный светодиод. Обычно мы использовали бы функцию delay() для включения и выключения светодиода. Но одновременно может выполняться только одна функция обработки прерывания, и ко всему этому сама функция delay() использует прерывание, она не может быть использована внутри другого прерывания.

Поэтому нам нужен другой способ включать и выключать светодиод. Вместо использования функции delay() мы можем считать состояние вывода 9 и использовать его для установки нового состояния. Итак, мы используем !digitalRead(ledRed) в качестве второго параметра функции digitalWrite(). Восклицательный знак ! — это логический оператор для обозначения НЕ (NOT). Это приведет считыванию текущего состояния вывода 9, а затем к установке вывода 9 с противоположным значением. Если вывод 9 находится в высоком состоянии HIGH, то будет установлен низкий уровень LOW, а если на выводе 9 низкий уровень LOW, то будет установлен высокий уровень HIGH.

В функции loop() у нас есть код, который мигает зеленым светодиодом. Здесь, как и обычно, мы мигаем светодиодом с помощью функции delay(), чтобы сделать паузу между установкой высокого и низкого уровней на светодиоде.

Если все работает правильно, зеленый светодиод должен менять свое состояние каждые 500 миллисекунд, а красный светодиод — каждые 5 секунд. Это очень простой пример, но прерывания по таймеру полезны и во многих других случаях. Вместо того, чтобы использовать прерывания для включения светодиода, вы можете использовать его для считывания показаний датчика через определенные промежутки времени.

Вот как работает код в программе Proteus:

Прерывания по таймеру в ардуино
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии