Горький опыт: прерывания на Arduino

Перепечатка публикации с сайта lourie.info.

Опыт — сын ошибок трудных.

Вот что происходит, если сделать прерывания неправильно

И если вы лишь делаете первые шаги в создании собственных устройств на Arduino, ESP32 или стали счастливым обладателем одного из семейства устройств Troyka, вам будет крайне полезно узнать, какие есть варианты организации взаимодействия с элементами управления (в данном случае — с кнопками) и чем они отличаются друг от друга.

Для начала, давайте разберемся, в чем преимущества прерываний против опроса кнопок в режиме цикла for или while.

Самый простой способ получения информации о состоянии кнопок, это периодически опрашивать их внутри цикла. Например, на MicroPython код непрерывного опроса кнопки будет выглядеть примерно так:

from machine import Pin
import time
while True:
   if Pin(PIN_BUTTON, Pin.IN, Pin.PULL_UP).value() == 1:
      <Делаем что-то с тем, что получили единицу с кнопки>
   time.sleep(0.5)

Преимущество такого подхода, очевидно, в том, что это очень простой код. При желании, конечно, его можно «облагородить», добавив функции и определив константы, но смысл, полагаю, понятен.

Но есть, конечно же, и очевидный же недостаток: мы каждый раз после опроса состояния кнопки ожидаем (в данном примере — полсекунды, что задано в параметре time.sleep(0.5). Неужели нельзя сделать что-то более event-driven, как в мире «больших» компьютеров? Конечно, можно, но для этого придется погрузиться в мир аппаратных прерываний.

Для этого возьмем пример платы Troyka OLED (OLED-дисплей с двумя кнопками, расположенными слева и справа от модуля — на фото выше). Почему выбран экран для примера? Потому что когда я попробовал реализовать опрос кнопок в цикле, мне также приходилось постоянно перерисовывать экран. Выглядело это… не очень, поверьте!.

При подключении их к Troyka Slot Shield (это плата на базе Arduino Uno, поэтому C# код, приведенный ниже, предназначен для компиляции в Arduino IDE) кнопки оказываются подключенными к цифровым входам 6 и 7. Нам понадобится эта информация для определения пинов:

#include 
// создаём объект для работы с дисплеем
// и передаём I²C адрес дисплея
TroykaOLED myOLED(0x3C);
//Зададим пины кнопок
const byte LEFT_BUTTON = 7;
const byte RIGHT_BUTTON = 6;
//Initializing the button states as 1 (HIGH)
volatile byte left_state = HIGH;
volatile byte right_state = HIGH;

Обратите внимание на декларацию volatile — именно она поясняет компилятору, что данный параметр будет изменяться во время обработки прерываний.

Далее, необходимо определить функции обработки прерываний. У нас их две, для левой и правой кнопки, и в обоих мы будем читать значение соответствующей переменной:

void rightButtonISR(){
  right_state = digitalRead(RIGHT_BUTTON);
  }

void leftButtonISR(){
  left_state = digitalRead(LEFT_BUTTON);
  }

Внутри setup() стартуем последовательный интерфейс вызовом функции Serial.begin() и добавляем код, подключающий определенные нами функции обработки прерываний к соответствующим пинам с помощью функции attachInterrupt():

void setup() {
  Serial.begin(9600);
  pinMode(RIGHT_BUTTON, INPUT);
  attachInterrupt(RIGHT_BUTTON, rightButtonISR, FALLING);
  pinMode(LEFT_BUTTON, INPUT);
  attachInterrupt(LEFT_BUTTON, leftButtonISR, FALLING);
}

Параметр FALLING определяет срабатывание прерываний при снижении напряжения, снимаемого с кнопки (это нюанс конкретно работы данного модуля Troyka, где кнопка находится в нормально замкнутом положении — т.е. размыкается при нажатии, у вашего модуля могут быть свои «заморочки»). В зависимости от ваших задач вы можете использовать альтернативные значения этого параметра: LOW (прерывание вызывается при достижении низкого уровня напряжения), CHANGE (прерывание вызывается при любом изменении напряжения), RISING (прерывание вызывается при росте напряжения на входе, к которому присоединена функция прерывания). Внутри основной функции loop() мы уже будем работать с состояниями для левой и правой кнопки, переданными нам из обработчиков прерываний (где мы вызывали процедуру digitalRead()):

void loop() {
  if (right_state != HIGH)
  {
    //тут должен быть ваш код, что-то делающий, когда нажата правая кнопка
    right_state = HIGH;

    //возвращаем состояние кнопки в исходное значение
    }
  if (left_state != HIGH)
  {
    //код, что-то делающий после нажатия левой кнопки
    left_state = HIGH;
    //возвращаем состояние кнопки в исходное значение
    }
}

Результат работы системы с прерываниями выглядит так (обратите внимание, что экран обновляется только при нажатии, а не постоянно по циклу):

Ну вот и все! Удачных вам поделок.

Полный код скетча из видео доступен тут.

З.Ы. Почему этот пост называется «трудный опыт» — потому что в ходе постижения дзена прерываний на Arduino выяснилось, что к документации на форуме Arduino.cc надо относится с известной степенью критического осмысления — так, если попробовать задействовать функцию, вызывающую системное прерывание, как например, это делает функция millis(), то результат будет примерно таким:

В общем, будьте внимательны и осторожны и перепроверяйте все сами!

Опубликовано

в

от

Метки: