亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

单 LED 光绘:自制电动双轴系统绘制动态图像

元件与材料

NEMA17 步进电机×2
DRV8825 步进电机驱动板×2
SD 卡读取模块×1
Arduino Mega 2560×1
同步带、V 型轮等机械传动件×1 套

所需工具

3D 打印机(打印结构件)

软件与在线服务

项目简介

创意来源

光绘摄影利用长曝光记录移动光源轨迹。若想用单一光源绘制多色、高细节图像该如何?答案是:构建一台搭载 RGB LED 的二轴 CNC 机械臂,让 LED 在空间中按像素点移动并变色,配合相机长曝光,即可“画”出图像。
单 LED 光绘:自制电动双轴系统绘制动态图像 单 LED 光绘:自制电动双轴系统绘制动态图像

实施计划

四大核心模块:二轴 CNC 机构、RGB LED、SD 卡存储、长曝光相机。Arduino Mega 从 SD 卡读取位图,控制 X/Y 轴步进电机移动 LED 至各像素坐标并点亮对应颜色,逐行扫描完成一幅图,随后自动寻找下一张图片。

单 LED 光绘:自制电动双轴系统绘制动态图像

机械结构制作

采用模块化设计:两根同步带驱动横梁在平行铝型材上滑动,行程可按需扩展。X 轴末端 3D 打印端盖,一侧安装步进电机与轴承。
单 LED 光绘:自制电动双轴系统绘制动态图像 单 LED 光绘:自制电动双轴系统绘制动态图像 单 LED 光绘:自制电动双轴系统绘制动态图像

位图读取原理

选用 BMP 格式因其结构简单。关键文件偏移地址:0x12(宽)、0x16(高)、0x1C(色深)、0xA(像素数据起始)、0x36(典型像素数据位置)。程序按 16/32 位分块读取,解析头部信息后逐行逐像素获取 RGB 值。
单 LED 光绘:自制电动双轴系统绘制动态图像

图像预处理

受限于相机最长 30 秒曝光,单次绘制像素上限约 288 个(约 18×16 像素)。使用 GIMP 绘制极简像素画(宝可梦球、爱心、跳跃马里奥),存入 SD 卡根目录下的 “bitmaps” 文件夹。
单 LED 光绘:自制电动双轴系统绘制动态图像

绘制程序逻辑

软件维护虚拟网格坐标追踪 LED 位置。启动后归零,LED 闪烁 5 次提示摄影师准备。逐行逐列读取像素,驱动步进电机移动至对应网格坐标,设置 LED 颜色。像素间距 5 mm,微步细分 4,每毫米 3.75 步。

实拍效果

相机设定:20 秒曝光、F36 光圈、ISO 100、-5EV 曝光补偿(抑制鬼影)。
单 LED 光绘:自制电动双轴系统绘制动态图像 单 LED 光绘:自制电动双轴系统绘制动态图像 单 LED 光绘:自制电动双轴系统绘制动态图像

宝可梦球轮廓清晰;爱心因分辨率低像素感强;马里奥因亮色像素多出现明显鬼影。

未来改进方向

主要优化:让 LED 在移动过程中熄灭,仅在定位静止时点亮,大幅减少运动模糊,提升成像锐度。

完整代码

// 位图读取部分参考 Adafruit
#include <SD.h>
#include <SPI.h>
#include "DRV8825.h"
#define MOTOR_STEPS 200
#define RPM 150
#define MICROSTEPS 4
// 引脚定义
#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
#define Y_DIR_FLAG 1
#define STEPS_PER_MM (3.75 * MICROSTEPS)
#define SPACE_BETWEEN_POSITIONS 5
#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) {
    Serial.print("File size: "); Serial.println(read32(bmpFile));
    (void)read32(bmpFile);
    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) {
      bmpDepth = read16(bmpFile);
      Serial.print("Bit Depth: "); Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) {
        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(); ((uint8_t *)&result)[1] = f.read(); 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(); 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);
}

原理图

单 LED 光绘:自制电动双轴系统绘制动态图像

制造工艺

  1. 掌握FMEA:维护经理构建与使用失效模式与影响分析的分步指南
  2. 产品设计回归天然材料:美学、可持续与制造挑战的深度对话
  3. 树莓派 PIR 运动传感器控制 LED 实战指南
  4. 两块 Arduino 通过 Firebase 传递温湿度数据
  5. Arduino复刻“棺材舞”旋律:蜂鸣器+三色LED灯光秀完整教程
  6. 焕新旧遥控器:用 Arduino 打造 DIY 智能红外控制设备
  7. Firmata + Xbox 手柄无线遥控 Arduino 小车:UWP 应用桥接方案
  8. 声控 8 路 LED 灯光秀:Arduino 入门项目
  9. 从零构建智能 Arduino 四足机器人:完整指南
  10. 可见光通信 (Li-Fi) 实验:Arduino 实现 LED 无线数据传输
  11. Bolt IoT + Arduino UNO 实现 6 路 LED 无级调光
  12. Python 优雅交换两变量:无需临时变量的一行代码