Desde su creación en 1984, Tetris no es solo un videojuego; es un fenómeno cultural, un desafío a la percepción espacial y un ejercicio de toma de decisiones bajo presión. Llevar este clásico a la vida real, utilizando componentes electrónicos discretos, es el sueño de todo aficionado a la electrónica y la programación. Este proyecto no solo te enseñará sobre la interacción entre software y hardware con la plataforma Arduino, sino que también te sumergirá en el fascinante control de matrices de LEDs y la gestión de entradas de usuario mediante botones.
🔩Funcionamiento
El Cerebro del Sistema: Arduino Uno
El Arduino Uno es la plataforma de microcontrolador de código abierto elegida. Actúa como la Unidad Central de Procesamiento (CPU) de nuestra consola, ejecutando la lógica de juego, gestionando la física de las piezas (caída, rotación y colisión) y orquestando la comunicación con la pantalla y los controles.
- Microcontrolador: ATmega328P. Este chip maneja el bucle principal (
loop()) que constantemente verifica el tiempo de caída, las entradas de los botones y actualiza el display. - Pines Digitales: Los pines digitales son cruciales para dos funciones:
- Comunicación SPI (Pines 10, 11, 13): Se utilizan para enviar datos a la matriz de LEDs.
- Entradas de Botón (Pines 2, 3, 4, 5): Permiten al jugador interactuar con el juego.
- Memoria (RAM): Aproximadamente 2 KB de SRAM. Esto es un factor limitante, ya que aquí se almacena la matriz de juego interna (
board[8][32]), las variables de posición de la pieza y las variables de control del juego.
Importancia en Tetris: El Arduino debe realizar la detección de colisiones y la limpieza de líneas de forma rápida y eficiente para evitar el lag, lo que requiere algoritmos optimizados.
El Chip Controlador MAX7219
El MAX7219 es un controlador de display LED de cátodo común. Su función principal es multiplexar las 64 LEDs de cada matriz 8×8, controlando individualmente cada LED mientras minimiza el número de cables y el consumo de energía del microcontrolador.
- Comunicación: Utiliza el protocolo SPI (Serial Peripheral Interface). Esto permite controlar hasta 8 dispositivos encadenados (como nuestras 4 matrices) usando solo tres pines de Arduino (DIN, CLK, CS), un ahorro masivo comparado con intentar controlar los 4 x 64 = 256 LEDs directamente.
- Almacenamiento: Cada MAX7219 tiene un registro de memoria interna que almacena el estado de los 64 píxeles, liberando a la pequeña RAM del Arduino de esta tarea.
- Encadenamiento (Daisy-Chain): La salida de datos (
DOUT) de un MAX7219 se conecta a la entrada (DIN) del siguiente. Esto permite que el Arduino envíe un flujo continuo de 32 bytes de datos (8 bytes por módulo) para actualizar toda la pantalla 32×8.
Los Módulos de Matriz de LEDs 8×8
Cada módulo contiene 64 LEDs y el chip MAX7219. Al encadenar cuatro módulos, obtenemos una gran superficie para visualizar el juego.
Importancia en Tetris: La velocidad de actualización de la matriz debe ser alta para evitar el parpadeo perceptible. El MAX7219 maneja la alta frecuencia de refresco de manera eficiente, asegurando que las piezas en movimiento se vean sólidas y no intermitentes.
PINES del módulo Matriz 8×8 (MAX7219)
| Pin módulo Matriz 8×8 (MAX7219) | Protocolo | Notas Importantes |
|---|---|---|
| VCC | Alimentación | Uso de 5V. |
| GND | Tierra | Conexión de referencia. |
| CS (Chip Select) | SS para SPI | Selecciona el módulo |
| DIN (Data In) | MOSI para SPI | Entrada de datos. |
| CLK (Clock) | SCK para SP | Señal de reloj |
🔨Componentes
| Componente | Cantidad | Especificación | Función |
|---|---|---|---|
| Placa Arduino | 1 | Arduino Uno, Nano, etc. | Control de componentes(cerebro) |
| Matriz LED | 4 | 8×8 | LEDs para mostrar los símbolos |
| Módulo multiplexor | 4 | MAX7219 | Controlador de matriz LED con interfaz SPI |
| Botones | 4 | Push | Para mover/rotar los símbolos |
| Resistencias | 4 | 10kΩ | Pull-down para los botones |
| Cables de conexión | n | Unir los componentes |
🔌Conexiones
Conexión módulo Display 8×8(MAX7219)
| MAX7219 Pin | Arduino Pin |
|---|---|
| VCC | 5v |
| GND | GND |
| DIN | D11 |
| CS | D10 |
| CLK | D13 |
Conexión de los botones(Configuración Pull-Down):
| Arduino Pin | Arduino Pin |
|---|---|
| 5v | Pin 2 (con resistencia de 10kΩ a GND) |
| 5v | Pin 3 (con resistencia de 10kΩ a GND) |
| 5v | Pin 4 (con resistencia de 10kΩ a GND) |
| 5v | Pin 5 (con resistencia de 10kΩ a GND) |
Para asegurar una lectura digital estable, los pulsadores se conectan en configuración Pull-Down: un extremo a 5V, el otro extremo al Pin Digital (2, 3,4 o 5) de Arduino y, crucialmente, una resistencia de 10kΩ entre ese mismo pin digital y GND. Esto garantiza que el pin digital lea un estado LOW cuando el botón está abierto y HIGH (5V) solo cuando es presionado.
0️⃣Código
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 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | #include <Arduino.h> #include <LedControl.h> // ********************************************** // 1. CONFIGURACIÓN DE HARDWARE Y CONSTANTES // ********************************************** // Pines de comunicación LedControl (SPI) #define DIN_PIN 11 #define CLK_PIN 13 #define CS_PIN 10 #define MAX_DEVICES 4 // Número de módulos 8x8 en cascada // Pines de Botones (Usando INPUT_PULLUP) #define ACCELERATE_PIN 2 #define ROTATE_PIN 3 #define LEFT_PIN 4 #define RIGHT_PIN 5 // Dimensiones de Juego (Lógica de juego: 8 columnas, 32 filas) #define GRID_W 8 #define GRID_H 32 #define NUM_PIECE_TYPES 7 #define NUM_ROTATIONS 4 // Constantes de Tiempo y Dirección #define DROP_NORMAL_MS 750 #define DROP_ACCEL_MS 50 #define DEBOUNCE_MS 150 #define DOWN 0 #define LEFT 1 #define RIGHT 2 // Objeto LedControl LedControl lc = LedControl(DIN_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); // Tablero de juego y Variables Globales int grid[GRID_W][GRID_H]; // Tablero lógico 8x32 int currentPiece, currentRotation, currentX, currentY; unsigned long lastDropTime = 0; int currentDropInterval = DROP_NORMAL_MS; unsigned long lastButtonActionTime = 0; int score = 0; // --- Definición de los 7 Tetrominos (I, J, L, O, S, T, Z) --- // [Tipo][Rotación][Fila][Columna] const byte PIECES[NUM_PIECE_TYPES][NUM_ROTATIONS][4][4] = { // 0. I {{{0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0}}, {{0,0,1,0}, {0,0,1,0}, {0,0,1,0}, {0,0,1,0}}, {{0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0}}, {{0,0,1,0}, {0,0,1,0}, {0,0,1,0}, {0,0,1,0}}}, // 1. J {{{1,0,0,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0}}, {{0,1,1,0}, {0,1,0,0}, {0,1,0,0}, {0,0,0,0}}, {{0,0,0,0}, {1,1,1,0}, {0,0,1,0}, {0,0,0,0}}, {{0,1,0,0}, {0,1,0,0}, {1,1,0,0}, {0,0,0,0}}}, // 2. L {{{0,0,1,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0}}, {{0,1,0,0}, {0,1,0,0}, {0,1,1,0}, {0,0,0,0}}, {{0,0,0,0}, {1,1,1,0}, {1,0,0,0}, {0,0,0,0}}, {{1,1,0,0}, {0,1,0,0}, {0,1,0,0}, {0,0,0,0}}}, // 3. O {{{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}, {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}, {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}, {{0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}}}, // 4. S {{{0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0}}, {{0,1,0,0}, {0,1,1,0}, {0,0,1,0}, {0,0,0,0}}, {{0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0}}, {{0,1,0,0}, {0,1,1,0}, {0,0,1,0}, {0,0,0,0}}}, // 5. T {{{0,1,0,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0}}, {{0,1,0,0}, {0,1,1,0}, {0,1,0,0}, {0,0,0,0}}, {{0,0,0,0}, {1,1,1,0}, {0,1,0,0}, {0,0,0,0}}, {{0,1,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0}}}, // 6. Z {{{1,1,0,0}, {0,1,1,0}, {0,0,0,0}, {0,0,0,0}}, {{0,0,1,0}, {0,1,1,0}, {0,1,0,0}, {0,0,0,0}}, {{1,1,0,0}, {0,1,1,0}, {0,0,0,0}, {0,0,0,0}}, {{0,0,1,0}, {0,1,1,0}, {0,1,0,0}, {0,0,0,0}}} }; // --- Funciones Prototipo (Declaración Adelantada) --- void setLedVirtual(int x, int y, bool state); void initGrid(); void newPiece(); bool checkCollision(int x, int y, int rotation); void movePiece(int direction); void rotatePiece(); void lockPiece(); void checkLines(); void updateDisplay(); void handleInput(); void gameOver(); // ================================= SETUP ================================= void setup() { randomSeed(analogRead(A0)); // Inicialización de los 4 módulos de la matriz for (int i = 0; i < MAX_DEVICES; i++) { lc.shutdown(i, false); lc.setIntensity(i, 2); lc.clearDisplay(i); } pinMode(ROTATE_PIN, INPUT_PULLUP); pinMode(ACCELERATE_PIN, INPUT_PULLUP); pinMode(LEFT_PIN, INPUT_PULLUP); pinMode(RIGHT_PIN, INPUT_PULLUP); initGrid(); newPiece(); } // ================================= LOOP ================================= void loop() { handleInput(); unsigned long currentTime = millis(); if (currentTime - lastDropTime >= currentDropInterval) { movePiece(DOWN); lastDropTime = currentTime; } updateDisplay(); } // ======================= 2. LÓGICA DE JUEGO ======================= void initGrid() { for (int x = 0; x < GRID_W; x++) { for (int y = 0; y < GRID_H; y++) { grid[x][y] = 0; } } } void newPiece() { currentPiece = random(0, NUM_PIECE_TYPES); currentRotation = 0; currentX = GRID_W / 2 - 2; currentY = 0; // Se spawnea en la fila superior (Y=0) if (checkCollision(currentX, currentY, currentRotation)) { gameOver(); } } bool checkCollision(int newX, int newY, int newRotation) { const byte (*pieceShape)[4] = PIECES[currentPiece][newRotation]; for (int dy = 0; dy < 4; dy++) { for (int dx = 0; dx < 4; dx++) { if (pieceShape[dy][dx] == 1) { int gx = newX + dx; int gy = newY + dy; // 1. Verificar límites if (gx < 0 || gx >= GRID_W || gy >= GRID_H) return true; // 2. Verificar colisión con piezas fijadas if (gy >= 0 && grid[gx][gy] != 0) return true; } } } return false; } void movePiece(int direction) { int newX = currentX; int newY = currentY; if (direction == LEFT) newX--; else if (direction == RIGHT) newX++; else if (direction == DOWN) newY++; if (!checkCollision(newX, newY, currentRotation)) { currentX = newX; currentY = newY; } else if (direction == DOWN) { lockPiece(); checkLines(); newPiece(); } } void rotatePiece() { int newRotation = (currentRotation + 1) % NUM_ROTATIONS; if (!checkCollision(currentX, currentY, newRotation)) { currentRotation = newRotation; } } void lockPiece() { const byte (*pieceShape)[4] = PIECES[currentPiece][currentRotation]; for (int dy = 0; dy < 4; dy++) { for (int dx = 0; dx < 4; dx++) { if (pieceShape[dy][dx] == 1) { int gx = currentX + dx; int gy = currentY + dy; if (gy >= 0 && gy < GRID_H) { grid[gx][gy] = currentPiece + 1; } } } } } void checkLines() { int linesCleared = 0; for (int y = GRID_H - 1; y >= 0; y--) { bool isLineFull = true; for (int x = 0; x < GRID_W; x++) { if (grid[x][y] == 0) { isLineFull = false; break; } } if (isLineFull) { linesCleared++; // Mover filas hacia abajo (compactación de memoria) for (int moveY = y; moveY > 0; moveY--) { for (int x = 0; x < GRID_W; x++) { grid[x][moveY] = grid[x][moveY - 1]; } } for (int x = 0; x < GRID_W; x++) { grid[x][0] = 0; } y++; } } if (linesCleared > 0) { score += linesCleared * 100; } } // ======================= 3. CONTROL DE DISPLAY LedControl.h (REAJUSTADO FINAL) ======================= /** * @brief Mapea la coordenada virtual (x, y) a la coordenada física (device, row, col). * Este mapeo asume: * 1. Cableado produce una rotación de 90 grados (X juego -> R física, Y juego -> C física). * 2. La caída va de Y=0 (arriba) al Dispositivo 3, y Y=31 (abajo) al Dispositivo 0. */ void setLedVirtual(int x, int y, bool state) { if (x >= 0 && x < GRID_W && y >= 0 && y < GRID_H) { // --- 1. Mapeo del Dispositivo (INVERTIDO) --- // Y=0 (Lógica: Arriba) está en el primer bloque (0-7) de Y. // Si el Módulo 3 está físicamente arriba, entonces: // device_map = 0 para Y=0..7; device_map = 1 para Y=8..15, etc. int device_map = y / 8; // Invertimos la asignación: 0 -> 3, 1 -> 2, 2 -> 1, 3 -> 0. // Esto fuerza que el Dispositivo 3 (arriba) se use para Y=0. int device = MAX_DEVICES - 1 - device_map; // --- 2. Mapeo de la Columna y Fila (CORRECCIÓN DE ROTACIÓN) --- // La columna X del juego (0-7) se mapea a la Fila física R (0-7) del chip, invertida. int row = 7 - x; // La fila Y del juego se mapea a la Columna física C del chip (0-7). // Usamos el resto de Y para la posición local dentro del módulo. int col = y % 8; // LedControl.h: setLed(dispositivo, fila, columna, estado) lc.setLed(device, row, col, state); } } void updateDisplay() { // Limpiar toda la pantalla antes de dibujar el nuevo frame for(int i = 0; i < MAX_DEVICES; i++) { lc.clearDisplay(i); } // 1. Dibujar piezas fijas (grid) for (int x = 0; x < GRID_W; x++) { for (int y = 0; y < GRID_H; y++) { if (grid[x][y] > 0) { setLedVirtual(x, y, true); } } } // 2. Dibujar la pieza cayendo const byte (*pieceShape)[4] = PIECES[currentPiece][currentRotation]; for (int dy = 0; dy < 4; dy++) { for (int dx = 0; dx < 4; dx++) { if (pieceShape[dy][dx] == 1) { setLedVirtual(currentX + dx, currentY + dy, true); } } } } void handleInput() { unsigned long currentTime = millis(); // Control de Caída Rápida (Soft Drop) if (digitalRead(ACCELERATE_PIN) == HIGH) { currentDropInterval = DROP_ACCEL_MS; } else { currentDropInterval = DROP_NORMAL_MS; } // Control de Movimiento y Rotación con Debounce if (currentTime - lastButtonActionTime > DEBOUNCE_MS) { if (digitalRead(ROTATE_PIN) == HIGH) { rotatePiece(); lastButtonActionTime = currentTime; } else if (digitalRead(LEFT_PIN) == HIGH) { movePiece(LEFT); lastButtonActionTime = currentTime; } else if (digitalRead(RIGHT_PIN) == HIGH) { movePiece(RIGHT); lastButtonActionTime = currentTime; } } } void gameOver() { // Animación de Game Over: Llenado de la pantalla for (int y = GRID_H - 1; y >= 0; y--) { for (int x = 0; x < GRID_W; x++) { setLedVirtual(x, y, true); delay(20); } } // Parpadeo final while(true) { for (int i = 0; i < MAX_DEVICES; i++) lc.setIntensity(i, 0); delay(300); for (int i = 0; i < MAX_DEVICES; i++) lc.setIntensity(i, 15); delay(300); } } |
🖌️Diseños

🎬Videos
📑Conclusión
La creación del juego Tetris utilizando la plataforma Arduino y la matriz de LEDs 32×8 MAX7219 es una demostración excepcional de cómo la programación de bajo nivel se fusiona con la manipulación directa del hardware para recrear un clásico atemporal.
Puntos Clave de la Ingeniería del Proyecto
- Optimización de Recursos: Este proyecto ilustra la necesidad de una programación eficiente. La memoria limitada del Arduino obliga a utilizar estructuras de datos concisas (la matriz de juego 8×32) y algoritmos rápidos para la detección de colisiones y la rotación de figuras.
- Dominio de la Comunicación Serial: El uso del chip MAX7219 y el protocolo SPI es fundamental. Permite al microcontrolador gestionar 256 LEDs (la matriz 32×8) utilizando solo tres pines, demostrando la eficacia de la multiplexación en la electrónica moderna.
- Física de Juego: La lógica de Tetris se reduce a ecuaciones de movimiento (X ± 1, Y + 1) y geometría matricial. El sistema debe coordinar de manera precisa la temporización de la caída con la respuesta instantánea a las entradas del usuario (botones), un desafío crucial resuelto mediante el control del
intervaloCaiday la implementación de debounce.
Este proyecto trasciende el mero pasatiempo; es una prueba de concepto que valida la capacidad de las plataformas open source como Arduino para actuar como controladores de sistemas complejos. Al construir un juego, el desarrollador aprende no solo a encender LEDs, sino a gestionar el tiempo, el espacio y la interacción en un bucle continuo de procesamiento. El Tetris en 32×8 es, en esencia, una CPU, una GPU y un sistema de entrada reducidos a sus componentes más esenciales.

