Arduino millis() вместо delay(): тайминг без блокировки кода
Разбираем millis() в Arduino: почему delay() замораживает скетч, мигание без delay, несколько задач сразу, отсчёт без сбоя через 49 дней, micros(), ESP32 и Pico.
Функция millis() в Arduino возвращает время с момента запуска платы в миллисекундах, и это стандартный способ отсчитывать время без delay(). Когда в коде стоит delay(1000), Arduino целую секунду больше ничего не делает: нажатие кнопки остаётся незамеченным, датчики не опрашиваются, дисплей не обновляется. С millis() вопрос звучит иначе: «сколько времени прошло с последнего выполнения задачи?» Ты выполняешь задачи, которым пора, пропускаешь остальные, а loop() никогда не останавливается. Так одна плата Arduino может одновременно мигать светодиодами, читать кнопки, измерять показания датчиков и отправлять отчёты в последовательный порт.
В этом руководстве разберём, почему delay() блокирует программу, знаменитый пример blink without delay (мигание без delay), как запускать несколько задач одновременно, как безопасно пережить переполнение через 49,7 дня, функцию micros(), которая считает микросекунды, и готовые шаблоны таймеров. В последних разделах посмотрим, как та же логика пишется на ESP32 и на Raspberry Pi Pico (MicroPython).

Что такое delay() и почему она останавливает программу?
delay(ms) заставляет процессор крутиться в цикле, пока не пройдёт заданное время. Всё это время следующая строка в loop() не выполняется; это называется блокирующим ожиданием. Для мигания одним светодиодом этого достаточно, но проблемы начинаются, как только в проекте появляется кнопка. Код ниже раз в секунду мигает светодиодом и заодно проверяет, нажата ли кнопка:
1const int LED_PIN = 13; // dahili LED / built-in LED
2const int BUTON_PIN = 2; // buton, diğer ucu GND / button, other leg to GND
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 pinMode(BUTON_PIN, INPUT_PULLUP);
7 Serial.begin(9600);
8}
9
10void loop() {
11 digitalWrite(LED_PIN, HIGH);
12 delay(1000); // 1 sn bekle, buton okunmaz / wait 1 s, button is not read
13 digitalWrite(LED_PIN, LOW);
14 delay(1000);
15 if (digitalRead(BUTON_PIN) == LOW) { // 2 saniyede bir bakılır / checked once every 2 s
16 Serial.println(F("Butona basildi / Button pressed"));
17 }
18}В этом коде кнопку замечают лишь раз в две секунды, и то только если её держат именно в этот момент; короткое нажатие теряется между двумя вызовами delay(). Прерывания во время delay() продолжают работать: millis() считает дальше, а байты, пришедшие в последовательный порт, складываются в 64-байтный буфер. Но твой код ждёт. Стоит добавить дисплей, датчики и зуммер, как каждая delay() тормозит всю остальную работу и устройство начинает казаться неповоротливым. Решение — перестать ждать и начать измерять время.
Что такое millis() и как она работает?
millis() возвращает число миллисекунд с момента включения или сброса платы в виде unsigned long (32 бита, без знака). На Arduino Uno этот счётчик обновляется в фоне прерыванием по переполнению аппаратного таймера Timer0. На частоте 16 MHz Timer0 переполняется каждые 1,024 мс; код ядра накапливает лишние 0,024 мс и примерно каждые 43 мс увеличивает счётчик не на 1, а на 2. Поэтому некоторые значения millis() просто пропускает, и проверки на равенство вроде millis() == 5000 ненадёжны; всегда используй >=. Учти также, что при отключённых прерываниях (например, внутри обработчика прерывания) millis() не растёт.
| Функция | Что делает | Разрешение (Uno, 16 MHz) | Переполнение через | Блокирует программу? |
|---|---|---|---|---|
| delay(ms) | Ждёт заданное число миллисекунд | 1 мс | - | Да |
| delayMicroseconds(us) | Короткое ожидание (точно от 3 до 16383 µs) | Около 1 µs | - | Да |
| millis() | Миллисекунды с момента запуска | 1 мс (иногда шаг 2) | Около 49,7 дня | Нет |
| micros() | Микросекунды с момента запуска | 4 µs | Около 71,6 минуты | Нет |
Blink without delay: мигание светодиодом с millis()
- Заведи переменную типа unsigned long, в которой хранится момент последнего выполнения задачи (oncekiZaman).
- При каждом проходе loop() читай текущее время через millis().
- Проверяй, прошло ли достаточно времени, условием simdi - oncekiZaman >= ARALIK.
- Если время вышло, выполни задачу (переключи светодиод) и обнови oncekiZaman.
- Если нет, ничего не делай и дай loop() идти дальше; именно в этот момент работают остальные задачи.
1const int LED_PIN = LED_BUILTIN; // Uno'da 13 / pin 13 on the Uno
2const unsigned long ARALIK = 500; // yanıp sönme aralığı (ms) / blink interval (ms)
3
4unsigned long oncekiZaman = 0; // son değişim anı / time of the last toggle
5bool ledDurumu = false;
6
7void setup() {
8 pinMode(LED_PIN, OUTPUT);
9}
10
11void loop() {
12 unsigned long simdi = millis(); // şu anki zaman / current time
13 if (simdi - oncekiZaman >= ARALIK) { // taşmaya karşı güvenli / overflow-safe
14 oncekiZaman += ARALIK; // kaymayı önler / prevents drift
15 ledDurumu = !ledDurumu;
16 digitalWrite(LED_PIN, ledDurumu);
17 }
18 // burada başka işler beklemeden çalışır / other work runs here without waiting
19}В коде стоит oncekiZaman += ARALIK, а не oncekiZaman = simdi. Разница небольшая, но важная: если loop() иногда опаздывает на несколько миллисекунд, второй способ каждый раз прибавляет это опоздание, и за несколько часов светодиод «уплывает», а первый сдвигает целевое время фиксированными шагами, поэтому средний период остаётся ровно 500 мс. Используй += для часов, метрономов и регулярных измерений. Если программа может надолго заблокироваться (например, пока ждёт Wi-Fi), += будет пытаться наверстать пропущенные шаги один за другим; в таком случае надёжнее = simdi. Если хочешь ещё и менять яркость светодиода, не останавливая код, загляни в статью PWM и analogWrite.
Несколько дел одновременно: многозадачность с millis()
У Arduino Uno одноядерный процессор, и по-настоящему делать два дела одновременно он не может. Но loop() способна выполняться десятки тысяч раз в секунду, поэтому если давать каждой задаче несколько микросекунд, когда подходит её очередь, со стороны всё выглядит «одновременным». Это называется кооперативной многозадачностью. Правило простое: у каждой задачи своя переменная времени и свой интервал, и ни одна задача не ждёт долго. В примере ниже бок о бок работают четыре задачи:
1const int KIRMIZI_LED = 8; // 300 ms'de bir yanıp söner / blinks every 300 ms
2const int YESIL_LED = 9; // 1000 ms'de bir yanıp söner / blinks every 1000 ms
3const int SARI_LED = 10; // butonla açılıp kapanır / toggled by the button
4const int BUTON = 2; // diğer ucu GND / other leg to GND
5
6unsigned long kirmiziZaman = 0, yesilZaman = 0, butonZaman = 0, seriZaman = 0;
7bool kirmizi = false, yesil = false, sari = false;
8int sonOkuma = HIGH; // son ham okuma / last raw reading
9int kararliDurum = HIGH; // titreşimsiz durum / debounced state
10
11void setup() {
12 pinMode(KIRMIZI_LED, OUTPUT);
13 pinMode(YESIL_LED, OUTPUT);
14 pinMode(SARI_LED, OUTPUT);
15 pinMode(BUTON, INPUT_PULLUP);
16 Serial.begin(9600);
17}
18
19void loop() {
20 unsigned long simdi = millis();
21
22 // Görev 1: kırmızı LED / Task 1: red LED
23 if (simdi - kirmiziZaman >= 300) {
24 kirmiziZaman = simdi;
25 kirmizi = !kirmizi;
26 digitalWrite(KIRMIZI_LED, kirmizi);
27 }
28
29 // Görev 2: yeşil LED / Task 2: green LED
30 if (simdi - yesilZaman >= 1000) {
31 yesilZaman = simdi;
32 yesil = !yesil;
33 digitalWrite(YESIL_LED, yesil);
34 }
35
36 // Görev 3: 50 ms debounce ile buton / Task 3: button with 50 ms debounce
37 int okuma = digitalRead(BUTON);
38 if (okuma != sonOkuma) { // değişti, sayacı yeniden başlat / changed, restart the timer
39 butonZaman = simdi;
40 sonOkuma = okuma;
41 }
42 if (simdi - butonZaman >= 50 && okuma != kararliDurum) {
43 kararliDurum = okuma;
44 if (kararliDurum == LOW) { // basıldı / pressed
45 sari = !sari;
46 digitalWrite(SARI_LED, sari);
47 }
48 }
49
50 // Görev 4: saniyede bir seri rapor / Task 4: serial report once per second
51 if (simdi - seriZaman >= 1000) {
52 seriZaman = simdi;
53 Serial.print(F("Calisma suresi (s) / Uptime (s): "));
54 Serial.println(simdi / 1000);
55 }
56}| Задача | Пин | Интервал | Что делает |
|---|---|---|---|
| Красный светодиод | 8 | 300 мс | Мигает быстро |
| Зелёный светодиод | 9 | 1000 мс | Мигает медленно |
| Кнопка + жёлтый светодиод | 2 и 10 | Антидребезг 50 мс | Переключает жёлтый светодиод при каждом нажатии |
| Отчёт в порт | - | 1000 мс | Выводит время работы в секундах |
Логика кнопки тоже построена на millis(): при каждом изменении сырого показания таймер запускается заново, и если показание держится стабильным 50 мс, нажатие считается настоящим. Так дребезг контактов кнопки не превращает одно нажатие в десятки, а код никогда не ждёт. Подробнее о подключении кнопки и встроенном подтягивающем резисторе читай в статье Как подключить кнопку к Arduino. Для светодиодов достаточно резистора 220 Ω; для других цветов и токов воспользуйся калькулятором резистора для светодиода.
Переполнение через 49,7 дня и сравнения, устойчивые к переполнению
unsigned long вмещает не больше 4 294 967 295. Когда millis() доходит до этого числа, примерно через 49 дней, 17 часов и 2 минуты, счёт снова начинается с 0. Если плата работает неделями (управление теплицей, аквариум, сигнализация), этот момент наступит. Хорошая новость: код с вычитанием вида simdi - onceki >= aralik переполнения не боится. Беззнаковое вычитание выполняется по модулю 2 в степени 32. Например, если onceki = 4 294 967 000, а после переполнения simdi = 200, то simdi - onceki даёт не отрицательное число, а правильный результат 496. Поэтому, пока ожидаемый интервал короче 49,7 дня, сравнение всегда работает.
| Код | Результат | Почему? |
|---|---|---|
| if (millis() - onceki >= aralik) (форма с вычитанием) | Верно | Беззнаковое вычитание даёт правильную разницу даже через переполнение |
| if (millis() >= onceki + aralik) (форма со сложением) | Неверно | Когда onceki + aralik переполняется, сумма становится маленьким числом и условие срабатывает слишком рано |
| if (millis() == hedef) (проверка на равенство) | Неверно | millis() иногда шагает на 2, поэтому точное значение может так и не появиться |
| int onceki = millis(); (16-битный int) | Неверно | На Uno int 16-битный и после 32,7 секунды хранит неверные значения |
| long onceki = millis(); (знаковый long) | Рискованно | Знаковый тип через 24,8 дня уходит в минус; чтобы не путаться, используй unsigned long |
Измеряем микросекунды с micros()
micros() работает так же, но возвращает микросекунды с момента запуска. На платах с частотой 16 MHz, таких как Arduino Uno и Nano, её разрешение 4 µs (8 µs на платах 8 MHz), а переполняется она примерно каждые 71,6 минуты; правило вычитания действует и здесь. Её используют для точных задач: измерить, сколько выполняется фрагмент кода, засечь длительность коротких импульсов или генерировать звук. Пример ниже дважды в секунду измеряет, сколько длится вызов analogRead(); на Uno получится около 110–120 µs:
1unsigned long raporZaman = 0;
2
3void setup() {
4 Serial.begin(9600);
5}
6
7void loop() {
8 if (millis() - raporZaman >= 500) { // yarım saniyede bir ölç / measure every half second
9 raporZaman = millis();
10 unsigned long basla = micros(); // başlangıç (µs) / start (µs)
11 int deger = analogRead(A0);
12 unsigned long sure = micros() - basla; // geçen süre (µs) / elapsed time (µs)
13 Serial.print(F("A0 = "));
14 Serial.print(deger);
15 Serial.print(F(" sure / time: "));
16 Serial.print(sure);
17 Serial.println(F(" us"));
18 }
19}Для длинных интервалов выбирай millis(), а не micros(); период переполнения в 71 минуту слишком короткий для долгих ожиданий. delayMicroseconds() нужна только для коротких неизбежных пауз в несколько микросекунд, например чтобы подать импульс запуска 10 µs на пин TRIG ультразвукового датчика расстояния HC-SR04. Такие короткие паузы заметно не задерживают остальные задачи.
Шаблоны таймеров: периодический, однократный и конечный автомат
- Периодическая задача: работа, которая повторяется через заданный интервал: мигающий светодиод, опрос датчика каждые 2 секунды или обновление дисплея раз в секунду. Этот шаблон уже разобран выше.
- Однократный таймер (таймаут): срабатывает один раз через заданное время после события. Хороший пример — свет на лестнице: при движении включи свет, запомни время и выключи, если 30 секунд не было нового движения. Если при новом движении обновлять время, период продлевается.
- Ожидание с таймаутом: когда ждёшь ответа или сигнала, задаётся верхний предел, чтобы никогда не зависнуть навечно; например, показать ошибку, если данные не пришли за 2 секунды.
- Конечный автомат: текущий шаг устройства хранится в переменной, и когда время шага выходит, программа переходит к следующему. Так пишут светофоры, программы стиральной машины и уровни в играх.
- Распознавание долгого нажатия: если измерять через millis(), сколько держат кнопку, коротким и долгим нажатиям можно назначить разные действия.
1// Yeniden kullanılabilir zamanlayıcı / Reusable timer
2struct Zamanlayici {
3 unsigned long baslangic = 0;
4 unsigned long sure = 0;
5
6 void kur(unsigned long ms) { // süreyi şimdiden başlat / start the period from now
7 baslangic = millis();
8 sure = ms;
9 }
10 bool doldu() const { // süre doldu mu? / has it expired?
11 return millis() - baslangic >= sure;
12 }
13};
14
15const int KIRMIZI = 11, SARI = 12, YESIL = 13;
16enum Durum { DUR, HAZIRLAN, GEC, YAVASLA }; // trafik lambası adımları / traffic light states
17Durum durum = DUR;
18Zamanlayici isik;
19
20void lambalar(bool k, bool s, bool y) {
21 digitalWrite(KIRMIZI, k);
22 digitalWrite(SARI, s);
23 digitalWrite(YESIL, y);
24}
25
26void setup() {
27 pinMode(KIRMIZI, OUTPUT);
28 pinMode(SARI, OUTPUT);
29 pinMode(YESIL, OUTPUT);
30 lambalar(true, false, false);
31 isik.kur(5000); // kırmızı 5 sn / red for 5 s
32}
33
34void loop() {
35 if (isik.doldu()) {
36 switch (durum) {
37 case DUR: durum = HAZIRLAN; lambalar(true, true, false); isik.kur(1500); break;
38 case HAZIRLAN: durum = GEC; lambalar(false, false, true); isik.kur(5000); break;
39 case GEC: durum = YAVASLA; lambalar(false, true, false); isik.kur(2000); break;
40 case YAVASLA: durum = DUR; lambalar(true, false, false); isik.kur(5000); break;
41 }
42 }
43 // loop serbest: buton, sensör, ekran... / loop stays free: buttons, sensors, display...
44}В этом коде маленькая структура (struct) Zamanlayici сводит запуск периода и проверку, истёк ли он, к одной строке. Светофор — это конечный автомат из четырёх шагов: на каждом шаге задаётся состояние ламп и запускается отсчёт до следующего перехода. Поскольку loop() никогда не ждёт, к той же плате легко добавить кнопку для пешеходов, датчик освещённости на фоторезисторе (LDR) для ночного режима или дисплей с оставшимся временем. Если таймеров нужно много, готовые библиотеки таймеров из «Менеджера библиотек» (Library Manager) работают по тому же принципу.
millis(), delay() и Ticker на ESP32
В ядре ESP32 для Arduino у millis() и micros() те же имена и тот же 32-битный тип unsigned long; их значения вычисляются из 64-битного счётчика esp_timer_get_time(). Значит, периоды переполнения 49,7 дня и 71,6 минуты действуют и на ESP32, и правило вычитания то же. Важное отличие — в delay(): на ESP32 delay() вызывает vTaskDelay() из FreeRTOS. Процессор не крутится вхолостую; пока идёт ожидание, работают другие задачи, например Wi-Fi и Bluetooth. Но твоя loop() всё равно ждёт, поэтому веб-сервер, MQTT или опрос кнопок запаздывают. Для периодических задач удобна и библиотека Ticker, которая входит в ядро ESP32:
1#include <Ticker.h>
2
3const int LED1 = 2; // çoğu DevKit'te dahili LED / built-in LED on most DevKits
4const int LED2 = 4;
5
6Ticker ticker;
7bool led1Durum = false;
8bool led2Durum = false;
9unsigned long oncekiZaman = 0;
10
11void led1Degistir() { // kısa tut, uzun iş yapma / keep it short, no long work
12 led1Durum = !led1Durum;
13 digitalWrite(LED1, led1Durum);
14}
15
16void setup() {
17 Serial.begin(115200);
18 pinMode(LED1, OUTPUT);
19 pinMode(LED2, OUTPUT);
20 ticker.attach_ms(250, led1Degistir); // her 250 ms'de çağır / call every 250 ms
21}
22
23void loop() {
24 unsigned long simdi = millis();
25 if (simdi - oncekiZaman >= 1000) { // Arduino ile aynı desen / same pattern as on Arduino
26 oncekiZaman = simdi;
27 led2Durum = !led2Durum;
28 digitalWrite(LED2, led2Durum);
29 int64_t us = esp_timer_get_time(); // 64 bit, pratikte taşmaz / 64-bit, practically never overflows
30 Serial.printf("millis=%lu esp_timer=%lld us\n", simdi, us);
31 }
32}Функцию, переданную в Ticker, вызывает в фоне задача таймера; не делай в ней ничего медленного (HTTP-запросы, длинный вывод в порт, delay), только поставь флаг или переключи пин. 64-битный esp_timer_get_time() не переполнится сотни тысяч лет, так что его можно использовать для измерений с микросекундной точностью на устройстве, которое работает месяцами. Пин встроенного светодиода на разных платах может отличаться; карту пинов своей платы можно посмотреть в утилите Распиновка. Если ESP32 для тебя в новинку, для установки и выбора пинов хорошо начать со статьи Что такое ESP32?.
Raspberry Pi Pico (MicroPython): ticks_ms() и ticks_diff()
В MicroPython вместо delay() используются time.sleep(), time.sleep_ms() и time.sleep_us(), а вместо millis() — time.ticks_ms(). Но будь внимательнее: значения тиков сбрасываются не на 2 в степени 32, как на Arduino, а на меньшем пределе, который зависит от порта. На Pico этот предел — 2 в степени 30 мс, около 12,4 дня (проверить на своей плате можно через time.ticks_add(0, -1)). Поэтому никогда не вычитай и не сравнивай два значения тиков напрямую; разницу получай через time.ticks_diff(new, old), а время в будущем считай через time.ticks_add(). Код ниже мигает встроенным светодиодом Pico без блокировки, а кнопка на GP14 переключает светодиод на GP15:
1from machine import Pin
2import time
3
4led = Pin("LED", Pin.OUT) # dahili LED (Pico ve Pico W) / onboard LED (Pico and Pico W)
5dis_led = Pin(15, Pin.OUT) # GP15'e harici LED / external LED on GP15
6buton = Pin(14, Pin.IN, Pin.PULL_UP) # GP14 buton, diğer ucu GND / GP14 button, other leg to GND
7
8ARALIK = 500 # ms
9onceki = time.ticks_ms()
10son_okuma = 1 # son ham okuma / last raw reading
11kararli = 1 # titreşimsiz durum / debounced state
12degisim = time.ticks_ms() # son değişim anı / time of the last change
13
14while True:
15 simdi = time.ticks_ms()
16 if time.ticks_diff(simdi, onceki) >= ARALIK: # taşmaya karşı güvenli / wrap-safe
17 onceki = time.ticks_add(onceki, ARALIK) # kaymasız / drift-free
18 led.toggle()
19 okuma = buton.value()
20 if okuma != son_okuma: # değişti, sayacı yeniden başlat / changed, restart the timer
21 degisim = simdi
22 son_okuma = okuma
23 if time.ticks_diff(simdi, degisim) >= 50 and okuma != kararli: # 50 ms debounce
24 kararli = okuma
25 if kararli == 0: # basıldı / pressed
26 dis_led.toggle()
27Для периодических задач в MicroPython есть и machine.Timer: строка Timer(period=500, mode=Timer.PERIODIC, callback=function) вызывает твою функцию каждые 500 мс. Делай такие обработчики короткими, как и с Ticker. Если программировать Pico из Arduino IDE (с ядром arduino-pico), millis() и micros() работают точно так же, как на Arduino. Как установить MicroPython на Pico и разобраться с её пинами, читай в статье Что такое Raspberry Pi Pico?.
Идеи проектов с millis()
- Тест реакции: зажги светодиод в случайный момент, измерь через millis() время до нажатия кнопки и покажи результат в миллисекундах.
- Свет на лестнице: включай свет, когда PIR-датчик замечает движение, и выключай через 30 секунд после последнего движения, а код тем временем занимается другими делами.
- Парктроник с ускоряющимся писком: сокращай интервал сигналов зуммера по мере того, как расстояние, измеренное HC-SR04, уменьшается.
- Мелодия без блокировки: переключай ноты на зуммере через millis(), а не через delay(), чтобы кнопки работали, пока играет мелодия.
- Датчик и дисплей вместе: опрашивай датчик DHT11 каждые 2 секунды, а дисплей обновляй каждые 250 мс.
- Секундомер: собери секундомер с кнопками «старт», «стоп» и «сброс», который показывает время на 7-сегментном индикаторе.
Частые ошибки и как их исправить
| Ошибка | Признак | Решение |
|---|---|---|
| Переменная времени объявлена как int | Примерно через 33 секунды программа сходит с ума | Сделай все переменные времени unsigned long |
| Запись millis() >= onceki + aralik | Через 49,7 дня отсчёт времени ломается | Перейди на форму millis() - onceki >= aralik |
| Проверка millis() == hedef | Событие иногда так и не срабатывает | Используй >= вместо равенства |
| onceki не обновляется | Когда время вышло, задача повторяется на каждом проходе | Внутри условия пиши onceki = simdi или onceki += aralik |
| delay(), оставшаяся в коде таймера | Остальные задачи всё равно запаздывают | Разбей долгие ожидания на конечный автомат |
| Ожидание через millis() внутри прерывания | Плата зависает | Никогда не жди внутри ISR; только ставь флаг |
| Произведение типа int, например 60 * 1000 | Интервал получается отрицательным или слишком коротким | Пиши 60000UL или 60UL * 1000 |
Когда освоишься с millis(), проекты изменятся полностью: светодиоды, кнопки, датчики и дисплеи работают вместе и не ждут друг друга. Для следующего шага можно выбрать проект, где нужен отсчёт времени, из списка идей проектов на Arduino. В приложении BlueGrays можно пошагово, со схемой подключения и готовым кодом, изучить такие проекты, как «Светофор», «Полицейская мигалка», «Умное освещение лестницы и коридора» и «Тест реакции со звуковым сигналом», и попробовать их на своей плате.
Частые вопросы
Что такое millis() в Arduino?
millis() — функция, которая возвращает время с момента включения или сброса Arduino в миллисекундах. Её значение имеет тип unsigned long и примерно через 49,7 дня снова начинается с нуля. Её используют, чтобы отсчитывать время, не останавливая программу.
Чем millis() отличается от delay()?
delay() приостанавливает программу на заданное время, поэтому ничего другого, например опроса кнопок или обновления дисплея, в это время не происходит. millis() только сообщает, сколько прошло времени, и никогда не ждёт. Если проверять прошедшее время и выполнять задачи, которым пора, можно запускать несколько задач одновременно.
Сломается ли программа, когда millis() переполнится через 49 дней?
Нет, если проверять время вычитанием вида millis() - onceki >= aralik; беззнаковое вычитание даёт правильную разницу даже через переполнение. Проблемы появляются только у сравнений со сложением, вроде millis() >= onceki + aralik, и у переменных типа int.
Можно ли сбросить millis() на Arduino?
Официальной функции для сброса millis() нет, а менять внутреннюю переменную ядра не рекомендуется. Да это и не нужно: сохрани в переменную момент, от которого хочешь отсчитывать, и считай прошедшее время от него. При сбросе платы millis() естественным образом начинается с 0.
Может ли Arduino делать два дела одновременно?
У Uno одно ядро, поэтому физически выполнять две инструкции одновременно она не может, но если разбить каждую задачу на короткие шаги с millis(), loop() выполняется тысячи раз в секунду и даёт очередь каждой задаче. Со стороны кажется, что светодиод, кнопка, датчик и дисплей работают одновременно. У ESP32 к тому же два ядра и задачи FreeRTOS.
Достаточно ли точна millis(), чтобы сделать часы?
millis() зависит от генератора платы; из-за керамического резонатора на Arduino Uno она может уходить на несколько минут в сутки. Для измерения коротких интервалов этого хватает, но для часов, которые должны идти верно днями, точнее модуль RTC, например DS1307 или DS3231, или получение времени по NTP на ESP32.
Что собрать после прочтения
Все проекты
Мигающий светодиод
Самое первое «привет» в электронике: включение и выключение одного светодиода. Arduino включает и выключает светодиод через короткие промежутки, поэтому свет быстро мигает. Ты самым простым способом в

Светофор
Крошечная копия светофора, который ты каждый день видишь на дороге. Красный горит две секунды, жёлтый мигает на мгновение, зелёный горит две секунды, затем через жёлтый снова возвращается красный, и т

Полицейская мигалка на светодиодах
Как огни на полицейской машине: красный светодиод три раза быстро вспыхивает, затем три раза вспыхивает синий, и узор повторяется без перерыва. Нужны всего два светодиода и два резистора, подключённые

Бегущие огни с регулируемой скоростью
Шесть светодиодов загораются друг за другом, и кажется, что свет бежит от одного края к другому. Поверни ручку потенциометра, и скорость этого бега сразу меняется: в одну сторону свет мчится, в другую

Включение и выключение светодиода кнопкой (вкл/выкл)
Нажми кнопку один раз — свет загорится, нажми ещё раз — погаснет, совсем как выключатель в твоей комнате. Arduino замечает каждое нажатие и переводит светодиод в противоположное состояние, выжидая кор

Умное освещение лестницы и коридора
Ступи на лестницу или в коридор, и свет загорается ступенька за ступенькой в ту сторону, куда ты идёшь. На каждом конце стоит датчик расстояния; мимо какого ни пройдёшь, восемь светодиодов загораются

Тест времени реакции на Arduino со звуковым сигналом: измерь рефлексы в миллисекундах (LCD + зуммер + 3 светодиода)
Насколько быстрые у тебя рефлексы? Нажми кнопку: красный светодиод говорит приготовиться, жёлтый — внимание, и начинается случайное ожидание от 1,5 до 3,5 секунды. Как только загорится зелёный светодиод и пискнет зуммер, нажми снова; время реакции в миллисекундах появится на I2C LCD, а под ним — лучший и средний результат сессии. Нажмёшь до зелёного — на экране TOO EARLY! и звук проигрыша. Удержание кнопки — новый игрок, и у всех одинаковый порядок ожиданий.

Цифровые часы на 4-разрядном семисегментном индикаторе
Светящиеся цифровые часы из четырёх разрядов, почти как на кухонной духовке. Модуль часов DS1302 хранит время, а Arduino каждую секунду читает часы и минуты и показывает их на четырёхразрядном индикат
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

