Project showcase

Polar Drawing Machine © MIT

Polar drawing machine built with fischertechnik and Arduino. Thanks to a G-code interpreter, it can draw any bitmap or vector graphics.

  • 16,435 views
  • 13 comments
  • 122 respects

Apps and online services

About this project

Basic idea

"Normal" plotters have two linear axes and are controlled with Cartesian coordinates. Unlike these machines, the "Polar Drawing Machine" does only have one linear axis but a rotary plate instead of the second axis". Therefore, my drawing machine is controlled with polar coordinates. Any point on the piece of paper can be described by an angle and a radius i.e. the distance between the point an the center of the rotary plate.

Technically, this feature causes some difficulties because there is hardly any CNC software available that can handle polar coordiantes. However, I have solved the difficulties and develeped my own polar drawing machine:

"Tiger" drawn by polar drawing machine

Main Components:

  • Rotary plate to adjust the angle phi
  • Linear axis to adjast the radius r
  • Pen lift up / down mechanism
  • Ready-to-use microcontroller, based on an Arduino Mega

Rotary Plate

The drawing paper is stuck on the rotary plate. By rotating the plate, the angle phi can be adjusted. The rotary plate is driven by a NEMA 17 stepper motor, which allows fast and accurate positioning.

Exploded view of the rotary plate

Linear Axis

The linear axis is predominantely built of fischertechnik parts. This measure saves 3D printing costs and time. Moreover, fischertechnik parts can be reused in other projects.

The linear axis is driven by a NEMA 14 stepper motor. Unlike the NEMA 17 motor, it has less torque but a higher rotational speed, which is essential due to the high gear redcution at the screw. Although the stepper motor allows precise positioning, a limit switch is required to set a reference point for calibration purposes.

Pen Lift Mechanism

The pen lift mechanism is designed to lift the pen up and down quickly. Instead of using a servo motor which often causes jitter and lacks stability, I prefer using a simple DC Motor, which rotates a cam disk. The cam disk either pushes the pen down or lets the return spring push the pen up. There is a limit switch to set the end position.

Microcontroller

The microcontroller could be a project on its own as it is a plug-and-play controller with integrated motor drivers. It focuses on controlling fischertechnik hardware, such as DC motors and a variety of different sensors.

Plug-and-play microcontroller for fischertechnik hardware

Software

How to get a plot from a bitmap?

Step1: Open the bitmap in Inkskape, vectorize it and export it as.dxf file

Step 2: Generate G-code with an CAM processor

Step 3: Consisting of Cartesian coordinates, the G-code has to be transferred to polar coordinates. Therefore, I have written a Java applet.

Step 4: Transfer the polar G-code to the drawing machine. The Arduino runs a G-code interpreter in order to execute the commands. It controls the stepper motors so that the plot is drawn on the peace of paper.

More Videos

First attempts: Drawing lines, circles and a square

Code

Plotter Firmaware 3.1Arduino
#include <MegaDueShield.h>

#define BUFFER_SIZE 1200
#define RAXIS_ZERO 2822 // Kugelschreiber

/*  Plotter Firmware 3_1
     
     March, 2017

     requires a G-code preprocessor
     requires C# App to load G-code

     G-code is transferred while drawing
     optimized parallelization of serial communication and drawing
     optimized serial communication

*/

StepperMotor & nema14 = *shield.getStepper(1);
StepperMotor & nema17 = *shield.getStepper(2);

DCMotor & stift = *shield.getDCMotor(1);

long posn14 = 0;
long posn17 = 0;

uint8_t lastCommand = 0;
boolean complete = false;
uint8_t commands[BUFFER_SIZE];
int commandPos[BUFFER_SIZE][2];

/*  G-Code commands:
   G00: Rapid positioning
   G01: Linear interpolation
   G02: Circular interpolation, clockwise
   G03: Circular interpolation, counterclockwise

   M03: Spindle on: Pen down
   M05: Spindle off: Pen up
*/

/* 11 Umdrehungen = 52,3mm
   2200 Schritte = 52,3mm
   1mm = 41,35338 Schritte

   r = 0mm: 2843 Schritte
   pos (r) = 2843 - r * 41,35338
*/

long rToPos(float r)
{
  return RAXIS_ZERO - (r * 41.885);
}

int rStepsToPos(int steps)
{
  return RAXIS_ZERO - steps;
}

/* 2Pi = 1800 Schritte

   pos (phi) = phi / 2Pi * 1800
*/

long phiToPos(float phi)
{
  while (posn17 >= 1800)
  {
    posn17 -= 1800;
  }
  while (posn17 < 0)
  {
    posn17 += 1800;
  }
  float posf = phi / 2.0 / PI * 1800;
  long pos = (long) posf;

  if (pos - posn17 > 900)
  {
    pos -= 1800;
  }

  if (posn17 - pos > 900)
  {
    pos += 1800;
  }
  return pos;
}

int phiStepsToPos(int steps)
{
  while (posn17 >= 1800)
  {
    posn17 -= 1800;
  }
  while (posn17 < 0)
  {
    posn17 += 1800;
  }
  if (steps - posn17 > 900)
  {
    steps -= 1800;
  }

  if (posn17 - steps > 900)
  {
    steps += 1800;
  }
  return steps;
}

void homeR()
{
  int counter = 0;
  while (!digitalRead(D4))
  {
    long pos = posn14 - 1;
    bool t = false;
    while (!t) {
      t = nema14.stepping(posn14, pos);
    }
    counter++;
  }
  posn14 = 0;
  //Serial.println("Referenzpunkt");
  //Serial.println(counter);
}

void penZ(bool value)
{

  if (value)
  {
    // Pen up
    while (digitalRead(C4) != 1)
    {
      stift.ccw(200);
    }
    stift.stop();
  }
  else
  {
    // Pen down
    stift.cw(200);
    delay(500);
    stift.stop();
  }
}

bool findCharacter(char character)
{
  if (Serial.available() > 0)
  {
    char serialCharacter = Serial.read();
    if (serialCharacter == character)
    {
      return true;
    }
  }
  return false;
}

int parseInteger()
{
  bool delimiter = false;
  int returnInt = 0;
  while (!delimiter)
  {
    while(Serial.available() < 1) {}
    int character = (int) Serial.read();
    character -= 48;
    if(character >= 0 && character <= 9)
    {
      returnInt *= 10;
      returnInt += character;
    }
    else
    {
      delimiter = true;
    }
  }
  return returnInt;
}

bool readGCodeLine(uint8_t & command, int arguments[])
{
  //int arguments[2];
  Serial.print('n');
  Serial.print('\n');
  Serial.flush();
  while (!findCharacter('G')) {}
  command = parseInteger();
  switch (command) {
    case 0:
      while (!findCharacter('P')) {}
      arguments[0] = parseInteger();
      while (!findCharacter('R')) {}
      arguments[1] = parseInteger();
      break;
    case 8:
      complete = true;
      break;
    case 9:
      while (!findCharacter('Z')) {}
      arguments[0] = parseInteger();
      break;
  }
}

bool stepwiseReadGCodeLine(uint8_t & milestone, uint8_t & command, int arguments[])
{
  switch (milestone)
  {
    case 0:
      Serial.print('n');
      Serial.print('\n');
      Serial.flush();
      milestone++;
      return false;
    case 1:
      if (Serial.available() > 0) {
        milestone++;
      }
      return false;
    case 2:
      if (findCharacter('G'))
      {
        milestone++;
      }
      return false;
    case 3:
      command = parseInteger();
      milestone++;
      return false;
    case 4:
      switch (command)
      {
        case 0:
          milestone = 5;
          break;
        case 8:
          complete = true;
          milestone = 8;
          break;
        case 9:
          milestone = 7;
          break;
      }
      return false;
    case 5:
      if (findCharacter('P'))
      {
        arguments[0] = parseInteger();
        milestone++;
      }
      return false;
    case 6:
      if (findCharacter('R'))
      {
        arguments[1] = parseInteger();
        milestone = 8;
      }
      return false;
    case 7:
      if (findCharacter('Z'))
      {
        arguments[0] = parseInteger();
        milestone = 8;
      }
      return false;
    case 8:
      return true;
  }
}

void processGCode()
{
  uint8_t command;
  int arguments[2];
  int nextArguments[2];
  uint8_t milestone = 0;

  while(!findCharacter('s')) {}

  readGCodeLine(command, arguments);  // get first G-code line
  nema14.wakeUp();
  delay(10);
  while (!complete)
  {
    bool t = false;
    milestone = 0;
    switch (command)
    {
      case 0:
        while (!t) {
          t = nema14.stepping(posn14, rStepsToPos(arguments[1]));
          t &= nema17.stepping(posn17, phiStepsToPos(arguments[0]));
          t &= stepwiseReadGCodeLine(milestone, command, nextArguments);
        }
        arguments[0] = nextArguments[0];
        arguments[1] = nextArguments[1];
        break;
      case 9:
        penZ(arguments[0]);
        readGCodeLine(command, arguments);
        break;
      case 8:
        break;
    }
  }
}

void setup() {
  Serial.begin(115200);

  delay(1000);
  nema14.motorConfig(200, 190, 600, 400);
  nema17.motorConfig(200, 120, 280, 400);

  // Stift
  pinMode(C4, INPUT_PULLUP);
  penZ(1);

  // R-Achse
  pinMode(D4, INPUT_PULLUP);
  homeR();
  while (!nema17.stepping(posn17, 900)) {}
  posn17 = 0;
  nema14.release();

  delay(400);

  //fahren();
  complete = false;
  processGCode();
  int p = posn17 + 900;
  bool t = false;
  while (!t)
  {
    t = nema17.stepping(posn17, p);
    t &= nema14.stepping(posn14, 50);
  }
  nema14.release();
}

void loop() {
  // put your main code here, to run repeatedly:

}

Custom parts and enclosures

180 teeth gear for timing belt

Schematics

Shield

No document.

Comments

Similar projects you might like

Archimedes: The AI Robot Owl

Project in progress by Alex Glow

  • 3,988 views
  • 4 comments
  • 35 respects

Humaniod A.I Talking Robot With Arduino

Project showcase by Ashwini kumar sinha

  • 1,397 views
  • 2 comments
  • 5 respects

Art Deco FM Radio Project Using Arduino

Project tutorial by Nick Koumaris

  • 1,413 views
  • 1 comment
  • 8 respects

Pill Me Please - The Automated Pill Packaging Machine!

Project tutorial by KevinZhangHK

  • 477 views
  • 0 comments
  • 9 respects

SensorBoard

Project showcase by andrea camilloni

  • 307 views
  • 0 comments
  • 1 respect
Add projectSign up / Login