5
\$\begingroup\$

I'm quite new to AVR programming (avr-gcc).

To react on button-press, I am using a PCINT ISR with the internal pull-up resistor enabled like this:

ISR(PCINT0_vect) {
    if (bit_is_clear(PINB, PB0)) {
        _delay_ms(40);
        if (bit_is_clear(PINB, PB0)) {
            // do something
        }
    }
}

It works okay but I guess it is not particularly smart to spend 40 milliseconds in an ISR just to debounce a button.

I have read Ganssle's Debouncing Contacts Part 2 which is a bit too advanced for my current level of experience, and there is one statement that I am not sure about:

The undebounced switch must connect to a programmed I/O pin, never to an interrupt.

So is my approach to connect the button to a PCINT already wrong? I haven't gotten around timers yet, but should I rather use some kind of timed interrupt to evaluate the button state every 1 ms or so instead of the button triggering an interrupt directly?

I can live with my debouncing not being very smart for now, but at least I want to get the basics of reacting on a button-press right.

\$\endgroup\$

4 Answers 4

6
\$\begingroup\$

I suggest you get into the habit of doing this sort of thing with a timer interrupt and polling the switch rather than trying to use interrupts. 1kHz is fast enough.

As others have said the switch will generate multiple edges at unpredictable times which could cause problems or interaction with other routines.

You can write non-blocking routines that debounce the switch.

The cardinal rule is that you don't spend any more time in an ISR than you have to- read the state of the switch, if it's changed, shove a number in a static variable for the number of milliseconds before accepting a state change and decrement the number each time you service the interrupt and the state has not changed from the last read but is different from the last accepted state. When you get a stable state change shove it into a queue for the background routine to deal with (queue could have a depth of 1 in some cases). Update the last accepted state and done.

\$\endgroup\$
1
  • \$\begingroup\$ I will go for a timer interrupt, it seems to be the common way to do it, and try to implement something like you suggest. \$\endgroup\$ Apr 16, 2015 at 9:57
9
\$\begingroup\$

Torsten, yes, introducing a 40ms delay into the ISR is a wrong idea. Usually, buttons are polled in the main() loop.

Interrupts are good when you have to detect an event with a very small latency, or when an event is very brief. Button presses occur on the scale of hundreds of ms. An operator will hold the button down for 50ms to 100ms. Then if your device reacts within 10ms or within 70ms, the operator will not feel the difference. That's plenty slow, so it doesn't have to be detected via an interrupt.

A button could be connected to the interrupt if you want to wake up the microcontroller from sleep via an interrupt. After waking up you would poll the button.

\$\endgroup\$
1
  • \$\begingroup\$ If I poll in the main loop, I have to be sure work done there doesn't take longer than, say, 10ms, right? So if I am not sure, I would use a timer interrupt? \$\endgroup\$ Apr 16, 2015 at 10:08
2
\$\begingroup\$

The problem with interrupts and buttons is that button gives a lot of edges, and thus one button press might invoke an interrupt dozens of times. This is usually not what you want. Also, some MCUs do not like very short pulses on interrupt input, but this is not the case for AVRs.

In your case, if you really want to use PCINT, you can do it if you disable further interrupts in the handler. This way, only the very first press will be detected. Of course, you will want to eventually re-enable PCINT interrupt so you can detect next button press. This normally means you need a timer, and when you have a timer, you might as well keep it free-running and poll buttons in handler -- this will be easier.

However, the 'button to interrupt pin' can be occasionally useful if you wanted to start a timer anyway, for example for a short beep on a keypress. In this case, the scenario goes like this:

  • User presses a button, interrupt fires
  • Button interrupt handler disables button interrupts, turns on beeper and starts a timer
  • When timer expires, timer interrupt turns off beeper and re-enables button interrupts
\$\endgroup\$
1
  • \$\begingroup\$ The keypress beep is a nice example. If using PCINT is not a good way then I don't want to use it, the next chapter in my book is about timers so I will go that way. \$\endgroup\$ Apr 16, 2015 at 9:53
1
\$\begingroup\$

Trying to follow the answers here and after reading up about timers, I would like to throw my own simple solution in the ring.

With the CPU of the ATmega328P running at 1 MHz, I am setting up the 8-bit timer 0 in normal mode with a clock prescaler /64 so it overflows every 16 ms:

TCCR0B |= (1 << CS00) | (1 << CS01);

and I am enabling the overflow interrupt of timer 0:

TIMSK0 |= (1 << TOIE0);

in the corresponding ISR I am checking the button state:

volatile bool buttonPressed = false;    

ISR(TIMER0_OVF_vect) {
    if (bit_is_clear(PINB, PB0) && ! buttonPressed) {
        buttonPressed = true;
        // do something
    } else if (bit_is_set(PINB, PB0)) {
        buttonPressed = false;
    }
}

Still not an elaborate debouncing algorithm, but now there's no time wasted in the ISR, response time is nice and no button bounce issues no matter how hard I try with the different buttons I have here.

UPDATE:

After some really extensive testing, it seems to me that polling every 31 ms debounces perfectly, even better than with 16 ms - maybe because 16 ms is a bit close to the nominal bounce time of 15 ms of the buttons I use.

To achieve the 32 Hz I've changed the timer from overflow mode to clear timer on compare match mode with a prescaler/256 which gives 3.9 kHz @ 1 MHz and set the respective output compare register to 122.

\$\endgroup\$
0

Your Answer

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

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