Search code examples
cinterruptavrinterrupt-handlingattiny

Best way to handle multiple PCINT in AVR


I'm testing some things on a Attiny85 and thought about the best way to handle the interrupt rutine. I know it is bad to have a lot of code in the interrupt handler, but I'm uncertain of any other ways to do this. I want my main program to sleep and wake on PCINT, the PCINT comes from multiple pins (rotary encoder A, b & switch and a receiving UART) so I was thinking just having a lot of code in the handler.

The code to determining which pin caused the interrupt, would look like this

#include <avr/io.h>
#include <stdint.h>            // has to be added to use uint8_t
#include <avr/interrupt.h>    // Needed to use interrupts
volatile uint8_t portbhistory = 0xFF;     // default is high because the pull-up

int main(void)
{
    DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); // Clear the PB0, PB1, PB2 pin
    // PB0,PB1,PB2 (PCINT0, PCINT1, PCINT2 pin) are now inputs

    PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); // turn On the Pull-up
    // PB0, PB1 and PB2 are now inputs with pull-up enabled

    PCICR |= (1 << PCIE0);     // set PCIE0 to enable PCMSK0 scan
    PCMSK0 |= (1 << PCINT0);   // set PCINT0 to trigger an interrupt on state change 

    sei();                     // turn on interrupts

    while(1)
    {
    /*main program loop here */
    }
}

ISR (PCINT0_vect)
{
    uint8_t changedbits;

    changedbits = PINB ^ portbhistory;
    portbhistory = PINB;

    if(changedbits & (1 << PB0))
    {
    /* PCINT0 changed */
    }

    if(changedbits & (1 << PB1))
    {
    /* PCINT1 changed */
    }

    if(changedbits & (1 << PB2))
    {
    /* PCINT2 changed */
    }
}

And then ofc inside each of the if-statements in the interrupt handler, there would be code handling something, like this code, turning on the Timer0

TCNT0 = 0; // Set counter to 0
OCR0A = SERIAL_BIT_TIME; // Call timer interrupt in middle of first bit
position = 0; // Reset position and data
TIMSK |= 1 << OCIE0A; // Enable interrupt for compare register A (timer interrupt)
TIFR |= 1 << OCF0A; // Clear timer interrupt flag to prevent it jumping directly there
PCMSK &= ~(1 << SERIAL_RECEIVE); // Disable pin change interrupt

or with the switch input, the code inside the if-statement would be

if (lightState)
{
    dali.transmit(ADDRESS, OFF);
    lightState = 0;
} 
else
{
    dali.transmit(ADDRESS, ON);
    lightState = 1;
}

Would this be a dumb solution?


Solution

  • volatile uint8_t flag;
    
    int main(void)
    {
        DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); // Clear the PB0, PB1, PB2 pin
        // PB0,PB1,PB2 (PCINT0, PCINT1, PCINT2 pin) are now inputs
    
        PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); // turn On the Pull-up
        // PB0, PB1 and PB2 are now inputs with pull-up enabled
    
        PCICR |= (1 << PCIE0);     // set PCIE0 to enable PCMSK0 scan
        PCMSK0 |= (1 << PCINT0);   // set PCINT0 to trigger an interrupt on state change 
    
        sei();                     // turn on interrupts
    
        while(1)
        {
            gotosleep();
            do
            {
                switch(flag)
                {
                    case 1:
                        dosomething1();
                        break;
                    case 2:
                        dosomething2();
                        break;
                    case 3:
                        dosomething3();
                        break;
                }
            cli();
            flag = 0;
            sei();
            }while(flag); 
    
        }
    }
    
    ISR (PCINT0_vect)
    {
        uint8_t changedbits;
    
        changedbits = PINB ^ portbhistory;
        portbhistory = PINB;
    
        if(changedbits & (1 << PB0))
        {
            flag = 1;
        }
    
        if(changedbits & (1 << PB1))
        {
            flag = 2;
        }
    
        if(changedbits & (1 << PB2))
        {
            flag = 3;
        }
    }