# Polling several buttons from an interrupt

I am trying to poll 3 different buttons from a watchdog time out interrupt, on an ATtiny13.

My code works perfectly for individual buttons, however I can't seem to be able to poll all 3 in a loop. All buttons are associated to an arbitrary number for identification in 0x01,0x02 and 0x04.

For example, this code polls the button 0x02 and works fine:

ISR (WDT_vect){
if (debounce(0x02)==1){
PORTB ^= _BV(PB1);//flip led 1
}}


However, if I try to poll all 3 in a loop, no detection seems to occur. In this example I simply toggle the same led for all 3 buttons:

ISR (WDT_vect){
for (int d=0x01;d<0x04;d<<1){
if (debounce(d)==1){
PORTB ^= _BV(PB1);//flip led 1
}}}


Stacked if-else don't work either:

ISR (WDT_vect){

if (debounce(0x02)==1){
PORTB ^= _BV(PB1);//flip led 1
} else  if (debounce(0x04)==1){
PORTB ^= _BV(PB3);//flip led 2
}
}


The rest of the relevant code shortened for clarity:

/***
* curbtn: one of 0x01,0x02,0x04, matches mute, vol+,vol-
* returns 1 if the button is considered pressed
*/
uint8_t debounce(uint8_t  curbtn){
static uint8_t button_history = 0;
uint8_t pressed = 0;
button_history = button_history << 1;
button_history |= read_btn(curbtn);
if ((button_history & 0b11000111) == 0b00000111) {
pressed = 1;
button_history = 0b11111111;
}
return pressed;
}
/**
* sets up ports to read a specific button
* returns 1 if the selected button is pressed
*/
uint8_t read_btn(uint8_t  curbtn){
uint8_t ret=0x00;
if (curbtn==0x01){
DDRB &=~_BV(PB2);//PB2 en entree
PORTB |=_BV(PB2);//pull-up actif
ret= ( (PINB & _BV(PB2)) == 0 );
} else if (curbtn==0x02){
DDRB |=_BV(PB2);//PB2 en sortie
PORTB &=~_BV(PB2);//PB2 a 0
DDRB &=~_BV(PB0);//PB0 en entree
PORTB |=_BV(PB0);//pull up sur PB0
ret= ( (PINB & _BV(PB0)) == 0 );
} else if (curbtn==0x04){
DDRB |=_BV(PB0);//PB0 en sortie
PORTB &=~_BV(PB0);//PB0 a 0
DDRB &=~_BV(PB4);//PB4 en entree
PORTB |=_BV(PB4);//pull up sur PB4
ret= ((PINB & (1<<PB4)) == 0);//lecture de PB0
}
return ret;
}


This is the circuit layout:

I'd like to know if I'm going in the right direction, and in what way my polling code should be corrected.

• Schematic of how the buttons are attached? – bigjosh Mar 9 '17 at 18:54
• Why do you prefer to poll rather than use a pin change interrupt? – bigjosh Mar 9 '17 at 18:55
• Does debounce() track the button state from one invocation to the next? If so can it track multiple different buttons or only a single button? – kkrambo Mar 9 '17 at 20:04
• @kkrambo I included the complete function, you've probably found the issue. Yes, it keeps the value from one invocation to the other, but isn't appropriate for more than one button. I assume the next step would be to properly implement a button_history_xx for each button ? – Polyphil Mar 9 '17 at 20:18
• You have single static variable (button_history) in debounce function, you try to use for all buttons "together", don't you? So when looping over buttons, you get there interleaved bits from all three btns and it never debounces, I guess . – Martin Mar 9 '17 at 21:51

## 2 Answers

Made a lot of progress, there were 2 problems.

As suggested by Martin and kkrambo there was the issue of correctly tracking the state of all 3 buttons. The code I posted kept a static history variable. Now the main code includes 3 different history variables.

uint8_t mute_history=0;
uint8_t volp_history=0;
uint8_t volm_history=0;

uint8_t read_btn(uint8_t  curbtn){
uint8_t ret=0x00;
if (curbtn==0x01){
DDRB &=~_BV(PB2);//PB2 en entree
PORTB |=_BV(PB2);//pull-up actif
nop();nop();nop();nop();
ret= ( (PINB & _BV(PB2)) == 0 );
} else if (curbtn==0x02){
DDRB |=_BV(PB2);//PB2 en sortie
PORTB &=~_BV(PB2);//PB2 a 0
DDRB &=~_BV(PB0);//PB0 en entree
PORTB |=_BV(PB0);//pull up sur PB0
nop();nop();nop();nop();
ret= ( (PINB & _BV(PB0)) == 0 );
} else if (curbtn==0x04){
DDRB |=_BV(PB0);//PB0 en sortie
PORTB &=~_BV(PB0);//PB0 a 0
DDRB &=~_BV(PB4);//PB4 en entree
PORTB |=_BV(PB4);//pull up sur PB4
nop();nop();nop();nop();
ret= ((PINB & (1<<PB4)) == 0);//lecture de PB0
}
return ret;
}

uint8_t debounce(uint8_t  *button_history,uint8_t  curbtn){
uint8_t pressed = 0;
*button_history = *button_history << 1;
*button_history |= read_btn(curbtn);
if ((*button_history & 0b11000111) == 0b00000111) {
pressed=1;
*button_history = 0b11111111;
}
return pressed;
}

ISR (WDT_vect){

if (debounce(&volp_history,0x02)==1){
PORTB ^= _BV(PB3);//flip led 2
}

if (debounce(&mute_history,0x01)==1){
PORTB &= ~_BV(PB1);//turn off
PORTB &= ~_BV(PB3);//turn off
}

if (debounce(&volm_history,0x04)==1){
PORTB ^= _BV(PB1);//flip led 2
}

}


The second issue was a timing one, which was suggested to me by user Tom Carpenter in this thread. His comments were:

@Polyphil try adding some nop instructions. You can use the following: #define nop() __asm__ __volatile__ ("nop \n\t"), and then in your code do nop();nop();nop(); just before you do the return statement.

@Polyphil the inputs on the ATTiny have a two clock cycle latency due to a synchroniser chain, so it takes at least 2-3 clock cycles after changing the pull-up value before it is reflected in the PIN register. Adding a nop causes the processor to wait a clock cycle.

hence the nops().

The circuit behaves as expected at the moment.

How often are you calling the WDT? Why not pick a WDT prescaller so that you do not need to debounce at all? If you only check the buttons every (say) 50-100ms, then they should debounce themselves and your code can be as simple as just checking each of the buttons in turn sequentially and acting on the state.

• I haven't changed the prescaler settings. Assuming it defaults to no prescaling it should be every 16ms according to the datasheet. I like how the system reacts with this particular debouncing function but I don't see how your idea wouldn't work. The difference would be that keeping the button pressed would read as a new press every time the WDT times out. – Polyphil Mar 10 '17 at 11:40
• @bigjosh You have to sample at least twice to actually debounce, or your result may be inconsistent if you happen to sample while the bouncing is going on. – JimmyB Mar 10 '17 at 13:40
• @jimmyb if you sample at a rate slower than the max bounce time and faster than the shortest up/down time, then you will always see every state change and not see any bouncing. In practice, if you check a button once every 50ms, then you .ISS the bounces. – bigjosh Mar 10 '17 at 18:36
• @polyphil Try increasing the prescaler a step or two and I bet you will stop seeing bounces. In practice I think it is unlikely you will be able to feel any change in responsiveness. – bigjosh Mar 10 '17 at 18:38