Как определить, было ли нажато более одной кнопки
Я проектирую велосипедный указатель поворота, использующий в качестве входных данных две кнопки, по одной с каждой стороны, с соответствующей подсветкой для каждой кнопки. Идея такова:
- Нажатие на одну сторону заставляет эту же сторону начать мигать;
- Повторное нажатие на ту же сторону выключает ее;
- Нажатие на другую сторону выключает эту сторону и включает другую сторону;
- (а теперь самое интересное!) если нажаты обе кнопки (или, точнее, если одна кнопка нажата до того, как отпущена другая), то — и только тогда — я начинаю мигать обеими сторонами (функция «предупреждающего сигнала»).
На мой взгляд, проблема в том, что мне нужно дождаться, пока я отпущу одну кнопку (это будет один полный щелчок), чтобы включить одну сторону, потому что если я нажму вторую, пока первая остается нажатой, это будет другое событие/жест («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();
}
};
@heltonbiker, 👍2
Обсуждение1 ответ
Интересный вопрос, и на него сложно ответить идеально. :)
Некоторое время назад я написал класс менеджера переключателей. Это просто файл .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 -> правый переключатель нажат и отпущен
- ОБА -> Режим аварийных огней (мигают оба фонаря)
Если мы опускаем левую кнопку, пока правая кнопка все еще нажата, или наоборот, мы переключаемся в режим предупреждения.
При отпускании кнопки мы переключаемся из состояния «переключатель нажат» в состояние «переключатель отпущен».
Повторное нажатие, когда горит один или оба индикатора, отменяет их.
Обратите внимание, что мои переключатели были подключены к земле с помощью внутреннего подтягивающего резистора, поэтому они имеют НИЗКИЙ уровень при нажатии и ВЫСОКИЙ уровень при ненажатии.
- Как масштабировать растровое изображение (массив uint8_t) в Arduino?
- Использование функции millis() вместо функции delay() при воспроизведении мелодии
- Матрица и пространство состояний Реализация Arduino
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
- Что лучше использовать: #define или const int для констант?
- Функции со строковыми параметрами
- Как работать с аналоговыми контактами в цикле?
- Какие есть другие IDE для Arduino?
Где ваша государственная машина?, @Ignacio Vazquez-Abrams
@IgnacioVazquez-Abrams Я публикую это, смотрите мою правку., @heltonbiker