linuxcnc-ethercat

Multi-Rate Systems

One EtherCAT bus can serve devices that need very different rates: a fast analog input at 8 kHz, servo drives at 2 kHz, digital I/O at 1 kHz, a serial gateway at 250 Hz. Without help, the whole HAL then has to run at the fastest rate. Sync Units fix that: each group of slaves is its own EtherCAT domain, exchanged every Nth bus cycle, and can be serviced from a HAL thread of its own rate. The bus thread stays small, and the drives and the rest of the HAL run at the rate they actually use, phase-locked to the bus and its distributed clocks.

This page walks through setting one up. The reference material is in Distributed Clocks, Master pins and the configuration reference. A complete example is in examples/sync-units/multirate.hal and multirate-conf.xml.

1. Plan the rates

A typical layout:

Sync Unit Cycle Divider Serviced by Contents
default 125 us (8 kHz) 1 ecat-thread fast analog input
motion 500 us (2 kHz) *4 servo-thread DC servo drives
io 1 ms (1 kHz) *8 io-thread digital I/O
serial 4 ms (250 Hz) *32 ecat-thread serial gateway

2. Assign slaves to Sync Units

Give every slave that does not belong in the default unit a syncUnit and syncUnitCycle, and optionally a syncUnitPhase:

<master idx="0" appTimePeriod="125000" refClockSyncCycles="-1">
  <slave idx="0" type="EL3162" name="ain"/>
  <slave idx="1" type="generic" ... name="x-drive"
         syncUnit="motion" syncUnitCycle="*4">
    <dcConf assignActivate="300" sync0Cycle="*4" sync0Shift="20000"/>
    ...
  </slave>
  <slave idx="2" type="EL1809" name="din"
         syncUnit="io" syncUnitCycle="*8" syncUnitPhase="2"/>
</master>

3. Build the HAL threads

Create one HAL thread per rate. Each unit thread’s period must equal its unit’s cycle exactly. LinuxCNC gives faster threads higher priority, so the bus thread preempts the others, which is required.

loadrt threads name1=ecat-thread period1=125000 name2=servo-thread period2=500000 name3=io-thread period3=1000000
loadusr -W lcec_conf ethercat-conf.xml
loadrt lcec

addf lcec.read-all ecat-thread
# ... consumers of the 8 kHz pins ...
addf lcec.write-all ecat-thread

addf lcec.0.syncunit.motion.read servo-thread
# ... motion and drive logic ...
addf lcec.0.syncunit.motion.write servo-thread

addf lcec.0.syncunit.io.read io-thread
# ... I/O logic ...
addf lcec.0.syncunit.io.write io-thread

initf lcec.activate ecat-thread
start

Activate the master with initf in the bus thread. initf lcec.activate ecat-thread runs the master activation once, in realtime context, right before the bus thread’s first cycle. That fixes the DC reference time the grid (tick 0) and every SYNC0 start from, and the unit threads lock to that grid, so a clean activation is what keeps them in phase from the first cycle. initf needs LinuxCNC 2.10 or later. Without the line (or on 2.9, where halcmd does not know initf), lcec activates inline in the bus thread’s first write-all, logs a warning on 2.10+, and DC starts with a dirty phase that the PLL then trims.

With LinuxCNC’s motion controller, base_period_nsec gives the bus thread (base-thread) and servo_period_nsec the servo thread.

Rules:

4. Tune the host

A bus thread at 4 kHz or more leaves little room for wakeup latency. On top of the usual realtime setup (PREEMPT_RT kernel, RT cores taken out of general scheduling with isolcpus/nohz_full/rcu_nocbs, IRQs kept off them with irqaffinity, the performance governor), check:

5. Check it

Once all slaves are in OP:

Pin Expect
lcec.0.all-op, lcec.0.dc-phased TRUE
lcec log no “not activated via initf” warning
lcec.0.syncunit.<unit>.threaded TRUE for every unit whose functs you added
lcec.0.syncunit.<unit>.phase-locked TRUE within a fraction of a second; phase-err within a few hundred ns
lcec.0.syncunit.<unit>.late-count, stale-count constant after lock
lcec.0.syncunit.<unit>.wkc-state 2; wkc-change-count constant
drive status / error words no fault (catches DC cycles a drive rejects)

A short soak with cycle_jitter-style components first in each thread and these counters sampled at the start and end is a good acceptance test.

6. Troubleshooting

Symptom Likely cause
datagrams UNMATCHED / SKIPPED in the kernel log, WKC drops on the fastest unit every few seconds Wakeup latency above the bus period; check kernel.timer_migration and cyclictest (section 4)
A burst of UNMATCHED only at start-up Master activation inside the RT thread; harmless
phase-locked never TRUE Thread period differs from the unit cycle (an error is logged). If several units share a thread, only the one that sets the thread’s phase updates its phase pins
late-count keeps rising The unit thread’s work does not fit in its window of divider - 1 bus cycles minus phase-offset; lower phase-offset, move work out, or use a longer unit cycle
Unit stops being sent, error “no outputs from its thread” The unit’s write funct is not running: missing, in another thread, or the thread stalled
Drives fault right after OP SYNC0 cycle not supported by the drive (section 1)