Функция 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. Такой подход позволяет:
- одновременно управлять светодиодами, моторами и датчиками;
- писать более отзывчивый код (реагирующий на кнопки в реальном времени);
- строить сложные системы с несколькими таймерами.