Разработка универсального класса для обработки кнопок в микроконтроллерах Arduino

В данной статье представлен универсальный подход к обработке кнопок в микроконтроллерах Arduino с использованием объектно-ориентированного программирования. Разработан класс Button, который инкапсулирует всю логику обработки кнопок, включая устранение дребезга и обработку событий нажатия, удержания и отпускания. Особое внимание уделено использованию указателей на функции для реализации гибких и масштабируемых обработчиков событий.

Введение

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

Объектно-ориентированный подход

Использование объектно-ориентированного программирования (ООП) в разработке приложений для Arduino позволяет создавать более структурированный и поддерживаемый код. Классы и объекты помогают инкапсулировать данные и методы, связанные с определенной функциональностью, что упрощает масштабирование и повторное использование кода.

Класс Button

Описание

Класс Button предназначен для обработки кнопок с учетом дребезга контактов и событий нажатия, удержания и отпускания. Он инкапсулирует всю необходимую логику и позволяет назначать индивидуальные обработчики для каждого типа события.

Реализация

class Button { public: // Конструктор класса Button(uint8_t pin, void (*onPress)() = nullptr, void (*onHold)() = nullptr, void (*onRelease)() = nullptr, uint32_t debounceDelay = 50) : pin(pin), onPress(onPress), onHold(onHold), onRelease(onRelease), debounceDelay(debounceDelay) { pinMode(pin, INPUT_PULLUP); // Настраиваем пин кнопки buttonState = 0; lastButtonState = 0; buttonTimer = 0; buttonPressed = false; } // Метод обновления состояния кнопки, вызываемый в loop() void update() { // Читаем состояние кнопки с инверсией результата (с учетом INPUT_PULLUP) uint8_t currentState = !digitalRead(pin); // нажатое состояние - 1 // Проверяем, изменилось ли состояние кнопки if (currentState != lastButtonState) { buttonTimer = millis(); // Сбрасываем таймер дребезга } // Проверяем, прошло ли достаточно времени для устранения дребезга if ((millis() - buttonTimer) > debounceDelay) { // Если состояние кнопки стабилизировалось и отличается от предыдущего устойчивого состояния if (currentState != buttonState) { buttonState = currentState; if (buttonState == 1) { // Кнопка нажата if (onPress != nullptr) { onPress(); } buttonPressed = true; } else { // Кнопка отпущена if (onRelease != nullptr && buttonPressed) { onRelease(); } buttonPressed = false; } } else if (buttonState == 1 && onHold != nullptr) { // Кнопка удерживается onHold(); } } lastButtonState = currentState; } private: uint8_t pin; uint8_t buttonState; // Текущее устойчивое состояние кнопки uint8_t lastButtonState; // Предыдущее состояние кнопки uint32_t buttonTimer; // Таймер для отслеживания времени последнего изменения состояния кнопки uint32_t debounceDelay; // Задержка для устранения дребезга bool buttonPressed; // Флаг, указывающий, была ли кнопка нажата void (*onPress)(); // Указатель на функцию-обработчик нажатия кнопки void (*onHold)(); // Указатель на функцию-обработчик удержания кнопки void (*onRelease)(); // Указатель на функцию-обработчик отпускания кнопки };

Конструктор класса

Конструктор класса Button принимает следующие параметры:

  • uint8_t pin: номер пина, к которому подключена кнопка.
  • void (*onPress)(): указатель на функцию, вызываемую при нажатии кнопки.
  • void (*onHold)(): указатель на функцию, вызываемую при удержании кнопки.
  • void (*onRelease)(): указатель на функцию, вызываемую при отпускании кнопки.
  • uint32_t debounceDelay: задержка для устранения дребезга (по умолчанию 50 мс).

Внутри конструктора происходит инициализация:

  • Настраивается пин кнопки как вход с подтяжкой к питанию (INPUT_PULLUP).
  • Инициализируются переменные состояния кнопки и флаги.

Приватные переменные класса

  • uint8_t pin: пин кнопки.
  • uint8_t buttonState: текущее устойчивое состояние кнопки после устранения дребезга.
  • uint8_t lastButtonState: предыдущее состояние кнопки.
  • uint32_t buttonTimer: таймер для отслеживания времени последнего изменения состояния кнопки.
  • uint32_t debounceDelay: задержка для устранения дребезга.
  • bool buttonPressed: флаг, указывающий, что кнопка была нажата.
  • void (*onPress)(): указатель на функцию-обработчик нажатия кнопки.
  • void (*onHold)(): указатель на функцию-обработчик удержания кнопки.
  • void (*onRelease)(): указатель на функцию-обработчик отпускания кнопки.

Метод update()

Метод update() должен вызываться в основном цикле программы для обновления состояния кнопки и обработки событий.

Алгоритм работы:

  • Чтение текущего состояния кнопки: Используется digitalRead(pin) с инверсией (!) из-за подтяжки INPUT_PULLUP. currentState равен 1, если кнопка нажата, и 0, если отпущена.
  • Обработка дребезга: Если текущее состояние отличается от предыдущего (lastButtonState), сбрасывается таймер buttonTimer. Если прошло достаточно времени (debounceDelay), состояние считается стабильным.
  • Обновление состояния кнопки: Если стабильное состояние (currentState) отличается от предыдущего устойчивого состояния (buttonState), происходит обновление buttonState.
  • Обработка событий:Нажатие кнопки:Если кнопка переходит в состояние 1, вызывается функция onPress(), если она указана.Устанавливается флаг buttonPressed.Отпускание кнопки:Если кнопка переходит в состояние 0 и ранее была нажата, вызывается функция onRelease(), если она указана.Сбрасывается флаг buttonPressed.Удержание кнопки:Если кнопка находится в устойчивом нажатом состоянии, вызывается функция onHold(), если она указана.
  • Обновление предыдущего состояния:lastButtonState обновляется для использования в следующем цикле.

Использование указателей на функции

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

  • void (*onPress)(): вызывается при нажатии кнопки.
  • void (*onHold)(): вызывается при удержании кнопки.
  • void (*onRelease)(): вызывается при отпускании кнопки.

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

Пример:

void onButton1Press() { // Действие при нажатии кнопки 1 } Button button1(BUTTON_PIN_1, onButton1Press);

Программа и пример реализации

Ссылка на проект
Ссылка на проект

Инициализация переменных и первичная настройка

#define NUM_BUTTONS 2 #define BUTTON_PIN_1 2 #define BUTTON_PIN_2 3 #define LED_PIN 8 #define RELAY_PIN 9 bool ledState = 0; bool relayState = 0; void setupDevices() { pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, ledState); pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, relayState); } void setup() { Serial.begin(9600); setupDevices(); }

Глобальные переменные ledState и relayState используются для хранения состояния светодиода и реле соответственно. Это позволяет контролировать устройства в разных частях программы.

Функции - обработчики событий

void onButton1Press() { ledState = !ledState; digitalWrite(LED_PIN, ledState); Serial.println("LED toggled!"); } void onButton2Hold() { if (!relayState) { relayState = true; digitalWrite(RELAY_PIN, HIGH); Serial.println("RELAY ON!"); } } void onButton2Release() { if (relayState) { relayState = false; digitalWrite(RELAY_PIN, LOW); Serial.println("RELAY OFF!"); } }

Каждая кнопка может иметь свои уникальные обработчики событий. Например, кнопка 1 переключает состояние светодиода при нажатии, а кнопка 2 включает реле при удержании и выключает при отпускании.

Создание объектов кнопок

Button buttons[NUM_BUTTONS] = { Button(BUTTON_PIN_1, onButton1Press), Button(BUTTON_PIN_2, nullptr, onButton2Hold, onButton2Release), };

Использование массива объектов Button и циклического вызова метода update() для каждого из них обеспечивает масштабируемость и простоту добавления новых кнопок.

Основной цикл

void loop() { for (int i = 0; i < NUM_BUTTONS; i++) { buttons[i].update(); } }

Преимущества подхода

  • Инкапсуляция: Класс Button скрывает всю сложность обработки кнопок, предоставляя простой интерфейс.
  • Масштабируемость: Легко добавлять новые кнопки без изменения основной структуры программы.
  • Гибкость: Возможность назначать индивидуальные обработчики для разных событий кнопки.
  • Чистый код: Основная функция loop() остается простой и понятной.
Начать дискуссию