PlatformIO Community

Simulating Software Interrupts on ATmega328p

Hey,
does anyone know, how one would simulate an interrupt from software on an ATmega328p(same as Arduino Uno).

My externally triggered INT0 interrupt currently works via a pushbutton.

I’d like to trigger the interrupt via software.

• Bit 0 – INT0: External Interrupt Request 0 Enable
When the INT0 bit is set (one) and the I-bit in the Status Register 
(SREG) is set (one), the external pin interrupt is enabled. 
The Interrupt Sense Control0 bits 1/0 (ISC01 and ISC00) in the 
External Interrupt Control Register A (EICRA) define whether the 
external interrupt is activated on rising and/or falling edge of the INT0 
pin or level sensed. Activity on the pin will cause an interrupt request 
even if INT0 is configured as an output. The corresponding interrupt of
 External Interrupt Request 0 is executed from the INT0 Interrupt 
Vector.
Activity on the pin will cause an interrupt request even if INT0 is configured as an output.

This part and some forum post from 2007 suggest that a simulation of a software interrupt is actually possible, so I was wondering of someone knows, how one would trigger the INT0 interrupt on an ATmega328p-pu.

The pin is PD2.

Here is some abstract code that I’m trying to run.
InterruptVectorTable is supposed to take care of the function pointers and enable/disable interrupts(and global interrupts) as well as trigger them via software in order to test it.

void ToggleLED()
{
    // toggle the LED
    PORTB ^= 1 << PB5;  // PB5 = 5
}

void InitGPIO()
{
    // make port output & HIGH
    // our power source
    DDRB |= 1 << DDB4;
    PORTB |= 1 << PB4;

    // make Port B5 output & LOW
    // Connected to an LED - Depending on the state of this, 
    // the LED is either on or off.
    DDRB |= 1 << DDB5;
    PORTB &= ~(1 << PB5);

    // Make Port D2 Input External Interrupt
    DDRD &= ~(1 << DDD2);
    PORTD |= (1 << PD2);
}

void InitINT0()
{
    // external interrupt 0

    // on any logical change
    // Manual - Page 80
    EICRA |= (1 << ISC01);
    EICRA &= ~(1 << ISC00);

}

int main(void)
{

   auto& VectorTable = InterruptVectorTable::getInstance();

    InitGPIO();
    InitINT0();

    VectorTable.addCallback(1, ToggleLED);
    VectorTable.enableIRQ();

    while (1)
    {
        _delay_ms(1000);
        
        VectorTable.triggerIRQ(1);
    }

    return 0;
}

Taking that definition ‘activity’ loosely, what happens if you write to the interrupt pin? Maybe that counts as activity?

I just had a look at the datasheet (13. External Interrupts), and it actually does say …

Observe that, if enabled, the interrupts will trigger even if the INT7:0 or PCINT23:0 pins are configured as outputs. This feature provides a way of generating a software interrupt.

It seems a mode change may even be enough… i.e. changing it from an input to an output…

:wink:

1 Like

// might miss some includes
#include <util/delay.h>
#include <avr/interrupt.h>

#define DISABLE(REGISTER, BIT) (REGISTER) &= ~(1 << BIT)
#define ENABLE(REGISTER, BIT) (REGISTER) |= (1 << BIT)
#define TOGGLE(REGISTER, BIT) (REGISTER) ^= (1 << BIT)
#define TRIGGER(REGISTER, BIT) TOGGLE(REGISTER, BIT);TOGGLE(REGISTER, BIT)
#define IS_SET(REGISTER, BIT) (REGISTER & (1 << BIT)) > 0

ISR(INT0_vect)
{
    // toggle the LED
    TOGGLE(PORTB, PORTB5);
}

void InitGPIO()
{
    // make port output & HIGH
    // our power source
    ENABLE(DDRB, DDB4);
    ENABLE(PORTB, PORTB4);

    // make Port B5 output & LOW
    // Connected to an LED - Depending on the state of this, 
    // the LED is either on or off.
    ENABLE(DDRB, DDB5);
    DISABLE(PORTB, PORTB5);

    // Make Port D2 Input External Interrupt
    DISABLE(DDRD, DDD2);
    ENABLE(PORTD, PORTD2);
}

void InitINT0()
{
    // Manual - Page 80
    // The falling edge of INT0 generates an interrupt request.
    ENABLE(EICRA, ISC01);
    DISABLE(EICRA, ISC00);
}

void triggerINT0()
{
     ENABLE(DDRD, DDD2);
     ENABLE(PIND, PIND2);
     ENABLE(PORTD, PORTD2);
}

int main(void)
{
    InitGPIO();
    InitINT0();
    sei();
    while(1)
    {
        _delay_ms(1000);
        triggerINT0();
    }
}

That’s not the full code, but it’s used to depict my part that I’m trying to work on.
It seems weirdly that the interrupt is triggered exactly, when I enable(set to 1) all three of the register bits.

I’m not yet sure, why it needs explicitly to be all three of these registers that trigger my interrupt there.

And depending on the way the interrupt is triggered, it seems that the software interrupt needs to be triggered differently :smiley: that sounds like configuration fun for all cases for all interrupts.

2 Likes

It seems that some interrupt are easily triggered by changing the pin states or changing multiple registers + states. but all in all it seems not to be a feasible attempt to simulate all of the possible interrupts for the small benefit it might create.
It seems to be way easier to simply call the ISR functions instead of triggering the interrupts.
The configuration overhead to configure all the interrupts will seemingly bloat the firmware to a not wanted size.

1 Like