Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Мини CNC 2D плоттер из старых DVD-приводов на L293D Motor Shield

Компоненты и материалы

Программное обеспечение

О проекте

Самодельный мини-CNC плоттер из механики DVD-приводов. Две подвижные каретки (sled) с шаговыми двигателями становятся осями X и Y. Сервопривод поднимает/опускает перо/маркер. Управление — Arduino + L293D Shield. G-code генерируется в Inkscape, отправляется через Processing.

Механическая сборка

  1. Разберите два DVD-привода. Извлеките внутренние каретки (sled) с шаговыми двигателями и направляющими — это и есть оси X и Y.
  2. Сделайте отверстия в корпусах DVD для крепления кареток перпендикулярно друг другу (X горизонтальна, Y вертикально).
  3. Соберите на базе корпусов или отдельной рамы.
Мини CNC 2D плоттер из старых DVD-приводов на L293D Motor Shield
Две каретки DVD приводов, установленные перпендикулярно

Подготовка софта

  1. Скачайте Arduino IDE.
  2. Скачайте Processing IDE.
  3. Загрузите в Arduino скетч CNC (см. ниже).
  4. Загрузите в Processing скетч gctrl (G-code sender).

Генерация G-code в Inkscape

  1. Установите Inkscape 0.48.5 (именно эту версию — совместима с аддоном).
  2. Установите аддон G-code export (ссылка в оригинале с инструкцией).
  3. Нарисуйте/импортируйте векторную картинку → Extensions → Gcodetools → Export to G-code.
  4. Смотрите видео-туториал на YouTube по созданию G-code.

Работа с Processing (gctrl)

  1. Запустите Processing-скетч, нажмите Play.
  2. Нажмите 'p' — выбор COM-порта Arduino.
  3. 1 / 2 / 3 — скорость джоггания (ручного движения).
  4. 'g' — загрузка G-code файла.
  5. Enter — старт печати.

Готовые G-code примеры (Dropbox):

Мини CNC 2D плоттер из старых DVD-приводов на L293D Motor Shield
Плоттер в процессе работы

Код для 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;

Servo penServo;
AF_Stepper myStepperY(stepsPerRevolution, 1);
AF_Stepper myStepperX(stepsPerRevolution, 2);

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

float StepInc = 1;
int StepDelay = 0, LineDelay = 0, penDelay = 50;
float StepsPerMillimeterX = 100.0;
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 <= ' ') {}
          else if (c == '/') {}
          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 && !indexX) { newPos.y = atof(indexY + 1); newPos.x = actuatorPos.x; }
            else if (indexX && !indexY) { newPos.x = atof(indexX + 1); newPos.y = actuatorPos.y; }
            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) {
  if (x1 >= Xmax) x1 = Xmax; if (x1 <= Xmin) x1 = Xmin;
  if (y1 >= Ymax) y1 = Ymax; if (y1 <= Ymin) y1 = Ymin;
  x1 = (int)(x1 * StepsPerMillimeterX);
  y1 = (int)(y1 * StepsPerMillimeterY);
  float x0 = Xpos, y0 = Ypos;
  long dx = abs(x1 - x0), dy = abs(y1 - y0);
  int sx = x0 < x1 ? StepInc : -StepInc;
  int sy = y0 < y1 ? StepInc : -StepInc;
  long i, over = 0;
  if (dx > dy) {
    for (i = 0; i < dx; ++i) {
      myStepperX.onestep(sx, STEP);
      over += dy; if (over >= dx) { over -= dx; myStepperY.onestep(sy, STEP); }
      delay(StepDelay);
    }
  } else {
    for (i = 0; i < dy; ++i) {
      myStepperY.onestep(sy, STEP);
      over += dx; if (over >= dy) { over -= dy; myStepperX.onestep(sx, STEP); }
      delay(StepDelay);
    }
  }
  delay(LineDelay);
  Xpos = x1; Ypos = y1;
}

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 — отправитель G-code)

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

Serial port = null;
String portname = 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;
  text("INSTRUCTIONS", 12, y); y += dy;
  text("p: select serial port", 12, y); y += dy;
  text("1: speed 0.001 in (1 mil)", 12, y); y += dy;
  text("2: speed 0.010 in (10 mil)", 12, y); y += dy;
  text("3: speed 0.100 in (100 mil)", 12, y); y += dy;
  text("arrows: jog X-Y", 12, y); y += dy;
  text("PgUp/PgDn: jog Z", 12, y); y += dy;
  text("$: show grbl settings", 12, y); y += dy;
  text("h: go home", 12, y); y += dy;
  text("0: zero machine", 12, y); y += dy;
  text("g: stream g-code file", 12, y); y += dy;
  text("x: stop streaming", 12, y); y += dy;
  y = height - dy;
  text("jog speed: " + speed + " in/step", 12, y); y -= dy;
  text("port: " + portname, 12, y);
}

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.000 Z0.000\n");
    if (keyCode == RIGHT) port.write("G91\nG20\nG00 X"  + speed + " Y0.000 Z0.000\n");
    if (keyCode == UP)    port.write("G91\nG20\nG00 X0.000 Y"  + speed + " Z0.000\n");
    if (keyCode == DOWN)  port.write("G91\nG20\nG00 X0.000 Y-" + speed + " Z0.000\n");
    if (keyCode == KeyEvent.VK_PAGE_UP)   port.write("G91\nG20\nG00 X0.000 Y0.000 Z"  + speed + "\n");
    if (keyCode == KeyEvent.VK_PAGE_DOWN) port.write("G91\nG20\nG00 X0.000 Y0.000 Z-" + speed + "\n");
    if (key == 'h') port.write("G90\nG20\nG00 X0.000 Y0.000 Z0.000\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(java.io.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();
}

Схема подключения

Мини CNC 2D плоттер из старых DVD-приводов на L293D Motor Shield
Подключение L293D Shield, сервы, шаговых двигателей DVD к Arduino

Производственный процесс

  1. Сборка домашней машины для написания домашних заданий (плоттер-писатель)
  2. Создание потрясающей системы Ambilight для монитора на Arduino
  3. Робот для поиска людей на базе Arduino Uno: пошаговая интеграция датчиков
  4. Android-управляемая робот-платформа на Raspberry Pi и Motor Shield
  5. Создание настраиваемой «бьющейся» клавиши на Arduino: пошаговое руководство
  6. Измерение массы Земли с Arduino: пошаговое руководство по DIY
  7. Создание PHP-веб-интерфейса для управления Arduino Uno
  8. Генерация музыкальных тонов на Arduino: полный спектр 88 клавиш пианино
  9. Автоматическая дверь на Arduino с ультразвуковым датчиком HC-SR04
  10. Цифровой компас в реальном времени на Arduino и Processing