До сих пор мы ставили delay() между действиями — и это работало. Но у delay() есть цена: пока он «спит», станция не реагирует ВООБЩЕ ни на что — ни на кнопку, ни на новое показание датчика. Сегодня разберём, как вести время без остановки программы. Это новая для тебя тема — в Python-курсе аналога не было.

После урока сможешь: объяснить, почему delay() блокирует программу, использовать millis() для отсчёта интервалов без остановки кода, вести два независимых процесса одновременно. В конце — станция с двумя независимыми ритмами.
Проблема delay(): станция «замирает»
Представь: в loop() стоит delay(1000) для мигания светодиода. Если в это время нажать кнопку — станция её не заметит: весь код внутри loop() ждёт, пока пройдёт секунда, и только потом снова доходит до digitalRead. Чем больше delay(), тем «глуше» станция к остальным событиям.
Проблема delay() — окно «глухоты»:
loop(): мигание -> delay(1000) -----------> проверка кнопки
^
|
кнопку нажали ЗДЕСЬ, но
программа её ещё не видит —
она "спит" в delay()
Представь повара, который одновременно варит суп и печёт пирог. Если он встанет у плиты с секундомером и будет ждать ровно 10 минут, ничего больше не делая, — это
delay(): пирог подгорит, пока повар «глухой» ко всему остальному. Хороший повар вместо этого поглядывает на часы на стене — засекает время и продолжает заниматься обоими блюдами. Часы на стене, на которые можно посмотреть в любой момент, не останавливая работу, — это и естьmillis().
millis() — часы платы с момента включения
Команда millis() возвращает, сколько миллисекунд прошло с момента включения платы — число, которое только растёт, никогда не останавливаясь и не ожидая. Хранить его нужно в типе unsigned long — это «длинное целое без знака», рассчитанное на очень большие растущие значения.
void setup() {
Serial.begin(9600);
}
void loop() {
unsigned long teper = millis();
Serial.println(teper);
}
0
1
2
3
...
Паттерн неблокирующего интервала
Идея: помнить, КОГДА действие выполнялось в прошлый раз (переменная previousMillis), и на каждом проходе loop() (без ожидания!) сравнивать: прошло ли уже достаточно времени. Если да — выполнить действие и запомнить новое «прошлый раз». Если нет — ничего не делать и идти дальше, не останавливаясь.
unsigned long previousMillis = 0;
const long interval = 1000;
bool ledState = false;
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
ledState = !ledState;
digitalWrite(13, ledState);
}
// сюда можно дописать ЛЮБОЙ другой код —
// он выполнится немедленно, без ожидания
}
Неблокирующий интервал (каждый проход loop() — мгновенный):
currentMillis - previousMillis >= interval ?
| |
да нет
v v
сделать действие ничего не делать,
и запомнить новое идти дальше по loop()
previousMillis |
| |
+------> loop() продолжается, не останавливаясь <------+
Ключевое отличие от delay(): loop() выполняется снова и снова без остановки, много раз в секунду. Условие внутри if просто пропускает действие, пока не пришло время, — но остальной код (проверка кнопки, чтение датчика) выполняется на КАЖДОМ проходе.
Два независимых процесса одновременно
Главная сила паттерна — можно вести НЕСКОЛЬКО независимых интервалов сразу, каждый со своей переменной previousMillis и своим interval. Они не мешают друг другу, даже если их периоды не кратны один другому — а с delay() так сделать было бы невозможно.
Два независимых ритма на одной шкале времени (мс): LED1 (interval=500): |----500----|----500----|----500----| LED2 (interval=300): |--300--|--300--|--300--|--300--|--300-- Оба идут на своих переменных previousMillis — независимо друг от друга
Миссия: два независимых ритма
Подключи два светодиода: LED1 на пин 13 (интервал 500 мс), LED2 на пин 12 (интервал 300 мс). Заведи для каждого свою пару переменных previousMillis/ledState и переключай их независимо в одном loop() — без единого delay().
unsigned long prevLed1 = 0;
unsigned long prevLed2 = 0;
const long interval1 = 500;
const long interval2 = 300;
bool state1 = false;
bool state2 = false;
void setup() {
pinMode(13, OUTPUT);
pinMode(12, OUTPUT);
}
void loop() {
unsigned long teper = millis();
if (teper - prevLed1 >= interval1) {
prevLed1 = teper;
state1 = !state1;
digitalWrite(13, state1);
}
if (teper - prevLed2 >= interval2) {
prevLed2 = teper;
state2 = !state2;
digitalWrite(12, state2);
}
}
Проверь себя
Контрольный вопрос. Что возвращает команда millis()?
Подсказка: Это разбиралось в блоке «millis() — часы платы с момента включения».
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Почему delay(1000) внутри loop() — проблема для отзывчивости станции?
Подсказка: Обратись к блоку «Проблема delay(): станция замирает».
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Зачем для millis() используют тип unsigned long, а не обычный int?
Подсказка: Число миллисекунд с момента включения платы очень быстро становится большим.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. previousMillis = 1000, interval = 500, currentMillis = 1600. Сработает ли условие currentMillis — previousMillis >= interval?
Подсказка: Посчитай разность 1600 — 1000 и сравни результат с 500.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. previousMillis = 1000, interval = 500, currentMillis = 1300. Сработает ли условие currentMillis — previousMillis >= interval?
Подсказка: Посчитай разность 1300 — 1000 и сравни результат с 500.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Программист забыл строку previousMillis = currentMillis; внутри if. Что произойдёт после того, как порог интервала будет пройден один раз?
Исходный код для этого задания:
if (currentMillis - previousMillis >= interval) { // строка previousMillis = currentMillis; ПРОПУЩЕНА digitalWrite(13, !digitalRead(13)); }Подсказка: previousMillis никогда не меняется — значит разность currentMillis — previousMillis будет только расти и оставаться больше interval.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. currentMillis = 900. Для LED1 (prevLed1=500, interval1=300) и LED2 (prevLed2=700, interval2=300) — какой из них сработает?
Исходный код для этого задания:
if (currentMillis - prevLed1 >= interval1) { /* LED1 */ } if (currentMillis - prevLed2 >= interval2) { /* LED2 */ }Подсказка: Посчитай разность для каждого отдельно: 900-500 и 900-700, сравни обе с 300.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Задание. Напиши код сам. Допиши условие вместо ___, которое проверяет, что прошло не меньше interval миллисекунд с previousMillis.
Исходный код для этого задания:
unsigned long currentMillis = millis(); if (___) { previousMillis = currentMillis; }Подсказка: Разность текущего и предыдущего времени должна быть не меньше interval — сравнение через >=.
✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Задание. Напиши программу целиком (около 11 строк). Пин 8 — светодиод. Через каждые 400 миллисекунд переключай его состояние (было включено — выключи, и наоборот), используя millis(), без единого delay().
Исходный код для этого задания:
// объяви previousMillis, interval=400, ledState и напиши setup()+loop()Подсказка: Собери из паттерна занятия: unsigned long previousMillis=0, const long interval=400, bool ledState=false снаружи; в loop() — currentMillis, сравнение, если сработало — обновить previousMillis, инвертировать ledState, digitalWrite.
✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. В программе используется millis() для интервалов, но внутри loop() всё равно стоит delay(50) «для стабильности». Что это значит для отзывчивости станции?
Подсказка: Любой delay() внутри loop(), даже маленький, снова блокирует выполнение на это время.
Онлайн-проверка ответа появится позже
Контрольный вопрос. Тип unsigned long, в котором хранится millis(), физически переполняется примерно через 50 дней непрерывной работы платы (счётчик доходит до предела и начинается заново с 0). Что произойдёт с вычислением currentMillis — previousMillis в этот момент, если оба значения — unsigned long?
Подсказка: Подумай, что физически происходит с unsigned-типом при переполнении — он не уходит в минус, а начинается заново с 0. Как это отражается на результате вычитания двух близких значений через границу переполнения?
Онлайн-проверка ответа появится позже
Задание. Напиши программу целиком (около 14 строк). Два независимых процесса без единого delay(): LED на пине 13 переключается каждые 500 мс, LED на пине 12 — каждые 350 мс. У каждого — своя пара previousMillis/ledState.
Исходный код для этого задания:
// заведи две независимые пары previousMillis/ledState и два interval, напиши setup()+loop()Подсказка: Повтори паттерн одного процесса дважды с разными именами переменных (prevLed1/prevLed2, state1/state2) — оба блока if идут подряд в одном loop(), не мешая друг другу.
✅ Готово, если: программа запускается без ошибок и выводит то, что просят в задании.
Онлайн-проверка ответа появится позже
Что дальше
Теперь станция умеет вести время, не останавливаясь. На следующем занятии подключим настоящий датчик среды — DHT22 — и научимся пользоваться внешней библиотекой.
Итог: ты понимаешь, почему delay() блокирует программу, умеешь вести интервал через millis() без остановки кода и вести несколько независимых процессов сразу. Дальше — библиотеки и реальный датчик DHT22.
