Main Site Documentation

How to differentiate between a button press and button hold on an input port?


#1

Hello GHI folks,

I have a button attached to an input port on a FEZ Panda.
I would like to perform one action if the button is pressed for < 1 second
I would like to perform a different action if the button is held down for >= 1 second.

I understand about the rising edge and falling edge of the button press.
I also understand about timers.

I am not sure if the easiest approach is to start a timer when the button is pressed and stop the timer when the button is released. I then look to see if elapsed time is less than or greater than 1 second. This seems like a lot of work and not the best approach.

I would appreciate any recommendations from you gurus!

Thanks much,
Larry Scott
Walnut Creek, CA


#2

Hi Larry,

First up, buttons are “noisy”. Getting a clear-cut start and end is not guaranteed. So a button press might actually look like a series of short press-releases, until it stabilises, then becomes a solid press. Often times you can approach this as a signal filtering problem in hardware (search for “debounce”) or you can cater for it in software.

A timer approach is one way, but as you say can be tricky to get the logic right. Alternately you can use the time captured events with a state machine that continues to scan the status and elapsed time, but that can be computationally expensive if you have a complex app.

There are some other gotchas I can see for using a timer. You need to cater for the scenario where if you start a timer and at 1sec you check the button state again, and the user has released and pressed again exactly at that point - so that becomes more complex state machine to track.

I am sure there are other people who have approached this in some way who may be able to assist more, but I suspect it’s worth clarifying your usage example. Do you want the function to change at the one second mark, or at the release? I was assuming your scenario might be something like a volume up button; press it, and the volume increments one, but after holding it for a second it starts behaving differently and increments in 5. But I can see how it could be some other behaviour, like press a button and it pauses the music playback (at the point of release) but if the button is held down for greater than one second then it goes into random play mode (for example; that wouldn’t be my UI decision but it’s just trying to demonstrate the difference in the two ideas)


#3

@ Larry1 - I don’t thnik you need to use timers.

I would register for both the falling and rising edge events of an InterruptPort with glitch filter on and a pull up resistor. Ground the port with the button.

When you press the button, the falling edge will occur. In the falling edge event handler,
the DateTime of the event is passed. Store the time.

When the button is released, the rising edge event is raised. In this handler, subtract the saved depress start time from the time passed to the event. This will give you a TimeSpan object which contains the elapsed time between the press and release.


#4

@ Mike, that’ll work well if the usage scenario is the pause/random play one I talk about, but doesn’t cater so well for the volume change one, because at the pre-determined time when the function changes you need to act differently. If you have a polling loop that checks for the button press and release end-points, and if time from pressed to now is greater than threshold (calculating time diffs each cycle I guess) then change behaviour. But in that scenario a timer might actually be more elegant?

Hmm, makes me want to dig out a UFO (UFO=UnFinished Object, as SWMBO calls them) that needed to deal with this kind of problem


#5

@ Mike -

You can also set 2 timers :

1 for the “less than one second”, 1 for the “more than one second”.

to do this, simply use the first timer with a duetime of 900ms, and a timeout of Infinite (this will raise the timer only once per event) and the second timer with a due time of 1100ms and a timeout Inifinite (only one raise per event).

Then regarding the events, when using an InterruptPort with the EdgeBoth parm set, put in the Interrupt event :


if(data2 == 1) //Pushed
{
timer1.Change(Callback1, 900, Timeout.Infinite);
timer2.Changed(Callback2, 1100, Timeout.Infinite);
}
else //Released
{
timer1.Change(Callback1, Timeout.Infinite, Timeout.Infinite);
timer2.Changed(Callback2, Timeout.Infinite, Timeout.Infinite);
}

With that code, if the button is pushed less than 900ms, no callback is done, between 900ms and 1.1sec, Callback1 is thrown, and over both Calbacks are thrown…

In the meanwhile, releasing the button will put both timer in such a pause mode…

Maybe you will finally decide that Timer1 has no meaning in your case and simply consider Timer2…


#6

@ LouisCpro - best practice depends upon usage. The OP asked for a simple depress time solution. Best practice is usually the simplest solution that works.

your code does not address the requirements of detecting less than or grEater than a second.


#7

@ Mike - Changed in the meantime :wink:

I did not understood about a depress time but more an HOLD time…Made an error ?


#8

@ Mike -

Also, I do not agree when you say that my use case does not comply.

In your case, if the guy push the button and sleep on it for a while, then you’ll be waiting for him to release to take care of the elapsed time.

In my case, even if he keep his finger on it for the eternity, the HOLD for more than 1 second will always been raise after 1 second of elapsed time…It is really an "AT LEAST’ timer…

Dont you think ?


#9

I still think there’s two ways of interpreting what the OP wants to do, so we need clarity to make sure we know what approaches work, where.


#10

Agree with Brett the complexity is largely affected by whether you want to have an event fired at the 1 second mark or when the button is released. I.e. if you hold it down for 5 seconds is it ok to fire the “Long” event at 5 seconds.

If you want the simple case i.e. when the button is released this seems to do the trick:

using System;
using Microsoft.SPOT;
using GTM = Gadgeteer.Modules;

namespace GadgeteerApp1
{
    /// <summary>
    /// derives from button and adds new functionality to fire one of two events
    /// when a button is released depending upon whether it was held down for more than 1 seconds or not
    /// </summary>
    class MyButton : GTM.GHIElectronics.Button
    {
        private DateTime _lastPressTime;


        public MyButton(int socketNumber)
            : base(socketNumber)
        {
            _lastPressTime = DateTime.Now;


            this.ButtonPressed += MyButton_ButtonPressed;
            this.ButtonReleased += MyButton_ButtonReleased;
        }

        /// <summary>
        /// Fired when the button is released after being held for less than 1 second
        /// </summary>
        public event ButtonEventHandler ButtonShortPress;

        /// <summary>
        /// Fired when the button is released after being held for more than 1 second
        /// </summary>
        public event ButtonEventHandler ButtonLongPress;

        /// <summary>
        /// Stores the time when a button is pressed
        /// </summary>
        /// <param name="sender">not used</param>
        /// <param name="state">not used</param>
        void MyButton_ButtonPressed(GTM.GHIElectronics.Button sender, GTM.GHIElectronics.Button.ButtonState state)
        {
            _lastPressTime = DateTime.Now;
            Debug.Print("Button pressed at " + _lastPressTime.ToString());
        }

        /// <summary>
        /// Checks how long a button was pressed for and fires the approriate event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="state"></param>
        void MyButton_ButtonReleased(GTM.GHIElectronics.Button sender, GTM.GHIElectronics.Button.ButtonState state)
        {
            Debug.Print("Button released at " + DateTime.Now.ToString());
            if (DateTime.Now >= _lastPressTime.AddSeconds(1))
            {
                if (ButtonLongPress != null)
                {
                    Debug.Print("Long press");
                    ButtonLongPress(this, ButtonState.Released);
                }
            }
            else
            {
                if (ButtonShortPress != null)
                {
                    Debug.Print("Short press");
                    ButtonShortPress(this, ButtonState.Released);
                }
            }
        }

    }
}

My test code:

 public partial class Program
    {
        private MyButton button;

        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            /*******************************************************************************************
            Modules added in the Program.gadgeteer designer view are used by typing 
            their name followed by a period, e.g.  button.  or  camera.
            
            Many modules generate useful events. Type +=<tab><tab> to add a handler to an event, e.g.:
                button.ButtonPressed +=<tab><tab>
            
            If you want to do something periodically, use a GT.Timer and handle its Tick event, e.g.:
                GT.Timer timer = new GT.Timer(1000); // every second (1000ms)
                timer.Tick +=<tab><tab>
                timer.Start();
            *******************************************************************************************/


            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
            Debug.Print("Program Started");
            button = new MyButton(11);
            button.ButtonLongPress += button_ButtonLongPress;
            button.ButtonShortPress += button_ButtonShortPress;
        }

        void button_ButtonShortPress(GTM.GHIElectronics.Button sender, GTM.GHIElectronics.Button.ButtonState state)
        {
            multicolorLed.TurnRed();
        }

        void button_ButtonLongPress(GTM.GHIElectronics.Button sender, GTM.GHIElectronics.Button.ButtonState state)
        {
            multicolorLed.TurnBlue();
        }


    }