Настраиваем внешние прерывания микроконтроллера ATmega8

В микроконтроллере ATmega8 предусмотрено два входа внешних прерываний: PD2 — INT0 и PD3 — INT1. В этом уроке разберём настройку этих входов и проверим работу прерываний на простом примере со светодиодами и кнопками.

Выводы INT0 и INT1

Выводы INT0 и INT1 микроконтроллера ATmega8
PD2 — INT0, PD3 — INT1

Регистр GICR

Для разрешения внешних прерываний используется регистр GICR.

Регистр GICR ATmega8
Регистр GICR
В данном регистре нас интересуют биты, разрешающие работу INT0 и INT1. Установка соответствующего бита в логическую 1 разрешает работу выбранного внешнего прерывания.

Регистр MCUCR

Регистр MCUCR задаёт условие, при котором будет вызвано внешнее прерывание.

Регистр MCUCR ATmega8
Регистр MCUCR

Биты ISC00 и ISC01 используются для настройки INT0. Биты ISC10 и ISC11 используются для настройки INT1.

Настройка INT0

ISC01 ISC00 Условие срабатывания INT0
0 0 Низкий логический уровень на входе INT0
0 1 Любое изменение логического уровня
1 0 Ниспадающий фронт
1 1 Возрастающий фронт

Для входа INT1 принцип настройки аналогичный, только используются биты ISC11 и ISC10.

Глобальное разрешение прерываний

Кроме разрешения конкретного INT0 или INT1, необходимо глобально разрешить работу прерываний.

Для этого можно использовать функцию:

sei();

Или установить соответствующий бит регистра SREG:

SREG |= (1 << 7);

Проверяем работу прерываний

Для проверки подключим два светодиода к выводам PD0 и PD1. Две кнопки подключим к PD2 и PD3, то есть к входам INT0 и INT1.

Дополнительно к выводу PD4 подключим третий светодиод. Он будет мигать каждые 500 мс, показывая, что основной цикл программы продолжает выполняться независимо от обработки внешних прерываний.

Программный код

#define F_CPU 4000000UL

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


void init_interrupt(void);
void int_pcb(void);


/* Светодиод HL1 — PD0 */
#define HL1_on     PORTD |=  (1 << PD0)
#define HL1_off    PORTD &= ~(1 << PD0)

/* Светодиод HL2 — PD1 */
#define HL2_on     PORTD |=  (1 << PD1)
#define HL2_off    PORTD &= ~(1 << PD1)

/* Светодиод HL3 — PD4 */
#define HL3_on     PORTD |=  (1 << PD4)
#define HL3_off    PORTD &= ~(1 << PD4)


/* Прерывание INT0 — вывод PD2 */
ISR(INT0_vect)
{
    HL1_on;
    HL2_off;
}


/* Прерывание INT1 — вывод PD3 */
ISR(INT1_vect)
{
    HL2_on;
    HL1_off;
}


int main(void)
{
    init_interrupt();
    int_pcb();

    while (1)
    {
        HL3_on;
        _delay_ms(500);

        HL3_off;
        _delay_ms(500);
    }
}


void init_interrupt(void)
{
    /*
        Разрешаем INT0 и INT1
    */
    GICR |= (1 << INT0) |
            (1 << INT1);

    /*
        INT0 и INT1 срабатывают
        при любом изменении логического уровня
    */
    MCUCR |= (1 << ISC00) |
             (1 << ISC10);

    /*
        Глобально разрешаем прерывания
    */
    sei();
}


void int_pcb(void)
{
    /*
        PD2 и PD3 — входы.
        Включаем внутренние подтягивающие резисторы.
    */
    PORTD = 0b00001100;

    /*
        PD0, PD1 и PD4 — выходы
    */
    DDRD = 0b00010011;
}

Как работает программа

В основном цикле программы светодиод на выводе PD4 постоянно включается и выключается с интервалом 500 мс. При изменении сигнала на входе INT0 выполнение основного кода временно прерывается и вызывается обработчик ISR(INT0_vect). При изменении сигнала на INT1 вызывается ISR(INT1_vect). После выполнения обработчика микроконтроллер возвращается к выполнению основного цикла.