工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

部品と材料

NEMA17ステッピングモーター ×2
Theremino DRV8825ステッピングモータードライバー ×2
SDカードリーダー ×1
Arduino Mega 2560 ×1
タイミングベルトとVホイール各種 ×1式

必要な工具と機械

アプリとオンラインサービス

プロジェクトについて

アイデア

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

ライトペインティング(長時間露光で光の軌跡を撮影する手法)の動画や記事を見て、自分でも挑戦したくなりました。通常は小さな光源を動かして線を描きますが、もっと詳細な絵や多色表現をしたいと思い、単一のRGB LEDを搭載した2軸CNCマシンを自作して「光で絵を描く」システムを構築しました。

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

計画

このプロジェクトには4つの主要コンポーネントが必要です:

  1. 2軸CNCマシン
  2. RGB LED
  3. SDカード
  4. 長時間露光撮影可能なカメラ

Arduino MegaがSDカードからビットマップ画像を読み取り、水平方向に移動しながら対応するピクセルの色でLEDを点灯させ、1行終わるごとに垂直方向に1ステップ下がります。全ピクセル描画後、次のビットマップを探索し、画像がなくなるまで繰り返します。

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

リグの構築

CNCマシンの設計・製作経験を活かし、モジュラーで拡張性のあるシンプルな設計にしました。平行なアルミ押出材にクロスバーを取り付け、2本のタイミングベルトでX/Y軸を駆動します。これにより各軸の長さを自由にカスタマイズ可能です。X軸の両端には3Dプリントしたエンドキャップを取り付け、片側にはX軸ステッピングモーターとベアリングのマウントを設けています。

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く
シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く
シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

ビットマップの読み取り

シンプルで読み取りやすいBMP形式を採用しました。ファイルヘッダー内の重要なアドレス(0x12:幅、0x16:高さ、0x1C:色深度、0xA:ピクセルデータ位置、0x36:通常のピクセルデータ開始位置)を読み取り、16bitまたは32bit単位でデータを取得しながらピクセルを行ごとに読み込みます。

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

画像の準備

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

一般的なカメラの最大露光時間は30秒程度のため、描画可能なピクセル数は約288個(≒18×16ピクセル)に制限されます。GIMPでシンプルなピクセルアート(ポケボール、ハート、ジャンプするマリオ)を作成し、SDカードの「bitmaps」フォルダに配置します。プログラムはこのフォルダ内の全画像を順に読み込みます。

描画プログラム

ステッピングモーターには位置フィードバックがないため、ソフトウェアでグリッド座標系を用いてLED位置を管理します。起動時に原点(0,0)に設定し、最初の画像を読み込みます。LEDを5回点滅させて撮影開始の合図を送り、ビットマップを行・列の順で走査しながらステッピングモーターを対応位置に移動、各位置でピクセルの色に合わせてLEDを点灯させます。

画像の再現

SDカードを挿入し、モーター用12V電源を投入して起動。カメラ設定:露光20秒、絞りF36、ISO100、露出補正-5段(ゴースト軽減)。

最初のポケボール:

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く
少しぼやけていますが形は明確です。

ハート(9×9ピクセル):

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く
ピクセル数が少ないため個々のピクセルは不明瞭です。

ジャンプするマリオ:

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く
明るい色のピクセルが多いためゴースト(残像)が目立ちます。

今後の改善案

コード

Light Painting Program (C/C++)

// 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)
#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;
  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) { // BMP signature
    Serial.print("File size: "); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // creator bytes
    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) { // planes
      bmpDepth = read16(bmpFile);
      Serial.print("Bit Depth: "); Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // uncompressed
        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(); // 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);
}

CADファイル

カスタムパーツとエンクロージャーの設計ファイル

回路図

シングルLEDライトペインティング:自作モーター駆動システムで動的な画像を描く

製造プロセス

  1. FMEAマスターガイド:メンテナンスマネージャーのための故障モード影響解析構築・活用ステップバイステップ
  2. プロダクトデザインで自然素材への回帰が進む理由
  3. Raspberry PiでPIRモーションセンサーとLEDを制御
  4. Firebase経由で2台のArduino間を温湿度データで連携するIoTチュートリアル
  5. Arduinoで「棺桶ダンス」メロディーを再現するプロジェクト
  6. 古いリモコンを再利用:ArduinoでDIYスマートデバイスを構築
  7. FirmataとXbox OneコントローラーでArduinoローバーをシームレス操作
  8. 音声反応型 8 LED ライトディスプレイ
  9. スマートArduino四足歩行ロボットの製作:ステップバイステップガイド
  10. LEDによる無線データ転送(Li-Fi)— Arduinoプロジェクト
  11. Pythonで一時変数なしで2変数をスワップするワンライナー