Robótica com C — Bloco 2
Todo sistema que muda de comportamento ao longo do tempo — um semáforo, uma fechadura, um robô — pode ser modelado como uma máquina de estados: um conjunto de estados possíveis e as regras que determinam quando e como transitar entre eles.
Um semáforo é o exemplo mais simples e concreto que existe:
Três estados, três transições, ciclo infinito. Parece trivial — mas esse padrão é idêntico ao que você vai usar no robô para alternar entre "avançar", "desviar" e "parar". Aprender bem aqui significa chegar nos projetos com o padrão já internalizado.
Conexão com C: você usou switch e enum desde as aulas iniciais. No Arduino eles ganham um papel central — enum nomeia os estados de forma legível e switch decide o comportamento de cada um. A variável de estado é simplesmente um int ou enum que guarda em qual estado o sistema está agora.
enum para nomear estadosVocê poderia representar os estados do semáforo com inteiros: 0 = verde, 1 = amarelo, 2 = vermelho. Funciona, mas o código fica opaco. enum resolve isso sem custo nenhum — por baixo dos panos ainda é um inteiro, mas o nome torna o código autoexplicativo:
// Sem enum — funciona, mas quem lê não sabe o que é 0, 1 ou 2 int estado = 0; if (estado == 0) { /* verde */ } // Com enum — estado é um nome, não um número mágico typedef enum { VERDE, // vale 0 automaticamente AMARELO, // vale 1 VERMELHO // vale 2 } EstadoSemaforo; EstadoSemaforo estado = VERDE; if (estado == VERDE) { /* imediatamente legível */ }
typedef enum no Arduino: funciona exatamente igual ao C que você aprendeu. A partir da Aula 24, você já usava enum com typedef. Aqui a novidade é o uso — o enum representa um estado de hardware que muda ao longo do tempo, não apenas uma constante.
Com o enum definido, o switch fica naturalmente legível:
switch (estado) {
case VERDE:
// acende verde, apaga os outros
break;
case AMARELO:
// acende amarelo, apaga os outros
break;
case VERMELHO:
// acende vermelho, apaga os outros
break;
}
O semáforo usa três LEDs (vermelho, amarelo/laranja e verde) cada um com resistor de 220Ω. No Wokwi basta arrastar e conectar:
Pino 11 → Resistor 220Ω → LED VERMELHO → GND Pino 10 → Resistor 220Ω → LED AMARELO → GND Pino 9 → Resistor 220Ω → LED VERDE → GND
| Pino | Cor | Constante no código |
|---|---|---|
| 11 | 🔴 Vermelho | PINO_VERMELHO |
| 10 | 🟡 Amarelo | PINO_AMARELO |
| 9 | 🟢 Verde | PINO_VERDE |
delay()A versão mais direta. Boa para entender a estrutura antes de evoluir para millis():
/* * Semáforo v1 — com delay() * Verde: 4s | Amarelo: 3s | Vermelho: 5s */ const int PINO_VERDE = 9; const int PINO_AMARELO = 10; const int PINO_VERMELHO = 11; const int T_VERDE = 4000; const int T_AMARELO = 3000; const int T_VERMELHO = 5000; typedef enum { VERDE, AMARELO, VERMELHO } EstadoSemaforo; EstadoSemaforo estado = VERDE; // começa no verde // Apaga todos os LEDs void todosApagados() { digitalWrite(PINO_VERDE, LOW); digitalWrite(PINO_AMARELO, LOW); digitalWrite(PINO_VERMELHO, LOW); } void setup() { pinMode(PINO_VERDE, OUTPUT); pinMode(PINO_AMARELO, OUTPUT); pinMode(PINO_VERMELHO, OUTPUT); Serial.begin(9600); } void loop() { switch (estado) { case VERDE: todosApagados(); digitalWrite(PINO_VERDE, HIGH); Serial.println("VERDE — siga"); delay(T_VERDE); estado = AMARELO; // transição break; case AMARELO: todosApagados(); digitalWrite(PINO_AMARELO, HIGH); Serial.println("AMARELO — atenção"); delay(T_AMARELO); estado = VERMELHO; // transição break; case VERMELHO: todosApagados(); digitalWrite(PINO_VERMELHO, HIGH); Serial.println("VERMELHO — pare"); delay(T_VERMELHO); estado = VERDE; // transição — fecha o ciclo break; } }
VERDE — siga
AMARELO — atenção
VERMELHO — pare
VERDE — siga
...
Limitação do delay(): enquanto o semáforo está esperando no delay(T_VERDE), o Arduino não faz absolutamente nada. Para este projeto isolado isso não é problema. Mas se você quiser adicionar um botão de pedestre ou um sensor, o delay() vai ignorá-los durante a espera. A versão 2 resolve isso.
millis()A versão profissional: o loop() continua rodando livremente — o semáforo só troca de estado quando o tempo programado expirou. Isso deixa o Arduino disponível para outras tarefas simultaneamente.
A lógica é a mesma da Aula 41b: guarda o instante da última troca e verifica se já passou o tempo do estado atual:
/* * Semáforo v2 — com millis() (versão profissional) * O loop() fica livre para outras tarefas */ const int PINO_VERDE = 9; const int PINO_AMARELO = 10; const int PINO_VERMELHO = 11; typedef enum { VERDE, AMARELO, VERMELHO } EstadoSemaforo; EstadoSemaforo estado = VERDE; unsigned long ultimaTroca = 0; // momento em que o estado mudou // Retorna o tempo (ms) que o estado atual deve durar unsigned long tempoDuEstado(EstadoSemaforo e) { switch (e) { case VERDE: return 4000; case AMARELO: return 3000; case VERMELHO: return 5000; } return 1000; } // Retorna o próximo estado na sequência EstadoSemaforo proximoEstado(EstadoSemaforo e) { switch (e) { case VERDE: return AMARELO; case AMARELO: return VERMELHO; case VERMELHO: return VERDE; } return VERDE; } // Acende apenas o LED do estado atual void atualizarLEDs(EstadoSemaforo e) { digitalWrite(PINO_VERDE, e == VERDE); digitalWrite(PINO_AMARELO, e == AMARELO); digitalWrite(PINO_VERMELHO, e == VERMELHO); } void setup() { pinMode(PINO_VERDE, OUTPUT); pinMode(PINO_AMARELO, OUTPUT); pinMode(PINO_VERMELHO, OUTPUT); Serial.begin(9600); atualizarLEDs(estado); ultimaTroca = millis(); } void loop() { unsigned long agora = millis(); // Verifica se o tempo do estado atual expirou if (agora - ultimaTroca >= tempoDuEstado(estado)) { estado = proximoEstado(estado); // avança o estado ultimaTroca = agora; // reinicia o cronômetro atualizarLEDs(estado); Serial.print("→ Estado: "); Serial.println(estado == VERDE ? "VERDE" : estado == AMARELO ? "AMARELO" : "VERMELHO"); } // Aqui há espaço para: leitura de botão de pedestre, // atualização de display, comunicação Serial, etc. }
→ Estado: AMARELO
→ Estado: VERMELHO
→ Estado: VERDE
→ Estado: AMARELO
Três funções bem separadas: tempoDuEstado() sabe quanto tempo cada estado dura, proximoEstado() sabe qual estado vem a seguir, e atualizarLEDs() sabe qual LED acender. O loop() só coordena — não sabe nada sobre pinos ou tempos. Esse é o padrão que os projetos do Bloco 3 vão usar.
A vantagem real do millis() aparece aqui. Adicione um botão no pino 7: quando pressionado durante o ciclo VERDE, força a transição imediata para AMARELO — sem travar nada.
// Acrescente ao código da v2 — só as partes novas: const int PINO_BTN_PEDESTRE = 7; void setup() { // ... (igual ao anterior) ... pinMode(PINO_BTN_PEDESTRE, INPUT_PULLUP); } void loop() { unsigned long agora = millis(); // Botão de pedestre: força AMARELO se estiver no VERDE if (digitalRead(PINO_BTN_PEDESTRE) == LOW && estado == VERDE) { Serial.println("Pedestre solicitou travessia!"); estado = AMARELO; ultimaTroca = agora; atualizarLEDs(estado); } // Lógica de tempo (igual ao anterior) if (agora - ultimaTroca >= tempoDuEstado(estado)) { estado = proximoEstado(estado); ultimaTroca = agora; atualizarLEDs(estado); } }
Isso seria impossível com delay(). Com delay(T_VERDE), o Arduino ficaria congelado durante 4 segundos e não detectaria o botão. Com millis(), o loop() passa pelo teste do botão milhares de vezes por segundo — e reage imediatamente ao pressionamento.
| Aspecto | C puro (terminal) | Arduino |
|---|---|---|
| Representar estados | enum + switch |
enum + switch — idêntico |
| Controlar tempo | sleep() ou time(NULL) |
delay() (bloqueante) ou millis() (não bloqueante) |
| Saída do estado | printf() no terminal |
digitalWrite() em pinos físicos + Serial.println() |
| Encapsular lógica | Funções com parâmetro enum |
Funções com parâmetro enum — idêntico |
| Loop principal | while(1) |
loop() — equivalente |
| Reatividade a eventos | Verificar condição dentro do while | Verificar condição dentro do loop() — idêntico |
O padrão que você vai usar em todos os projetos do Bloco 3:
enumloop(), verificar se é hora de transitar com millis()O robô, a campainha, o portão e o carrinho do Bloco 3 vão todos usar exatamente essa estrutura.
Teste o que você aprendeu sobre máquinas de estados e semáforo: