Grafito CANStepper

Three homing methods

MethodNeedsBest for
endstopswitch on IO8repeatable reference, production machines
stallguard / sensorlesshard stop only (TMC DIAG)no-wiring setups, enclosed axes
set_zeronothing“wherever it is now is zero”

All three run inside the firmware — the host starts them and waits for HOMING_DONE / HOMING_FAILED. The Python API is:

node.home(method="endstop", direction=-1, speed_deg_s=30.0)
node.home(method="stallguard", direction=-1, speed_deg_s=25.0)
node.home(method="set_zero")

The homing sequence for endstop and stallguard:

  1. Optionally drop to homing_current (parameter; 0 keeps run current).
  2. Move at the commanded speed and direction until the trigger, bounded by homing_timeout_ms.
  3. Hard stop. The trigger point becomes .
  4. Back off by homing_backoff degrees.
  5. Restore current, emit HOMING_DONE, set the homed status flag.

A timeout emits HOMING_FAILED and raises HomingFailed on the host.

Note: G-code G28 is still a soft zero (set_zero on the logical axes). For physical endstop or StallGuard search, call node.home(...) / axis.home(...) (or register a custom G28 handler).

Example scripts

Runnable demos under can_stepper/examples/ (use PYTHONPATH=. if the installed package is older than this tree):

ScriptWhat it does
endstop_monitor.pyPoll IO8 + print ENDSTOP_HIT / RELEASED (no motion)
home_endstop.pyConfigure IO8 endstop, home, jog away
home_sensorless.pyStallGuard home against a hard stop
home_set_zero.pySoftware zero at current pose
home_axis_mm.pySame flows via Axis in millimetres
cd can_stepper

# Validate wiring first (toggle the switch by hand)
PYTHONPATH=. python3 examples/endstop_monitor.py /dev/ttyACM0 1 30

# Physical endstop home (direction must move toward the switch)
PYTHONPATH=. python3 examples/home_endstop.py /dev/ttyACM0 1 -1

# Sensorless / StallGuard (direction toward hard stop; last arg = SGTHRS)
PYTHONPATH=. python3 examples/home_sensorless.py /dev/ttyACM0 1 -1 60

# Software zero only
PYTHONPATH=. python3 examples/home_set_zero.py /dev/ttyACM0 1

# Axis in mm: endstop | stallguard | sensorless | set_zero
PYTHONPATH=. python3 examples/home_axis_mm.py endstop /dev/ttyACM0 1 -1
PYTHONPATH=. python3 examples/home_axis_mm.py stallguard /dev/ttyACM0 1 -1

Endstop homing (IO8)

from canstepper import CANStepperBus, EndstopAction

with CANStepperBus.serial("/dev/ttyACM0") as bus:
    node = bus.node(1)
    node.enable()
    node.configure_endstop(
        enabled=True,
        active_high=False,           # LOW when switch pressed (default)
        action=EndstopAction.STOP,   # 0 report, 1 stop, 2 stop+zero
    )
    node.configure_homing(
        current_percent=30,
        backoff_deg=5.0,
        timeout_ms=30000,
    )
    node.home(method="endstop", direction=-1, speed_deg_s=20.0)
    print(node.get_status().homed, node.get_position())

Wiring on IO8 — read this twice

IO8 is an ESP32-C3 strapping pin: it must be HIGH at reset or the chip may fail to boot. Full board notes: hardware guide.

Recommended (active-low, normally-open to GND):

3.3V ---- 10k ----+---- IO8 (GPIO 8 / HOME)
                  |
               100–330 Ω series (optional ESD; keep small)
                  |
               [NO switch]
                  |
                 GND
  • Open at rest → pin HIGH → boots correctly; endstop_active is False.
  • Closed → pin LOW → endstop active.
  • Firmware also enables INPUT_PULLUP; an external 10k is fine.
  • Do not use a large series resistor (e.g. 4.7 kΩ) with a strong 10k pull-up — the closed switch forms a divider (~1 V) that may not read as logic LOW. Prefer ≤330 Ω series, or only the internal pull-up with 4.7 kΩ series.

Sensorless (StallGuard) homing

No home switch. The TMC2209 measures motor load; when the axis hits a hard stop, DIAG (GPIO 0) rises and the firmware treats that as the home trigger.

node.set_stall_threshold(60)   # 0–255, HIGHER = more sensitive
node.configure_homing(current_percent=30, backoff_deg=8.0, timeout_ms=30000)
node.home(method="stallguard", direction=-1, speed_deg_s=25.0)
# alias:
# node.home(method="sensorless", direction=-1, speed_deg_s=25.0)

Tuning tips:

  • Use a reduced homing current (25–40 %) so the crash is gentle.
  • StallGuard needs some speed — very slow seeks (below ~10 deg/s on a typical NEMA17) often fail to trigger.
  • Threshold too high → false trips during acceleration; too low → the motor grinds into the stop. Start around 50–60, adjust.
  • Live reading: node.get_stallguard() / node.get_driver_status().stallguard.

Software set-zero

node.home(method="set_zero")
# or
node.set_zero()

No motion; current encoder pose becomes 0° and homed is set.

Endstop as a safety input

Independent of homing, an enabled endstop can guard normal motion via endstop_action:

ActionValueBehavior on trigger
report0event telemetry only
stop1instant stop (default)
stop + zero2instant stop, position becomes 0

Every edge also emits ENDSTOP_HIT / ENDSTOP_RELEASED events:

from canstepper import Event

def on_evt(node_id, event, detail, data):
    if event in (Event.ENDSTOP_HIT, Event.ENDSTOP_RELEASED):
        print(event.name, data)

bus.on_event(on_evt)
print(node.get_status().endstop_active)

Axis-level homing (millimetres)

Axis.home() converts units and applies per-axis defaults:

from canstepper import Axis

axis = Axis(node, rotation_distance=40.0, require_homing=True)
axis.home(
    method="endstop",       # or "stallguard" / "sensorless" / "set_zero"
    direction=-1,
    speed=8.0,              # mm/s
    current_percent=30,
    backoff=1.0,            # mm
)

With require_homing=True, motion before a successful home raises NotHomed.

On this page