Skip to the content
PCBKitNew board
2 layers97.5 × 67.3 mmDrawMv2.0, the printed circuit board in 3D

x, y robot steered by FCODE Instructions

Size
97.5 × 67.3 mm
Copper layers
2
Thickness
1.6 mm
Parts
43
Nets
59
Pads
188
Tracks, vias
393, 8
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
21 Dec 2025
Updated
30 Oct 2026

Overview

DrawMv2.0 is an open-source printed circuit board by vpalaga, from the vpalaga/DrawMachine repository on GitHub. It is a 2-layer board of 97.5 × 67.3 mm with 43 parts on 59 nets, routed with 393 tracks and 8 vias.

Its parts include 11 connectors, 10 capacitors, 6 resistors, 3 ICs and 2 diodes. It belongs with the robots and motor control boards and the RP2040 boards of this library.

README

README.md

DrawMachine

An XY pen plotter driven by a Raspberry Pi Pico and a home-made G-code dialect called FCODE.

Feed it a .dxf drawing, it works out where the pen has to go, and a Pico steps three stepper motors (X, Y and a Z pen lift) to draw it on A4 paper.

The goal: a 100% working, reliable XY plotter that does my homework. Partially based on the Scrabble bot.


How it works

mermaid
flowchart LR
    A[".dxf drawing"] --> B["FCODEgenerator<br/>Python"]
    B --> C[".FCODE file<br/>absolute positions in mm"]
    C --> D["DataSendToPico<br/>Python, over USB serial"]
    D --> E["Pico firmware<br/>C++, relative steps"]
    E --> F["TMC2209 drivers<br/>X, Y, Z steppers"]
  1. FCODEgenerator reads a DXF (or you script the pen by hand), centres it on the paper, reorders the strokes to minimise pen travel, and writes an .FCODE text file. Positions are in millimetres.
  2. DataSendToPico reads the FCODE file line by line, converts millimetres to motor steps, range-checks every move and sends one command at a time to the Pico over USB CDC (virtual serial port). It waits for the Pico to confirm each command.
  3. StepperDriver-pico is the firmware. It parses each command, coordinates X and Y step pulses so both axes finish a move together, lifts and lowers the pen, and homes against the end switches.

Repository layout

PathWhat's in it
FCODEgenerator/DXF reader, path optimiser, FCODE writer and preview renderer (Python)
FCODEgenerator/dxfexamples/Sample DXF files to try
DataSendToPico/Host-side sender: file runner, mm to steps conversion, serial transport, test CLI and GUI (Python)
StepperDriver-pico/Pico firmware (C++ / Pico SDK)
StepperDriver-pico/Pico_pins_map.txtASCII Pico pinout showing which GPIO does what
StepperDriver-pico/bresenham_test.pyDraws the step pattern of the line algorithm with PIL, used to design the move function
pcb/2.0/DrawMv2.0/Current board: KiCad project for the v2.0 controller PCB
pcb/test/Retired first prototype board (v1), kept for reference only
pcb2_0_schematic.pdfPDF of the v2.0 schematic
images/Documentation images

Hardware

  • Controller: Raspberry Pi Pico (USB CDC serial to the PC).
  • Drivers: three TMC2209 stepper drivers, one each for X, Y and Z. Microstepping is set on the board with switches, and the host software assumes 8x.
  • Motion: 200 steps/rev steppers on 2 mm pitch rods, so the host uses 800 steps per mm (200 x 8 microsteps / 2 mm).
  • Pen lift: the Z stepper raises and lowers the pen. The default stroke is 6500 steps and can be changed with SZS.
  • Limit switches: one each for X, Y and Z, used for calibration. Each limit-switch input has an external 4.7 kΩ pull-up resistor.
  • Work area: A4 landscape, 297 x 210 mm (A5 and A3 are also defined in the generator).
  • Indicators: an "instruction" LED (lit while commands are arriving) and a "console mode" LED.
  • Board extras (v2.0): 6 A fuse and bulk capacitor on the motor supply, status LEDs and a debug header.

Pin map (from motorDriver.cpp)

A full ASCII drawing of the Pico with every pin is in StepperDriver-pico/Pico_pins_map.txt.

FunctionGPIO
X stepper: STEP / DIR / EN13 / 12 / 10
Y stepper: STEP / DIR / EN8 / 6 / 9
Z stepper: STEP / DIR / EN4 / 3 / 5
End switch X / Y / Z11 / 7 / 2
Extra buttons B1 / B218 / 17
LED instruction / console mode15 / 14

The limit switches connect their GPIO inputs to GND when pressed. The firmware also enables the Pico's internal pull-ups; the external 4.7 kΩ pull-ups were added to stabilize the switch signals. Motor enable pins are active low.


Getting started

1. Build and flash the firmware

Requirements: Pico SDK 2.2.0, CMake 3.13+, the ARM GNU toolchain (the CMake file is set up for the VS Code Pico extension).

bash
cd StepperDriver-pico
mkdir build && cd build
cmake ..
make -j

Hold BOOTSEL, plug the Pico in, and copy motorDriver.uf2 onto the drive that appears. On boot the console LED blinks six times.

CMakeLists.txt defaults to PICO_BOARD pico. If you are using a Pico 2 / Pico 2 W, configure with cmake -DPICO_BOARD=pico2_w .. (or pico2).

2. Install the Python dependencies

Python 3.10+ is required (the code uses match and X | Y type hints). Run this command from the repository root:

bash
pip install -r requirements.txt

The GUI also needs tkinter, which ships with most Python installers.

3. Generate an FCODE file

Run from inside FCODEgenerator/ (the modules import each other without a package prefix):

bash
cd FCODEgenerator
python
from main import FGenerator

gen = FGenerator("dxfexamples/simpleShape.dxf",
                 acc=0.1,          # curve flattening accuracy in mm
                 text=False,       # True = also draw TEXT / MTEXT as outlines
                 optimize=True,    # reorder strokes to cut pen travel
                 join_tol=0.5)     # points closer than this (mm) count as the same point

gen.generate_instructions()
gen.save(show_visualization=True)  # writes .fcodes/simpleShape.FCODE and opens a preview

The preview draws pen-down moves in black, pen-up travel in red and the paper outline in blue, so you can check placement before anything moves.

4. Plot it

Plug the Pico in before starting Python, then:

bash
cd DataSendToPico
python -c "from src.Controller import Controller; Controller('../FCODEgenerator/.fcodes/simpleShape.FCODE').do_file()"

This runs the .FCODE file generated above. The controller initializes the pen head, homes X and Y with CLB, then moves the carriage to the configured paper-origin offset. Make sure the carriage has room to move before you run it.


FCODE

File format

An FCODE file is plain text: a header, then one instruction per line.

text
header_length 3
name simpleShape
plane_size_mm (297, 210)
MOV 10.0 10.0
PDN
MOV 10.0 200.0
MOV 287.0 200.0
MOV 287.0 10.0
MOV 10.0 10.0
PUP
MOV 76.54 127.03
PDN
MOV 88.93 142.52
...
  • header_length is the number of header lines (including itself). The sender skips that many lines.
  • MOV x y in a file is an absolute position in mm, measured from the bottom-left corner of the paper. Coordinates are rounded to 0.01 mm.
  • The pen starts up, so the first stroke begins with a travel MOV followed by PDN.

Instructions

InstructionArgsMeaningWhere it runs
MOV x y2Move the head to (x, y) in mm. Draws if the pen is downHost converts to relative steps, Pico executes
PUP0Pen upPico
PDN0Pen downPico
WAT s1Wait s secondsPico
CLB0Calibrate X and Y against their end switches. The host then moves to the paper-origin offset and zeroes its positionHost and Pico
HCB0Calibrate Z (pen head) onlyPico
SZS steps1Set the pen up/down stroke length in steps (1-50000)Pico
HOM0HCB, then move to the home position (0, 300) mmHost only
SCM m1Set console mode (0, 1 or 2)Pico (used internally by the sender)

PDN returns an error if the firmware already considers the pen down. PUP runs the lift and endstop-seek sequence. The generator avoids emitting redundant pen-state instructions.

Automatic recalibration

To fight lost steps on long jobs, the generator inserts HCB every 200 instructions and CLB every 1000 (head_calibration_after_instruction and overall_calibration_after_instruction).

Writing FCODE by hand

FGenerator also works without a DXF. Run this from FCODEgenerator/, pass a name instead of a .dxf path, and drive the pen yourself:

python
from main import FGenerator
from gen import PAPER_SIZES

w, h = PAPER_SIZES["A4"]
gen = FGenerator("outline_A4", text=False, optimize=True, join_tol=0.5)

gen.draw_to(0, h)
gen.draw_to(w, h)
gen.draw_to(w, 0)
gen.draw_to(0, 0)
gen.home()
gen.save()

Available calls: pen_up, pen_down, move_to, travel_to (pen up, then move), draw_to (pen down, then move), draw_polyline, wait, head_calibrate, calibrate, home, save. See example_manual_instructions.py for a 5000-segment random-walk example.

Supported DXF content

LINE, CIRCLE, ARC, ELLIPSE, LWPOLYLINE, POLYLINE, SPLINE, SOLID / TRACE / 3DFACE, POINT (drawn as a small circle), TEXT / MTEXT / ATTRIB (as outlines, only with text=True). Block references (INSERT) and DIMENSION entities are exploded. Anything else is skipped.


Pico serial protocol

The Pico enumerates as a USB CDC serial device. The sender finds it by USB vendor ID 2E8A.

  • The host sends one line per instruction, terminated with \n, at most 127 characters.
  • Arguments are separated by whitespace. On the wire MOV takes relative steps, not millimetres (MOV 800 -1600 moves +1 mm in X and -2 mm in Y).
  • For every line the Pico replies with two lines:
    1. Receive status, sent immediately: 0 = line understood, 1 = rejected (unknown instruction, wrong argument count, or a bad number). After a 1 there is no second reply.
    2. Finish status, sent when the instruction is done: 0 = OK, 1 = failed (for example a limit switch was never reached during calibration).
  • If no instruction arrives for 3 seconds, the instruction LED goes off and the motor drivers are disabled.

Firmware behaviour

  • XY motion: the axis with more steps determines the move duration; follow-axis pulses are distributed across that duration so both axes finish together.
  • Timing: X/Y and Z pulse timing is configurable with SMS. The firmware defaults to 50 us half-cycles for X/Y and 100 us for Z; the host adjusts motor speed for each move.
  • Motor enables: X/Y remain enabled while instructions are being processed and are disabled after 3 seconds without a new instruction. The Z motor is disabled after each pen movement.
  • Calibration: CLB drives X and Y towards their switches; HCB moves Z away from its switch, then raises it back until the switch triggers. Every calibration loop has a step limit (400000 for XY, 50000 for Z) so a missing or miswired switch can't make it run forever.
  • Pen stroke length: SZS steps sets the Z-axis down stroke and the reference length used for the fixed pen-up move and endstop seek.

Example of coordinated X/Y step pulses


Host-side tools (DataSendToPico/)

FilePurpose
Controller.pyReads an FCODE file and runs it instruction by instruction, printing progress
MotorController.pymm to steps conversion, host travel range checks (380 x 315 mm), paper-origin offset (A4 preset: 33 mm, 48 mm from the switches, so measure your own), request logging
MotorOverclass.pyPer-axis constants and position bookkeeping (steps per mm, max travel)
CDC_send.pyTransmitter: finds the Pico, sends commands and reads the two status replies, with a 200 s response timeout
CDC_test.pyTiny CLI: type raw instructions such as MOV 100 100 and see both replies
CDC_gui.pyTkinter GUI for the same thing, with quick buttons for CLB, HCB, PUP, PDN
settings.pySPEED_MODE (skips response validation) and other flags
tools.pyColoured request timing/logging helpers

PCB

The controller board is v2.0, designed in KiCad (about 98 x 67 mm). It carries the Pico, three TMC2209 drivers (U1 to U3, one per axis), per-driver microstepping switches, end-switch inputs with 4.7 kΩ pull-ups, a fused motor supply (6 A fuse and a 330 uF bulk capacitor), status LEDs and a debug header. This is the board the firmware pin map targets.

DrawMachine v2.0 PCB layout

  • KiCad project: pcb/2.0/DrawMv2.0/
  • Schematic PDF: pcb2_0_schematic.pdf

The v1 prototype in pcb/test/ is no longer used and is kept only for reference. The *-backups folders are KiCad's automatic backups.


Known issues and to-do

  • Standard G-code support, which would be easier than maintaining FCODE.

Parts

43
QtyValueFootprintReferences
9SW_SPDTown1:SW_Slide-03_Wuerth-WS-SLTV_10x2.5x6.4_P2.54mm_okMS1, MS1, MS1, MS2, MS2, MS2, SPR, SPR, SPR
6100nFCapacitor_THT:C_Disc_D5.0mm_W2.5mm_P2.50mmC1, C2, C3, C8, C9, C10
310KResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR5, R6, R7
347uFCapacitor_THT:CP_Radial_D6.3mm_P2.50mmC5, C6, C7
3~own:TMC2209U1, U2, U3
2SMALLLED_THT:LED_D3.0mmD1, D2
2~Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR2, R3
11KResistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_HorizontalR1
1330uFCapacitor_THT:CP_Radial_D10.0mm_P5.00mmC4
16AFuse:Fuseholder_Clip-5x20mm_Bel_FC-203-22_Lateral_P17.80x5.00mm_D1.17mm_HorizontalF1
1Conn_01x03_PinConnector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_VerticalJ11
1Conn_01x03_Pinown1:XH3pinJ8
1Conn_01x04_Pinown1:XH4pinJ9
1M1own1:4pinTerminalJ1
1M2own1:4pinTerminalJ2
1M3own1:4pinTerminalJ3
1PWRown1:UntitledJ4
1RaspberryPi_Pico_WModule:RaspberryPi_Pico_Common_THTA1
1X_SWown1:XH2pinJ5
1Y_SWown1:XH2pinJ6
1Z_SWown1:XH2pinJ7
1debugown1:debugJ10

Questions

How big is the DrawMv2.0 PCB?

It measures 97.5 × 67.3 mm and has 2 copper layers, 1.6 mm thick.

What parts does the DrawMv2.0 use?

43 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the DrawMv2.0 KiCad files?

From the vpalaga/DrawMachine repository on GitHub, at the commit this page was made from: the board is pcb/2.0/DrawMv2.0/DrawMv2.0.kicad_pcb and its schematic pcb/2.0/DrawMv2.0/DrawMv2.0.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the DrawMv2.0 design in my own project?

Its repository states no licence, so its author keeps all rights: ask vpalaga before reusing the design.

Similar boards

  • 2 layers52.2 × 52.2 mmBase, the printed circuit board in 3D

    Base

    google/mozc-devices

    Circuit diagrams and firmware source code for Gboard DIY keyboards

    43 parts3kApache-2.0
  • 2 layers33 × 34 mmESP32-bat monitor latch, the printed circuit board in 3D

    ESP32-bat monitor latch

    pcbreflux/espressif

    all espressif stuff will committed here

    43 parts608GPL-3.0
  • 2 layers42.9 × 19 mmPoly kb 2.25U, the printed circuit board in 3D

    Poly kb 2.25U

    thpoll83/PolyKybd

    PolyKybd

    43 parts411CERN-OHL-W-2.0
  • 2 layers99.1 × 99.7 mmRC6502 Prototyping, the printed circuit board in 3D

    RC6502 Prototyping

    tebl/RC6502-Apple-1-Replica

    An expandable SBC-version of the Apple 1 computer, easy to get started with and lot of cool stuff that can be added to it!

    43 parts193GPL-3.0
  • 2 layers26.6 × 38.7 mmMdb slave esp32s3-mini, the printed circuit board in 3D

    Mdb slave esp32s3-mini

    nodestark/mdb-esp32-cashless

    ESP32-based MDB cashless device for vending machines, supporting EVA DTS DEX/DDCMP telemetry and payments over the MDB protocol via Bluetooth and MQTT

    43 parts190MIT
  • 2 layers97.3 × 71 mmZynScreen v1, the printed circuit board in 3D

    ZynScreen v1

    zynthian/zynthian-hw

    Zynthian hardware stuff: PCBs, schemes, parts specifications, pin assignment, ...

    43 parts156