Найти в Дзене
Stepan.Burmistrov

Качественная обработка нажатия кнопки в микроконтроллерах Arduino

Оглавление

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

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

  1. Дребезг контактов — физическое явление, при котором кнопка при нажатии или отпускании может несколько раз быстро переключаться между состояниями замкнуто и разомкнуто.
  2. Определение момента нажатия и отпускания — важно точно понимать, когда кнопка была нажата и когда отпущена, чтобы выполнить соответствующее действие.
  3. Одно нажатие — одно событие — мы должны сделать так, чтобы каждое нажатие кнопки вызывало ровно одно действие, а не несколько из-за дребезга или других факторов.

Теперь давайте рассмотрим, как эти задачи решаются в коде на языке Arduino C/C++.

Ссылка на проект в TinkerCad: https://www.tinkercad.com/things/eFYKEbV4XB0-arduinobutton

// Определение пина для кнопки и светодиода
#define BUTTON_PIN 2
#define LED_PIN 3

// Глобальные переменные для отслеживания состояния кнопки и времени последнего нажатия
uint8_t buttonState = 0;      // Текущее состояние кнопки (0 - не нажата, 1 - нажата)
uint8_t buttonPressed = 0;    // Флаг, указывающий была ли кнопка нажата (0 - нет, 1 - да)
uint32_t buttonTimer = 0;     // Таймер для отслеживания времени последнего изменения состояния кнопки

// Функция для переключения состояния светодиода
void buttonFunction(){
  // Читаем текущее состояние светодиода и устанавливаем противоположное значение
  digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

// Функция для управления состоянием кнопки и вызова функции buttonFunction при ее нажатии
void buttonControl(){
  // Читаем состояние кнопки с инверсией результат (с учетом INPUT_PULLUP, когда нажатое состояние будет 0)
  buttonState = !digitalRead(BUTTON_PIN); // нажатое состояние - 1

  // Проверяем, прошло ли достаточно времени с момента последнего изменения состояния кнопки
  if (millis() - buttonTimer > 50) {
    // Если кнопка была нажата и не была вызвана функция
    if (!buttonPressed and buttonState){
      buttonTimer = millis();
      buttonFunction();       // Вызываем функцию для переключения светодиода
      buttonPressed = 1;      // Устанавливаем флаг, что кнопка была нажата
    } 
    // Когда отпустили кнопку
    else if (buttonPressed and !buttonState){
      buttonPressed = 0;      // Сбрасываем флаг нажатия кнопки
    }
  }
}

// Настройка начальных параметров
void setup()
{
  pinMode(LED_PIN, OUTPUT);           // Устанавливаем пин светодиода как выход
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // Устанавливаем пин кнопки как вход с подтяжкой к питанию
}

// Основной цикл программы
void loop()
{
  buttonControl();  // Вызываем функцию управления кнопкой
}

Определение пинов и глобальных переменных

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

#define BUTTON_PIN 2
#define LED_PIN 3

Далее, мы объявляем несколько глобальных переменных, которые помогут нам отслеживать состояние кнопки:

uint8_t buttonState = 0; // Текущее состояние кнопки (0 - не нажата, 1 - нажата)
uint8_t buttonPressed = 0; // Флаг, указывающий было ли обработано нажатие кнопки(0 - нет, 1 - да)
uint32_t buttonTimer = 0; // Таймер для отслеживания времени последнего изменения состояния кнопки
  • buttonState хранит текущее состояние кнопки: нажата или нет.
  • buttonPressed — это флаг, который указывает, была ли кнопка уже нажата. Этот флаг помогает нам реализовать режим "одно нажатие — одно событие".
  • buttonTimer используется для реализации антидребезга — отслеживания времени последнего изменения состояния кнопки.

Основная функция обработки кнопки

Основная работа по обработке кнопки выполняется в функции buttonControl():

void buttonControl() {
buttonState = !digitalRead(BUTTON_PIN); // нажатое состояние - 1

Мы читаем текущее состояние кнопки с инверсией результата (!digitalRead(BUTTON_PIN)). Это связано с тем, что кнопка подключена к пину с подтяжкой к питанию (INPUT_PULLUP), и в обычном состоянии на пине будет высокий уровень (логическая "1"), а при нажатии кнопки — низкий уровень (логический "0").

Почему важна обработка с таймером?

Дребезг контактов — это физическое явление, при котором контакты кнопки могут многократно и быстро переключаться между состояниями "замкнуто" и "разомкнуто" при нажатии или отпускании. Чтобы избежать ложных срабатываний из-за дребезга, мы используем простой метод антидребезга с таймером:

if (millis() - buttonTimer > 50) {

Функция millis() возвращает количество миллисекунд, прошедших с момента запуска программы. Мы проверяем, прошло ли 50 миллисекунд с момента последнего изменения состояния кнопки (buttonTimer). Если прошло, то мы считаем, что дребезг завершился, и можем корректно обработать нажатие кнопки.

Как работает флаг buttonPressed?

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

if (!buttonPressed and buttonState) {
buttonTimer = millis();
buttonFunction(); // Вызываем функцию для переключения светодиода
buttonPressed = 1; // Устанавливаем флаг, что кнопка была нажата
}
else if (buttonPressed and !buttonState) {
buttonPressed = 0; // Сбрасываем флаг нажатия кнопки
}
  • Нажатие кнопки: Если кнопка была не обработана(buttonPressed == 0), а сейчас произошло физическое нажатие (buttonState == 1), то мы выполняем действие (вызываем функцию buttonFunction()), обновляем buttonTimer и устанавливаем buttonPressed в 1. Это предотвращает повторное выполнение действия, пока кнопка не будет отпущена.
  • Отпускание кнопки: Когда кнопка отпускается (buttonState == 0), флаг buttonPressed сбрасывается в 0. Это позволяет функции распознать следующее нажатие и снова выполнить действие.

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

Функция обработки кнопки: переключение состояния светодиода

Когда кнопка нажата, вызывается функция buttonFunction(), которая выполняет действие — в данном случае переключает состояние светодиода:

void buttonFunction() {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

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

Настройка начальных параметров и основной цикл

Функция setup() используется для начальной настройки параметров микроконтроллера:

void setup() {
pinMode(LED_PIN, OUTPUT); // Устанавливаем пин светодиода как выход
pinMode(BUTTON_PIN, INPUT_PULLUP); // Устанавливаем пин кнопки как вход с подтяжкой к питанию
}

А функция loop() является основным циклом программы, который постоянно вызывает функцию buttonControl():

void loop() {
buttonControl(); // Вызываем функцию управления кнопкой
}

Метод обработки нажатия кнопки, представленный в данном коде, решает сразу несколько важных задач: обработку дребезга, точное определение нажатий и отпусканий, а также реализацию режима "одно нажатие — одно событие". Этот подход является базовым, но очень эффективным для большинства устройств. Вы можете легко адаптировать и расширить его для более сложных задач, таких как обработка длительных нажатий, двойных нажатий или управления несколькими кнопками одновременно.

Надеюсь, этот материал был полезен для вас! Успехов в ваших проектах на Arduino!