Перепечатка публикации с сайта 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(), то результат будет примерно таким: