2

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:

  1. Pressing one side makes same side start blinking;
  2. Pressing same side again to turn it off;
  3. Pressing other side turns off this side and turns other side on;
  4. (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 Jul 16 '15 at 2:18
  • @IgnacioVazquez-Abrams I'm posting it, see my edit. – heltonbiker Jul 16 '15 at 2:24
2

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 Jul 16 '15 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.