Главная > AVR, In English > Some words on AVR’s INT0

Some words on AVR’s INT0

A while ago I was asked a question about external interrupts on AVR. Trying to find the answer I’ve learned rather interesting things, which, I think, worth sharing.

Consider a typical interrupt implementation: there are control registers to configure a particular interrupt, there are status registers containing interrupt flags. When an interrupt is configured and a specific event occurs, corresponding flag is set in hardware. Then, if interrupts are globally enabled, MCU jumps to an interrupt service routine (ISR); otherwise the ISR will be executed as soon as interrupts are globally re-enabled, provided that interrupt flag is still set.

INT0 can be triggered in a number of cases: on a rising edge on a pin, on falling edge, on pin state change or when there is a low level on a pin. The latter is the most interesting. The datasheet states that when INT0 is configured in a level-sensitive mode, the corresponding flag is always cleared.

How come? What about an ISR call? How will MCU know that the event has occured? And what if low level occurs when interrupts are globally disabled — will the ISR be executed after re-enabling them, as with other interrupts?

 

To answer these questions I took my breadboard from a shelf and did some experimentation. So:

1. Yes, when INT0 is configured as level-sensitive its corresponding flag is always cleared;

2. In spite of this, INT0 ISR is executed if interrupts are globally enabled — magic!

3. If the level of interest occured while interrupts were globally disabled (and vanished before they were re-enabled), MCU will not know that the event has ever occured and ISR won’t be executed.

Of course, this is true for INT1 also, for this interrupts are identical.

 

Very strange, indeed. Why was it designed to behave so? The one answer may be that this mode is present exclusively to wake a part from a deep sleep mode. Edge-triggered and pin change interrupts do not work when clock oscillator is disabled, but level-sensitive interrupt does work. So on parts where there’s no PCINT (e.g. ATmega8) this is the only option to wake part externally, apart from system reset. Maybe this logic was challenging to design, and the only possible way to implement it resulted in this odd operation. On newer parts, with PCINT capability present, INT0/1 level-triggered mode is obviously legacy, for it has some downsides like triggering constantly while the level is present (which may result in an infinite loop).

Below I include the code which I used to derive described results. The test mode is chosen through #define statement. For modes 1 and 2 (flag and interrupt tests) external button and pull-up resistor are required, mode 3 (test of interrupt on a level generated while interrupts are globally disabled) works autonomous.

 

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


// 1 - test flag, 2 - test interrupt, 3 - test interrupt generated while interrupts are globally disabled
#define TEST_TYPE			3

#define LOW_LEVEL_INTMASK		0
#define FALLING_EDGE_INTMASK		_BV(ISC01)
#define ANY_CHANGE_INTMASK		_BV(ISC00)

//The interrupt under test will be configured in this mode
#define TESTED_MODE_MASK		LOW_LEVEL_INTMASK

#define INTERRUPT_LED			_BV(PB1)
#define FLAG_LED			_BV(PB2)

volatile uint8_t interrupt_was_processed=0;

ISR (INT0_vect)
{
  interrupt_was_processed=1;
}

void main(void)
{
#if TEST_TYPE==3
  PORTD|=_BV(PD2); // Set default value
  DDRD|=_BV(PD2); // OMG, INT0 pin is an output! Yeah, it is permitted, and we'll be generating software interrupt.
#else
  DDRD&=~_BV(PD2); // Make sure INT0 pin is an input
#endif
  DDRB|=INTERRUPT_LED | FLAG_LED;  // Debug LEDs
  
  //Flash sequence to make sure LEDs are properly connected  

  PORTB|=INTERRUPT_LED | FLAG_LED;
  _delay_ms(500);
  PORTB&=~(INTERRUPT_LED | FLAG_LED);
  _delay_ms(500);
  PORTB|=INTERRUPT_LED | FLAG_LED;
  _delay_ms(500);
  PORTB&=~(INTERRUPT_LED | FLAG_LED);
  _delay_ms(500);

  EICRA=TESTED_MODE_MASK;
  EIMSK=_BV(INT0); // Turn on INT0

#if TEST_TYPE==2
  sei();
#endif

  while (1)
  {

#if TEST_TYPE==2
  	if (interrupt_was_processed)
	{
	  PORTB|=INTERRUPT_LED;
          _delay_ms(50);
          PORTB&=~INTERRUPT_LED;
          _delay_ms(50);
          PORTB|=INTERRUPT_LED;
          _delay_ms(50);
          PORTB&=~INTERRUPT_LED;
          _delay_ms(50);

	  interrupt_was_processed=0;
	}
#elif TEST_TYPE==1
	if (EIFR & _BV(INTF0))
	{
	  PORTB|=FLAG_LED;
          _delay_ms(50);
          PORTB&=~FLAG_LED;
          _delay_ms(50);
          PORTB|=FLAG_LED;
          _delay_ms(50);
          PORTB&=~FLAG_LED;
          _delay_ms(50);

	  EIFR=_BV(INTF0); // Clear flag
	}
#elif TEST_TYPE==3
    // Now, get ready.
	cli();
	interrupt_was_processed=0;
	PORTB&=~(INTERRUPT_LED | FLAG_LED);
    // Interrupts are globally disabled. Generate software interrupt, falling edge on INT0 pin:
	PORTD&=~_BV(PD2);
	_delay_ms(1); // Just to be sure
	PORTD|=_BV(PD2);

	//A-a-a-and... Test it!
	sei();

	_delay_ms(100); // Holding breath...

	//A-a-a-and...

	if (interrupt_was_processed)
	{
	  PORTB|=INTERRUPT_LED | FLAG_LED; // WIN! MAGIC!
	}
	else
	{
	  PORTB|=FLAG_LED; // Or nothing happened?
	}

	_delay_ms(1000); // Take some time to chill after marvellous result

#else
  #error Unknown test mode
#endif

  }
}
Рубрики:AVR, In English
  1. Комментариев нет.
  1. No trackbacks yet.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s