0
\$\begingroup\$

I am looking forward to counting the number of presses of a button in a 2 seconds interval in ATMEGA16A. I have prepared some code below. Is there another way around? My first solution was to perform polling inside the while() loop, but I think that would be imprecise.

Problem description: Two buttons, START and PV are given and two internal states: IDLE and COUNTING. Once START is pressed the state changes to COUNTING and the timer starts to count up. During this time, the number of PV presses is recorded by incrementing a variable 'na'. One the timer reaches 2 seconds, no further presses are recorded. The output is represented by 12-LEDs, 3 of them for the tens, named LZ1-LZ3 and 9 LEDs for the units, named LU1-LU9, for a maximum count of 39.

My second solution used interrupts for the 2 buttons and the timer overflow. Initially, the clock for the timer was set to 0, so the timer won't start. I would only enable an interrupt for the START(INT0) button. It would enable the interrupt for the PV (INT1). When a low signal is detected on START (button pressed), it would change the state to COUNTING and it would enable interrupts for PV. When PV is pressed, na is incremented. When the timer overflows (i.e. it reaches 2 seconds), the interrupts for PV would be disabled, the timer would be stopped and reset and the program would go into state IDLE to output the last 'na'.

I used timer 1 in CTC mode with N = 46875 and a clock of 256.

Now, for some code:

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

#define N 46875                         //TOP value for the timer 1

#define IDLE 0                          //in this state we print the last number of presses
#define COUNTING 1                      //we do nothing here, only used as intermediary state

#define LZ1 PD4
#define LZ2 PD5
#define LZ3 PD6
#define LU9 PD7

volatile unsigned char na = 0;          //number of presses shared between main and ISRs
volatile unsigned char state = IDLE;    //initial state shared between main and ISRs


/************************************************************************/
/*
PA0-PA9: LEDs which display units 0-8
PD0-1: unused
PD1-2: START button(INT0) and PV button(INT1)
PD4-6: tenths LEDs
PD7: LED for unit 9
ex: 35 presses of PV
LU - LED for units
LZ - LED for tenths
PD4 PD5 PD6             PA 0 1 2 3 4 5 6 7  PD7
LZ1 LZ2 LZ3 (PD4-6)     LU 1 2 3 4 5 6 7 8   9
1   1   1                  0 0 0 0 1 1 1 1   1
*/
/************************************************************************/
int main(void)
{
    //setup ports
    DDRA = 0xff;            //whole PORTA as output
    DDRD = 0b111100--;      //buttons PS and PV on PD2 and PD3

    //setup timer 1
    TCCR1B = 0b---10000;    //CTC mode 4 and CLOCk = '0' -> timer 1 not counting yet
    TCCR1A = 0b------00;    //CTC mode 4
    OCR1A = N - 1;          //setup TOP for CTC mode

    //setup interrupts
    TIMSK |= (1<<OCIE1A);   //enable interrupt for ISR
    GIFR |= (1<<INT1);      //enable interrupts from INT1 (PV)
    MCUCR = 0b----0000;     //INT 0 and INT1 are active on low
    sei();                  //enable global interrupts

    //declarations
    unsigned char lut_units[] = {
        0b00000000, // 0
        0b00000001, // 1
        0b00000011, // 2
        0b00000111, // 3
        0b00001111, // 4
        0b00011111, // 5
        0b00111111, // 6
        0b01111111, // 7
        0b1111111   // 8
    };//lookup table used to store the LED configurations for units 0-9

    unsigned char units = 0, tens = 0;  //used as temporary variables

    while (1)
    {
        switch(state):{
            //used to print out the number of presses
            case IDLE:{
                units = na % 10;            //extract the units from the number of presses ex. 23 -> 3
                tens = na / 10;         //extract the tenths from the number of presses ex. 23 -> 2

                //write the units to the LEDS
                PORTA = lut_units[units];   //write the appropriate lookup table value to PORTD
                if(units < 9){
                    PORTD &= ~(1<<LU9);     //the LEd for 9 units is placed on PD7, turn it OFF
                    }else{
                    PORTD |= (1<<LU9);      //the LEd for 9 units is placed on PD7, turn it ON
                }

                //write the tenths to the 3 LEDs on PORTD
                if(tens & (1<<0)){
                    PORTD |= (1<<LZ1);                              //only LZ1 on for 1 tenth
                }

                if(tens & (1<<2)){
                    PORTD |= (1<<LZ1) | (1<<LZ2);                   //LZ1 and LZ2 for 2 tenths
                }

                if(tens > 2){
                    PORTD |= (1<<LZ1) | (1<<LZ2) | (1<<LZ3);        //LZ1, LZ2 and LZ3 for more tenths
                }
            }

            case COUNTING:{
                //do nothing, the interrupts will handle everything
            }
        }
    }
}

/************************************************************************/
/*
ISR for the START button on PD2 (INT0). Once this is pressed the
program switches its state to counting
*/
/************************************************************************/
ISR(INT0_vect)
{
    state = COUNTING;                               //switch state to counting
    GICR = (1<<INT1);                               //allow interrupts from PV
    TCCR1B |= (1<<CS12);                            //set the clock to 256
    na = 0;
}

/************************************************************************/
/*
ISR for the PV button which is pressed and counted.
*/
/************************************************************************/
ISR(INT1_vect)
{
    na++;   //simply increment the number of presses
}


/************************************************************************/
/*
ISR for the compare match with OCR1A. When this ISR is triggered it
means 2 seconds have passed and that the program should no longer
count any presses from PV. The timer should be reset and put in
stand-by.
*/
/************************************************************************/
ISR(TIMER1_COMPA_vect)
{
    GICR &= ~(1<<INT1);     //disable interrupts coming from PV
    TCCR1B &= 0b11111000;   //set the clock to '0'
    TCNT1 = 0;              //reset the timer 1 state to BOTTOM
    state = IDLE;           //switch the state to IDLE
}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ We discourage broad, open-ended design review questions here on EE.SE, because the answer(s) tend to become long strings of unrelated edits and/or comments. While this might help you with your immediate problems, it is of no value to the site overall. We DO allow design review questions in which you explain your choices and then focus on a few points about which you still have doubts. To get a better feel of what is or is not acceptable, search for "design review" on the meta site. \$\endgroup\$
    – Dave Tweed
    Sep 11, 2019 at 18:35
  • 1
    \$\begingroup\$ Note that "tenths" and "tens" are two different quantities. I fixed the wording in your problem description, but not in your code. \$\endgroup\$
    – Dave Tweed
    Sep 11, 2019 at 18:37
  • \$\begingroup\$ Both methods described are applicable to this problem. If the problem expands then there are pro/con to each. A human will not be able to push the button faster than the cpu can read the I/O, using either method. Make sure you add some input debouncing either in hardware or software. \$\endgroup\$
    – Aaron
    Sep 11, 2019 at 20:33
  • \$\begingroup\$ Thanks for the tips. The problem stated that the buttons have hardware debouncing so there is no need for me to do that in software. \$\endgroup\$
    – CyberFox
    Sep 12, 2019 at 12:07

1 Answer 1

1
\$\begingroup\$

Is there another way around?

The T0 or T1 pin configured as a clock source for Timer/Counter0 or Timer/Counter1 respectively is intended exactly for the purpose of counting total number of external impulses in hardware without need for code to do busy-loop waiting and/or react on each single event.

Nevertheless, the input signal needs to be clean without bounces as there is no option for filtering in the MCU, so you need external debouncing (simple RC filter will do in many situations).

\$\endgroup\$
1
  • \$\begingroup\$ The problem stated that the buttons are 'special'. (no debouncing needed). Thanks for the tip. \$\endgroup\$
    – CyberFox
    Sep 12, 2019 at 12:04

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.