4.06. Аппаратные прерывания.md


До сих пор все наши программы работали последовательно. Внутри бесконечного цикла loop() микроконтроллер неустанно проверял условия одно за другим: «Нажата ли кнопка? А сейчас? А теперь?». Такой метод называется опросом (polling), и он похож на то, как если бы вы каждые пять минут подбегали к почтовому ящику в ожидании письма. Это работает, но отнимает всё ваше время и внимание. А что, если письмо придёт и его тут же заберут, пока вы отвернулись? Вы его пропустите.

Точно так же и микроконтроллер может пропустить короткое событие (например, быстрое нажатие кнопки), если в этот момент он был занят выполнением delay() или сложными вычислениями. Чтобы решить эту проблему, существует гораздо более элегантный и эффективный механизм — аппаратные прерывания.

Принцип работы прерываний

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

Аппаратное прерывание работает точно так же. Вы «говорите» микроконтроллеру: «Следи за этим пином. Как только на нём изменится сигнал, немедленно приостанови loop(), выполни вот эту специальную короткую функцию и вернись обратно».

Это даёт два огромных преимущества: 1. Мгновенная реакция: Микроконтроллер реагирует на событие сразу же, а не когда до него дойдёт очередь в loop(). 2. Эффективность: Процессор не тратит время на постоянные проверки, а может заниматься другими полезными задачами.

Прерывания на плате Рудирон

На плате Рудирон некоторые пины специально предназначены для работы с аппаратными прерываниями. Как мы видим из документации и схемы, это пины 31, 32 и 35. Удобно, что именно к этим пинам подключены встроенные кнопки B2, B3 и B1 соответственно. Это позволяет нам экспериментировать с прерываниями без подключения внешних компонентов.

Настройка прерываний - attachInterrupt()

Чтобы «повесить» прерывание на пин, используется функция attachInterrupt(). Её обычно вызывают один раз в setup().

Синтаксис функции:

attachInterrupt(digitalPinToInterrupt(номерПина), ISR, режим);

Давайте разберём каждый параметр: 1. digitalPinToInterrupt(номерПина): Это важная функция-помощник. Она преобразует обычный номер пина (например, 35) в специальный номер прерывания, понятный микроконтроллеру. Всегда используйте её, чтобы указать, какой пин будет источником прерывания. 2. ISR (Interrupt Service Routine): Это имя вашей функции, которая будет вызвана при срабатывании прерывания. Её также называют обработчиком прерывания. Это должна быть функция, которая не принимает аргументов и ничего не возвращает (void myFunction()). 3. режим: Указывает, на какое именно изменение сигнала должен реагировать микроконтроллер: * LOW: Прерывание срабатывает, пока на пине удерживается низкий уровень. * CHANGE: При любом изменении уровня (с LOW на HIGH или с HIGH на LOW). * RISING: При переходе с LOW на HIGH (передний фронт импульса). * FALLING: При переходе с HIGH на LOW (задний фронт импульса).

Чтобы отключить прерывание, используется функция detachInterrupt(digitalPinToInterrupt(номерПина)).

Золотые правила написания обработчика прерываний (ISR)

Функция-обработчик прерывания — особенная. Во время её выполнения весь остальной мир для микроконтроллера замирает. Поэтому при её написании нужно соблюдать несколько строгих правил: 1. Делайте её максимально короткой и быстрой. loop() ждёт! Внутри ISR обычно только меняют значение переменной (устанавливают флаг), а все долгие действия выполняют в loop(). 2. Не используйте delay() и delayMicroseconds(). Эти функции зависят от таймеров, которые отключаются во время прерывания. 3. Будьте осторожны с Serial. Отправка данных по Serial тоже зависит от прерываний и может работать некорректно или привести к потере данных. Лучше установить флаг в ISR, а вывести данные в loop(). 4. Используйте volatile. Если вы используете переменную и в ISR, и в loop(), её нужно объявить с ключевым словом volatile. Например: volatile bool flag = false;. Это говорит компилятору, что переменная может измениться в любой момент (из-за прерывания), и он не должен кэшировать её значение, а всегда считывать из памяти.

Пример (включение/выключение встроенного светодиода L1 по нажатию кнопки B1):

// Используем встроенные имена для пинов
// BUTTON_BUILTIN_1 соответствует пину 35, который поддерживает прерывания
// LED_BUILTIN_1 соответствует пину 5

// Эта переменная будет изменяться в прерывании, поэтому она volatile
volatile bool ledState = LOW;

void setup() {
  pinMode(LED_BUILTIN_1, OUTPUT);
  pinMode(BUTTON_BUILTIN_1, INPUT_PULLDOWN); 

  // Настраиваем прерывание. При нажатии кнопки сигнал на пине станет высоким (RISING)
  attachInterrupt(digitalPinToInterrupt(BUTTON_BUILTIN_1), toggleLed, RISING);
  
  Serial.begin(9600);
  Serial.println("Система готова. Нажмите кнопку B1, чтобы изменить состояние светодиода L1.");
}

void loop() {
  // Основной цикл просто обновляет состояние светодиода на основе значения переменной ledState
  digitalWrite(LED_BUILTIN_1, ledState);

  // Здесь может быть любой другой код, и он не будет мешать реакции на кнопку.
}

// Это наш обработчик прерывания (ISR).
// Название может быть любым, главное — чтобы оно совпадало с тем, что мы указали в attachInterrupt
void toggleLed() {
  // Инвертируем состояние светодиода
  ledState = !ledState;
}

В этом примере loop() просто устанавливает состояние светодиода. При каждом нажатии на кнопку B1 мгновенно срабатывает прерывание и вызывается функция toggleLed(), которая меняет значение флага ledState. В следующем цикле loop() светодиод изменит своё состояние. Всё происходит мгновенно, и основной цикл не тратит время на проверку кнопки.

Зачем это нужно?

Аппаратные прерывания — это фундаментальный инструмент для создания отзывчивых и эффективных систем. Они незаменимы, когда нужно:

  • Мгновенно реагировать на важные сигналы (например, кнопка аварийной остановки).
  • Считать быстрые импульсы от энкодеров или датчиков скорости, не пропуская ни одного.
  • Декодировать сигналы от пультов дистанционного управления.
  • «Пробуждать» микроконтроллер из режима сна для экономии энергии.

Поздравляем с освоением прерываний! Теперь ваши проекты могут стать по-настоящему быстрыми и эффективными. Если вы хотите применить эти знания на практике, переходите к лабораторным работам, чтобы создать по-настоящему многозадачное устройство. А если вы готовы изучить ещё один способ заставить Рудирон делать несколько дел одновременно, но уже чисто программным методом, отправляйтесь в следующий параграф, где мы познакомимся с функцией millis().