Millis() в Arduino – как управлять несколькими задачами одновременно без delay()

Функция millis() позволяет выполнять несколько операций одновременно: мигать разными светодиодами с независимыми интервалами, обрабатывать кнопку, считывать датчики — и всё это без блокировки скетча. В отличие от delay(), который полностью останавливает Arduino, millis() даёт возможность создавать отзывчивые проекты умные устройства, системы автоматики и IoT-проекты своими руками.

На практике покажем, как один скетч управляет четырьмя светодиодами с разными периодами (1 с, 3 с, 6 с) и мгновенно реагирует на кнопку — без единого delay().

millis arduino, delay без delay. millis примеры

Необходимые компоненты

  • Arduino Uno / Nano / Mega
  • 4 светодиода + резисторы 220–330 Ом
  • 1 кнопка (или тактовая)
  • Макетная плата и провода

Подключение

  • Светодиод 1 → пин 9
  • Светодиод 2 → пин10
  • Светодиод 3 → пин11
  • Светодиод 4 (от кнопки) → пин12
  • Кнопка → пин8 и GND (с внутренней подтяжкой)

Как это работает

  1. millis() возвращает количество миллисекунд с момента старта Arduino
  2. Для каждой задачи храним время последнего переключения (previousMillis)
  3. Если (millis() — previousMillis >= интервал) — выполняем действие и обновляем previousMillis
  4. Все задачи проверяются каждый проход loop() — никакого блокирования
  5. Переполнение millis() через ~50 дней обрабатывается автоматически (вычитание работает корректно)

Пример кода

const byte ledPins[] = {9, 10, 11};          // светодиоды с millis
const unsigned long intervals[] = {1000, 3000, 6000}; // интервалы в мс
unsigned long previousMillis[3] = {0, 0, 0};
bool ledState[3] = {false, false, false};

const byte buttonLed = 12;
const byte buttonPin = 8;

void setup() {
  for (byte i = 0; i < 3; i++) pinMode(ledPins[i], OUTPUT);
  pinMode(buttonLed, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

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

  // Управление тремя светодиодами
  for (byte i = 0; i < 3; i++) {
    if (currentMillis - previousMillis[i] >= intervals[i]) {
      previousMillis[i] = currentMillis;
      ledState[i] = !ledState[i];
      digitalWrite(ledPins[i], ledState[i]);
    }
  }

  // Управление светодиодом от кнопки (мгновенная реакция)
  digitalWrite(buttonLed, !digitalRead(buttonPin));
}
Управляет тремя светодиодами на пинах 9, 10 и 11 с разными интервалами включения/выключения (1, 3 и 6 секунд) без использования delay().
Светодиод на пине 12 повторяет состояние кнопки на пине 8 (инвертировано, т.е. загорается при нажатии).

Разбор кода

Массивы позволяют легко добавлять любое количество светодиодов. Условие if проверяется каждые ~10–50 мкс — кнопка отрабатывает мгновенно. Нет ни одного delay() — Arduino всегда свободна для других задач.

Преимущества

  • Одновременная работа десятков устройств
  • Мгновенная реакция на кнопки и датчики
  • Плавные анимации и ШИМ без мерцания
  • Возможность создавать сложные автоматы состояний
  • Надёжная работа в течение месяцев (переполнение не ломает логику)
  • Основа всех серьёзных проектов Arduino

Применение

  • Световые эффекты и бегущие огни
  • Умное освещение и реле с разными таймерами
  • Автополив, вентиляция, инкубаторы
  • Меню на дисплее с антидребезгом кнопок
  • Многоканальные логгеры данных
  • Любые проекты умного дома и IoT своими руками

Заключение

Переход от delay() к millis() — это главный шаг от «новичка» к «уверенному ардуинщику». Один раз освоив эту технику, вы навсегда забудете про «зависающие» проекты. Попробуйте пример прямо сейчас — и ваши светодиоды, кнопки и датчики начнут работать по-настоящему одновременно!

FAQ — работа с millis() и управление без delay на Arduino

Зачем использовать millis() вместо delay() в Arduino-проектах?

delay() полностью останавливает выполнение программы на заданное время. В этот момент Arduino не может обрабатывать кнопки, переключать светодиоды или выполнять другие задачи. millis() позволяет отслеживать интервалы времени без блокировки, благодаря чему несколько процессов работают параллельно и не мешают друг другу.

Как работает функция millis() и что именно она возвращает?

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

Правда ли, что millis() «переполняется» через 49 дней?

Да. Переменная, в которой хранится значение millis(), имеет тип unsigned long (32 бит). Максимальное значение — около 4,29 млрд. После превышения лимита отсчёт начинается с нуля. При правильном сравнении через разницу значений (millis() — lastTime) переполнение не вызывает ошибок, и таймеры продолжают работать корректно.

Можно ли управлять несколькими светодиодами одновременно с помощью millis()?

Да. Используя для каждого светодиода собственные переменные времени и интервалов, можно независимо контролировать их режимы: например, один мигает раз в секунду, другой — каждые 3 секунды, третий — каждые 6 секунд. В отличие от delay(), подобная работа не блокируется и выполняется параллельно.

Как работать с кнопкой без задержек, если используется millis()?

При работе через millis() считывание кнопки происходит в основном цикле без задержек. Благодаря этому реакция на нажатие происходит мгновенно, независимо от того, что делают остальные светодиоды. Это решает проблему, когда при задержках кнопка реагировала только «в свою очередь».

Можно ли полностью заменить delay() на millis() во всех скетчах?

Да, в большинстве случаев. millis() обеспечивает высокую отзывчивость и возможность выполнения нескольких задач параллельно. Однако delay() всё же подходит для простых демонстрационных примеров, где не требуется многозадачность и важно только последовательное выполнение событий.

📁 Скачать примеры