El juego conocido como «Quiz» que consiste en unos pulsadores de colores que serán activados por los participantes para dar una respuesta a una pregunta dada en el menor tiempo posible.
Lo usaremos en clase como herramienta de evaluación, autoevaluación y entretenimiento, realizando preguntas breves y dinámicas, que nos servirá para medir conocimientos, habilidades o competencias sobre un tema específico.
La comunicación entre los botones será inalámbrica (WIFI) usando los ESP8266 y ESP-01, serán alimentados por una batería de litio recargable (18650) y podremos añadir tantos pulsadores como deseemos, en el proyecto emplearemos cinco.
Materiales por cada pulsador
Materiales para centralita de control
Esquema eléctrico de los pulsadores
Vamos a usar GP2 de entrada para el pulsador en pull up, eso nos evitará usar una resistencia y el GP0 (que en LOW activa el modo programación al arrancar) para activar el led del pulsador.
La activación del led será a través de un Mosfet de canal P para conseguir la tención de la entrada sin pasar por el regulador de 3.3V, esto hará que el led luzca con la potencia adecuada. Añadiremos una resistencia de 10k de VCC a GP0 para garantizar que no arranca en modo programación.
Nota: Empecé queriendo usar un transistor 2N2222 por simpleza, pero al estar su base en LOW al arrancar, el ESP-01 se ponía en modo programación y no ejecutaba ningún programa a sí que mi solución fue la del Mosfet canal P, ahora tan solo se invierte la lógica en la programación.

La alimentación la recularemos con un LM1117T para administrar los 3.3V estables con los que funciona el ESP-01. Los condensadores darán mayor estabilidad.
Un pequeño led nos dará la información de que el sistema está alimentado al pulsar el interruptor.

La batería de litio 18650 estará conectada al cargador tipo USB C (o mini depende del gusto) TP4056, que con sus dos led indicadores (rojo-azul) nos informará de la carga de la pila que estado encendido tiene una duración de alrededor de 2 días.

El ESP-01
Barato, bajo consumo y de pequeño tamaño, ideal para este proyecto.
El mayor problema del ESP-01 es que debe ser alimentado a 3.3V y que para programarlo para que trabaje de forma autónoma hay que arrancarlo con el GPIO0 conectado a GND, esto es un engorro pues una vez programado hay que volver a desconectar para que se ejecute el programa con normalidad. La chapuza es clara como se puede apreciar en la siguiente fotografía. Con maña y un pequeño cablecito se puede crear el puente necesario entre estos pines para quitarlo cuando convenga.

Como se ve en la imagen, he empleado un programador TTL conectado a una placa «ESP-01 Adapter» que permite alimentarlo a 3.3V.
Las conexiones son las típicas en este caso.
- TX a RX
- RX a TX
- Y la alimentación de USB de 5V será regulada por la placa adaptadora.
Luego en el IDE de Arduino elegir una placa «Generic ESP 8266«. Y si todo sale bien visualizaremos algo parecido a esto.

Una vez enviado el programa, recuerda retirar el puente de GPIO0 a GND y reiniciar el módulo para que funcione.
Aquí te presento los pines de conexión del ESP-01.

Un punto a tener en cuenta sobre todo a la hora de realizar la PCB es que para que el ESP-01 entre en modo operacional y encienda, debemos poner en HIGH el pin CH_PD (6), ya que en la PCB final no usaremos el adaptador que hemos usado para programar que trae incorporada esta conexión a HIGH (ver esquema de conexiones).
Lo primero que debemos hacer con los ESP-01 que irán en los pulsadores es obtener sus direcciones MAC, pues deben ser reconocidos por la centralita que será comandada por un ESP8266 y a la hora de programar esta última, deberemos introducir dichas direcciones. Este punto lo trataremos más adelante en el apartado de programación de la centralita.
Para ello es necesario programar los ESP-01 con el siguiente programa.
Código para encontrar la MAC de los ESP-01
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ifdef ESP32 #include <WiFi.h> // SI ES UNA ESP32 ENTONCES UTILIZAMOS ESTA LIBRERIA #else #include <ESP8266WiFi.h> // SI ES UNA ESP8266 UTILIZAMOS ESTA LIBRERIA #endif void setup(){ Serial.begin(115200); } void loop(){ Serial.print("MAC Address de nuestra microcontroladora: "); Serial.println(WiFi.macAddress()); } |
Obtendremos por el serial (115200) las direcciones MAC de tanto los ESP-01 como los de la ESP8266.
Este programa de obtención de las MAC debe usarse también para averiguar la dirección MAC del ESP8266 que irá en la centralita, pues a la hora de programar los pulsadores la necesitaremos.
En definitiva te recomiendo que apuntes todas las MAC en algún lugar para emplearlas más tarde en las propias programaciones.

Esquema eléctrico de la centralita
Simpleza máxima es la que emplearemos.
Un pulsador para resetear los módulos de pulsación por WIFI conectado al pin D2 (GPOI4 en el ESP8266) en PullUp, y cuatro tiras de leds WS2812b de 8 leds cada una con paralelo en el pin D7 (GPIO13) mediante una resistencia de 470omh.
Estos leds indicarán el color del módulo pulsador ganador. Hay que tener en cuenta que el brillo de los 32 leds no puede ser demasiado intenso pues la batería no suministra demasiada corriente y podemos provocar el reinicio del sistema por saturación, así que mejor no superar la intensidad que provoca la interrupción y podemos apagar algunos leds, por lo menos en colores que suponen mezclas como por ejemplo del amarillo o el blanco. (Se puede jugar con este valor en la programación).
Recordar que el ESP2866 trae su propio regulador de tensión a 3.3V así que no añadiremos más electrónica en la PCB. Sin embargo los leds WS2812b deben conectarse a una tensión de 5V por eso emplearemos un Step UP.

Código de los pulsadores emisores
Recuerda que hay que cambiar en la cabecera del programa la línea de broadcastAddress[] (en la línea 8) que debe ser la del ESP8266 con el que queremos mantener la comunicación.
Expresaremos los 6 bytes de las MAC en hexadecimal, de la forma «0x00» con cada uno de sus 6 valores.
Además, debemos asignar en la variable buttonNumber (en la línea 16) el número del botón que estemos programando (del 1 al 5 en este caso, dependiendo del color).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
#include <ESP8266WiFi.h> #include <espnow.h> #define Led_Pin 0 #define Button_Pin 2 //Tenemos lógica invertida // Poner la mac del receptor uint8_t broadcastAddress[] = { 0x48, 0x3F, 0xDA, 0x7F, 0x48, 0xCE }; int state = 0; // CAMBIAR EL NÚMERO PARA IDENTIFICAR EL PULSADOR // Azul button = 1 // rojo button = 2 // verde button = 3 // amarillo button = 4 // Blanco Button=5 int buttonNumber = 5; int incomingState; // Variable to store if sending data was successful String success; //Structure to send data //Must match the receiver structure typedef struct struct_message { int value; } struct_message; // Create a struct_message for send data struct_message ValueSent; // Create a struct_message to hold incoming data struct_message incomingValue; // ESPNOW function called when you want to send data void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { // Serial.print("Last Packet Send Status: "); if (sendStatus == 0) { // Serial.println("Delivery success"); // } else { // Serial.println("Delivery fail"); } } // ESPNOW function called when you receive data void OnDataRecv(uint8_t *mac, uint8_t *incomingData, uint8_t len) { memcpy(&incomingValue, incomingData, sizeof(incomingValue)); incomingState = incomingValue.value; state = incomingState; } void blink(int i) { while (i > 0) { digitalWrite(Led_Pin, LOW); delay(200); digitalWrite(Led_Pin, HIGH); delay(200); --i; } } void setup() { //Serial.begin(115200); pinMode(Button_Pin, INPUT_PULLUP); pinMode(Led_Pin, OUTPUT); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); WiFi.disconnect(); // Init ESP-NOW if (esp_now_init() != 0) { // Serial.println("Error initializing ESP-NOW"); return; } // Set ESP-NOW Role esp_now_set_self_role(ESP_NOW_ROLE_COMBO); // Once ESPNow is successfully Init, we will register for Send CB to // get the status of Trasnmitted packet esp_now_register_send_cb(OnDataSent); // Register peer esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_COMBO, 1, NULL, 0); // Register for a callback function that will be called when data is received esp_now_register_recv_cb(OnDataRecv); //Call blink after first setup blink(4); state = 0; } void loop() { //Check for the state(current step) and perform the required action //Estado 0 :Espera a ser pulsado y manda el número del pulsador al receptor. //Estado 1 :Parpadea 5 veces y se pone en estado=0 //Estado 2 :Parpadea 2 veces y se queda en Estado=4 (encendido y bloqueado) //Estado 3 :No se usará, lo puse para ir descartando perdedores apaga el led. //Estado 4 :Enciende el led if (state == 0) { if (digitalRead(Button_Pin) == 0) { ValueSent.value = buttonNumber; // Send message via ESP-NOW esp_now_send(broadcastAddress, (uint8_t *)&ValueSent, sizeof(ValueSent)); delay(50); } } if (state == 1) { blink(5); state = 0; } if (state == 2) { blink(2); state = 4; } if (state == 3) { digitalWrite(Led_Pin, HIGH); } if (state == 4) { digitalWrite(Led_Pin, LOW); } } |
Repetir el mismo código cambiando las variables mencionadas en cada uno de los botones que usarás.

Código de la Centralita
La programación de la placa ESP8266 es muy distinta a la de una placa ESP32, así que cuidado con las librerías empleadas y sobre todo con la librería ESP_now.h y la Espnow.h que suelen confundirse (por el guion).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
#include <ESP8266WiFi.h> #include <espnow.h> #include <Adafruit_NeoPixel.h> #define LED_PIN D7 #define NUMPIXELS 8 Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); byte maximo = 50; byte brillo; #define resetButton 4 unsigned long lastPressTime = 0; const unsigned long debounceDelay = 2000; // --- MACs de los botones. //Debes cambiar esto y poner os tuyos propios. //Recuerda que hay un programa en la web para averiguarlos. uint8_t broadcastAddress1[] = { 0xCC, 0x50, 0xE3, 0x08, 0xDA, 0x1C }; // Azul uint8_t broadcastAddress2[] = { 0x2C, 0x3A, 0xE8, 0x12, 0x63, 0x8E }; // Rojo uint8_t broadcastAddress3[] = { 0xA0, 0x20, 0xA6, 0x2A, 0x18, 0xBE }; // Verde uint8_t broadcastAddress4[] = { 0xCC, 0x50, 0xE3, 0x2B, 0xD4, 0xDA }; // Amarillo uint8_t broadcastAddress5[] = { 0x2C, 0xF4, 0x32, 0x31, 0x04, 0x9C }; // Blanco // --- Estructura de mensaje (igual en emisor/receptor) --- typedef struct { int value; } struct_message; struct_message sendToButton; struct_message incomingButton; // --- Canal (todos los dispositivos deben usar el MISMO) --- const uint8_t CHANNEL = 1; // --- Flags para procesar FUERA del callback --- volatile bool rxFlag = false; volatile int rxValue = 0; void checkAndSend(int i); void blink(int i); // Añade el peer si no existe (sin struct peer) bool ensurePeer(uint8_t *mac) { if (!mac) return false; // Evitar direcciones vacías bool empty = true; for (int i = 0; i < 6; i++) if (mac[i] != 0x00) { empty = false; break; } if (empty) return false; if (esp_now_is_peer_exist(mac)) return true; // add_peer(mac, role, channel, key, keyLen) → 0 = OK uint8_t *key = NULL; uint8_t keyLen = 0; int r = esp_now_add_peer(mac, ESP_NOW_ROLE_COMBO, CHANNEL, key, keyLen); return (r == 0); } // === CALLBACKS (ligeros, sin delay/Serial/tone/etc.) === void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { // 0 = OK, !=0 fallo (no bloquear aquí) } void OnDataRecv(uint8_t *mac, uint8_t *data, uint8_t len) { if (len != sizeof(struct_message)) return; struct_message tmp; memcpy(&tmp, data, sizeof(tmp)); // Antirrebote básico aquí: solo marcar flag si pasó el tiempo unsigned long now = millis(); if (now - lastPressTime < debounceDelay) return; rxValue = tmp.value; rxFlag = true; lastPressTime = now; } void setup() { pixels.begin(); pixels.setBrightness(maximo); pixels.clear(); pixels.show(); pinMode(resetButton, INPUT_PULLUP); WiFi.mode(WIFI_STA); WiFi.disconnect(); if (esp_now_init() != 0) { ESP.restart(); } esp_now_set_self_role(ESP_NOW_ROLE_COMBO); esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); ensurePeer(broadcastAddress1); ensurePeer(broadcastAddress2); ensurePeer(broadcastAddress3); ensurePeer(broadcastAddress4); ensurePeer(broadcastAddress5); resetAll(); } void loop() { if (rxFlag) { rxFlag = false; checkAndSend(rxValue); } // Reset if (digitalRead(resetButton) == LOW) { dale(); delay(400); } } void blink(int x, int rojo, int verde, int azul) { brillo = maximo; pixels.setBrightness(brillo); while (x-- > 0) { for (int i = 0; i < NUMPIXELS; i++) { pixels.setPixelColor(i, pixels.Color(rojo, verde, azul)); pixels.show(); delay(12); pixels.setPixelColor(i, pixels.Color(0, 0, 0)); pixels.show(); delay(12); } } for (int i = 0; i < NUMPIXELS; i++) { pixels.setPixelColor(i, pixels.Color(rojo, verde, azul)); pixels.show(); } } // --- Envíos según botón que “ganó” --- bool sendTo(uint8_t *mac, int v) { if (!ensurePeer(mac)) return false; struct_message m; m.value = v; return esp_now_send(mac, (uint8_t *)&m, sizeof(m)) == 0; } void checkAndSend(int i) { switch (i) { case 1: { // Azul ganó sendTo(broadcastAddress2, 3); sendTo(broadcastAddress3, 3); sendTo(broadcastAddress4, 3); sendTo(broadcastAddress5, 3); sendTo(broadcastAddress1, 2); blink(4, 0, 0, 255); break; } case 2: { // Rojo ganó sendTo(broadcastAddress1, 3); sendTo(broadcastAddress3, 3); sendTo(broadcastAddress4, 3); sendTo(broadcastAddress5, 3); sendTo(broadcastAddress2, 2); blink(4, 255, 0, 0); break; } case 3: { // Verde ganó sendTo(broadcastAddress1, 3); sendTo(broadcastAddress2, 3); sendTo(broadcastAddress4, 3); sendTo(broadcastAddress5, 3); sendTo(broadcastAddress3, 2); blink(4, 0, 255, 0); break; } case 4: { // Amarillo ganó sendTo(broadcastAddress1, 3); sendTo(broadcastAddress2, 3); sendTo(broadcastAddress3, 3); sendTo(broadcastAddress5, 3); sendTo(broadcastAddress4, 2); blink(4, 255, 255, 0); break; } case 5: { // Blanco ganó sendTo(broadcastAddress1, 3); sendTo(broadcastAddress2, 3); sendTo(broadcastAddress3, 3); sendTo(broadcastAddress4, 3); sendTo(broadcastAddress5, 2); blink(4, 100, 100, 150); //Menor consumo de batería break; } default: break; } } // --- Reset void dale() { pixels.setBrightness(maximo); for (int i = 0; i < NUMPIXELS; i++) { pixels.setPixelColor(i, pixels.Color(50, 50, 50)); } pixels.show(); delay(12); pixels.clear(); pixels.show(); sendTo(broadcastAddress1, 4); delay(5); sendTo(broadcastAddress2, 4); delay(5); sendTo(broadcastAddress3, 4); delay(5); sendTo(broadcastAddress4, 4); delay(5); sendTo(broadcastAddress5, 4); delay(100); sendTo(broadcastAddress1, 3); delay(5); sendTo(broadcastAddress2, 3); delay(5); sendTo(broadcastAddress3, 3); delay(5); sendTo(broadcastAddress4, 3); delay(5); sendTo(broadcastAddress5, 3); delay(5); sendTo(broadcastAddress1, 0); delay(5); sendTo(broadcastAddress2, 0); delay(5); sendTo(broadcastAddress3, 0); delay(5); sendTo(broadcastAddress4, 0); delay(5); sendTo(broadcastAddress5, 0); delay(5); } void resetAll() { pixels.setBrightness(maximo); pixels.clear(); pixels.show(); sendTo(broadcastAddress1, 1); delay(5); sendTo(broadcastAddress2, 1); delay(5); sendTo(broadcastAddress3, 1); delay(5); sendTo(broadcastAddress4, 1); delay(5); sendTo(broadcastAddress5, 1); delay(5); } |
Piezas de montaje para los pulsadores






