md
Push Button and LED on a Single GPIO Pin
October 13, 2019
<-Detecting Multiple Button Clicks in an Arduino Sketch --

The Sonoff SV has a populated 9 pin header that provides connections to three ESP8266 GPIO pins, ground and Vcc. Compared to a Sonoff Basic, access to three GPIO pins is a great improvement, but it is short of what I needed for an automatic garage door closer. So one GPIO pin has to do double duty: control a status light-emitting diode (LED) and read a normally open push button.

The push button will be used to disable or enable the automatic door closure. The red LED is turned on when the automatic function is disabled.

Here is how I connected the push button, LED and current limiting resistor.

The first schematic shows the circuit in its usual state. The GPIO pin is in output mode and set to low. Since 0 volts are across the LED it is off. In the next diagram to the right, the LED is turned on by setting the GPIO output pin to high. Since the switch is normally open, it has no effect in either case. The GPIO pin is set to input mode to read the state of the push button. When it is open (which will be the case almost always) the LED will be turned off. However the duration of this state is very short. Also the time between checks is relatively long so that visually, it will not be apparent that the LED is being turned off when checking the push button state. On those rare occasions that the push button is pressed, the LED will turn on, but that hopefully be seen as visual feedback.

In the first version of this post, I created a new push button library that explicitly handled the LED. That was a mistake. It is better to modify the library without any reference to the LED that may or may not be connected to the same GPIO pin as the as the push button.
The old post and its mdButtonLed library are still available just in case the trick used here to read the OUPUT state of the GPIO pin does not work.

It was not difficult to modify my original push button Arduino library in order to leave the GPIO pin in OUTPUT mode most of the time. Here is the header file for the new library.

#ifndef mdButton_h
#define mdButton_h

#include "Arduino.h"

#define DEFAULT_ACTIVE                  LOW
#define DEFAULT_DEBOUNCE_PRESS_TIME      15
#define DEFAULT_DEBOUNCE_RELEASE_TIME    30
#define DEFAULT_MULTI_CLICK_TIME        400  // if 0, does not check for multiple button clicks
#define DEFAULT_HOLD_TIME              2000  // minimum time of button press for mdButton.status() to return a -1 (long button press)
#define DEFAULT_CHECK_INTERVAL           50  // time between successive polling of the button pin 
                                             // all times in milliseconds

class mdButton
{
  public:
    mdButton(uint8_t pin, bool active = DEFAULT_ACTIVE); //constructor
  
    // Set attributes
    void setDebouncePressTime(uint16_t value);
    void setDebounceReleaseTime(uint16_t value);
    void setMultiClickTime(uint16_t value);
    void setHoldTime(uint16_t value);
    void setCheckInterval(uint16_t value);
 
    // status, number of clicks since last update
    // -1 = button held, 0 = button not pressed, 1, 2, ... number of times button pressed
    int status();
  
  private:
    uint8_t pin_;    
    bool active_;                   
    uint16_t debouncePressTime_   = DEFAULT_DEBOUNCE_PRESS_TIME; 
    uint16_t debounceReleaseTime_ = DEFAULT_DEBOUNCE_RELEASE_TIME;
    uint16_t multiClickTime_      = DEFAULT_MULTI_CLICK_TIME;  
    uint16_t holdTime_            = DEFAULT_HOLD_TIME;
    uint16_t checkInterval_       = DEFAULT_CHECK_INTERVAL;
  
    // State variables 
    unsigned long lastButtonCheck_;
    long eventTime_; 
    enum buttonState_t { AWAIT_PRESS, DEBOUNCE_PRESS, AWAIT_RELEASE, DEBOUNCE_RELEASE, AWAIT_MULTI_PRESS } buttonState_;
    int clicks_;

    bool isButtonPressed_(void);
};

#endif

The only change in the public interface of the mdButton class is the new setCheckInterval function. It is used to set the time between successive checks of the GPIO pin to see if the button has been pressed. By default, that value is 50 milliseconds. The value should not be decreased very much. If a LED were connected to the pin then its brightness would be diminished significantly with a much lower check interval because the GPIO pin will be in INPUT mode a greater, proportion of the time. The library can also handle normally open push buttons that are active low.

When wiring the LED and switch in this fashion, it is necessary to activate the pull up resistor on the GPIO pin when putting it in input mode. Otherwise the value read will probably be low as if the switch was pressed.

To get about 1.8 V at a conservative 15 mA across a LED, a resistor of 100 ohms should be used in series with the LED with a 3.3 V power source. There are many LED calculators on the Web to help in arriving at the value of the current limiting resistor: ledcacl.com, ledcalculator.net ... If these components are at the end of a long cable to the Sonoff SV, it would be a good idea to measure the supply voltage at the LED in order to account for the resistance of the wire. Finally, the diagram on the right is to remind me of which pin is the anode, which I am always forgetting.

The new mdButton library can be downloaded here: mdButton.zip. It contains an additional example sketch showing how the GPIO pin connected to a push button can also drive a LED with my mdBlinky library without any change to the latter.

<-Detecting Multiple Button Clicks in an Arduino Sketch --