Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Marcelo Ávila de Oliveira
Published © GPL3+

Mini Plotter

What if two stepper motors from two old DVD drives were used to make a mini plotter? Let's go for it!

IntermediateShowcase (no instructions)Over 1 day825
Mini Plotter

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×1
Motor Control Shield with L293D
×1
Micro Servo EMAX ES08MA II
×1
Stepper Motor
×2
Arduino Bluetooth Shield
×1

Software apps and online services

Arduino IDE
Arduino IDE
MIT App Inventor 2
MIT App Inventor 2

Story

Read more

Schematics

Arduino schematics (2.0.0)

Arduino schematics (3.0.0)

Code

Arduino code (2.0.0)

Arduino
//----------------------------------------------------------------------------//
// Filename    : MiniPlotter.ino                                              //
// Description : Mini CNC plotter                                             //
// Version     : 2.0.0                                                        //
// Author      : Marcelo Avila de Oliveira <marceloavilaoliveira@gmail.com>   //
//----------------------------------------------------------------------------//

//----------------------------------------------------------------------------//
// DEFINITIONS                                                                //
//----------------------------------------------------------------------------//

// TURN ON DEBUG MODE
// #define DEBUG

//----------------------------------------------------------------------------//
// LIBRARIES                                                                  //
//----------------------------------------------------------------------------//

// SERVO LIBRARY
#include <Servo.h>

// ADAFRUIT MOTOR SHIELD LIBRARY
#include <AFMotor.h>

//----------------------------------------------------------------------------//
// CONSTANTS                                                                  //
//----------------------------------------------------------------------------//

// PINS
const int z_pin = 9;

// POSITIONS
const int z_up = 0;
const int z_dn = 35;

// DRAWING SETTINGS
const char STEP = INTERLEAVE;
const float x_steps_per_mm = 13.4;
const float y_steps_per_mm = 13.4;

// DELAYS
const int step_delay_up = 2;
const int step_delay_dn = 5;
const int move_delay = 100;
const int pen_delay = 250;

// LIMITS [mm]
const float x_min = 0;
const float x_max = 40;
const float y_min = 0;
const float y_max = 40;

// READ
const int line_size = 80;

// STEP MOTORS (STEPS/REV, NUMBER)
AF_Stepper x_motor(48, 1);
AF_Stepper y_motor(48, 2);
Servo z_motor;

//----------------------------------------------------------------------------//
// VARIABLES                                                                  //
//----------------------------------------------------------------------------//

// POSITION

float x = x_min;
float y = y_min;
int z = 0;

// READ

int line_index = 0;
char line[line_size];
boolean ignore = false;

//----------------------------------------------------------------------------//
// FUNCTIONS (SETUP)                                                          //
//----------------------------------------------------------------------------//

void setup() {
    // INITIATE SERIAL COMMUNICATION
    Serial.begin(38400);

    // RESET POSITIONS
    reset();

    #ifdef DEBUG
        Serial.println();
        Serial.println("MiniPlotter is alive!");
        Serial.println();
        Serial.print("X from ");
        Serial.print(x_min);
        Serial.print(" to ");
        Serial.print(x_max);
        Serial.println(" mm");
        Serial.print("Y from ");
        Serial.print(y_min);
        Serial.print(" to ");
        Serial.print(y_max);
        Serial.println(" mm");
        Serial.println();
    #endif
}

void reset() {
    move_z(0);
    move_z(1);
    move_z(0);
    move_xy(x_min, y_min);
    move_xy(x_max, y_max);
    move_z(1);
    move_z(0);
    move_xy(x_min, y_min);
}

//----------------------------------------------------------------------------//
// FUNCTIONS (READ)                                                           //
//----------------------------------------------------------------------------//

void read_serial() {
    char c;

    while (Serial.available()) {
        c = Serial.read();

        #ifdef DEBUG
            Serial.print("Read character: ");
            Serial.println(c);
        #endif

        if (c == '\n' || c == '\r') {
            // END OF LINE REACHED

            if (line_index > 0) {
                line[line_index] = '\0';
                #ifdef DEBUG
                    Serial.print("Read line: ");
                    Serial.println(line);
                    Serial.println();
                #endif
                process_line(line, line_index);
                line_index = 0;
            } else {
                #ifdef DEBUG
                    Serial.println("Empty line");
                    Serial.println();
                #endif
            }

            ignore = false;
        } else {
            if (ignore) {
                if (c == ')') {
                    // STOP IGNORING LINE
                    ignore = false;
                }
            } else {
                if (c <= ' ') {
                    // THROW AWAY WHITESPACE AND CONTROL CHARACTERS
                } else if (c == '/') {
                    // BLOCK DELETE NOT SUPPORTED, IGNORE CHARACTER
                } else if (c == '(') {
                    // START IGNORING LINE
                    ignore = true;
                } else if (c == ';') {
                    // SEMICOLON NOT SUPPORTED, IGNORE LINE
                    ignore = true;
                } else if (line_index >= line_size - 1) {
                    Serial.println("ERROR: Line size exceded");
                    ignore = false;
                } else if (c >= 'a' && c <= 'z') {
                    // TO UPCASE
                    line[line_index++] = c-'a'+'A';
                } else {
                    line[line_index++] = c;
                }
            }
        }
    }
}

void process_line(char* line, int line_size) {
    int line_index = 0;
    char buffer[50];

    #ifdef DEBUG
        Serial.print("Process line: ");
        Serial.println(line);
        Serial.println("");
    #endif

    while (line_index < line_size) {
        switch (line[line_index++]) {
            case 'S':
            {
                buffer[0] = line[line_index++];
                buffer[1] = line[line_index++];
                buffer[2] = '\0';
                float size = atof(buffer);

                while (line_index < line_size) {
                    char c = line[line_index++];
                    plot_char(c, size);
                }
            }
            case 'G':
            {
                buffer[0] = line[line_index++];
                buffer[1] = '\0';

                switch (atoi(buffer)) {
                    case 0:
                    {
                        char* z_index = strchr(line+line_index, 'Z');
                        int z_pos;

                        if (z_index <= 0) {
                            z_pos = z;
                        } else {
                            z_pos = atoi(z_index + 1);
                            z_index = '\0';
                        }

                        move_z(z_pos);
                        break;
                    }
                    case 1:
                    {
                        char* x_index = strchr(line+line_index, 'X');
                        char* y_index = strchr(line+line_index, 'Y');
                        float x_pos, y_pos;

                        if (y_index <= 0) {
                            x_pos = atof(x_index + 1);
                            y_pos = y;
                        } else if (x_index <= 0) {
                            y_pos = atof(y_index + 1);
                            x_pos = x;
                        } else {
                            y_pos = atof(y_index + 1);
                            y_index = '\0';
                            x_pos = atof(x_index + 1);
                        }

                        move_xy(x_pos, y_pos);
                        break;
                    }
                }
                break;
            }
            case 'M':
            {
                buffer[0] = line[line_index++];
                buffer[1] = line[line_index++];
                buffer[2] = line[line_index++];
                buffer[3] = '\0';

                switch (atoi(buffer)) {
                    case 300:
                    {
                        char* s_index = strchr(line+line_index, 'S');
                        float s_pos = atof(s_index + 1);

                        if (s_pos == 50) {
                            // PEN UP
                            move_z(0);
                        }

                        if (s_pos == 30) {
                            // PEN DOWN
                            move_z(1);
                        }
                        break;
                    }
                }
                break;
            }
        }
    }
}

//----------------------------------------------------------------------------//
// FUNCTIONS (MOVE)                                                           //
//----------------------------------------------------------------------------//

void motors_attach_detach(int mode) {
    // MODE:
    // 0 = ATTACH
    // 1 = DETACH

    if (mode == 0) {
        z_motor.attach(z_pin);
    } else {
        z_motor.detach();
    }
}

void move_z(int z_pos) {
    // Z_POS: Z POSITION
    // 0 = DOWN
    // 1 = UP

    #ifdef DEBUG
        Serial.print("Move: Z");
        Serial.print(z);
        Serial.print(" => Z");
        Serial.println(z_pos);
        Serial.println("");
    #endif

    // MOVE
    if (z_pos != z) {
        motors_attach_detach(0);
        if (z_pos == 0) {
            z_motor.write(z_up);
        } else {
            z_motor.write(z_dn);
        }
        delay(pen_delay);
        motors_attach_detach(1);
    }

    // UPDATE THE POSITION
    z = z_pos;
}

void move_xy(float x_pos, float y_pos) {
    // X_POS, Y_POS: X AND Y POSITIONS

    #ifdef DEBUG
        Serial.print("Move: X");
        Serial.print(x);
        Serial.print(" Y");
        Serial.print(y);
        Serial.print(" => X");
        Serial.print(x_pos);
        Serial.print(" Y");
        Serial.println(y_pos);
        Serial.println("");
    #endif

    // ADJUST THE POSITIONS UP TO THE LIMITS
    if (x_pos >= x_max) {
        x_pos = x_max;
    }

    if (x_pos <= x_min) {
        x_pos = x_min;
    }

    if (y_pos >= y_max) {
        y_pos = y_max;
    }

    if (y_pos <= y_min) {
        y_pos = y_min;
    }

    // CONVERT COORDINATES TO STEPS
    float x_step = (int)(x * x_steps_per_mm);
    float y_step = (int)(y * y_steps_per_mm);
    float x_pos_step = (int)(x_pos * x_steps_per_mm);
    float y_pos_step = (int)(y_pos * y_steps_per_mm);

    // CALCULATE THE CHANGE
    long dx = abs(x_pos_step - x_step);
    long dy = abs(y_pos_step - y_step);
    int sx = x_step < x_pos_step ? FORWARD : BACKWARD;
    int sy = y_step < y_pos_step ? FORWARD : BACKWARD;

    // MOVE
    long over = 0;
    if (dx > dy) {
        for (int i = 0; i < dx; ++i) {
            x_motor.onestep(sx, STEP);
            over += dy;

            if (over >= dx) {
                over -= dx;
                y_motor.onestep(sy, STEP);
            }

            if (z == 0) {
                delay(step_delay_up);
            } else {
                delay(step_delay_dn);
            }
        }
    } else {
        for (int i = 0; i < dy; ++i) {
            y_motor.onestep(sy, STEP);
            over += dx;

            if (over >= dy) {
                over -= dy;
                x_motor.onestep(sx, STEP);
            }

            if (z == 0) {
                delay(step_delay_up);
            } else {
                delay(step_delay_dn);
            }
        }
    }
    delay(move_delay);

    // UPDATE THE POSITIONS
    x = x_pos;
    y = y_pos;
}

//----------------------------------------------------------------------------//
// FUNCTIONS (PLOT)                                                           //
//----------------------------------------------------------------------------//

void plot_char(char c, float height) {
    // C: CHARACTER TO BE PLOTTED [A-Z0-9]
    //
    // HEIGHT: IN MM (WIDTH = HEIGHT / 2)

    #ifdef DEBUG
        Serial.print("Character = ");
        Serial.println(c);
        Serial.println();
    #endif

    float scale = height / 12;
    float x0 = x;
    float y0 = y;

    switch (c) {
        case 'A':
            move_z(1);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'B':
            // TBD
            break;
        case 'C':
            move_xy(x0+6*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'D':
            // TBD
            break;
        case 'E':
            move_xy(x0+6*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'F':
            // TBD
            break;
        case 'G':
            // TBD
            break;
        case 'H':
            // TBD
            break;
        case 'I':
            // TBD
            break;
        case 'J':
            // TBD
            break;
        case 'K':
            // TBD
            break;
        case 'L':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'M':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+3*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'N':
            // TBD
            break;
        case 'O':
            move_xy(x0+0*scale, y0+2*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'P':
            // TBD
            break;
        case 'Q':
            // TBD
            break;
        case 'R':
            move_z(1);
            move_xy(x0,         y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+8*scale, y0+0*scale);
            break;
        case 'S':
            // TBD
            break;
        case 'T':
            // TBD
            break;
        case 'U':
            // TBD
            break;
        case 'V':
            // TBD
            break;
        case 'X':
            // TBD
            break;
        case 'Y':
            // TBD
            break;
        case 'Z':
            // TBD
            break;
    }
}

//----------------------------------------------------------------------------//
// MAIN                                                                       //
//----------------------------------------------------------------------------//

void loop()
{
    read_serial();
}

Arduino code (3.0.0)

Arduino
//----------------------------------------------------------------------------//
// Description : Mini CNC plotter                                             //
// Author      : Marcelo Avila de Oliveira <marceloavilaoliveira@gmail.com>   //
//----------------------------------------------------------------------------//

//----------------------------------------------------------------------------//
// DEFINITIONS                                                                //
//----------------------------------------------------------------------------//

// TURN ON DEBUG MODE
// #define DEBUG

//----------------------------------------------------------------------------//
// LIBRARIES                                                                  //
//----------------------------------------------------------------------------//

// SERVO LIBRARY
#include <Servo.h>

// ADAFRUIT MOTOR SHIELD LIBRARY
#include <AFMotor.h>

//----------------------------------------------------------------------------//
// CONSTANTS                                                                  //
//----------------------------------------------------------------------------//

// PINS
const int z_pin = 9;

// POSITIONS
const int z_up = 0;
const int z_dn = 35;

// DRAWING SETTINGS
const char STEP = INTERLEAVE;
const float x_steps_per_mm = 13.4;
const float y_steps_per_mm = 13.4;

// DELAYS
const int step_delay_up = 2;
const int step_delay_dn = 4;
const int move_delay =100;
const int pen_delay = 200;

// LIMITS [mm]
const float x_min = 0;
const float x_max = 40;
const float y_min = 0;
const float y_max = 40;

// SERIAL AND BLUETOOTH COMMUNICATION
const int line_size = 80;

// STEP MOTORS (STEPS/REV, NUMBER)
AF_Stepper x_motor(48, 1);
AF_Stepper y_motor(48, 2);
Servo z_motor;

//----------------------------------------------------------------------------//
// VARIABLES                                                                  //
//----------------------------------------------------------------------------//

// POSITION

float x = x_min;
float y = y_min;
int z = 0;

// SERIAL COMMUNICATION
char line_se[line_size];
int line_index_se = 0;
boolean string_se = false;
boolean ignore_se = false;

// BLUETOOTH COMMUNICATION
char line_bt[line_size];
int line_index_bt = 0;
boolean string_bt = false;
boolean ignore_bt = false;

//----------------------------------------------------------------------------//
// FUNCTIONS (SETTINGS)                                                       //
//----------------------------------------------------------------------------//

void setup() {
    // INITIATE SERIAL COMMUNICATION
    Serial.begin(38400);

    // INITIATE BLUETOOTH COMMUNICATION
    setup_bluetooth();

    // RESET POSITIONS
    reset();

    #ifdef DEBUG
        Serial.println("MiniPlotter is alive!");
        Serial.println();
    #endif
}

void setup_bluetooth() {
    #ifdef DEBUG
        Serial.println("Setting Bluetooth");
        Serial.println();
    #endif

    Serial1.begin(38400);                   // Set baud rate
    Serial1.print("\r\n+STWMOD=0\r\n");     // Set to work in slave mode
    Serial1.print("\r\n+STNA=Arduino\r\n"); // Set name
    Serial1.print("\r\n+STOAUT=1\r\n");     // Permit Paired device to connect me
    Serial1.print("\r\n+STAUTO=0\r\n");     // Auto-connection should be forbidden here
    delay(2000);                            // This delay is required.
    Serial1.print("\r\n+INQ=1\r\n");        // Make the slave inquirable 
    delay(2000);                            // This delay is required.
    while (Serial1.available()) {           // Clear data
        delay(50);
        Serial1.read();
    }
}

void reset() {
    move_xy(x_min, y_min);
    move_xy(x_max, y_max);
    move_xy(x_min, y_min);
    move_z(0);
    move_z(1);
    move_z(0);
}

//----------------------------------------------------------------------------//
// FUNCTIONS (READ)                                                           //
//----------------------------------------------------------------------------//

void check_bluetooth() {
    char c;
    boolean ok = false;

    while (Serial1.available() && !ok) {
        c = Serial1.read();
        delay(50);
        if (c == '#') {
            while (Serial1.available()) {
                c = Serial1.read();
                process_character(c, line_bt, line_index_bt, string_bt, ignore_bt);
            }

            ok = true;
        }
    }
}

void check_serial() {
    char c;

    while (Serial.available()) {
        c = Serial.read();
        process_character(c, line_se, line_index_se, string_se, ignore_se);
    }
}

void process_character(char c, char* line, int& line_index, boolean& string, boolean& ignore) {
    #ifdef DEBUG
        Serial.print("Process character: ");
        Serial.println(c);
    #endif

    if (c == '\n' || c == '\r') {
        // END OF LINE REACHED

        if (line_index > 0) {
            // PROCESS THE LINE

            line[line_index] = '\0';
            #ifdef DEBUG
                Serial.print("Read line: ");
                Serial.println(line);
                Serial.println();
            #endif
            process_line(line, line_index);
        } else {
            // EMPTY LINE, IGNORE
        }

        line_index = 0;
        string = false;
        ignore = false;
    } else {
        if (line_index == 0 && (c == 's' || c == 'S')) {
            // IT'S A PLOT STRING COMMAND
            string = true;
        }
        if (ignore) {
            if (c == ')' && ! string) {
                // STOP IGNORING LINE
                ignore = false;
            }
        } else {
            if (c <= ' ' && ! string) {
                // THROW AWAY WHITESPACE AND CONTROL CHARACTERS
            } else if (c == '/' && ! string) {
                // BLOCK DELETE NOT SUPPORTED, IGNORE CHARACTER
            } else if (c == '(' && ! string) {
                // START IGNORING LINE
                ignore = true;
            } else if (c == ';' && ! string) {
                // SEMICOLON NOT SUPPORTED, IGNORE LINE
                ignore = true;
            } else if (line_index >= line_size - 1) {
                Serial.println("ERROR: Line size exceded");
                ignore = false;
            } else if (c >= 'a' && c <= 'z') {
                // TO UPCASE
                line[line_index++] = c-'a'+'A';
            } else {
                line[line_index++] = c;
            }
        }
    }
}

void process_line(char* line, int line_size) {
    int line_index = 0;
    char buffer[50];

    #ifdef DEBUG
        Serial.print("Process line: ");
        Serial.println(line);
        Serial.println("");
    #endif

    while (line_index < line_size) {
        switch (line[line_index++]) {
            case 'G':
            {
                // MOVE PEN
                // FORMAT: G0 Zx
                //           x = 1 (PEN DOWN)
                //           x = 0 (PEN UP)
                // OR:     G1 Xx.xx Yy.yy
                //           x.xx/y.yy = POSITION IN MM
                buffer[0] = line[line_index++];
                buffer[1] = '\0';

                switch (atoi(buffer)) {
                    case 0:
                    {
                        // MOVE Z
                        char* z_index = strchr(line+line_index, 'Z');
                        int z_pos;

                        if (z_index <= 0) {
                            z_pos = z;
                        } else {
                            z_pos = atoi(z_index + 1);
                            z_index = '\0';
                        }

                        move_z(z_pos);

                        break;
                    }
                    case 1:
                    {
                        // MOVE X,Y
                        char* x_index = strchr(line+line_index, 'X');
                        char* y_index = strchr(line+line_index, 'Y');
                        float x_pos, y_pos;

                        if (y_index <= 0) {
                            x_pos = atof(x_index + 1);
                            y_pos = y;
                        } else if (x_index <= 0) {
                            y_pos = atof(y_index + 1);
                            x_pos = x;
                        } else {
                            y_pos = atof(y_index + 1);
                            y_index = '\0';
                            x_pos = atof(x_index + 1);
                        }

                        move_xy(x_pos, y_pos);

                        break;
                    }
                }

                break;
            }

            case 'M':
            {
                // MOVE PEN
                // FORMAT: M300 Sx
                //           x = 30 (PEN DOWN)
                //           x = 50 (PEN UP)
                buffer[0] = line[line_index++];
                buffer[1] = line[line_index++];
                buffer[2] = line[line_index++];
                buffer[3] = '\0';

                switch (atoi(buffer)) {
                    case 300:
                    {
                        char* s_index = strchr(line+line_index, 'S');
                        float s_pos = atof(s_index + 1);

                        if (s_pos == 50) {
                            // PEN UP
                            move_z(0);
                        }

                        if (s_pos == 30) {
                            // PEN DOWN
                            move_z(1);
                        }

                        break;
                    }
                }

                break;
            }

            case 'R':
            {
                // RESET POSITIONS
                reset();
                break;
            }

            case 'S':
            {
                // PLOT STRING
                // FORMAT: Sxx yyyyy
                //           xx    = HEIGHT IN MM (WIDTH = HEIGHT / 2)
                //           yyyyy = STRING
                buffer[0] = line[line_index++];
                buffer[1] = line[line_index++];
                buffer[2] = '\0';
                float size = atof(buffer);

                while (line_index < line_size) {
                    char c = line[line_index++];
                    plot_char(c, size);
                }

                break;
            }
        }
    }
}

//----------------------------------------------------------------------------//
// FUNCTIONS (MOVE)                                                           //
//----------------------------------------------------------------------------//

void motors_attach_detach(int mode) {
    // MODE:
    // 0 = ATTACH
    // 1 = DETACH

    if (mode == 0) {
        z_motor.attach(z_pin);
    } else {
        z_motor.detach();
    }
}

void move_z(int z_pos) {
    // Z_POS: Z POSITION
    // 0 = DOWN
    // 1 = UP

    #ifdef DEBUG
        Serial.print("Move: Z");
        Serial.print(z);
        Serial.print(" => Z");
        Serial.println(z_pos);
        Serial.println("");
    #endif

    // MOVE
    if (z_pos != z) {
        motors_attach_detach(0);
        if (z_pos == 0) {
            z_motor.write(z_up);
        } else {
            z_motor.write(z_dn);
        }
        delay(pen_delay);
        motors_attach_detach(1);
    }

    // UPDATE THE POSITION
    z = z_pos;
}

void move_xy(float x_pos, float y_pos) {
    // X_POS, Y_POS: X AND Y POSITIONS

    #ifdef DEBUG
        Serial.print("Move: X");
        Serial.print(x);
        Serial.print(" Y");
        Serial.print(y);
        Serial.print(" => X");
        Serial.print(x_pos);
        Serial.print(" Y");
        Serial.println(y_pos);
        Serial.println("");
    #endif

    // ADJUST THE POSITIONS UP TO THE LIMITS
    if (x_pos >= x_max) {
        x_pos = x_max;
    }

    if (x_pos <= x_min) {
        x_pos = x_min;
    }

    if (y_pos >= y_max) {
        y_pos = y_max;
    }

    if (y_pos <= y_min) {
        y_pos = y_min;
    }

    // CONVERT COORDINATES TO STEPS
    float x_step = (int)(x * x_steps_per_mm);
    float y_step = (int)(y * y_steps_per_mm);
    float x_pos_step = (int)(x_pos * x_steps_per_mm);
    float y_pos_step = (int)(y_pos * y_steps_per_mm);

    // CALCULATE THE CHANGE
    long dx = abs(x_pos_step - x_step);
    long dy = abs(y_pos_step - y_step);
    int sx = x_step > x_pos_step ? FORWARD : BACKWARD;
    int sy = y_step > y_pos_step ? FORWARD : BACKWARD;

    // MOVE
    long over = 0;
    if (dx > dy) {
        for (int i = 0; i < dx; ++i) {
            x_motor.onestep(sx, STEP);
            over += dy;

            if (over >= dx) {
                over -= dx;
                y_motor.onestep(sy, STEP);
            }

            if (z == 0) {
                delay(step_delay_up);
            } else {
                delay(step_delay_dn);
            }
        }
    } else {
        for (int i = 0; i < dy; ++i) {
            y_motor.onestep(sy, STEP);
            over += dx;

            if (over >= dy) {
                over -= dy;
                x_motor.onestep(sx, STEP);
            }

            if (z == 0) {
                delay(step_delay_up);
            } else {
                delay(step_delay_dn);
            }
        }
    }
    delay(move_delay);

    // UPDATE THE POSITIONS
    x = x_pos;
    y = y_pos;
}

//----------------------------------------------------------------------------//
// FUNCTIONS (PLOT)                                                           //
//----------------------------------------------------------------------------//

void plot_char(char c, float height) {
    // C: CHARACTER TO BE PLOTTED = [A-Z0-9 +-_/.:%#()><]
    // OR A COMMAND TO BE EXECUTED:
    // = = MOVE TO THE BEGINNING OF THE LINE
    // ^ = MOVE TO THE PREVIOUS LINE
    // | = MOVE TO THE NEXT LINE
    //
    // HEIGHT: IN MM (WIDTH = HEIGHT / 2)

    #ifdef DEBUG
        Serial.print("Character = ");
        Serial.println(c);
        Serial.println();
    #endif

    // VERIFY IF THERE'S ENOUGHT SPACE TO PLOT
    if (c == '=') {
        // MOVE TO THE BEGINNING OF THE LINE
    } else if (c == '^') {
        // MOVE TO THE PREVIOUS LINE
        if ((y + 7 / 6 * height > y_max)) {
            return;
        }
    } else if (c == '|') {
        // MOVE TO THE NEXT LINE
        if ((y - 7 / 6 * height < y_min)) {
            return;
        }
    } else {
        // CHARACTER
        if ((x + height / 2 > x_max) || (y + height > y_max)) {
            return;
        }
    }

    float scale = height / 12;
    float x0 = x;
    float y0 = y;

    switch (c) {
        case 'A':
            move_z(1);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+6*scale);
            break;
        case 'B':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+4*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+4*scale, y0+6*scale);
            break;
        case 'C':
            move_xy(x0+6*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'D':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+0*scale);
            break;
        case 'E':
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'F':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+6*scale);
            break;
        case 'G':
            move_xy(x0+4*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            break;
        case 'H':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'I':
            move_z(1);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+0*scale);
            break;
        case 'J':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+0*scale, y0+4*scale);
            break;
        case 'K':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'L':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'M':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+3*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'N':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+12*scale);
            break;
        case 'O':
            move_xy(x0+2*scale, y0+0*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            break;
        case 'P':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+6*scale);
            break;
        case 'Q':
            move_xy(x0+2*scale, y0+0*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+3*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'R':
            move_z(1);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'S':
            move_xy(x0+0*scale, y0+2*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+6*scale, y0+4*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+2*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+8*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            break;
        case 'T':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+0*scale);
            break;
        case 'U':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+6*scale, y0+12*scale);
            break;
        case 'V':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+12*scale);
            break;
        case 'X':
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case 'W':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+3*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+12*scale);
            break;
        case 'Y':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+0*scale);
            break;
        case 'Z':
            move_xy(x0+0*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case '0':
            move_xy(x0+0*scale, y0+2*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_z(0); 
            move_xy(x0+1*scale, y0+1*scale);
            move_z(1);
            move_xy(x0+5*scale, y0+11*scale);
            break;
        case '1':
            move_z(1);
            move_xy(x0+6*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+0*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+12*scale);
            move_xy(x0+3*scale, y0+0*scale);
            break;
        case '2':
            move_xy(x0+0*scale, y0+10*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+2*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+4*scale);
            move_xy(x0+0*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case '3':
            move_xy(x0+0*scale, y0+10*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+2*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+4*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+4*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+2*scale);
            break;
        case '4':
            move_xy(x0+6*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case '5':
            move_xy(x0+0*scale, y0+2*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+6*scale, y0+4*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+12*scale);
            break;
        case '6':
            move_xy(x0+0*scale, y0+4*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+6*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+4*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            break;
        case '7':
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+12*scale);
            break;
        case '8':
            move_xy(x0+2*scale, y0+0*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+2*scale);
            move_xy(x0+0*scale, y0+4*scale);
            move_xy(x0+2*scale, y0+6*scale);
            move_xy(x0+0*scale, y0+8*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+6*scale, y0+8*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+2*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+4*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+4*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            break;
        case '9':
            move_xy(x0+0*scale, y0+2*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+0*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+6*scale, y0+2*scale);
            move_xy(x0+6*scale, y0+10*scale);
            move_xy(x0+4*scale, y0+12*scale);
            move_xy(x0+2*scale, y0+12*scale);
            move_xy(x0+0*scale, y0+10*scale);
            move_xy(x0+0*scale, y0+8*scale);
            move_xy(x0+2*scale, y0+6*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+6*scale, y0+8*scale);
            break;
        case '+':
            move_xy(x0+1*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+5*scale, y0+6*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+8*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+4*scale);
            break;
        case '-':
            move_xy(x0+1*scale, y0+6*scale);
            move_z(1);
            move_xy(x0+5*scale, y0+6*scale);
            break;
        case '_':
            move_z(1);
            move_xy(x0+6*scale, y0+0*scale);
            break;
        case '/':
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            break;
        case '.':
            move_xy(x0+2*scale, y0+0*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            break;
        case ':':
            move_xy(x0+2*scale, y0+0*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+2*scale);
            move_xy(x0+4*scale, y0+0*scale);
            move_xy(x0+2*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+2*scale, y0+4*scale);
            move_z(1);
            move_xy(x0+2*scale, y0+6*scale);
            move_xy(x0+4*scale, y0+6*scale);
            move_xy(x0+4*scale, y0+4*scale);
            move_xy(x0+2*scale, y0+4*scale);
            break;
        case '%':
            move_z(1);
            move_xy(x0+6*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+11*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+9*scale);
            move_xy(x0+1*scale, y0+9*scale);
            move_xy(x0+1*scale, y0+11*scale);
            move_xy(x0+3*scale, y0+11*scale);
            move_z(0);
            move_xy(x0+3*scale, y0+3*scale);
            move_z(1);
            move_xy(x0+5*scale, y0+3*scale);
            move_xy(x0+5*scale, y0+1*scale);
            move_xy(x0+3*scale, y0+1*scale);
            move_xy(x0+3*scale, y0+3*scale);
            break;
        case '#':
            move_xy(x0+1*scale, y0+0*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+12*scale);
            move_z(0);
            move_xy(x0+5*scale, y0+12*scale);
            move_z(1);
            move_xy(x0+3*scale, y0+0*scale);
            move_z(0);
            move_xy(x0+5.33*scale, y0+4*scale);
            move_z(1);
            move_xy(x0+0*scale, y0+4*scale);
            move_z(0);
            move_xy(x0+0.67*scale, y0+8*scale);
            move_z(1);
            move_xy(x0+6*scale, y0+8*scale);
            break;
        case '(':
...

This file has been truncated, please download it to see its full contents.

Android code (3.0.0)

Java
Android MIT App Inventor (http://ai2.appinventor.mit.edu/)
No preview (download only).

Credits

Marcelo Ávila de Oliveira
5 projects • 9 followers

Comments