Cubo de LEDs
Ángel Hernanz, Javier Martín y Yolanda Cobo. Grupo 3
Introducción
Cuando se nos asignó este proyecto para la asignatura de , comenzamos a pensar y buscar posibles trabajos a realizar que fuesen diferentes y, a la vez, entretenidos tanto de ver como de realizar. Sopesamos numerosas posibilidades, lo que conllevó un debate para seleccionar la mejor opción.
Ante esto, nos hemos decantado por la creación de una lámpara de led de tamaño 5x5x5 que, al encenderla, comenzará a mostrar varias combinaciones de leds que, mediante la combinación de leds encendiéndose y otros leds apagándose que, en su conjunto, forman un efecto visual increíble.
Hemos seleccionado este proyecto debido a que tiene una gran proyección de futuro y es un modelo muy empleado a día de hoy, por lo que realizamos una pequeña miniatura como representación de las grandes creaciones.
Materiales
Los materiales empleados, junto con la cantidad empleada y el coste de los mismos, vienen representados en la tabla 1:
MATERIAL | CANTIDAD | COSTE |
Arduino Mega 2560 | 1 | 19,49€ |
Leds de color azul | 150 | 26,7€ |
Alambre | 1 | 3,89€ |
NTE8 NPN transistor | 8 | 7,68€ |
Resistencia 180 ohmios | 35 | 3,2€ |
Resistencia 330 ohmios | 5 | 0,35€ |
Placa prototyping 9×15 cm | 5 | 6,49€ |
Cables | 35 | 10€ |
Tabla de madera | 1 | 10€ |
En total, el proyecto ha tenido un coste de 87,8€.
Además, hemos empleado las siguientes herramientas para poder crear el cubo.
- Alicates: cortar y pelar cables.
- Soldador y estaño: soldar los leds para formar las capas del cubo.
- Regla: medir la distancia entre los agujeros del tablón y entre los leds.
- Taladro y broca: realizar los agujeros en la table de madera.
Proceso Hardware
En primer lugar, procedimos a soldar los leds entre sí, formando 5 capas, cada una de ellas con una estructura de 5×5 leds. Para ello, tuvimos que crearnos una plantilla en el tablón de madera que nos sirvió para que todos los leds quedasen a la misma distancia entre sí. Esta plantilla la realizamos con un taladro, agujereando 25 veces para introducir la cabeza del led en ellos, de forma que nos es más sencillo unirlos.
Para la unión, tuvimos que cortar 25 segmentos de 20cm de alambre. Tras esto, pusimos un alambre junto a 5 leds en fila, colocando el pin negativo de cada led sobre el propio alambre para, posteriormente, soldarlos. Una vez tenemos soldados los leds en filas de 5 en 5 de forma vertical, soldamos dos alambres más de forma horizontal para que se quedasen unidas todas las filas y formar una capa de 25 leds. Este proceso lo repetimos 5 veces para formar las 5 capas.
Tras crear estas 5 capas, debemos unirlas entre ellas. Para ello, hemos vuelto a recortar 25 alambres de 20cm cada uno y los pegamos a la base de nuestro tablón. Tras esto, soldamos el pin positivo de cada led a la varilla vertical.
En este punto tuvimos un problema debido a que soldamos los pines positivos al formar las capas en lugar de los negativos y, posteriormente, soldamos los pines negativos al formar el cubo en lugar de los positivos. Por ello, tuvimos que volver a empezar todo el cubo de 0 y soldar los pines correctos.
Tras haber solucionado este problema, comenzamos a colocar las 25 resistencias de 180 ohmios conectando la fila e y f de la placa protoboard. Además, unimos la columna de ánodos más a la izquierda junto con las columnas de 1 a 5 de la fila j, después la segunda columna del cubo con las filas 6-10 y así sucesivamente hasta conectar todos los leds a la placa. De este modo, quedan conectados los positivos de los leds junto a las resistencias. Una vez que han sido conectadas todas las resistencias, pasamos a conectarlas a su vez con los pines del 28 al 53 del Arduino Mega 2560.
Tras esto, pasamos a conectar los pines negativos de los leds con los 5 transistores con el fin de aumentar la intensidad de corriente del circuito. Finalmente, pasamos a conectar estos transistores a los pines del Arduino del 22 al 27.
En este paso, también tuvimos un problema. Esto se debía a que nosotros entendíamos que una pata de cada transistor debía ir conectada a tierra, es decir, a 0 voltios; sin embargo, al conectar todos los transistores al pin de tierra del Arduino, solo nos funcionaba la capa superior del cubo, es decir, solo funcionaba una capa. Ante esta situación, nos dimos cuenta de que hacían cortocircuito en el pin GND de la placa de Arduino y, para poder solucionarlo, optamos por poner cinco de los pines del Arduino a 0 (tierra) y conectar una para de cada transistor a cada uno de estos pines a tierra.
Proceso Software
Finalmente, adjuntamos el código con algunos comentarios para que sea más sencilla la comprensión del mismo. Hemos implementado tan solo 6 combinaciones de leds; sin embargo, para incrementar su variedad, solo bastaría con emplear la misma estructura, pero efectuando un cambio de orden y duración de los leds. En este punto, nos costó un poco el sincronizar todos los leds pero, tras varias comprobaciones, conseguimos obtenerlo.
El estado de cada LED en el cubo viene marcado en la matriz llamada cube[5][5][5]. Si aparece un 1 significará que el LED está encendido y si aparece un 0, significará que el led está apagado. Por lo tanto, las animaciones las hemos creado jugando con 0 y 1 colocados en la matriz cube.
Nuestro loop() es realmente sencillo ya que solo se encarga de ir llamando a los diferentes procedimientos que se encargan de llevar a cabo cada efecto.
Tras las funciones principales del programa, encontramos una serie de funciones que son comunes a todos los efectos.
Finalmente, como explicamos en un comentario en el código, para poder llevar al máximo el tiempo en el que los leds están encendidos hemos empleado el acceso directo al puerto para ir configurando los pines en lugar de emplear la conocida función digitalWrite.
const int myLayer[5] = {22, 23, 24, 25, 26};
volatile byte cube[5][5][5];
// ************************************** SETUP ***************************************
void setup() {
// poner los pins como salida y a nivel bajo
for (int x = 22; x < 52; x++) {
pinMode(x, OUTPUT);
digitalWrite(x, LOW);
}
clearCube(); // apagar el cubo
for (int x = 2; x < 7; x++) {
pinMode(x, OUTPUT);
digitalWrite(x, LOW);
}
cli(); // detiene las interrupciones
TCCR1A = 0; // registro TCCR1A a 0
TCCR1B = 0; // registro TCCR1B a 0
TCNT1 = 0; // contador a 0
OCR1A = 19999;
TCCR1B |= (1 << WGM12);
TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);
TIMSK1 |= (1 << OCIE1A);
sei(); // permite las interrupciones
}
// *************************************** LOOP **************************************
void loop() {
dancingFireflys();
rotatingX();
pulsingSphere();
sinewave2();
rain();
cubeFlash();
}
//******************************* LUCIÉRNAGAS VOLADORAS *****************************
// Columnas que se van moviendo aleatoriamente por el cubo.
void dancingFireflys(){
int xpos = 3;
int ypos = 3;
int myrandom, count;
for (int k=0; k<250; k++) {
count++;
myrandom = random(5);
if (myrandom == 0) {
xpos++;
}
if (myrandom == 1) {
xpos--;
}
if (myrandom == 2) {
ypos++;
}
if (myrandom == 3) {
ypos--;
}
if (xpos < 0) {
xpos = 1;
}
if (xpos > 4) {
xpos = 3;
}
if (ypos < 0) {
ypos = 1;
}
if (ypos > 4) {
ypos = 3;
}
ledOn(xpos, ypos, 4);
if (count%2) copyDown();
delay(20);
}
pause();
}
//********************************** X ROTATORIA **************************************
// Una X rota por todos los lados del cubo
void rotatingX() {
rotX(0, 4);
rotX(1, 4);
rotX(3, 3);
rotX(4, 3);
rotX(0, 2);
rotX(1, 2);
rotX(3, 1);
rotX(4, 1);
rotX(0, 0);
rotX(1, 0);
for (int k = 0; k < 4; k++) {
rotX(3, 0);
rotX(4, 0);
rotX(0, 0);
rotX(1, 0);
}
for (int k = 0; k < 4; k++) {
rotX(3, 0);
rotX(5, 0);
rotX(0, 0);
rotX(2, 0);
}
rotX(3, 1);
rotX(5, 1);
rotX(0, 2);
rotX(2, 2);
rotX(3, 3);
rotX(5, 3);
rotX(0, 4);
rotX(2, 4);
for (int k = 0; k < 7; k++) {
rotX(3, 4);
rotX(5, 4);
rotX(0, 4);
rotX(2, 4);
}
pause();
}
// Esta función la empleamos para crear la crear la X con los leds en las diferentes posiciones.
void rotX(int k, int y) {
if (k < 3) {
ledOn(1, y, 1);
ledOn(1, y, 3);
ledOn(2, y, 2);
ledOn(3, y, 1);
ledOn(3, y, 3);
}
else {
ledOn(2, y, 1);
ledOn(2, y, 2);
ledOn(2, y, 3);
ledOn(1, y, 2);
ledOn(3, y, 2);
}
switch (k) {
case 0: //
ledOn(0, y, 0);
ledOn(0, y, 4);
ledOn(4, y, 0);
ledOn(4, y, 4);
break;
case 1:
ledOn(0, y, 1);
ledOn(1, y, 4);
ledOn(3, y, 0);
ledOn(4, y, 3);
break;
case 2:
ledOn(1, y, 0);
ledOn(0, y, 3);
ledOn(3, y, 4);
ledOn(4, y, 1);
break;
case 3:
ledOn(0, y, 2);
ledOn(2, y, 0);
ledOn(4, y, 2);
ledOn(2, y, 4);
break;
case 4:
ledOn(1, y, 0);
ledOn(0, y, 3);
ledOn(4, y, 1);
ledOn(3, y, 4);
break;
case 5:
ledOn(0, y, 1);
ledOn(1, y, 4);
ledOn(4, y, 3);
ledOn(3, y, 0);
break;
}
showCube(6);
}
//*********************************** PULSOS DE ESFERA ********************************
// Esfera que crece y decrece
void pulsingSphere() {
float count;
for (int k = 0; k < 6; k++) {
for (count = 0.5; count < 2.8; count = count + 0.3) { // crece la esfera
sphere(count);
}
for (count = 2.7; count > 0.5; count = count - 0.3) { // decrece la esfera
sphere(count);
}
}
}
// Crear la esfera y se usa en la función principal del efecto
void sphere(float radius) {
float polar, j, k, l;
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
for (int z = 0; z < 5; z++) {
j = float(x);
k = float(y);
l = float(z);
polar = sqrt((j - 2) * (j - 2) + (k - 2) * (k - 2) + (l - 2) * (l - 2));
if (polar < radius) {
ledOn(x, y, z);
}
else {
ledOff(x, y, z);
}
}
}
}
delay(20); // empleamos el delay para controlar la velocidad de crecimiento y decrecimiento.
}
//*********************************** OLA VERSIÓN 2 ***********************************
// Se crea una ola a lo largo de la diagonal del cubo.
void sinewave2() {
int z;
for (int k = 0; k < 7; k++) {
for (int i = 0; i < 40; i++) {
for (byte xx = 0; xx < 5; xx++) {
for (byte yy = 0; yy < 5; yy++) {
z = ((byte)(2.5 + sin((xx / 1.39) + (yy / 1.39) + (float)i / 6.28) * 2.5));
if (z > 7) {
z = 7;
}
ledOn(xx, yy, z);
}
}
delay(10);
clearCube();
}
}
}
// ************************************* LLUVIA ****************************************
void rain() {
for (int k = 0; k < 75; k++) {
int myx = random(5);
int myy = random(5);
cube[myx][myy][4] = 1;
delay(70);
copyDown();
}
pause();
}
// *********************************** CUBO FLASH *************************************
// El cubo crece y decrece
void cubeFlash() {
for (int k = 0; k < 12; k++) {
cubeInCube(0);
showCube(11);
cubeInCube(1);
showCube(8);
cubeInCube(2);
showCube(15);
cubeInCube(1);
showCube(8);
}
pause();
}
// función que controla el encendido y apagado de los leds.
void cubeInCube(int k) {
switch (k) {
case 0: // encender solo el centro del cubo
ledOn(2, 2, 2);
break;
case 1: // encender todas las capas menos las más externas
for (int layer = 1; layer < 4; layer++) {
for (int column = 1; column < 4; column++) {
for (int row = 1; row < 4; row++) {
cube[column][row][layer] = 1;
}
}
}
ledOff(2, 2, 2); // apaga el centro del cubo
break;
case 2: // enciende todo el cubo
for (int layer = 0; layer < 5; layer++) {
for (int column = 0; column < 5; column++) {
for (int row = 0; row < 5; row++) {
cube[column][row][layer] = 1;
}
}
}
for (int layer = 1; layer < 4; layer++) {
for (int column = 1; column < 4; column++) {
for (int row = 1; row < 4; row++) {
cube[column][row][layer] = 0;
}
}
}
break;
}
}
// Copia en una capa lo que se encuentra en la superior y, a la vez, apaga la capa copiada.
void copyDown() {
for (int z = 0; z < 4; z++) {
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
cube[x][y][z] = cube[x][y][z + 1];
}
}
}
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
cube[x][y][4] = 0;
}
}
}
// apaga todo el cubo
void clearCube() {
memset(cube, 0, 125 * sizeof cube[0][0][0]);
}
// enciende LED
void ledOn(int x, int y, int z) {
cube[x][y][z] = 1;
}
// apaga LED
void ledOff(int x, int y, int z) {
cube[x][y][z] = 0;
}
// muestra la animación durante el tiempo determinado
void showCube(int mytime) {
delay(10 * mytime);
clearCube();
}
// pausa entre las diferentes animaciones
void pause() {
clearCube();
delay(500);
}
// *************************** Interrupción del temporizador de refresco *********************
// Accedemos directamente a los puertos en lugar de usar digitalWrite para ahorrar tiempo.
ISR(TIMER1_COMPA_vect) {
for (int x = 0; x < 5; x++) {
if (cube[0][0][x] == 1)PORTL |= (1 << PORTL5); else PORTL &= ~(1 << PORTL5); // pin 44
if (cube[0][1][x] == 1)PORTL |= (1 << PORTL6); else PORTL &= ~(1 << PORTL6); // pin 43
if (cube[0][2][x] == 1)PORTL |= (1 << PORTL7); else PORTL &= ~(1 << PORTL7); // pin 42
if (cube[0][3][x] == 1)PORTG |= (1 << PORTG0); else PORTG &= ~(1 << PORTG0); // pin 41
if (cube[0][4][x] == 1)PORTG |= (1 << PORTG1); else PORTG &= ~(1 << PORTG1); // pin 40
if (cube[1][0][x] == 1)PORTA |= (1 << PORTA5); else PORTA &= ~(1 << PORTA5); // pin 27
if (cube[1][1][x] == 1)PORTD |= (1 << PORTD7); else PORTD &= ~(1 << PORTD7); // pin 38
if (cube[1][2][x] == 1)PORTG |= (1 << PORTG2); else PORTG &= ~(1 << PORTG2); // pin 39
if (cube[1][3][x] == 1)PORTB |= (1 << PORTB2); else PORTB &= ~(1 << PORTB2); // pin 51
if (cube[1][4][x] == 1)PORTB |= (1 << PORTB3); else PORTB &= ~(1 << PORTB3); // pin 50
if (cube[2][0][x] == 1)PORTC |= (1 << PORTC0); else PORTC &= ~(1 << PORTC0); // pin 37
if (cube[2][1][x] == 1)PORTC |= (1 << PORTC1); else PORTC &= ~(1 << PORTC1); // pin 36
if (cube[2][2][x] == 1)PORTC |= (1 << PORTC2); else PORTC &= ~(1 << PORTC2); // pin 35
if (cube[2][3][x] == 1)PORTC |= (1 << PORTC3); else PORTC &= ~(1 << PORTC3); // pin 34
if (cube[2][4][x] == 1)PORTC |= (1 << PORTC4); else PORTC &= ~(1 << PORTC4); // pin 33
if (cube[3][0][x] == 1)PORTL |= (1 << PORTL0); else PORTL &= ~(1 << PORTL0); // pin 49
if (cube[3][1][x] == 1)PORTL |= (1 << PORTL1); else PORTL &= ~(1 << PORTL1); // pin 48
if (cube[3][2][x] == 1)PORTL |= (1 << PORTL2); else PORTL &= ~(1 << PORTL2); // pin 47
if (cube[3][3][x] == 1)PORTL |= (1 << PORTL3); else PORTL &= ~(1 << PORTL3); // pin 46
if (cube[3][4][x] == 1)PORTL |= (1 << PORTL4); else PORTL &= ~(1 << PORTL4); // pin 45
if (cube[4][0][x] == 1)PORTC |= (1 << PORTC5); else PORTC &= ~(1 << PORTC5); // pin 32
if (cube[4][1][x] == 1)PORTC |= (1 << PORTC6); else PORTC &= ~(1 << PORTC6); // pin 31
if (cube[4][2][x] == 1)PORTC |= (1 << PORTC7); else PORTC &= ~(1 << PORTC7); // pin 30
if (cube[4][3][x] == 1)PORTA |= (1 << PORTA7); else PORTA &= ~(1 << PORTA7); // pin 29
if (cube[4][4][x] == 1)PORTA |= (1 << PORTA6); else PORTA &= ~(1 << PORTA6); // pin 28
digitalWrite(myLayer[x], HIGH);
delayMicroseconds(1200);
digitalWrite(myLayer[x], LOW);
}
}
Proyección en un futuro
El objetivo de nuestro proyecto sería poder extrapolarlo a cubos de leds de tamaño mucho más grande (32x32x32, por ejemplo), en los que podrían llegar a representarse hologramas o animaciones mucho más impresionantes y reales.
Vídeo
Finalmente, insertamos el vídeo que recopila toda la información necesaria sobre nuestro proyecto (hardware y software), además de una demostración de su funcionamiento.
LINK: