This is a copy/paste checklist for bringing up and verifying:
PREEMPT_RT kernel + CPU isolation + IgH EtherCAT master + gradient-rt-motion + monitoring.
- Assume motors can move once you enable/jog. Keep the mechanism safe/clear.
- Start with tiny jogs (e.g.
0.005–0.01 rad) and increase slowly. - RTCore has a hard safety cap when started with
--max-rpm 100(recommended for bring-up). - RTCore is single-client over IPC: don’t run the Python controller and
rtcore_jog.pyat the same time.
From the repo root:
cd ~/GradientOScd systemd/ethercat-host
./install.shWhat this does (high level):
- Installs NIC naming rules (
ethercat0/uplink0) and marksethercat0unmanaged. - Installs + enables tuning units (NIC offload/EEE tuning, IRQ affinity pinning, CPU governor).
- Installs
/etc/ethercat.conftemplate and a drop-in soethercat.serviceuses it.
cd ~/GradientOS/systemd/ethercat-host
sudo ./rtos-apply-cmdline.sh
sudo rebootAfter reboot:
uname -a
grep -E '^CONFIG_PREEMPT_RT=' "/boot/config-$(uname -r)"Expected:
uname -acontainsPREEMPT_RTCONFIG_PREEMPT_RT=y
Notes:
- On this RevPi kernel,
/sys/kernel/realtimemay not exist even when RT is enabled.
cat /proc/cmdline
cat /sys/devices/system/cpu/isolated
# Default IRQ routing (hex mask). For 4 cores, "3" means CPU0-CPU1.
cat /proc/irq/default_smp_affinityExpected:
/proc/cmdlineincludes:isolcpus=2,3 ... irqaffinity=0,1isolatedshows:2-3(or equivalent)default_smp_affinityis3(hex mask for CPUs 0–1)
Notes (important on this image):
/sys/devices/system/cpu/nohz_fulland/sys/devices/system/cpu/rcu_nocbsmay be missing on this kernel build (becauseCONFIG_NO_HZ_FULLis not set). That’s not a failure signal.
cd ~/GradientOS
./scripts/ethercat/diagnose_host.shuname -r
ls -l "/lib/modules/$(uname -r)/build"cd ~/GradientOS
./scripts/ethercat/install_igh.shFor this appliance wiring:
- EtherCAT NIC MAC:
c8:3e:a7:14:1c:75(RevPieth0/ethercat0) - Uplink MAC:
c8:3e:a7:14:1c:76(RevPieth1/uplink0)
Check:
sudo cat /etc/ethercat.confExpected:
MASTER0_DEVICE="c8:3e:a7:14:1c:75"DEVICE_MODULES="generic"
sudo systemctl enable --now ethercat.service
sudo ethercat master
sudo ethercat slaves -v
sudo ethercat slavesExpected:
ethercat master: Link UP, Rx frames > 0, Lost frames ~0ethercat slaves: at least your drives appear (usually PREOP until RTCore runs)
cd ~/GradientOS
make -C src/gradient_rt_motion
sudo install -m 0755 src/gradient_rt_motion/gradient-rt-motion /usr/local/bin/gradient-rt-motionsudo /usr/local/bin/gradient-rt-motion --num-axes 2 --max-rpm 100In another terminal, confirm bus health + OP:
sudo ethercat master
sudo ethercat slavesExpected:
ethercat master:Active: yes, Lost frames ~0ethercat slaves: both drives show OP
In another terminal while RTCore is running:
pid=$(pgrep -n -f '[g]radient-rt-motion')
echo "pid=$pid"
ps -T -p "$pid" -o tid,cls,rtprio,pri,psr,commExpected:
rt-cyclethread:CLSFF (FIFO),RTPRIOaround 90,PSRis 2 or 3metricsthread: normal scheduling (TS), ideally on CPU0/1ipc-helperthread: appears when a client connects; should be CPU0/1
With RTCore running:
ls -l /run/gradient-rt-motion/
python3 -m json.tool /run/gradient-rt-motion/metrics.json | head -n 80
python3 scripts/sampler/rtcore_metrics.py summaryExpected (ballpark):
rt_hznear 1000rt_overrun_count0wkc_actual == wkc_expected(e.g.6/6for 2 axes in current mapping)
cd ~/GradientOS
python3 scripts/rtcore_jog.py statuscd ~/GradientOS
python3 scripts/rtcore_jog.py console --rate-hz 2
# alias (same behavior, useful for validation runs):
python3 scripts/rtcore_jog.py test --rate-hz 2Notes:
- Console watch output includes diagnostics by default (
ovdelta, WKC mismatch alerting, EtherCAT lost-frame deltas). - Use
--no-diagif you want the old minimal status output.
Inside the console:
config
arm 0x1
jog 0 0.01
status
disarm
quit
Notes:
arm 0x1enables axis0 only (safe single-axis bring-up).- Use
reset(orfault_reset) if you need to clear DS402 faults.
RTCore already writes /run/gradient-rt-motion/metrics.json, so monitoring does not consume the single IPC client.
Sampler instructions are maintained in one place:
scripts/sampler/README.md(canonical sampler guide)
Quick start:
cd ~/GradientOS
./scripts/sampler/run_sampler.shWhat to look for in the dashboard:
- RTCore loop Hz stays ~1000
- RTCore wake jitter stays bounded (watch max)
- Timer jitter compare: CPU2 (isolated RT CPU) should generally look better than CPU0
- If the dashboard is blank in an IDE terminal, use the fallback commands from
scripts/sampler/README.md(or run from an external terminal/SSH session).
watch -n 0.5 'python3 ~/GradientOS/scripts/sampler/rtcore_metrics.py summary'This helper prints per-sample deltas and flags events that matter during soak:
OVERRUN+Nwhenrt_overrun_countincreasesWKC_MISMATCHwhenwkc_actual != wkc_expectedLOST+Nwhen EtherCATLost framesincreases
Run for 10 minutes:
cd ~/GradientOS
python3 scripts/sampler/rtcore_diag_watch.py --interval 1.0 --duration 600Quick 30-second check:
cd ~/GradientOS
python3 scripts/sampler/rtcore_diag_watch.py --interval 1.0 --duration 30If sudo ethercat master is unavailable in your shell context, skip lost-frame polling:
cd ~/GradientOS
python3 scripts/sampler/rtcore_diag_watch.py --no-ethercat --interval 1.0 --duration 60Use this exact order when you want to jog servos while watching telemetry:
- Terminal A (root): keep RTCore running
sudo /usr/local/bin/gradient-rt-motion --num-axes 2 --max-rpm 100- Terminal B (pi): start Sampler (or fallback watch if TUI fails)
cd ~/GradientOS
./scripts/sampler/run_sampler.shNote: over SSH this launcher defaults to text monitoring; add --tui only if you want to force the full-screen dashboard.
- Terminal C (pi): run jog console
cd ~/GradientOS
python3 scripts/rtcore_jog.py console --rate-hz 2- Terminal D (optional): run long soak diagnostics in parallel
cd ~/GradientOS
python3 scripts/sampler/rtcore_diag_watch.py --interval 1.0 --duration 600Rules while running:
rtcore_jog.pyis the IPC client; keep only one jog/controller client connected.- Sampler +
rtcore_diag_watch.pyread metrics and can run alongside jog. - For safe stop: in console run
disarm, thenquit, then stop RTCore if needed.
cd ~/GradientOS/systemd/rt-motion
./install.shBy default RTCore’s compiled defaults assume 6 axes. For your current 2-drive bring-up, edit:
sudo nano /etc/systemd/system/gradient-rt-motion.serviceChange the ExecStart line to:
ExecStart=/usr/local/bin/gradient-rt-motion --num-axes 2 --max-rpm 100
Then:
sudo systemctl daemon-reload
sudo systemctl restart gradient-rt-motion.service
sudo systemctl status gradient-rt-motion.service --no-pagerethercat mastershows Rx=0 / Slaves=0:- Wrong NIC bound (check
Main:MAC), wrong cable/port, or not connected to drive CN3 (IN).
- Wrong NIC bound (check
Active: noinethercat master:- Normal unless a
libecrtapp (RTCore) has activated the master.
- Normal unless a
python3 scripts/rtcore_jog.py ...says single-client / broken pipe:- Another client is connected. Stop other controller/jog session or restart RTCore.
nohz_full/rcu_nocbssysfs files missing:- Expected on this kernel build; rely on
isolcpus+ IRQ affinity + measured results instead.
- Expected on this kernel build; rely on