Tentang proyek ini
Ide
Setelah menonton beberapa video dan melihat banyak artikel tentang lukisan cahaya, saya memutuskan untuk mencobanya. Lukisan cahaya melibatkan penggunaan kamera dengan waktu pencahayaan yang sangat lama untuk menangkap sumber cahaya yang kecil. Hal ini memungkinkan satu cahaya menjadi terangkai menjadi guratan panjang dalam satu gambar.
Tetapi bagaimana jika seseorang ingin membuat gambar yang lebih detail atau menggunakan banyak warna berbeda? Beginilah cara saya mendapatkan ide untuk membuat mesin CNC 2 sumbu yang memiliki LED RGB tunggal yang dapat mengubah warna dan "melukis" gambar.
Rencana
Proyek ini akan membutuhkan empat komponen utama untuk bekerja:mesin CNC 2 sumbu, LED RGB, kartu SD, dan kamera yang mampu mengambil bidikan dengan eksposur lama. Pertama, Arduino Mega akan membaca kartu SD dan menemukan bitmap untuk dicetak.
Kemudian, itu akan melintasi secara horizontal dan menyalakan LED yang sesuai sambil juga bergerak ke bawah satu baris setiap kali lebar gambar terlampaui. Terakhir, ia akan menunggu sebentar dan kemudian mencari bitmap berikutnya, akhirnya berhenti setiap kali tidak ada lagi gambar untuk dibuat.
Membangun Rig
Karena pengalaman saya merancang dan membangun mesin CNC, langkah ini tidak terlalu sulit. Saya ingin membuat sesuatu yang modular yang juga dapat diperluas untuk proyek lain, jadi saya memilih desain sederhana yang menggunakan dua timing belt yang dipasang pada palang yang bergerak di sepanjang ekstrusi aluminium paralel.
Ini memungkinkan panjang setiap sumbu sangat dapat disesuaikan. Ujung sumbu X memiliki penutup ujung yang dicetak 3D, salah satunya memiliki dudukan untuk motor stepper sumbu X dan bantalan.
Membaca Bitmap
Saya memilih format file bitmap karena kesederhanaannya dan betapa mudahnya untuk dibaca. Berdasarkan format file, ada beberapa alamat penting dalam file itu sendiri yang harus dibaca. Ini adalah 0x12 (lebar), 0x16 (tinggi), 0x1C (kedalaman warna), 0xA (lokasi data piksel), dan terakhir 0x36 (tempat data piksel biasanya berada).
Data dibaca dalam potongan dua atau empat byte (16 atau 32 bit), yang juga memajukan penunjuk ke alamat berikutnya. Fungsi baca melewati dan mengambil semua data penting, termasuk offset dan ukuran. Kemudian ia melewati dan membaca setiap piksel, baris demi baris.
Mempersiapkan Gambar
Karena sebagian besar kamera dibatasi hingga maksimum 30 detik waktu pencahayaan, ada batas sekitar 288 piksel total yang dapat ditampilkan dalam jumlah waktu tersebut. Ini setara dengan sekitar gambar 18 x 16. Untuk membuat gambar saya, saya memuat gimp dan mulai membuat seni piksel yang sangat sederhana. Ini termasuk Pokéball, hati, dan Mario yang melompat. Kemudian saya menempatkan ketiga gambar ini ke dalam direktori yang disebut "bitmaps" di direktori root kartu SD. Program membaca semua gambar dari folder ini.
Program Melukis
Karena motor stepper tidak memiliki sistem umpan balik pemosisian internal, posisinya harus dilacak oleh perangkat lunak. Program yang saya tulis melacak posisi LED dengan sistem grid untuk memudahkan penskalaan. Ketika Arduino Mega dinyalakan, posisi stepper diatur ke 0, 0 dan kemudian gambar pertama ditemukan dan dibaca. Kemudian, LED berkedip lima kali untuk memberi tahu fotografer bahwa sudah hampir waktunya untuk mulai memotret. Bitmap dibaca dengan perulangan pertama melalui setiap baris, dan di dalam setiap baris, setiap kolom dibaca. Dengan mengetahui baris dan kolom saat ini, motor stepper dapat dipindahkan ke posisi yang sama. Di setiap posisi, LED diubah menjadi warna piksel yang sesuai.
(kembali)-Membuat Gambar
Setelah memasukkan kartu SD dan mencolokkan sumber daya 12v untuk motor, saatnya untuk menghidupkan mesin. Pada kamera saya, saya mengaturnya untuk waktu eksposur 20 detik, aperture F36, ISO 100, dan kompensasi eksposur -5 stop untuk meminimalkan efek ghosting. Gambar pertama yang diambil adalah pokeball, lihat di sini:
Meski agak buram, bentuknya masih bisa terlihat jelas. Kemudian ia membuat bitmap hati:
Karena gambar ini hanya berukuran 9 kali 9 piksel, setiap piksel individu jauh lebih sedikit didefinisikan. Terakhir, saya melukis gambar Mario melompat:
Gambar ini memiliki ghosting yang tinggi, terutama karena banyaknya piksel berwarna cerah.
Ide Masa Depan untuk Perbaikan
Lukisan cahaya yang saya buat ternyata jauh lebih baik daripada yang saya kira sebelumnya, tetapi masih ada ruang untuk perbaikan. Hal utama yang ingin saya lakukan adalah mengurangi jumlah keburaman dengan membuat LED bergerak saat gelap dan kemudian hanya menyala saat diam. Teknik ini akan sangat meningkatkan kejernihan gambar yang dibuat ulang.
Kode
Program Lukisan RinganC/C++
//Fungsi pembacaan bitmap sebagian dari Adafruit#include #include #include "DRV8825.h"#define MOTOR_STEPS 200#define RPM 150#define MICROSTEPS 4//pin definition#define STEPPER_X_DIR 7#menentukan STEPPER_X_STEP 6#menentukan STEPPER_X_EN 8#menentukan STEPPER_Y_DIR 4#menentukan STEPPER_Y_STEP 5#menentukan STEPPER_Y_EN 12#menentukan X 0#menentukan Y 1#menentukan X_DIR_FLAG-1 untuk membalik arah -1 Y//1# 1 atau -1 untuk membalik arah#menentukan STEPS_PER_MM (3,75 * MICROSTEPS) //langkah yang diperlukan untuk memindahkan 1mm#menentukan SPACE_BETWEEN_POSITIONS 5 //5mm per gerakan#menentukan R A0#menentukan G A1#menentukan B A2#menentukan SD_CS 22int posisi saat ini[] ={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.mulai(SD_CS); buatBitmap(); stepperX.disable(); stepperY.disable(); while(1);}void loop() {}void createBitmaps(){ File dir =SD.open("bitmaps"); while(true){ File bitmap =dir.openNextFile(); if(!bitmap){ istirahat; } paintBitmap(bitmap); penundaan (15000); } }#define BUFFPIXEL 20void paintBitmap(File bmpFile){ int bmpWidth, bmpHeight; uint8_t bmpDepth; uint32_t bmpImageOffset; uint32_t rowSize; // Tidak selalu =bmpWidth; mungkin memiliki padding uint8_t sdbuffer[3 * BUFFPIXEL]; // buffer piksel (R+G+B per piksel) uint8_t buffidx =sizeof(sdbuffer); // Posisi saat ini di sdbuffer boolean goodBmp =false; // Setel ke true pada header yang valid parse boolean flip =true; // BMP disimpan dari bawah ke atas int w, h, baris, col; uint8_t r, g, b; uint32_t pos =0, startTime =milis(); Serial.println(); Serial.print("Memuat gambar '"); Serial.print(bmpFile.name()); Serial.println('\''); // Buka file yang diminta pada kartu SD // Parse header BMP if (read16(bmpFile) ==0x4D42) { // BMP signature Serial.print("File size:"); Serial.println(read32(bmpFile)); (batal) read32(bmpFile); // Baca &abaikan byte pembuat bmpImageOffset =read32(bmpFile); // Mulai dari data gambar Serial.print("Offset Gambar:"); Serial.println(bmpImageOffset, DEC); // Baca header DIB Serial.print("Ukuran header:"); Serial.println(read32(bmpFile)); bmpWidth =read32(bmpFile); bmpHeight =read32(bmpFile); if (read16(bmpFile) ==1) { // # planes -- harus '1' bmpDepth =read16(bmpFile); // bit per piksel Serial.print("Kedalaman Bit:"); Serial.println(Kedalaman bmp); if ((bmpDepth ==24) &&(read32(bmpFile) ==0)) { // 0 =tidak terkompresi goodBmp =true; // Format BMP yang didukung -- lanjutkan! Serial.print("Ukuran Gambar :"); Serial.print(lebar bmp); Serial.print('x'); Serial.println(bmpHeight); // Baris BMP diisi (jika diperlukan) hingga batas 4-byte rowSize =(bmpWidth * 3 + 3) &~3; // Jika bmpHeight negatif, gambar dalam urutan top-down. // Ini bukan kanon tetapi telah diamati di alam liar. if (bmpHeight <0) { bmpHeight =-bmpHeight; membalik =salah; } // Pangkas area yang akan dimuat w =bmpWidth; h =tinggi bmp; if(bmpWidth*bmpHeight>290){ //Too large Serial.println("File terlalu besar untuk dicetak."); kembali; } untuk(uint8_t i=0; i<5;i++){ analogWrite(R, 150); penundaan (500); analogWrite(R, 0); penundaan (500); } for (baris =0; baris=sizeof(sdbuffer)) { // Memang bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx =0; // Atur indeks ke awal } // Konversi piksel dari format BMP ke TFT, tekan untuk menampilkan b =sdbuffer[buffidx++]; g =sdbuffer[buffidx++]; r =sdbuffer[buffidx++]; moveToPosition(kolom, baris); aktifkanLED(r,g,b); // dioptimalkan! //tft.pushColor(tft.Color565(r,g,b)); } // akhir piksel analogWrite(R, 0); analogWrite(G, 0); analogWrite(B, 0); } // end scanline Serial.print("Dimuat di "); Serial.print(millis() - startTime); Serial.println("ms"); } // end goodBmp } } bmpFile.close(); moveToPosition(0,0); if (!goodBmp) Serial.println("Format BMP tidak dikenali.");}uint16_t read16(File f) { hasil uint16_t; ((uint8_t *)&result)[0] =f.read(); // LSB ((uint8_t *)&hasil)[1] =f.read(); // MSB mengembalikan hasil;}uint32_t read32(File f) { hasil uint32_t; ((uint8_t *)&result)[0] =f.read(); // LSB ((uint8_t *)&hasil)[1] =f.read(); ((uint8_t *)&result)[2] =f.read(); ((uint8_t *)&result)[3] =f.read(); // MSB mengembalikan hasil;}void ActivateLED(int r, int g, int b){ Serial.print(F("LED memiliki nilai:")); 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); posisi saat ini[X] =x; posisi saat ini[Y] =y; Serial.print("Posisi stepper:"); Serial.print(Posisi saat ini[X]); Serial.print(", "); Serial.println(currentPositions[Y]);}void init_steppers(){ stepperX.begin(RPM); stepperX.setEnableActiveState(RENDAH); stepperX.enable(); stepperX.setMicrostep(MICROSTEPS); stepperY.begin(RPM); stepperY.setEnableActiveState(RENDAH); stepperY.aktifkan(); stepperY.setMicrostep(MICROSTEPS);}
Suku cadang dan penutup khusus
Skema