En este tutorial, vamos a dar vida a un letrero animado utilizando dos módulos de LEDs RGB WS28B20. El corazón del proyecto es la tarjeta UNIT Pulsar ESP32-C6, y para llevar la interactividad al siguiente nivel, utilizaremos sensores capacitivos Touch TTP223B. Prepárate para descubrir una nueva forma de interactuar con tus proyectos electrónicos.
Materiales necesarios
- 2 TTP223B Sensor Capacitivo Touch – UNIT DevLab
- 2 Módulo WS28B20-1010 Matriz 8×16 LED RGB – UNIT DevLab
- UNIT Pulsar ESP32-C6
- 2 Arnés QWIIC 4 pines Pitch 1mm
- 2 Conectores SH1.0mm con Cable 28 AWG 15cm
- Protoboard
Conexiones
Para formar nuestra pantalla de 32×8, utilizaremos dos Módulos Matriz 8×16 LED RGB (WS28B20-1010). La interconexión se realiza mediante cables Arnés QWIIC de 4 pines (1mm). El flujo de datos debe ser el siguiente: la señal parte de la UNIT Pulsar ESP32-C6 y se conecta al puerto de entrada (IN) de la primera matriz. Posteriormente, conectamos un segundo arnés desde la salida (OUT) de esta primera matriz hacia la entrada (IN) del segundo módulo.
Configuración de los Sensores Táctiles
- Utilizaremos dos Sensores Capacitivos TTP223B, conectados mediante cables SH1.0mm (28 AWG). Es fundamental observar la serigrafía en la tarjeta para una conexión correcta:
- VCC: Conectado a la salida de 3.3V de la UNIT Pulsar.
- GND: Ambos sensores pueden compartir el mismo pin de tierra (GND) de la tarjeta.
- OUT: El pin de señal de cada sensor se conectará a los pines 4 y 5 de la UNIT Pulsar, respectivamente.
- Nota: El cuarto cable del arnés SH1.0 no será utilizado en esta ocasión, por lo que se recomienda dejarlo sin conexión o aislado
Instalación de la Biblioteca
Para poder usar nuestro modulo instalaremos las siguientes bibliotecas
- Adafruit_GFX.h
- Adafruit_NeoMatrix.h
- Adafruit_NeoPixel.h
Para cargarlas al Arduino IDE sigue los siguientes pasos:
- Abre el entorno de desarrollo Arduino en tu computadora.
- Ve a “Sketch” > “Incluir Biblioteca” > “Gestor de Bibliotecas”.
- Busca Adafruit_GFX y Adafruit_NeoMatrix, Adafruit_NeoPixel selecciona la biblioteca y da clic en instalar.
Realización del Código
El siguiente programa nos permitirá asignar animaciones a cada uno de los sensores touch. estas se visualizaran en los modulos RGB.
En los comentarios del codigo se describe el funcionamiento para que puedas modificarlo y agregar las animaciones que mas te agraden.
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#define PIN 6 // Comunicacion al modulo RGB
#define ANCHO_TOTAL 32 // total de ancho de hambos modulos
#define ALTO_TOTAL 8 //
// MATRIZ: 2 paneles 16x8 formando 32x8
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
16, 8, // Tamaño de CADA panel
2, 1, // 2 paneles de ancho, 1 de alto
PIN,
NEO_MATRIX_BOTTOM + NEO_MATRIX_LEFT +
NEO_MATRIX_COLUMNS + // Los datos avanzan por columnas
NEO_MATRIX_PROGRESSIVE + // Forma instalada de los leds
NEO_TILE_TOP + NEO_TILE_LEFT + // El primer PANEL está arriba a la izquierda
NEO_TILE_ROWS, // Los paneles están puestos de forma HORIZONTAL
NEO_GRB + NEO_KHZ800
);
const int TOUCH_PIN1 = 15; // Sensor touch 1
const int TOUCH_PIN2 = 19; // Sensor touch 2
volatile bool sensor1 = false; // bandera , sabe cuando es tocado el sensor
volatile bool sensor2 = false; // bandera , sabe cuando es tocado el sensor
// INTERRUPCIONES
void IRAM_ATTR pulso1() { sensor1 = true; }
void IRAM_ATTR pulso2() { sensor2 = true; }
// ---------- ANIMACIÓN PAISAJE ----------
void paisajeAnimado(int duracionMS) {
unsigned long inicio = millis();
int offset = 0;
int anchoTotal = matrix.width(); // 32 leds
while (millis() - inicio < duracionMS) {
if (sensor1) { sensor1 = false; return; }
matrix.fillScreen(0);
// MONTAÑAS: Dibujamos 4 montañas para cubrir el flujo de 32 píxeles
for (int i = 0; i < 4; i++) {
// Cada montaña tiene una base de 12.
// Las espaciamos y restamos el offset para que caminen a la izquierda.
int base = (i * 12) - offset;
matrix.fillTriangle(
base, 7,
base + 6, 0,
base + 12, 7,
matrix.Color(0, 60, 0)
);
}
matrix.drawFastHLine(0, 7, 32, matrix.Color(40, 20, 0));
matrix.show();
offset++;
// Reiniciamos solo cuando la primera montaña ya salió y la última entró
if (offset >= 12) offset = 0;
delay(60);
}
}
// ---------- ANIMACIÓN pista de carreras ----------
void pistaCarreras(int duracionMS) {
unsigned long inicio = millis();
int offset = 0;
while (millis() - inicio < duracionMS) {
if (sensor1) { sensor1 = false; return; }
matrix.fillScreen(0);
// 1. PASTO (Bordes verdes arriba y abajo)
matrix.drawFastHLine(0, 0, 32, matrix.Color(0, 40, 0)); // Borde superior
matrix.drawFastHLine(0, 7, 32, matrix.Color(0, 40, 0)); // Borde inferior
// 2. ASFALTO (Gris oscuro en el centro)
matrix.fillRect(0, 1, 32, 6, matrix.Color(15, 15, 15));
// 3. LÍNEAS DE LA CARRETERA (Blancas y discontinuas)
for (int i = 0; i < 6; i++) {
// El movimiento del offset hace que las líneas "vengan" hacia nosotros
int lineaX = (i * 8) - (offset % 8);
matrix.drawFastHLine(lineaX, 4, 3, matrix.Color(150, 150, 150));
}
// 4. EL COCHE (Visto desde arriba)
// El coche se mueve un poco de arriba a abajo para simular que conduce
int cocheY = (offset % 20 < 10) ? 2 : 5; // Cambia de carril cada 10 ciclos
// Cuerpo del coche (Rojo)
matrix.fillRect(4, cocheY, 4, 2, matrix.Color(200, 0, 0));
// Spoilers o detalles (Negro/Gris)
matrix.drawPixel(4, cocheY, matrix.Color(0, 0, 0));
matrix.drawPixel(4, cocheY + 1, matrix.Color(0, 0, 0));
// Luces traseras (Casi siempre se ven desde atrás, pero aquí ponemos brillo)
matrix.drawPixel(3, cocheY, matrix.Color(100, 0, 0));
matrix.drawPixel(3, cocheY + 1, matrix.Color(100, 0, 0));
// 5. OTRO COCHE (Obstáculo que rebasamos)
int rivalX = 32 - (offset % 64);
if (rivalX > -4) {
matrix.fillRect(rivalX, 3, 4, 2, matrix.Color(0, 0, 200)); // Coche azul
}
matrix.show();
offset++;
delay(50); // Velocidad rápida para sensación de carrera
}
}
void setup() {
Serial.begin(115200); // comuniacion serial
matrix.begin();
matrix.setTextWrap(false);
matrix.setBrightness(40);
pinMode(TOUCH_PIN1, INPUT_PULLDOWN); // activa la resistencia interna
pinMode(TOUCH_PIN2, INPUT_PULLDOWN); // activa la resistencia interna
attachInterrupt(digitalPinToInterrupt(TOUCH_PIN1), pulso1, RISING);
attachInterrupt(digitalPinToInterrupt(TOUCH_PIN2), pulso2, RISING);
matrix.fillScreen(0); // reinicia los leds
matrix.show(); // manda la animacion
}
// ---------- LOOP ----------
void loop() {
if (sensor1) { // detecta que el sensor fue pulsado
Serial.println("Sensor 1"); // imprime mensaje del sensor pulsado
sensor1 = false; // realizar el cambio de la bandera
paisajeAnimado(5000); // llamado de la animacion y duracion
matrix.fillScreen(0); // reinicia los leds
matrix.show(); // manda la animacion
}
if (sensor2) { // detecta que el sensor fue pulsado
Serial.println("Sensor 2"); // imprime mensaje del sensor pulsado
sensor2 = false;// realizar el cambio de la bandera
pistaCarreras(4000);// llamado de la animacion y duracion
matrix.fillScreen(0); // reinicia los leds
matrix.show(); // manda la animacion
}
}
Demostración
Conclusiones
Esta práctica demuestra la versatilidad de la UNIT Pulsar ESP32-C6 para gestionar sistemas de visualización extendidos.
A diferencia de las matrices tradicionales, estos módulos destacan por su direccionamiento individual, lo que nos permite controlar el color y brillo de cada uno de los 128 LEDs de forma independiente.

