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. Нам необходимы только следующие:
- GICR — основной контролирующий регистр модуля прерываний. В необходимо выставить два бита — INT0 и INT1. Они разрешают жти прерывания. То есть, если на выводах контроллера PD2 и PD3 соответственно будет меняться напряжение — прерывания будут обрабатываться (смотри Pin Configurations на стр. 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--; }
Обратите внимание на изменения в коде.
- Добавлена настройка вывода PD3 на вход
- Инициализируется только прерывание INT0
- Добавлена настройка таймера
- В обработчике прерывания мы проверяем состояние вывода PD3 и в зависимости от него увеличиваем или уменьшаем с контролем переполнения переменную pwm, а затем меняем яркость светодиода
Обратите внимание, что меняться будет яркость зеленого светодиода. Это связано с тем, что на первых платах мы ставили светодиоды с другим порядком расположения цветов. Из-за этого на виде меняется яркость голубого светодиода:
Скорее всего вы заметите, что у энкодера дребезг контактов еще больше.
Индивидуальные задания
- Измените код первого примера так, чтобы при нажатии на кнопку светодиод менял свое состояние. То есть при каждом последующем нажатии он должен включаться или выключаться.
- Используя знания о работе с сегментным индикатором подключите его и выведите на дисплей значение переменной, изменяемой энкодером
Остальные статьи цикла можно найти здесь.
Мы будем очень рады, если вы поддержите наш ресурс и посетите магазин наших товаров shop.customelectronics.ru.
Метки: AVR, interrupt, прерывания, энкодер Просмотров: 17120