Представьте, что вы сидите на кухне и варите кашу. Чтобы она не убежала, вам нужно помешивать её каждую минуту. Если вы будете просто смотреть на кастрюлю 30 минут — это скучно и непродуктивно.
А если вы параллельно будете мыть посуду, чистить картошку и болтать по телефону, но при этом каждую минуту отвлекаться на кашу? Поздравляю, вы только что поняли суть работы прерываний в микроконтроллерах AVR.
Большинство новичков пишут код, как школьник, который зубрит стих: строка за строкой, сверху вниз. Но мир микроконтроллеров жесток. Пока ваша программа тупо ждет нажатия кнопки (а это миллионы тактов процессора!), вы упускаете данные с датчика или «вешаете» всю систему.
Выход один — прерывания. И сегодня я разберу этот механизм по косточкам.
Что такое прерывание на пальцах?
Микроконтроллер AVR (например, популярные ATmega8, ATmega328P, ATtiny85) — это трудоголик. Он выполняет ваш код по порядку: i++, потом j++, потом проверка условия...
Но вдруг — БАМ! — на ножку пришел сигнал (кнопку нажали, таймер досчитал, данные из UART пришли).
Что делает процессор? Он не дожидается конца программы. Он делает пометку в блокноте: «Я остановился на строчке №42», лечит «рану» — выполняет специальный маленький блок кода (это называется обработчик прерывания или ISR), а затем возвращается и продолжает варить кашу, то есть выполнять основной код.
Главный плюс: Микроконтроллер не тратит время на постоянные проверки «А не нажали ли кнопку?». Он спит спокойно и просыпается только когда это реально нужно.
Анатомия прерывания в AVR (на примере кода)
Допустим, мы хотим, чтобы при каждом нажатии кнопки загорался светодиод. Без прерываний мы бы писали while(1){ if(PINB & (1<<PB0)){ PORTB ^= (1<<PB1); } } — и это жуткий костыль.
С прерываниями нужно сделать 3 шага:
Шаг 1. Скажем компилятору: «Слушай сюда!»
В начале программы подключаем библиотеку avr/interrupt.h и разрешаем глобальные прерывания одной магической командой sei(); (Set Enable Interrupts).
Шаг 2. Настроим ножку на «вызов» (прерывание)
В AVR есть внешние прерывания INT0 и INT1. Мы говорим микроконтроллеру: «Если на ножке PD2 будет спад напряжения (кнопку нажали) — немедленно докладывай».
Шаг 3. Пишем «Скорую помощь» (обработчик прерывания)
Обработчик пишется по строгому шаблону:
ISR(INT0_vect)
{
// Здесь КРАТКИЙ код. Никаких delay()!
PORTB ^= (1<<PB1); // Переключили светодиод
}
И всё! Пока основная программа может крутить 3D-графику на OLED-экране или опрашивать 10 датчиков, прерывание тихонько переключит вам светодиод в нужный момент.
Три кита, на которых стоят прерывания AVR
Чтобы вы не потерялись в инструкциях, запомните три источника событий:
- Внешние (INT0, INT1, PIN CHANGE): Нажал кнопку, пришел импульс с энкодера. Это для кнопок и датчиков движения.
- Таймерные (TIMER0_COMP, TIMER1_OVF): Самые любимые. Вы говорите: «Буди меня каждые 1 мс». Идеально для создания точных задержек, ШИМ-сигналов и «прошивки» светодиодных лент (WS2812).
- Периферийные (ADC, UART, TWI, SPI): Закончилось преобразование АЦП? Пришёл байт по UART? Прерывание само все подхватит.
Главная ошибка новичка (убейте в себе это привычку!)
Однажды вы напишете в обработчике прерывания функцию _delay_ms(1000); или printf(). И ваше устройство зависнет.
Запомните железное правило: Обработчик прерывания должен выполняться быстро, как выстрел. Максимум 10-20 микросекунд. Внутри обработчиков прерываний нельзя делать долгих задержек, ждать флагов или выполнения сложных математических расчетов.
Правильный подход:
- В прерывании вы просто ставите флаг (например, flag_button_pressed = 1;).
- Выходите из прерывания.
- В основном бесконечном цикле while(1) вы видите, что флаг установлен, выполняете длительные операции и сбрасываете флаг.
Почему без прерываний ваш код — детский лепет?
Я часто вижу на форумах вопросы: «Почему у меня мигает диод не каждую секунду, а хаотично?» или «Почему не видит нажатие кнопки?»
Ответ один: вы пытались сделать всё в одном цикле, и микроконтроллер просто не успевал вернуться к проверке кнопки, так как был занят другим.
Прерывания — это признак взрослого кода. Как только вы освоите прерывания, вы перестанете бояться таймеров, начнете понимать RTOS (операционные системы реального времени) и сможете делать проекты, где одновременно крутится мотор, мигает экран и опрашивается пульт.
Что делать прямо сейчас?
Откройте Arduino IDE (или Atmel Studio) и напишите пустышку:
#include <avr/interrupt.h>
void setup()
{
// Разрешаем прерывания
sei();
}
void loop()
{
// Тут ваш код
}
Настройте внешнее прерывание INT0. Прикрутите кнопку. И вы почувствуете магию — микроконтроллер сам отреагирует на событие, даже если он «спит» или крутит бесконечный цикл.
Лайфхак: Если вы используете Arduino (Uno/Nano), то ножки 2 и 3 — это INT0 и INT1. Функция attachInterrupt() скрывает всю сложность, но рано или поздно вам придется лезть под капот к регистрам MCUCR и GICR. Не бойтесь — это просто.
Итог
Механизм прерываний в AVR — это не страшная магия ассемблера, а удобный инструмент. Он позволяет создавать отзывчивые, многозадачные и энергоэффективные устройства.
Начните с малого: повесьте кнопку на прерывание. Потом добавьте таймер. Потом UART. И в какой-то момент вы поймете, что старая добрая «каша из проверок флагов в цикле» — это прошлый век.
А вы уже используете прерывания в своих проектах или до сих пор «вешаете» проц задержками? Пишите в комментариях!
На этом всё. Подписывайтесь на канал, чтобы ничего не пропустить…