Manufaktur industri
Industri Internet of Things | bahan industri | Pemeliharaan dan Perbaikan Peralatan | Pemrograman industri |
home  MfgRobots >> Manufaktur industri >  >> Manufacturing Technology >> Proses manufaktur

Melukis Cahaya dengan LED Tunggal: Buat Gambar Dinamis dengan Sistem Motor DIY

Komponen dan Perlengkapan

Motor Stepper NEMA172
Driver Theremino DRV8825 untuk Motor Stepper Sistem Theremino2
Pembaca Kartu SD1
Arduino Mega 25601
Berbagai timing belt dan v-wheels1

Alat dan Mesin yang Diperlukan

Printer 3D (generik)

Aplikasi dan Layanan Online

Arduino IDE, GIMP, Autodesk Fusion 360

Tentang Proyek Ini

Ide

Setelah menonton beberapa video dan melihat banyak artikel tentang light painting, saya mencoba melakukannya sendiri. Light painting melibatkan penggunaan kamera dengan waktu eksposur sangat lama untuk menangkap sumber cahaya kecil. Ini memungkinkan satu cahaya menjadi garis panjang dalam satu gambar.

Tetapi bagaimana jika seseorang ingin membuat gambar yang lebih detail atau menggunakan banyak warna berbeda? Inilah bagaimana saya mendapatkan ide untuk membangun mesin CNC 2 sumbu yang memiliki satu LED RGB yang dapat berubah warna dan "melukis" gambar.

Rencana

Proyek ini membutuhkan empat komponen utama untuk bekerja: mesin CNC 2 sumbu, LED RGB, kartu SD, dan kamera yang mampu mengambil foto eksposur panjang. Pertama, Arduino Mega akan membaca kartu SD dan menemukan bitmap untuk dicetak.

Kemudian, mesin akan bergerak secara horizontal dan menyala kan LED yang sesuai sambil juga turun satu baris setiap kali lebar gambar tercapai. Terakhir, ia akan menunggu sebentar lalu mencari bitmap berikutnya, dan akhirnya berhenti ketika tidak ada gambar lagi yang akan dibuat.

Membangun Rangka

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 menetapkan desain sederhana yang menggunakan dua timing belt yang terpasang pada crossbar yang bergerak di sepanjang profil aluminium paralel. Ini memungkinkan panjang setiap sumbu sangat dapat disesuaikan. Ujung sumbu X memiliki tutup ujung hasil cetak 3D, salah satu dilengkaki dengan montir untuk motor stepper sumbu X dan bearing.

Membaca Bitmap

Saya memilih format file bitmap karena kesederhanaannya dan seberapa mudah dibacanya. 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 akhirnya 0x36 (di mana data piksel biasanya berada). Data dibaca dalam potongan yang terdiri dari dua atau empat byte (16 atau 32 bit), yang juga memajukan pointer ke alamat berikutnya. Fungsi baca melalui dan mengambil semua data penting, termasuk offset dan ukuran. Kemudian ia melalui dan membaca setiap piksel, baris per baris.

Menyiapkan Gambar

Karena sebagian besar kamera terbatas pada waktu eksposur maksimum 30 detik, ada batas sekitar 288 total piksel yang dapat ditampilkan dalam waktu tersebut. Ini setara dengan gambar sekitar 18 x 16. Untuk membuat gambar saya, saya membuka GIMP dan mulai membuat pixel art yang sangat sederhana. Ini termasuk bola Poké, hati, dan Mario yang melompat. Kemudian saya menempatkan tiga 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 posisi internal, posisinya harus dilacak oleh perangkat lunak. Program yang saya tulis melacak posisi LED dengan sistem grid untuk memungkinkan penskalaan yang mudah. 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 penggemar foto bahwa hampir waktunya untuk mulai menangkap. Bitmap dibaca dengan terlebih dahulu mengulangi setiap baris, dan dalam setiap baris, setiap kolom dibaca. Dengan mengetahui baris dan kolom saat ini, motor stepper dapat dipindahkan ke posisi yang sama. Pada setiap posisi, LED diubah menjadi warna piksel yang sesuai.

(Membuat Ulang) Gambar

Setelah memasukkan kartu SD dan menyambungkan sumber daya 12V untuk motor, saatnya untuk menyala mesin. Pada kamera saya, saya mengatur waktu eksposur 20 detik, diafragma F36, ISO 100, dan kompensasi eksposur -5 stops untuk meminimalkan efek hantu. Gambar pertama yang digambar adalah bola Poké, terlihat di sini:

Meskipun agak kabur, bentuknya masih dapat terlihat dengan jelas. Kemudian ia membuat bitmap hati:

Karena gambar ini hanya berukuran 9 x 9 piksel, setiap piksel individual kurang terdefinisi. Terakhir, saya melukis gambar Mario yang melompat:

Gambar ini memiliki hantu yang berat, terutama karena banyaknya piksel berwarna cerah.

Ide Masa Depan untuk Peningkatan

Lukisan cahaya yang saya buat ternyata jauh lebih baik dari yang saya kira pada awalnya, tetapi masih ada ruang untuk peningkatan. Hal utama yang ingin saya lakukan adalah mengurangi jumlah keburaman dengan membuat LED bergerak saat padam lalu hanya menyala saat diam. Teknik ini akan sangat meningkatkan kejelasan gambar yang dibuat ulang.

Kode

Program Melukis Cahaya

//Bitmap reading function partially from Adafruit #include <SD.h> #include <SPI.h> #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 or -1 to flip direction #define Y_DIR_FLAG 1 //1 or -1 to flip direction #define STEPS_PER_MM (3.75 * MICROSTEPS) //steps needed to move 1mm #define SPACE_BETWEEN_POSITIONS 5 //5mm per move #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; // Not always = bmpWidth; may have padding uint8_t sdbuffer[3 * BUFFPIXEL]; // pixel buffer (R+G+B per pixel) uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer boolean goodBmp = false; // Set to true on valid header parse boolean flip = true; // BMP is stored bottom-to-top 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('\''); // Open requested file on SD card // Parse BMP header if (read16(bmpFile) == 0x4D42) { // BMP signature Serial.print("File size: "); Serial.println(read32(bmpFile)); (void)read32(bmpFile); // Read & ignore creator bytes bmpImageOffset = read32(bmpFile); // Start of image data Serial.print("Image Offset: "); Serial.println(bmpImageOffset, DEC); // Read DIB header Serial.print("Header size: "); Serial.println(read32(bmpFile)); bmpWidth = read32(bmpFile); bmpHeight = read32(bmpFile); if (read16(bmpFile) == 1) { // # planes -- must be '1' bmpDepth = read16(bmpFile); // bits per pixel Serial.print("Bit Depth: "); Serial.println(bmpDepth); if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed goodBmp = true; // Supported BMP format -- proceed! Serial.print("Image size: "); Serial.print(bmpWidth); Serial.print('x'); Serial.println(bmpHeight); // BMP rows are padded (if needed) to 4-byte boundary rowSize = (bmpWidth * 3 + 3) & ~3; // If bmpHeight is negative, image is in top-down order. // This is not canon but has been observed in the wild. if (bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; } // Crop area to be loaded w = bmpWidth; h = bmpHeight; if(bmpWidth*bmpHeight>290){ //Too large 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++) { // For each scanline... moveToPosition(0, row); // Seek to start of scan line. It might seem labor- // intensive to be doing this on every line, but this // method covers a lot of gritty details like cropping // and scanline padding. Also, the seek only takes // place if the file position actually needs to change // (avoids a lot of cluster math in SD library). if (flip) // Bitmap is stored bottom-to-top order (normal BMP) pos = bmpImageOffset + (bmpHeight - 1 - row) * rowSize; else // Bitmap is stored top-to-bottom pos = bmpImageOffset + row * rowSize; if (bmpFile.position() != pos) { // Need seek? bmpFile.seek(pos); buffidx = sizeof(sdbuffer); // Force buffer reload } // optimize by setting pins now for (col = 0; col<w; col++) { // For each pixel... // Time to read more pixel data? if (buffidx >= sizeof(sdbuffer)) { // Indeed bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx = 0; // Set index to beginning } // Convert pixel from BMP to TFT format, push to display b = sdbuffer[buffidx++]; g = sdbuffer[buffidx++]; r = sdbuffer[buffidx++]; moveToPosition(col, row); activateLED(r,g,b); // optimized! //tft.pushColor(tft.Color565(r,g,b)); } // end pixel analogWrite(R, 0); analogWrite(G, 0); analogWrite(B, 0); } // end scanline Serial.print("Loaded in "); Serial.print(millis() - startTime); Serial.println(" ms"); } // end goodBmp } } 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(); // LSB ((uint8_t *)&result)[1] = f.read(); ((uint8_t *)&result)[2] = f.read(); ((uint8_t *)&result)[3] = f.read(); // MSB 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); } 

Bagian dan Enclosure Khusus

Skematik

Melukis Cahaya dengan LED Tunggal: Buat Gambar Dinamis dengan Sistem Motor DIY

Proses manufaktur

  1. Mengapa Desainer Kembali ke Bahan Alam dalam Desain Produk
  2. Mengontrol LED dengan Sensor Gerak PIR pada Raspberry Pi
  3. Menguasai FMEA: Panduan Langkah demi Langkah untuk Manajer Pemeliharaan dalam Membuat dan Menggunakan Analisis Modus dan Efek Kegagalan
  4. Mentransfer Data Suhu dan Kelembaban antara Dua Arduino menggunakan Firebase
  5. Tukar Dua Variabel Python dengan Mudah Tanpa Variabel Sementara
  6. Proyek Melodi Arduino Coffin Dance – Buat dan Mainkan Lagu Viral
  7. Hidupkan Kembali Remote Lama: Buat Perangkat Pintar DIY dengan Arduino
  8. Kendalikan Rover Arduino dengan Mulus menggunakan Firmata dan Gamepad Xbox One
  9. Tampilan Lampu 8 LED Teraktivasi Suara
  10. Membuat Robot Quadruped Arduino Cerdas: Panduan Langkah demi Langkah