Création d'images à l'aide d'une seule LED
Composants et fournitures
| × | 2 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
Outils et machines nécessaires
|
Applications et services en ligne
| ||||
| ||||
|
À propos de ce projet
Idée
Après avoir regardé plusieurs vidéos et vu de nombreux articles sur le light painting, j'ai décidé de l'essayer. Le light painting consiste à utiliser un appareil photo avec un temps d'exposition très long pour capturer une petite source de lumière. Cela permet à une seule lumière de devenir une longue traînée dans une seule image.
Mais que se passe-t-il si quelqu'un veut créer une image plus détaillée ou utiliser plusieurs couleurs différentes ? C'est ainsi que j'ai eu l'idée de construire une machine CNC à 2 axes dotée d'une seule LED RVB capable de changer de couleur et de « peindre » une image.
Le plan
Ce projet nécessiterait quatre composants principaux pour fonctionner :une machine CNC à 2 axes, une LED RVB, une carte SD et une caméra capable de prendre des photos à longue exposition. Tout d'abord, l'Arduino Mega lirait la carte SD et trouverait un bitmap à imprimer.
Ensuite, il traverserait horizontalement et allumerait les LED correspondantes tout en descendant d'une rangée à chaque fois que la largeur de l'image est dépassée. Enfin, il attendra un peu, puis recherchera le prochain bitmap, s'arrêtant enfin chaque fois qu'il n'y a plus d'images à créer.
Construire la plate-forme
En raison de mon expérience dans la conception et la construction de machines CNC, cette étape n'était pas trop difficile. Je voulais créer quelque chose de modulaire qui pourrait également être étendu à d'autres projets, j'ai donc opté pour une conception simple qui utilise deux courroies de distribution fixées à des barres transversales qui se déplacent le long d'extrusions d'aluminium parallèles.
Cela permet à la longueur de chaque axe d'être très personnalisable. Les extrémités de l'axe X ont des embouts imprimés en 3D, dont l'un a un support pour le moteur pas à pas et le roulement de l'axe X.
Lecture de bitmaps
J'ai choisi le format de fichier bitmap en raison de sa simplicité et de sa facilité de lecture. En fonction du format de fichier, il y a quelques adresses importantes dans le fichier lui-même qui doivent être lues. Ce sont 0x12 (largeur), 0x16 (hauteur), 0x1C (profondeur de couleur), 0xA (emplacement des données de pixels) et enfin 0x36 (où se trouvent généralement les données de pixels).
Les données sont lues par blocs de deux ou quatre octets (16 ou 32 bits), ce qui fait également avancer le pointeur à l'adresse suivante. La fonction de lecture parcourt et récupère toutes les données importantes, y compris les décalages et les tailles. Ensuite, il parcourt et lit chaque pixel, ligne par ligne.
Préparation des images
Étant donné que la plupart des appareils photo sont limités à un maximum de 30 secondes de temps d'exposition, il y a une limite d'environ 288 pixels au total qui peuvent être affichés dans ce laps de temps. Cela équivaut à environ une image de 18 x 16. Pour faire mes images, j'ai chargé gimp et j'ai commencé à créer du pixel art très simple. Ceux-ci comprenaient un Pokéball, un cœur et un Mario sauteur. Ensuite, j'ai placé ces trois images dans un répertoire appelé "bitmaps" dans le répertoire racine de la carte SD. Le programme lit toutes les images de ce dossier.
Programme de peinture
Étant donné que les moteurs pas à pas n'ont pas de système de rétroaction de positionnement interne, leurs positions doivent être suivies par un logiciel. Le programme que j'ai écrit garde une trace de la position de la LED avec un système de grille pour permettre une mise à l'échelle facile. Lorsque l'Arduino Mega démarre, les positions du stepper sont définies sur 0, 0, puis la première image est trouvée et lue. Ensuite, la LED clignote cinq fois pour indiquer au photographe qu'il est presque temps de commencer la capture. Le bitmap est lu en parcourant d'abord chaque ligne, et à l'intérieur de chaque ligne, chaque colonne est lue. En connaissant la ligne et la colonne actuelles, les moteurs pas à pas peuvent être déplacés vers ces mêmes positions. A chaque position, la LED est changée en la couleur de ce pixel correspondant.
(re)-créer une image
Après avoir inséré la carte SD et branché une source d'alimentation 12v pour les moteurs, il était temps d'allumer la machine. Sur mon appareil photo, je l'ai réglé pour un temps d'exposition de 20 secondes, une ouverture de F36, une sensibilité ISO de 100 et une compensation d'exposition de -5 diaphragmes pour minimiser les effets d'image fantôme. La première image dessinée était une pokeball, vue ici :
Bien qu'elle soit un peu floue, la forme est toujours clairement visible. Ensuite, il a créé un bitmap de cœur :
Étant donné que cette image ne mesurait que 9 x 9 pixels, chaque pixel individuel est beaucoup moins défini. Enfin, j'ai peint une image de Mario sautant :
Cette image a des images fantômes importantes, principalement en raison de l'abondance de pixels aux couleurs vives.
Idées futures d'améliorations
Les peintures lumineuses que j'ai créées se sont avérées bien meilleures que je ne le pensais au départ, mais il y a encore de la place pour des améliorations. La principale chose que je voudrais faire est de réduire la quantité de flou en faisant bouger la LED lorsqu'elle est sombre, puis ne s'allume qu'à l'arrêt. Cette technique améliorerait grandement la clarté des images recréées.
Code
- Programme de Light Painting
Programme Light PaintingC/C++
//Fonction de lecture Bitmap partiellement d'Adafruit#include#include #include "DRV8825.h"#define MOTOR_STEPS 200#define RPM 150#define MICROSTEPS 4//pin definitions#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 pour inverser la direction#_define Y_D // 1 ou -1 pour inverser la direction#define STEPS_PER_MM (3.75 * MICROSTEPS) //pas nécessaires pour déplacer 1mm#define SPACE_BETWEEN_POSITIONS 5 //5mm par mouvement#define R A0#define G A1#define B A2#define 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_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){ Fichier bitmap =dir.openNextFile(); if(!bitmap){ break; } paintBitmap(bitmap); retard (15000); } }#define BUFFPIXEL 20void paintBitmap(File bmpFile){ int bmpWidth, bmpHeight; uint8_t bmpDepth; uint32_t bmpImageOffset; uint32_t rowSize; // Pas toujours =bmpWidth; peut avoir un remplissage uint8_t sdbuffer[3 * BUFFPIXEL]; // tampon de pixels (R+V+B par pixel) uint8_t buffidx =sizeof(sdbuffer); // Position actuelle dans sdbuffer booléen goodBmp =false; // Définie sur true lors de l'analyse d'en-tête valide boolean flip =true; // BMP est stocké de bas en haut int w, h, row, col; uint8_t r, g, b; uint32_t pos =0, startTime =millis(); Serial.println(); Serial.print("Chargement de l'image '"); Serial.print(bmpFile.name()); Serial.println('\''); // Ouvrir le fichier demandé sur la carte SD // Analyser l'en-tête BMP if (read16(bmpFile) ==0x4D42) { // Signature BMP Serial.print("Taille du fichier :"); Serial.println(read32(bmpFile)); (void)read32(bmpFile); // Lire et ignorer les octets du créateur bmpImageOffset =read32(bmpFile); // Début des données d'image Serial.print("Image Offset:"); Serial.println(bmpImageOffset, DEC); // Lire l'en-tête DIB Serial.print("Taille de l'en-tête :"); Serial.println(read32(bmpFile)); bmpWidth =read32(bmpFile); bmpHeight =read32(bmpFile); if (read16(bmpFile) ==1) { // # plans -- doit être '1' bmpDepth =read16(bmpFile); // bits par pixel Serial.print("Bit Depth:"); Serial.println(bmpDepth); if ((bmpDepth ==24) &&(read32(bmpFile) ==0)) { // 0 =goodBmp non compressé =true; // Format BMP pris en charge -- continuez ! Serial.print("Taille de l'image :"); Serial.print(bmpWidth); Serial.print('x'); Serial.println(bmpHeight); // Les lignes BMP sont remplies (si nécessaire) jusqu'à la limite de 4 octets rowSize =(bmpWidth * 3 + 3) &~3; // Si bmpHeight est négatif, l'image est dans l'ordre descendant. // Ce n'est pas canon mais a été observé dans la nature. if (bmpHeight <0) { bmpHeight =-bmpHeight; flip =faux; } // Zone de recadrage à charger w =bmpWidth; h =bmpHauteur ; if(bmpWidth*bmpHeight>290){ //Trop volumineux Serial.println("Le fichier est trop volumineux pour être imprimé."); retourner; } for(uint8_t i=0; i<5;i++){ analogWrite(R, 150); retard (500); analogWrite(R, 0); retard (500); } for (row =0; row =sizeof(sdbuffer)) { // En effet bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx =0; // Définir l'index au début } // Convertir le pixel du format BMP au format TFT, appuyer pour afficher b =sdbuffer[buffidx++]; g =sdbuffer[buffidx++]; r =sdbuffer[buffidx++]; moveToPosition(col, ligne); activerLED(r,g,b); // optimisé ! //tft.pushColor(tft.Color565(r,g,b)); } // pixel de fin analogWrite(R, 0); analogWrite(G, 0); analogWrite(B, 0); } // fin de la ligne de balayage Serial.print("Chargé dans "); Serial.print(millis() - startTime); Serial.println("ms"); } // fin goodBmp } } bmpFile.close(); déplacerVersPosition(0,0); if (!goodBmp) Serial.println("Format BMP non reconnu.");}uint16_t read16(File f) { uint16_t result; ((uint8_t *)&result)[0] =f.read(); // LSB ((uint8_t *)&result)[1] =f.read(); // MSB renvoie le résultat ;}uint32_t read32(File f) { uint32_t result ; ((uint8_t *)&result)[0] =f.read(); // LSB ((uint8_t *)&result)[1] =f.read(); ((uint8_t *)&result)[2] =f.read(); ((uint8_t *)&result)[3] =f.read(); // MSB renvoie le résultat;}void activateLED(int r, int g, int b){ Serial.print(F("LED a la valeur :")); 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("Positions pas à pas :"); 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);}
Pièces et boîtiers personnalisés
Schémas
Processus de fabrication
- Qu'est-ce qui nous a ramenés à l'utilisation de matériaux naturels dans la conception de produits ?
- Capteur de mouvement utilisant Raspberry Pi
- Guide du responsable de la maintenance pour la création et l'utilisation des AMDEC
- Envoyer des données de capteur d'un Arduino à un autre à l'aide de Firebase
- Échangez deux variables Python sans en utiliser une troisième
- Mélodie de la danse du cercueil
- Réutiliser les anciennes télécommandes
- Contrôler Arduino Rover à l'aide de Firmata et de la manette Xbox One
- 8x éclairage LED par le son