Fundamentos · 12 min de leitura

Arduino millis() vs delay(): temporização sem bloquear o código

Aprenda o millis() do Arduino: por que o delay() congela o sketch, piscar sem delay, várias tarefas ao mesmo tempo, temporização segura contra o estouro de 49 dias, micros(), ESP32 e Pico.

A função millis() do Arduino retorna o tempo desde que a placa começou a funcionar, em milissegundos, e é o jeito padrão de temporizar coisas sem delay(). Quando você escreve delay(1000), o Arduino não faz mais nada durante um segundo inteiro: um toque no botão passa despercebido, os sensores não são lidos e o display não é atualizado. Com millis() você pergunta “quanto tempo passou desde a última tarefa?”; roda as tarefas que estão na hora, pula as que não estão, e o loop() nunca para. Assim, um único Arduino consegue piscar LEDs, ler botões, medir sensores e enviar relatórios pela porta serial, tudo ao mesmo tempo.

Neste guia você vai aprender por que o delay() trava o programa, o famoso exemplo blink without delay (piscar sem delay), como rodar várias tarefas ao mesmo tempo, como passar com segurança pelo estouro que acontece depois de 49,7 dias, a função micros(), que conta microssegundos, e padrões de temporizador reutilizáveis. Nas últimas seções vamos ver como a mesma lógica é escrita no ESP32 e no Raspberry Pi Pico (MicroPython).

Placa Arduino Uno e o seu LED L embutido
Arduino Uno: graças ao LED “L” embutido no pino 13, você não precisa de nenhuma peça extra para testar o millis().

O que é o delay() e por que ele para o programa?

O delay(ms) mantém o processador girando em um laço até passar o tempo informado. Durante esse tempo a próxima linha do loop() não é alcançada; isso se chama espera bloqueante. Isso serve para piscar um único LED, mas o problema começa no momento em que você coloca um botão no projeto. O código abaixo pisca um LED uma vez por segundo e também verifica se um botão está apertado:

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

Neste código o botão só é percebido uma vez a cada dois segundos, e só se por acaso estiver apertado exatamente naquele instante; um toque curto se perde entre as duas chamadas de delay(). As interrupções continuam rodando durante o delay(): o millis() continua contando e os bytes que chegam pela porta serial ficam guardados no buffer de 64 bytes. Mas o seu código espera. Conforme você acrescenta display, sensores e buzzer, cada delay() atrasa todas as outras tarefas e o aparelho fica lento. A solução é parar de esperar e começar a medir o tempo.

O que é o millis() e como ele funciona?

O millis() retorna o número de milissegundos desde que a placa foi ligada ou reiniciada, como um unsigned long (32 bits, sem sinal). No Arduino Uno esse contador é atualizado em segundo plano pela interrupção de estouro do timer de hardware Timer0. A 16 MHz o Timer0 estoura a cada 1,024 ms; o código do core acumula os 0,024 ms extras e, mais ou menos a cada 43 ms, aumenta o contador em 2 em vez de 1. Ou seja, o millis() pula alguns valores, e é por isso que comparações de igualdade como millis() == 5000 não são confiáveis; use sempre >=. Repare também que o millis() não avança enquanto as interrupções estão desativadas (por exemplo, dentro de uma rotina de interrupção).

FunçãoO que fazResolução (Uno, 16 MHz)Estoura depois deBloqueia o programa?
delay(ms)Espera os milissegundos informados1 ms-Sim
delayMicroseconds(us)Espera curta (precisa de 3 a 16383 µs)Cerca de 1 µs-Sim
millis()Milissegundos desde a inicialização1 ms (às vezes avança de 2 em 2)Cerca de 49,7 diasNão
micros()Microssegundos desde a inicialização4 µsCerca de 71,6 minutosNão
  1. Crie uma variável unsigned long para lembrar quando a tarefa foi feita pela última vez (oncekiZaman).
  2. A cada passada pelo loop(), leia o tempo atual com millis().
  3. Verifique se passou tempo suficiente com a condição simdi - oncekiZaman >= ARALIK.
  4. Se o tempo acabou, faça a tarefa (inverta o LED) e atualize o oncekiZaman.
  5. Se não acabou, não faça nada e deixe o loop() seguir; é exatamente nesse momento que as suas outras tarefas rodam.
C++ (Arduino IDE)
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}

No código escrevemos oncekiZaman += ARALIK em vez de oncekiZaman = simdi. A diferença é pequena, mas importante: se o loop() às vezes atrasa alguns milissegundos, o primeiro método soma esse atraso toda vez e o LED vai se desviando com as horas, enquanto o segundo avança o horário-alvo em passos fixos e o período médio fica exatamente em 500 ms. Use += para relógios, metrônomos e medições regulares. Se o seu programa pode ficar bloqueado por muito tempo (por exemplo, esperando o Wi-Fi), o += vai tentar recuperar os passos perdidos um atrás do outro; nesse caso, = simdi é mais seguro. Se você também quer mudar o brilho do LED sem parar o código, dê uma olhada no artigo sobre PWM e analogWrite.

Fazendo várias coisas ao mesmo tempo: multitarefa com millis()

O Arduino Uno tem um processador de um núcleo e não consegue de verdade fazer duas coisas ao mesmo tempo. Mas, como o loop() pode rodar dezenas de milhares de vezes por segundo, dar a cada tarefa alguns microssegundos quando chega a vez dela parece “simultâneo” para quem vê de fora. Isso se chama multitarefa cooperativa. A regra é simples: cada tarefa tem a própria variável de tempo e o próprio intervalo, e nenhuma tarefa espera por muito tempo. No exemplo abaixo, quatro tarefas rodam lado a lado:

C++ (Arduino IDE)
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}
TarefaPinoIntervaloO que faz
LED vermelho8300 msPisca rápido
LED verde91000 msPisca devagar
Botão + LED amarelo2 e 10Debounce de 50 msInverte o LED amarelo a cada toque
Relatório serial-1000 msMostra o tempo de funcionamento em segundos

A lógica do botão também é baseada em millis(): cada vez que a leitura bruta muda, o temporizador recomeça, e, se a leitura fica estável por 50 ms, o toque é aceito como real. Assim, o repique mecânico do botão não transforma um toque em dezenas, e o código nunca espera. Para os detalhes da ligação do botão e do resistor de pull-up interno, veja Como ligar um botão no Arduino. Um resistor de 220 Ω basta para os LEDs; para outras cores e correntes, use a calculadora de resistor para LED.

O estouro de 49,7 dias e comparações seguras contra estouro

Um unsigned long guarda no máximo 4.294.967.295. Quando o millis() chega a esse número, depois de cerca de 49 dias, 17 horas e 2 minutos, ele volta a contar do 0. Se a sua placa fica ligada por semanas (controle de estufa, aquário, sistema de alarme), esse momento vai chegar. A boa notícia: o código escrito com subtração, na forma simdi - onceki >= aralik, não é afetado pelo estouro. A subtração sem sinal é feita módulo 2 elevado a 32. Por exemplo, se onceki = 4.294.967.000 e, depois do estouro, simdi = 200, então simdi - onceki não dá um número negativo; dá o resultado correto, 496. Então, desde que o intervalo que você espera seja menor que 49,7 dias, a comparação sempre funciona.

CódigoResultadoPor quê?
if (millis() - onceki >= aralik) (forma com subtração)CorretoA subtração sem sinal dá a diferença certa mesmo depois do estouro
if (millis() >= onceki + aralik) (forma com soma)ErradoQuando onceki + aralik estoura, vira um número pequeno e a condição dispara cedo demais
if (millis() == hedef)ErradoO millis() às vezes avança de 2 em 2, então o valor exato pode nunca aparecer
int onceki = millis(); (int de 16 bits)ErradoNo Uno o int tem 16 bits e guarda valores errados depois de 32,7 segundos
long onceki = millis(); (long com sinal)ArriscadoUm tipo com sinal fica negativo depois de 24,8 dias; use unsigned long para evitar confusão
Nunca declare variáveis de tempo como int. No Arduino Uno o int tem 16 bits e guarda no máximo 32.767, então o seu programa começa a se comportar de forma estranha a partir do 33º segundo. Toda variável que trabalha com millis() ou micros() deve ser um unsigned long (ou uint32_t). Ao escrever intervalos grandes, use também a forma 60000UL: no Uno, um produto como 60 * 1000 passa do limite do int e vira um número negativo.

Medindo microssegundos com micros()

O micros() funciona do mesmo jeito, mas retorna os microssegundos desde a inicialização. Em placas de 16 MHz como o Arduino Uno e o Nano a resolução é de 4 µs (8 µs em placas de 8 MHz), e ele estoura a cada 71,6 minutos mais ou menos; a regra da subtração vale aqui também. Ele é usado em tarefas precisas, como medir quanto tempo um trecho de código leva, cronometrar pulsos curtos ou gerar som. O exemplo abaixo mede, duas vezes por segundo, quanto tempo leva uma chamada de analogRead(); no Uno você vai ver cerca de 110–120 µs:

C++ (Arduino IDE)
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 mais longos, prefira millis() a micros(); um período de estouro de 71 minutos é curto demais para esperas longas. O delayMicroseconds() serve só para esperas curtas e inevitáveis de alguns microssegundos, como enviar o pulso de disparo de 10 µs ao pino TRIG do sensor de distância HC-SR04. Esperas tão curtas não atrasam de forma perceptível as suas outras tarefas.

Padrões de temporizador: periódico, de disparo único e máquina de estados

  • Tarefa periódica: uma tarefa que se repete em um intervalo definido, como um LED piscando, ler um sensor a cada 2 segundos ou atualizar um display a cada segundo. Você já viu esse padrão acima.
  • Temporizador de disparo único (timeout): roda uma vez, um tempo definido depois de um evento. A luz de escada é um bom exemplo: acenda a luz quando houver movimento, guarde o horário e apague se nenhum movimento novo chegar em 30 segundos. Renovar o horário a cada movimento novo prolonga o período.
  • Espera com limite de tempo: ao esperar uma resposta ou um sinal, você define um limite máximo para nunca ficar preso para sempre; por exemplo, mostre um erro se nenhum dado chegar em 2 segundos.
  • Máquina de estados: você guarda a etapa atual do aparelho em uma variável e passa para a próxima etapa quando o tempo de cada uma acaba. Semáforos, programas de máquina de lavar e fases de jogos são escritos assim.
  • Detecção de toque longo: medindo com millis() quanto tempo um botão fica apertado, você pode dar funções diferentes a toques curtos e longos.
C++ (Arduino IDE)
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}

Neste código, uma pequena estrutura (struct) chamada Zamanlayici reduz a uma linha só o ato de iniciar um período e perguntar se ele terminou. O semáforo é uma máquina de estados com quatro etapas: em cada etapa as lâmpadas são definidas e o tempo da próxima transição é iniciado. Como o loop() nunca espera, você pode acrescentar facilmente na mesma placa um botão de pedestre, um sensor de luz LDR para um modo noturno ou um display com o tempo restante. Se você precisar de muitos temporizadores, as bibliotecas de timer prontas do Gerenciador de Bibliotecas funcionam com o mesmo princípio.

millis(), delay() e Ticker no ESP32

No core Arduino do ESP32, millis() e micros() têm os mesmos nomes e o mesmo tipo unsigned long de 32 bits; os valores são calculados a partir do contador de 64 bits esp_timer_get_time(). Isso significa que os períodos de estouro de 49,7 dias e 71,6 minutos valem no ESP32 também, e você usa a mesma regra da subtração. A diferença importante está no delay(): no ESP32, o delay() chama o vTaskDelay() do FreeRTOS. O processador não fica girando à toa; outras tarefas, como Wi-Fi e Bluetooth, rodam enquanto você espera. Mas o seu loop() continua esperando, então tarefas como um servidor web, MQTT ou a leitura de botões atrasam. Para tarefas periódicas, a biblioteca Ticker, que vem com o core do ESP32, também é prática:

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

A função que você passa ao Ticker é chamada por uma tarefa de timer em segundo plano; não faça nada lento nela (requisições HTTP, saídas seriais longas, delay), só levante uma flag ou mude um pino. O esp_timer_get_time() de 64 bits não estoura por centenas de milhares de anos, então você pode usá-lo em medições que precisam de precisão de microssegundos em um aparelho que fica ligado por meses. O pino do LED embutido pode variar de placa para placa; confira o mapa de pinos da sua placa na ferramenta de pinagem. Se você está começando com o ESP32, o artigo O que é ESP32? é um bom começo para configuração e escolha de pinos.

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

No MicroPython você usa time.sleep(), time.sleep_ms() e time.sleep_us() em vez de delay(), e time.ticks_ms() em vez de millis(). Mas atenção: os valores de tick não dão a volta em 2 elevado a 32, como no Arduino, e sim em um limite menor que depende da plataforma. No Pico esse limite é de 2 elevado a 30 ms, cerca de 12,4 dias (você pode conferir na sua placa com time.ticks_add(0, -1)). Por isso, nunca subtraia nem compare dois valores de tick diretamente; obtenha a diferença com time.ticks_diff(new, old) e use time.ticks_add() para calcular um horário futuro. O código abaixo pisca o LED da placa do Pico sem bloquear, enquanto um botão no GP14 inverte um LED no 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 tarefas periódicas, o MicroPython também tem o machine.Timer: a linha Timer(period=500, mode=Timer.PERIODIC, callback=function) chama a sua função a cada 500 ms. Mantenha esses callbacks curtos, assim como no Ticker. Se você programa o Pico pela Arduino IDE (com o core arduino-pico), millis() e micros() funcionam exatamente como no Arduino. Para instalar o MicroPython no Pico e conhecer os pinos dele, veja o artigo O que é Raspberry Pi Pico?.

Ideias de projetos com millis()

  • Teste de reação: acenda um LED em um momento aleatório, meça com millis() o tempo até o botão ser apertado e mostre o resultado em milissegundos.
  • Luz de escada: acenda a luz quando um sensor PIR detectar movimento e apague 30 segundos depois do último movimento, enquanto o código continua fazendo outras tarefas.
  • Sensor de estacionamento que acelera: encurte o intervalo dos bipes do buzzer conforme a distância medida pelo HC-SR04 diminui.
  • Melodia sem bloqueio: encadeie as notas em um buzzer com millis() em vez de delay(), para que os botões continuem funcionando enquanto a música toca.
  • Sensor e display juntos: leia o sensor DHT11 a cada 2 segundos, mas atualize o display a cada 250 ms.
  • Cronômetro: monte um cronômetro com botões de iniciar, parar e zerar que mostra o tempo em um display de 7 segmentos.

Erros comuns e como corrigir

ErroSintomaSolução
Variável de tempo declarada como intO programa enlouquece depois de uns 33 segundosFaça de toda variável de tempo um unsigned long
Escrever millis() >= onceki + aralikA temporização quebra depois de 49,7 diasPasse para a forma millis() - onceki >= aralik
Verificar millis() == hedefO evento às vezes nunca disparaUse >= em vez de igualdade
Esquecer de atualizar o oncekiDepois que o tempo acaba, a tarefa se repete a cada passadaEscreva onceki = simdi ou onceki += aralik dentro da condição
Um delay() esquecido dentro do código do temporizadorAs outras tarefas continuam atrasandoDivida as esperas longas em uma máquina de estados
Esperar com millis() dentro de uma interrupçãoA placa congelaNunca espere dentro de uma ISR; só levante uma flag
Um produto int como 60 * 1000O intervalo sai negativo ou curto demaisEscreva 60000UL ou 60UL * 1000
Um jeito fácil de conferir se o seu código realmente roda sem bloqueio é contar quantas vezes o loop() roda por segundo: incremente um contador a cada passada, mostre-o na porta serial uma vez por segundo e zere. Se o número cair de repente de milhares para um dígito, alguma coisa em algum lugar está esperando (delay, pulseIn ou um laço while longo).

Quando você pega o jeito do millis(), seus projetos mudam completamente: LEDs, botões, sensores e displays trabalham juntos sem esperar uns pelos outros. Para o próximo passo, você pode escolher um projeto que precise de temporização na lista de ideias de projetos com Arduino. No app BlueGrays você explora passo a passo projetos como Semáforo, Estrobo de polícia, Iluminação inteligente de escada e corredor e Teste de reação com alerta sonoro, com esquema do circuito e código pronto, e testa na sua própria placa.

Perguntas frequentes

O que é o millis() no Arduino?

O millis() é uma função que retorna o tempo desde que o Arduino foi ligado ou reiniciado, em milissegundos. O valor é um unsigned long e recomeça do zero depois de cerca de 49,7 dias. Ele é usado para temporizar coisas sem parar o programa.

Qual é a diferença entre millis() e delay()?

O delay() pausa o programa pelo tempo informado, então nada mais, como ler botões ou atualizar um display, pode acontecer nesse meio-tempo. O millis() só diz quanto tempo passou e nunca espera. Verificando o tempo decorrido e rodando a tarefa que estiver na hora, você roda várias tarefas ao mesmo tempo.

Meu programa quebra quando o millis() estoura depois de 49 dias?

Não, se você verificar o tempo com subtração, na forma millis() - onceki >= aralik; a subtração sem sinal dá a diferença certa mesmo depois do estouro. Os problemas só aparecem com comparações escritas com soma, como millis() >= onceki + aralik, e com variáveis int.

Dá para zerar o millis() no Arduino?

Não há uma função oficial para zerar o millis(), e mexer na variável interna do core não é recomendado. Nem é preciso: guarde em uma variável o momento a partir do qual você quer contar e calcule o tempo decorrido a partir dele. Reiniciar a placa naturalmente faz o millis() recomeçar do 0.

O Arduino consegue fazer duas coisas ao mesmo tempo?

Como o Uno tem um núcleo só, ele não consegue executar fisicamente duas instruções ao mesmo tempo, mas, se você dividir cada tarefa em passos curtos com millis(), o loop() roda milhares de vezes por segundo e dá a vez a cada tarefa. Para quem vê de fora, o LED, o botão, o sensor e o display parecem funcionar todos juntos. O ESP32 também tem dois núcleos e tarefas do FreeRTOS.

O millis() é preciso o bastante para fazer um relógio?

O millis() depende do oscilador da placa; por causa do ressonador cerâmico do Arduino Uno, ele pode se desviar até alguns minutos por dia. Serve bem para medir intervalos curtos, mas, para um relógio que precisa ficar certo por dias, um módulo RTC como o DS1307 ou o DS3231, ou pegar a hora por NTP em um ESP32, é mais preciso.

Ver todos os projetos
LED piscante
#1Iniciante

LED piscante

O primeiro contato com a eletrônica: acender e apagar um único LED. O Arduino acende e apaga o LED em intervalos curtos, fazendo a luz piscar rapidamente. Você vê da forma mais simples como a placa co

4.6(188)
1127
Semáforo
#3Iniciante

Semáforo

Uma pequena versão do semáforo que você vê na rua todos os dias. O vermelho fica aceso por dois segundos, o amarelo acende por um instante, o verde fica aceso por dois segundos e depois volta ao verme

4.8(131)
397
Luzes de polícia com LEDs
#4Iniciante

Luzes de polícia com LEDs

Como as luzes de uma viatura: o LED vermelho pisca três vezes rapidamente, depois o LED azul pisca três vezes, e a sequência se repete sem parar. Basta ligar dois LEDs e dois resistores ao Arduino. Um

4.7(103)
258
Sequencial de LEDs com velocidade ajustável
#7Intermediário

Sequencial de LEDs com velocidade ajustável

Seis LEDs acendem um após o outro, fazendo a luz parecer correr de uma ponta à outra. Ao girar o botão do potenciômetro, a velocidade muda na hora: para um lado, a luz corre rapidamente; para o outro,

4.6(72)
146
Ligar e desligar um LED com botão
#10Iniciante

Ligar e desligar um LED com botão

Pressione o botão uma vez para acender a luz e outra vez para apagar, como o interruptor do seu quarto. O Arduino detecta cada toque e inverte o estado do LED, aguardando um breve instante para que as

4.8(100)
167
Iluminação inteligente de escadas e corredores
#82Intermediário
Premium

Iluminação inteligente de escadas e corredores

Ao entrar na escada ou no corredor, as luzes acendem uma a uma no sentido em que você caminha. Há um sensor de distância em cada extremidade; ao passar por um deles, os oito LEDs acendem a partir daqu

5.0(35)
51
Teste de reação com Arduino e aviso sonoro: meça seus reflexos em milissegundos (LCD + buzzer + 3 LEDs)
#97Intermediário
Premium

Teste de reação com Arduino e aviso sonoro: meça seus reflexos em milissegundos (LCD + buzzer + 3 LEDs)

Seus reflexos são rápidos? Aperte o botão: o LED vermelho diz preparar, o amarelo diz apontar e começa uma espera aleatória de 1,5 a 3,5 segundos. Assim que o LED verde acende e o buzzer apita, aperte de novo; seu tempo de reação aparece em milissegundos no LCD I2C, com seu recorde da sessão e sua média embaixo. Apertou antes do verde? A tela mostra TOO EARLY! e toca um som de derrota. Segure o botão para um novo jogador com a mesma ordem de esperas.

4.6(33)
50
Relógio digital com display de 7 segmentos de 4 dígitos
#48Intermediário
Premium

Relógio digital com display de 7 segmentos de 4 dígitos

Um relógio digital luminoso de quatro dígitos, parecido com o de um forno de cozinha. O módulo de relógio DS1302 mantém a hora e, a cada segundo, o Arduino lê as horas e os minutos e os exibe no displ

4.8(56)
73