clickButton library

I adapted the version of clickButton from the code.google folks. This little library allows you to get 6 functions from a single button using short and long clicks. It is VERY handy when you don’t have a lot of I/O for buttons. I have included the “multiclick” example in the code.

PLEASE NOTE: Due to the nature of the Spark core firmware, any hardware configuration done in the class instantiations will not work. As such, you will see in setup() the need for pinMode for the input pin connected to the button. You will also see this code in the class definition but it does not work. I left the code there in case the core firmware changes this situation in the future. Again, I did not do enough work on this to take any credit. Cheers!

/*    ClickButton
 
 Arduino library that decodes multiple clicks on one button.
 Also copes with long clicks and click-and-hold.
 
 Usage: ClickButton buttonObject(pin [LOW/HIGH, [CLICKBTN_PULLUP]]);
 
  where LOW/HIGH denotes active LOW or HIGH button (default is LOW)
  CLICKBTN_PULLUP is only possible with active low buttons.
 
 Returned click counts:

   A positive number denotes the number of (short) clicks after a released button
   A negative number denotes the number of "long" clicks
 
NOTE!
 This is the OPPOSITE/negative of click codes from the last pre-2013 versions!
 (this seemed more logical and simpler, so I finally changed it)

 Based on the Debounce example at arduino playground site

 Copyright (C) 2010,2012, 2013 raron

 GNU GPLv3 license
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 Contact: raronzen@gmail.com

 History:
 2013.08.29 - Some small clean-up of code, more sensible variable names etc.
                Added another example code for multiple buttons in an object array
 2013.04.23 - A "minor" debugging: active-high buttons now work (wops)!
                Thanks goes to John F. H. for pointing that out!
 2013.02.17 - Some improvements, simplified click codes.
				Added a LED fader example. Thanks to Tom K. for the idea.
 2012.01.31 - Tiny update for Arduino 1.0
 2010.06.15 - First version. Basically just a small OOP programming exercise.
*/

// -------- clickButton.h --------

#define CLICKBTN_PULLUP HIGH

class ClickButton
{
  public:
    ClickButton(uint8_t buttonPin);
    ClickButton(uint8_t buttonPin, boolean active);
    ClickButton(uint8_t buttonPin, boolean active, boolean internalPullup);
    void Update();
    int clicks;                   // button click counts to return
    boolean depressed;            // the currently debounced button (press) state (presumably it is not sad :)
    long debounceTime;
    long multiclickTime;
    long longClickTime;
  private:
    uint8_t _pin;                 // Arduino pin connected to the button
    boolean _activeHigh;          // Type of button: Active-low = 0 or active-high = 1
    boolean _btnState;            // Current appearant button state
    boolean _lastState;           // previous button reading
    int _clickCount;              // Number of button clicks within multiclickTime milliseconds
    long _lastBounceTime;         // the last time the button input pin was toggled, due to noise or a press
};

// -------- end clickButton.h --------


// -------- clickButton.cpp --------

ClickButton::ClickButton(uint8_t buttonPin)
{
  _pin           = buttonPin;
  _activeHigh    = LOW;           // Assume active-low button
  _btnState      = !_activeHigh;  // initial button state in active-high logic
  _lastState     = _btnState;
  _clickCount    = 0;
  clicks         = 0;
  depressed      = false;
  _lastBounceTime= 0;
  debounceTime   = 20;            // Debounce timer in ms
  multiclickTime = 250;           // Time limit for multi clicks
  longClickTime  = 1000;          // time until long clicks register
  pinMode(_pin, INPUT);
}


ClickButton::ClickButton(uint8_t buttonPin, boolean activeType)
{
  _pin           = buttonPin;
  _activeHigh    = activeType;
  _btnState      = !_activeHigh;  // initial button state in active-high logic
  _lastState     = _btnState;
  _clickCount    = 0;
  clicks         = 0;
  depressed      = 0;
  _lastBounceTime= 0;
  debounceTime   = 20;            // Debounce timer in ms
  multiclickTime = 250;           // Time limit for multi clicks
  longClickTime  = 1000;          // time until long clicks register
  pinMode(_pin, INPUT);
}

ClickButton::ClickButton(uint8_t buttonPin, boolean activeType, boolean internalPullup)
{
  _pin           = buttonPin;
  _activeHigh    = activeType;
  _btnState      = !_activeHigh;  // initial button state in active-high logic
  _lastState     = _btnState;
  _clickCount    = 0;
  clicks         = 0;
  depressed      = 0;
  _lastBounceTime= 0;
  debounceTime   = 20;            // Debounce timer in ms
  multiclickTime = 250;           // Time limit for multi clicks
  longClickTime  = 1000;          // time until "long" click register

  // Turn on internal pullup resistor if applicable
  if (_activeHigh == LOW && internalPullup == CLICKBTN_PULLUP)
    pinMode(_pin, INPUT_PULLUP);
  else
    pinMode(_pin, INPUT);
}



void ClickButton::Update()
{
  long now = (long)millis();      // get current time
  _btnState = digitalRead(_pin);  // current appearant button state

  // Make the button logic active-high in code
  if (!_activeHigh) _btnState = !_btnState;

  // If the switch changed, due to noise or a button press, reset the debounce timer
  if (_btnState != _lastState) _lastBounceTime = now;


  // debounce the button (Check if a stable, changed state has occured)
  if (now - _lastBounceTime > debounceTime && _btnState != depressed)
  {
    depressed = _btnState;
    if (depressed) _clickCount++;
  }

  // If the button released state is stable, report nr of clicks and start new cycle
  if (!depressed && (now - _lastBounceTime) > multiclickTime)
  {
    // positive count for released buttons
    clicks = _clickCount;
    _clickCount = 0;
  }

  // Check for "long click"
  if (depressed && (now - _lastBounceTime > longClickTime))
  {
    // negative count for long clicks
    clicks = 0 - _clickCount;
    _clickCount = 0;
  }

  _lastState = _btnState;
}

// -------- end clickButton.cpp --------


/* ClickButton library demo

  OUtput a message on Serial according to different clicks on one button.
  
  Short clicks:

    Single click - 
    Double click - 
    Triple click - 
    
  Long clicks (hold button for one second or longer on last click):
    
    Single-click - 
    Double-click - 
    Triple-click - 

  2010, 2013 raron
 
 GNU GPLv3 license
*/


// the Button
const int buttonPin1 = 4;
ClickButton button1(buttonPin1, LOW, CLICKBTN_PULLUP);

// Button results 
int function = 0;


void setup()
{
  Serial.begin(9600);
  pinMode(D4, INPUT_PULLUP);

  // Setup button timers (all in milliseconds / ms)
  // (These are default if not set, but changeable for convenience)
  button1.debounceTime   = 20;   // Debounce timer in ms
  button1.multiclickTime = 250;  // Time limit for multi clicks
  button1.longClickTime  = 1000; // time until "held-down clicks" register
}


void loop()
{
  // Update button state
  button1.Update();

  // Save click codes in LEDfunction, as click codes are reset at next Update()
  if (button1.clicks != 0) function = button1.clicks;
  
  if(button1.clicks == 1) Serial.println("SINGLE click");

  if(function == 2) Serial.println("DOUBLE click");

  if(function == 3) Serial.println("TRIPLE click");

  if(function == -1) Serial.println("SINGLE LONG click");

  if(function == -2) Serial.println("DOUBLE LONG click");

  if(function == -3) Serial.println("TRIPLE LONG click");
  
  function = 0;
  delay(5);

}
13 Likes

Very nice @peekay123. I will definitely grab this and use it on things :smile: Didn’t know about this library, thanks for sharing.

The HW initialization in Class constructors is a newly known issue… I was beating my head against the desk trying to figure out some stuff and stumbled on it. What I’ve been doing currently to fix it is pull out the hardware bits from the constructors, and put them into a .init() method which you call from your setup() {}

void ClickButton::init() {
// Turn on internal pullup resistor if applicable
  if (_activeHigh == LOW && internalPullup == CLICKBTN_PULLUP)
    pinMode(_pin, INPUT_PULLUP);
  else
    pinMode(_pin, INPUT);  
}

You would need to save the internalPullup as a private variable though first _internalPullup to use in init();

1 Like

Great idea BDub! Still getting use to the Spark “restrictions”. I’ll add that to my github posts I am building up.

:smiley:

2 Likes

Hi peekay123,
I like the clickbutton above, I will try it out but…I currently have a simple application counting the number of button pushes. It works well until my core loses connectivity with the cloud. A reset occurs to reconnect the core but my total count goes back to “0”. Do you know how I could keep my previous count?
Thanks!
Marc

Dup, it sounds like your loop() code does not “end” and return control to the core firmware enough. Can you post the code so I can take a look? :smile:

Thanks peekay123!

I am using the watchdog fix to keep the core connected but my value keeps going back to 0 everytime the dog barks

#define FEED_ID “1212121212” //note: fake id here…
#define XIVELY_API_KEY “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx” //note: fake key here

TCPClient client;

// this constant won’t change:
const int buttonPin = D2; // the pin that the pushbutton is attached to
const int ledPin = D0; // the pin that the LED is attached to
unsigned long LastUpTime = 0;

// Variables will change:
int buttonPush = 0; // counter for the number of button presses
int buttonState = 0; // current state of the button
int lastButton = 0; // previous state of the button

void setup()
{
Spark.variable(“buttonPush” , &buttonPush, INT);
Spark.variable(“buttonState” , &buttonState, INT);
Spark.variable(“lastButton” , &lastButton, INT);
// initialize the button pin as a input:
pinMode(buttonPin, INPUT);
// initialize the LED as an output:
pinMode(ledPin, OUTPUT);
// initialize serial communication:
ledStatus(2, 100); //blink
Serial.begin(9600);

}

void loop() {
// read the pushbutton input pin:
buttonState = digitalRead(buttonPin);

// compare the buttonState to its previous state
if (buttonState != lastButton) {
// if the state has changed, increment the counter
if (buttonState == HIGH) {
// if the current state is HIGH then the button
// wend from off to on:
buttonPush++;
Serial.println(“on”);
Serial.print("number of button pushes: ");
Serial.println(buttonPush);
}
else {
// if the current state is LOW then the button
// wend from on to off:
Serial.println(“off”);
}
}
// save the current state as the last state,
//for next time through the loop
lastButton = buttonState;

// turns on the LED every four button pushes by
// checking the modulo of the button push counter.
// the modulo function gives you the remainder of
// the division of two numbers:
if (buttonPush % 4 == 0) {
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}

int temp_calc = buttonPush;

if (millis()-LastUpTime>2000)
{
xivelycount(temp_calc);
LastUpTime = millis();
}

}
void xivelycount(int count) {

//Serial.println(“Connecting to server…”);
if (client.connect(“api.xively.com”, 8081))
{

    // Connection succesful, update datastreams
    client.print("{");
    client.print("  \"method\" : \"put\",");
    client.print("  \"resource\" : \"/feeds/");
    client.print(FEED_ID);
    client.print("\",");
    client.print("  \"params\" : {},");
    client.print("  \"headers\" : {\"X-ApiKey\":\"");
    client.print(XIVELY_API_KEY);
    client.print("\"},");
    client.print("  \"body\" :");
    client.print("    {");
    client.print("      \"version\" : \"1.0.0\",");
    client.print("      \"datastreams\" : [");
    client.print("        {");
    client.print("          \"id\" : \"countingsheep\",");
    client.print("          \"current_value\" : \"");
    client.print(count);
    client.print("\"");
    client.print("        }");
    client.print("      ]");
    client.print("    },");
    client.print("  \"token\" : \"0x123abc\"");
    client.print("}");
    client.println();

    ledStatus(3, 500);        
} 
else 
{
    // Connection failed
    //Serial.println("connection failed");
    ledStatus(3, 2000);// tried 1000 instead on 2000 to keep the core alive
}


if (client.available()) 
{
    // Read response
    //char c = client.read();
    //Serial.print(c);
}

if (!client.connected()) 
{
    //Serial.println();
    //Serial.println("disconnecting.");
    client.stop();
}

client.flush();
client.stop();

}
void ledStatus(int x, int t)
{
for (int j = 0; j <= x-1; j++)
{
digitalWrite(ledPin, HIGH);
delay(t);
digitalWrite(ledPin, LOW);
delay(t);
}
}

Dup, your “code” section was not correctly set in your reply. However, I don’t see anything in your code for the ClickButton library! Perhaps you posted the wrong code? :question:

Hi Peekay123,
you are right, the code does not include the clickbutton library yet. I have a simple button code and so I am trying to figure out how to keep the last count even with a core reset. I have lots to learn :smile:
thanks for the help
Dup

I started to write my own debounce class, but I decided to do a quick search of the forums in case someone had already done this. @peekay123 has!

However, I’m getting some strange compiler errors:

ClickButton.cpp:5:22: error: 'LOW' was not declared in this scope
ClickButton.cpp: In member function 'void ClickButton::Update()':
ClickButton.cpp:59:27: error: 'millis' was not declared in this scope
ClickButton.cpp:60:31: error: 'digitalRead' was not declared in this scope```

It was also choking on ```uint8_t``` and ```boolean```.  I changed those to ```int``` and ```bool``` respectively.

It seems like the Sparkulator is compiling user-included files before the core firmware, so things like ```LOW``` and ```millis()``` are not yet available.  It compiles just fine if I include the ```ClickButton.h``` and ```ClickButton.cpp``` file contents at the top of my user code.

It probably just needs

#include "application.h"
1 Like

Bazinga!

…Clickity-click for character count…

1 Like

I love that library. One button can do six things! :ok_hand:

BTW, thanks bko for helping wgbartley.

1 Like

Or, it can handle states for a single button quite well, too! Sure did make life a little easier.

And, yes, add a "Thanks!" to that "Bazinga!"

1 Like

Is it possible to use it with a button configured as pulldown ?

Yes, creating a button with:

clickButton button(buttonPin, HIGH);

will make a button that is active HIGH so you will need an external pull-down resistor. :smile:

1 Like

@peekay123 many THANKS for adapting the library, now able to handle multiple functions with 1 button :grinning:

@JohnC, I LOVE that library and not enough people use it IMO!

1 Like

Hello,

Please if any help me completing my project i possibly want this.
There should be a button to change the device status (only one button please).
If the device is already off, pressing this button should bring the device in running mode.
If the device is on, pressing this button for 5 seconds should bring the device in configuration mode
If the device is in configuration mode, pressing the button for 5 seconds should restart the device, and pressing the button 3 times should set the variables to default values.
If the device is on, pressing the button for 10 seconds turn of the device
now please tell me how can i do that i weak in programming and i seriously want to do this.
thanks in advance for helping

What have you tried so far?

Hello, I want to utilize the “DOUBLE LONG Click” for a button to control some LEDs. I want the LEDs to Loop until the button is released. This means the button will be double clicked and held down; the LEDs will Loop; then when the button is released the LEDs will stop Looping.

if(function == -2) Serial.println(“DOUBLE LONG click”);
digitalWrite(1, HIGH);
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
delay(delayTime);
digitalWrite(1, LOW);
digitalWrite(2, LOW);
digitalWrite(3, LOW);
delay(delayTime);