I am designing a turn signal bicycle blinker using two pushbuttons as input, one for each side, with a corresponding light for each button. The idea is this:
- Pressing one side makes same side start blinking;
- Pressing same side again to turn it off;
- Pressing other side turns off this side and turns other side on;
- (now the tricky part!) if both buttons are pressed (or more precisely, if one button is pressed before the other is released), then - and only then - I start blinking both sides ("warning signal" feature).
As I see it, the problem is that I need to wait until I release a single button (that would be one complete click) to turn a single side on, because if I press the second while the first remains pressed, that would be another event/gesture ("both_click", for lack of a better name).
Another design option would be to turn a single light on immediately on press, and if I press the second before release the first, then in would also turn the other light (thus comprising the "warning", both-lights-blinking mode). While that sounds feasible, there is all the debouncing logic in the Button ("Botao") class, and I am afraid I painted myself into a corner with the specific object-oriented design choices I've made.
EDIT: a third design option would be to add a run
method on each button and, instead of only running the tested "pseudo-event" method, save the current state and event in variables, testing those variables in loop()
(property button.wasPressed
instead of method button.wasPressed()
, for example).
I'm posting my current code (relevant parts only). Note there is a comment in the loop where I think I should detect double click.
Main .ino file:
#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() {
// HOW SHOULD I DETECT BOTH WERE PRESSED??
// if (bothPressed()) { pisca.toggleWarning(); }
if (botao1.wasPressed()) { pisca.toggleLeft(); }
if (botao4.wasPressed()) { pisca.toggleRight(); }
pisca.run();
}
Botao.cpp (this is a button class with debounce)
#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 (the state-machine itself)
#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;
// some clever bit-twiddling can never hurt too much:
_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();
}
};
-
Where is your state machine?Ignacio Vazquez-Abrams– Ignacio Vazquez-Abrams2015年07月16日 02:18:07 +00:00Commented Jul 16, 2015 at 2:18
-
@IgnacioVazquez-Abrams I'm posting it, see my edit.heltonbiker– heltonbiker2015年07月16日 02:24:55 +00:00Commented Jul 16, 2015 at 2:24
1 Answer 1
Interesting question, and tricky to get perfect. :)
I wrote a switch manager class a while back. It's just a .h file which you can put in your libraries folder (or add to your sketch).
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_; // when the switch last changed state
unsigned long lastLowTime_;
unsigned long lastHighTime_;
public:
// constructor
SwitchManager ()
{
pinNumber_ = noSwitch;
f_ = NULL;
oldSwitchState_ = HIGH;
switchStateChangeTime_ = 0;
lastLowTime_ = 0;
lastHighTime_ = 0;
} // end of constructor
void begin (const int pinNumber, handlerFunction f)
{
pinNumber_ = pinNumber;
f_ = f;
if (pinNumber_ != noSwitch)
pinMode (pinNumber_, INPUT_PULLUP);
} // end of begin()
void check ()
{
// we need a valid pin number and a valid function to call
if (pinNumber_ == noSwitch || f_ == NULL)
return;
// see if switch is open or closed
byte switchState = digitalRead (pinNumber_);
// has it changed since last time?
if (switchState != oldSwitchState_)
{
// debounce
if (millis () - switchStateChangeTime_ >= debounceTime)
{
switchStateChangeTime_ = millis (); // when we closed the switch
oldSwitchState_ = switchState; // remember for next time
if (switchState == LOW)
{
lastLowTime_ = switchStateChangeTime_;
f_ (LOW, lastLowTime_ - lastHighTime_, pinNumber_);
}
else
{
lastHighTime_ = switchStateChangeTime_;
f_ (HIGH, lastHighTime_ - lastLowTime_, pinNumber_);
}
} // end if debounce time up
} // end of state change
} // end of check()
unsigned long getLastStateChangeTime () const { return switchStateChangeTime_; }
unsigned long getLastStateLowTime () const { return lastLowTime_; }
unsigned long getLastStateHighTime () const { return lastHighTime_; }
}; // class SwitchManager
More details here.
Put that into a folder called SwitchManager
, put that into your sketch folder -> libraries
folder and restart the IDE.
That basically handles:
- debouncing
- detecting state changes (eg. now on, was off)
Using that we can now write a sketch that does the indicators:
#include <SwitchManager.h>
typedef enum {
NONE,
LH_DOWN,
RH_DOWN,
LH_LIGHT_ON,
RH_LIGHT_ON,
BOTH
};
const unsigned long BLINK_INTERVAL = 500; // ms
// pin assignments
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)
{
// switch down?
if (newState == LOW)
{
switch (state)
{
// if other switch down, switch to warning mode
case RH_DOWN:
state = BOTH;
break;
// if already on or warning signal, turn all off
case LH_LIGHT_ON:
case BOTH:
state = NONE;
break;
// otherwise switch is now down, but not yet released
default:
state = LH_DOWN;
break;
} // end of switch
return;
} // end of LH switch down
// switch must be up
if (state == LH_DOWN) // if down, switch to down-and-released mode
state = LH_LIGHT_ON;
} // end of handleLHPress
void handleRHPress (const byte newState, const unsigned long interval, const byte whichPin)
{
// switch down?
if (newState == LOW)
{
switch (state)
{
// if other switch down, switch to warning mode
case LH_DOWN:
state = BOTH;
break;
// if already on or warning signal, turn all off
case RH_LIGHT_ON:
case BOTH:
state = NONE;
break;
// otherwise switch is now down, but not yet released
default:
state = RH_DOWN;
break;
} // end of switch
return;
} // end of RH switch down
// switch must be up
if (state == RH_DOWN) // if down, switch to down-and-released mode
state = RH_LIGHT_ON;
} // end of handleRHPress
void setup ()
{
LHswitch.begin (LH_SWITCH_PIN, handleLHPress);
RHswitch.begin (RH_SWITCH_PIN, handleRHPress);
pinMode (LH_LIGHT, OUTPUT);
pinMode (RH_LIGHT, OUTPUT);
} // end of setup
unsigned long lastBlink;
bool onCycle;
void blinkLights ()
{
lastBlink = millis ();
onCycle = !onCycle;
// default to off
digitalWrite (LH_LIGHT, LOW);
digitalWrite (RH_LIGHT, LOW);
// every second time, turn them all off
if (!onCycle)
return;
// blink light
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;
} // end of switch on state
} // end of blinkLights
void loop ()
{
LHswitch.check (); // check for presses
RHswitch.check (); // check for presses
if (millis () - lastBlink >= BLINK_INTERVAL)
blinkLights ();
// other stuff
} // end of loop
The state machine has a number of states:
- NONE -> all lights off
- LH_DOWN -> LH switch depressed, not yet released
- RH_DOWN -> RH switch depressed, not yet released
- LH_LIGHT_ON -> LH switch depressed, and released
- RH_LIGHT_ON -> RH switch depressed, and released
- BOTH -> Warning lights mode (flash both lights)
If we get a LH down while the RH is still pressed, or vice-versa, we switch to warning mode.
On a button release we switch from "switch down" to "switch released".
A second press, while either or both lights are on cancels them.
Note that my switches were wired to ground with the internal pull-up, so they are LOW when pressed and HIGH when not pressed.
-
Amazing! Well this is a lot of stuff to digest, and as you said it's a bit hard to fully understand, but surely I'm gonna test it tonight and bring some feedback. Thank you very much for now!!heltonbiker– heltonbiker2015年07月16日 12:35:11 +00:00Commented Jul 16, 2015 at 12:35
Explore related questions
See similar questions with these tags.