Fundamentos · 12 min de lectura

Arduino millis() en lugar de delay(): temporizar sin bloquear

Arduino millis(): por qué delay() detiene tu programa, blink sin delay, varias tareas a la vez, desbordamiento de 49 días sin errores, micros(), ESP32 y Pico.

La función millis() de Arduino devuelve el tiempo transcurrido desde que la placa empezó a funcionar, en milisegundos, y es la forma estándar de temporizar sin usar delay(). Cuando escribes delay(1000), el Arduino no hace nada más durante un segundo entero: si pulsas un botón no se entera, los sensores no se leen y la pantalla no se actualiza. Con millis(), en cambio, preguntas "¿cuánto tiempo ha pasado desde la última tarea?"; ejecutas las tareas que tocan, saltas las que no y loop() nunca se detiene. Así, un solo Arduino puede hacer parpadear LED, leer pulsadores, medir sensores y enviar informes al puerto serie al mismo tiempo.

En esta guía aprenderás por qué delay() bloquea tu programa, el famoso ejemplo blink without delay (parpadeo sin delay), cómo ejecutar varias tareas a la vez, cómo superar sin problemas el desbordamiento (overflow) que ocurre a los 49,7 días, la función micros() para medir microsegundos y patrones de temporizador reutilizables. En las últimas secciones verás cómo se escribe la misma lógica en el ESP32 y en la Raspberry Pi Pico (MicroPython).

Placa Arduino Uno con su LED L integrado
Arduino Uno: gracias al LED "L" integrado en el pin 13 no necesitas piezas adicionales para probar millis().

¿Qué es delay() y por qué detiene tu programa?

delay(ms) mantiene al procesador dando vueltas en un bucle hasta que pasa el tiempo indicado. Mientras tanto no se llega a la siguiente línea de loop(); a esto se le llama espera bloqueante. Para hacer parpadear un solo LED no hay problema, pero en cuanto añades un pulsador al proyecto empiezan los líos. El siguiente código hace parpadear un LED cada segundo y además comprueba si el pulsador está pulsado:

Arduino (C++)
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}

En este código el pulsador solo se detecta una vez cada dos segundos, y solo si justo en ese instante está pulsado; una pulsación corta se pierde entre las dos llamadas a delay(). Las interrupciones siguen funcionando durante delay(): millis() sigue contando y los bytes que llegan por el puerto serie se guardan en el búfer de 64 bytes. Pero tu código espera. A medida que añades pantalla, sensores y zumbador, cada delay() retrasa todas las demás tareas y el dispositivo se siente lento. La solución es dejar de esperar y empezar a medir el tiempo.

¿Qué es millis() y cómo funciona?

millis() devuelve los milisegundos transcurridos desde que la placa se encendió o se reinició, como unsigned long (32 bits, sin signo). En el Arduino Uno este contador se actualiza en segundo plano mediante la interrupción de desbordamiento del temporizador de hardware Timer0. A 16 MHz, Timer0 se desborda cada 1,024 ms; el núcleo acumula los 0,024 ms sobrantes y, aproximadamente cada 43 ms, aumenta el contador en 2 en lugar de 1. Es decir, millis() se salta algunos valores, por eso las comprobaciones de igualdad como millis() == 5000 no son fiables; usa siempre >=. Además, millis() no avanza mientras las interrupciones están desactivadas (por ejemplo, dentro de una rutina de interrupción).

FunciónQué haceResolución (Uno, 16 MHz)Se desborda tras¿Bloquea el programa?
delay(ms)Espera los milisegundos indicados1 ms-Sí
delayMicroseconds(us)Espera corta (precisa entre 3 y 16383 µs)Aprox. 1 µs-Sí
millis()Milisegundos desde el arranque1 ms (a veces salta de 2 en 2)Aprox. 49,7 díasNo
micros()Microsegundos desde el arranque4 µsAprox. 71,6 minutosNo
  1. Crea una variable de tipo unsigned long para recordar cuándo se hizo la tarea por última vez (oncekiZaman).
  2. En cada vuelta de loop(), lee el tiempo actual con millis().
  3. Comprueba si ha pasado suficiente tiempo con la condición simdi - oncekiZaman >= ARALIK.
  4. Si el tiempo se ha cumplido, haz la tarea (invertir el LED) y actualiza oncekiZaman.
  5. Si no, no hagas nada y deja que loop() continúe; justo en ese momento se ejecutan tus otras tareas.
Arduino (C++)
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}

En el código escribimos oncekiZaman += ARALIK en lugar de oncekiZaman = simdi. La diferencia es pequeña pero importante: si loop() llega a veces unos milisegundos tarde, el primer método acumula ese retraso cada vez y el LED se desfasa con las horas; el segundo avanza la hora objetivo en pasos fijos, de modo que el periodo medio sigue siendo exactamente 500 ms. Usa += para relojes, metrónomos y mediciones periódicas. Si tu programa puede quedarse bloqueado mucho tiempo (por ejemplo, esperando la conexión Wi-Fi), += intentará recuperar los pasos perdidos uno tras otro; en ese caso = simdi es más seguro. Si además quieres cambiar el brillo del LED sin detener el código, echa un vistazo al artículo sobre PWM y analogWrite.

Varias cosas a la vez: multitarea con millis()

El Arduino Uno tiene un procesador de un solo núcleo y no puede hacer de verdad dos cosas a la vez. Pero como loop() puede ejecutarse decenas de miles de veces por segundo, dedicar unos microsegundos a cada tarea cuando le toca parece "simultáneo" visto desde fuera. Esto se llama multitarea cooperativa. La regla es sencilla: cada tarea tiene su propia variable de tiempo y su propio intervalo, y ninguna tarea espera mucho. En el siguiente ejemplo funcionan cuatro tareas juntas:

Arduino (C++)
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}
TareaPinIntervaloQué hace
LED rojo8300 msParpadea rápido
LED verde91000 msParpadea despacio
Pulsador + LED amarillo2 y 10Antirrebote de 50 msEnciende o apaga el LED amarillo en cada pulsación
Informe serie-1000 msMuestra el tiempo de funcionamiento en segundos

La lógica del pulsador también se basa en millis(): cada vez que cambia la lectura en bruto, el temporizador se reinicia, y si la lectura se mantiene estable durante 50 ms la pulsación se da por buena. Así el rebote mecánico del pulsador no convierte una pulsación en decenas, y el código nunca espera. Para saber cómo conectar un pulsador y usar la resistencia pull-up interna, consulta Cómo conectar un pulsador a Arduino. Para los LED basta una resistencia de 220 Ω; para otros colores y corrientes usa la calculadora de resistencia para LED.

El desbordamiento de 49,7 días y las comparaciones seguras

Un unsigned long puede guardar como máximo 4.294.967.295. Cuando millis() llega a ese número, al cabo de unos 49 días, 17 horas y 2 minutos, vuelve a contar desde 0. Si tu placa va a estar encendida durante semanas (control de un invernadero, un acuario, una alarma), ese momento llegará. La buena noticia: el código escrito con resta, de la forma simdi - onceki >= aralik, no se ve afectado por el desbordamiento. La resta sin signo se hace módulo 2 elevado a 32. Por ejemplo, si onceki = 4.294.967.000 y tras el desbordamiento simdi = 200, la operación simdi - onceki no da un número negativo, sino el resultado correcto: 496. Así que, mientras el intervalo que esperas sea menor de 49,7 días, la comparación siempre funciona.

Forma de escribirloResultado¿Por qué?
if (millis() - onceki >= aralik)CorrectoLa resta sin signo da la diferencia correcta incluso a través del desbordamiento
if (millis() >= onceki + aralik)IncorrectoCuando onceki + aralik se desborda se convierte en un número pequeño y la condición se cumple antes de tiempo
if (millis() == hedef)Incorrectomillis() a veces salta de 2 en 2 y el valor exacto puede no aparecer nunca
int onceki = millis();IncorrectoEn el Uno un int es de 16 bits y guarda valores erróneos pasados 32,7 segundos
long onceki = millis();ArriesgadoUn tipo con signo pasa a negativo a los 24,8 días; usa unsigned long para evitar confusiones
Nunca declares las variables de tiempo como int. En el Arduino Uno un int es de 16 bits y guarda como máximo 32.767, así que tu programa empieza a comportarse de forma extraña a partir del segundo 33. Toda variable que trabaje con millis() o micros() debe ser unsigned long (o uint32_t). Al escribir intervalos grandes usa también la forma 60000UL: en el Uno, un producto como 60 * 1000 supera el límite de int y se convierte en un número negativo.

Medir microsegundos con micros()

micros() funciona igual, pero devuelve los microsegundos desde el arranque. En placas de 16 MHz como Arduino Uno y Nano su resolución es de 4 µs (8 µs en placas de 8 MHz) y se desborda aproximadamente cada 71,6 minutos; la regla de la resta también se aplica aquí. Se usa para trabajos precisos como medir cuánto tarda un trozo de código, temporizar pulsos cortos o generar sonido. El siguiente ejemplo mide dos veces por segundo cuánto tarda una llamada a analogRead(); en el Uno verás unos 110–120 µs:

Arduino (C++)
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}

Para intervalos largos prefiere millis() a micros(); 71 minutos hasta el desbordamiento se quedan cortos para esperas largas. delayMicroseconds() es solo para esperas cortas e inevitables de unos pocos microsegundos, como enviar el pulso de disparo de 10 µs al pin TRIG del sensor de distancia HC-SR04. Esperas tan breves no retrasan de forma apreciable tus otras tareas.

Patrones de temporizador: periódico, de un solo disparo y máquina de estados

  • Tarea periódica: un trabajo que se repite a intervalos fijos, como un LED que parpadea, leer un sensor cada 2 segundos o refrescar la pantalla cada segundo. Ya has visto el patrón más arriba.
  • Temporizador de un solo disparo (timeout): se ejecuta una vez, un tiempo determinado después de un evento. La luz de escalera es un buen ejemplo: enciende la luz cuando hay movimiento, guarda la hora y apágala si en 30 segundos no llega movimiento nuevo. Renovar la hora con cada movimiento alarga el periodo.
  • Espera con límite de tiempo: cuando esperas una respuesta o una señal, pones un límite superior para no quedarte atascado para siempre; por ejemplo, muestras un error si no llegan datos en 2 segundos.
  • Máquina de estados: guardas en una variable el paso en el que está el dispositivo y pasas al siguiente cuando se acaba el tiempo de cada paso. Los semáforos, los programas de una lavadora o los niveles de un juego se programan así.
  • Detección de pulsación larga: midiendo con millis() cuánto tiempo se mantiene pulsado un botón, puedes asignar tareas distintas a las pulsaciones cortas y largas.
Arduino (C++)
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}

En este código una pequeña estructura (struct) llamada Zamanlayici reduce a una sola línea iniciar un periodo y preguntar si ha terminado. El semáforo es una máquina de estados de cuatro pasos: en cada paso se ajustan las luces y se arranca el tiempo de la siguiente transición. Como loop() nunca espera, puedes añadir fácilmente a la misma placa un pulsador para peatones, un sensor de luz LDR para un modo nocturno o una pantalla con el tiempo restante. Si necesitas muchos temporizadores, las bibliotecas de temporizadores del Gestor de bibliotecas funcionan con el mismo principio.

millis(), delay() y Ticker en el ESP32

En el núcleo Arduino del ESP32, millis() y micros() tienen los mismos nombres y el mismo tipo unsigned long de 32 bits; sus valores se calculan a partir del contador de 64 bits esp_timer_get_time(). Por eso los desbordamientos de 49,7 días y 71,6 minutos también se aplican en el ESP32 y usas la misma regla de la resta. La diferencia importante está en delay(): en el ESP32, delay() llama a vTaskDelay() de FreeRTOS. El procesador no da vueltas en vacío; mientras esperas siguen funcionando otras tareas como el Wi-Fi y el Bluetooth. Aun así, tu loop() espera, de modo que un servidor web, MQTT o la lectura de pulsadores se retrasan. Para tareas periódicas también resulta práctica la biblioteca Ticker, incluida en el núcleo del ESP32:

Arduino (C++)
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}

La función que le pasas a Ticker la llama en segundo plano una tarea de temporizador; no hagas en ella nada lento (peticiones HTTP, salidas serie largas, delay), solo activa un indicador o cambia un pin. El contador de 64 bits esp_timer_get_time() no se desborda en cientos de miles de años, así que puedes usarlo para mediciones con precisión de microsegundos en un dispositivo que vaya a estar encendido durante meses. El pin del LED integrado puede cambiar según la placa; puedes ver el mapa de pines de la tuya en la herramienta Pinout. Si eres nuevo con el ESP32, el artículo ¿Qué es el ESP32? es un buen punto de partida para la instalación y la elección de pines.

Raspberry Pi Pico (MicroPython): ticks_ms() y ticks_diff()

En MicroPython se usan time.sleep(), time.sleep_ms() y time.sleep_us() en lugar de delay(), y time.ticks_ms() en lugar de millis(). Pero cuidado: los valores de ticks no vuelven a cero en 2 elevado a 32 como en Arduino, sino en un límite menor que depende del port. En la Pico ese límite es 2 elevado a 30 ms, unos 12,4 días (puedes comprobarlo en tu placa con time.ticks_add(0, -1)). Por eso nunca restes ni compares directamente dos valores de ticks; obtén la diferencia con time.ticks_diff(nuevo, viejo) y usa time.ticks_add() para calcular un instante futuro. El siguiente código hace parpadear el LED integrado de la Pico sin bloquear mientras un pulsador en GP14 enciende y apaga un LED en GP15:

MicroPython
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

Para tareas periódicas MicroPython también ofrece machine.Timer: la línea Timer(period=500, mode=Timer.PERIODIC, callback=funcion) llama a tu función cada 500 ms. Mantén estas funciones de callback cortas, igual que con Ticker. Si programas la Pico desde el Arduino IDE (con el núcleo arduino-pico), millis() y micros() funcionan exactamente como en Arduino. Para instalar MicroPython en la Pico y conocer sus pines, consulta el artículo ¿Qué es la Raspberry Pi Pico?.

Ideas de proyectos con millis()

  • Test de reacción: enciende un LED en un momento aleatorio, mide con millis() el tiempo hasta que se pulsa el botón y muestra el resultado en milisegundos.
  • Luz de escalera: enciende la luz cuando un sensor PIR detecte movimiento y apágala 30 segundos después del último movimiento, mientras el código sigue con otras tareas.
  • Sensor de aparcamiento que acelera: acorta el intervalo de pitidos del zumbador a medida que disminuye la distancia medida con el HC-SR04.
  • Melodía sin bloqueo: encadena las notas de un zumbador con millis() en lugar de delay(), para que los pulsadores sigan funcionando mientras suena la melodía.
  • Sensor y pantalla a la vez: lee el sensor DHT11 cada 2 segundos, pero refresca la pantalla cada 250 ms.
  • Cronómetro: construye un cronómetro con botones de inicio, parada y puesta a cero que muestre el tiempo en un display de 7 segmentos.

Errores frecuentes y cómo solucionarlos

ErrorSíntomaSolución
Variable de tiempo declarada como intEl programa se vuelve loco a los 33 segundos aprox.Haz que todas las variables de tiempo sean unsigned long
Escribir millis() >= onceki + aralikLa temporización falla a los 49,7 díasCambia a la forma millis() - onceki >= aralik
Comprobar millis() == hedefEl evento a veces no se dispara nuncaUsa >= en lugar de la igualdad
Olvidar actualizar oncekiUna vez cumplido el tiempo, la tarea se repite en cada vueltaEscribe onceki = simdi u onceki += aralik dentro de la condición
Un delay() olvidado dentro del código del temporizadorLas demás tareas siguen retrasándoseDivide las esperas largas en una máquina de estados
Esperar con millis() dentro de una interrupciónLa placa se cuelgaNo esperes nunca dentro de una ISR, solo activa un indicador
Producto int como 60 * 1000El intervalo sale negativo o demasiado cortoEscribe 60000UL o 60UL * 1000
Una forma fácil de comprobar que tu código realmente no bloquea es contar cuántas veces por segundo se ejecuta loop(): incrementa un contador en cada vuelta, imprímelo por el puerto serie una vez por segundo y ponlo a cero. Si el número cae de repente de miles a una sola cifra, en algún sitio hay código que espera (delay, pulseIn o un bucle while largo).

Cuando dominas la lógica de millis(), tus proyectos cambian por completo: LED, pulsadores, sensores y pantallas funcionan juntos sin esperarse unos a otros. Como siguiente paso puedes elegir un proyecto que necesite temporización de la lista de ideas de proyectos con Arduino. En la app BlueGrays puedes ver paso a paso proyectos como el Semáforo, las Luces de policía, la Iluminación inteligente de escaleras y pasillos o el Test de reacción con aviso sonoro, con esquema del circuito y código listo, y probarlos en tu propia placa.

Preguntas frecuentes

¿Qué es millis() en Arduino?

millis() es una función que devuelve el tiempo transcurrido desde que el Arduino se encendió o se reinició, en milisegundos. Su valor es de tipo unsigned long y vuelve a empezar desde cero tras unos 49,7 días. Se usa para temporizar sin detener el programa.

¿Cuál es la diferencia entre millis() y delay()?

delay() detiene el programa durante el tiempo indicado, así que mientras tanto no puede hacerse nada más, como leer pulsadores o actualizar una pantalla. millis() solo te dice cuánto tiempo ha pasado y nunca espera. Comprobando el tiempo transcurrido y ejecutando la tarea que toque, puedes llevar varias tareas a la vez.

¿Se rompe mi programa cuando millis() se desborda a los 49 días?

No, si compruebas el tiempo con una resta de la forma millis() - onceki >= aralik; la resta sin signo da la diferencia correcta incluso a través del desbordamiento. Los problemas aparecen solo con comparaciones escritas con suma, como millis() >= onceki + aralik, y con variables int.

¿Se puede poner a cero millis() en Arduino?

No existe una función oficial para reiniciar millis() y no se recomienda modificar la variable interna del núcleo. Tampoco hace falta: guarda en una variable el instante desde el que quieres contar y calcula el tiempo transcurrido a partir de ahí. Reiniciar la placa hace que millis() empiece de nuevo desde 0.

¿Puede un Arduino hacer dos cosas a la vez?

Como el Uno tiene un solo núcleo, no puede ejecutar físicamente dos instrucciones al mismo tiempo, pero si divides cada tarea en pasos cortos con millis(), loop() se ejecuta miles de veces por segundo y da turno a todas. Visto desde fuera, el LED, el pulsador, el sensor y la pantalla parecen funcionar a la vez. El ESP32, además, tiene dos núcleos y tareas de FreeRTOS.

¿Es millis() lo bastante preciso para hacer un reloj?

millis() depende del oscilador de la placa; por el resonador cerámico del Arduino Uno puede desviarse hasta unos minutos al día. Es suficiente para medir intervalos cortos, pero para un reloj que deba mantenerse exacto durante días es mejor un módulo RTC como el DS1307 o el DS3231, o tomar la hora por NTP con un ESP32.

Ver todos los proyectos
Parpadeo de LED
#1Principiante

Parpadeo de LED

Proyecto básico de entrada que enciende y apaga un único LED a determinados intervalos con Arduino. Enseña el uso de las funciones digitalWrite y delay.

4.7(163)
784
Semáforo
#3Principiante

Semáforo

Proyecto que simula un semáforo real con LED rojos, amarillos y verdes. Enseña temporización y lógica de control secuencial.

4.8(123)
306
Luces de policía LED
#4Principiante

Luces de policía LED

Proyecto que crea un efecto de luz estroboscópica policial haciendo parpadear dos LEDs a intervalos rápidos. Enseña para el control de bucle y temporización.

4.7(95)
212
Luz secuencial LED con velocidad regulable
#7Intermedio

Luz secuencial LED con velocidad regulable

El proyecto que puede ajustar la velocidad de la luz secuencial LED en tiempo real con el potenciómetro. Enseña el control dinámico del tiempo de retardo mediante la lectura del valor del potenciómetr

4.6(71)
133
Encender y apagar un LED con un botón
#10Principiante

Encender y apagar un LED con un botón

Un proyecto de conmutación que cambia el estado del LED (encendido/apagado) cada vez que se pulsa un botón. Enseña la variable de estado y la lógica de rebote.

4.8(100)
158
Iluminación inteligente de escaleras y pasillos
#82Intermedio
Premium

Iluminación inteligente de escaleras y pasillos

Un elegante proyecto de automatización con un sensor de distancia bidireccional HC-SR04 que se enciende y se apaga alternativamente en la dirección del movimiento.

5.0(35)
51
Prueba de reacción con señal sonora con Arduino
#97Intermedio
Premium

Prueba de reacción con señal sonora con Arduino

Estación de prueba de reacción Arduino Este proyecto es una configuración de prueba digital desarrollada para medir la velocidad de reflejo de un usuario ante estímulos visuales y auditivos con una precisión de milisegundos (ms). El sistema consta de un controlador Arduino, un grupo de notificación LED de tres etapas, un zumbador para aviso sonoro y una pantalla LCD I2C que muestra los resultados instantáneamente. Principio de funcionamiento: Cuando se inicia la prueba, el sistema prepara al us

4.6(33)
49
Reloj digital con display de 7 segmentos de 4 dígitos
#48Intermedio
Premium

Reloj digital con display de 7 segmentos de 4 dígitos

Proyecto de reloj digital que muestra horas y minutos con módulo RTC DS1302 y display de 7 segmentos de 4 dígitos. Combina datos de reloj en tiempo real y visualización por segmentos.

4.8(56)
73

Descubre por qué un pulsador responde tarde cuando usas delay(), cómo manejar LED, pulsadores y sensores a la vez con millis(), cómo superar el desbordamiento de 49,7 días y cuáles son los equivalentes en ESP32 y Pico.