Three homing methods
| Method | Needs | Best for |
|---|---|---|
endstop | switch on IO8 | repeatable reference, production machines |
stallguard / sensorless | hard stop only (TMC DIAG) | no-wiring setups, enclosed axes |
set_zero | nothing | “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:
- Optionally drop to
homing_current(parameter; 0 keeps run current). - Move at the commanded speed and direction until the trigger, bounded by
homing_timeout_ms. - Hard stop. The trigger point becomes 0°.
- Back off by
homing_backoffdegrees. - Restore current, emit
HOMING_DONE, set thehomedstatus 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):
| Script | What it does |
|---|---|
endstop_monitor.py | Poll IO8 + print ENDSTOP_HIT / RELEASED (no motion) |
home_endstop.py | Configure IO8 endstop, home, jog away |
home_sensorless.py | StallGuard home against a hard stop |
home_set_zero.py | Software zero at current pose |
home_axis_mm.py | Same 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 -1Endstop 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_activeis 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:
| Action | Value | Behavior on trigger |
|---|---|---|
| report | 0 | event telemetry only |
| stop | 1 | instant stop (default) |
| stop + zero | 2 | instant 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.
Related
- Hardware guide — IO8 strapping pin, power, CAN
- Python library — node / axis API
- Protocol —
HOMEcommand, endstop parameters - G-code layer — soft
G28only (physical home via API)