An Arduino traffic light, with a crossing button that works
Intermedio
- 503componentes en el banco de trabajo
- 44placas funcionando ahora
- 1.00xtiempo real, en todas las placas
El semáforo es el proyecto que casi todos los cursos de Arduino usan en su segunda semana, porque son tres LEDs y unos cuantos delays y aun así parece algo de verdad. La versión de arriba va un paso más allá que la mayoría: es un cruce peatonal con semáforo, con una lámpara para peatones, un botón para pedir el paso y la regla de que el verde nunca termina antes de tiempo. Está corriendo ahora mismo en un Arduino Uno simulado, con el mismo sketch que puedes leer más abajo en esta página.
Presiona el botón de la protoboard y mira qué pasa. La lámpara azul WAIT (espera) se enciende en el instante en que se cierra el contacto, el verde se mantiene hasta cumplir su tiempo mínimo, luego ámbar, luego rojo con la lámpara blanca de cruce, luego la fase de parpadeo, y de vuelta a verde. Ábrelo en el editor para cambiar cualquiera de los tiempos y ejecútalo otra vez.
What you need
- Un Arduino Uno
- Cinco LEDs: rojo, amarillo, verde, blanco y azul
- Cinco resistencias de 220 ohm, una por LED
- Un botón
- Un inversor séxtuple 74HC04, para la lámpara WAIT
- Una protoboard y cables jumper
Wiring
| Componente y pin | Va a | Nota |
|---|---|---|
| LED rojo, con 220 ohm | Pin 8 | Rojo del semáforo |
| LED amarillo, con 220 ohm | Pin 9 | Ámbar del semáforo |
| LED verde, con 220 ohm | Pin 10 | Verde del semáforo |
| LED blanco, con 220 ohm | Pin 11 | La lámpara de cruce |
| Botón, un lado | Pin 2 | El otro lado a GND; el sketch activa la pull-up interna |
| Entrada del 74HC04 | La línea del botón | Su salida maneja la lámpara azul WAIT, sin nada de código |
| Pin 14 y pin 7 del 74HC04 | 5 V y GND | El chip no hace nada hasta que ambos estén conectados |
Cómo se construye la secuencia
Un cruce tiene un estado de reposo, verde para los autos, y solo sale de ese estado cuando alguien lo pide. El sketch lo escribe como un ciclo con cuatro fases: verde hasta que llega una solicitud (pero nunca menos de tres segundos ni más de nueve), ámbar durante un segundo y medio, rojo con la lámpara de cruce durante cuatro segundos, y luego doce parpadeos de medio segundo del ámbar y la lámpara de cruce juntos, que es lo que hace un cruce peatonal real al final de su fase de cruce.
Cada fase es una llamada a una pequeña función auxiliar lights() que fija el rojo, el ámbar y el verde de una vez. Escribir las tres lámparas juntas, cada vez, es lo que evita el error más común del semáforo, en el que una fase olvida apagar la lámpara de la fase anterior y terminan encendidos dos colores a la vez.
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 qué el botón está en una interrupción
El primer sketch de cruce que escribe casi todo el mundo revisa el botón con digitalRead() en algún lugar de loop(). Funciona cuando lo pruebas manteniendo presionado el botón, y falla en cuanto alguien le da un toque, porque durante la mayor parte del ciclo la placa está dentro de un delay() y no está viendo el pin para nada. Un toque durante la fase roja simplemente nunca se detecta.
Este sketch pone el botón en el pin 2, que es el pin INT0 del Uno, y le asocia una interrupción en su flanco de bajada. El manejador hace una sola cosa: activa una bandera llamada requested. Haga lo que haga el ciclo principal, incluso si está esperando en un delay, la pulsación se detecta. Luego el ciclo lee la bandera con las interrupciones detenidas un instante, para nunca leer un valor escrito a medias, y la borra al final del ciclo.
La bandera se declara volatile, lo que le dice al compilador que algo fuera del flujo normal del programa puede cambiarla. Si omites esa palabra, un compilador que optimiza puede leer la variable una vez y no volver a revisarla nunca.
volatile bool requested = false;
static void onPress() { requested = true; }
void setup() {
pinMode(BUTTON, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON), onPress, FALLING);
}La lámpara que no necesita código
La lámpara azul WAIT no la maneja el Uno en absoluto. Cuelga de una compuerta de un inversor 74HC04 conectado directo a la línea del botón: la línea está en alto gracias a la pull-up, el botón la baja, el inversor convierte ese bajo en alto y la lámpara se enciende. Reacciona en nanosegundos, antes de que el sketch alcance a notar nada.
Está en el circuito para recordar que no todo trabajo le toca al microcontrolador. También muestra algo del simulador: aquí el 74HC04 es un chip real de 14 pines con su alimentación en los pines 14 y 7, y si borras cualquiera de esos cables de alimentación en el editor, la lámpara deja de funcionar, exactamente como pasaría en tu mesa.
Pruébalo en el editor
Abre el circuito en el editor y cambia MIN_GREEN de 3000 a 500. Presiona el botón en cuanto se encienda el verde y ahora el cruce detiene a los autos casi de inmediato, que es justo por lo que los cruces reales tienen un mínimo.
Luego rómpelo a propósito. Reemplaza la interrupción por un simple digitalRead() del pin 2 dentro de waitForRequest() y dale un toque al botón durante la fase roja. La pulsación se pierde. Vuelve a poner la interrupción y se detecta siempre.
Por último, arrastra un analizador lógico desde la caja de componentes y conecta sus cuatro canales a los pines 8, 9, 10 y 11. Toda la secuencia aparece como un diagrama de tiempos, con la fase de parpadeo como una fila ordenada de pulsos, que es una forma mucho más rápida de revisar los tiempos que mirar las lámparas.
Common mistakes
Un LED sin resistencia. Aquí cada lámpara tiene su propia resistencia de 220 ohm, que limita la corriente a unos 14 miliamperios desde un pin de 5 volts. Un LED conectado directo a un pin intenta tomar mucho más de lo que el pin debería dar.
Leer el botón solo entre delays, como se describe arriba. Si a veces se ignora una pulsación, casi siempre es por esto.
Esperar que el botón se lea HIGH al presionarlo. Con INPUT_PULLUP el pin está en alto en reposo y una pulsación lo lleva a tierra, así que presionado es LOW. Por la misma razón, la interrupción está en el flanco de bajada.
Dos colores encendidos a la vez, porque una fase encendió una lámpara y la siguiente nunca la apagó. Fijar las tres juntas en una sola función elimina todo ese tipo de error.
Preguntas
¿Puedo armar un semáforo sencillo sin el botón?
Sí. Borra el botón y la llamada a waitForRequest() y dale al verde su propio delay() fijo. El resultado es la secuencia clásica de tres LEDs. La versión con botón vale las pocas líneas de más porque así se comportan los cruces reales.
¿Por qué el pin 2 para el botón?
En un Uno, solo los pines 2 y 3 tienen sus propias interrupciones externas, INT0 e INT1. Cualquier pin se puede leer con digitalRead(), pero solo esos dos pueden llamar a una función en el momento en que cambian.
¿Esto funciona sin cambios en un Arduino Uno real?
El sketch solo usa llamadas estándar de Arduino: pinMode, digitalWrite, delay, millis y attachInterrupt. Es el mismo código que cargarías desde el Arduino IDE.
¿Cómo lo convierto en un cruce de cuatro vías?
Agrega un segundo juego de tres lámparas en otros tres pines y dale a cada fase dos llamadas, una para cada calle. Un cruce de calles también tiene una fase de rojo y ámbar antes del verde, que un cruce peatonal no tiene.
Sigue así
Ármalo en la vida real
Abre el editor, cambia un valor y mira cómo el número cambia con él. Nada que instalar y sin necesidad de cuenta.