Manual del proyecto · Proyecto 01
Sistema de Semaforización
Diseño y construcción de una maqueta/prototipo de un sistema de semáforos para la circulación ordenada y óptima del tráfico vehicular.
- Dificultad:
- Nivel educativo:
- Secundario. Ciclo Básico.
Contenido del manual
- 01 Materiales y equipamiento
- 02 Construir y programar un semáforo Nivel inicial
- 03 Dos semáforos sincronizados en una intersección Nivel intermedio
- 04 Monitoreo remoto con Internet de las Cosas Nivel avanzado
Materiales y equipamiento
Insumos
| Imagen | Insumo | Cantidad | Plataforma |
|---|---|---|---|
| | LED rojo 5 mm | 2 | Todas |
| | LED amarillo 5 mm | 2 | Todas |
| | LED verde 5 mm | 2 | Todas |
| | Resistencia de 220 ohm | 10 | Todas |
| | Cable dupont macho-hembra | 20 | Todas |
| | Cable dupont macho-macho | 20 | Todas |
| | Protoboard | 1 | Todas |
| Filamento PLA para impresora 3D Para imprimir la maqueta del semáforo (paquete de fabricación del proyecto). | según modelo | Todas | |
| | Arduino UNO R3 El original de 2019 sumaba un módulo IoT OBLOQ para el nivel avanzado; ese módulo está discontinuado. Con UNO, el nivel IoT se sigue de manera conceptual y la práctica se realiza con la variante ESP32. | 1 | Arduino UNO R3 |
| | Placa ESP32 DevKit v1 Reemplaza al conjunto Arduino UNO + módulo OBLOQ del original: el ESP32 trae WiFi integrado, necesario para el nivel avanzado (IoT). | 1 | ESP32 DevKit |
| | Cable USB tipo B | 1 | Arduino UNO R3 |
| | Cable micro USB (con línea de datos) | 1 | ESP32 DevKit |
| | Fuente de 9 V 1 A (plug centro positivo, 5,5 x 2,1 mm) (opcional) Para alimentar la maqueta sin la computadora. El ESP32 se alimenta directamente por USB con una fuente de 5 V. | 1 | Arduino UNO R3 |
Equipamiento del taller
- Computadora
- Impresora 3D
- Soldador
- Estaño
- Alicate
- Pinza de punta
- Pinza brusela
Plano de conexionado en protoboard
Deslizá horizontalmente para ver el plano completo
Deslizá horizontalmente para ver el plano completo
| Desde | Hacia | Color de cable |
|---|---|---|
| Arduino UNO · GND | Bus − de la protoboard | negro |
| Semáforo 1 · LED verde (ánodo) | Arduino UNO · 11 | verde |
| Semáforo 1 · LED verde (cátodo) | Resistencia 220 Ω (verde 1) | negro |
| Resistencia 220 Ω (verde 1) | Bus − de la protoboard | negro |
| Semáforo 1 · LED amarillo (ánodo) | Arduino UNO · 12 | amarillo |
| Semáforo 1 · LED amarillo (cátodo) | Resistencia 220 Ω (amarillo 1) | negro |
| Resistencia 220 Ω (amarillo 1) | Bus − de la protoboard | negro |
| Semáforo 1 · LED rojo (ánodo) | Arduino UNO · 13 | rojo |
| Semáforo 1 · LED rojo (cátodo) | Resistencia 220 Ω (rojo 1) | negro |
| Resistencia 220 Ω (rojo 1) | Bus − de la protoboard | negro |
| Semáforo 2 · LED verde (ánodo) | Arduino UNO · 8 | verde |
| Semáforo 2 · LED verde (cátodo) | Resistencia 220 Ω (verde 2) | negro |
| Resistencia 220 Ω (verde 2) | Bus − de la protoboard | negro |
| Semáforo 2 · LED amarillo (ánodo) | Arduino UNO · 9 | amarillo |
| Semáforo 2 · LED amarillo (cátodo) | Resistencia 220 Ω (amarillo 2) | negro |
| Resistencia 220 Ω (amarillo 2) | Bus − de la protoboard | negro |
| Semáforo 2 · LED rojo (ánodo) | Arduino UNO · 10 | rojo |
| Semáforo 2 · LED rojo (cátodo) | Resistencia 220 Ω (rojo 2) | negro |
| Resistencia 220 Ω (rojo 2) | Bus − de la protoboard | negro |
| Desde | Hacia | Color de cable |
|---|---|---|
| ESP32 DevKit v1 · GND | Bus − de la protoboard | negro |
| Semáforo 1 · LED verde (ánodo) | ESP32 DevKit v1 · GPIO16 | verde |
| Semáforo 1 · LED verde (cátodo) | Resistencia 220 Ω (verde 1) | negro |
| Resistencia 220 Ω (verde 1) | Bus − de la protoboard | negro |
| Semáforo 1 · LED amarillo (ánodo) | ESP32 DevKit v1 · GPIO17 | amarillo |
| Semáforo 1 · LED amarillo (cátodo) | Resistencia 220 Ω (amarillo 1) | negro |
| Resistencia 220 Ω (amarillo 1) | Bus − de la protoboard | negro |
| Semáforo 1 · LED rojo (ánodo) | ESP32 DevKit v1 · GPIO18 | rojo |
| Semáforo 1 · LED rojo (cátodo) | Resistencia 220 Ω (rojo 1) | negro |
| Resistencia 220 Ω (rojo 1) | Bus − de la protoboard | negro |
| Semáforo 2 · LED verde (ánodo) | ESP32 DevKit v1 · GPIO19 | verde |
| Semáforo 2 · LED verde (cátodo) | Resistencia 220 Ω (verde 2) | negro |
| Resistencia 220 Ω (verde 2) | Bus − de la protoboard | negro |
| Semáforo 2 · LED amarillo (ánodo) | ESP32 DevKit v1 · GPIO21 | amarillo |
| Semáforo 2 · LED amarillo (cátodo) | Resistencia 220 Ω (amarillo 2) | negro |
| Resistencia 220 Ω (amarillo 2) | Bus − de la protoboard | negro |
| Semáforo 2 · LED rojo (ánodo) | ESP32 DevKit v1 · GPIO22 | rojo |
| Semáforo 2 · LED rojo (cátodo) | Resistencia 220 Ω (rojo 2) | negro |
| Resistencia 220 Ω (rojo 2) | Bus − de la protoboard | negro |
Construir y programar un semáforo
Realizar el montaje sencillo de un circuito en una protoboard
Colocamos un LEDLEDComponente electrónico tipo diodo que emite luz. Tiene polaridad: la pata larga es la positiva y la corta (o el lado plano del encapsulado) la negativa. Conectado al revés no se daña, pero no enciende. verde en la protoboardProtoboardPlaca experimental para el prototipado rápido de circuitos: sus orificios permiten insertar y conectar componentes sin soldadura. Las líneas superior e inferior recorren la placa de punta a punta y suelen usarse para la alimentación., como indica el esquema.
En un LED, la pata larga siempre es la pata positiva y es donde se conecta nuestro pin. La pata corta es la negativa; también podés reconocerla por el lado plano del encapsulado. Si lo conectás con la polaridad invertida no se va a romper, pero tampoco va a encender.
La resistenciaResistenciaComponente que se opone al paso de la corriente eléctrica. Su valor se mide en ohm (Ω). Se usa para determinar la cantidad de corriente de una rama del circuito, por ejemplo para evitar que un LED se queme por exceso de corriente. de 220 ohm limita la cantidad de corriente que circula por esa rama del circuito: sin ella, el LED podría quemarse por exceso de corriente. Va conectada entre la pata corta del LED y la línea de GND de la protoboard.
Conectamos la pata larga del LED al pin 13 de la placa ArduinoArduinoPlaca electrónica con un microcontrolador programable y comunicación USB/serial. Desde la computadora, un IDE permite escribir el código, compilarlo y cargarlo en la placa. Tiene entradas y salidas digitales (0 a 13) y entradas analógicas (A0 a A5). UNO, y la línea de GND de la protoboard a uno de los pines GND de la placa.
En la placa ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. el armado es el mismo, pero los pines se llaman
GPIO y usamos otros números (los GPIO 0, 2, 12 y 15 se reservan porque intervienen en el
arranque de la placa). Conectá la pata larga del LED al GPIO que indica la constante
PIN_LED al comienzo del código del paso siguiente, y la línea de GND de la protoboard a
un pin GND de la placa.
Antes de pasar al programa, podés explorar el circuito completo del semáforo en el simulador. Es el mismo esquema que vas a armar sobre la mesa, y responde al comportamiento real del código:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Verificá
Todavía no cargamos ningún programa, así que el LED no va a encender. Antes de seguir, revisá: la pata larga del LED va hacia el cable que llega al pin de la placa, la resistencia conecta la pata corta con la línea de GND, y esa línea llega a un pin GND de la placa.
Escribir tu primer programa
Ahora vamos a escribir el programa que hace parpadear el LED que acabamos de conectar. La programación la realizaremos directamente en el lenguaje de Arduino (C++), usando el Arduino IDE en su versión 2.x, que se descarga gratis desde arduino.cc/en/software. IDE proviene de la sigla en inglés de entorno de desarrollo integrado: es el programa donde escribimos el código, lo compilamos y lo enviamos a la placa.
Un programa de Arduino se llama sketch y tiene una estructura mínima de dos bloques:
- Un bloque de código que se ejecuta por única vez al inicializarse el dispositivo. Este
bloque está contenido dentro de la función
setup(). - Un bloque de código que se ejecuta repetidamente luego de
setup(). Este bloque está contenido dentro de la funciónloop().
Después de // se incluyen comentarios para el lector, que no tienen ningún efecto en el
programa. Estos comentarios sirven para clarificar el código y que sea más fácil de
interpretar para otras personas.
Con esa estructura, nuestro primer programa necesita tres ideas nuevas, una por línea:
pinMode(pin, OUTPUT)le avisa a la placa, dentro desetup(), que ese pin va a usarse como salida.digitalWrite(pin, HIGH)enciende la salida (y conLOWla apaga).delay(1000)detiene el programa durante 1000 milisegundos, es decir, un segundo.
Ver el código completo · parpadeo.ino
// Parpadeo de un LED: el "Hola Mundo" de la electrónica.
// Enciende y apaga un LED cada 1 segundo.
//
// Plataforma: Arduino UNO. El pin 13 tiene un LED integrado en la placa,
// así que este programa funciona incluso antes de conectar el LED externo.
//
// Basado en: Fragmento de código 1 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop)
// por C++ idiomático de Arduino, con constantes nombradas para pin y tiempos.
// Constantes con nombre en lugar de "números mágicos": si mañana movemos
// el LED a otro pin o queremos otro ritmo, tocamos UNA línea acá arriba
// y no hace falta revisar el resto del programa. Este hábito es la base
// de todos los programas que siguen en la guía.
const int PIN_LED = 13; // LED integrado del UNO
const unsigned long DURACION_ENCENDIDO_MS = 1000;
const unsigned long DURACION_APAGADO_MS = 1000;
void setup() {
// Un pin digital puede leer o escribir. Acá declaramos que este pin
// va a ESCRIBIR (encender y apagar el LED).
pinMode(PIN_LED, OUTPUT);
}
void loop() {
// delay() detiene TODO el programa durante ese tiempo. Para un primer
// programa es ideal: el código se lee de arriba hacia abajo igual que
// la secuencia real de la luz. En el nivel avanzado veremos por qué
// esto se vuelve un problema (código "bloqueante") y cómo resolverlo
// con millis().
digitalWrite(PIN_LED, HIGH); // encender
delay(DURACION_ENCENDIDO_MS);
digitalWrite(PIN_LED, LOW); // apagar
delay(DURACION_APAGADO_MS);
} // Parpadeo de un LED: el "Hola Mundo" de la electrónica.
// Enciende y apaga un LED cada 1 segundo.
//
// Plataforma: ESP32 (DevKit v1). El GPIO 2 tiene un LED azul integrado
// en la placa, así que este programa funciona incluso antes de conectar
// el LED externo.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO). Con la
// misma resistencia de 220 ohm el LED enciende un poco más tenue, y NUNCA
// hay que inyectar 5 V en un GPIO del ESP32: se puede dañar la placa.
//
// Basado en: Fragmento de código 1 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop)
// por C++ idiomático de Arduino con constantes nombradas, y se portó la
// guía (pensada para Arduino UNO) a ESP32.
// Constantes con nombre en lugar de "números mágicos": si mañana movemos
// el LED a otro pin o queremos otro ritmo, tocamos UNA línea acá arriba
// y no hace falta revisar el resto del programa. Este hábito es la base
// de todos los programas que siguen en la guía.
const int PIN_LED = 2; // LED integrado del DevKit v1
const unsigned long DURACION_ENCENDIDO_MS = 1000;
const unsigned long DURACION_APAGADO_MS = 1000;
void setup() {
// Un pin digital puede leer o escribir. Acá declaramos que este pin
// va a ESCRIBIR (encender y apagar el LED).
pinMode(PIN_LED, OUTPUT);
}
void loop() {
// delay() detiene TODO el programa durante ese tiempo. Para un primer
// programa es ideal: el código se lee de arriba hacia abajo igual que
// la secuencia real de la luz. En el nivel avanzado veremos por qué
// esto se vuelve un problema (código "bloqueante") y cómo resolverlo
// con millis().
digitalWrite(PIN_LED, HIGH); // encender
delay(DURACION_ENCENDIDO_MS);
digitalWrite(PIN_LED, LOW); // apagar
delay(DURACION_APAGADO_MS);
} Leelo en voz alta como si fuera una receta: “encendé la luz, esperá un segundo, apagala,
esperá un segundo… y volvé a empezar”. Eso es exactamente lo que loop() repite sin
descanso mientras la placa tenga energía. Antes de cargarlo en la placa, podés probar el
mismo comportamiento en el simulador del paso anterior.
Para el aula
El material original de 2019 enseñaba este paso con bloques en mBlock3, que hoy está discontinuado. La función didáctica de los bloques —bajar la barrera de entrada— la cumple acá la combinación de un código muy corto, comentado línea por línea, y el simulador en el navegador, que permite equivocarse sin miedo antes de tocar la placa. La experiencia de aula muestra que un sketch de ocho líneas leído como receta es tan abordable como el programa de bloques equivalente, y evita la transición posterior bloques → código, que el propio original ya señalaba como necesaria en el nivel superior.
Verificá
En el Arduino IDE, apretá el botón de verificar (el tilde ✓). Si abajo aparece “Compilación completada” sin mensajes en rojo, tu programa está bien escrito. Si aparece un error, revisá los punto y coma al final de cada línea y que las llaves abran y cierren.
Subir el código a la placa
Para subir el código de nuestro programa a la placa, necesitamos:
- Conectar la placa a la computadora con el cable USB.
- Chequear que en el selector de placa del Arduino IDE esté seleccionada la placa correcta.
- Seleccionar el puertoPuerto COMPuerto de comunicaciones por el cual el sistema operativo se comunica con un dispositivo externo, como la placa. Se asigna automáticamente al conectarla por USB y puede cambiar de número; en el IDE hay que indicar cuál usar. al que está conectada la placa.
- Cliquear el botón Subir (la flecha →) en la parte superior de la interfaz.
En el Arduino IDE 2.x, la placa y el puerto se eligen juntos en el menú desplegable de la barra superior: al conectar la placa suele aparecer directamente como opción. Si no aparece, entrá a “Select other board and port” y buscala por nombre.
Para el Arduino UNO: usá el cable USB tipo B, elegí la placa Arduino UNO y el puerto
que se agregó al conectarla (en Windows es un puerto COM numerado; en Linux suele
llamarse /dev/ttyACM0 o similar).
Para el ESP32 hay dos pasos previos que se hacen una sola vez: instalar el soporte de placas ESP32 desde el gestor de placas del IDE (buscá “esp32” e instalá el paquete de Espressif) y, si el puerto no aparece, instalar el driver del conversor USB-serie de tu placa (generalmente CP210x o CH340). Después, elegí la placa ESP32 Dev Module (o “DOIT ESP32 DEVKIT V1”) y el puerto correspondiente. Si al subir el IDE queda esperando en “Connecting…”, mantené apretado el botón BOOT de la placa hasta que empiece la carga.
Al terminar de subir nuestro código, veremos el mensaje de carga completada en la barra inferior del IDE.
Verificá
Con la conexión previamente realizada, el LED debería encenderse y apagarse siguiendo intervalos de un segundo. Si no parpadea, repasá la polaridad del LED y que el pin del código sea el mismo al que conectaste el cable.
Expandir el circuito a tres LED
Continuamos agregando tres LED, uno de cada color (verde, amarillo y rojo), con sus respectivas resistencias de 220 ohm, como indica el circuito.
Colocamos todas las resistencias hacia la línea de GND de la protoboard, que se conecta al pin GND de la placa.
Conectamos el LED rojo al pin 13, el amarillo al pin 12 y el verde al pin 11.
En el ESP32 usamos tres GPIO de propósito general seguros para salidas. Los números
exactos están en las constantes PIN_LED_ROJO, PIN_LED_AMARILLO y PIN_LED_VERDE
al comienzo del código: conectá cada LED al GPIO que indica su constante.
Ahora modificamos el programa anterior para que realice ciclos de encendido y apagado para las luces de los tres colores, con los intervalos de demora de un semáforo real: verde 5 segundos, amarillo 1 segundo, rojo 5 segundos y, antes de volver al verde, rojo y amarillo juntos durante 1 segundo (el aviso de “preparate para avanzar”).
Ver el código completo · semaforo-simple.ino
// Semáforo simple de tres luces (un solo semáforo).
// Secuencia: verde 5 s → amarillo 1 s → rojo 5 s → rojo+amarillo 1 s,
// y vuelve a empezar. La fase rojo+amarillo avisa que está por venir
// el verde, como en los semáforos reales de varios países.
//
// Plataforma: Arduino UNO.
// Conexión: LED verde → pin 11, LED amarillo → pin 12, LED rojo → pin 13,
// cada uno con su resistencia de 220 ohm a GND.
//
// Basado en: Fragmento de código 2 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop)
// por C++ idiomático de Arduino, con constantes nombradas para pines y tiempos.
// Pines del semáforo 1 (los mismos del esquema original de la guía).
const int PIN_SEM1_VERDE = 11;
const int PIN_SEM1_AMARILLO = 12;
const int PIN_SEM1_ROJO = 13;
// Tiempos de cada fase. Son los MISMOS en toda la guía (y en el simulador):
// si acá se cambian, el comportamiento deja de coincidir con el resto.
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_ROJO_MS = 5000;
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
void setup() {
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
}
void loop() {
// En cada fase escribimos las TRES luces, aunque alguna no cambie:
// así cada bloque describe el estado completo del semáforo y no hay
// que "acordarse" de qué quedó encendido en la fase anterior.
// Fase 1: VERDE — los autos circulan.
digitalWrite(PIN_SEM1_VERDE, HIGH);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, LOW);
delay(DURACION_VERDE_MS);
// Fase 2: AMARILLO — atención, prepararse para frenar.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, LOW);
delay(DURACION_AMARILLO_MS);
// Fase 3: ROJO — detenerse.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
delay(DURACION_ROJO_MS);
// Fase 4: ROJO + AMARILLO — aviso de que está por venir el verde.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, HIGH);
delay(DURACION_ROJO_AMARILLO_MS);
// Al terminar la fase 4, loop() vuelve a ejecutarse desde arriba:
// ese "volver a empezar" automático es lo que hace el ciclo infinito.
} // Semáforo simple de tres luces (un solo semáforo).
// Secuencia: verde 5 s → amarillo 1 s → rojo 5 s → rojo+amarillo 1 s,
// y vuelve a empezar. La fase rojo+amarillo avisa que está por venir
// el verde, como en los semáforos reales de varios países.
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: LED verde → GPIO 16, LED amarillo → GPIO 17, LED rojo → GPIO 18,
// cada uno con su resistencia de 220 ohm a GND.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO). Con la
// misma resistencia de 220 ohm los LED encienden un poco más tenues, y
// NUNCA hay que inyectar 5 V en un GPIO del ESP32.
//
// Basado en: Fragmento de código 2 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop)
// por C++ idiomático de Arduino con constantes nombradas, y se portó a ESP32
// usando GPIO seguros (16/17/18, lejos de los pines de arranque o "strapping").
// Pines del semáforo 1.
const int PIN_SEM1_VERDE = 16;
const int PIN_SEM1_AMARILLO = 17;
const int PIN_SEM1_ROJO = 18;
// Tiempos de cada fase. Son los MISMOS en toda la guía (y en el simulador):
// si acá se cambian, el comportamiento deja de coincidir con el resto.
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_ROJO_MS = 5000;
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
void setup() {
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
}
void loop() {
// En cada fase escribimos las TRES luces, aunque alguna no cambie:
// así cada bloque describe el estado completo del semáforo y no hay
// que "acordarse" de qué quedó encendido en la fase anterior.
// Fase 1: VERDE — los autos circulan.
digitalWrite(PIN_SEM1_VERDE, HIGH);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, LOW);
delay(DURACION_VERDE_MS);
// Fase 2: AMARILLO — atención, prepararse para frenar.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, LOW);
delay(DURACION_AMARILLO_MS);
// Fase 3: ROJO — detenerse.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
delay(DURACION_ROJO_MS);
// Fase 4: ROJO + AMARILLO — aviso de que está por venir el verde.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, HIGH);
delay(DURACION_ROJO_AMARILLO_MS);
// Al terminar la fase 4, loop() vuelve a ejecutarse desde arriba:
// ese "volver a empezar" automático es lo que hace el ciclo infinito.
} Fijate que el programa es una estructura secuencial: las órdenes se ejecutan una detrás
de la otra, en el orden exacto en que están escritas, y al llegar al final loop() vuelve a
empezar. Ese orden es la “coreografía” del semáforo.
Podés volver al simulador del paso 1 para comparar el comportamiento esperado con el de tu circuito real.
Los tiempos de tu semáforo
Los intervalos del modelo son un punto de partida. Observen con el grupo un semáforo real cercano y cronometren cuánto dura cada luz. Después, modifiquen los valores de demora del programa para que el prototipo imite al semáforo observado. ¿Qué pasa si el amarillo dura demasiado poco? ¿Y si el verde de una calle muy transitada dura demasiado?
Verificá
El semáforo debería repetir en ciclo: verde 5 segundos → amarillo 1 segundo → rojo 5 segundos → rojo y amarillo juntos 1 segundo → verde otra vez. Si algún color no enciende, revisá su resistencia y su pin; si encienden en el orden equivocado, revisá qué pin quedó asignado a cada color en el código.
Soldar y ensamblar la maqueta
En este momento se procederá a construir la maqueta del semáforo. Para ello se debe soldar cada componente e imprimir el modelo 3D del semáforo que alojará el circuito de las luces. Tené en cuenta que si se pretende seguir con el nivel intermedio y agregar un segundo semáforo, puede resultar más conveniente saltar este paso y continuar con los siguientes, para programar la conexión de los dos semáforos en la protoboard antes del armado final.
Para armar la maqueta, se deben imprimir las piezas en la impresora 3D. El modelo original del semáforo se distribuía de forma libre y gratuita en el repositorio Saberes Digitales; sus archivos están en re-publicación como paquete de fabricación de este proyecto (vas a encontrarlo en la ficha, junto con un rediseño de la maqueta para corte láser en MDF de 3 mm). Cuando estén listas todas las piezas, las ensamblamos para construir el semáforo.
En caso de querer realizar una modificación en el modelo, independientemente del programa de modelado que utilicemos, debemos exportar nuestras piezas en formato .stlFormato .stlFormato de archivo que contiene la forma de un objeto sólido 3D, sin color, texturas ni propiedades físicas. Es el más usado para intercambiar e imprimir modelos 3D; funciona como la 'exportación final' de un diseño, como un PDF de un documento de texto.. El .stl es un formato de archivo de diseño asistido por computadora (CAD) que define la geometría de objetos sólidos 3D; es el más popular a la hora de intercambiar digitalmente modelos para ser impresos en 3D. El programa laminador de la impresora convierte ese .stl en código GCódigo GConjunto de acciones que la impresora 3D (o cualquier máquina CNC) debe realizar para completar un trabajo. Se genera analizando un archivo STL, y es específico de cada modelo de impresora.: las instrucciones de movimiento que tu impresora ejecuta para materializar la pieza.
Una vez que las conexiones han sido probadas y el sistema de luces funciona correctamente, podemos pasar a armar los circuitos de la maqueta prescindiendo de la protoboard. Para hacerlo, soldamos cada LED con su cable y su resistencia. Luego, lo colocamos dentro de la pieza correspondiente del semáforo.
Verificá
Con los LED ya soldados y montados en la maqueta, volvé a conectar la placa: la secuencia del paso 4 tiene que seguir funcionando igual que en la protoboard. Una soldadura fría (opaca, granulosa) suele ser la causa de una luz que titila o no enciende.
Cierre del nivel
Al terminar este nivel, tu semáforo tiene que estar funcionando solo: el circuito de tres LED montado con sus resistencias, el programa cargado desde el Arduino IDE, y la secuencia verde → amarillo → rojo → rojo + amarillo repitiéndose en ciclo con sus tiempos. Si además ensamblaste la maqueta impresa en 3D, ya tenés un prototipo digno de una esquina de verdad. En el nivel intermedio ese semáforo deja de estar solo: vamos a sumarle el segundo semáforo de la intersección y a sincronizarlos.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El circuito de tres LED (rojo, amarillo y verde) está montado en la protoboard, cada uno con su resistencia de 220 ohm y con la polaridad correcta.
- La placa ejecuta el programa del semáforo: verde 5 segundos, amarillo 1 segundo, rojo 5 segundos y rojo + amarillo 1 segundo, en un ciclo que se repite solo.
- Sabés subir un programa desde el Arduino IDE 2.x: elegir la placa, elegir el puerto y verificar que el código quedó cargado.
- La maqueta del semáforo está ensamblada con los LED soldados (o el paso quedó planificado para después del nivel intermedio).
Dos semáforos sincronizados en una intersección
Situación problema
Para que el semáforo pueda cumplir su función, es necesario agregar un segundo semáforo que lo acompañe y regule el tráfico que circula por la otra calle de la intersección. Antes de instalar el segundo semáforo, debemos tener en cuenta que ambos semáforos deberán estar sincronizados entre sí para funcionar de forma adecuada y conjunta.
En este segundo momento se agregan la conexión y la programación de un segundo semáforo desde la misma placa.
Armar el circuito de la intersección
Para armar la intersección con dos semáforos debemos conectar a la protoboardProtoboardPlaca experimental para el prototipado rápido de circuitos: sus orificios permiten insertar y conectar componentes sin soldadura. Las líneas superior e inferior recorren la placa de punta a punta y suelen usarse para la alimentación. tres LEDLEDComponente electrónico tipo diodo que emite luz. Tiene polaridad: la pata larga es la positiva y la corta (o el lado plano del encapsulado) la negativa. Conectado al revés no se daña, pero no enciende. más que en el circuito anterior (rojo, amarillo y verde), con sus respectivas resistenciasResistenciaComponente que se opone al paso de la corriente eléctrica. Su valor se mide en ohm (Ω). Se usa para determinar la cantidad de corriente de una rama del circuito, por ejemplo para evitar que un LED se queme por exceso de corriente. de 220 ohm hacia la línea de GND.
El primer semáforo conserva sus pines del nivel inicial (rojo 13, amarillo 12, verde 11). Para el segundo semáforo conectaremos el LED rojo al pin 10, el amarillo al pin 9 y el verde al pin 8.
En el ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT., cada uno de los seis LED va a un GPIO de salida
seguro. Los números exactos están en las constantes del comienzo del código
(PIN_S1_ROJO, PIN_S1_AMARILLO, PIN_S1_VERDE, PIN_S2_ROJO, PIN_S2_AMARILLO,
PIN_S2_VERDE): conectá cada LED al GPIO que indica su constante.
Podés recorrer el circuito completo, con los dos semáforos funcionando en la esquina, en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
Verificá
Antes de programar, verificá con el programa del nivel inicial que los tres LED nuevos respondan: cambiá momentáneamente los pines del código por los del segundo semáforo y comprobá que encienden. Así separás los problemas de circuito de los problemas de código.
Programar la secuencia para dos semáforos
En este momento vamos a sumar la secuencia de nuestro segundo semáforo al código previamente armado. La planificación de la secuencia para dos semáforos es más compleja, por lo que requiere que se le dedique un poco más de tiempo a su armado y comprensión.
Para pensar cómo armar el código, es posible imaginar esta secuencia como viñetas de un cómic en donde hay una serie de acciones a realizar y una demora entre cuadro y cuadro. Cada viñeta muestra la esquina congelada en un instante: qué luz tiene cada semáforo y cuánto tiempo dura ese cuadro antes de pasar al siguiente.
| Viñeta | Semáforo 1 | Semáforo 2 | Demora |
|---|---|---|---|
| Nº 1 | Verde | Rojo | 5 s |
| Nº 2 | Amarillo | Rojo | 1 s |
| Nº 3 | Rojo | Rojo | 1 s |
| Nº 4 | Rojo | Rojo + amarillo | 1 s |
| Nº 5 | Rojo | Verde | 5 s |
| Nº 6 | Rojo | Amarillo | 1 s |
| Nº 7 | Rojo | Rojo | 1 s |
| Nº 8 | Rojo + amarillo | Rojo | 1 s |
| Nº 9 | Verde | Rojo | — |
Como se puede ver en la tabla, el cuadro Nº 9 es igual al cuadro Nº 1: es en ese punto donde comienza a repetirse la secuencia. Por lo tanto, en total tenemos ocho pasos que se repiten y una determinada demora en cada paso.
Dos detalles de esta coreografía merecen atención:
- Nunca las dos calles tienen verde a la vez: cuando un semáforo habilita el paso, el otro está en rojo.
- En las viñetas Nº 3 y Nº 7 ambos semáforos están en rojo durante un segundo. Ese respiro le da tiempo al último vehículo a despejar la bocacalle antes de habilitar a la calle transversal.
Para el aula
También puede realizarse una observación con los alumnos de algún semáforo cercano a la escuela: cronometrar los cuadros reales, detectar el momento de “todo rojo” y dibujar la secuencia como viñetas antes de tocar el código. Pasar de la observación al papel y del papel al programa es exactamente el recorrido de abstracción que este nivel busca ejercitar.
Traducimos las ocho viñetas al programa: cada viñeta es un grupo de órdenes de encendido y apagado seguido de su demora, en estructura secuencial.
Ver el código completo · interseccion.ino
// Intersección con dos semáforos sincronizados (6 LED).
// Recorre los 8 cuadros del "cómic" de la Fig. 10 de la guía original:
//
// Cuadro | Semáforo 1 | Semáforo 2 | Duración
// -------+-----------------+-----------------+---------
// 1 | VERDE | ROJO | 5 s
// 2 | AMARILLO | ROJO | 1 s
// 3 | ROJO | ROJO | 1 s (todo rojo)
// 4 | ROJO | ROJO + AMARILLO | 1 s
// 5 | ROJO | VERDE | 5 s
// 6 | ROJO | AMARILLO | 1 s
// 7 | ROJO | ROJO | 1 s (todo rojo)
// 8 | ROJO + AMARILLO | ROJO | 1 s
//
// El cuadro 9 es igual al 1: ahí la secuencia vuelve a empezar.
// Los cuadros 3 y 7 dejan TODO en rojo un instante: es el margen de
// seguridad para que la intersección se vacíe antes de habilitar la
// otra calle. Ningún semáforo real pasa de verde a verde sin ese margen.
//
// Plataforma: Arduino UNO.
// Conexión: semáforo 1 → verde 11, amarillo 12, rojo 13;
// semáforo 2 → verde 8, amarillo 9, rojo 10 (como en la guía).
//
// Basado en: Fragmento de código 3 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop)
// por C++ idiomático de Arduino con constantes nombradas. Además, cada
// cuadro escribe las SEIS luces completas (el original solo escribía los
// pines que cambiaban): así cada bloque de código se corresponde 1 a 1
// con un cuadro del cómic y puede leerse de forma aislada.
// Semáforo 1 (calle principal).
const int PIN_SEM1_VERDE = 11;
const int PIN_SEM1_AMARILLO = 12;
const int PIN_SEM1_ROJO = 13;
// Semáforo 2 (calle transversal).
const int PIN_SEM2_VERDE = 8;
const int PIN_SEM2_AMARILLO = 9;
const int PIN_SEM2_ROJO = 10;
// Tiempos de la secuencia (los mismos de toda la guía y del simulador).
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_TODO_ROJO_MS = 1000; // margen de seguridad
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
void setup() {
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
pinMode(PIN_SEM2_VERDE, OUTPUT);
pinMode(PIN_SEM2_AMARILLO, OUTPUT);
pinMode(PIN_SEM2_ROJO, OUTPUT);
}
void loop() {
// Cuadro 1: Semáforo 1 VERDE / Semáforo 2 ROJO.
digitalWrite(PIN_SEM1_VERDE, HIGH);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, LOW);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_VERDE_MS);
// Cuadro 2: Semáforo 1 AMARILLO / Semáforo 2 ROJO.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, LOW);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_AMARILLO_MS);
// Cuadro 3: TODO ROJO — la intersección se vacía antes del cambio.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_TODO_ROJO_MS);
// Cuadro 4: Semáforo 1 ROJO / Semáforo 2 ROJO + AMARILLO (por arrancar).
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, HIGH);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_ROJO_AMARILLO_MS);
// Cuadro 5: Semáforo 1 ROJO / Semáforo 2 VERDE — ahora circula la otra calle.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, HIGH);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, LOW);
delay(DURACION_VERDE_MS);
// Cuadro 6: Semáforo 1 ROJO / Semáforo 2 AMARILLO.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, HIGH);
digitalWrite(PIN_SEM2_ROJO, LOW);
delay(DURACION_AMARILLO_MS);
// Cuadro 7: TODO ROJO — otra vez el margen de seguridad.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_TODO_ROJO_MS);
// Cuadro 8: Semáforo 1 ROJO + AMARILLO / Semáforo 2 ROJO.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_ROJO_AMARILLO_MS);
// loop() vuelve a empezar: el cuadro 9 del cómic es igual al cuadro 1.
} // Intersección con dos semáforos sincronizados (6 LED).
// Recorre los 8 cuadros del "cómic" de la Fig. 10 de la guía original:
//
// Cuadro | Semáforo 1 | Semáforo 2 | Duración
// -------+-----------------+-----------------+---------
// 1 | VERDE | ROJO | 5 s
// 2 | AMARILLO | ROJO | 1 s
// 3 | ROJO | ROJO | 1 s (todo rojo)
// 4 | ROJO | ROJO + AMARILLO | 1 s
// 5 | ROJO | VERDE | 5 s
// 6 | ROJO | AMARILLO | 1 s
// 7 | ROJO | ROJO | 1 s (todo rojo)
// 8 | ROJO + AMARILLO | ROJO | 1 s
//
// El cuadro 9 es igual al 1: ahí la secuencia vuelve a empezar.
// Los cuadros 3 y 7 dejan TODO en rojo un instante: es el margen de
// seguridad para que la intersección se vacíe antes de habilitar la
// otra calle. Ningún semáforo real pasa de verde a verde sin ese margen.
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: semáforo 1 → verde GPIO 16, amarillo GPIO 17, rojo GPIO 18;
// semáforo 2 → verde GPIO 19, amarillo GPIO 21, rojo GPIO 22.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO); nunca
// inyectar 5 V en un GPIO.
//
// Basado en: Fragmento de código 3 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: se reemplazó el código generado por mBlock3 (_delay, _loop)
// por C++ idiomático de Arduino con constantes nombradas y se portó a ESP32
// con GPIO seguros (se evitan los pines de arranque o "strapping"). Además,
// cada cuadro escribe las SEIS luces completas (el original solo escribía
// los pines que cambiaban): así cada bloque de código se corresponde 1 a 1
// con un cuadro del cómic y puede leerse de forma aislada.
// Semáforo 1 (calle principal).
const int PIN_SEM1_VERDE = 16;
const int PIN_SEM1_AMARILLO = 17;
const int PIN_SEM1_ROJO = 18;
// Semáforo 2 (calle transversal).
const int PIN_SEM2_VERDE = 19;
const int PIN_SEM2_AMARILLO = 21;
const int PIN_SEM2_ROJO = 22;
// Tiempos de la secuencia (los mismos de toda la guía y del simulador).
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_TODO_ROJO_MS = 1000; // margen de seguridad
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
void setup() {
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
pinMode(PIN_SEM2_VERDE, OUTPUT);
pinMode(PIN_SEM2_AMARILLO, OUTPUT);
pinMode(PIN_SEM2_ROJO, OUTPUT);
}
void loop() {
// Cuadro 1: Semáforo 1 VERDE / Semáforo 2 ROJO.
digitalWrite(PIN_SEM1_VERDE, HIGH);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, LOW);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_VERDE_MS);
// Cuadro 2: Semáforo 1 AMARILLO / Semáforo 2 ROJO.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, LOW);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_AMARILLO_MS);
// Cuadro 3: TODO ROJO — la intersección se vacía antes del cambio.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_TODO_ROJO_MS);
// Cuadro 4: Semáforo 1 ROJO / Semáforo 2 ROJO + AMARILLO (por arrancar).
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, HIGH);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_ROJO_AMARILLO_MS);
// Cuadro 5: Semáforo 1 ROJO / Semáforo 2 VERDE — ahora circula la otra calle.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, HIGH);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, LOW);
delay(DURACION_VERDE_MS);
// Cuadro 6: Semáforo 1 ROJO / Semáforo 2 AMARILLO.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, HIGH);
digitalWrite(PIN_SEM2_ROJO, LOW);
delay(DURACION_AMARILLO_MS);
// Cuadro 7: TODO ROJO — otra vez el margen de seguridad.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, LOW);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_TODO_ROJO_MS);
// Cuadro 8: Semáforo 1 ROJO + AMARILLO / Semáforo 2 ROJO.
digitalWrite(PIN_SEM1_VERDE, LOW);
digitalWrite(PIN_SEM1_AMARILLO, HIGH);
digitalWrite(PIN_SEM1_ROJO, HIGH);
digitalWrite(PIN_SEM2_VERDE, LOW);
digitalWrite(PIN_SEM2_AMARILLO, LOW);
digitalWrite(PIN_SEM2_ROJO, HIGH);
delay(DURACION_ROJO_AMARILLO_MS);
// loop() vuelve a empezar: el cuadro 9 del cómic es igual al cuadro 1.
} La esquina de tu escuela
Adapten la secuencia a una esquina real: elijan una intersección conocida donde una calle tenga mucho más tránsito que la otra y decidan, con argumentos, cuánto verde le corresponde a cada una. Modifiquen las demoras del programa y defiendan su propuesta ante el resto de los grupos como si fueran la oficina de tránsito del municipio.
Verificá
Observá el ciclo completo, de punta a punta, un par de veces: mientras un semáforo está en verde o amarillo, el otro tiene que estar en rojo; entre el rojo de uno y el verde del otro tiene que aparecer el segundo de “todo rojo”; y antes de cada verde tiene que verse el rojo + amarillo. Si dos luces del mismo semáforo quedan encendidas fuera de esos momentos, revisá el orden de las órdenes en esa viñeta.
Cierre del nivel
Al terminar este nivel, la intersección completa funciona con una sola placa: los seis LED conectados, y los dos semáforos recorriendo en ciclo la secuencia de ocho estados, con sus momentos de “todo rojo” y de rojo + amarillo. Además, podés contar la secuencia cuadro a cuadro, como un cómic, que es la mejor prueba de que la entendiste. Si habías postergado la soldadura y el ensamblado de las maquetas, este es el momento de retomar el paso 5 del nivel inicial para las dos columnas. En el nivel avanzado, la central de tránsito va a poder mirar esta esquina desde internet: vamos a sumarle Internet de las Cosas.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- Los seis LED de la intersección (dos semáforos completos) están conectados a una sola placa, cada uno con su resistencia.
- Los dos semáforos recorren la secuencia completa de ocho estados en ciclo, sin habilitar nunca las dos calles al mismo tiempo.
- Podés explicar la secuencia cuadro a cuadro, como las viñetas de un cómic, incluyendo los momentos en que ambos semáforos quedan en rojo.
Monitoreo remoto con Internet de las Cosas
Situación problema
Se desea monitorear, desde una central, el estado y el funcionamiento de todos los semáforos que se han instalado. Para ello se decidió instalar un sistema de IoT, que permite enviar a la central vía internet esta información, que es obtenida a través de los sistemas que ya se instalaron para cada semáforo.
En esta actividad se programará el envío de los datos obtenidos a un dispositivo móvil a través de Internet de las Cosas (IoT).
Introducción a Internet de las Cosas (IoT)
Internet de las Cosas (en inglés Internet of Things, abreviado IoTIoT (Internet de las Cosas)Interconexión digital de objetos cotidianos con internet. Permite, por ejemplo, enviar los datos de un sensor a una central y visualizarlos desde un dispositivo móvil. Requiere un dispositivo capaz de conectarse a internet y un servidor que reciba y aloje los datos.) es un concepto que refiere a la interconexión digital de objetos cotidianos con internet. Esta interconexión puede tener diversas funciones. Por ejemplo, puede utilizarse para monitorear la temperatura de un ambiente, enviando los datos obtenidos por un sensor a una central donde se recopile la información. De esta manera podría visualizarse en un dispositivo móvil la temperatura de un laboratorio, de un invernadero o de una sala de un hospital.
Para poder incorporar IoT a nuestro proyecto es necesario:
- Un dispositivo capaz de conectarse a internet.
- Un servidor que reciba y aloje los datos.
Existen diversas formas de lograr el cometido de registrar y almacenar los datos de nuestro sistema de semáforos. En este caso, se detallará cómo hacerlo con el WiFi integrado de la placa ESP32ESP32Placa con un microcontrolador de Espressif que trae WiFi (y Bluetooth) integrados. Se programa desde el Arduino IDE igual que un Arduino, con pines llamados GPIO. En este proyecto reemplaza al conjunto Arduino UNO + módulo OBLOQ para el nivel IoT. y con los servidores de Adafruit IOAdafruit IOPlataforma online de uso gratuito, orientada al uso educativo, que centraliza y gestiona la información que reportan los dispositivos IoT. Permite crear feeds y paneles de control para monitorear los datos desde cualquier dispositivo..
El material original de 2019 resolvía este punto agregando al Arduino UNO un módulo externo llamado OBLOQ, un adaptador WiFi-serie hoy discontinuado. Como el UNO clásico no tiene conectividad propia, en esta reedición la práctica de IoT se realiza con la variante ESP32 del proyecto: podés seguir este nivel de manera conceptual con tu UNO (los pasos 2 y 3 funcionan perfectamente en él) y cambiar al ESP32 con el selector de plataforma para la parte práctica de conexión, que es idéntica en lo didáctico y más simple en el circuito.
El ESP32 cumple los dos roles del “dispositivo capaz de conectarse a internet”: es el microcontrolador que maneja los LED del semáforo y, a la vez, trae radio WiFi integrada. No hace falta ningún módulo adicional ni cableado extra: la conexión a internet se resuelve por completo desde el código.
Programar sin código bloqueante
Antes de comenzar a utilizar IoT debemos hacer una aclaración con respecto a la función
delay() que figura en el código que usamos hasta ahora. Esta función brinda un tiempo de
espera al sistema que puede utilizarse con varios fines. Suele utilizarse bastante en las
primeras aproximaciones a la programación, ya que su comportamiento resulta fácil de
comprender y su programación no requiere más que una línea de código.
Sin embargo, esta función tiene una complicación, dado que genera un
código bloqueanteCódigo bloqueanteCódigo que detiene todo el procesamiento hasta que se cumple una condición, como delay(), que deja al programa 'colgado' durante el tiempo indicado. En IoT es conflictivo: impide atender tareas simultáneas como la publicación de datos y la conexión a internet.. Esto significa que, cuando el
programa entra en esa función, se detiene todo el procesamiento hasta que se cumpla el
tiempo indicado. En otras palabras, cuando el programa entra al delay() queda “colgado”
por el período de tiempo establecido.
Al utilizar IoT, es conflictivo utilizar código bloqueante, ya que al detenerse el procesamiento se impide también que el sistema realice otras operaciones que funcionan en simultáneo. Por ejemplo, las tareas de publicación y el mantenimiento constante de la conexión a internet.
Para evitar estos problemas, se puede utilizar una alternativa de código no bloqueante,
como la función millis(). Esta función arroja un valor sobre un conteo de tiempo, que se
realiza desde el momento en que se inicia el sistema. Es decir, funciona como un cronómetro
(en milisegundos) que, cada vez que es consultada desde el código, “devuelve” el valor en
el que se encuentra. De esta manera podemos pedirle al sistema que informe cuánto tiempo
transcurrió desde el inicio de las operaciones para dar indicaciones temporales sobre una
tarea, sin detener todas las demás.
A continuación se presenta un ejemplo de cómo se puede programar la intermitencia de un
LED que se prenda y apague cada un segundo (expresado en 1000 milisegundos) sin utilizar
código bloqueante, usando millis() para consultar cuánto tiempo pasó:
Ver el código completo · parpadeo-sin-delay.ino
// Parpadeo de un LED SIN delay(): código no bloqueante con millis().
//
// ¿Por qué? delay() "cuelga" el procesador: mientras espera, no puede
// atender botones, sensores ni la conexión a internet (clave para IoT).
// millis() funciona como un cronómetro que corre desde el encendido:
// en vez de detenernos a esperar, cada vuelta de loop() PREGUNTAMOS
// cuánto tiempo pasó y solo actuamos cuando corresponde.
//
// Plataforma: Arduino UNO (pin 13, LED integrado).
//
// Basado en: Fragmento de código 4 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: constantes nombradas para pin e intervalo, y
// "millisAnterior" declarado como unsigned long (el original usaba long,
// que a los ~25 días de funcionamiento continuo desborda y rompe la cuenta;
// con unsigned long la resta millisActual - millisAnterior sigue siendo
// correcta incluso cuando millis() da la vuelta a los ~49 días).
const int PIN_LED = 13; // LED integrado del UNO
const unsigned long INTERVALO_PARPADEO_MS = 1000;
// Estado actual del LED (LOW = apagado, HIGH = encendido).
int estadoLed = LOW;
// Momento (en ms desde el arranque) en que conmutamos el LED por última vez.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LED, OUTPUT);
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya pasó un segundo desde la última conmutación? Si no pasó,
// no hacemos nada y loop() vuelve a empezar: eso es NO bloquear.
if (millisActual - millisAnterior >= INTERVALO_PARPADEO_MS) {
// Conmutar el estado del LED.
if (estadoLed == LOW) {
estadoLed = HIGH;
} else {
estadoLed = LOW;
}
digitalWrite(PIN_LED, estadoLed);
// Guardamos "cuándo fue la última vez": así la próxima espera
// se mide desde este momento y no desde el arranque.
millisAnterior = millisActual;
}
// En este punto el procesador queda libre para realizar otras tareas
// (leer sensores, atender la red, etc.) en cada vuelta de loop().
} // Parpadeo de un LED SIN delay(): código no bloqueante con millis().
//
// ¿Por qué? delay() "cuelga" el procesador: mientras espera, no puede
// atender botones, sensores ni la conexión a internet (clave para IoT,
// y en el ESP32 el WiFi es justamente el plato fuerte). millis() funciona
// como un cronómetro que corre desde el encendido: en vez de detenernos
// a esperar, cada vuelta de loop() PREGUNTAMOS cuánto tiempo pasó y solo
// actuamos cuando corresponde.
//
// Plataforma: ESP32 (DevKit v1), GPIO 2 (LED azul integrado).
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO).
//
// Basado en: Fragmento de código 4 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: portado a ESP32; constantes nombradas para pin e
// intervalo, y "millisAnterior" declarado como unsigned long (el original
// usaba long, que desborda la cuenta; con unsigned long la resta
// millisActual - millisAnterior sigue siendo correcta incluso cuando
// millis() da la vuelta a los ~49 días).
const int PIN_LED = 2; // LED integrado del DevKit v1
const unsigned long INTERVALO_PARPADEO_MS = 1000;
// Estado actual del LED (LOW = apagado, HIGH = encendido).
int estadoLed = LOW;
// Momento (en ms desde el arranque) en que conmutamos el LED por última vez.
unsigned long millisAnterior = 0;
void setup() {
pinMode(PIN_LED, OUTPUT);
}
void loop() {
unsigned long millisActual = millis();
// ¿Ya pasó un segundo desde la última conmutación? Si no pasó,
// no hacemos nada y loop() vuelve a empezar: eso es NO bloquear.
if (millisActual - millisAnterior >= INTERVALO_PARPADEO_MS) {
// Conmutar el estado del LED.
if (estadoLed == LOW) {
estadoLed = HIGH;
} else {
estadoLed = LOW;
}
digitalWrite(PIN_LED, estadoLed);
// Guardamos "cuándo fue la última vez": así la próxima espera
// se mide desde este momento y no desde el arranque.
millisAnterior = millisActual;
}
// En este punto el procesador queda libre para realizar otras tareas
// (leer sensores, atender la red WiFi, etc.) en cada vuelta de loop().
} En el ejemplo se puede observar que, para tomar el valor de millis(), se define un valor
inicial al que llamamos millisAnterior, que es igual a cero. Luego, para saber el tiempo
transcurrido, se calcula la diferencia entre el valor de millisActual y el de
millisAnterior. Como queremos generar una intermitencia de 1 segundo, evaluamos si esta
diferencia es mayor o igual a 1000. En caso de que haya transcurrido más de un segundo, el
sistema modificará el estado de la luz; si ha transcurrido menos tiempo, el estado se
mantendrá estable.
En última instancia se establece que, si esta diferencia es mayor o igual a 1000, se le
asigne a millisAnterior el valor de millisActual. De esta manera, la diferencia entre
ambas vuelve a ser cero hasta que vuelva a transcurrir otro segundo.
Para el aula
Este es el concepto central de todo el nivel (y uno de los aprendizajes más transferibles de la guía completa): la diferencia entre esperar deteniendo el mundo y consultar un reloj sin detenerlo. Vale la pena teatralizarlo antes de tocar código: un estudiante “bloqueante” que se queda congelado contando hasta cinco sin poder responder preguntas, contra un estudiante “no bloqueante” que sigue atendiendo consultas y cada tanto mira el reloj de la pared. Cuando el grupo puede predecir cuál de los dos puede, además, “atender el teléfono de internet”, el paso 5 se explica solo.
Verificá
Cargá el programa: el LED tiene que parpadear cada un segundo, exactamente igual que la
versión con delay() del nivel inicial. Que por fuera se vean idénticos y por dentro sean
tan distintos es, justamente, el punto de este paso.
Una máquina de estados para el semáforo
En el parpadeo del paso anterior había dos estados: LOW (bajo) y HIGH (alto). En el
caso del semáforo podemos considerar los siguientes:
- Poner en verde
- Esperando en verde
- Poner en amarillo
- Esperando en amarillo
- Poner en rojo
- Esperando en rojo
- Poner en rojo y amarillo
- Esperando en rojo y amarillo
Esta forma de organizar el programa se llama máquina de
estadosMáquina de estadosForma de organizar un programa donde una variable guarda el estado actual del sistema y, en cada vuelta del loop, solo se ejecuta lo que corresponde a ese estado. El semáforo no bloqueante usa ocho estados que se recorren según el tiempo transcurrido.: una variable guarda en qué estado está el semáforo, y en cada vuelta de
loop() el programa hace solo lo que corresponde a ese estado. Los estados “Poner en…”
encienden las luces y anotan la hora; los estados “Esperando en…” solo consultan millis()
y, cuando pasó el tiempo de esa luz, saltan al estado siguiente. Así queda el semáforo del
nivel inicial quitando el código bloqueante:
Ver el código completo · semaforo-no-bloqueante.ino
// Intersección completa (6 LED) SIN delay(): máquina de estados con millis().
// Misma secuencia de 8 cuadros del nivel intermedio, pero no bloqueante:
// el procesador queda libre en cada vuelta de loop() para otras tareas
// (esta es la base sobre la que el nivel IoT agrega la conexión a internet).
//
// Estado | Semáforo 1 | Semáforo 2 | Duración
// -------+-----------------+-----------------+---------
// 1 | VERDE | ROJO | 5 s
// 2 | AMARILLO | ROJO | 1 s
// 3 | ROJO | ROJO | 1 s (todo rojo)
// 4 | ROJO | ROJO + AMARILLO | 1 s
// 5 | ROJO | VERDE | 5 s
// 6 | ROJO | AMARILLO | 1 s
// 7 | ROJO | ROJO | 1 s (todo rojo)
// 8 | ROJO + AMARILLO | ROJO | 1 s → vuelve al 1
//
// Plataforma: Arduino UNO.
// Conexión: semáforo 1 → verde 11, amarillo 12, rojo 13;
// semáforo 2 → verde 8, amarillo 9, rojo 10.
//
// Basado en: Fragmento de código 5 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) el original aplicaba la máquina de estados a UN solo
// semáforo; acá se extiende a la intersección completa de 6 LED manteniendo
// el estilo (if por estado + millisAnterior). (2) El original separaba cada
// paso en dos estados ("poner" y "esperando"); acá cada estado enciende sus
// luces AL ENTRAR (dentro del if de transición), lo que deja exactamente
// 8 estados: uno por cuadro del cómic de la Fig. 10. (3) unsigned long para
// los tiempos y una función auxiliar aplicarLuces() para escribir las seis
// luces de un cuadro completo de una sola vez.
// Semáforo 1 (calle principal).
const int PIN_SEM1_VERDE = 11;
const int PIN_SEM1_AMARILLO = 12;
const int PIN_SEM1_ROJO = 13;
// Semáforo 2 (calle transversal).
const int PIN_SEM2_VERDE = 8;
const int PIN_SEM2_AMARILLO = 9;
const int PIN_SEM2_ROJO = 10;
// Tiempos de la secuencia (los mismos de toda la guía y del simulador).
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_TODO_ROJO_MS = 1000; // margen de seguridad
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
// Estado actual de la máquina (1 a 8, un estado por cuadro del cómic).
int estado = 1;
// Momento (ms desde el arranque) en que entramos al estado actual.
unsigned long millisAnterior = 0;
// Escribe las seis luces de un cuadro completo. Que cada estado defina
// TODAS las luces (y no solo las que cambian) evita que un cuadro dependa
// de lo que dejó encendido el anterior: cada estado se entiende solo.
// Orden de parámetros: semáforo 1 (verde, amarillo, rojo),
// semáforo 2 (verde, amarillo, rojo).
void aplicarLuces(int sem1Verde, int sem1Amarillo, int sem1Rojo,
int sem2Verde, int sem2Amarillo, int sem2Rojo) {
digitalWrite(PIN_SEM1_VERDE, sem1Verde);
digitalWrite(PIN_SEM1_AMARILLO, sem1Amarillo);
digitalWrite(PIN_SEM1_ROJO, sem1Rojo);
digitalWrite(PIN_SEM2_VERDE, sem2Verde);
digitalWrite(PIN_SEM2_AMARILLO, sem2Amarillo);
digitalWrite(PIN_SEM2_ROJO, sem2Rojo);
}
void setup() {
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
pinMode(PIN_SEM2_VERDE, OUTPUT);
pinMode(PIN_SEM2_AMARILLO, OUTPUT);
pinMode(PIN_SEM2_ROJO, OUTPUT);
// Encendemos las luces del estado inicial (cuadro 1): S1 verde / S2 rojo.
aplicarLuces(HIGH, LOW, LOW, LOW, LOW, HIGH);
}
void loop() {
unsigned long millisActual = millis();
// Cada if pregunta: "¿estoy en el estado N y ya se cumplió su tiempo?"
// Si la respuesta es sí, enciende las luces del estado siguiente,
// guarda el momento del cambio y avanza. Si es no, no hace nada:
// por eso este código es no bloqueante.
// Estado 1 (S1 verde / S2 rojo): tras 5 s pasa al estado 2.
if (estado == 1 && millisActual - millisAnterior >= DURACION_VERDE_MS) {
aplicarLuces(LOW, HIGH, LOW, LOW, LOW, HIGH); // cuadro 2: S1 amarillo / S2 rojo
millisAnterior = millisActual;
estado = 2;
}
// Estado 2 (S1 amarillo / S2 rojo): tras 1 s pasa al estado 3.
if (estado == 2 && millisActual - millisAnterior >= DURACION_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, LOW, HIGH); // cuadro 3: todo rojo
millisAnterior = millisActual;
estado = 3;
}
// Estado 3 (todo rojo): tras 1 s pasa al estado 4.
if (estado == 3 && millisActual - millisAnterior >= DURACION_TODO_ROJO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, HIGH, HIGH); // cuadro 4: S2 rojo+amarillo
millisAnterior = millisActual;
estado = 4;
}
// Estado 4 (S1 rojo / S2 rojo+amarillo): tras 1 s pasa al estado 5.
if (estado == 4 && millisActual - millisAnterior >= DURACION_ROJO_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, HIGH, LOW, LOW); // cuadro 5: S2 verde
millisAnterior = millisActual;
estado = 5;
}
// Estado 5 (S1 rojo / S2 verde): tras 5 s pasa al estado 6.
if (estado == 5 && millisActual - millisAnterior >= DURACION_VERDE_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, HIGH, LOW); // cuadro 6: S2 amarillo
millisAnterior = millisActual;
estado = 6;
}
// Estado 6 (S1 rojo / S2 amarillo): tras 1 s pasa al estado 7.
if (estado == 6 && millisActual - millisAnterior >= DURACION_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, LOW, HIGH); // cuadro 7: todo rojo
millisAnterior = millisActual;
estado = 7;
}
// Estado 7 (todo rojo): tras 1 s pasa al estado 8.
if (estado == 7 && millisActual - millisAnterior >= DURACION_TODO_ROJO_MS) {
aplicarLuces(LOW, HIGH, HIGH, LOW, LOW, HIGH); // cuadro 8: S1 rojo+amarillo
millisAnterior = millisActual;
estado = 8;
}
// Estado 8 (S1 rojo+amarillo / S2 rojo): tras 1 s vuelve al estado 1.
if (estado == 8 && millisActual - millisAnterior >= DURACION_ROJO_AMARILLO_MS) {
aplicarLuces(HIGH, LOW, LOW, LOW, LOW, HIGH); // cuadro 1: S1 verde / S2 rojo
millisAnterior = millisActual;
estado = 1;
}
// En este punto el procesador queda libre para realizar otras tareas.
} // Intersección completa (6 LED) SIN delay(): máquina de estados con millis().
// Misma secuencia de 8 cuadros del nivel intermedio, pero no bloqueante:
// el procesador queda libre en cada vuelta de loop() para otras tareas.
// Este programa es la base directa de semaforo-iot.ino: sin código
// bloqueante, el ESP32 puede mantener el WiFi y publicar por MQTT
// sin que el semáforo se "cuelgue".
//
// Estado | Semáforo 1 | Semáforo 2 | Duración
// -------+-----------------+-----------------+---------
// 1 | VERDE | ROJO | 5 s
// 2 | AMARILLO | ROJO | 1 s
// 3 | ROJO | ROJO | 1 s (todo rojo)
// 4 | ROJO | ROJO + AMARILLO | 1 s
// 5 | ROJO | VERDE | 5 s
// 6 | ROJO | AMARILLO | 1 s
// 7 | ROJO | ROJO | 1 s (todo rojo)
// 8 | ROJO + AMARILLO | ROJO | 1 s → vuelve al 1
//
// Plataforma: ESP32 (DevKit v1).
// Conexión: semáforo 1 → verde GPIO 16, amarillo GPIO 17, rojo GPIO 18;
// semáforo 2 → verde GPIO 19, amarillo GPIO 21, rojo GPIO 22.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO); nunca
// inyectar 5 V en un GPIO.
//
// Basado en: Fragmento de código 5 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) el original aplicaba la máquina de estados a UN solo
// semáforo; acá se extiende a la intersección completa de 6 LED manteniendo
// el estilo (if por estado + millisAnterior). (2) El original separaba cada
// paso en dos estados ("poner" y "esperando"); acá cada estado enciende sus
// luces AL ENTRAR (dentro del if de transición), lo que deja exactamente
// 8 estados: uno por cuadro del cómic de la Fig. 10. (3) unsigned long para
// los tiempos, GPIO seguros de ESP32 y una función auxiliar aplicarLuces()
// para escribir las seis luces de un cuadro completo de una sola vez.
// Semáforo 1 (calle principal).
const int PIN_SEM1_VERDE = 16;
const int PIN_SEM1_AMARILLO = 17;
const int PIN_SEM1_ROJO = 18;
// Semáforo 2 (calle transversal).
const int PIN_SEM2_VERDE = 19;
const int PIN_SEM2_AMARILLO = 21;
const int PIN_SEM2_ROJO = 22;
// Tiempos de la secuencia (los mismos de toda la guía y del simulador).
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_TODO_ROJO_MS = 1000; // margen de seguridad
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
// Estado actual de la máquina (1 a 8, un estado por cuadro del cómic).
int estado = 1;
// Momento (ms desde el arranque) en que entramos al estado actual.
unsigned long millisAnterior = 0;
// Escribe las seis luces de un cuadro completo. Que cada estado defina
// TODAS las luces (y no solo las que cambian) evita que un cuadro dependa
// de lo que dejó encendido el anterior: cada estado se entiende solo.
// Orden de parámetros: semáforo 1 (verde, amarillo, rojo),
// semáforo 2 (verde, amarillo, rojo).
void aplicarLuces(int sem1Verde, int sem1Amarillo, int sem1Rojo,
int sem2Verde, int sem2Amarillo, int sem2Rojo) {
digitalWrite(PIN_SEM1_VERDE, sem1Verde);
digitalWrite(PIN_SEM1_AMARILLO, sem1Amarillo);
digitalWrite(PIN_SEM1_ROJO, sem1Rojo);
digitalWrite(PIN_SEM2_VERDE, sem2Verde);
digitalWrite(PIN_SEM2_AMARILLO, sem2Amarillo);
digitalWrite(PIN_SEM2_ROJO, sem2Rojo);
}
void setup() {
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
pinMode(PIN_SEM2_VERDE, OUTPUT);
pinMode(PIN_SEM2_AMARILLO, OUTPUT);
pinMode(PIN_SEM2_ROJO, OUTPUT);
// Encendemos las luces del estado inicial (cuadro 1): S1 verde / S2 rojo.
aplicarLuces(HIGH, LOW, LOW, LOW, LOW, HIGH);
}
void loop() {
unsigned long millisActual = millis();
// Cada if pregunta: "¿estoy en el estado N y ya se cumplió su tiempo?"
// Si la respuesta es sí, enciende las luces del estado siguiente,
// guarda el momento del cambio y avanza. Si es no, no hace nada:
// por eso este código es no bloqueante.
// Estado 1 (S1 verde / S2 rojo): tras 5 s pasa al estado 2.
if (estado == 1 && millisActual - millisAnterior >= DURACION_VERDE_MS) {
aplicarLuces(LOW, HIGH, LOW, LOW, LOW, HIGH); // cuadro 2: S1 amarillo / S2 rojo
millisAnterior = millisActual;
estado = 2;
}
// Estado 2 (S1 amarillo / S2 rojo): tras 1 s pasa al estado 3.
if (estado == 2 && millisActual - millisAnterior >= DURACION_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, LOW, HIGH); // cuadro 3: todo rojo
millisAnterior = millisActual;
estado = 3;
}
// Estado 3 (todo rojo): tras 1 s pasa al estado 4.
if (estado == 3 && millisActual - millisAnterior >= DURACION_TODO_ROJO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, HIGH, HIGH); // cuadro 4: S2 rojo+amarillo
millisAnterior = millisActual;
estado = 4;
}
// Estado 4 (S1 rojo / S2 rojo+amarillo): tras 1 s pasa al estado 5.
if (estado == 4 && millisActual - millisAnterior >= DURACION_ROJO_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, HIGH, LOW, LOW); // cuadro 5: S2 verde
millisAnterior = millisActual;
estado = 5;
}
// Estado 5 (S1 rojo / S2 verde): tras 5 s pasa al estado 6.
if (estado == 5 && millisActual - millisAnterior >= DURACION_VERDE_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, HIGH, LOW); // cuadro 6: S2 amarillo
millisAnterior = millisActual;
estado = 6;
}
// Estado 6 (S1 rojo / S2 amarillo): tras 1 s pasa al estado 7.
if (estado == 6 && millisActual - millisAnterior >= DURACION_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, LOW, HIGH); // cuadro 7: todo rojo
millisAnterior = millisActual;
estado = 7;
}
// Estado 7 (todo rojo): tras 1 s pasa al estado 8.
if (estado == 7 && millisActual - millisAnterior >= DURACION_TODO_ROJO_MS) {
aplicarLuces(LOW, HIGH, HIGH, LOW, LOW, HIGH); // cuadro 8: S1 rojo+amarillo
millisAnterior = millisActual;
estado = 8;
}
// Estado 8 (S1 rojo+amarillo / S2 rojo): tras 1 s vuelve al estado 1.
if (estado == 8 && millisActual - millisAnterior >= DURACION_ROJO_AMARILLO_MS) {
aplicarLuces(HIGH, LOW, LOW, LOW, LOW, HIGH); // cuadro 1: S1 verde / S2 rojo
millisAnterior = millisActual;
estado = 1;
}
// En este punto el procesador queda libre para realizar otras tareas
// (en semaforo-iot.ino, acá se atiende la conexión WiFi/MQTT).
} Teniendo un programa de funcionamiento de semáforos con un código no bloqueante (es decir,
sin usar la función delay()) estamos en condiciones de incorporar IoT a nuestro proyecto.
Verificá
El semáforo tiene que comportarse exactamente igual que en el nivel inicial: verde 5 s, amarillo 1 s, rojo 5 s, rojo + amarillo 1 s, en ciclo. Si alguna luz queda “pegada”, revisá que cada estado “Poner en…” cambie al estado de espera correspondiente y que cada espera compare contra el tiempo correcto.
Crear un panel de control en Adafruit IO
Ahora vamos a preparar la “central de monitoreo”. Usaremos Adafruit IOAdafruit IOPlataforma online de uso gratuito, orientada al uso educativo, que centraliza y gestiona la información que reportan los dispositivos IoT. Permite crear feeds y paneles de control para monitorear los datos desde cualquier dispositivo., una plataforma online con posibilidad de uso gratuito, orientada al uso educativo, que se encarga de centralizar la información que reportan los dispositivos. Primero debemos crear una cuenta de usuario en io.adafruit.com.
Una vez que ingresamos con nuestro usuario, el trabajo tiene dos partes: crear los feeds donde se publicarán los datos y armar el panel que los muestre.
Un feedFeedFuente de datos en la que se puede publicar y a la que uno puede suscribirse. Permite enviar datos para que se almacenen en el tiempo, y también leerlos recibiendo las actualizaciones de quienes publican allí. es una fuente de datos en la que uno puede publicar, así
como también se puede suscribir para recibir los datos de cierto feed. Desde la sección
Feeds, creamos un feed llamado semaforo1: en él publicaremos, desde nuestro
dispositivo, la información sobre el estado del primer semáforo (si más adelante sumás el
segundo semáforo, su feed será semaforo2).
Este feed tendrá el estado del semáforo expresado con la siguiente convención:
- Cuando el valor sea 1, estará en verde.
- Cuando el valor sea 2, estará en amarillo.
- Cuando el valor sea 3, estará en rojo.
Durante la fase de rojo y amarillo (el cuadro previo al verde) también se publica
3: la luz roja sigue encendida, así el panel refleja siempre un estado real.
(La guía original publicaba un cuarto valor, 4, que su propio panel nunca mostraba;
lo simplificamos — el desvío está documentado en la ficha del proyecto.)
Luego, desde la sección Dashboards, creamos un nuevo panel con el nombre y la descripción que quieras. Dentro del panel, agregamos bloques del tipo Indicator (indicador): son luces virtuales que se encienden cuando se cumple una condición sobre un feed. Para nuestro semáforo, podemos ubicar tres indicadores con diferentes colores, uno abajo del otro, como una columna de semáforo virtual:
- El indicador rojo se asocia al feed
semaforo1con la condición “igual a 3”: estará encendido (rojo) cuando el feed valga 3, y apagado (gris) cuando valga otra cosa. - El indicador amarillo, con la condición “igual a 2”.
- El indicador verde, con la condición “igual a 1”.
Cada bloque permite elegir un título, un color para el estado activado y otro para el desactivado, y luego se puede ajustar el tamaño y la ubicación de los bloques dentro del panel. Al finalizar, la columna de indicadores replicará a distancia el estado del semáforo real.
Por último, necesitamos las credenciales que le permitirán a nuestro dispositivo publicar en la cuenta: en Adafruit IO, tocá el ícono de la llave (API Key). Vas a ver dos datos, el nombre de usuario (Username) y la clave (Active Key): anotalos, porque los vamos a copiar dentro del código en el paso siguiente. La clave es secreta: cualquiera que la tenga puede publicar en tus feeds.
Verificá
Para probar el panel sin la placa, entrá al feed semaforo1 y agregale un valor a mano
(Add Data): con 3, el indicador rojo del panel tiene que encenderse; con 1, el
verde. Si los indicadores no reaccionan, revisá la condición de cada bloque y que estén
asociados al feed correcto.
Programación IoT
Con el panel listo y el código ya libre de bloqueos, falta el último eslabón: que el semáforo publique su estado cada vez que cambia de luz.
Esta parte práctica corresponde a la variante ESP32: el Arduino UNO clásico no tiene WiFi (el original lo resolvía con el módulo OBLOQ, hoy discontinuado). Cambiá la plataforma a ESP32 con el selector para ver el código y las instrucciones de conexión. Todo lo conceptual —cuándo publicar, la convención de valores, el porqué del código no bloqueante— ya lo tenés resuelto con tu UNO en los pasos anteriores.
El programa final integra tres cosas que ya conocés: la máquina de estados del paso 3, la conexión WiFi nativa del ESP32 y la publicación en Adafruit IO (mediante el protocolo MQTT, usando la biblioteca Adafruit MQTT Library, que se instala desde el gestor de bibliotecas del Arduino IDE junto con sus dependencias).
Antes de subirlo, tenés que completar cuatro datos al comienzo del código: el nombre (SSID) y la contraseña de tu red WiFi, y el usuario y la clave que copiaste de Adafruit IO en el paso anterior.
La publicación reutiliza la máquina de estados: en cada estado “Poner en…”, además de
encender las luces, el programa publica en el feed semaforo1 el valor de la convención
(1 = verde, 2 = amarillo, 3 = rojo; en rojo y amarillo también 3, como vimos en el
paso 4). Y en cada vuelta de loop() se
llama a la función de actualización de la biblioteca, que mantiene viva la conexión con el
servidor. Por esto es importante que nuestro código no sea bloqueante: si el programa
quedara colgado en un delay(), la conexión no podría atenderse y las publicaciones se
perderían.
Ver el código completo · semaforo-iot.ino
Nota: Este código está disponible para ESP32. Cambiá la plataforma con el selector del encabezado para verlo y practicarlo.
// Intersección completa (6 LED) con monitoreo IoT vía Adafruit IO (MQTT).
// La máquina de estados de semaforo-no-bloqueante.ino sigue manejando las
// luces; además, cada vez que un semáforo cambia de color, se publica su
// nuevo estado en un "feed" de Adafruit IO para verlo desde el Panel de
// Control (la "central de monitoreo") en cualquier dispositivo con internet.
//
// Convención de códigos publicados: 1 = verde, 2 = amarillo, 3 = rojo.
// Durante rojo + amarillo también se publica 3 (ver constantes más abajo).
// Feeds: "semaforo1" (calle principal) y "semaforo2" (calle transversal).
//
// Plataforma: ESP32 (DevKit v1) — usa su WiFi integrado.
// Conexión: semáforo 1 → verde GPIO 16, amarillo GPIO 17, rojo GPIO 18;
// semáforo 2 → verde GPIO 19, amarillo GPIO 21, rojo GPIO 22.
// Nota eléctrica: el ESP32 trabaja a 3,3 V (no 5 V como el UNO); nunca
// inyectar 5 V en un GPIO.
//
// Librería requerida (Library Manager del IDE de Arduino):
// "Adafruit MQTT Library" (de Adafruit). Se eligió porque cada feed se
// representa como un objeto y publish() acepta números directamente,
// igual que el olq.publish() del original; con PubSubClient habría que
// armar a mano los topics y convertir los números a texto.
//
// Basado en: Fragmento de código 6 — Sistema de Semaforización,
// Saberes Digitales (INET/EDUCAR 2019)
// Modernización: (1) el módulo OBLOQ y la librería ObloqAdafruit quedaron
// discontinuados; se reemplazan por el WiFi nativo del ESP32 (WiFi.h) y un
// cliente MQTT estándar contra io.adafruit.com. (2) La máquina de estados
// se extiende del semáforo único del original a la intersección completa
// de 6 LED con dos feeds. (3) Se publica SOLO cuando un semáforo cambia de
// color: publicar en cada vuelta de loop() saturaría el límite gratuito de
// Adafruit IO (30 publicaciones por minuto; este ciclo genera 8 cada 16 s,
// justo al límite — si Adafruit avisa "throttled", alargá las duraciones).
#include <WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
// ---------------------------------------------------------------------------
// Credenciales: reemplazar los cuatro valores por los propios.
// El usuario y la key de Adafruit se copian desde io.adafruit.com
// haciendo clic en el ícono de la "llave" (igual que en la guía original).
// ---------------------------------------------------------------------------
#define WIFI_SSID "TU_SSID"
#define WIFI_PASSWORD "TU_PASSWORD"
#define IO_USERNAME "TU_USUARIO_ADAFRUIT"
#define IO_KEY "TU_KEY_ADAFRUIT"
// Servidor MQTT de Adafruit IO.
const char SERVIDOR_MQTT[] = "io.adafruit.com";
const int PUERTO_MQTT = 1883;
// Semáforo 1 (calle principal).
const int PIN_SEM1_VERDE = 16;
const int PIN_SEM1_AMARILLO = 17;
const int PIN_SEM1_ROJO = 18;
// Semáforo 2 (calle transversal).
const int PIN_SEM2_VERDE = 19;
const int PIN_SEM2_AMARILLO = 21;
const int PIN_SEM2_ROJO = 22;
// Tiempos de la secuencia (los mismos de toda la guía y del simulador).
const unsigned long DURACION_VERDE_MS = 5000;
const unsigned long DURACION_AMARILLO_MS = 1000;
const unsigned long DURACION_TODO_ROJO_MS = 1000; // margen de seguridad
const unsigned long DURACION_ROJO_AMARILLO_MS = 1000;
// Si se cae la conexión MQTT, reintentamos cada tanto SIN bloquear:
// el semáforo debe seguir funcionando aunque no haya internet.
const unsigned long INTERVALO_REINTENTO_MQTT_MS = 5000;
// Códigos de estado que se publican (convención del original: 1/2/3).
// Modernización: el original publicaba 4 en rojo+amarillo, pero su panel
// solo mapeaba 1/2/3 y quedaba gris. Como el rojo sigue encendido en esa
// fase, publicamos 3 y el panel refleja siempre un estado real.
const int CODIGO_VERDE = 1;
const int CODIGO_AMARILLO = 2;
const int CODIGO_ROJO = 3;
const int CODIGO_ROJO_AMARILLO = CODIGO_ROJO;
// Cliente MQTT sobre el WiFi del ESP32, y un objeto por feed.
WiFiClient clienteWifi;
Adafruit_MQTT_Client mqtt(&clienteWifi, SERVIDOR_MQTT, PUERTO_MQTT,
IO_USERNAME, IO_KEY);
Adafruit_MQTT_Publish feedSemaforo1(&mqtt, IO_USERNAME "/feeds/semaforo1");
Adafruit_MQTT_Publish feedSemaforo2(&mqtt, IO_USERNAME "/feeds/semaforo2");
// Estado actual de la máquina (1 a 8, un estado por cuadro del cómic).
int estado = 1;
// Momento (ms desde el arranque) en que entramos al estado actual.
unsigned long millisAnterior = 0;
// Momento del último intento de conexión MQTT.
unsigned long millisUltimoIntentoMqtt = 0;
// Color actual de cada semáforo y último color que llegó a publicarse.
// Separarlos permite publicar solo los CAMBIOS y, si la conexión estaba
// caída durante un cambio, ponerse al día apenas vuelva.
int codigoSem1 = CODIGO_VERDE;
int codigoSem2 = CODIGO_ROJO;
int codigoSem1Publicado = 0; // 0 = todavía no se publicó nada
int codigoSem2Publicado = 0;
// Escribe las seis luces de un cuadro completo (ver semaforo-no-bloqueante.ino).
// Orden: semáforo 1 (verde, amarillo, rojo), semáforo 2 (verde, amarillo, rojo).
void aplicarLuces(int sem1Verde, int sem1Amarillo, int sem1Rojo,
int sem2Verde, int sem2Amarillo, int sem2Rojo) {
digitalWrite(PIN_SEM1_VERDE, sem1Verde);
digitalWrite(PIN_SEM1_AMARILLO, sem1Amarillo);
digitalWrite(PIN_SEM1_ROJO, sem1Rojo);
digitalWrite(PIN_SEM2_VERDE, sem2Verde);
digitalWrite(PIN_SEM2_AMARILLO, sem2Amarillo);
digitalWrite(PIN_SEM2_ROJO, sem2Rojo);
}
// Mantiene viva la conexión MQTT sin bloquear el semáforo: si está caída,
// intenta reconectar como mucho una vez cada INTERVALO_REINTENTO_MQTT_MS.
void mantenerConexionMqtt() {
if (mqtt.connected()) {
return;
}
unsigned long millisActual = millis();
if (millisActual - millisUltimoIntentoMqtt < INTERVALO_REINTENTO_MQTT_MS) {
return; // todavía no toca reintentar
}
millisUltimoIntentoMqtt = millisActual;
Serial.print("Conectando a Adafruit IO... ");
int8_t resultado = mqtt.connect(); // 0 = conexión exitosa
if (resultado == 0) {
Serial.println("conectado.");
} else {
Serial.println(mqtt.connectErrorString(resultado));
mqtt.disconnect(); // deja el socket limpio para el próximo intento
}
}
// Publica en cada feed SOLO si el color cambió desde la última publicación
// exitosa. Si publish() falla (o no hay conexión), no actualizamos el
// registro: se reintentará en la próxima vuelta de loop().
void publicarCambios() {
if (!mqtt.connected()) {
return; // sin internet el semáforo sigue; la central se actualiza al volver
}
if (codigoSem1 != codigoSem1Publicado) {
if (feedSemaforo1.publish((int32_t)codigoSem1)) {
codigoSem1Publicado = codigoSem1;
}
}
if (codigoSem2 != codigoSem2Publicado) {
if (feedSemaforo2.publish((int32_t)codigoSem2)) {
codigoSem2Publicado = codigoSem2;
}
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_SEM1_VERDE, OUTPUT);
pinMode(PIN_SEM1_AMARILLO, OUTPUT);
pinMode(PIN_SEM1_ROJO, OUTPUT);
pinMode(PIN_SEM2_VERDE, OUTPUT);
pinMode(PIN_SEM2_AMARILLO, OUTPUT);
pinMode(PIN_SEM2_ROJO, OUTPUT);
// Encendemos las luces del estado inicial (cuadro 1): S1 verde / S2 rojo.
aplicarLuces(HIGH, LOW, LOW, LOW, LOW, HIGH);
// Conexión WiFi. Esperar acá con delay() es aceptable porque el semáforo
// todavía no arrancó su ciclo; una vez dentro de loop(), nada bloquea.
Serial.print("Conectando a la red WiFi");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi conectado, IP: ");
Serial.println(WiFi.localIP());
}
void loop() {
unsigned long millisActual = millis();
// --- Máquina de estados de la intersección (8 cuadros del cómic) ---
// Cada transición enciende las luces del estado siguiente y anota qué
// color muestra ahora cada semáforo; la publicación se hace más abajo.
// Estado 1 (S1 verde / S2 rojo): tras 5 s pasa al estado 2.
if (estado == 1 && millisActual - millisAnterior >= DURACION_VERDE_MS) {
aplicarLuces(LOW, HIGH, LOW, LOW, LOW, HIGH); // cuadro 2: S1 amarillo / S2 rojo
codigoSem1 = CODIGO_AMARILLO;
codigoSem2 = CODIGO_ROJO;
millisAnterior = millisActual;
estado = 2;
}
// Estado 2 (S1 amarillo / S2 rojo): tras 1 s pasa al estado 3.
if (estado == 2 && millisActual - millisAnterior >= DURACION_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, LOW, HIGH); // cuadro 3: todo rojo
codigoSem1 = CODIGO_ROJO;
codigoSem2 = CODIGO_ROJO;
millisAnterior = millisActual;
estado = 3;
}
// Estado 3 (todo rojo): tras 1 s pasa al estado 4.
if (estado == 3 && millisActual - millisAnterior >= DURACION_TODO_ROJO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, HIGH, HIGH); // cuadro 4: S2 rojo+amarillo
codigoSem1 = CODIGO_ROJO;
codigoSem2 = CODIGO_ROJO_AMARILLO;
millisAnterior = millisActual;
estado = 4;
}
// Estado 4 (S1 rojo / S2 rojo+amarillo): tras 1 s pasa al estado 5.
if (estado == 4 && millisActual - millisAnterior >= DURACION_ROJO_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, HIGH, LOW, LOW); // cuadro 5: S2 verde
codigoSem1 = CODIGO_ROJO;
codigoSem2 = CODIGO_VERDE;
millisAnterior = millisActual;
estado = 5;
}
// Estado 5 (S1 rojo / S2 verde): tras 5 s pasa al estado 6.
if (estado == 5 && millisActual - millisAnterior >= DURACION_VERDE_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, HIGH, LOW); // cuadro 6: S2 amarillo
codigoSem1 = CODIGO_ROJO;
codigoSem2 = CODIGO_AMARILLO;
millisAnterior = millisActual;
estado = 6;
}
// Estado 6 (S1 rojo / S2 amarillo): tras 1 s pasa al estado 7.
if (estado == 6 && millisActual - millisAnterior >= DURACION_AMARILLO_MS) {
aplicarLuces(LOW, LOW, HIGH, LOW, LOW, HIGH); // cuadro 7: todo rojo
codigoSem1 = CODIGO_ROJO;
codigoSem2 = CODIGO_ROJO;
millisAnterior = millisActual;
estado = 7;
}
// Estado 7 (todo rojo): tras 1 s pasa al estado 8.
if (estado == 7 && millisActual - millisAnterior >= DURACION_TODO_ROJO_MS) {
aplicarLuces(LOW, HIGH, HIGH, LOW, LOW, HIGH); // cuadro 8: S1 rojo+amarillo
codigoSem1 = CODIGO_ROJO_AMARILLO;
codigoSem2 = CODIGO_ROJO;
millisAnterior = millisActual;
estado = 8;
}
// Estado 8 (S1 rojo+amarillo / S2 rojo): tras 1 s vuelve al estado 1.
if (estado == 8 && millisActual - millisAnterior >= DURACION_ROJO_AMARILLO_MS) {
aplicarLuces(HIGH, LOW, LOW, LOW, LOW, HIGH); // cuadro 1: S1 verde / S2 rojo
codigoSem1 = CODIGO_VERDE;
codigoSem2 = CODIGO_ROJO;
millisAnterior = millisActual;
estado = 1;
}
// --- Parte IoT ---
// Gracias a que la máquina de estados no bloquea, estas dos tareas se
// atienden en CADA vuelta de loop(): esta es la razón de fondo por la
// que la guía enseña a abandonar delay() antes de llegar a IoT.
mantenerConexionMqtt();
publicarCambios();
} Así queda funcionando nuestra central de monitoreo en miniatura. Podés ver el sistema completo —la intersección más el panel de la central reflejando cada publicación— en el simulador:
Simulación del comportamiento
La misma máquina de estados del firmware, portada a TypeScript, maneja el
comportamiento del dispositivo. No se ejecuta el binario real: la ejecución
del .hex con avr8js está en el roadmap.
Simulación lista. Presioná «Iniciar» para arrancar la secuencia.
La central completa
Para simplificar el proceso, implementamos IoT para un solo semáforo. Extendé el sistema
para abarcar los dos semáforos de la intersección: creá el feed semaforo2, duplicá la
columna de indicadores en el panel y publicá el estado del segundo semáforo en cada cambio
de luz. Pregunta para discutir en grupo: ¿hace falta publicar los dos, o el estado de uno
alcanza para deducir el del otro?
Verificá
Con el programa cargado y la placa conectada a tu red WiFi, abrí el panel de Adafruit IO en el celular o la computadora: los indicadores tienen que ir siguiendo al semáforo real (verde, amarillo, rojo) con cada cambio de luz, con una pequeña demora de red. Si no llegan datos, revisá las credenciales y el nombre de la red; si llegan pero los indicadores no se encienden, revisá la convención de valores en las condiciones de los bloques.
Cierre del nivel
Al terminar este nivel, tu semáforo dejó de ser solo un circuito: es un dispositivo IoT.
El programa funciona con millis() y una máquina de estados, sin ningún delay() que lo
cuelgue; sabés explicar por qué eso es imprescindible cuando hay que atender internet en
simultáneo; y la central de monitoreo —tu panel de Adafruit IO— muestra desde cualquier
dispositivo el estado del semáforo en cada cambio de luz. Con esto queda completo el
recorrido del proyecto. Si el grupo quiere seguir, en la ficha del proyecto están las
extensiones sugeridas: semáforo peatonal, señal sonora accesible y pulsador de cruce
manejado desde el circuito o desde IoT.
Evidencia de cierre
Al terminar este nivel, esto tiene que estar funcionando:
- El semáforo funciona con código no bloqueante basado en millis() y una máquina de estados: no queda ningún delay() en el programa.
- Podés explicar con tus palabras por qué el código bloqueante es un problema cuando el dispositivo tiene que atender internet en simultáneo.
- El panel de control en Adafruit IO refleja en tiempo casi real el estado del semáforo (verde, amarillo o rojo) publicado por el dispositivo (variante ESP32).