NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Debouncing on interrupt button press

March 28, 2013
by lnino
lnino's Avatar

Hey guys,

it has been some time ago when I made my first attempts with Interrupts. So I decided to give it another try.

I have a spinning 7 segment display and I want to interrupt it when I press a button (PC5) to do another operation.

I trasposed a interrupt code of another thread and it works great. The only thing is I think a made somehting wrong with the debouncing.

Something when I press the button the operation executes twice instead of once. This is normally an inidication of a debouce failure. But in my other project the debouncing works like I used it.

Does someone has an idea how it should work?

#define F_CPU 14745600

#include <avr/io.h>
#include <inttypes.h>
#include <avr/interrupt.h>

#include "../mylibs/7segment_U.h"
#include "../mylibs/7segment_U.c"

#include <util/delay.h>

// Prototypes
void spinning_left(int);

int main() {

DDRC &= ~(1<<PC5); // PC5 to input
PORTC |= (1<<PC5); // pull up resistor

PCICR |= (1<<PCIE1);
PCMSK1 |= (1<<PCINT13);

sei();

init_seg();

while(1) {

    spinning_left(40);

    }

 }

ISR(PCINT1_vect) {

if (PINC&(1<<PC5)) {
    segment('1',400);
    segment('2',400);

        _delay_ms(400); // Debouncing
    }
}

void spinning_left(int speed) {
    segment('U',speed);
    segment('V',speed);
    segment('W',speed);
    segment('X',speed);
    segment('Y',speed);
    segment('Z',speed);
}

image

March 28, 2013
by esoderberg
esoderberg's Avatar

Inino,

This blurb from AVR Freaks might explain your results:

Each vector has an associated ??IF?? bit. When the event occurs the ??IF?? bit is set. After completing an opcode the AVR core then checks the ??IF?? bits in vector table order and for the first one it finds to be set it then calls to that interrupts vector handler. If the same even occurs once while that interrupt is being handled the ??IF?? bit for the interrupt is set again. When the ISR performs RETI the interrupt system is re-enabled and a return to the interrupted code is made where ONE opcode will be executed. When that opcode completes the whole process starts again. So you will get one main line opcode executed for each handling of interrupt. If the interrupt event occurs TWICE while the ISR is being handled then ??IF?? does not count interrupts but only says that "one or more occured" so one occurrence will be handled and any other will be lost.

This is why an ISR should always complete in 2us..3us and NEVER take as long a 1ms and definitely not longer than the expected period between interrupting events.

March 29, 2013
by lnino
lnino's Avatar

Hi esoderberg,

thanks for your reply.

And how can I change the code that the ISR takes a shorter time? Reduce the milliseconds in the delay function for the debouncing?

What do I have to change in my code?

March 29, 2013
by esoderberg
esoderberg's Avatar

Inino,

I think the simplest way would be to poll vice using an interrupt.

if (PINC&(1<<PC5))//if button pressed wait...
{_delay_ms(400);

    if (PINC&(1<<PC5)) {//if button still pressed 400 ms later take as single input
    segment('1',400);//or any code you want to execute on good button hit

    segment('2',400);}  }

The down side to this is your code sits around doing nothing for 400 ms every button press. If you wanted something that would run faster and use interrupts, you could have a clock running in the background and use the interrupt to mark the time (at tm) for a start of button hit(not much time executing interrupt code at all), then outside interrupt check:

if((PINC&(1<<PC5)) && ((t-tm)>200)){ //t is current time (in ms), set tm=t in interrupt
 segment('1',400);
 segment('2',400); //or any code you want to execute on good button hit

      tm=t+1300;}//reset time mark so each valid press doesn't result in multiple hits 
                 //If button held for at least 1.5 seconds will then give one hit per 1.5 sec.
   if (!((PINC&(1<<PC5))) tm = t;//reset button clock if button not still pressed

NOT tested code, just my quick swag.

Eric

March 29, 2013
by lnino
lnino's Avatar

Hi esoderberg,

thanks for your reply.

I will test your code example.

Meanwhile I found an awesome code for debouncing a button. I tried it and it works great.

Here it is:

   #define F_CPU 14745600

    #include <avr/io.h>
    #include <inttypes.h>
    #include <avr/interrupt.h>
    #include <util/delay.h>

inline uint8_t debounce(volatile uint8_t *port, uint8_t pin)
{
    if ( !(*port & (1 << pin)) )
    {
        /* Pin pulled to GND, wait 100ms  */
        _delay_ms(50); 
        _delay_ms(50);
        if ( *port & (1 << pin) )
        {
            /* Give the user time to release the button */
            _delay_ms(50);
            _delay_ms(50); 
            return 1;
        }
    }
    return 0;
}

int main(void)
{
    DDRB &= ~( 1 << PB0 );        /* PIN PB0 Input  */
    PORTB |= ( 1 << PB0 );        /* Pullup-Resistor */

    if (debounce(&PINB, PB0))
    {
        //  Do something   }
}
March 29, 2013
by esoderberg
esoderberg's Avatar

Inino,

Posted code below works. The polling code is simpler (with no extra overhead of running a clock if not already doing so) so if it works for you that's great. However, the interrupt code below won't cause your code to hang up on each button push so it may be of value for time sensitive programs where waiting for 200 ms or more on each press isn't advisable.

#include "button_debounce.h"
#include "lcd.h"

volatile int16_t t, tbm, k;

void time_setup() {
    // TOP set by OCR0A register
    TCCR0A |= (1<<WGM01);
    // clocked from CLK/64, which is 16000000/64, or 250000 increments per second
    TCCR0B = 0;
    TCCR0B |= (1<<CS01) | (1<<CS00);
    OCR0A = 249;
    // enable interrupt on compare event
    // (250000 / 250 = 1000 per second) with ocr0a = 249
    TIMSK0 |= (1<<OCIE0A);
}

    ////////////////////////////////////
ISR(TIMER0_COMPA_vect){t++;}// t will store the elapsed time in ms

/////////////////////////////////////   
ISR(PCINT0_vect){if (! (PINB&(1<<PB2)))tbm=t;}

void btn_set(){
DDRB &= ~(1<<PB2);
PORTB |= (1<<PB2);
PCICR |= (1<<PCIE0);
PCMSK0 |= (1<<PCINT2);}

int main(void){

//Turn on interrupts
sei();

time_setup();

btn_set();

// init LCD
lcd_init();
FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
lcd_home();

    while(1){

            if (     ! (PINB&(1<<PB2)) && (t>(tbm+40)) )  {tbm=t+960;//If button held for at least 1 second will then give one hit per 1 sec.
                                                            k++;}//code that executes on valid button hit - just increments k here

        lcd_line_three();
        fprintf_P(&lcd_stream, PSTR("button presses:%5d"), k);

            }
}
April 07, 2013
by JimFrederickson
JimFrederickson's Avatar

One of the things that you really want to think about is before you use them are the "_delay_ms()" functions.

I, personally, ONLY use them for delays of < 5ms. (Even then I don't use them often.)

With the delay of 200ms that you used in one of your examples that is, essentially, stopping your program for nearly 3,000,000 cycles.

For "simple"/"test" programs this is not a problem, but for future more complex programs this will become a problem. So staying out of that mindset sooner, is often better.

For most, more complex programs, you will want to be able to juggle multiple events/actions in a manner that appears simultaneously for that you need to have "even program flow".

"Doubletap" is in the library that would shed some light on this. We had a discussion of this in the Forums here in September of last year.

Ultimately, you want interrupts to really do "as little as possible". Interrupts "CAN NEVER" do more processing than the frequency of the interrupt occurrence. (As a simple example of this, if you have an interrupt that occurs 1,000 times a second then the code for the interrupt CANNOT use more than 14,745 cycles in length.) The code for the interrupt must ALWAYS finish before the next interrupt occurs.

For using "keys" of any sort there are really 2 things that are important.

1 - "debouncing" of the actual key press/key release. This is effectively removing multiple quick contact/no contact situations from the actual key action. So that in the end you have a solid "key press" or "key release".

2 - Does your program care if the "key is being pressed" or that the "key was pressed". (The same applies for releases as well, but it is also basically the difference between a "switch" and a "key"/"button".) These are 2 different situations. Mostly your program will care the that "key was pressed" and then there will need to be code in your program to wait for a "key release event" before processing the next "key press event".

A common error is continuously checking if the "key press event" without ever looking for a "key release event". Even if you press and release the key as quickly as you can, the Microcontroller will see dozens of "key press events" before you can release the key.

For me almost everything I do involves some sort of "switch" or "key", and I debounce both of them and store their results separate.

(I am traveling and will write more later... The "library double tap" example is simplified the discussion I think is better...)

Post a Reply

Please log in to post a reply.

Did you know that you can connect digital calipers to a microcontroller? Learn more...