Как определить, было ли нажато более одной кнопки

Я проектирую велосипедный указатель поворота, использующий в качестве входных данных две кнопки, по одной с каждой стороны, с соответствующей подсветкой для каждой кнопки. Идея такова:

  1. Нажатие на одну сторону заставляет эту же сторону начать мигать;
  2. Повторное нажатие на ту же сторону выключает ее;
  3. Нажатие на другую сторону выключает эту сторону и включает другую сторону;
  4. (а теперь самое интересное!) если нажаты обе кнопки (или, точнее, если одна кнопка нажата до того, как отпущена другая), то — и только тогда — я начинаю мигать обеими сторонами (функция «предупреждающего сигнала»).

На мой взгляд, проблема в том, что мне нужно дождаться, пока я отпущу одну кнопку (это будет один полный щелчок), чтобы включить одну сторону, потому что если я нажму вторую, пока первая остается нажатой, это будет другое событие/жест («both_click», за неимением лучшего названия).

Другой вариант дизайна — включить один индикатор сразу при нажатии, и если я нажму второй, прежде чем отпущу первый, то также включится другой индикатор (таким образом, включив режим «предупреждения», когда оба индикатора мигают). Хотя это звучит осуществимо, в классе Button («Botao») есть вся логика устранения дребезга, и я боюсь, что загнал себя в угол конкретными объектно-ориентированными решениями по дизайну, которые я сделал.

ПРАВКА: третий вариант дизайна — добавить метод run для каждой кнопки и, вместо того, чтобы запускать только тестируемый метод «псевдособытия», сохранять текущее состояние и событие в переменных, проверяя эти переменные в loop() (например, свойство button.wasPressed вместо метода button.wasPressed()).

Я публикую свой текущий код (только соответствующие части). Обратите внимание, что в цикле есть комментарий, где, как мне кажется, я должен обнаружить двойной щелчок.

Основной файл .ino:

#include "PiscaPisca.cpp"
#include "Botao.cpp"

PiscaPisca pisca;

Botao botao1;
Botao botao4;

void setup() {

  pisca.configure(LEFT, RIGHT, BUZZER);

  botao1.configure(BUTTON1);
  botao4.configure(BUTTON4);
}

void loop() {

  // КАК Я МОГУ ОПРЕДЕЛИТЬ, ЧТО ОБЕ КНОПКИ БЫЛИ НАЖАНЫ??
  // если (bothPressed()) { pisca.toggleWarning(); }

  if (botao1.wasPressed()) { pisca.toggleLeft();  }
  if (botao4.wasPressed()) { pisca.toggleRight(); }

  pisca.run();
}

Botao.cpp (это класс кнопки с функцией устранения дребезга)

#include <Arduino.h>

class Botao
{
    int _pino;

    const int DEBOUNCE_DELAY = 30;

    int buttonState;
    int lastState = HIGH;

    int lastDebounceTime;

    public : void configure(int pino)
    {
        _pino = pino;
        pinMode(pino, INPUT_PULLUP);
    }

    public : boolean wasPressed()
    {        
        return debounce(LOW);
    }

    public : boolean wasReleased()
    {
        return debounce(HIGH);
    }

    public : boolean debounce(int state)
    {        
        boolean gotEvent = false;

        int reading = digitalRead(_pino);

        if (reading != lastState) {
            lastDebounceTime = millis();
        }

        if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
            if (reading != buttonState) {
                buttonState = reading;

                if (buttonState == state) {
                    gotEvent = true;
                }
            }
        }

        lastState = reading;

        return gotEvent;
    }    
};

PiscaPisca.cpp (сама конечная машина)

#include "Arduino.h"
#include "PwmPin.cpp"
#include "Beeper.cpp"

typedef enum {
    NONE        = 0,
    RIGHT_LIGHT = 1,
    LEFT_LIGHT  = 2,
    BOTH        = 3
};

class PiscaPisca
{
    PwmPin _left;
    PwmPin _right;

    Beeper _beeper;

    long _timeReference = 0;

    const int PERIOD = 350;

    boolean
        _running = false,
        _lightState = false;

    int _sides_to_turn = NONE;  


    public : void configure(int leftPin, int rightPin, int buzzerPin)
    {
        _left.configure(leftPin);
        _right.configure(rightPin);
        _beeper.configure(buzzerPin);
    }

    public : void run() 
    {        
        evaluateBlink();
    }



    public : void toggleLeft()
    {
        checkRestart(LEFT_LIGHT);
    }

    public : void toggleRight()
    {
        checkRestart(RIGHT_LIGHT);
    }

    void checkRestart(int lightSide)
    {
        _timeReference = 0;  

        // немного хитрой манипуляции никогда не повредит:
        _sides_to_turn = lightSide & ~_sides_to_turn;  

        Serial.println(lightSide);
        Serial.println(_sides_to_turn);     

        if (_sides_to_turn > 0)
        {
            _running = true;
        }
        else
        {
            lightsOff();
            _running = false;
            _lightState = false;      
        }
    }



    void evaluateBlink() 
    {
        if (!_running)
        {
            return;
        }
        else
        {
            long currentMillis = millis();
            if (currentMillis - _timeReference > PERIOD) {
                _timeReference = currentMillis;
                _lightState = !_lightState;
                performBlink();
            }
        }
    }

    void performBlink()
    {
        if (_lightState)
        {
            _beeper.beepIn();
            lightsOn();
        }
        else
        {
            _beeper.beepOut();
            lightsOff();
        }        
    }

    void lightsOn()
    {
        if (isLightSet(LEFT_LIGHT))
        {
            _left.on();
        }
        if (isLightSet(RIGHT_LIGHT))
        {
            _right.on();
        }        
    }

    boolean isLightSet(int lightSide)
    {
        return (_sides_to_turn & lightSide) == lightSide;
    }

    void lightsOff()
    {
        _left.off();
        _right.off();
    }



};

, 👍2

Обсуждение

Где ваша государственная машина?, @Ignacio Vazquez-Abrams

@IgnacioVazquez-Abrams Я публикую это, смотрите мою правку., @heltonbiker


1 ответ


2

Интересный вопрос, и на него сложно ответить идеально. :)

Некоторое время назад я написал класс менеджера переключателей. Это просто файл .h, который можно поместить в папку библиотек (или добавить в свой скетч).

SwitchManager.h

#include <Arduino.h>

class SwitchManager
  {
  enum { debounceTime = 10, noSwitch = -1 };
  typedef void (*handlerFunction) (const byte newState, 
                                   const unsigned long interval, 
                                   const byte whichSwitch);

  int pinNumber_;
  handlerFunction f_;
  byte oldSwitchState_;
  unsigned long switchStateChangeTime_;  // когда переключатель последний раз изменил состояние
  unsigned long lastLowTime_;
  unsigned long lastHighTime_;

  public:

     // конструктор
     SwitchManager () 
       {
       pinNumber_ = noSwitch;
       f_ = NULL;
       oldSwitchState_  = HIGH;
       switchStateChangeTime_ = 0;
       lastLowTime_  = 0;
       lastHighTime_ = 0;
       }  // конец конструктора

     void begin (const int pinNumber, handlerFunction f)
       {
       pinNumber_ = pinNumber;
       f_ = f;
       if (pinNumber_ != noSwitch)
         pinMode (pinNumber_, INPUT_PULLUP);
       }  // конец begin()

     void check ()
       {
       // нам нужен действительный номер PIN-кода и действительная функция для вызова
       if (pinNumber_ == noSwitch || f_ == NULL)
         return;

        // смотрим, открыт или закрыт переключатель
        byte switchState = digitalRead (pinNumber_);

        // изменилось ли что-то с прошлого раза?
        if (switchState != oldSwitchState_)
          {
          // устранение дребезга
          if (millis () - switchStateChangeTime_ >= debounceTime)
             {
             switchStateChangeTime_ = millis ();  // когда мы закрыли переключатель
             oldSwitchState_ =  switchState;  // запомнить на следующий раз
             if (switchState == LOW)
               {
               lastLowTime_ = switchStateChangeTime_;
               f_ (LOW, lastLowTime_ -  lastHighTime_, pinNumber_);
               }
             else
               {
               lastHighTime_ = switchStateChangeTime_;
               f_ (HIGH, lastHighTime_ - lastLowTime_, pinNumber_);
               }

             }  // конец, если время дребезга истекло
          }  // конец изменения состояния
       }  // конец проверки()

     unsigned long getLastStateChangeTime () const { return switchStateChangeTime_; }
     unsigned long getLastStateLowTime ()    const { return lastLowTime_; }
     unsigned long getLastStateHighTime ()   const { return lastHighTime_; }

  };  // класс SwitchManager

Более подробную информацию можно найти здесь.

Поместите это в папку с именем SwitchManager, поместите ее в папку скетча -> папку libraries и перезапустите IDE.

Это в основном решает:

  • устранение дребезга
  • обнаружение изменений состояния (например, сейчас включено, было выключено)

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

#include <SwitchManager.h>

typedef enum {
    NONE,
    LH_DOWN,
    RH_DOWN,
    LH_LIGHT_ON,
    RH_LIGHT_ON,
    BOTH
};

const unsigned long BLINK_INTERVAL = 500; // мс

// назначения выводов
const byte LH_SWITCH_PIN = 2;
const byte RH_SWITCH_PIN = 3;
const byte LH_LIGHT = A4;
const byte RH_LIGHT = A5;

SwitchManager LHswitch; 
SwitchManager RHswitch; 

byte state = NONE;

void handleLHPress (const byte newState, const unsigned long interval, const byte whichPin)
  {
  // переключить вниз?
  if (newState == LOW)
     {
     switch (state)
       {
       // если другой переключатель выключен, переключиться в режим предупреждения
       case RH_DOWN:
         state = BOTH;
         break;

       // если уже включен или есть предупреждающий сигнал, выключить все
       case LH_LIGHT_ON:
       case BOTH:
         state = NONE;
         break;

       // в противном случае переключатель теперь опущен, но еще не отпущен
       default:
         state = LH_DOWN;
         break;
       }  // конец переключения
     return;
     }  // конец LH переключателя вниз

  // переключатель должен быть поднят

  if (state == LH_DOWN)  // если нажато, переключиться в режим «нажато и отпущено»
    state = LH_LIGHT_ON;  
  }  // конец handleLHPress

void handleRHPress (const byte newState, const unsigned long interval, const byte whichPin)
  {
  // переключить вниз?
  if (newState == LOW)
     {
     switch (state)
       {
       // если другой переключатель выключен, переключиться в режим предупреждения
       case LH_DOWN:
         state = BOTH;
         break;

       // если уже включен или есть предупреждающий сигнал, выключить все
       case RH_LIGHT_ON:
       case BOTH:
         state = NONE;
         break;

       // в противном случае переключатель теперь опущен, но еще не отпущен
       default:
         state = RH_DOWN;
         break;
       }  // конец переключения
     return;
     }  // конец правого переключателя вниз

  // переключатель должен быть поднят

  if (state == RH_DOWN)  // если нажато, переключиться в режим «нажато и отпущено»
    state = RH_LIGHT_ON;  
  }  // конец handleRHPress

void setup ()
  {
  LHswitch.begin (LH_SWITCH_PIN, handleLHPress);
  RHswitch.begin (RH_SWITCH_PIN, handleRHPress);
  pinMode (LH_LIGHT, OUTPUT);
  pinMode (RH_LIGHT, OUTPUT);
  }  // конец настройки

unsigned long lastBlink;
bool onCycle;

void blinkLights ()
  {
  lastBlink = millis ();
  onCycle = !onCycle;

  // по умолчанию выключено
  digitalWrite (LH_LIGHT, LOW);
  digitalWrite (RH_LIGHT, LOW);

  // каждый второй раз выключать их все
  if (!onCycle)
    return;

  // мигающий свет
  switch (state)
    {
    case NONE:
      break;

    case LH_DOWN:
    case LH_LIGHT_ON:
      digitalWrite (LH_LIGHT, HIGH);
      break;

    case RH_DOWN:
    case RH_LIGHT_ON:
      digitalWrite (RH_LIGHT, HIGH);
      break;

    case BOTH:
      digitalWrite (LH_LIGHT, HIGH);
      digitalWrite (RH_LIGHT, HIGH);
      break;

    }  // конец состояния включения

  }  // конец blinkLights


void loop ()
  {
  LHswitch.check ();  // проверка нажатий
  RHswitch.check ();  // проверка нажатий

  if (millis () - lastBlink >= BLINK_INTERVAL)
    blinkLights ();

  // прочее
  }  // конец цикла

Конечный автомат имеет ряд состояний:

  • НЕТ -> все огни выключены
  • LH_DOWN -> левый переключатель нажат, но еще не отпущен
  • RH_DOWN -> правый переключатель нажат, но еще не отпущен
  • LH_LIGHT_ON -> левый переключатель нажат и отпущен
  • RH_LIGHT_ON -> правый переключатель нажат и отпущен
  • ОБА -> Режим аварийных огней (мигают оба фонаря)

Если мы опускаем левую кнопку, пока правая кнопка все еще нажата, или наоборот, мы переключаемся в режим предупреждения.

При отпускании кнопки мы переключаемся из состояния «переключатель нажат» в состояние «переключатель отпущен».

Повторное нажатие, когда горит один или оба индикатора, отменяет их.

Обратите внимание, что мои переключатели были подключены к земле с помощью внутреннего подтягивающего резистора, поэтому они имеют НИЗКИЙ уровень при нажатии и ВЫСОКИЙ уровень при ненажатии.

,