Grafito CANStepper

The Grafito CAN Stepper Protocol v1 is the contract between hosts and boards. The Python reference codec is canstepper/protocol.py; the firmware implements the same tables.

Physical layer

  • CAN 2.0A, standard 11-bit identifiers, 1 Mbps.
  • Daisy-chain, 120 Ω termination at both physical ends, up to 31 nodes.

Addressing

CAN ID (11 bit) = (node_id << 6) | msg_id
FieldBitsRangeMeaning
node_id10..60–310 = broadcast (all nodes execute), 1–31 = one board
msg_id5..00–630–31 command (host to node), 32–63 telemetry (node to host)
  • All multi-byte values are little-endian.
  • A remote frame (RTR) with a telemetry msg_id polls that telemetry.
  • Nodes broadcast telemetry periodically: the fast group (POSITION, MOTION, PID_STATUS) at fast_rate_hz (default 10 Hz), the slow group (STATUS, DRIVER, ENV, FOLLOW_STATUS, CAN_HEALTH) at slow_rate_hz (default 1 Hz). Rate 0 disables a group.

Serial bridge

Any node bridges its USB CDC port (115200 baud) to the bus; one text line per frame in each direction:

<CAN_ID hex> <RTR 0|1> <payload hex>\n

Example — move node 1 to 180°: 44 0 0000000000C06640 (0x44 = (1 shl 6) or 4 = MOVE_ABS; payload is f64 180.0).

  • Payload length = hex digits / 2 (0–8 bytes). RTR lines carry no payload.
  • Lines starting with # are debug output — skip them.
  • The bridging node executes frames addressed to it (or broadcast) and re-transmits them on CAN; everything it sees on the bus, plus its own telemetry, is printed to the port.

Commands (msg_id 0–31)

#NamePayloadBehavior
0PINGNode answers with STATUS immediately
1ESTOPInstant stop + driver disable. Latched until ENABLE 1
2STOPDecelerating stop, driver stays energized
3ENABLEu8 0/1Driver off/on; enabling clears the e-stop latch
4MOVE_ABSf64 degAbsolute position. OL: FAS trap. CL fw ≥1.2: trap plan + v_ff + PID
5MOVE_RELf64 degRelative to active target (or current). Same profiles as MOVE_ABS
6MOVE_VELf32 deg/sSigned continuous velocity; 0 = ramped stop
7SET_ZEROStop; current angle becomes 0°
8HOMEu8 method, i8 dir, f32 speedHoming routine (methods: 0 set-zero, 1 endstop, 2 StallGuard)
9SET_PARAMu8 id + 4-byte valueNode replies with PARAM (status + readback)
10GET_PARAMu8 idNode replies with PARAM
11SAVE_CONFIGPersist parameter table to flash
12LOAD_DEFAULTSFactory defaults + persist; node keeps its ID
13FOLLOWu8 en, u8 leader, u8 flags, u8 rsvd, f32 ratioflags: bit0 invert, bit1 encoder-corrected
14FOLLOW_SYNCRecapture leader/local zero on next leader frame
15SET_POSITIONf64 degRelabel current shaft position without physical movement
16–31reservedignored

Closed-loop motion profile (firmware ≥1.2)

When closed_loop = 1 and the node receives MOVE_ABS / MOVE_REL:

  1. Plan a rest-to-rest trapezoid (or triangle if short) from the encoder angle to the target using vmax = min(max_speed, cl_max_speed) and amax = cl_max_accel.
  2. Each 5 ms: reference position r(t) and feedforward velocity v_ff(t).
  3. Track: v_cmd = v_ff + Kp·(r − encoder) + Ki·∫ − Kd·v_meas.
  4. Settle when the plan is finished, |target − encoder| ≤ pid_tolerance, and speed is low → MOVE_DONE (event 7).

Open-loop position uses FastAccelStepper’s trapezoid on step counts. MOVE_VEL is open-loop continuous velocity. FOLLOW with encoder correction uses a continuous braking-law chase (not a full re-planned trap each frame).

Tuning, measured RPM, and soak data: Closed-loop speed tuning.

Parameters

4-byte values, u32 or f32. Ranges are hardware-sanity checks only — motion limits are configurable defaults, never firmware clamps. Persisted by SAVE_CONFIG, loaded at power-on.

IDNameTypeDefaultRangeDescription
1node_idu3211–31CAN address
2steps_per_revu322001–100000full steps per motor rev
3microstepsu32161–256, power of 2TMC microstepping
4run_currentu32301–100% of driver max
5hold_currentu32150–100standstill current, %
6stall_thresholdu32100–255SGTHRS, higher = more sensitive
7invert_diru3200/1flip physical rotation
8closed_loopu3210/1encoder position loop
9max_speedf32720positiveopen-loop position cruise, deg/s
10accelerationf322880positiveopen-loop position ramp, deg/s²
11cl_max_speedf32720positiveCL trapezoid cruise vmax, deg/s (fw ≥1.2)
12cl_max_accelf322880positiveCL trapezoid accel/decel, deg/s²
13pid_kpf3212.0≥ 0tracking Kp on (r − encoder); v_ff carries the move
14pid_kif320.3≥ 0tracking Ki
15pid_kdf320.10≥ 0D-on-measured-velocity
16pid_tolerancef320.350.001–360settle window, deg
17fast_rate_hzu32100–500POSITION/MOTION/PID_STATUS
18slow_rate_hzu3210–500STATUS/DRIVER/ENV/…
19enable_on_bootu3210/1
20zero_on_bootu3200/1
21standstill_modeu3200–3normal / freewheel / brake / strong
22endstop_enableu3200/1
23endstop_active_highu3200/10 = active low, pull-up
24endstop_actionu3210–2report / stop / stop+zero
25homing_currentu3200–1000 = keep run current
26homing_backofff322.0≥ 0deg
27homing_timeout_msu3230000100–600000
28stealthchopu3210/1off = SpreadCycle

Telemetry (msg_id 32–63)

#NamePayloadWhen
32STATUSu8 flags, u8 mode, u8 fault, u8 fw_major, u8 fw_minor, u8 proto, u8 node_idslow, PING, RTR
33POSITIONf64 encoder deg (multi-turn)fast, RTR — drives followers
34MOTIONf32 velocity deg/s, f32 position error degfast, RTR
35TARGETf64 active target degRTR
36EVENTu8 event, u8 detail, f32 dataon occurrence
37DRIVERu16 StallGuard, u8 TMC-UART-okslow, RTR
38ENVf32 MCU temp °C, f32 bus voltsslow, RTR
39PARAMu8 id, u8 status (0 ok / 1 unknown / 2 rejected), 4-byte valuereply to SET/GET_PARAM
40FOLLOW_STATUSu8 en, u8 leader, u8 flags, u8 synced, f32 ratioslow (following), RTR
41CAN_HEALTHu8 state, u8 tx_err, u8 rx_err, u8 recoveries, u16 tx_failed, u16 bus_errorsslow, RTR
42ENC_COUNTSi64 counts (16384 per rev)RTR
43PID_STATUSu8 state, u8 fault, f32 output deg/sfast (closed loop), RTR
44–63reserved

STATUS flag bits

BitMeaning
0x01driver enabled
0x02moving
0x04homed
0x08e-stop latched
0x10endstop active
0x20stall latched
0x40encoder frame OK

Enumerations

  • mode: 0 idle, 1 position, 2 velocity, 3 homing, 4 following
  • fault: 0 none, 1 encoder invalid, 2 no progress, 3 homing timeout
  • event: 1 boot, 2 endstop hit, 3 endstop released, 4 stall, 5 homing done, 6 homing failed, 7 move done, 8 e-stop, 9 fault (data is usually the position in degrees at the event)

Leader / follower

FOLLOW makes a node track another node's POSITION broadcasts entirely on-bus. The first leader frame after enable (or FOLLOW_SYNC) captures both zero references, so engagement never jumps:

follower = local_ref + (leader − leader_ref) × ratio × (invert ? −1 : 1)

With flags bit1 the follower closes its own encoder loop on the mapped target (recommended); otherwise it steps open-loop. A 0.15° hysteresis deadband rejects stationary leader-encoder noise without adding lag.

Bus load

A 1 Mbps standard frame with 8-byte payload is roughly 111–135 bits with stuffing. At default rates (about 35 frames/s per node), 31 nodes produce about 1085 frames/s — roughly 15 % bus load. Budget accordingly when raising fast_rate_hz.

On this page