3D Printing & CNCCommander

Pi-Controlled Pen Plotter with GRBL

Build a two-axis drawing machine that turns SVG art into ink on paper, with a Pi preparing and streaming the G-code.

LevelCommander
Time8 h
Est. cost~$181
Steps6

A pen plotter is a CNC machine that draws instead of cuts. An Arduino Uno running GRBL moves two stepper motors with smooth acceleration, a small servo lifts the pen, and the Pi uses vpype to optimize SVG paths and stream them over USB.

⚠ SAFETY
Stepper drivers get hot — add a fan or heatsinks and never unplug a motor while the shield is powered. Keep fingers clear of belts and pulleys while it moves.

Mission steps

  1. Build the frame

    Print or buy an XY gantry; belts and smooth rods give cleaner lines than threaded rods. Mount the pen holder on a hinge a servo can lift.

  2. Flash GRBL

    Load GRBL onto the Uno R3 from the Arduino IDE, or a GRBL fork that drives a pen servo with M3/M5 if you skip a Z stepper.

  3. Wire and set current

    Seat the A4988 drivers on the CNC shield, plug the steppers into X and Y, and set each driver's current limit with a multimeter.

  4. Configure GRBL

    Set steps/mm ($100, $101), max rates and acceleration, then draw a test square and measure it.

  5. Prepare the art

    On the Pi run pip install vpype vpype-gcode and convert an SVG with a profile that sets your pen-up and pen-down commands.

  6. Stream and draw

    Run the streaming script with the .gcode file and watch the plotter draw.

Code

python3 -m venv ~/plot && source ~/plot/bin/activate
pip install vpype vpype-gcode pyserial
# 'myplotter' is a profile you define in ~/.vpype.toml with your pen up/down codes
vpype read drawing.svg linemerge linesort gwrite --profile myplotter drawing.gcode
# stream.py — send G-code to GRBL line by line, waiting for 'ok'
import serial, sys, time

port = serial.Serial('/dev/ttyUSB0', 115200)   # genuine Unos often show as /dev/ttyACM0
port.write(b"\r\n\r\n"); time.sleep(2); port.reset_input_buffer()

for raw in open(sys.argv[1]):
    line = raw.split(';')[0].strip()
    if not line:
        continue
    port.write((line + '\n').encode())
    reply = port.readline().decode().strip()
    if reply != 'ok':
        print(f"{line!r} -> {reply}")
print('Done')

Tip: test every script on the bench before installing it in a moving machine.

Questions

Why the Uno R3 and not the Uno R4 WiFi?

Classic GRBL is written for the ATmega328P chip in the Uno R3. The R4 uses a different processor, so GRBL does not run on it.