This guide covers the current
pik1d-based setup which replaces the earlier socat approach documented in the original guide.
pik1d is a small C daemon that bridges the K1 MCU serial ports to PTYs on the
Pi over a USB CDC ACM link. It uses the serialmux code for the MCU channels and
can also launch a sibling tcpbridge process for a second CDC ACM link used by
the touchscreen Moonraker tunnel.
The TCP tunnel is intended for low-bandwidth Moonraker API traffic from the K1 touchscreen to the Pi. It should not be used for high-bandwidth traffic such as webcam streams or file transfers.
- Make sure your K1 mainboard has a populated micro-USB header. This is very risky to do unless you have a way to recover your K1 to stock. See Creality's recovery flashing instructions.
- You should already be using a probe supported by Simple AF because stock Klipper does not support the K1's multiple load cells. This guide assumes a Cartographer over USB, but most probes should work.
- You will need an SBC that supports USB OTG mode. This guide assumes a Raspberry Pi 4, but other devices are technically possible.
-
The Pi may backfeed power to the K1 via the VCC+ line of the USB cable. This is very risky and can cause all sorts of hard-to-debug issues. The Pi and K1 must still share ground, so only the VCC+ line should be cut -- leave shielding and GND intact.
Preventing backfeeding (pick one):
- Kapton tape over the VCC+ pin of the USB cable plugged into the printer
- Cable surgery to snip the VCC+ wire on the cable
- A USB power blocker dongle (widely available)
- A printed jig to block the VCC+ pin
- JST connectors wired to the K1 mainboard's USB header with the VCC+ wire removed
Powering the Pi (its USB-C port is occupied by OTG, pick one):
- A USB-C power/data splitter (still needs VCC+ cut on the K1 side)
- A PoE adapter or HAT
- Regulated voltage to the GPIO power pins if you know exactly what you are doing
Note for Pi 5 users: The Pi 5 has unique power requirements and software-side checks. A USB-C power splitter can supply rated power with an appropriate supply.
-
- Connect the Pi's USB-C port to the K1's USB header, having addressed the VCC+ issue
above. Shielded cables are strongly recommended.
- Simple: USB-A male to USB-C male into the K1's front USB port
- Neat: a JST to USB cable plugged directly into the K1 mainboard header
- Combined: a USB-C power + data splitter for the Pi plus a JST to USB cable with the power wire snipped
- Connect the Cartographer to the Pi over USB using the included cable
- Recommended: unplug the camera cable from the K1, adapt it from JST to USB, and plug into the Pi
- Optional: redirect the K1's front USB port to the Pi using the same JST adapter
You may also need male/male or female/female JST adapter cables.
- Connect the Pi's USB-C port to the K1's USB header, having addressed the VCC+ issue
above. Shielded cables are strongly recommended.
The following files are involved:
src/-- C source forpik1d,tcpbridge, and the shared serial mux codeS99pik1.in-- K1 init script template, rendered tobuild/S99pik1setup_pik1.sh-- Pi gadget setup scriptpik1.service.in-- Pi systemd service template
Pre-built binaries for K1 (build/pik1d.mipsel, build/tcpbridge.mipsel) and
Pi (build/pik1d.aarch64, build/tcpbridge.aarch64) are included in the repo
and are updated with each release. Normal installs use these included files.
On a development machine, you can optionally rebuild them from source with the
cross-compiler targets:
make toolchain # one-time: downloads musl.cc cross-compilers into .toolchain/
make mipsel # K1 binaries → build/pik1d.mipsel, build/tcpbridge.mipsel
make aarch64 # Pi binaries → build/pik1d.aarch64, build/tcpbridge.aarch64-
Install Simple AF for RPi.
-
Run the following script once to configure the Pi to act as a USB gadget. This puts the USB-C port into OTG/peripheral mode, which disables host mode on that port.
#!/usr/bin/env bash set -euo pipefail BOOT=/boot/firmware # Use /boot for pre-Bookworm images CFG="$BOOT/config.txt" CMD="$BOOT/cmdline.txt" # Enable dwc2 overlay (puts USB-C into OTG mode) grep -qxF 'dtoverlay=dwc2' "$CFG" || echo 'dtoverlay=dwc2' | tee -a "$CFG" # Add dwc2 to kernel cmdline (guard against running twice) grep -qF 'modules-load=dwc2' "$CMD" || sed -i.bak -E 's/$/ modules-load=dwc2/' "$CMD" # Autoload libcomposite at boot grep -qxF 'libcomposite' /etc/modules || echo 'libcomposite' | tee -a /etc/modules echo "Done. Reboot for changes to take effect."
Reboot the Pi after running this.
If the gadget fails to bind after reboot, try changing
dtoverlay=dwc2todtoverlay=dwc2,dr_mode=peripheralin/boot/firmware/config.txt. Some Pi configurations require the mode to be set explicitly. -
From the repo directory on the Pi, using the included pre-built binaries:
make install-pi
This copies the binaries and setup script to
/opt/pik1/, installs and enablespik1.service, and runssystemctl daemon-reload. PassSUDO=if running as root, orPI_DIR=/your/pathto override the install prefix.The service runs
setup_pik1.shas root first (needed for configfs access), then startspik1das UID 1000 so the PTY devices it creates are accessible to Klipper.If there are any weird permissions errors then make sure UID 1000 (your klipper user, typically
pior similar) is in thedialoutgroup so it can open/dev/ttyGS0and/dev/ttyGS1:sudo usermod -aG dialout $(id -un 1000) -
Add or update the MCU serial paths in your
printer.cfg:[mcu] serial: /tmp/klipper_mcu restart_method: command [mcu nozzle_mcu] serial: /tmp/klipper_toolhead restart_method: command
restart_method: commandis required. Hardware reset via DTR/RTS does not work over the serialmux tunnel as no hardware control lines are available.
-
Install Simple AF on the K1.
-
From a slim repo clone on the K1, such as
/root/pik1:make install-k1
This copies
build/pik1d.mipselandbuild/tcpbridge.mipselto/usr/data/pik1/, renders and installs/etc/init.d/S99pik1, and disables the services below by renaming them with a_prefix so the init system skips them. To use a different install directory, pass the sameK1_DIRvalue at install and uninstall time:make install-k1 K1_DIR=/your/install/path make uninstall-k1 K1_DIR=/your/install/path
Service Reason S55klipper_serviceMust not run — K1 is now bridge-only S56moonraker_serviceMust not run — no local Klipper S55klipper_mcuHost MCU software, not needed S50nginx_serviceProxied Moonraker, no longer relevant S50unslungUnrelated to printing S50webcamCamera can be moved to the Pi S99guppyscreenSee optional TCP tunnel section below If transferring via scp rather than running from a repo clone on the K1, render the init script locally with the same install path you will use on the printer:
K1_DIR=/usr/data/pik1 make render-k1-init K1_DIR="$K1_DIR" ssh root@<k1-ip> "mkdir -p $K1_DIR" scp build/pik1d.mipsel root@<k1-ip>:$K1_DIR/pik1d scp build/tcpbridge.mipsel root@<k1-ip>:$K1_DIR/tcpbridge scp build/S99pik1 root@<k1-ip>:/etc/init.d/S99pik1 ssh root@<k1-ip> "chmod +x $K1_DIR/pik1d $K1_DIR/tcpbridge /etc/init.d/S99pik1"
Then disable the K1-side services that should not start in bridge mode:
ssh root@<k1-ip> ' for svc in S50nginx_service S50unslung S50webcam \ S55klipper_mcu S55klipper_service S56moonraker_service S99guppyscreen; do if [ -f /etc/init.d/$svc ]; then mv /etc/init.d/$svc /etc/init.d/_$svc fi done '
-
Reboot the K1 so the renamed init scripts take effect. Reboot the Pi as well so the USB gadget and
pik1.servicestart from a clean boot sequence.On the K1:
reboot
On the Pi:
sudo reboot
Upon start, the K1 daemon logs to
/tmp/pik1.log.
The TCP tunnel forwards the Simple AF K1 touchscreen's (guppyscreen) Moonraker requests to the Pi over the second USB CDC ACM link. This is strongly recommended over the alternative of pointing guppyscreen at the Pi's WiFi IP address -- WiFi is unreliable enough that you will eventually lose display functionality mid-print. The tunnel runs over the same wired USB link as the MCU bridge and stays up as long as the physical connection does.
The tunnel is low-bandwidth and intended for Moonraker API traffic only (temperatures, print status, controls). Do not route webcam streams or file transfers through it.
The included K1 init script and Pi systemd service enable this tunnel by default
with matching tcp: specs. guppyscreen requires no configuration changes -- it
continues talking to localhost:7125 as normal and the tunnel forwards those
connections to the Pi transparently.
K1 init script -- /etc/init.d/S99pik1 starts pik1d in MCU-exporting mode:
DAEMON_ARGS="--usb $USB_ID \
mcu:0:$CH0_DEV:$CH0_BAUD \
mcu:1:$CH1_DEV:$CH1_BAUD \
tcp:$TCP_ADDR:$TCP_PORT"Also re-enable guppyscreen if you disabled it:
mv /etc/init.d/_S99guppyscreen /etc/init.d/S99guppyscreenTCP_ADDR is 127.0.0.1 by default so only local touchscreen requests are
accepted on the K1. Set it to 0.0.0.0 only if you deliberately want the
forwarded listener exposed on the K1 network interface.
Pi systemd service -- /etc/systemd/system/pik1.service starts pik1d in
PTY-hosting mode:
ExecStart=/opt/pik1/pik1d --usb 1d6b:0104 \
pty:0:/tmp/klipper_mcu \
pty:1:/tmp/klipper_toolhead \
tcp:127.0.0.1:7125Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart pik1No changes are needed to guppyscreen's configuration -- it continues
talking to 127.0.0.1:7125 as if Moonraker were local.
-
sudo systemctl status pik1 journalctl -u pik1 -f
After rebooting both devices, it should show
active (running). Journal lines include systemd timestamps and process metadata, andpik1d/tcpbridgemessages may appear in a slightly different order. The message text should look like:setup_pik1: loading libcomposite setup_pik1: creating gadget at /sys/kernel/config/usb_gadget/pik1 setup_pik1: binding gadget to UDC: fe980000.usb setup_pik1: ttyGS0 ready setup_pik1: ttyGS1 ready [pik1] mode=host channels=2 tcp=127.0.0.1:7125 tunnel=/dev/ttyGS1 [pik1] child: spawned /opt/pik1/tcpbridge pid=... [mux] link opened: /dev/ttyGS0 [tcp] tcpbridge /dev/ttyGS1 forward 127.0.0.1:7125 [tcp] link opened: /dev/ttyGS1 [mux] link up [tcp] link up [mux] ch0 PTY /tmp/klipper_mcu -> /dev/pts/2 [mux] ch1 PTY /tmp/klipper_toolhead -> /dev/pts/3 -
cat /tmp/pik1.log
A normal startup looks like. Because
pik1dandtcpbridgewrite to the same log, adjacent lines may occasionally interleave:[pik1] mode=exporter channels=2 tcp=127.0.0.1:7125 tunnel=/dev/ttyACM1 [pik1] child: spawned /usr/data/pik1/tcpbridge pid=... [mux] link opened: /dev/ttyACM0 [tcp] tcpbridge /dev/ttyACM1 listen 127.0.0.1:7125 [tcp] listening on port 7125 [tcp] link opened: /dev/ttyACM1 [mux] link up [tcp] link up [mux] ch0 MCU active [mux] ch1 MCU activeIf you changed
TCP_ADDRin/etc/init.d/S99pik1, the logged TCP address will match that configured value. -
[ 7.614279] dwc2 fe980000.usb: bound driver configfs-gadget.pik1 [ 7.846025] dwc2 fe980000.usb: new device is high-speed [ 7.900000] dwc2 fe980000.usb: new address ... -
Klipper should connect to both MCUs within about 15 seconds of the K1 booting -- this is the GD32 bootloader dwell time and is normal.
FIRMWARE_RESTARTalso takes approximately 15 seconds for the same reason.A good indicator of successful first-boot setup is the printer's LEDs turning off and then back on as the MCUs initialise under Klipper control.
-
make uninstall-k1
This removes the init script and binaries and restores all disabled services. Alternatively:
mv /etc/init.d/S99pik1 /etc/init.d/_S99pik1 mv /etc/init.d/_S55klipper_service /etc/init.d/S55klipper_service mv /etc/init.d/_S56moonraker_service /etc/init.d/S56moonraker_service # restore any other services as needed -
sudo systemctl stop pik1
Optional -- the service will just sit idle if left running.
-
Revert
printer.cfgserial paths and reconfigure cables as needed.You can run both the Pi and K1 standalone simultaneously (e.g. for camera services) without conflict as long as the pik1 init script is disabled.
- Pi mount: Once everything is working you can print a nice combined mount for the K1 mainboard and a Raspberry Pi.
- KlipperScreen: The Pi runs KlipperScreen by default. Connect an HDMI touchscreen to the Pi's HDMI port to use it, or uninstall it if not needed.