Guias · 14 min de leitura

Semáforo com Arduino: circuito, código, botão de pedestre e cruzamento

Monte um semáforo com Arduino: ligação de 3 LEDs com 220 Ω, código com delay e millis, botão de pedestre, cruzamento de duas vias, Tinkercad e versões para ESP32 e Pico.

Um semáforo com Arduino é o primeiro projeto clássico da eletrônica: três LEDs, vermelho, amarelo e verde, acendem e apagam em sequência como um sinal de trânsito de verdade. Você só precisa de um Arduino Uno, três LEDs, três resistores de 220 Ω e alguns jumpers, e a primeira versão funcionando fica pronta em meia hora. Mesmo assim, esse projeto simples ensina muito: saídas digitais, cálculo de LED e resistor, temporização, leitura de botão e a máquina de estados, a forma mais limpa de organizar o seu código, tudo cabe nesses poucos LEDs.

Neste guia você vai ver primeiro em que ordem e por quanto tempo os semáforos reais mudam, depois montar o circuito e escrever o código mais simples com delay(). Em seguida vai adicionar um botão de pedestre, reescrever a mesma lógica com millis() e uma máquina de estados para que ela nunca bloqueie, e controlar um cruzamento de duas vias. As seções finais mostram como testar o projeto no Tinkercad sem instalar nada, as versões para ESP32 e Raspberry Pi Pico (MicroPython) e dicas para escrever um relatório escolar sobre o projeto.

LEDs de 5 mm vermelho, amarelo e verde para um semáforo com Arduino
LEDs de 5 mm: um vermelho, um amarelo e um verde bastam para o semáforo; separe mais alguns para o sinal de pedestre e para o cruzamento.

Como funciona um semáforo de verdade?

Um semáforo é um temporizador que dá a vez a cada fluxo de trânsito. No Reino Unido, na Alemanha e na Turquia, o ciclo é vermelho → vermelho + amarelo → verde → amarelo → vermelho. Vermelho e amarelo juntos significam “o verde está vindo, prepare-se, mas não saia ainda”, enquanto o amarelo sozinho significa “o sinal vai ficar vermelho, prepare-se para parar”. Nos EUA, na França, na Itália, na Espanha e também no Brasil não existe a fase vermelho + amarelo: o sinal passa direto do vermelho para o verde. O tempo do amarelo também não é aleatório: as regras alemãs definem 3 segundos de amarelo em vias com limite de 50 km/h, 4 segundos a 60 km/h e 5 segundos a 70 km/h, porque um motorista que está perto demais para parar quando o amarelo aparece ainda precisa conseguir atravessar o cruzamento com segurança.

FaseLuz(es) acesa(s)SignificadoTempo neste guia
1VermelhoPare5 s
2Vermelho + amareloO verde está vindo, prepare-se1 s
3VerdeSiga5 s
4AmareloO vermelho está vindo, pare3 s
Em um cruzamentoTodas as direções no vermelhoIntervalo de segurança para liberar o cruzamento1 s

Lista de peças e o cálculo do resistor de 220 Ω

PeçaQtd.Observação
Arduino Uno (ou Nano)1Um ESP32 ou Raspberry Pi Pico também serve
LEDs de 5 mm: vermelho, amarelo, verde1 de cada+1 vermelho e +1 verde para o sinal de pedestre, +1 conjunto para o cruzamento
Resistor de 220 Ω3Um por LED; 8 para todas as versões
Botão (6×6 mm)1Só na versão com pedestre
Protoboard1Meia protoboard (400 furos) basta
Jumpers10–20Macho-macho
Cabo USB1Para carregar o código e alimentar a placa

LEDs não limitam a própria corrente; um LED ligado direto em 5 V puxa mais corrente do que ele e o pino aguentam. Por isso cada LED leva um resistor em série. Você encontra o valor dele com a lei de Ohm: R = (tensão de alimentação − tensão direta do LED) / corrente. Para um LED vermelho, (5 V − 2,0 V) / 0,015 A ≈ 200 Ω; com o valor comercial mais próximo, 220 Ω, a corrente fica em cerca de 13,6 mA. Isso acende o LED com bom brilho e fica abaixo dos 20 mA recomendados para um pino do Arduino. Um resistor de 220 Ω é marcado vermelho-vermelho-marrom-dourado nas peças de quatro faixas e vermelho-vermelho-preto-preto-marrom nas de cinco faixas; para ter certeza, use a ferramenta código de cores de resistores e, para outras cores e tensões, a calculadora de resistor para LED.

Cor do LEDTensão direta (aprox.)Corrente em 5 V (220 Ω)Corrente em 3,3 V (220 Ω)
Vermelho1,8–2,2 V≈ 13,6 mA≈ 5,9 mA
Amarelo1,9–2,2 V≈ 13,2 mA≈ 5,5 mA
Verde (verde-amarelado)2,0–2,4 V≈ 13,2 mA≈ 5,5 mA
Verde (intenso, verde puro)2,8–3,4 V≈ 9,1 mA≈ 1,4 mA (bem fraco)

Ligação do circuito e tabela de pinos

A perna longa de cada LED (ânodo, +) vai através de um resistor a um pino digital do Arduino, e a perna curta (cátodo, −) vai ao GND. Não importa se o resistor fica do lado do ânodo ou do cátodo, desde que esteja em série. Na protoboard, o jeito mais organizado é ligar o pino GND do Arduino ao barramento azul (−) e levar as pernas curtas dos três LEDs a esse barramento. A tabela abaixo lista os pinos usados por todos os sketches deste guia; o primeiro sketch só usa as três primeiras linhas. Você pode ver a disposição dos pinos da sua placa na ferramenta Pinagem.

FunçãoArduino UnoESP32Raspberry Pi Pico
Vermelho dos veículos (direção A)D10GPIO 5GP13
Amarelo dos veículos (direção A)D9GPIO 18GP12
Verde dos veículos (direção A)D8GPIO 19GP11
Vermelho do pedestreD7GPIO 21GP15
Verde do pedestreD6GPIO 22GP14
Botão do pedestre (outra perna no GND)D2GPIO 23GP4
Vermelho / amarelo / verde da direção BD5 / D4 / D3GPIO 25 / 26 / 27GP8 / GP7 / GP6
Cátodos dos LEDs e botãoGNDGNDGND
Um LED invertido simplesmente fica apagado e não se danifica, mas, se você esquecer o resistor, o LED e o pino ficam sobrecarregados e o LED pode perder o brilho rápido. No Arduino Uno, mire em no máximo 20 mA por pino (o máximo absoluto é 40 mA) e mantenha a soma de todos os pinos abaixo de 200 mA. Se nenhum LED acender, confira o GND primeiro: se o barramento azul da protoboard não estiver ligado a um pino GND do Arduino, o circuito nunca se fecha.

Passo 1: o código de semáforo mais simples com delay()

C++ (Arduino IDE)
1// Arduino trafik lambası: delay() ile en basit sürüm / simplest version with delay()
2const int KIRMIZI = 10;  // kırmızı LED / red LED
3const int SARI = 9;      // sarı LED / yellow LED
4const int YESIL = 8;     // yeşil LED / green LED
5
6// Üç LED'i tek satırda ayarla / set all three LEDs in one line
7void lambalar(int k, int s, int y) {
8  digitalWrite(KIRMIZI, k);
9  digitalWrite(SARI, s);
10  digitalWrite(YESIL, y);
11}
12
13void setup() {
14  pinMode(KIRMIZI, OUTPUT);
15  pinMode(SARI, OUTPUT);
16  pinMode(YESIL, OUTPUT);
17}
18
19void loop() {
20  lambalar(HIGH, LOW, LOW);   // DUR: kırmızı / STOP: red
21  delay(5000);
22  lambalar(HIGH, HIGH, LOW);  // HAZIRLAN: kırmızı + sarı / GET READY: red + yellow
23  delay(1000);
24  lambalar(LOW, LOW, HIGH);   // GEÇ: yeşil / GO: green
25  delay(5000);
26  lambalar(LOW, HIGH, LOW);   // YAVAŞLA: sarı / SLOW DOWN: yellow
27  delay(3000);
28}

O sketch define os três pinos como saídas no setup() e executa as quatro fases em ordem dentro do loop(). Em vez de escrever três linhas de digitalWrite várias vezes, usamos uma pequena função chamada lambalar() (“lâmpadas”), assim cada fase fica em uma única linha e é fácil achar um erro. Para carregar, escolha Arduino Uno como placa e a porta certa no menu Ferramentas e clique em Carregar. Se o carregamento falhar, siga a lista de verificação em Erro ao carregar código no Arduino. Para mudar os tempos, basta editar os valores em milissegundos dentro de delay(): 5000 significa 5 segundos.

Passo 2: semáforo com botão de pedestre

As travessias com botão que você vê na cidade mantêm os carros no verde e só dão a vez aos pedestres quando alguém aperta o botão. Nesta versão, acrescentamos ao semáforo dos veículos um sinal de pedestre com dois LEDs (vermelho e verde) e um botão no D2. Uma perna do botão vai ao D2 e a perna diagonalmente oposta ao GND; como o código usa INPUT_PULLUP, não é preciso resistor externo e o pino lê LOW enquanto o botão está pressionado. Você pode ler mais sobre as ligações internas de um botão de quatro pernas e sobre debounce em Como ligar um botão ao Arduino.

Botão de quatro pernas de 6×6 mm
Botão de 6×6 mm: as quatro pernas são ligadas internamente aos pares; se você usar duas pernas diagonalmente opostas, a ligação sempre fica certa.
C++ (Arduino IDE)
1// Yaya butonlu trafik lambası (delay ile) / pedestrian push-button light (with delay)
2const int KIRMIZI = 10, SARI = 9, YESIL = 8;  // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6;   // yaya lambası / pedestrian light
4const int BUTON = 2;                          // diğer ucu GND / other leg to GND
5
6void setup() {
7  pinMode(KIRMIZI, OUTPUT);
8  pinMode(SARI, OUTPUT);
9  pinMode(YESIL, OUTPUT);
10  pinMode(YAYA_KIRMIZI, OUTPUT);
11  pinMode(YAYA_YESIL, OUTPUT);
12  pinMode(BUTON, INPUT_PULLUP);               // basılınca LOW okunur / reads LOW when pressed
13  digitalWrite(YESIL, HIGH);                  // araçlar geçer / cars go
14  digitalWrite(YAYA_KIRMIZI, HIGH);           // yayalar bekler / pedestrians wait
15}
16
17void loop() {
18  if (digitalRead(BUTON) == LOW) {            // yaya butona bastı / pedestrian pressed
19    delay(2000);                              // araçlara kısa süre tanı / short grace time for cars
20    digitalWrite(YESIL, LOW);
21    digitalWrite(SARI, HIGH);                 // sarı / yellow
22    delay(3000);
23    digitalWrite(SARI, LOW);
24    digitalWrite(KIRMIZI, HIGH);              // araçlar durur / cars stop
25    delay(1000);                              // tüm kırmızı güvenlik payı / all-red safety gap
26    digitalWrite(YAYA_KIRMIZI, LOW);
27    digitalWrite(YAYA_YESIL, HIGH);           // yayalar geçer / pedestrians cross
28    delay(6000);
29    for (int i = 0; i < 6; i++) {             // yaya yeşili 3 kez yanıp söner / green man flashes 3 times
30      digitalWrite(YAYA_YESIL, i % 2 == 0 ? LOW : HIGH);
31      delay(500);
32    }
33    digitalWrite(YAYA_YESIL, LOW);
34    digitalWrite(YAYA_KIRMIZI, HIGH);
35    delay(1000);                              // tüm kırmızı / all red
36    digitalWrite(SARI, HIGH);                 // kırmızı + sarı / red + yellow
37    delay(1000);
38    digitalWrite(KIRMIZI, LOW);
39    digitalWrite(SARI, LOW);
40    digitalWrite(YESIL, HIGH);                // araçlar yeniden geçer / cars go again
41    delay(8000);                              // en kısa yeşil süresi / minimum green time
42  }
43}

Este sketch funciona, mas tem uma fraqueza séria: depois que o botão é pressionado, o Arduino passa cerca de 25 segundos preso em linhas de delay(). Um segundo toque nesse tempo passa despercebido, nada pode ser lido da porta serial e nenhum display pode ser atualizado. Os toques durante a espera de 8 segundos no final se perdem completamente: o pedestre aperta e solta, nada acontece e ele precisa apertar de novo. Adicionar som ou uma contagem regressiva deixa essa estrutura ainda mais difícil de usar. A solução é parar de esperar e começar a medir o tempo.

Passo 3: código sem bloqueio com millis() e uma máquina de estados

Uma máquina de estados é um projeto em que o sistema está sempre em exatamente um estado e passa para o próximo quando certas condições são atendidas. Um semáforo é um exemplo perfeito: “verde para os carros”, “amarelo para os carros” e “pedestres atravessando” são, cada um, um estado. O código não espera mais com delay(); a cada passada o loop() faz só duas perguntas: em que estado estou e há quanto tempo estou nele? O tempo decorrido é medido com millis() - durumBaslangic (o momento em que o estado começou) e, quando o tempo acaba, o código avança. Se millis() é novidade para você, leia antes millis() vs delay() no Arduino. Escrever os estados em uma tabela antes de programar facilita muito o trabalho:

EstadoSinal dos veículosSinal do pedestreDuraçãoPróximo estado
ARAC_YESILVerdeVermelhoNo mínimo 8 s, até chegar um pedidoARAC_SARI
ARAC_SARIAmareloVermelho3 sYAYA_BEKLE
YAYA_BEKLEVermelhoVermelho1 sYAYA_GECER
YAYA_GECERVermelhoVerde6 sYAYA_FLAS
YAYA_FLASVermelhoVerde piscando3 sYAYA_BITTI
YAYA_BITTIVermelhoVermelho1 sKIRMIZI_SARI
KIRMIZI_SARIVermelho + amareloVermelho1 sARAC_YESIL
C++ (Arduino IDE)
1// Yaya butonlu trafik lambası: millis() + durum makinesi / pedestrian light: millis() + state machine
2const int KIRMIZI = 10, SARI = 9, YESIL = 8;  // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6;   // yaya lambası / pedestrian light
4const int BUTON = 2;                          // diğer ucu GND / other leg to GND
5
6enum Durum { ARAC_YESIL, ARAC_SARI, YAYA_BEKLE, YAYA_GECER, YAYA_FLAS, YAYA_BITTI, KIRMIZI_SARI };
7const char* const ISIM[] = { "ARAC_YESIL", "ARAC_SARI", "YAYA_BEKLE", "YAYA_GECER",
8                             "YAYA_FLAS", "YAYA_BITTI", "KIRMIZI_SARI" };
9
10// Süreler (ms) / durations (ms)
11const unsigned long MIN_YESIL = 8000;   // araçlara en az yeşil / minimum green for cars
12const unsigned long SARI_SURE = 3000;   // sarı / yellow
13const unsigned long GUVENLIK = 1000;    // tüm kırmızı / all red
14const unsigned long YAYA_SURE = 6000;   // yaya yeşili / pedestrian green
15const unsigned long FLAS_SURE = 3000;   // yanıp sönen yaya yeşili / flashing green man
16const unsigned long KS_SURE = 1000;     // kırmızı + sarı / red + yellow
17
18Durum durum = ARAC_YESIL;
19unsigned long durumBaslangic = 0;       // durumun başladığı an / when the state started
20bool yayaTalebi = false;                // butona basıldı mı? / was the button pressed?
21
22void aracLamba(bool k, bool s, bool y) {
23  digitalWrite(KIRMIZI, k);
24  digitalWrite(SARI, s);
25  digitalWrite(YESIL, y);
26}
27
28void yayaLamba(bool k, bool y) {
29  digitalWrite(YAYA_KIRMIZI, k);
30  digitalWrite(YAYA_YESIL, y);
31}
32
33// Yeni duruma geç: lambaları ayarla, saati başlat / enter a state: set the lamps, restart the clock
34void gec(Durum yeni) {
35  durum = yeni;
36  durumBaslangic = millis();
37  switch (yeni) {
38    case ARAC_YESIL:   aracLamba(0, 0, 1); yayaLamba(1, 0); break;
39    case ARAC_SARI:    aracLamba(0, 1, 0); yayaLamba(1, 0); break;
40    case YAYA_BEKLE:   aracLamba(1, 0, 0); yayaLamba(1, 0); break;
41    case YAYA_GECER:   aracLamba(1, 0, 0); yayaLamba(0, 1); yayaTalebi = false; break;
42    case YAYA_FLAS:    aracLamba(1, 0, 0); yayaLamba(0, 1); break;
43    case YAYA_BITTI:   aracLamba(1, 0, 0); yayaLamba(1, 0); break;
44    case KIRMIZI_SARI: aracLamba(1, 1, 0); yayaLamba(1, 0); break;
45  }
46  Serial.println(ISIM[yeni]);           // durum günlüğü / state log
47}
48
49void setup() {
50  Serial.begin(9600);
51  pinMode(KIRMIZI, OUTPUT);
52  pinMode(SARI, OUTPUT);
53  pinMode(YESIL, OUTPUT);
54  pinMode(YAYA_KIRMIZI, OUTPUT);
55  pinMode(YAYA_YESIL, OUTPUT);
56  pinMode(BUTON, INPUT_PULLUP);
57  gec(ARAC_YESIL);
58}
59
60void loop() {
61  unsigned long gecen = millis() - durumBaslangic;  // bu durumda geçen süre / time spent in this state
62
63  // yaya yeşili yanarken basış yok sayılır / presses during the green man are ignored
64  if (digitalRead(BUTON) == LOW && durum != YAYA_GECER) {
65    yayaTalebi = true;                  // talebi hatırla / remember the request
66  }
67
68  switch (durum) {
69    case ARAC_YESIL:
70      if (yayaTalebi && gecen >= MIN_YESIL) gec(ARAC_SARI);
71      break;
72    case ARAC_SARI:
73      if (gecen >= SARI_SURE) gec(YAYA_BEKLE);
74      break;
75    case YAYA_BEKLE:
76      if (gecen >= GUVENLIK) gec(YAYA_GECER);
77      break;
78    case YAYA_GECER:
79      if (gecen >= YAYA_SURE) gec(YAYA_FLAS);
80      break;
81    case YAYA_FLAS:
82      digitalWrite(YAYA_YESIL, (gecen / 250) % 2 == 0);  // 250 ms'de bir yanıp söner / toggles every 250 ms
83      if (gecen >= FLAS_SURE) gec(YAYA_BITTI);
84      break;
85    case YAYA_BITTI:
86      if (gecen >= GUVENLIK) gec(KIRMIZI_SARI);
87      break;
88    case KIRMIZI_SARI:
89      if (gecen >= KS_SURE) gec(ARAC_YESIL);
90      break;
91  }
92}

O coração do sketch é a função gec() (“ir para”): ela guarda o novo estado, reinicia o temporizador, ajusta as lâmpadas daquele estado e mostra o nome do estado no Monitor Serial. O switch dentro do loop() só verifica se o tempo acabou. Apertar o botão não dispara nada na hora; ele levanta a flag yayaTalebi (pedido de pedestre), e o ciclo começa assim que os carros tiverem tido pelo menos 8 segundos de verde. Agora nenhum toque se perde e, como o botão só muda uma flag, o repique dos contatos também não causa problema. Toques enquanto o bonequinho verde já está aceso são ignorados; caso contrário, um ciclo novo e desnecessário começaria logo depois de os pedestres atravessarem. Abra o Monitor Serial em 9600 baud para ver o nome de cada transição; essa saída ajuda tanto na depuração quanto no seu relatório escolar.

Quando quiser adicionar um recurso, primeiro acrescente uma linha à tabela e depois escreva um case no código. Por exemplo, você pode criar um estado noturno em que o amarelo pisca e passar para ele quando um sensor de luz LDR detectar escuridão. Para aprender máquinas de estados mais a fundo, veja Máquina de estados no Arduino.

Passo 4: um cruzamento de duas vias

Em um cruzamento real, os semáforos das duas vias são ligados entre si: enquanto a via A está no verde, a via B precisa estar no vermelho. Esta versão usa seis LEDs: D10, D9 e D8 para a direção A e D5, D4 e D3 para a direção B. Um detalhe importante é o curto intervalo de liberação com tudo vermelho: quando uma direção fica vermelha, a outra não fica verde na hora; o controlador espera 1 segundo para que os veículos que ainda estão no cruzamento possam sair. Desta vez os estados vão para uma tabela (um array) em vez de um switch; para cada passo, as lâmpadas acesas são guardadas como bits em um único byte, e a duração em milissegundos.

C++ (Arduino IDE)
1// İki yönlü kavşak: tablo tabanlı durum makinesi / two-way junction: table-driven state machine
2const byte A_KIRMIZI = 10, A_SARI = 9, A_YESIL = 8;  // A yönü (ana yol) / direction A (main road)
3const byte B_KIRMIZI = 5,  B_SARI = 4, B_YESIL = 3;  // B yönü (ara yol) / direction B (side road)
4const byte PINLER[6] = { A_KIRMIZI, A_SARI, A_YESIL, B_KIRMIZI, B_SARI, B_YESIL };
5
6struct Adim {
7  byte maske;            // bit sırası: A_K A_S A_Y B_K B_S B_Y / bit order: A_R A_Y A_G B_R B_Y B_G
8  unsigned long sure;    // ms
9};
10
11const Adim PLAN[] = {
12  { 0b001100, 8000 },   // A yeşil, B kırmızı / A green, B red
13  { 0b010100, 3000 },   // A sarı, B kırmızı / A yellow, B red
14  { 0b100100, 1000 },   // tüm yönler kırmızı / all red
15  { 0b100110, 1000 },   // A kırmızı, B kırmızı + sarı / A red, B red + yellow
16  { 0b100001, 5000 },   // A kırmızı, B yeşil / A red, B green
17  { 0b100010, 3000 },   // A kırmızı, B sarı / A red, B yellow
18  { 0b100100, 1000 },   // tüm yönler kırmızı / all red
19  { 0b110100, 1000 },   // A kırmızı + sarı, B kırmızı / A red + yellow, B red
20};
21const byte ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
22
23byte adim = 0;
24unsigned long adimBaslangic = 0;
25
26void uygula(byte maske) {
27  if ((maske & 0b001000) && (maske & 0b000001)) {  // iki yeşil aynı anda yanamaz / never two greens
28    maske = 0b100100;                              // güvenli durum: tüm kırmızı / fail-safe: all red
29  }
30  for (byte i = 0; i < 6; i++) {
31    digitalWrite(PINLER[i], (maske >> (5 - i)) & 1);  // soldaki bit A_KIRMIZI / leftmost bit is A_RED
32  }
33}
34
35void setup() {
36  for (byte i = 0; i < 6; i++) pinMode(PINLER[i], OUTPUT);
37  uygula(PLAN[0].maske);
38  adimBaslangic = millis();
39}
40
41void loop() {
42  if (millis() - adimBaslangic >= PLAN[adim].sure) {
43    adimBaslangic += PLAN[adim].sure;   // kaymasız zamanlama / drift-free timing
44    adim = (adim + 1) % ADIM_SAYISI;
45    uygula(PLAN[adim].maske);
46  }
47  // Burada buton, sensör ya da ekran işleri çalışabilir / buttons, sensors or a display can run here
48}

Cada linha do array PLAN é um passo. Em números binários como 0b001100, os bits da esquerda para a direita representam A vermelho, A amarelo, A verde, B vermelho, B amarelo e B verde; 1 significa aceso e 0 apagado. A função uygula() (“aplicar”) escreve esses seis bits nos pinos, em ordem. A verificação no início da função impede que um erro de digitação mostre verde nas duas direções ao mesmo tempo e, nesse caso, coloca o cruzamento em tudo vermelho. Os controladores de cruzamento reais também têm um circuito de segurança separado que detecta verdes conflitantes. Para mudar tempos ou adicionar passos, você só edita a tabela e nunca mexe no resto do código. Se faltarem pinos conforme o número de LEDs cresce, um registrador de deslocamento 74HC595 dá oito saídas com três pinos.

Teste no Tinkercad: simulação sem nenhuma peça

  1. Crie uma conta grátis em tinkercad.com e comece um novo circuito na seção Circuitos (Circuits).
  2. Arraste um Arduino Uno R3, uma protoboard, três LEDs e três resistores da lista de componentes à direita. Clique em um LED para escolher a cor e em um resistor para definir o valor como 220 Ω.
  3. Desenhe os fios: ânodos dos LEDs através dos resistores ao D10, D9 e D8, cátodos ao GND. Definir as cores dos fios (preto para o GND, por exemplo) deixa o esquema fácil de ler.
  4. Clique em Código (Code), escolha Texto (Text) no menu suspenso, apague o código de exemplo e cole o sketch deste guia.
  5. Clique em Iniciar simulação (Start Simulation); os LEDs começam a mudar em sequência. A saída serial aparece no Monitor Serial, na parte de baixo do painel de código.

O Tinkercad é a forma mais fácil de testar a ligação e o código antes de montar com peças reais; se você esquecer um resistor, o simulador mostra um aviso no LED de que a corrente está alta demais. Para testar a versão com pedestre, basta clicar no botão com o mouse enquanto a simulação roda. Em um computador lento, a simulação pode rodar mais devagar que o tempo real, então os tempos podem parecer um pouco longos. Para um trabalho escolar, você pode entregar o link de compartilhamento do Tinkercad ou uma captura de tela do circuito. Para instruções detalhadas, veja Simulação de Arduino no Tinkercad, e para uma alternativa que também suporta ESP32 e Pico, veja Wokwi.

Semáforo com ESP32 e modo noturno

Os sketches de Arduino deste guia também rodam no ESP32: selecione ESP32 Dev Module como placa na Arduino IDE e mude as constantes dos pinos como mostra a tabela. A versão abaixo acrescenta um recurso: o modo noturno. Em horários de pouco movimento, muitos cruzamentos passam para amarelo piscante. As placas ESP32 DevKit têm um botão BOOT ligado ao GPIO 0, então você nem precisa de um botão extra: depois que a placa inicia, ele pode ser lido como uma entrada normal. Cada toque alterna entre o modo normal e o modo noturno.

C++ (Arduino IDE)
1// ESP32 trafik lambası + gece modu (BOOT düğmesi) / ESP32 traffic light + night mode (BOOT button)
2const int KIRMIZI = 5;      // GPIO 5
3const int SARI = 18;        // GPIO 18
4const int YESIL = 19;       // GPIO 19
5const int BOOT_BUTON = 0;   // kart üzerindeki BOOT düğmesi / on-board BOOT button
6
7struct Adim { bool k, s, y; unsigned long sure; };
8const Adim PLAN[] = {
9  { true,  false, false, 5000 },   // kırmızı / red
10  { true,  true,  false, 1000 },   // kırmızı + sarı / red + yellow
11  { false, false, true,  5000 },   // yeşil / green
12  { false, true,  false, 3000 },   // sarı / yellow
13};
14const int ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
15
16int adim = 0;
17unsigned long adimBaslangic = 0;
18bool geceModu = false;
19int sonOkuma = HIGH, kararli = HIGH;     // debounce için / for debouncing
20unsigned long degisimZamani = 0;
21
22void lambalar(bool k, bool s, bool y) {
23  digitalWrite(KIRMIZI, k);
24  digitalWrite(SARI, s);
25  digitalWrite(YESIL, y);
26}
27
28void setup() {
29  Serial.begin(115200);
30  pinMode(KIRMIZI, OUTPUT);
31  pinMode(SARI, OUTPUT);
32  pinMode(YESIL, OUTPUT);
33  pinMode(BOOT_BUTON, INPUT_PULLUP);
34  lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
35  adimBaslangic = millis();
36}
37
38void loop() {
39  unsigned long simdi = millis();
40
41  // 50 ms kararlı kalan basışı kabul et / accept a press that stays stable for 50 ms
42  int okuma = digitalRead(BOOT_BUTON);
43  if (okuma != sonOkuma) {
44    sonOkuma = okuma;
45    degisimZamani = simdi;
46  }
47  if (simdi - degisimZamani >= 50 && okuma != kararli) {
48    kararli = okuma;
49    if (kararli == LOW) {                  // yeni basış / new press
50      geceModu = !geceModu;
51      Serial.println(geceModu ? "Gece modu / Night mode" : "Normal mod / Normal mode");
52      adim = 0;                            // normal mod kırmızıdan başlar / normal mode restarts on red
53      adimBaslangic = simdi;
54      lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
55    }
56  }
57
58  if (geceModu) {
59    lambalar(false, (simdi / 500) % 2 == 0, false);  // yanıp sönen sarı / flashing yellow
60    return;
61  }
62
63  if (simdi - adimBaslangic >= PLAN[adim].sure) {
64    adimBaslangic = simdi;
65    adim = (adim + 1) % ADIM_SAYISI;
66    lambalar(PLAN[adim].k, PLAN[adim].s, PLAN[adim].y);
67  }
68}

A leitura do botão inclui um debounce simples: um toque só conta se a leitura ficar igual por 50 ms. A tabela PLAN, que guarda os estados das lâmpadas, é uma forma mais legível da ideia usada no sketch do cruzamento. Como os pinos do ESP32 fornecem 3,3 V, os LEDs puxam menos corrente que no Arduino e parecem um pouco mais fracos. LEDs vermelhos e amarelos ficam com brilho suficiente com 220 Ω, mas um LED verde intenso com tensão direta perto de 3 V pode ficar bem fraco; nesse caso, baixe o resistor do verde para 100 Ω. Se o ESP32 é novidade para você, O que é o ESP32? é um bom ponto de partida para a instalação e os pinos seguros.

Semáforo de pedestre com Raspberry Pi Pico (MicroPython)

No Raspberry Pi Pico, escrevemos a mesma máquina de estados em MicroPython. Desta vez os estados ficam em uma lista: cada linha guarda o nome do estado, os valores das cinco lâmpadas e a duração. O botão usa uma interrupção: quando o GP4 cai para LOW, a função buton_kesmesi() (“interrupção do botão”) é executada e só levanta a flag de pedido. Se você salvar o código no Pico como main.py com o Thonny, ele começa sozinho sempre que a placa é ligada.

MicroPython
1# Raspberry Pi Pico (MicroPython): yaya butonlu trafik lambası / pedestrian traffic light
2from machine import Pin
3import time
4
5yesil = Pin(11, Pin.OUT)              # araç yeşil / vehicle green
6sari = Pin(12, Pin.OUT)               # araç sarı / vehicle yellow
7kirmizi = Pin(13, Pin.OUT)            # araç kırmızı / vehicle red
8yaya_yesil = Pin(14, Pin.OUT)         # yaya yeşil / pedestrian green
9yaya_kirmizi = Pin(15, Pin.OUT)       # yaya kırmızı / pedestrian red
10buton = Pin(4, Pin.IN, Pin.PULL_UP)   # GP4, diğer ucu GND / other leg to GND
11
12# (durum, kırmızı, sarı, yeşil, yaya kırmızı, yaya yeşil, süre ms)
13# (state, red, yellow, green, ped red, ped green, duration ms)
14DURUMLAR = [
15    ("ARAC_YESIL",   0, 0, 1, 1, 0, 8000),   # en az yeşil süresi / minimum green
16    ("ARAC_SARI",    0, 1, 0, 1, 0, 3000),
17    ("YAYA_BEKLE",   1, 0, 0, 1, 0, 1000),   # tüm kırmızı / all red
18    ("YAYA_GECER",   1, 0, 0, 0, 1, 6000),
19    ("YAYA_FLAS",    1, 0, 0, 0, 1, 3000),
20    ("YAYA_BITTI",   1, 0, 0, 1, 0, 1000),   # tüm kırmızı / all red
21    ("KIRMIZI_SARI", 1, 1, 0, 1, 0, 1000),
22]
23
24talep = False
25adim = 0                              # şu anki durumun sırası / index of the current state
26
27def buton_kesmesi(pin):               # kesmede yalnız bayrak kaldır / only set a flag in the IRQ
28    global talep
29    if DURUMLAR[adim][0] != "YAYA_GECER":   # yaya yeşilinde basış yok sayılır / ignored during the green man
30        talep = True
31
32buton.irq(trigger=Pin.IRQ_FALLING, handler=buton_kesmesi)
33
34def uygula(i):
35    global talep
36    ad, k, s, y, yk, yy, _ = DURUMLAR[i]
37    kirmizi.value(k)
38    sari.value(s)
39    yesil.value(y)
40    yaya_kirmizi.value(yk)
41    yaya_yesil.value(yy)
42    if ad == "YAYA_GECER":
43        talep = False                 # talep karşılandı / request served
44    print("Durum / state:", ad)
45
46uygula(adim)
47baslangic = time.ticks_ms()
48
49while True:
50    gecen = time.ticks_diff(time.ticks_ms(), baslangic)   # taşmaya karşı güvenli / wrap-safe
51    ad = DURUMLAR[adim][0]
52
53    if ad == "YAYA_FLAS":
54        yaya_yesil.value((gecen // 250) % 2 == 0)          # yanıp sönen yaya yeşili / flashing green man
55
56    if gecen >= DURUMLAR[adim][6] and (ad != "ARAC_YESIL" or talep):
57        adim = (adim + 1) % len(DURUMLAR)                   # yeşil, talep gelene kadar sürer / green lasts until a request
58        uygula(adim)
59        baslangic = time.ticks_ms()
60
61    time.sleep_ms(10)

No MicroPython você usa time.ticks_ms() em vez de millis() e time.ticks_diff() em vez de uma subtração simples; o contador de ticks dá a volta depois de um certo valor, então subtrair duas leituras diretamente pode dar resultados errados. Mantenha a rotina de interrupção curta: nada de print, nada de sleep e nada de cálculos longos, só mude uma flag. Os pinos do Pico também trabalham em 3,3 V, então a observação sobre o LED verde da seção do ESP32 vale aqui também. Você encontra como instalar o MicroPython e a numeração dos pinos em O que é o Raspberry Pi Pico? e o básico da linguagem em Fundamentos de MicroPython.

Erros comuns e como corrigi-los

SintomaCausa provávelSolução
Nenhum LED acendeGND desligado, ou o barramento de alimentação da protoboard é dividido no meioLigue o GND do Arduino ao barramento azul; em uma protoboard longa, una as duas metades com um jumper
Um LED fica apagadoLED invertido ou ligado no pino erradoPerna longa (ânodo) através do resistor ao pino, perna curta ao GND; compare o número do pino com o código
Acende a cor erradaOs pinos do vermelho e do verde estão trocados no códigoCorrija os fios ou as constantes conforme a tabela de pinos
LED muito forte e esquentandoSem resistorColoque um resistor de 220 Ω em série em cada LED
O botão não faz nada ou parece sempre pressionadoBotão ligado em duas pernas que já são unidas por dentro, ou INPUT_PULLUP esquecidoUse pernas diagonais e escreva pinMode(BUTON, INPUT_PULLUP)
Sem reação imediata ao botãoO tempo mínimo de verde dos carros ainda não acabouNão é bug; diminua MIN_YESIL
O programa enlouquece depois de um tempoUma variável de tempo foi declarada como intDeclare como unsigned long toda variável usada com millis()

Dicas para o seu trabalho escolar e relatório do projeto

O semáforo é um dos trabalhos com Arduino mais comuns nas aulas de ciências, tecnologia e robótica. O seu professor normalmente quer ver não só um circuito funcionando, mas que você o entende e consegue explicá-lo. Se você organizar o relatório com as seções abaixo, ele vai parecer bem estruturado e você não vai esquecer nada importante:

  • Objetivo: descreva em uma ou duas frases o que o projeto demonstra, por exemplo “temporizar um semáforo com saídas digitais”.
  • Lista de peças: informe o nome, a quantidade e o valor (220 Ω) de cada peça, como na tabela acima.
  • Esquema do circuito: adicione uma captura de tela do Tinkercad ou do Fritzing; se desenhar à mão, use os símbolos corretos. Como ler um esquema de circuito ajuda nisso.
  • Cálculo do resistor: mostre R = (5 − 2) / 0,015 ≈ 200 Ω e explique por que você escolheu 220 Ω.
  • Tabela de estados ou fluxograma: inclua a tabela de estados, durações e transições; ela é a prova mais forte de que você entende o código.
  • Código e explicação: inclua o código completo e explique as linhas importantes com as suas palavras.
  • Testes e observações: cronometre as fases, meça a corrente de um LED com um multímetro e compare com o seu cálculo.
  • Conclusão e melhorias: descreva os problemas que encontrou, como os resolveu e os recursos que poderia adicionar (botão de pedestre, som, modo noturno).
Se quiser se destacar, compare a sua primeira versão com delay() com a versão com millis(): explique por que os toques no botão se perdem na primeira e nunca se perdem na segunda. Um vídeo de 20–30 segundos do circuito funcionando e uma captura de tela da saída do Monitor Serial deixam o relatório ainda mais forte.

Ideias para levar o seu semáforo mais longe

  • Display de contagem regressiva: mostre os segundos que faltam para o verde acabar em um display de 7 segmentos.
  • Sinal sonoro para pedestres com deficiência visual: enquanto o sinal de pedestre estiver verde, apite rápido com um buzzer, e devagar enquanto estiver vermelho.
  • Cruzamento inteligente: detecte um carro esperando na via secundária com um sensor de distância HC-SR04 e dê verde à direção B só quando houver um carro.
  • Modo noturno automático: use um LDR para passar ao amarelo piscante quando escurecer.
  • Controle remoto: crie uma página web no ESP32 e mude o modo e os tempos pelo celular.
  • Passagem de nível: use a mesma lógica para os sinais de passagem de nível ferroviária, com dois LEDs vermelhos piscando alternadamente; os projetos Knight Rider e LED sequencial usam ideias de temporização parecidas.

Um semáforo parece um projeto pequeno, mas as ideias que ele traz, máquinas de estados, temporização sem bloqueio e transições seguras, também são a base dos robôs, sistemas de alarme e aparelhos de casa inteligente que você vai montar depois. Para escolher o que montar em seguida, navegue pela lista de ideias de projetos com Arduino. No app BlueGrays você encontra projetos de semáforo para Arduino, ESP32 e Raspberry Pi Pico, além de continuações como o semáforo de quatro modos com Pico, cada um com esquema do circuito passo a passo e código pronto para testar na sua placa.

Perguntas frequentes

Como fazer um semáforo com Arduino?

Ligue as pernas longas de um LED vermelho, um amarelo e um verde a pinos digitais do Arduino (por exemplo D10, D9 e D8) através de resistores de 220 Ω e as pernas curtas ao GND. No código, defina esses pinos como OUTPUT e acenda e apague os LEDs em ordem dentro do loop(). delay() basta para uma primeira versão; para botões e recursos extras, você precisa de millis().

Que resistor eu preciso para o LED do semáforo?

Em um Arduino Uno de 5 V, 220 Ω por LED é uma escolha comum e segura; um LED vermelho consome então cerca de 13–14 mA. 330 Ω dá uma luz mais fraca, mas ainda bem visível. 220 Ω também funciona no ESP32 e no Pico de 3,3 V; só se um LED verde intenso ficar fraco é que você deve baixar para 100 Ω.

Por que o vermelho e o amarelo acendem juntos em um semáforo?

Em países como o Reino Unido, a Alemanha e a Turquia, vermelho e amarelo juntos avisam que o verde está para acender: os motoristas se preparam, mas não saem até o verde aparecer. Os EUA, a França, a Itália, a Espanha e o Brasil não usam essa fase e passam direto do vermelho para o verde. No seu projeto com Arduino, você pode incluir ou deixar de fora essa fase como preferir.

Devo usar delay ou millis em um semáforo com Arduino?

Se você só alterna os LEDs em sequência, delay() é a opção mais simples e funciona bem. Assim que você adiciona um botão, sensor, display ou som, delay() pausa o programa e essas entradas se perdem. Um código escrito com millis() e uma máquina de estados nunca espera e consegue acompanhar tudo ao mesmo tempo.

Posso montar o projeto do semáforo no Tinkercad?

Sim. O Tinkercad Circuits tem o Arduino Uno, LEDs, resistores e botões como componentes prontos; desenhe o circuito, cole o código no editor de Texto e inicie a simulação. É ideal para testar o circuito e o código sem peças reais e para obter uma captura de tela ou um link de compartilhamento para o seu trabalho.

Como funciona um semáforo de pedestre com botão?

Os carros normalmente ficam com o verde, e apertar o botão registra um pedido. Quando o tempo mínimo de verde dos carros acaba, o sinal fica amarelo, depois vermelho e, após um curto intervalo de segurança, o sinal de pedestre fica verde. No código deste guia, o bonequinho verde pisca antes de terminar, e então o ciclo volta para os carros.

Ver todos os projetos
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
Semáforo com ESP32
#107Iniciante

Semáforo com ESP32

Simulação de um semáforo real com 3 LEDs: vermelho, amarelo e verde. Os tempos se aproximam da lógica real: vermelho longo, amarelo curto e verde longo. Um exemplo clássico de controle sequencial de GPIOs.

4.4(21)
44
Raspberry Pi Pico: semáforo (3 LEDs em sequência)
#150Iniciante

Raspberry Pi Pico: semáforo (3 LEDs em sequência)

Simulamos um semáforo real com LEDs vermelho, amarelo e verde — o primeiro projeto com 3 LEDs da série Pico! Verde por 4 segundos: "siga"; amarelo por 1,5 segundo: "prepare-se"; vermelho por 3 segundos: "pare"; e vermelho+amarelo por 1 segundo: "saída" (padrão de semáforo europeu). Aprenda definição de funções (def all_off()), lógica de máquina de estados (sequência) e sincronização de vários GPIOs.

4.0(1)
12
Dois LEDs piscando alternadamente
#2Iniciante

Dois LEDs piscando alternadamente

Dois LEDs piscam rapidamente, um de cada vez: quando um acende, o outro apaga, como se estivessem jogando uma bola de um para o outro. O Arduino mantém cada luz acesa por um quinto de segundo e passa

4.6(105)
343
Controle de LED com botão (momentâneo)
#9Iniciante

Controle de LED com botão (momentâneo)

É como uma campainha: a luz fica acesa enquanto você pressiona o botão e apaga quando você solta. O Arduino monitora o botão continuamente; enquanto ele está pressionado, o LED fica aceso e apaga assi

4.8(113)
229
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
Raspberry Pi Pico: semáforo com 4 modos selecionados por botão (máquina de estados)
#158Intermediário

Raspberry Pi Pico: semáforo com 4 modos selecionados por botão (máquina de estados)

4 modos de semáforo com apenas 1 botão! O modo muda a cada acionamento: Ciclo normal (vermelho → amarelo → verde em sequência), Só vermelho (sinal de parada), Amarelo piscante (atenção/modo noturno), Desligado (manutenção). Conceitos: máquina de estados (a variável mode determina qual comportamento executar), alternância cíclica de modos com módulo ((mode + 1) % 4 → ciclo infinito 0→1→2→3→0), temporização sem bloqueio (as fases dos LEDs são temporizadas…

5.0(1)
15