Для электронщиков и радиолюбителей

Использование таймеров Arduino без delay(): как писать неблокирующий код

Использование таймеров Arduino без delay(): как писать неблокирующий код

Функция delay() — один из первых инструментов, с которыми знакомятся начинающие разработчики на Arduino. Она проста: delay(1000); приостанавливает выполнение программы на одну секунду. Но у такого подхода есть большой недостаток — код блокируется. Во время задержки Arduino ничего другого делать не может: не читает датчики, не реагирует на кнопки, не управляет моторами.

Решение? Использовать неблокирующий код на основе функции millis(). Давайте разберёмся, как это работает.


1. Почему delay() — это плохо

Пример с delay():

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000); // ждём 1 сек
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000); // ждём ещё 1 сек
}

Светодиод мигает раз в секунду, но если мы захотим одновременно считывать кнопку или передавать данные по UART — Arduino будет «глуха» во время задержки.


2. Функция millis()

Функция millis() возвращает количество миллисекунд, прошедших с момента включения Arduino. Главное: она не блокирует выполнение программы.

Идея проста:

  • запоминаем момент времени начала события;
  • периодически проверяем, сколько прошло;
  • если прошло нужное время — выполняем действие.

3. Базовый пример мигания без delay()

unsigned long previousMillis = 0;  
const long interval = 1000; // интервал 1 сек

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis; // обновляем момент времени
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // переключаем светодиод
  }

  // тут можно выполнять другие задачи, пока ждём интервал
}

Теперь светодиод мигает раз в секунду, и при этом программа остаётся свободной для других действий.


4. Несколько задач одновременно

Самое большое преимущество такого подхода — можно запускать несколько процессов параллельно.

Пример: светодиод мигает каждую секунду, а другой светодиод — каждые 200 мс.

unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;

const long interval1 = 1000; // 1 сек
const long interval2 = 200;  // 200 мс

void setup() {
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}

void loop() {
  unsigned long currentMillis = millis();

  // светодиод на пине 8
  if (currentMillis - previousMillis1 >= interval1) {
    previousMillis1 = currentMillis;
    digitalWrite(8, !digitalRead(8));
  }

  // светодиод на пине 9
  if (currentMillis - previousMillis2 >= interval2) {
    previousMillis2 = currentMillis;
    digitalWrite(9, !digitalRead(9));
  }

  // в это время можно читать датчики, обрабатывать кнопки и т.п.
}

5. Что делать, если нужно много таймеров?

Чтобы не дублировать код, можно использовать массивы или даже готовые библиотеки. Например:

  • Metro — простая библиотека для неблокирующих интервалов.
  • TaskScheduler — продвинутая система задач.

6. Учёт переполнения millis()

millis() переполняется примерно каждые 49 дней. Но при правильном сравнении через вычитание (currentMillis - previousMillis >= interval) код продолжит работать корректно.


7. Итоги

Использование millis() вместо delay() открывает дорогу к более сложным и «многозадачным» проектам на Arduino. Такой подход позволяет:

  • одновременно управлять светодиодами, моторами и датчиками;
  • писать более отзывчивый код (реагирующий на кнопки в реальном времени);
  • строить сложные системы с несколькими таймерами.