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

废旧 DVD 光驱改装迷你 CNC 2D 绘图机 —— 基于 L293D 电机扩展板

所需组件与物料

所需软件

制作步骤

1. 机械结构组装

拆解两台 DVD 光驱,取出带步进电机的滑台(含导轨、丝杠、激光头座)。滑台即为现成的直线运动模块,分别作为 X 轴与 Y 轴。

废旧 DVD 光驱改装迷你 CNC 2D 绘图机 —— 基于 L293D 电机扩展板

在空光驱外壳上钻孔,用螺栓将 X/Y 滑台垂直固定(X 轴横向,Y 轴纵向承载 X 轴)。舵机安装在 X 轴滑块上,推动笔杆上下作为 Z 轴。

废旧 DVD 光驱改装迷你 CNC 2D 绘图机 —— 基于 L293D 电机扩展板

2. G-code 生成流程

  1. 安装 Inkscape 0.48.5(官方旧版下载)。
  2. 安装 gcodetools 插件(GitHub),按说明配置刀具参数、画布范围。
  3. 在 Inkscape 中绘制/导入矢量图(SVG),用扩展生成 G-code 文件(.gcode)。
  4. 参考 YouTube 教程 gcodetools inkscape tutorial 完整操作流程。

3. 运行绘图

  1. Arduino IDE 上传 Arduino CNC 固件(见下方代码)。
  2. Processing IDE 打开 gctrl.pde(见下方代码),点击运行。
  3. Processing 窗口操作:
    • 按 p 选择串口
    • 按 1/2/3 设定点动速度
    • 按 g 加载 .gcode 文件
    • 回车开始绘图

Arduino CNC 固件

#include <Servo.h>
#include <AFMotor.h>

#define LINE_BUFFER_LENGTH 512
char STEP = MICROSTEP;

const int penZUp = 95;    // 舵机抬笔角度
const int penZDown = 83;  // 舵机落笔角度
const int penServoPin = 10;

const int stepsPerRevolution = 48;  // DVD 步进电机步距角 7.5° → 48 步/圈

Servo penServo;
AF_Stepper myStepperY(stepsPerRevolution, 1);  // M1 接口
AF_Stepper myStepperX(stepsPerRevolution, 2);  // M2 接口

struct point { float x, y, z; };
point actuatorPos;

float StepInc = 1;
int StepDelay = 0, LineDelay = 0, penDelay = 50;
float StepsPerMillimeterX = 100.0;  // 需实测标定:走 100 步量距离
float StepsPerMillimeterY = 100.0;
float Xmin = 0, Xmax = 40, Ymin = 0, Ymax = 40, Zmin = 0, Zmax = 1;
float Xpos = Xmin, Ypos = Ymin, Zpos = Zmax;
boolean verbose = false;

void setup() {
  Serial.begin(9600);
  penServo.attach(penServoPin);
  penServo.write(penZUp); delay(100);
  myStepperX.setSpeed(600);
  myStepperY.setSpeed(600);
  Serial.println("Mini CNC Plotter alive and kicking!");
  Serial.print("X range: "); Serial.print(Xmin); Serial.print(" to "); Serial.print(Xmax); Serial.println(" mm.");
  Serial.print("Y range: "); Serial.print(Ymin); Serial.print(" to "); Serial.print(Ymax); Serial.println(" mm.");
}

void loop() {
  delay(100);
  char line[LINE_BUFFER_LENGTH]; char c;
  int lineIndex = 0; bool lineIsComment = false, lineSemiColon = false;

  while (1) {
    while (Serial.available() > 0) {
      c = Serial.read();
      if (c == '\n' || c == '\r') {
        if (lineIndex > 0) {
          line[lineIndex] = '\0';
          if (verbose) { Serial.print("Received: "); Serial.println(line); }
          processIncomingLine(line, lineIndex);
          lineIndex = 0;
        }
        lineIsComment = lineSemiColon = false;
        Serial.println("ok");
      } else {
        if (lineIsComment || lineSemiColon) {
          if (c == ')') lineIsComment = false;
        } else {
          if (c <= ' ') { /* ignore whitespace */ }
          else if (c == '/') { /* ignore */ }
          else if (c == '(') { lineIsComment = true; }
          else if (c == ';') { lineSemiColon = true; }
          else if (lineIndex >= LINE_BUFFER_LENGTH-1) { Serial.println("ERROR - lineBuffer overflow"); lineIsComment = lineSemiColon = false; }
          else if (c >= 'a' && c <= 'z') { line[lineIndex++] = c - 'a' + 'A'; }
          else { line[lineIndex++] = c; }
        }
      }
    }
  }
}

void processIncomingLine(char* line, int charNB) {
  int currentIndex = 0; char buffer[64];
  point newPos = {0,0,0};

  while (currentIndex < charNB) {
    switch (line[currentIndex++]) {
      case 'U': penUp(); break;
      case 'D': penDown(); break;
      case 'G':
        buffer[0] = line[currentIndex++]; buffer[1] = '\0';
        switch (atoi(buffer)) {
          case 0: case 1:  // G0/G1 移动
            char* indexX = strchr(line+currentIndex, 'X');
            char* indexY = strchr(line+currentIndex, 'Y');
            if (!indexY) { newPos.x = atof(indexX+1); newPos.y = actuatorPos.y; }
            else if (!indexX) { newPos.y = atof(indexY+1); newPos.x = actuatorPos.x; }
            else { newPos.y = atof(indexY+1); *indexY='\0'; newPos.x = atof(indexX+1); }
            drawLine(newPos.x, newPos.y);
            actuatorPos.x = newPos.x; actuatorPos.y = newPos.y;
            break;
        }
        break;
      case 'M':
        buffer[0]=line[currentIndex++]; buffer[1]=line[currentIndex++]; buffer[2]=line[currentIndex++]; buffer[3]='\0';
        switch (atoi(buffer)) {
          case 300:
            char* indexS = strchr(line+currentIndex, 'S');
            float Spos = atof(indexS+1);
            if (Spos == 30) penDown();
            if (Spos == 50) penUp();
            break;
          case 114:  // M114 回报位置
            Serial.print("Absolute position: X="); Serial.print(actuatorPos.x);
            Serial.print(" Y="); Serial.println(actuatorPos.y);
            break;
          default: Serial.print("Command not recognized: M"); Serial.println(buffer);
        }
        break;
    }
  }
}

void drawLine(float x1, float y1) {
  // 限幅
  x1 = constrain(x1, Xmin, Xmax);
  y1 = constrain(y1, Ymin, Ymax);
  if (verbose) { Serial.print("Target: "); Serial.print(x1); Serial.print(","); Serial.println(y1); }

  long x0 = Xpos, y0 = Ypos;
  long x1s = (long)(x1 * StepsPerMillimeterX);
  long y1s = (long)(y1 * StepsPerMillimeterY);
  long dx = abs(x1s - x0), dy = abs(y1s - y0);
  int sx = (x0 < x1s) ? StepInc : -StepInc;
  int sy = (y0 < y1s) ? StepInc : -StepInc;
  long over = 0, i;

  if (dx > dy) {
    for (i=0; i= dx) { over -= dx; myStepperY.onestep(sy, STEP); }
      delay(StepDelay);
    }
  } else {
    for (i=0; i= dy) { over -= dy; myStepperX.onestep(sx, STEP); }
      delay(StepDelay);
    }
  }
  Xpos = x1s; Ypos = y1s;
  delay(LineDelay);
}

void penUp() { penServo.write(penZUp); delay(penDelay); Zpos=Zmax; digitalWrite(15,LOW); digitalWrite(16,HIGH); if(verbose)Serial.println("Pen up!"); }
void penDown() { penServo.write(penZDown); delay(penDelay); Zpos=Zmin; digitalWrite(15,HIGH); digitalWrite(16,LOW); if(verbose)Serial.println("Pen down."); }

Processing 上位机代码 (gctrl.pde)

import java.awt.event.KeyEvent;
import javax.swing.JOptionPane;
import processing.serial.*;

Serial port = null;
String portname = null;  // null 则交互选择
boolean streaming = false;
float speed = 0.001;
String[] gcode; int i = 0;

void openSerialPort() {
  if (portname == null) return;
  if (port != null) port.stop();
  port = new Serial(this, portname, 9600);
  port.bufferUntil('\n');
}

void selectSerialPort() {
  String result = (String) JOptionPane.showInputDialog(frame,
    "Select the serial port for your Arduino.", "Select serial port",
    JOptionPane.QUESTION_MESSAGE, null, Serial.list(), 0);
  if (result != null) { portname = result; openSerialPort(); }
}

void setup() { size(500, 250); openSerialPort(); }

void draw() {
  background(0); fill(255);
  int y = 24, dy = 12;
  String[] help = {
    "INSTRUCTIONS",
    "p: select serial port",
    "1: speed 0.001 in (1 mil)",
    "2: speed 0.010 in (10 mil)",
    "3: speed 0.100 in (100 mil)",
    "arrows: jog X-Y",
    "PgUp/PgDn: jog Z",
    "$: show grbl settings",
    "h: go home",
    "0: zero machine (set home here)",
    "g: stream g-code file",
    "x: stop streaming",
    "current jog speed: " + speed + " in/step",
    "current serial port: " + portname
  };
  for (String s : help) { text(s, 12, y); y += dy; }
}

void keyPressed() {
  if (key == '1') speed = 0.001;
  if (key == '2') speed = 0.01;
  if (key == '3') speed = 0.1;
  if (!streaming) {
    if (keyCode == LEFT)  port.write("G91\nG20\nG00 X-" + speed + " Y0 Z0\n");
    if (keyCode == RIGHT) port.write("G91\nG20\nG00 X" + speed + " Y0 Z0\n");
    if (keyCode == UP)    port.write("G91\nG20\nG00 X0 Y" + speed + " Z0\n");
    if (keyCode == DOWN)  port.write("G91\nG20\nG00 X0 Y-" + speed + " Z0\n");
    if (keyCode == KeyEvent.VK_PAGE_UP)   port.write("G91\nG20\nG00 X0 Y0 Z" + speed + "\n");
    if (keyCode == KeyEvent.VK_PAGE_DOWN) port.write("G91\nG20\nG00 X0 Y0 Z-" + speed + "\n");
    if (key == 'h') port.write("G90\nG20\nG00 X0 Y0 Z0\n");
    if (key == 'v') port.write("$0=75\n$1=74\n$2=75\n");
    if (key == 's') port.write("$3=10\n");
    if (key == 'e') port.write("$16=1\n");
    if (key == 'd') port.write("$16=0\n");
    if (key == '0') openSerialPort();
    if (key == 'p') selectSerialPort();
    if (key == '$') port.write("$$\n");
  }
  if (!streaming && key == 'g') {
    gcode = null; i = 0;
    println("Loading file...");
    selectInput("Select a file to process:", "fileSelected");
  }
  if (key == 'x') streaming = false;
}

void fileSelected(File selection) {
  if (selection == null) { println("Cancelled."); return; }
  println("Selected: " + selection.getAbsolutePath());
  gcode = loadStrings(selection.getAbsolutePath());
  if (gcode == null) return;
  streaming = true; stream();
}

void stream() {
  if (!streaming) return;
  while (true) {
    if (i == gcode.length) { streaming = false; return; }
    if (gcode[i].trim().length() == 0) i++; else break;
  }
  println(gcode[i]);
  port.write(gcode[i] + '\n'); i++;
}

void serialEvent(Serial p) {
  String s = p.readStringUntil('\n');
  println(s.trim());
  if (s.trim().startsWith("ok") || s.trim().startsWith("error")) stream();
}

测试用 G-code 文件

原理图

废旧 DVD 光驱改装迷你 CNC 2D 绘图机 —— 基于 L293D 电机扩展板

关键提示:

完整图文教程见原文链接。

制造工艺

  1. 自制家用作业书写机:Arduino + GRBL 控制的笔绘仪
  2. 用 Arduino 打造绚丽显示器 Ambilight 系统
  3. 基于 Arduino Uno 的人体检测救援机器人:传感器集成分步指南
  4. Android 手势遥控车:Raspberry Pi + Arduino 101 + 电机扩展板,I2T 协同控制
  5. 自制可拳击的可编程键盘按钮:基于 Arduino 与压电元件的分步指南
  6. 用 Arduino 测量地球质量:DIY 分步指南
  7. 构建基于 PHP 的 Web 界面控制 Arduino Uno
  8. Arduino 合成乐音:从零实现 88 键钢琴全音域
  9. 基于 Arduino 和超声波传感器的自动门控制系统
  10. 蓝牙遥控 Arduino 小车完整 DIY 指南
  11. DIY 迷你示波器:基于 Arduino Nano 与 OLED 显示屏的逐步教程
  12. Arduino 实时数字罗盘:HMC5883L + Processing 可视化教程