Aprender

An Arduino traffic light, with a crossing button that works

Intermediário

Ou veja todas as aulas.

  • 503componentes na bancada
  • 44placas rodando agora
  • 1.00xtempo real, em todas as placas
Arduino Uno: um semáforo de pedestreslive0.000 s 0.00x
Aperte o botão para pedir para atravessar
O exemplo traffic, rodando num ATmega328P simulado. Aperte o botão na protoboard para pedir para atravessar.

O semáforo é o projeto que quase todo curso de Arduino usa na segunda semana, porque são três LEDs e alguns delays e mesmo assim parece alguma coisa de verdade. A versão acima vai um passo além da maioria: é um semáforo de pedestres com botão, com uma lâmpada para o pedestre, um botão de pedido e a regra de que o verde nunca termina antes da hora. Ele está rodando agora num Arduino Uno simulado, com o mesmo sketch que você pode ler mais abaixo nesta página.

Aperte o botão na protoboard e veja o que acontece. A lâmpada azul WAIT acende no instante em que o contato fecha, o verde se mantém até cumprir o tempo mínimo, depois vem o amarelo, depois o vermelho com a lâmpada branca de travessia, depois a fase piscante, e volta para o verde. Abra no editor para mudar qualquer um dos tempos e rode de novo.

What you need

  • Um Arduino Uno
  • Cinco LEDs: vermelho, amarelo, verde, branco e azul
  • Cinco resistores de 220 ohms, um por LED
  • Um botão
  • Um inversor sêxtuplo 74HC04, para a lâmpada WAIT
  • Uma protoboard e jumpers

Wiring

Componente e pinoVai paraNota
LED vermelho, por um resistor de 220 ohmsPino 8Vermelho dos carros
LED amarelo, por um resistor de 220 ohmsPino 9Amarelo dos carros
LED verde, por um resistor de 220 ohmsPino 10Verde dos carros
LED branco, por um resistor de 220 ohmsPino 11A lâmpada de travessia
Botão, um ladoPino 2O outro lado no GND; o sketch ativa o pull-up interno
Entrada do 74HC04A linha do botãoA saída dele aciona a lâmpada azul WAIT, sem nenhum código
Pino 14 e pino 7 do 74HC045 V e GNDO chip não faz nada até os dois estarem ligados

Como a sequência é montada

Uma travessia tem um estado de repouso, verde para os carros, e só sai dele quando alguém pede. O sketch escreve isso como um loop com quatro fases: verde até um pedido (mas nunca menos de três segundos e nunca mais de nove), amarelo por um segundo e meio, vermelho com a lâmpada de travessia por quatro segundos, e depois doze piscadas de meio segundo do amarelo junto com a lâmpada de travessia, que é o que um semáforo de pedestres de verdade faz no fim da fase de travessia.

Cada fase é uma chamada a uma pequena função auxiliar lights() que define o vermelho, o amarelo e o verde de uma vez. Escrever as três lâmpadas juntas, toda vez, é o que evita o bug de semáforo mais comum, em que uma fase esquece de apagar a lâmpada da fase anterior e duas cores acabam acesas ao mesmo tempo.

As quatro fases, do exemplo traffic (resumido)
void loop() {
  lights(0, 0, 1);          // green
  waitForRequest();

  lights(0, 1, 0);          // amber
  delay(AMBER_MS);

  lights(1, 0, 0);          // red, and walk
  digitalWrite(WALK, HIGH);
  delay(WALK_MS);

  for (int i = 0; i < 12; i++) {   // the flashing phase
    int on = (i & 1) ? LOW : HIGH;
    digitalWrite(WALK, on);
    lights(0, on == HIGH, 0);
    delay(500);
  }
  digitalWrite(WALK, LOW);
}

Por que o botão está numa interrupção

O primeiro sketch de travessia que a maioria das pessoas escreve confere o botão com digitalRead() em algum lugar do loop(). Ele funciona quando você testa segurando o botão, e falha no momento em que alguém dá só um toque, porque na maior parte do ciclo a placa está dentro de um delay() e nem olhando para o pino. Um toque durante a fase vermelha simplesmente nunca é visto.

Este sketch coloca o botão no pino 2, que é o pino INT0 do Uno, e liga uma interrupção à borda de descida dele. A rotina faz uma única coisa: ela marca uma flag chamada requested. Não importa o que o loop principal esteja fazendo, inclusive esperando num delay, o aperto é capturado. Depois o loop lê a flag com as interrupções seguradas por um instante, para nunca ler um valor escrito pela metade, e limpa a flag no fim do ciclo.

A flag é declarada como volatile, o que avisa o compilador que algo fora do fluxo normal do programa pode mudá-la. Deixe essa palavra de fora e um compilador que otimiza o código pode ler a variável uma vez e nunca mais olhar.

O pedido, capturado não importa o que o loop() esteja fazendo
volatile bool requested = false;

static void onPress() { requested = true; }

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON), onPress, FALLING);
}

A lâmpada que não precisa de código

A lâmpada azul WAIT não é acionada pelo Uno. Ela fica pendurada numa porta de um inversor 74HC04 ligado direto na linha do botão: a linha fica em nível alto pelo pull-up, o botão puxa para baixo, o inversor transforma esse nível baixo em alto, e a lâmpada acende. Ela reage em nanossegundos, antes de o sketch ter qualquer chance de perceber alguma coisa.

Ela está no circuito para lembrar que nem todo trabalho é do microcontrolador. Ela também mostra algo sobre o simulador: o 74HC04 aqui é um chip de 14 pinos de verdade, com a alimentação nos pinos 14 e 7, e se você apagar qualquer um dos fios de alimentação no editor a lâmpada para de funcionar, exatamente como aconteceria na mesa.

Experimente no editor

Abra o circuito no editor e mude MIN_GREEN de 3000 para 500. Aperte o botão no instante em que o verde acender e agora a travessia para os carros quase na hora, que é justamente por isso que as travessias de verdade têm um tempo mínimo.

Depois quebre de propósito. Troque a interrupção por um simples digitalRead() do pino 2 dentro de waitForRequest() e dê um toque no botão durante a fase vermelha. O aperto se perde. Coloque a interrupção de volta e ele é capturado toda vez.

Por fim, arraste um analisador lógico da caixa de componentes e prenda os quatro canais dele nos pinos 8, 9, 10 e 11. A sequência inteira aparece como um diagrama de tempo, com a fase piscante como uma fileira certinha de pulsos, o que é um jeito muito mais rápido de conferir os tempos do que ficar olhando as lâmpadas.

Common mistakes

LED sem resistor. Cada lâmpada aqui tem o seu próprio resistor de 220 ohms, que limita a corrente a cerca de 14 miliampères num pino de 5 volts. Um LED ligado direto num pino tenta puxar muito mais do que o pino deveria fornecer.

Ler o botão só entre os delays, como descrito acima. Se um aperto às vezes é ignorado, quase sempre o motivo é esse.

Esperar que o botão leia HIGH quando apertado. Com INPUT_PULLUP o pino fica em nível alto em repouso e o aperto puxa para o terra, então apertado é LOW. A interrupção é na borda de descida pelo mesmo motivo.

Duas cores acesas ao mesmo tempo, porque uma fase acendeu uma lâmpada e a seguinte nunca apagou. Definir as três juntas numa função elimina esse tipo de bug inteiro.

Perguntas

Dá para montar um semáforo simples sem o botão?

Dá. Apague o botão e a chamada a waitForRequest() e dê ao verde um delay() fixo só dele. O resultado é a sequência clássica de três LEDs. A versão com botão vale as poucas linhas a mais porque é assim que as travessias de verdade funcionam.

Por que o pino 2 para o botão?

No Uno, só os pinos 2 e 3 têm interrupções externas próprias, INT0 e INT1. Qualquer pino pode ser lido com digitalRead(), mas só esses dois conseguem chamar uma função no instante em que mudam.

Isso roda num Arduino Uno de verdade sem mudanças?

O sketch só usa chamadas padrão do Arduino: pinMode, digitalWrite, delay, millis e attachInterrupt. É o mesmo código que você enviaria pela Arduino IDE.

Como transformo isso num cruzamento de quatro vias?

Acrescente um segundo conjunto de três lâmpadas em mais três pinos e dê a cada fase duas chamadas, uma para cada rua. Um cruzamento também tem uma fase de vermelho com amarelo antes do verde, que um semáforo de pedestres com botão não tem.

Monte isto de verdade

Abra o editor, mude um valor e veja o número mudar junto. Nada para instalar, e não precisa de conta.