4
\$\begingroup\$

I need to detect if a switch has been pressed for longer than a set time, without using any Timer registers in my embedded code.

I am using software debouncing in the following manner to detect a normal switch press.

int buttonPress (void)
{
  static int downCount= 0; // static = value isn't lost between calls.

  int currentButton = ((IO0PIN & 0x00000200) == 0x00000200); 

  if (currentButton ) { // button is down

    if (downCount == 5) { // button has been down for 5 counts
      downCount++;      // increase the count so we don't trigger next time
      return 1;
    } else {              // some count other than 5
      if (downCount < 5)  // increase if it's less than 5
        downCount++;
    }
  } else   {    // button is up
    downCount = 0; 
  }
  return 0;
}   

However, I would like to detect whether this button has been pressed for 5 seconds or longer. The problem I am facing is that I cannot seem to be able to count up to 300,000 and if this is not reached, fall back on the original downCount of 5 to mark as a normal button press.

(Since the clock speed for my micro-controller is at 60 MHz, then it will take 5 seconds to reach 300,000.)

I have played around with some logic as follows, however in the case shown below,due to the introduction of the while statement, I am counting up to 300,000 irrespectively of how long the button has been pressed.

if (currentButton ) {   // button is down

  if (downCount == 5) { // button has been down for 5 counts
    downCount++;      // increase the count so we don't trigger next time
    while (downCount > 5) { 
      if (debounce == 300000) {
        flag_5SEC = 1;
      } else if (debounce < 300000) {
         debounce++;
      }
    }
    return 1;
  } else {              // some count other than 5
    if (downCount < 5)  // increase if it's less than 5
      downCount++;
  }
}

Any tips or suggestions on how to go about this task?

Note: I want to detect for a longer switch press explicitly in the debouncing code above, since doing anything else may have adverse side-affects on the rest of the code, which someone else had previously wrote.


I have managed to complete the aforementioned task using timers, however, since then, I have used all timer registers (and match resisters that are available) for more important tasks within the same project.

I do not need to detect an accurate time delay, simply if the button has been down for longer than, say, 2-3 seconds.

Comment by @DiBosco clarifies further:

So what you're saying is that if you get a switch pressed for, say, twenty counts and it is released that acts as normal press that does one function, and if it is pressed and held down for two seconds or so it carries out a different function...

\$\endgroup\$
14
  • 3
    \$\begingroup\$ It looks to me like once downCount hits five you're just stuck in that while loop. I assume you're calling this from the main infinite loop? You need a better way of coming into the function from the main loop, incrementing the counter and coming out if you've not hit your target count. However, unless you do this from a timer (you could set a flag in an interrupt even if it's a shared interrupt) and come in here from the main loop when that flag is set, it's going to wildly inaccurate. \$\endgroup\$
    – DiBosco
    Commented Feb 20, 2018 at 16:38
  • 2
    \$\begingroup\$ PS When you're replying to someone, make sure you use their name so they know you have replied. :) \$\endgroup\$
    – DiBosco
    Commented Feb 20, 2018 at 16:39
  • \$\begingroup\$ @DiBosco, thanks for your insightful comment, yes I am calling this from the main infinite loop. I agree with all your statements mentioned, and I am trying to figure out a better way to do all the things mentioned. Would the method I am trying to use (without timers) be any good if I simply want to detect a button press longer than, say, 2 seconds, instead of an exact value? \$\endgroup\$
    – rrz0
    Commented Feb 20, 2018 at 16:42
  • \$\begingroup\$ Sorry I misread the code.. not sure how you are calling that though... \$\endgroup\$
    – Trevor_G
    Commented Feb 20, 2018 at 17:36
  • 1
    \$\begingroup\$ How are you going to detect "5 seconds or longer" if you have no idea how much time has elapsed? It's all very well saying you will count instructions, but if you add something to the counting loop, it will be out. \$\endgroup\$ Commented Feb 21, 2018 at 6:47

4 Answers 4

15
\$\begingroup\$

I have managed to complete the aforementioned task using timers, however, since then, I have used all timer registers (and match resisters that are available) for more important tasks within the same project.

In that case, you're going about this all wrong. Unless you absolutely need the high resolution, low jitter, or other hardware features of the hardware timers, you should be doing your timing in software, using one of the hardware timers to generate continuous periodic interrupts that you can count in the ISR.

A "heartbeat interrupt" with a period of anywhere between 100 µs and 100 ms (or more) is a common feature of embedded systems. This allows you to create an arbitrary number of software timers.

Even if you're already using all of the hardware timers, as long as at least one of them is doing something periodic, such as generating a PWM signal or creating a clock for a serial interface, you can enable interrupts on it and use them for your software timebase. The period might not be a "convenient" value, but that is a simple scaling problem that the software can deal with.

\$\endgroup\$
6
  • 2
    \$\begingroup\$ Like minds :) 341 to go..... drum roll! \$\endgroup\$
    – Trevor_G
    Commented Feb 20, 2018 at 16:50
  • \$\begingroup\$ So, if I have a Timer interrupt with a period of 1 ms, I can simply set a flag when the button has been pressed, and then, while the flag is high, count inside the interrupt. Is this statement correct? \$\endgroup\$
    – rrz0
    Commented Feb 20, 2018 at 17:30
  • 1
    \$\begingroup\$ @Rrz0: Yes, but you actually need two counters: One to count the 10 ms or so "debounce" time in order to determine that the switch has changed state, and another to count the 5000 ms "hold" time for the switch. \$\endgroup\$
    – Dave Tweed
    Commented Feb 20, 2018 at 18:02
  • 2
    \$\begingroup\$ Dave, actually you only need one counter and two count comparison levels. If tick clock is 1mS, count of 10 = debounced, count of 5000 = held, then stop counting. \$\endgroup\$
    – Trevor_G
    Commented Feb 20, 2018 at 18:07
  • 1
    \$\begingroup\$ @Trevor_G: There are many ways to implement multiple timers when you have a periodic interrupt. Having a separate counter for each one is probably the simplest to understand. A single counter with multiple comparators is generally more efficient, but trickier to explain. The choice depends heavily on other aspects of the system. \$\endgroup\$
    – Dave Tweed
    Commented Feb 20, 2018 at 20:31
6
\$\begingroup\$

Your code seems to be a bit off, and is hard to really understand since it is not clear where that while loop sits with respect to the interrupt or whatever sets the DownCount function.

You also seem to be incrementing the debounce counter in two places in the while loop, which just seems wrong.

Your in-line solution is problematic for many reasons, not the least of which is your time will vary depending on what else is happening in the micro unless you stop and tight loop while you count. But stopping to count for five seconds is also a bad idea.

This kind of function is normally handled in micros by using a tick-counter aka heart-beat. Basically it is common practice to reserve one timer to act as a clock for your code. The timer would generate a regular interrupt, perhaps 10 times a second.

That interrupt handler can take care or numerous housekeeping functions like debouncing switches, flashing LEDs, checking for communication timeouts etc. It can also include counting how long a button has been pressed. When it has been pressed an appropriate length of time, said handler then calls the appropriate handler for that event.

This method lets you use a single timer for numerous functions.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks for pointing out the mistake in my code. That is indeed wrong. \$\endgroup\$
    – rrz0
    Commented Feb 20, 2018 at 16:47
  • \$\begingroup\$ I have started to understand that the method mentioned above is indeed the way to go. Do you suggest I take care of the software debouncing fully inside the interrupt? \$\endgroup\$
    – rrz0
    Commented Feb 20, 2018 at 17:45
  • \$\begingroup\$ @Rrz0 yes you can handle the debounce in the tick-clock interrupt too based on flags from the button interrupt if there is one or by polling the IO line, and flags and variables set from previous state of the tick-clock. \$\endgroup\$
    – Trevor_G
    Commented Feb 20, 2018 at 17:49
  • \$\begingroup\$ @Rrz0 I should also mention since it is unclear from your code, you need to debounce both the button press AND the button release. Surprisingly, switches bounce when you let go too. \$\endgroup\$
    – Trevor_G
    Commented Feb 20, 2018 at 18:03
4
\$\begingroup\$

Since buttons are operated by very slow humans, relative to the processor speed, you can get away with slow polling. Every 10 milliseconds for example.
You can easily create one function to handle many buttons, with many press patterns and hold actions.

A very simple version of what I often use looks like this. It only has a 5 seconds hold flag, none of the presses counting and flag acknowledging (flags will get set immediately after clearing again).
But with a bit of creativity you can add many things this way. But it will become a bit lenghty.
You can even add machine input channels and have them debounced this way.

#define BUTTON_INTERVAL 10
#define BUTTON_COUNT    1

/* Typedef for a button */
typedef struct button_s {
    void* port;         /* Use GPIO port type, whatever your platform uses, maybe even via bitbanding */
    uint32_t hi_time;   /* Time high */
    uint8_t pin;        /* Pin number */
    uint8_t flags;      /* Output flags */
} button_t;

/* The array of all buttons */
button_t buttons[BUTTON_COUNT];

/* Handle button polling */
void buttonHandler(void){
    uint8_t i;
    for(i=0;i<NR_OF_BUTTONS; i++){
        button_t *b = buttons[i];
        uint8_t state = *b->port & (1<<b->pin);
        if(state){
            // Saturating unsigned 32-bit addition 
            // your cpu might have an instruction for this eg: arm __uqadd(a,b))
            if(b->hi_time < 0xFFFFFFFF) b->hi_time += BUTTON_INTERVAL;
        }else{
            b->hi_time = 0;
        }
        // One of many possible flags comparisons:
        if( b->hi_time >= 5000 ){
            b->flags |= BUTTON_HI_5S;
        }
    }
}

/* Example of flag usage */
void waitForButton(){
    if( button[0]->flags & BUTTON_HI_5S ){
        button[0]->flags &= ~BUTTON_HI_5S;
        // ..stuff..
    }
}

Key points are, if you're going on your own:
- A structure for all data, you can even remap keys on the fly if you'd like.
- Infinite buttons using structures. Never write this code again!
- Saturating timers, they do not overflow.
- Flags, easily replaced for operating system calls.

\$\endgroup\$
3
\$\begingroup\$

If I'm understanding your needs correctly, you want something like this:

void SwitchCheck(void)
{   
    DebounceTimer = false; // If using timer flag
    if (currentButton) 
        {
        debounce++; 
        }
    else 
        {
        if (debounce > NORMAL_OPERATION)
              {
              // Do normal stuff here
              }
        debounce = 0;
        } 
    if (debounce == MAX_TIMEOUT) 
         {
         // Do your held down thing here
         }
}

Call this from your main loop. Ideally only call it after you have set a bit in a timer interrupt correctly, so that you have some accurate timing.

I have many, many times over the years done switch debounce without timers (although not really like this), just calling from the main loop and it does work. However, if you are looking for accuracy in your five second timing, this will not be good enough.

Further Edit:

In your timer ISR:

TimerISR()
{
DebounceTimer = true;
// Other stuff    
}

In main:

if (DebounceTimer)
    SwitchCheck();

Or just call it from main without the flag checking if you're not using a timer and empirically experiment with MAX_TIMEOUT.

\$\endgroup\$
6
  • \$\begingroup\$ I can confirm that I am not looking for accuracy in this particular case. \$\endgroup\$
    – rrz0
    Commented Feb 20, 2018 at 18:00
  • \$\begingroup\$ @JohnSmith I'm not sure what you mean. (Not being sarcastic, really not quite sure what you're getting at.) It will do nothing until it hits MAX_TIMEOUT and then it will execute what needs to be executed and the njust drop out. It does run the risk if it is held down for a VERY long time that DebounceTimer goes right round and re-executes. You might need a check to ensure that doesn't happen. However, it's just a guide rather than a complete solution. I may have completely missed something though in what the OP was after. \$\endgroup\$
    – DiBosco
    Commented Feb 20, 2018 at 18:20
  • \$\begingroup\$ @DiBosco, reading the above, no you have not completely missed what I'm after. However, I want to be able to detect a normal switch press where TIMEOUT would be much less than MAX_TIMEOUT. So,since nothing happens until it hits MAX_TIMEOUT, there is some adjusting to be done. \$\endgroup\$
    – rrz0
    Commented Feb 20, 2018 at 19:06
  • \$\begingroup\$ @Rrz0 So what you're saying is that if you get a switch pressed for, say, twenty counts and it is released that acts as normal press that does one function, and if it is pressed and held down for two seconds or so it carries out a different function? \$\endgroup\$
    – DiBosco
    Commented Feb 20, 2018 at 20:06
  • 1
    \$\begingroup\$ @Rrz0 OK, added extra. Haven't tested it, but I think that'll do what you want. You need to try different values of MAX_TIMEOUT and NORMAL_OPERATION for it to be what you want. \$\endgroup\$
    – DiBosco
    Commented Feb 20, 2018 at 20:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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