Herramienta pedagógica visual para explicar la multiplicación mediante la suma para Educación Primaria usando luces.
A través de la adición de unidades se llega al área del cuadrilátero marcado por dos valores, pasando por sus múltiplos, visualizando así la multiplicación de ambos valores dados.

El artefacto tendrá diferente modos de comportamiento que variarán en función de las necesidades del maestro: Modo exposición, modo experimentación y modo juego que irá incrementando su dificultad con la consecución de buenos resultados en el uso por el alumno.
Cuenta con una ayuda sonora y visual que apoya el concepto de múltiplo de un número al llegar a uno de ellos.
Permite el cuenteo hacia delante y hacia atrás y en modo juego expondrá multiplicaciones de forma aleatoria.
Materiales necesarios
El presupuesto total ronda los 30€
- 1 Arduino nano o microcontrolador general. 3€
- 1 Matriz leds Ws2812B 16 x 16. 8€
- 1 Encoder rotativo. 2€
- 3 Matrices leds rojos de 20mm 8×24 controlador MAX7219. 4€
- 1 Buzzer. 0,3€
- 1 Shield para 4 baterías de lítio 18650 V9. 4€
- 4 Baterías de lítio 18650. 10€
- 1 resistencia 1K.
- 1 transistor 2N2222.
- 1 Placa de prototipo 70×50.
- Pulsador (Standar de ordenador 9x9mm).
- 1 conector USB tipo square (opcional para reprogramar).
Matriz de 256 leds
Principal componente sobre el que se apoya el proyecto. Son leds ws2812b dispuestos en una matriz de 16×16 y se controlan a través de un solo pin. En nuestro caso será el D6.

Como podemos apreciar por la parte trasera, contamos con una conexión de entrada de tres cables: rojo (+) , blanco (-) y verde (datos a D6) marcada por DIN.

Otra conexión de salida, marcada con DOUT, para en caso de necesitarlo, conectarse a otra matriz y leds independientes.
Observamos en la parte central hay dos conexiones de cables algo más gruesos que en este proyecto usaremos para alimentar la matriz directamente de la fuente de 5v, pues la entrega de corriente por parte de la salida de Arduino sería insuficiente provocando mal funcionamiento en momentos de consumo excesivo.
Recordar que la entrega máxima de corriente del pin de salida de 5V en Arduino es de 800 a 1000 mA, insuficiente para determinados trabajos como este.
Ha de tenerse en cuenta a la hora de la programación la disposición de los leds de la matriz, pues este orden es determinante.
En el código de programación que veremos al final de esta entrada, haremos referencia a una matriz lógica de 256 elementos llamada «asignacion» que contendrá el orden adecuado de los leds en su posición correcta como puede verse la correspondencia con la numeración de la imagen de arriba.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
int asignacion[16][16] = { { 0, 31, 32, 63, 64, 95, 96, 127, 128, 159, 160, 191, 192, 223, 224, 255 }, { 1, 30, 33, 62, 65, 94, 97, 126, 129, 158, 161, 190, 193, 222, 225, 254 }, { 2, 29, 34, 61, 66, 93, 98, 125, 130, 157, 162, 189, 194, 221, 226, 253 }, { 3, 28, 35, 60, 67, 92, 99, 124, 131, 156, 163, 188, 195, 220, 227, 252 }, { 4, 27, 36, 59, 68, 91, 100, 123, 132, 155, 164, 187, 196, 219, 228, 251 }, { 5, 26, 37, 58, 69, 90, 101, 122, 133, 154, 165, 186, 197, 218, 229, 250 }, { 6, 25, 38, 57, 70, 89, 102, 121, 134, 153, 166, 185, 198, 217, 230, 249 }, { 7, 24, 39, 56, 71, 88, 103, 120, 135, 152, 167, 184, 199, 216, 231, 248 }, { 8, 23, 40, 55, 72, 87, 104, 119, 136, 151, 168, 183, 200, 215, 232, 247 }, { 9, 22, 41, 54, 73, 86, 105, 118, 137, 150, 169, 182, 201, 214, 233, 246 }, { 10, 21, 42, 53, 74, 85, 106, 117, 138, 149, 170, 181, 202, 213, 234, 245 }, { 11, 20, 43, 52, 75, 84, 107, 116, 139, 148, 171, 180, 203, 212, 235, 244 }, { 12, 19, 44, 51, 76, 83, 108, 115, 140, 147, 172, 179, 204, 211, 236, 243 }, { 13, 18, 45, 50, 77, 82, 109, 114, 141, 146, 173, 178, 205, 210, 237, 242 }, { 14, 17, 46, 49, 78, 81, 110, 113, 142, 145, 174, 177, 206, 209, 238, 241 }, { 15, 16, 47, 48, 79, 80, 111, 112, 143, 144, 175, 176, 207, 208, 239, 240 } }; |
En la carcasa superior se acoplará la pantalla matriz, que cuenta con 3 grandes orificios para facilitar el paso de los cables correspondientes.

Sobre la misma se colocará la pieza rotulada con la cuadrícula de numeración. Ambas piezas sujetarán se atornillarán con tornillos de 3x10mm, aprisionando entre ellas la matriz y dejándola inmovilizada.

La fuente
Bueno, es un proyecto de gran consumo. Alimentar tantos leds durante cierto tiempo, requiere de una apuesta fuerte, tanto en consumo instantáneo como en durabilidad, no queremos que la experiencia esté limitada por ello.
Para ello elegimos la barbaridad de 4 pilas 18650 en paralelo. Apostamos por el Shield 18650 V9, fuente capaz que posee dos entradas para su carga: USB tipo C y USB mini.
En nuestro caso emplearemos para cargar las baterías el puesto USB tipo C mediante un alargador que contará con acceso al exterior en la carcasa superior.

En dicho alargador usaremos tan solo los pines energéticos Vbus y Gnd. El inconveniente que nos encontramos es que al conectar la carga, el artefacto se enciende. Esto no impide la carga.
Para más información puedes consultar el datasheet de la shield V9.
Para activar la fuente de alimentación pondremos un pulsador (de ordenador, 9x9mm) en la carcasa superior que irá soldado en la parte trasera como se muestra en la imagen al pulsador de la placa.

Selector enconder rotativo
Un elemento con una teoría apasionante pero que pasaré por alto para indicar que conviene conectar los pines CLK y DT correspondiente al giro a los las interrupciones del microcontrolador, que en nuestro caso al tratarse de un Arduino Nano, deben ser D2 y D3. Aunque estas conexiones pueden realizarse en otros pines, es recomendado hacerlo en las indicadas pues la precisión aumenta considerablemente así como la ausencia de errores de lectura.

Por su parte Vcc debe ir a los 5 voltios y GND a tierra. En cuanto al pulsador SW que incorpora al presionar el eje de rotación lo conectaremos a D4.
Para su control necesitaremos la librería ESPRotary.h y Button2.h que también podrás descargar en la sección de librerías más abajo de la entrada.
Matrices led MAX7219
Conjunto de 3 matrices de 0.8 inch en serie, controladas por el chip max7219 y vendida por OPEN-SMART. Usaremos para su programación la librería <MAX7219_Dot_Matrix.h> que podremos descargar en la sección al final de este artículo.

Como se puede ver en la imagen, este módulo no cuenta con demasiadas facilidades para su agarre por lo que usaremos una pieza especial que habrá que imprimir para su colocación.

Esquema electrónico
Las baterías irán presionadas entre la carcasa superior y el shield impidiendo que se salgan.
Usaremos un alargador USB para poder reprogramar el invento sin tener que abrirlo para acceder al microcontrolador.

Obsérvense en la imagen anterior las piezas en blanco que servirán para la sujeción de la pantalla led roja, el pequeño buzzer y el pulsador de encendido y apagado.
Partes impresas
Programación en IDE de Arduino
Librerías
La mayoría están disponibles en el repositorio oficial de Arduino, pero coloco aquí las necesarias para el MAX7219 y el botón del encoder rotativo.
Código
Provisional a 17/11/2025 pero operativa.
|
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 |
#include <Adafruit_NeoPixel.h> #include <SPI.h> #include <bitBangedSPI.h> #include <MAX7219_Dot_Matrix.h> #include "ESPRotary.h"; #include "Button2.h" // https://github.com/LennartHennigs/Button2 #define ROTARY_PIN1 2 #define ROTARY_PIN2 3 #define BUTTON_PIN 4 #define PINWS 6 int matriz = 16; //Tamaño en pixeles de la matrix 16x16 int margen = 1; //pixeles entre los numeros y el area (separación). //int max=8; int fila; int multiplo; int pos; int ini; int pixeles = 256; int intenso = 5; int prime; int segun; char message[2] = "Hol"; boolean boton = 0; int contador; int solucion; int led_intenso = 100; //100 es soportado -->maximo posible 255. int buzz = 5; int fasel = 2; //fases de 1 a 5 int aciertos = 0; int asignacion[16][16] = { { 0, 31, 32, 63, 64, 95, 96, 127, 128, 159, 160, 191, 192, 223, 224, 255 }, { 1, 30, 33, 62, 65, 94, 97, 126, 129, 158, 161, 190, 193, 222, 225, 254 }, { 2, 29, 34, 61, 66, 93, 98, 125, 130, 157, 162, 189, 194, 221, 226, 253 }, { 3, 28, 35, 60, 67, 92, 99, 124, 131, 156, 163, 188, 195, 220, 227, 252 }, { 4, 27, 36, 59, 68, 91, 100, 123, 132, 155, 164, 187, 196, 219, 228, 251 }, { 5, 26, 37, 58, 69, 90, 101, 122, 133, 154, 165, 186, 197, 218, 229, 250 }, { 6, 25, 38, 57, 70, 89, 102, 121, 134, 153, 166, 185, 198, 217, 230, 249 }, { 7, 24, 39, 56, 71, 88, 103, 120, 135, 152, 167, 184, 199, 216, 231, 248 }, { 8, 23, 40, 55, 72, 87, 104, 119, 136, 151, 168, 183, 200, 215, 232, 247 }, { 9, 22, 41, 54, 73, 86, 105, 118, 137, 150, 169, 182, 201, 214, 233, 246 }, { 10, 21, 42, 53, 74, 85, 106, 117, 138, 149, 170, 181, 202, 213, 234, 245 }, { 11, 20, 43, 52, 75, 84, 107, 116, 139, 148, 171, 180, 203, 212, 235, 244 }, { 12, 19, 44, 51, 76, 83, 108, 115, 140, 147, 172, 179, 204, 211, 236, 243 }, { 13, 18, 45, 50, 77, 82, 109, 114, 141, 146, 173, 178, 205, 210, 237, 242 }, { 14, 17, 46, 49, 78, 81, 110, 113, 142, 145, 174, 177, 206, 209, 238, 241 }, { 15, 16, 47, 48, 79, 80, 111, 112, 143, 144, 175, 176, 207, 208, 239, 240 } }; Adafruit_NeoPixel pixels(pixeles, PINWS, NEO_GRB + NEO_KHZ800); MAX7219_Dot_Matrix myDisplay(3, 10); // Primer numero es la cantidad de módulos (3 una pantalla, 6 dos pantallas); Segundo número es el PIN. ESPRotary r = ESPRotary(ROTARY_PIN1, ROTARY_PIN2, 4); //el 4 es por la peculiaridad de mi encoder, que cuenta 4 pasadas en cada salto. Button2 b; void setup() { Serial.begin(9600); Serial.println("Saludos"); Serial.print("El Area Máximna es: "); Serial.print(matriz - margen - 1); Serial.print("x"); Serial.print(matriz - margen - 1); Serial.print("="); Serial.println((matriz - margen - 1) * (matriz - margen - 1)); pinMode(buzz, OUTPUT); randomSeed(analogRead(A0)); // Generamos una semilla aleatoria usando ruido del pin A0 int caca = random(3000); //He puesto esto aquí para evitar que salga siempre 1 en el primer número random. (Así he hecho trampa) pixels.begin(); pixels.setBrightness(led_intenso); myDisplay.begin(); myDisplay.setIntensity(intenso); if (fasel == 1) { //Numeros menores de 100 myDisplay.sendSmooth("f:1", 0); //-25 para escribir en la LCD 2 -- usar 0 para escribir en la LCD1 (Es el desplazamiento a la derecha). } if (fasel == 2) { //normal con 14x14 myDisplay.sendSmooth("f:2", 0); } if (fasel == 3) { //No muestra el resultado al inicio myDisplay.sendSmooth("f:3", 0); } if (fasel == 4) { //Sin puntos azules ni resultado al inicio, solos puntos blancos myDisplay.sendSmooth("f:4", 0); } if (fasel == 5) { //Ni siquiera puntos blancos, solo el numero en la pantalla myDisplay.sendSmooth("f:5", 0); } delay(350); myDisplay.sendSmooth("", 0); delay(100); r.setLeftRotationHandler(showDirection); r.setRightRotationHandler(showDirection); b.begin(BUTTON_PIN); b.setTapHandler(click); } void loop() { Serial.print("Fase:"); Serial.println(fasel); pixels.clear(); genera_multuplicacion(); propone(); if (fasel <= 2) { //Muestra solución en fases menores que 1 en LCD myDisplay.sendSmooth(message, 0); } else { myDisplay.sendSmooth("", 0); } pixels.show(); boton = 0; while (boton != 1) { r.loop(); b.loop(); } } void click(Button2& btn) { if (contador == solucion) { aciertos++; tone(buzz, 800, 100); delay(100); tone(buzz, 1500, 100); } else { aciertos--; tone(buzz, 1500, 100); delay(100); tone(buzz, 800, 100); } if (aciertos <= 0) { aciertos = 0; } Serial.print("Aciertos:"); Serial.println(aciertos); boton = 1; contador = 0; // myDisplay.sendSmooth("", 0); } void showDirection(ESPRotary& r) { if (r.directionToString(r.getDirection()) == "right") { //Giro a la derecha multiplo = segun * (contador / segun); contador++; fila = (contador - 1) / segun; if (contador >= segun * 14) { contador = (segun * 14); } if (fasel <= 4) { pixels.setPixelColor(asignacion[fila + 2][(contador + 1) - multiplo], pixels.Color(255, 255, 255)); //blanco } } if (r.directionToString(r.getDirection()) == "left") { //Giro a la izquierda contador--; if (contador <= 0) { contador = 0; } fila = contador / segun; multiplo = segun * (contador / segun); if (contador >= solucion) { pixels.setPixelColor(asignacion[fila + 2][(contador + 2) - multiplo], pixels.Color(0, 0, 0)); //negro } else { if (fasel <= 3) { pixels.setPixelColor(asignacion[fila + 2][(contador + 2) - multiplo], pixels.Color(0, 0, 255)); //repinta de azul } else { pixels.setPixelColor(asignacion[fila + 2][(contador + 2) - multiplo], pixels.Color(0, 0, 0)); //negro } } } if (contador % segun == 0 && contador != 0) { //Al pasar por un mútiplo for (int j = 0; j < segun; j++) { //amarillo pixels.setPixelColor(asignacion[(contador / segun) + 1][j + 2], pixels.Color(255, 255, 0)); } pixels.show(); itoa(contador, message, 10); intenso = 30; myDisplay.setIntensity(intenso); myDisplay.sendSmooth(message, 0); delay(20); for (int j = 0; j < segun; j++) { //rosita pixels.setPixelColor(asignacion[(contador / segun) + 1][j + 2], pixels.Color(150, 150, 0)); } pixels.show(); intenso = 0; myDisplay.setIntensity(intenso); tone(buzz, 1500, 50); delay(50); for (int j = 0; j < segun; j++) { //rosa pixels.setPixelColor(asignacion[(contador / segun) + 1][j + 2], pixels.Color(255, 0, 255)); } pixels.show(); delay(20); intenso = 0; myDisplay.setIntensity(intenso); delay(20); intenso = 30; myDisplay.setIntensity(intenso); delay(80); } else { pixels.show(); itoa(contador, message, 10); myDisplay.setIntensity(2); tone(buzz, 1500, 2); delay(2); myDisplay.sendSmooth(message, 0); } } void genera_multuplicacion() { if (fasel <= 1) { //Números menores de 10x10 prime = random(1, 12 - margen); segun = random(1, 12 - margen); } else { // Numeros hasta 14x14 prime = random(1, matriz - margen); segun = random(1, matriz - margen); } //prime = 5; //segun = 1; // Serial.print(prime); // Serial.print("X"); // Serial.print(segun); // Serial.print("="); solucion = prime * segun; // Serial.println(solucion); itoa(solucion, message, 10); } void propone() { pixels.setPixelColor(prime + margen, pixels.Color(0, 255, 0)); //nº Vertical pixels.setPixelColor(asignacion[prime + margen][1], pixels.Color(0, 255, 0)); pixels.setPixelColor(asignacion[0][segun + 1], pixels.Color(255, 0, 0)); //nº horizontal pixels.setPixelColor(asignacion[1][segun + 1], pixels.Color(255, 0, 0)); if (fasel <= 3) { for (int j = 0; j < prime; j++) { //Areas azules for (int i = 0; i < segun; i++) { pixels.setPixelColor(asignacion[j + 2][i + 2], pixels.Color(0, 0, 255)); } } } } |


















