Build a Mini CNC Pen Plotter with Arduino
A “real” CNC machine or laser cutter is expensive and needs dedicated space, but a mini CNC pen plotter — swapping the cutting head for a pen — fits on a desk with very cheap parts. It’s a great foundation project for understanding how a CNC machine moves precisely along X-Y coordinates using G-code commands, before investing in an actual CNC mill or laser cutter.
How it works
- The Arduino runs GRBL firmware (open-source, purpose-built for CNC control), receiving G-code commands over Serial from a computer — each G-code line describes a motion (e.g. “move to coordinate X=50, Y=30” or “raise/lower the pen”).
- GRBL translates each move command into a sequence of pulses driving the two stepper motors (X and Y axes), computing speed and acceleration so the drawn line moves smoothly instead of jerkily.
- The pen sits on a simple lift mechanism (usually a small servo) — lowered while drawing a stroke, raised while moving between disconnected strokes.
- On the computer, software like Inkscape (with a G-code export plugin) or image-to-vector tools convert an image or handwriting into a G-code file, then a control program (Universal G-code Sender or similar) streams each command line by line.
Parts list
- An Arduino Uno (flashed with GRBL firmware, well-supported by common CNC shields)
- A CNC Shield v3 (or an equivalent hand-wired setup) carrying 2-3 A4988 stepper drivers
- 2 NEMA17 stepper motors for the X and Y axes
- A small servo (SG90) for raising/lowering the pen
- Mechanical frame: linear rails, GT2 timing belt, or a pre-made DIY CNC plotter kit (widely available from electronics part suppliers)
- A 12V power supply for the CNC Shield/motor drivers
Sample code
Flash GRBL firmware onto the Arduino through the Arduino IDE (just download the GRBL source, open the example, and upload — no need to write firmware from scratch). Once flashed, you can stream G-code directly via the Serial Monitor or a simple Python script to test it:
import serial
import time
PORT = "COM3" # change to your port
BAUD = 115200
gcode_lines = [
"G21", # units in mm
"G90", # absolute positioning
"G0 X10 Y10", # rapid move to the starting point, no drawing
"M3 S0", # lower the pen (servo, depends on GRBL config)
"G1 X50 Y10 F1000", # draw a straight line to X=50,Y=10 at 1000mm/min
"G1 X50 Y50",
"G1 X10 Y50",
"G1 X10 Y10",
"M5", # raise the pen
]
ser = serial.Serial(PORT, BAUD, timeout=2)
time.sleep(2) # wait for GRBL to boot
for line in gcode_lines:
ser.write((line + "\n").encode())
while True:
response = ser.readline().decode().strip()
if response == "ok":
break
elif response:
print("GRBL:", response)
print("Finished drawing the square!")
ser.close()
Common pitfalls
- Not calibrating
$100/$101(steps/mm) in GRBL produces drawings at the wrong scale compared to the original design — measure the actual distance moved against the commanded distance to recompute these settings for your specific mechanics. - A loose GT2 timing belt causes step loss on sudden direction changes, making the drawing drift out of alignment over time — keep the belt tensioned just enough, not so tight it overloads the motor, and not so loose it slips.
- The pen-lift servo reacting out of sync with movement commands leaves stray marks while traveling to the start of a new stroke — add a small delay (
G4 P0.2— pause 0.2 seconds) after raise/lower commands before continuing to move. - Streaming the entire G-code file at once without waiting for “ok” overflows GRBL’s Serial buffer, causing the plotter to drop commands or stall mid-drawing — always send one line at a time and wait for “ok,” as shown in the sample code.
Extensions
Swap the pen for a low-power laser diode module to lightly engrave wood/leather (mind eye safety and fire risk), upgrade to three axes to turn it into a low-power mini mill, or check out the self-balancing robot with PID control post for a deeper look at precise real-time motor control loops.
Comments