An Arduino traffic light, with a crossing button that works
Intermediário
- 503componentes na bancada
- 44placas rodando agora
- 1.00xtempo real, em todas as placas
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 pino | Vai para | Nota |
|---|---|---|
| LED vermelho, por um resistor de 220 ohms | Pino 8 | Vermelho dos carros |
| LED amarelo, por um resistor de 220 ohms | Pino 9 | Amarelo dos carros |
| LED verde, por um resistor de 220 ohms | Pino 10 | Verde dos carros |
| LED branco, por um resistor de 220 ohms | Pino 11 | A lâmpada de travessia |
| Botão, um lado | Pino 2 | O outro lado no GND; o sketch ativa o pull-up interno |
| Entrada do 74HC04 | A linha do botão | A saída dele aciona a lâmpada azul WAIT, sem nenhum código |
| Pino 14 e pino 7 do 74HC04 | 5 V e GND | O 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.
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.
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.
Continuar
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.