1
\$\begingroup\$

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: enter image description here

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

\$\endgroup\$
  • \$\begingroup\$ Schematic of how the buttons are attached? \$\endgroup\$ – bigjosh Mar 9 '17 at 18:54
  • \$\begingroup\$ Why do you prefer to poll rather than use a pin change interrupt? \$\endgroup\$ – bigjosh Mar 9 '17 at 18:55
  • 1
    \$\begingroup\$ 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? \$\endgroup\$ – kkrambo Mar 9 '17 at 20:04
  • 1
    \$\begingroup\$ @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 ? \$\endgroup\$ – Polyphil Mar 9 '17 at 20:18
  • 2
    \$\begingroup\$ 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 . \$\endgroup\$ – Martin Mar 9 '17 at 21:51
1
\$\begingroup\$

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.

\$\endgroup\$
0
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – Polyphil Mar 10 '17 at 11:40
  • \$\begingroup\$ @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. \$\endgroup\$ – JimmyB Mar 10 '17 at 13:40
  • \$\begingroup\$ @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. \$\endgroup\$ – bigjosh Mar 10 '17 at 18:36
  • \$\begingroup\$ @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. \$\endgroup\$ – bigjosh Mar 10 '17 at 18:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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