Production Python machines still use GCSP v1 (firmware 1.12). CANopen is a separate sketch (firmware 2.1) for PLC tools that import an EDS. Flash one protocol per board, and do not mix GCSP and CANopen nodes on the same CAN bus — the ID maps collide. Motion matches GCSP 1.12: S-curve closed loop, acceleration feedforward, and optional encoder LUT.
What you get
| Item | Version / value |
|---|---|
| Firmware | 2.1 — CiA 301 + CiA 402 subset (pp / pv / hm) |
| Physical layer | CAN 2.0A, 11-bit, default 1 Mbps |
| Node ID | 1–127 (default 1). Not the GCSP 1–31 limit |
| Position units | MT6701 encoder counts, 16384 / revolution |
| Identity | vendor 0x000005A3 (placeholder), product 0x00004333, revision 0x00020100, device type 0x00040192 |
| Device name | CANStepper |
Vendor 0x5A3 is a development placeholder. Register a real CiA vendor ID
before a production EDS.
Downloads (EDS, DCF, object dictionary, firmware)
| File | Use |
|---|---|
| GrafitoCANStepper_C3_CANopen.ino | CANopen sketch (fw 2.1) |
| canopen_stack.h | Required header — flash the folder, not the .ino alone |
| GrafitoCANStepper.eds | CiA 306 EDS — fw 2.1 template (replace any 2.0 file) |
| GrafitoCANStepper_Node1.dcf | DCF instance, node 1, 1 Mbps, fw 2.1 |
| GrafitoCANStepper_Node2.dcf | DCF instance, node 2, 1 Mbps (flash CO_FACTORY_NODE_ID=2) |
| object_dictionary.json | Object dictionary (JSON) |
| object_dictionary.csv | Object dictionary (CSV) |
| Grafito_CANStepper_EDS_Configuration_Guide.pdf | PLC / EDS configuration guide (PDF) |
| File type | Role |
|---|---|
| EDS | Device template — add the product to TwinCAT / Codesys / a CANopen master |
| DCF | One instance (node ID + 1 Mbps + filled COB-IDs + ParameterValue) |
| JSON / CSV | The same object dictionary as a table (not for PLC import) |
| PDF guide | Full EDS / PLC configuration walkthrough |
Import the firmware 2.1 EDS as the device type (FileRevision 2,
identity revision 0x00020100). If a PLC already has an older
GrafitoCANStepper.eds (fw 2.0 / revision 0x00020000), delete it
and re-import — that pack does not match this board.
If the tool wants a configured node, import GrafitoCANStepper_Node1.dcf
or GrafitoCANStepper_Node2.dcf (node 2 needs
-DCO_FACTORY_NODE_ID=2 at flash). Node2 DCF commissions 0x200C = 0 so
the first jog is not killed by HOME. For another ID, regenerate from
canstepper.canopen402.render_dcf(node_id=N) — do not reuse node-1
COB-IDs.
Flash
Full procedure: Flashing firmware (Arduino IDE + CLI, factory node 2, boot banner, NVS).
Same hardware and Arduino settings as GCSP: ESP32C3 Dev Module, USB CDC On Boot: Enabled, libraries FastAccelStepper + TMC2209. Apply Vin 5–24 V (typically 24 V); USB-C is data only.
Download both the .ino and canopen_stack.h into the same folder
(GrafitoCANStepper_C3_CANopen/).
# Factory node 1 (default)
arduino-cli compile --upload -p /dev/ttyACM0 \
--fqbn esp32:esp32:esp32c3:CDCOnBoot=cdc \
path/to/GrafitoCANStepper_C3_CANopen
# Factory node 2 (second board on a daisy-chain)
arduino-cli compile --upload -p /dev/ttyACM0 \
--fqbn esp32:esp32:esp32c3:CDCOnBoot=cdc \
--build-property "compiler.cpp.extra_flags=-DCO_FACTORY_NODE_ID=2" \
path/to/GrafitoCANStepper_C3_CANopenHealthy boot (115200 baud). Opening USB-JTAG resets the C3 — wait for
this banner and heartbeat 0x7F:
# GrafitoCANStepper CANopen fw 2.1 CiA402
702 0 00
# CAN started (1000000 bit/s) node 2
702 0 7FNode ID is object 0x2000. Change it, write 0x2008 = 1 (save), then
reset so COB-IDs update. NVS namespace is co402 (it does not inherit
GCSP invert / node ID).
Do not mix with GCSP
GCSP uses CAN ID = (node_id << 6) | msg_id. That range overlaps CANopen
SYNC (0x080), EMCY, PDOs, SDOs and heartbeats (0x700+n). One protocol
per bus.
CiA 402 enable and motion
Modes of operation (0x6060): 1 profile position, 3 profile
velocity, 6 homing. Set the mode before cyclic PDOs.
Enable sequence (controlword 0x6040):
6 → 7 → 15Statusword 0x6041 low bits should walk 0x21 → 0x23 → 0x27 (with
0x0200 remote typically 0x0237 when enabled). Fault bit 0x0008:
write 0x0080 to reset.
Homing methods (0x6098): 35 set zero here; 17 / 18 endstop;
−1 / −2 StallGuard.
Manufacturer objects you will use on the bench:
| Index | Name | Notes |
|---|---|---|
| 0x2000 | Node ID | Save + reset to apply COB-IDs |
| 0x2003 | Run current | Modest 30–40 for first jog |
| 0x2006 | Invert direction | Set 1 if a positive move runs the wrong way |
| 0x2007 | Closed loop | 0 = open-loop step pulses; 1 = encoder PID |
| 0x2008 | Save config | Write 1 |
| 0x200C | Endstop enable | Set 0 for the first jog if HOME is already active |
| 0x6081 | Profile velocity | Counts/s. Default ~720 °/s; 4096 ≈ 90 °/s |
| 0x6083 | Profile acceleration | Counts/s². Default 65536 ≈ 1440 °/s² |
| 0x200F / 0x2010 / 0x2011 / 0x201A | PID Kp / Ki / Kd / Ka | Defaults 10 / 0.3 / 0.35 / 0.04 — tune from PLC |
| 0x2012 | PID tolerance | Settle window, default 0.35° |
| 0x201B | Profile jerk | Deg/s³. 0 = auto (amax / 0.05 s) |
| 0x201C | LUT enable | Requires a valid table (0x201E) |
| 0x201D | LUT command | Write 0 calibrate, 1 enable, 2 disable, 3 clear |
Default RPDO1: controlword + target position. TPDO1: statusword + actual position (firmware emits TPDOs every 20 ms in NMT operational).
Tune gains from the PLC
Closed-loop command is:
v = v_ff + Ka · a_ff + PID(r − encoder)PID only trims lag. Cruise speed is not a PID gain — set that with
0x6081. Write the gain objects as REAL32 (IEEE-754). In TwinCAT /
Codesys map them as REAL. Do not write integer 10 into 0x200F; it
must be float 10.0.
Objects to change
| Index | Name | Type | Factory | What it does |
|---|---|---|---|---|
| 0x200F | Kp | REAL32 | 10.0 | Stiffness. Raise if it lags the plan; lower if it buzzes. |
| 0x2011 | Kd | REAL32 | 0.35 | Damping. Raise if it rings at the end of a move. |
| 0x2010 | Ki | REAL32 | 0.3 | Steady-state. Keep small; too much causes overshoot / following error 0x7122. |
| 0x201A | Ka | REAL32 | 0.04 | Accel feedforward. Raise if it sags during accel/decel. |
| 0x2012 | PID tolerance | REAL32 | 0.35° | Target-reached band. Too tight at high RPM → 0x7122. |
Set these before touching PID
| Index | Name | Notes |
|---|---|---|
| 0x2006 | Invert | 1 if a positive target runs the wrong way (PID will fight the encoder). |
| 0x2003 | Run current % | Start 30–40. Gains cannot fix a starved motor. |
| 0x2007 | Closed loop | Must be 1 for encoder PID (0 = open-loop step pulses). |
| 0x6081 | Profile velocity | counts/s. Default 32768 ≈ 720 °/s. |
| 0x6083 | Profile accel | counts/s². Default 65536 ≈ 1440 °/s². |
| 0x201B | Jerk | deg/s³. 0 = auto (amax / 0.05 s). |
Save so it survives power-cycle: write 0x2008 = 1.
Order on the PLC
- Enable
6 → 7 → 15, confirm0x2007 = 1, invert with0x2006if needed. - Set a modest
0x6081/0x6083and current 30–40. - Leave Ki = 0.3 and Ka = 0.04.
- Change Kp in steps of ~2 (try 8 → 10 → 12).
- If it oscillates at settle, raise Kd (0.35 → 0.5) or drop Kp.
- Only then nudge Ka or Ki.
- Write
0x2008 = 1when it is good.
Measured precision defaults (fw 2.1 / GCSP 1.12): Kp 10, Ki 0.3, Kd 0.35, Ka 0.04, tolerance 0.35°. See Closed-loop speed tuning for the hardware tables (those pages use GCSP names; the objects above are the PLC map).
1 Mbps is the product maximum bitrate, not a slow cap. A PLC should command motion with PDOs (1–20 ms). USB hex-bridge SDO polling is for bring-up only and is much slower than the bus.
First-commission checklist
- Flash CANopen 2.1. Confirm banner and heartbeat
0x7n(n= node ID). - Read
0x1000(0x00040192) and0x1018(vendor / product / revision). - If
0x2018(endstop) is 1 and you are not at a real home, write0x200C = 0. - Set
0x2003(current) and a low0x6081for the first move. - Enable
6 → 7 → 15. Coils hold; shaft should not run. - Command a small move (for example +455 counts ≈ +10°). If the shaft
runs the other way, abort, set
0x2006 = 1, save, reset, retry. - Only then raise speed or switch to RPDO cyclic control.
Daisy-chain (two nodes)
- Flash board A as node 1, board B as node 2 (
CO_FACTORY_NODE_ID=2). - Confirm IDs before joining CAN (two node-1s collide).
- CAN_H to CAN_H, CAN_L to CAN_L. 120 Ω only on the two physical ends.
- Vin on both boards. USB on either board is a hex bridge / bus spy.
Heartbeats: node 1 = 701 0 7F, node 2 = 702 0 7F (pre-op). SDO to
0x601 / 0x602.
Host tools (Python)
From can_stepper/ with PYTHONPATH=.:
# Identity / 402 enable on one USB node (no motion by default)
python3 tools/canopen_bringup.py /dev/ttyACM0 --node 1
# Two-node listen via the USB board (default USB = node 2)
python3 tools/canopen_two_node.py /dev/ttyACM0 --usb-node 2 --peer 1
# Regenerate EDS + DCF + JSON/CSV from the same object table
python3 -c "from canstepper.canopen402 import write_canopen_descriptions; print(write_canopen_descriptions())"canstepper.canopen402 is the host-side source of truth for the object
dictionary, EDS/DCF renderers, and the in-process slave used by pytest.
Related
- Flashing firmware — Arduino IDE / CLI, node 1 vs node 2
- Closed-loop speed tuning — GCSP names for the same motion
- Firmware — GCSP 1.12 vs CANopen 2.1 downloads
- Protocol — GCSP v1 (do not use on a CANopen bus)
- Hardware — power, termination, GPIO8 strapping
- Quickstart — GCSP Python path