CUSTOMELECTRONICS.RU
Информационно-учебный блог о разработке электроники
Эл. почта: info@customelectronics.ru

AVR. Внешние прерывания. Подключение энкодера

Внешние прерывания очень — очень мощный инструмент. Контроллер способен при правильной настройке реагировать на внешнее событие. При этом останавливается выполнение основной программы и выполняется обработчик прерывания.

Подготовка к работе

На нашей плате TutorShield установлены две кнопки, которые подключены к цифровым выводам PD2 и PD3. Подробно подключение кнопок описано в предыдущей статье цикла. Помимо них, для дальнейших экспериментов подключим трехцветный светодиод к выводам PB1, PB2, PB3. Установите перемычки так, как показано на рисунке:

Установка перемычек

Установка перемычек

Первый пример

В предыдущей статье мы в самой функции main опрашивали регистры PIN и делали вывод о состоянии выводов контроллера. При этом легко пропустить изменение состояния. Для этого случая в контроллере есть механизм внешних прерываний.
Все что нужно для использования прерываний, это выполнить настройку управляющих регистров и настроить обработчик прерываний. Рассмотрим это на конкретном примере.

#include <avr/io.h>
#define F_CPU 16000000UL //16MHz
#include <util/delay.h>

#define LED_BLUE_PB 3
#define LED_ORANGE_PB 2
#define LED_GREEN_PB 1

int main(void) {
    //output configuration
    DDRB  |= (1<<LED_BLUE_PB)|(1<<LED_ORANGE_PB)|(1<<LED_GREEN_PB);
    PORTB &= ~((1<<LED_BLUE_PB)|(1<<LED_ORANGE_PB)|(1<<LED_GREEN_PB));
    //interrupt setup
    GICR |= (1<<INT0)|(1<<INT1);
    MCUCR |= (1<<ISC00)|(1<<ISC01)|(1<<ISC10)|(1<<ISC11);
    //global interrupt enable
    sei();
    while(1) {
        //do nothing
    }
}

ISR(INT0_vect) {
    PORTB |= 1<<LED_BLUE_PB;
}

ISR(INT1_vect) {
    PORTB &= ~(1<<LED_BLUE_PB);
}

Теперь давайте по порядку разберем, что именно происходит во всех строках.
В первую очередь на выход настраиваются выводы. Обратите внимание, что, коль скоро мы будем использовать прерывания, нет необходимости настраивать на входы выводы, подключенные к кнопкам.
Далее происходит настройка регистров, отвечающих за режим работы внешний прерываний. Подробную информацию о назначении всех битов этих регистров вы найдете на странице 66 даташита на контроллер Atmega8. Нам необходимы только следующие:

  1. GICR — основной контролирующий регистр модуля прерываний. В необходимо выставить два бита — INT0 и INT1. Они разрешают жти прерывания. То есть, если на выводах контроллера PD2 и PD3 соответственно будет меняться напряжение — прерывания будут обрабатываться (смотри Pin Configurations на стр. 2 даташита).
  2. MCUCR отвечает за режим вызова прерываний. Прерывания может быть вызвано при возрастании напряжения на выводе, при уменьшении, при изменении и тогда, когда на выводе логический ноль. Например для прерывания INT1 режим задается битами ISC11 и ISC10. Мы выставили эти биты в единицу. Мы предлагаем вам самостоятельно выяснить при каком условии будет вызвано прерывание (стр. 66 даташита)

Затем необходимо глобально разрешить контроллеру обрабатывать прерывания вообще. Это делается вызовом функции sei(). Возможность глобального разрешения и запрета прерываний позволяет вам во время выполнения важных задач отключать вызов обработчиков прерываний. В нашем проекте такого не понадобится, поэтому мы просто раз и навсегда разрешили контроллеру выполнять прерывания.
Далее по коду запускается бесконечный цикл в котором ничего не выполняется.
Для работы остается только описать контроллеру, что делать при наступлении прерывания. Причем, если прерывание вызвано, а обработчика для него нет — контроллер повиснет.
Итак, всего в микроконтроллере Atmega8 девятнадцать векторов прерываний. Все они описаны на странице 46 даташита. Нас интересуют прерывания INT0 и INT1. Функция объявляется следующим образом: ISR(INT0_vect). Далее описывается какое-то действие. В нашем случае при срабатывании прерывания INT0 голубой светодиод включается, а в обработчике прерывания INT1 — выключается.
Вот как это выглядит:

Как вы уже выяснили, прерывание срабатывает, когда кнопка отпускается, то фронту импульса на выводе. Попробуйте настроить прерывание так, чтобы состояние светодиода изменялось в момент нажатия на кнопку.

Второй пример

Скорее всего на практике вы не заметите разницу и светодиод у вас будет включаться при нажатии на кнопку. Это связано с тем, что при нажатии появляется, так называемый, дребезг контактов. То есть контакты перед тем как полностью замкнуться или разомкнуться успевают несколько раз перейти из одного состояния в другое. Проверить эту особенность можно если добавить в программу вывод в COM-порт некоторых значений.
Заведем переменную и каждый раз в прерывании будем отправлять ее компьютеру:

#include <avr/io.h>
#define F_CPU 16000000UL //16MHz
#include <util/delay.h>

#define LED_BLUE_PB 3
#define LED_ORANGE_PB 2
#define LED_GREEN_PB 1

uint8_t i = 0;

void UARTSend(uint8_t data) {
    while(!(UCSRA & (1<<UDRE)));
    UDR = data;
}

int main(void) {
    //output configuration
    DDRB  |= (1<<LED_BLUE_PB)|(1<<LED_ORANGE_PB)|(1<<LED_GREEN_PB);
    PORTB &= ~((1<<LED_BLUE_PB)|(1<<LED_ORANGE_PB)|(1<<LED_GREEN_PB));
    //interrupt setup
    GICR |= (1<<INT0)|(1<<INT1);
    MCUCR |= (1<<ISC00)|(1<<ISC01)|(1<<ISC10)|(1<<ISC11);
    //UART setup
    UBRRH = 0;
    UBRRL = 103; //baud rate 9600
    UCSRB = (1<<RXEN)|(1<<TXEN);
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //8 bit, 1 stop bit
    //global interrupt enable
    sei();
    while(1) {
        //do nothing
    }
}

ISR(INT0_vect) {
    PORTB |= 1<<LED_BLUE_PB;
    UARTSend(i);
    i++;
}

ISR(INT1_vect) {
    PORTB &= ~(1<<LED_BLUE_PB);
    UARTSend(i);
    i--;
}

Если вы посмотрите в мониторе COM-порта, то увидите, что иногда, при нажатии кнопки, вам будет приходить не один, а несколько байт. Это значит, что из-за дребезга контактов прерывание было вызвано несколько раз.
С проблемой дребезга контактов можно бороться как схемотехнически, так и программно. Сейчас мы не будем останавливаться на этом.

Обработка энкодера

Энкодер подключен к тем же выводам и при вращении генерирует последовательности импульсов на своих выводах при вращении вала. На практике самый простой способ обработки энкодера это настройка одного из выводов на прерывание и при его срабатывании проверять состояние другого вывода. При вращении вала энкодера мы будем менять яркость свечения светодиода, так что придется вспомнить статью про ШИМ.

#include <avr/io.h>
#define F_CPU 16000000UL //16MHz
#include <util/delay.h>

#define LED_BLUE_PB 3
#define LED_ORANGE_PB 2
#define LED_GREEN_PB 1

uint8_t i = 0;

void UARTSend(uint8_t data) {
    while(!(UCSRA & (1<<UDRE)));
    UDR = data;
}

int main(void) {
    //output configuration
    DDRB  |= (1<<LED_BLUE_PB)|(1<<LED_ORANGE_PB)|(1<<LED_GREEN_PB);
    PORTB &= ~((1<<LED_BLUE_PB)|(1<<LED_ORANGE_PB)|(1<<LED_GREEN_PB));
    //interrupt setup
    GICR |= (1<<INT0)|(1<<INT1);
    MCUCR |= (1<<ISC00)|(1<<ISC01)|(1<<ISC10)|(1<<ISC11);
    //UART setup
    UBRRH = 0;
    UBRRL = 103; //baud rate 9600
    UCSRB = (1<<RXEN)|(1<<TXEN);
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //8 bit, 1 stop bit
    //global interrupt enable
    sei();
    while(1) {
        //do nothing
    }
}

ISR(INT0_vect) {
    PORTB |= 1<<LED_BLUE_PB;
    UARTSend(i);
    i++;
}

ISR(INT1_vect) {
    PORTB &= ~(1<<LED_BLUE_PB);
    UARTSend(i);
    i--;
}

Обратите внимание на изменения в коде.

  1. Добавлена настройка вывода PD3 на вход
  2. Инициализируется только прерывание INT0
  3. Добавлена настройка таймера
  4. В обработчике прерывания мы проверяем состояние вывода PD3 и в зависимости от него увеличиваем или уменьшаем с контролем переполнения переменную pwm, а затем меняем яркость светодиода

Обратите внимание, что меняться будет яркость зеленого светодиода. Это связано с тем, что на первых платах мы ставили светодиоды с другим порядком расположения цветов. Из-за этого на виде меняется яркость голубого светодиода:

Скорее всего вы заметите, что у энкодера дребезг контактов еще больше.

Индивидуальные задания

  1. Измените код первого примера так, чтобы при нажатии на кнопку светодиод менял свое состояние. То есть при каждом последующем нажатии он должен включаться или выключаться.
  2. Используя знания о работе с сегментным индикатором подключите его и выведите на дисплей значение переменной, изменяемой энкодером

Остальные статьи цикла можно найти здесь.

Мы будем очень рады, если вы поддержите наш ресурс и посетите магазин наших товаров shop.customelectronics.ru.

Метки: , , , Просмотров: 11834