Creación de imágenes con un LED
Componentes y suministros
| × | 2 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
Herramientas y máquinas necesarias
|
Aplicaciones y servicios en línea
| ||||
| ||||
|
Acerca de este proyecto
Idea
Después de ver varios videos y ver numerosos artículos sobre la pintura con luz, decidí probarlo. La pintura con luz implica el uso de una cámara con un tiempo de exposición muy largo para capturar una pequeña fuente de luz. Esto permite que una sola luz se extienda en una racha larga en una sola imagen.
Pero, ¿qué pasa si alguien quiere crear una imagen más detallada o usar muchos colores diferentes? Así fue como se me ocurrió la idea de construir una máquina CNC de 2 ejes que tenga un solo LED RGB que pueda cambiar de color y “pintar” una imagen.
El plan
Este proyecto requeriría cuatro componentes principales para funcionar:una máquina CNC de 2 ejes, LED RGB, tarjeta SD y una cámara que sea capaz de tomar fotografías de larga exposición. Primero, Arduino Mega leería la tarjeta SD y buscaría un mapa de bits para imprimir.
Luego, cruzaría horizontalmente e iluminaría los LED correspondientes mientras también se movería hacia abajo una fila cada vez que se exceda el ancho de la imagen. Por último, esperará un poco y luego buscará el siguiente mapa de bits, para finalmente detenerse cuando no haya más imágenes para crear.
Construyendo la plataforma
Debido a mi experiencia en el diseño y construcción de máquinas CNC, este paso no fue demasiado difícil. Quería hacer algo modular que también pudiera expandirse para otros proyectos, así que me decidí por un diseño simple que usa dos correas de distribución unidas a barras transversales que se mueven a lo largo de extrusiones de aluminio paralelas.
Esto permite que la longitud de cada eje sea muy personalizable. Los extremos del eje X tienen tapones impresos en 3D, uno de los cuales tiene un soporte para el motor paso a paso del eje X y el cojinete.
Leer mapas de bits
Elegí el formato de archivo de mapa de bits debido a su simplicidad y la facilidad con que se puede leer. Según el formato del archivo, hay algunas direcciones importantes en el propio archivo que deben leerse. Estos son 0x12 (ancho), 0x16 (alto), 0x1C (profundidad de color), 0xA (ubicación de datos de píxeles) y finalmente 0x36 (donde suelen estar los datos de píxeles).
Los datos se leen en trozos de dos o cuatro bytes (16 o 32 bits), lo que también avanza el puntero a la siguiente dirección. La función de lectura revisa y captura todos los datos importantes, incluidos los desplazamientos y los tamaños. Luego revisa y lee cada píxel, fila por fila.
Preparando las imágenes
Dado que la mayoría de las cámaras están limitadas a un máximo de 30 segundos de tiempo de exposición, existe un límite de aproximadamente 288 píxeles en total que se pueden mostrar en ese período de tiempo. Esto equivale a aproximadamente una imagen de 18 x 16. Para hacer mis imágenes, cargué gimp y comencé a crear un pixel art muy simple. Estos incluían una Pokébola, un corazón y un Mario saltarín. Luego coloqué estas tres imágenes en un directorio llamado "mapas de bits" en el directorio raíz de la tarjeta SD. El programa lee todas las imágenes de esta carpeta.
Programa de pintura
Dado que los motores paso a paso no tienen un sistema de retroalimentación de posicionamiento interno, sus posiciones deben ser rastreadas por software. El programa que escribí realiza un seguimiento de la posición del LED con un sistema de cuadrícula para permitir un escalado fácil. Cuando Arduino Mega se inicia, las posiciones de los pasos se establecen en 0, 0 y luego se encuentra y se lee la primera imagen. Luego, el LED parpadea cinco veces para que el fotógrafo sepa que es casi el momento de comenzar a capturar. El mapa de bits se lee recorriendo primero cada fila y, dentro de cada fila, se lee cada columna. Al conocer la fila y la columna actuales, los motores paso a paso se pueden mover a esas mismas posiciones. En cada posición, el LED cambia al color del píxel correspondiente.
(re) -Creación de una imagen
Después de insertar la tarjeta SD y conectar una fuente de alimentación de 12 V para los motores, llegó el momento de encender la máquina. En mi cámara, la configuré para un tiempo de exposición de 20 segundos, una apertura de F36, ISO de 100 y una compensación de exposición de -5 pasos para minimizar los efectos de imagen fantasma. La primera imagen dibujada fue una pokebola, que se ve aquí:
Aunque está un poco borroso, la forma aún se puede ver claramente. Luego creó un mapa de bits de corazón:
Debido a que esta imagen tenía solo 9 por 9 píxeles, cada píxel individual está mucho menos definido. Por último, pinté una imagen de Mario saltando:
Esta imagen tiene muchas imágenes fantasma, principalmente debido a la abundancia de píxeles de colores brillantes.
Ideas futuras para mejoras
Las pinturas de luz que creé resultaron mucho mejores de lo que pensé inicialmente, pero todavía hay margen de mejora. Lo principal que me gustaría hacer es reducir la cantidad de desenfoque haciendo que el LED se mueva mientras está oscurecido y luego solo se ilumina cuando está quieto. Esta técnica mejoraría enormemente la claridad de las imágenes recreadas.
Código
- Programa de pintura con luz
Programa de pintura con luz C / C ++
// Función de lectura de mapa de bits parcialmente de Adafruit # incluye#include #include "DRV8825.h" #define MOTOR_STEPS 200 # define RPM 150 # define MICROSTEPS 4 // definiciones de pin # 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 o -1 para cambiar de dirección # definir 1_DIR_FLAG -1 // 1 o -1 para cambiar de dirección # definir 1_DIR_FLAG 1 o -1 para cambiar de dirección # definir STEPS_PER_MM (3.75 * MICROSTEPS) // pasos necesarios para mover 1 mm # definir SPACE_BETWEEN_POSITIONS 5 // 5 mm por movimiento # definir R A0 # definir G A1 # definir B A2 # definir SD_CS 22int 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_Ycombe_EN); init_steppers (); SD.begin (SD_CS); createBitmaps (); stepperX.disable (); stepperY.disable (); while (1);} void loop () {} void createBitmaps () {File dir =SD.open ("bitmaps"); while (verdadero) {Mapa de bits de archivo =dir.openNextFile (); si (! mapa de bits) {romper; } paintBitmap (mapa de bits); retraso (15000); }} #define BUFFPIXEL 20void paintBitmap (Archivo bmpFile) {int bmpWidth, bmpHeight; uint8_t bmpDepth; uint32_t bmpImageOffset; uint32_t rowSize; // No siempre =bmpWidth; puede tener relleno uint8_t sdbuffer [3 * BUFFPIXEL]; // búfer de píxeles (R + G + B por píxel) uint8_t buffidx =sizeof (sdbuffer); // Posición actual en sdbuffer boolean goodBmp =false; // Establecer en verdadero en un encabezado válido parse boolean flip =true; // BMP se almacena de abajo hacia arriba int w, h, row, col; uint8_t r, g, b; uint32_t pos =0, hora de inicio =milis (); Serial.println (); Serial.print ("Cargando imagen '"); Serial.print (bmpFile.name ()); Serial.println ('\' '); // Abrir el archivo solicitado en la tarjeta SD // Analizar el encabezado BMP if (read16 (bmpFile) ==0x4D42) {// Firma BMP Serial.print ("Tamaño de archivo:"); Serial.println (read32 (bmpFile)); (vacío) read32 (bmpFile); // Leer e ignorar los bytes del creador bmpImageOffset =read32 (bmpFile); // Inicio de los datos de la imagen Serial.print ("Image Offset:"); Serial.println (bmpImageOffset, DEC); // Leer el encabezado DIB Serial.print ("Tamaño del encabezado:"); Serial.println (read32 (bmpFile)); bmpWidth =read32 (bmpFile); bmpHeight =read32 (bmpFile); if (read16 (bmpFile) ==1) {// # planos - debe ser '1' bmpDepth =read16 (bmpFile); // bits por píxel Serial.print ("Profundidad de bits:"); Serial.println (bmpDepth); if ((bmpDepth ==24) &&(read32 (bmpFile) ==0)) {// 0 =sin comprimir goodBmp =true; // Formato BMP admitido:¡continúe! Serial.print ("Tamaño de imagen:"); Serial.print (bmpWidth); Serial.print ('x'); Serial.println (bmpHeight); // Las filas BMP se rellenan (si es necesario) hasta un límite de 4 bytes. RowSize =(bmpWidth * 3 + 3) &~ 3; // Si bmpHeight es negativo, la imagen está en orden de arriba hacia abajo. // Esto no es canon pero se ha observado en la naturaleza. if (bmpHeight <0) {bmpHeight =-bmpHeight; flip =falso; } // Área de cultivo a cargar w =bmpWidth; h =bmpHeight; if (bmpWidth * bmpHeight> 290) {// Demasiado grande Serial.println ("El archivo es demasiado grande para imprimirlo"); regreso; } para (uint8_t i =0; i <5; i ++) {analogWrite (R, 150); retraso (500); analogWrite (R, 0); retraso (500); } for (fila =0; fila =sizeof (sdbuffer)) {// De hecho bmpFile.read (sdbuffer, sizeof (sdbuffer)); buffidx =0; // Establecer el índice al principio} // Convertir el píxel de formato BMP a TFT, presionar para mostrar b =sdbuffer [buffidx ++]; g =sdbuffer [buffidx ++]; r =sdbuffer [buffidx ++]; moveToPosition (columna, fila); activeLED (r, g, b); // ¡optimizado! //tft.pushColor(tft.Color565(r,g,b)); } // fin de pixel analogWrite (R, 0); analogWrite (G, 0); analogWrite (B, 0); } // finaliza la línea de exploración Serial.print ("Cargado en"); Serial.print (millis () - startTime); Serial.println ("ms"); } // finaliza goodBmp}} bmpFile.close (); moveToPosition (0,0); if (! goodBmp) Serial.println ("No se reconoce el formato BMP.");} uint16_t read16 (File f) {uint16_t result; ((uint8_t *) &resultado) [0] =f.read (); // LSB ((uint8_t *) &resultado) [1] =f.read (); // MSB devuelve resultado;} uint32_t read32 (Archivo f) {uint32_t result; ((uint8_t *) &resultado) [0] =f.read (); // LSB ((uint8_t *) &resultado) [1] =f.read (); ((uint8_t *) &resultado) [2] =f.read (); ((uint8_t *) &resultado) [3] =f.read (); // MSB devuelve el resultado;} void activeLED (int r, int g, int b) {Serial.print (F ("El LED tiene el valor de:")); 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); posicionesActual [X] =x; posicionesActual [Y] =y; Serial.print ("Posiciones paso a paso:"); Serial.print (posicionesactual [X]); Serial.print (","); Serial.println (posicionesactual [Y]);} void init_steppers () {stepperX.begin (RPM); stepperX.setEnableActiveState (BAJO); stepperX.enable (); stepperX.setMicrostep (MICROSTEPS); stepperY.begin (RPM); stepperY.setEnableActiveState (BAJO); stepperY.enable (); stepperY.setMicrostep (MICROSTEPS);}
Piezas y carcasas personalizadas
Esquemas
Proceso de manufactura
- ¿Qué nos llevó de nuevo a utilizar materiales naturales en el diseño de productos?
- Sensor de movimiento con Raspberry Pi
- Una guía del administrador de mantenimiento para crear y usar AMFE
- Envía los datos del sensor de un Arduino a otro usando Firebase
- Intercambiar dos variables de Python sin usar una tercera
- Melodía de baile del ataúd
- Reutilizar controles remotos antiguos
- Controla Arduino Rover usando Firmata y el controlador Xbox One
- Iluminación LED de 8x por sonido
- Arduino Cuadrúpedo
- Transferencia de datos mediante luz LED (Li-Fi)