Construa um Cubo LED RGB 8x8x8 com Arduino – Guia DIY
Componentes e Materiais
| Componente | Quantidade | Observações |
|---|---|---|
| LED RGB Difuso Ânodo Comum | 512 | |
| Driver LED DM13A | 12 | |
| Decodificador 74HC138 3 para 8 linhas | 1 | |
| MOSFET P-Channel IRF9640 | 8 | |
| Arduino UNO | 1 | Também pode criar versão breadboard com ATmega328, cristal 16MHz, 2x capacitores 22pf |
| Fonte Chaveada 5V 2,5A (Digilent) | 1 | |
| Resistor 10k Ω | 8 | |
| Resistor 1k Ω | 8 | |
| Resistor 100 Ω | 8 |
Software e Serviços Online
- Arduino IDE
Sobre o Projeto
Vídeo
Vídeo da construção disponível.
Construção
Esta construção foi inspirada no cubo RGB de Kevin Darrah.
Olhando a construção do Kevin, sua paciência não deve ter limites - infelizmente a minha tem.
Decidi substituir os 24 registradores de deslocamento, 192 transistores e 640 resistores por 12 drivers LED DM13A (cerca de $1 cada no eBay).
O próprio cubo foi construído como Kevin descreve no seguinte vídeo:
Uma vez feito o cubo, criei uma placa de circuito impresso usando o método do toner para acomodar os chips drivers DM13A e o próprio cubo. Em vez de pagar o custo de uma placa feita comercialmente com metalização de furos passantes, decidi conectar manualmente cada conexão de LED ao pino apropriado do DM13A usando wire-wrap wire. Os arquivos Eagle incluídos contêm a versão com fiação manual e uma versão auto-roteada (não testada).
O microprocessador e a placa de ânodo também acomodam um MSGEQ7 - Equalizador Gráfico de Sete Bandas e pré-amplificador de microfone que planejo experimentar no futuro. Por enquanto, não são usados. Se quiser usar uma placa UNO ou Nano comercial, tudo que precisa é o decodificador 74HC138 3-para-8 e os 8 MOSFETs P-Channel e resistores associados. Você pode apenas conectá-los em protoboard se quiser.
A fonte 5V 20W foi comprada no eBay. Fiz o gabinete de pinho aparelhado 40mm x 9mm.
Adicionei algumas animações ao software do cubo do Kevin, mas basicamente permanece inalterado.
Conclusão
Você deve conseguir comprar 600 LEDs RGB 5mm Ânodo Comum difusos no eBay por cerca de $30. Mesmo simplificando a eletrônica, a construção do cubo foi muito demorada mas no final, recompensadora.
Código
Cube_8x8x8_V1.ino (C/C++)
/* Cubo LED RGB 8x8x8 por John Bradnam baseado no trabalho de Kevin Darrah
* Última V12 04/17/2013
* Release Notes:
* V11 - Correção de bug no timing BAM
* - Movido configuração pino Blank para ISR para que pinos fiquem inativos até serem escritos
* V12 - Operação bitwise para definir pinos LOW estava incorreta, deve ser PORTx &= ~(1<<pinNumber);
* Disclaimer: Não sou construtor profissional de cubo LED, é assim que eu fiz, este é meu cubo
*/
#include <SPI.h> // Biblioteca SPI usada para clock de dados para registradores de deslocamento
#define LATCH_PIN 2 // pode usar qualquer pino para travar registradores
#define BLANK_PIN 4 // qualquer pino, certifique-se de pull-up via 1k para 5V
#define DATA_PIN 11 // usado por SPI, deve ser pino 11
#define CLOCK_PIN 13 // usado por SPI, deve ser 13
#define LAYER_A 5 // Entrada A do 74138
#define LAYER_B 6 // Entrada B do 74138
#define LAYER_C 7 // Entrada C do 74138
#define SWITCH_PGM 10 // PB2
#define SWITCH_SEQ 9 // PB1
#define CUBE_SIZE 8 // Número de colunas, linhas ou níveis no cubo
#define CUBE_MAX (CUBE_SIZE - 1) // Índice máximo do cubo
#define LEDS_PER_LEVEL (CUBE_SIZE * CUBE_SIZE) // Número de LEDs por nível
//***variáveis***variáveis***variáveis***variáveis***variáveis***variáveis***variáveis***variáveis
// Estas variáveis são usadas pelo multiplexing e código de Modulação de Ângulo de Bit (BAM)
// É assim que o brilho de cada LED é armazenado,
// Cada LED só precisa de um 'bit' para saber se deve estar LIGADO ou DESLIGADO, então 64 Bytes dão 512 bits = 512 LEDs
// Como estamos modulando os LEDs, usando resolução de 4 bits, cada cor tem 4 arrays contendo 64 bits cada
byte red0[LEDS_PER_LEVEL], red1[LEDS_PER_LEVEL], red2[LEDS_PER_LEVEL], red3[LEDS_PER_LEVEL];
byte blue0[LEDS_PER_LEVEL], blue1[LEDS_PER_LEVEL], blue2[LEDS_PER_LEVEL], blue3[LEDS_PER_LEVEL];
byte green0[LEDS_PER_LEVEL], green1[LEDS_PER_LEVEL], green2[LEDS_PER_LEVEL], green3[LEDS_PER_LEVEL];
// note como mais resolução consumirá mais da sua preciosa RAM
int level = 0; // acompanha qual nível estamos enviando dados
int anodeLevel = 0; // incrementa através dos níveis de ânodo
int BAM_Bit, BAM_Counter = 0; // Variáveis BAM para acompanhar coisas
int animation = 0; // Acompanha animação no loop principal
//****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup
void setup() {
SPI.setBitOrder(MSBFIRST); // Most Significant Bit First
SPI.setDataMode(SPI_MODE0); // Modo 0 Borda de subida do dado, clock baixo
SPI.setClockDivider(SPI_CLOCK_DIV2); // Dados a 16MHz/2 - 8MHz
noInterrupts(); // mata interrupções até todos estarem configurados
// Usamos Timer 1 para atualizar o cubo
TCCR1A = B00000000; // Registrador A todos 0's já que não estamos toggleando pinos
TCCR1B = B00001011; // bit 3 para modo CTC, chamará interrupção em match de contador
// bits 0 e 1 dividem clock por 64, então 16MHz/64=250kHz
TIMSK1 = B00000010; // bit 1 para chamar interrupção em match OCR1A
OCR1A = 30; // brinque com isso, mas configurei para 30, o que significa:
// nosso clock roda a 250kHz, que é 1/250kHz = 4us
// com OCR1A=30, interrupção chamada a cada (30+1)x4us=124us,
// frequência de multiplex de ~8kHz
// finalmente configure as Saídas
pinMode(LATCH_PIN, OUTPUT);
pinMode(DATA_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(LAYER_A, OUTPUT);
pinMode(LAYER_B, OUTPUT);
pinMode(LAYER_C, OUTPUT);
digitalWrite(LAYER_A, LOW);
digitalWrite(LAYER_B, LOW);
digitalWrite(LAYER_C, LOW);
pinMode(SWITCH_PGM, INPUT);
pinMode(SWITCH_SEQ, INPUT);
// pinMode(BLANK_PIN, OUTPUT); // importante fazer por último, para LEDs não piscarem no boot
SPI.begin(); // inicia biblioteca SPI
interrupts(); // deixa o show começar, isso inicia o multiplexing
}
//***início loop***início loop***início loop***início loop***início loop***início loop***início loop***início loop***início loop
void loop() {
// Cada animação localizada em sub-rotina
// Para controlar um LED, simplesmente:
// LED(nível 0-CUBE_MAX, linha 0-CUBE_MAX, coluna 0-CUBE_MAX, brilho vermelho 0-15, verde 0-15, azul 0-15);
if (digitalRead(SWITCH_PGM) == HIGH) test_leds();
else {
clean();
animation = animation + 1;
switch (animation) {
case 1: rainVersionTwo(20); break;
case 2: folder(10); break;
case 3: sinwaveTwo(15); break;
case 4: randomColor(10); break;
case 5: wipe_out(10); break;
case 6: bouncyvTwo(15); break;
case 7: color_wheelTWO(10); break;
case 8: harlem_shake(); break;
case 9: ripples(10); break;
case 10: animation = 0; break;
}
}
}
//****Rotina LED****Rotina LED****Rotina LED****Rotina LED****Rotina LED****Rotina LED****Rotina LED****Rotina LED
void LED(int level, int row, int column, byte red, byte green, byte blue) {
// Verifica limites
level = constrain(level, 0, CUBE_MAX);
row = constrain(row, 0, CUBE_MAX);
column = constrain(column, 0, CUBE_MAX);
red = constrain(red, 0, 15);
green = constrain(green, 0, 15);
blue = constrain(blue, 0, 15);
// Obtém número do LED 0-511
int wholebyte = (level * LEDS_PER_LEVEL) + (row * CUBE_SIZE) + column;
// Índice no array. Cada posição guarda 1 byte = 8 bits
int whichbyte = int(wholebyte / 8);
int whichbit = (wholebyte & 7);
// Resolução de cor 4 bits, cada cor contém 4 arrays de 64 bytes
bitWrite(red0[whichbyte], whichbit, bitRead(red, 0));
bitWrite(red1[whichbyte], whichbit, bitRead(red, 1));
bitWrite(red2[whichbyte], whichbit, bitRead(red, 2));
bitWrite(red3[whichbyte], whichbit, bitRead(red, 3));
bitWrite(green0[whichbyte], whichbit, bitRead(green, 0));
bitWrite(green1[whichbyte], whichbit, bitRead(green, 1));
bitWrite(green2[whichbyte], whichbit, bitRead(green, 2));
bitWrite(green3[whichbyte], whichbit, bitRead(green, 3));
bitWrite(blue0[whichbyte], whichbit, bitRead(blue, 0));
bitWrite(blue1[whichbyte], whichbit, bitRead(blue, 1));
bitWrite(blue2[whichbyte], whichbit, bitRead(blue, 2));
bitWrite(blue3[whichbyte], whichbit, bitRead(blue, 3));
}
//***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM
ISR(TIMER1_COMPA_vect) {
// Chamado automaticamente em background na frequência definida por OCR1A
// Neste código, OCR1A=30, chamado a cada 124us, dando a cada nível 124us de tempo ON
// São 8 níveis, então brilho máximo de 1/8, pois nível deve desligar antes do próximo ligar
// Frequência de multiplex = 124us*8=992us, ou 1/992us = ~1kHz
PORTD |= 1 << BLANK_PIN; // Primeiro desliga todos LEDs, escrevendo 1 no pino blank
// BAM de 4 bits. 8 níveis, quando '1' escrito no brilho da cor,
// cada nível tem chance de acender por 1 ciclo, BAM_Bit acompanha qual bit modulamos dos 4 bits
// BAM_Counter é contagem de ciclos, incrementa a cada nível aceso
if (BAM_Counter == (CUBE_SIZE * 1)) BAM_Bit++;
else if (BAM_Counter == (CUBE_SIZE * 3)) BAM_Bit++;
else if (BAM_Counter == (CUBE_SIZE * 7)) BAM_Bit++;
BAM_Counter++;
int bytes_per_level = LEDS_PER_LEVEL / 8;
int shift_max = level + bytes_per_level;
switch (BAM_Bit) {
case 0:
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(blue0[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(green0[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(red0[shift_out]);
break;
case 1:
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(blue1[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(green1[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(red1[shift_out]);
break;
case 2:
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(blue2[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(green2[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(red2[shift_out]);
break;
case 3:
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(blue3[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(green3[shift_out]);
for (int shift_out = level; shift_out < shift_max; shift_out++) SPI.transfer(red3[shift_out]);
// Reset BAM_Counter
if (BAM_Counter == (CUBE_SIZE * 15)) {
BAM_Counter = 0;
BAM_Bit = 0;
}
break;
}
// Envia nível de ânodo
digitalWrite(LAYER_A, bitRead(anodeLevel, 0));
digitalWrite(LAYER_B, bitRead(anodeLevel, 1));
digitalWrite(LAYER_C, bitRead(anodeLevel, 2));
PORTD |= 1 << LATCH_PIN; // Latch HIGH
PORTD &= ~(1 << LATCH_PIN); // Latch LOW
PORTD &= ~(1 << BLANK_PIN); // Blank LOW para ligar LEDs com novos dados
anodeLevel = (anodeLevel + 1) & (CUBE_MAX);
level = (level + bytes_per_level) & (LEDS_PER_LEVEL - 1);
pinMode(BLANK_PIN, OUTPUT); // movido aqui para saídas ficarem desligadas até primeira chamada
}
//****Animações****Animações****Animações****Animações****Animações****Animações****Animações****Animações
// (Código das animações truncado por tamanho - download completo no GitHub)
// Teste de LEDs
void test_leds() {
clean(); delay(1000);
fill(15, 0, 0); delay(1000);
fill(0, 15, 0); delay(1000);
fill(0, 0, 15); delay(1000);
}
// Outras animações: wipe_out, rainVersionTwo, folder, sinwaveTwo, randomColor, bouncyvTwo, color_wheelTWO, harlem_shake, ripples
// Código completo disponível para download.
Esquemáticos
eagle_files_WfqPEUP7Mp.zip
Processo de manufatura
- Alerta Inteligente de Sede para Plantas – Sensor DIY para Hidratação Ideal
- Construa um Cubo de LED 5x5x5 Impressionante com Arduino Mega
- Relógio de Palavras em Italiano com Arduino Nano, NeoPixel, LCD e RTC
- Medidor kWh Confiável com Sigfox e Arduino MKR Fox 1200
- Misturador de Cores RGB com Arduino – Projeto DIY para Iniciantes
- Monitor de Temperatura com Bluetooth usando Arduino Nano
- Projetores LED RGB Externos com DMX – Alta Performance e Custo Acessível
- Fechadura por Gestos Sem Toque: Acesso Seguro com APDS-9960 e Arduino
- Circuito de Entrada Analógica Isolada para Arduino
- Teste de Tempo de Reação Preciso com Arduino UNO