Pintura de Luz com LED Único: Construa Imagens Dinâmicas com Sistema Motorizado DIY
Componentes e Materiais
- 2x Motor de Passo NEMA17
- 2x Driver Theremino DRV8825 para Motores de Passo
- 1x Leitor de Cartão SD
- 1x Arduino Mega 2560
- 1x Correias de sincronismo e rodas V diversas
Ferramentas e Máquinas Necessárias
- Impressora 3D (genérica)
Aplicativos e Serviços Online
- Arduino IDE
- GIMP
- Autodesk Fusion 360
Sobre o Projeto
Ideia
Após assistir vários vídeos e ver numerosos artigos sobre pintura de luz, decidi tentar. A pintura de luz envolve usar uma câmera com tempo de exposição muito longo para capturar uma pequena fonte de luz. Isso permite que um único ponto de luz se torne um risco longo em uma única imagem.
Mas e se alguém quiser criar uma imagem mais detalhada ou usar muitas cores diferentes? Foi assim que surgiu a ideia de construir uma máquina CNC de 2 eixos que tem um único LED RGB que pode mudar de cor e "pintar" uma imagem.
O Plano
Este projeto exigiria quatro componentes principais: uma máquina CNC de 2 eixos, LED RGB, cartão SD e uma câmera capaz de tirar fotos de longa exposição. Primeiro, o Arduino Mega lerá o cartão SD e encontrará um bitmap para imprimir.
Então, ele percorrerá horizontalmente e acenderá os LEDs correspondentes enquanto também desce uma linha cada vez que a largura da imagem é excedida. Por fim, aguardará um pouco e procurará o próximo bitmap, parando quando não houver mais imagens para criar.
Construindo a Estrutura
Devido à minha experiência projetando e construindo máquinas CNC, esta etapa não foi muito difícil. Queria fazer algo modular que também pudesse ser expandido para outros projetos, então optei por um design simples que usa duas correias de sincronismo presas a barras transversais que se movem ao longo de extrusões de alumínio paralelas.
Isso permite que o comprimento de cada eixo seja muito personalizável. As extremidades do eixo X têm tampas finais impressas em 3D, uma das quais tem um suporte para o motor de passo do eixo X e rolamento.
Leitura de Bitmaps
Escolhi o formato de arquivo bitmap devido à sua simplicidade e facilidade de leitura. Baseado no formato, existem alguns endereços importantes no arquivo que devem ser lidos: 0x12 (largura), 0x16 (altura), 0x1C (profundidade de cor), 0xA (localização dos dados do pixel) e 0x36 (onde os dados do pixel geralmente estão).
Os dados são lidos em pedaços de dois ou quatro bytes (16 ou 32 bits), o que também avança o ponteiro para o próximo endereço. A função de leitura percorre e obtém todos os dados importantes, incluindo offsets e tamanhos. Em seguida, percorre e lê cada pixel, linha por linha.
Preparando as Imagens
Como a maioria das câmeras é limitada a no máximo 30 segundos de tempo de exposição, há um limite de cerca de 288 pixels totais que podem ser exibidos nesse tempo. Isso equivale a uma imagem de aproximadamente 18 x 16 pixels. Para criar minhas imagens, carreguei o GIMP e comecei a criar pixel art simples. Incluíam uma Pokéball, um coração e um Mario pulando. Em seguida, coloquei essas três imagens em um diretório chamado "bitmaps" no diretório raiz do cartão SD. O programa lê todas as imagens desta pasta.
Programa de Pintura
Como motores de passo não têm sistema de feedback de posição interno, suas posições devem ser rastreadas por software. O programa que escrevi mantém a posição do LED com um sistema de grade para permitir fácil escalonamento. Quando o Arduino Mega inicia, as posições dos steppers são definidas como 0,0 e a primeira imagem é encontrada e lida. Então, o LED pisca cinco vezes para avisar o fotógrafo que está quase na hora de começar a capturar. O bitmap é lido percorrendo cada linha, e dentro de cada linha, cada coluna é lida. Sabendo a linha e coluna atuais, os motores de passo podem ser movidos para essas mesmas posições. Em cada posição, o LED muda para a cor do pixel correspondente.
(Re)criando uma Imagem
Após inserir o cartão SD e conectar uma fonte de 12V para os motores, era hora de ligar a máquina. Na minha câmera, configurei para tempo de exposição de 20 segundos, abertura F36, ISO 100 e compensação de exposição de -5 stops para minimizar efeitos de ghosting. A primeira imagem desenhada foi uma pokéball:
Embora um pouco borrada, a forma ainda pode ser vista claramente. Em seguida, criou um coração:
Como esta imagem era apenas 9x9 pixels, cada pixel individual é muito menos definido. Por fim, pintei um Mario pulando:
Esta imagem tem ghosting intenso, principalmente devido à abundância de pixels coloridos brilhantes.
Ideias Futuras para Melhorias
As pinturas de luz que criei ficaram muito melhores do que eu pensava inicialmente, mas ainda há espaço para melhorias. A principal coisa que gostaria de fazer é reduzir a quantidade de borrão fazendo o LED se mover enquanto apagado e só acender quando parado. Essa técnica melhoraria muito a clareza das imagens recriadas.
Código
Programa de Pintura de Luz C/C++
// Função de leitura de bitmap parcialmente da Adafruit
#include <SD.h>
#include <SPI.h>
#include "DRV8825.h"
#define MOTOR_STEPS 200
#define RPM 150
#define MICROSTEPS 4
// definições de pinos
#define STEPPER_X_DIR 7
#define STEPPER_X_STEP 6
#define STEPPER_X_EN 8
#define STEPPER_Y_DIR 4
#define STEPPER_Y_STEP 5
#define STEPPER_Y_EN 12
#define X 0
#define Y 1
#define X_DIR_FLAG -1 // 1 ou -1 para inverter direção
#define Y_DIR_FLAG 1 // 1 ou -1 para inverter direção
#define STEPS_PER_MM (3.75 * MICROSTEPS) // passos necessários para mover 1mm
#define SPACE_BETWEEN_POSITIONS 5 // 5mm por movimento
#define R A0
#define G A1
#define B A2
#define SD_CS 22
int currentPositions[] = {0, 0};
DRV8825 stepperX(MOTOR_STEPS, STEPPER_X_DIR, STEPPER_X_STEP, STEPPER_X_EN);
DRV8825 stepperY(MOTOR_STEPS, STEPPER_Y_DIR, STEPPER_Y_STEP, STEPPER_Y_EN);
void setup() {
Serial.begin(115200);
init_steppers();
SD.begin(SD_CS);
createBitmaps();
stepperX.disable();
stepperY.disable();
while(1);
}
void loop() { }
void createBitmaps(){
File dir = SD.open("bitmaps");
while(true){
File bitmap = dir.openNextFile();
if(!bitmap){ break; }
paintBitmap(bitmap);
delay(15000);
}
}
#define BUFFPIXEL 20
void paintBitmap(File bmpFile){
int bmpWidth, bmpHeight;
uint8_t bmpDepth;
uint32_t bmpImageOffset;
uint32_t rowSize;
uint8_t sdbuffer[3 * BUFFPIXEL];
uint8_t buffidx = sizeof(sdbuffer);
boolean goodBmp = false;
boolean flip = true;
int w, h, row, col;
uint8_t r, g, b;
uint32_t pos = 0, startTime = millis();
Serial.println();
Serial.print("Loading image '");
Serial.print(bmpFile.name());
Serial.println("'");
if (read16(bmpFile) == 0x4D42) {
Serial.print("File size: "); Serial.println(read32(bmpFile));
(void)read32(bmpFile);
bmpImageOffset = read32(bmpFile);
Serial.print("Image Offset: "); Serial.println(bmpImageOffset, DEC);
Serial.print("Header size: "); Serial.println(read32(bmpFile));
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if (read16(bmpFile) == 1) {
bmpDepth = read16(bmpFile);
Serial.print("Bit Depth: "); Serial.println(bmpDepth);
if ((bmpDepth == 24) && (read32(bmpFile) == 0)) {
goodBmp = true;
Serial.print("Image size: "); Serial.print(bmpWidth); Serial.print('x'); Serial.println(bmpHeight);
rowSize = (bmpWidth * 3 + 3) & ~3;
if (bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; }
w = bmpWidth; h = bmpHeight;
if(bmpWidth*bmpHeight > 290){
Serial.println("File is too large to be printed."); return;
}
for(uint8_t i=0; i<5; i++){
analogWrite(R, 150); delay(500); analogWrite(R, 0); delay(500);
}
for (row = 0; row < h; row++) {
moveToPosition(0, row);
if (flip) pos = bmpImageOffset + (bmpHeight - 1 - row) * rowSize;
else pos = bmpImageOffset + row * rowSize;
if (bmpFile.position() != pos) {
bmpFile.seek(pos); buffidx = sizeof(sdbuffer);
}
for (col = 0; col < w; col++) {
if (buffidx >= sizeof(sdbuffer)) {
bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx = 0;
}
b = sdbuffer[buffidx++]; g = sdbuffer[buffidx++]; r = sdbuffer[buffidx++];
moveToPosition(col, row);
activateLED(r,g,b);
}
analogWrite(R, 0); analogWrite(G, 0); analogWrite(B, 0);
}
Serial.print("Loaded in "); Serial.print(millis() - startTime); Serial.println(" ms");
}
}
}
bmpFile.close();
moveToPosition(0,0);
if (!goodBmp) Serial.println("BMP format not recognized.");
}
uint16_t read16(File f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(File f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read();
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read();
return result;
}
void activateLED(int r, int g, int b){
Serial.print(F("LED has value of: ")); Serial.print(r); Serial.print(", "); Serial.print(g); Serial.print(", "); Serial.println(b);
analogWrite(R, r); analogWrite(G, g); analogWrite(B, b);
}
void moveToPosition(int x, int y){
int newPosX = (x-currentPositions[X])*STEPS_PER_MM*X_DIR_FLAG*SPACE_BETWEEN_POSITIONS;
int newPosY = (y-currentPositions[Y])*STEPS_PER_MM*Y_DIR_FLAG*SPACE_BETWEEN_POSITIONS;
stepperX.move(newPosX); stepperY.move(newPosY);
currentPositions[X] = x; currentPositions[Y] = y;
Serial.print("Stepper positions: "); Serial.print(currentPositions[X]); Serial.print(", "); Serial.println(currentPositions[Y]);
}
void init_steppers(){
stepperX.begin(RPM); stepperX.setEnableActiveState(LOW); stepperX.enable(); stepperX.setMicrostep(MICROSTEPS);
stepperY.begin(RPM); stepperY.setEnableActiveState(LOW); stepperY.enable(); stepperY.setMicrostep(MICROSTEPS);
}
Peças Personalizadas e Gabinetes
Arquivos CAD disponíveis no projeto.
Esquemáticos

Processo de manufatura
- Por Que Designers Estão Retornando a Materiais Naturais no Design de Produto
- Controle um LED com Sensor de Movimento PIR no Raspberry Pi
- Dominando FMEA: Guia Passo a Passo do Gerente de Manutenção para Construir e Usar Análise de Modo e Efeito de Falha
- Transferência de Dados de Temperatura e Umidade entre Dois Arduinos via Firebase
- Troque Duas Variáveis Python Sem Variável Temporária – Sem Esforço
- Projeto Arduino: Melodia Coffin Dance – Construa e Toque a Música Viral
- Revitalize Controles Remotos Antigos: Crie Dispositivos Inteligentes DIY com Arduino
- Controle um Rover Arduino com Firmata e Gamepad Xbox One
- Display de Luz com 8 LEDs Ativado por Som
- Construa um Robô Quadrúpede Inteligente com Arduino: Guia Passo a Passo