Есть на GitHub репозиторий dbraun1981/hal-cia402: один файл cia402.comp, папка example, 9 коммитов, 48 звёзд, лицензия GPL-2.0. При этом через него проходит заметная часть всех станков, где LinuxCNC разговаривает с сервоприводами по EtherCAT. Похожих компонентов практически нет: ни развитых форков, ни конкурентов, ни «переписал на Rust».
Когда мы начинали стенд, это казалось странным: настолько важный кусок — и один, маленький, почти не развивается. Неужели все пишут своё? Спустя месяцы наладки ответ другой, и он интереснее. Разберём весь стек — от сетевой карты до joint.0.motor-pos-cmd - на рабочих примерах, а по дороге вскроем то, чего нет ни в одном README.
Сразу о том, откуда у меня факты: я работаю в компании, которая делает платформу управления станками «из браузера до серво», и всё, что ниже, — с наладки нашего живого стенда: три сервопривода Inovance IS620N, ещё один привод Wecon вместо шпинделя, LinuxCNC на промышленном боксе. Каждое важное число в статье помечено: [замерено] — проверили железом, [из паспорта] — из документации, [не знаем] — честно не знаем.
Зачем EtherCAT, если шаговики и так едут
Классическая связка LinuxCNC — step/dir через LPT или Mesa: контроллер шлёт импульсы, привод молчит. Команда идёт в одну сторону; что происходит с осью на самом деле — никто не знает.
EtherCAT — полевой Ethernet: кадр выходит из мастера, пробегает сквозь все слейвы «на лету» (каждый читает и пишет свои байты, не останавливая кадр) и возвращается. Отсюда цикл 1 кГц и жёстче на обычной сетевой карте, а distributed clocks синхронизируют оси с точностью до микросекунд [из паспорта].
Но главное не скорость, а обратная связь. Каждую миллисекунду позиция, скорость и момент каждой оси — просто числа в памяти. Из этого бесплатно получаются:
— осциллограф приводов — без осциллографа; — контроль отставания (following error) в реальном времени; — touch-off по моменту: опускаем ось малыми шагами и смотрим на момент — при касании он растёт. Щуп не нужен, момент и так приходит каждый такт.
Данные ходят двумя дорогами:
— PDO (process data) — циклический обмен, «горячие» данные каждый такт: позиции, statusword, момент; — SDO (service data) — почтовый ящик для параметров: медленно, по запросу, вне цикла.
Это разделение — ось всей дальнейшей истории.
EtherCAT ≠ Beckhoff
EtherCAT придумал Beckhoff — и на этом эксклюзив заканчивается. Протокол передан в ETG (EtherCAT Technology Group), спецификация открыта, мастер IgH — open source и вендор-нейтрален. Для стека из этой статьи не нужно ничего от Beckhoff: ни TwinCAT, ни их промышленных ПК, ни их клеммников. Обычный x86-бокс, обычная сетевая карта, Linux с PREEMPT_RT.
Наш стенд — тому иллюстрация: на нём нет ни одного устройства Beckhoff. Оси — сервоприводы Inovance IS620N (тот самый средний китайский ценовой сегмент), шпинделя как такового на стенде нет, его место занимает сервопривод Wecon VD3E в скоростном режиме - для стека это то же самое устройство CiA 402, дискретное IO — каплер Omron (E-STOP-петля, концевики). В хозяйстве заведены профили и под Schneider LXM28E с Mitsubishi MR-J4 — все они говорят один и тот же CiA 402, и весь стек статьи работает с каждым без единой строчки нового кода. Разница между вендорами начинается ровно там, где кончается профиль, — об этом вторая половина статьи.
Для тех, кто держит EtherCAT за «дорогую промышленную экзотику», это главная практическая новость: связка «обычный ПК + IgH + lcec + cia402.comp + недорогие приводы» собирается в работающий станок без единого проприетарного компонента.
Стек: четыре слоя
IgH EtherCAT Master - модуль ядра, гоняет кадры; утилита `ethercat` ↓ lcec (linuxcnc-ethercat) - HAL-драйвер: PDO ↔ HAL-пины ↓ cia402.comp (hal-cia402) - машина состояний профиля, скейлы, homing ↓ motion LinuxCNC - траектория
У lcec два способа описать слейв в ethercat-conf.xml:
type="generic"— маппинг PDO пишете руками, объект за объектом. Больше буков, но полный контроль и полное понимание;type="basic_cia402"— драйвер сам маппит стандартные объекты профиля. Быстрый старт, меньше контроля.
Конвейер каждого такта servo-thread выглядит так:
lcec.read-all → cia402.N.read-all → motion → cia402.N.write-all → lcec.write-all
Порядок не случаен: сначала прочитать состояние железа, привести его к виду, понятному motion, дать motion посчитать, перевести команду обратно на язык привода, отправить. Перепутаете порядок addf — получите задержку в такт и фазовый сдвиг, который потом будете искать неделями в «странном поведении PID».
CiA 402 за пять минут
CiA 402 — профиль «CANopen for drives», который EtherCAT унаследовал через CoE (CANopen over EtherCAT). Его центральная идея: привод нельзя «включить единицей». Он проходит машину состояний:
Switch on disabled → Ready to switch on → Switched on → Operation enabled (и отдельной веткой - Fault)
Управление - словом controlword (объект 0x6040), ответ — словом statusword (0x6041). Команды [из стандарта]:
Команда |
controlword |
Переход |
|---|---|---|
Shutdown |
|
→ Ready to switch on |
Switch on |
|
→ Switched on |
Enable operation |
|
→ Operation enabled |
Fault reset |
|
Fault → Switch on disabled |
Ответы statusword (по маске 0x006F): Ready = 0x0021, Switched on = 0x0023, Operation enabled = 0x0027; по маске 0x004F: Fault = 0x0008, Switch on disabled = 0x0040 [из стандарта].
Режим работы - объект 0x6060 (и его зеркало 0x6061): 8 = CSP (циклическая синхронная позиция), 9 = CSV (циклическая скорость), 6 = внутренний homing привода.
Смысл CSP: LinuxCNC каждый такт шлёт целевую позицию 0x607A, а контуры тока/скорости/позиции замыкает сам привод. Планирование траектории — наверху, сервоконтуры — внизу, каждый занимается своим.
Вот эту машину состояний кто-то должен крутить. Либо вы пишете её руками из HAL-кирпичей (и ошибаетесь), либо берёте cia402.comp.
Классический пример: разбираем example/ построчно
Установка компонента — одна команда:
git clone https://github.com/dbraun1981/hal-cia402 cd hal-cia402 sudo halcompile --install cia402.comp
ethercat-conf.xml
В примере слейв объявлен как generic (vid/pid 0x26C/0x3C — привод автора; у вас будут свои), и все объекты замапплены руками:
Объект |
Что это |
HAL-пин |
Направление |
|---|---|---|---|
|
controlword |
|
→ привод |
|
режим работы |
|
→ привод |
|
целевая позиция |
|
→ привод |
|
целевая скорость |
|
→ привод |
|
statusword |
|
← привод |
|
режим (факт) |
|
← привод |
|
позиция (факт) |
|
← привод |
|
скорость (факт) |
|
← привод |
|
момент (факт) |
|
← привод |
Плюс distributed clocks: sync0Cycle="*1" — синхроимпульс каждый цикл.
Обратите внимание: момент 0x6077 автор замаппил сразу. Правильно сделал — ниже расскажу, почему у нас с этим вышла история.
cia402.hal
loadrt [KINS]KINEMATICS loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS loadrt lcec loadrt cia402 count=3 loadrt pid names=x-pid,y-pid,z-pid addf lcec.read-all servo-thread addf cia402.0.read-all servo-thread addf cia402.1.read-all servo-thread addf cia402.2.read-all servo-thread addf motion-command-handler servo-thread addf motion-controller servo-thread addf x-pid.do-pid-calcs servo-thread addf y-pid.do-pid-calcs servo-thread addf z-pid.do-pid-calcs servo-thread addf cia402.0.write-all servo-thread addf cia402.1.write-all servo-thread addf cia402.2.write-all servo-thread addf lcec.write-all servo-thread
Связки для оси X (для Y/Z - то же с другими индексами):
# шина → компонент net x-statusword lcec.0.0.cia-statusword => cia402.0.statusword net x-opmode-display lcec.0.0.opmode-display => cia402.0.opmode-display net x-drv-act-pos lcec.0.0.actual-position => cia402.0.drv-actual-position net x-drv-act-velo lcec.0.0.actual-velocity => cia402.0.drv-actual-velocity # компонент → шина net x-controlword cia402.0.controlword => lcec.0.0.cia-controlword net x-modes-of-operation cia402.0.opmode => lcec.0.0.opmode net x-drv-target-pos cia402.0.drv-target-position => lcec.0.0.target-position net x-drv-target-velo cia402.0.drv-target-velocity => lcec.0.0.target-velocity # motion ↔ компонент net x-enable <= joint.0.amp-enable-out => cia402.0.enable net x-amp-fault => joint.0.amp-fault-in <= cia402.0.drv-fault net x-pos-cmd <= joint.0.motor-pos-cmd => cia402.0.pos-cmd net x-pos-fb => joint.0.motor-pos-fb <= cia402.0.pos-fb net x-home-index <= joint.0.index-enable => cia402.0.home setp cia402.0.csp-mode 1 setp cia402.0.pos-scale 3600
Что тут происходит на самом деле:
joint.0.amp-enable-out => cia402.0.enable— это вся магия. LinuxCNC говорит «хочу мочь двигаться», а компонент сам проводит привод через Shutdown → Switch on → Enable operation, следя за statusword на каждом шаге. Вместо ручной логики битов — один пин. Ровно ради этой строки компонент и существует.pos-scale— счётчиков привода на единицу станка. У автора 3600; ваша формула:counts_per_unit = (счётчик энкодера на оборот × передача) / шаг винта. Для 23-битного энкодера (8 388 608 counts/об [замерено] — про это ниже) и винта 5 мм без редуктора: 1 677 721.6 counts/мм.cia402.0.home←joint.0.index-enable— homing по индексной метке делает сам привод, LinuxCNC только жмёт на курок через стандартный handshake index-enable.
INI для homing
HOME_SEARCH_VEL = 0.0 HOME_LATCH_VEL = 0.2 HOME_USE_INDEX = TRUE
SEARCH_VEL = 0 — концевик не ищем, сразу ловим индекс на малой скорости. Отдельная приятность абсолютных энкодеров: физический поиск не нужен вовсе, homing просто защёлкивает флаг от известной позиции — ось «находит ноль», не шевельнувшись.
halrun: пощупать привод без станка
Секрет, который экономит дни: не поднимайте весь LinuxCNC, пока не увидели привод живым в halrun.
Предохранитель: серво под питанием может поехать. Ось — без нагрузки и без инструмента, E-STOP — под рукой, руки — не в зоне хода.
Сначала - что вообще на шине:
$ ethercat slaves 0 0:0 PREOP + IS620N (COE) $ ethercat pdos -p0 # фактический маппинг PDO $ ethercat upload -p0 -t uint16 0x6041 0 # statusword руками
Теперь мини-стенд в halrun - только шина, без motion:
$ halrun halcmd: loadusr -W lcec_conf ethercat-conf.xml halcmd: loadrt lcec halcmd: loadrt threads name1=servo-thread period1=1000000 halcmd: addf lcec.read-all servo-thread halcmd: addf lcec.write-all servo-thread halcmd: start halcmd: show pin lcec.0.0.cia-statusword
И главный трюк — ручной проход машины состояний, голыми setp, без всякого компонента:
halcmd: setp lcec.0.0.opmode 8 # CSP halcmd: setp lcec.0.0.cia-controlword 6 # Shutdown halcmd: show pin lcec.0.0.cia-statusword # ждём 0x0021 (по маске 0x006F) halcmd: setp lcec.0.0.cia-controlword 7 # Switch on → 0x0023 halcmd: setp lcec.0.0.cia-controlword 15 # Enable op → 0x0027
Если на любом шаге statusword не отвечает ожидаемой маской — дальше идти бессмысленно. Вы только что сэкономили себе отладку «почему станок не едет» на уровне, где видно, почему: неисправность, невзведённое питание силовой части, незакрытый STO — всё проявляется здесь, а не в загадочном поведении GUI.
Одна практическая тонкость [замерено на своей шкуре]: в CSP перед Enable operation целевая позиция должна равняться фактической. Иначе в момент включения привод прыгнет в накопленную цель — рывком, на полной динамике. Само выравнивание делает не компонент, а motion LinuxCNC: пока машина выключена, motor-pos-cmd следует за motor-pos-fb, и к моменту Enable команда уже равна факту. Держится это на замкнутой обратной связи - на том, что cia402.N.pos-fb заведён в motor-pos-fb. Разорвали цепочку или ходите по машине состояний руками - выравнивания не будет: сначала прочитайте actual-position и запишите её в target-position.
Как адаптировать пример под свой привод
1. Личность слейва. ethercat cstruct -p0 выдаёт фактические vid/pid и структуру объектов; ESI-файл вендора - то же в XML. vid/pid в вашем конфиге должны совпасть с живой шиной. И определяйте тип привода по vid/pid, а не по имени прошивки [замерено]: имена совпадают у разных устройств, и однажды у нас пинаут одного привода уверенно нарисовался в интерфейсе на совсем другом.
2. PDO под себя. Захотели добавить момент 0x6077, ошибку слежения 0x60F4 — святое дело. Но сначала выясните, сколько TxPDO разрешает ваш привод. Мы добавили второй PDO 0x1A01 «как у всех» — а у IS620N его не существует: привод принимает ровно один TxPDO из фиксированного набора (0x1A00 либо 0x1B01..0x1B04), это записано в объекте 0x1C13:01 [из паспорта, проверено замером]. Второй PDO — это SDO-ошибка на этапе конфигурации: слейв не доходит до OP, станок не едет. Наши тесты были зелёные — они проверяли наличие строк в XML, а не форму допустимого.
3. Масштабы. pos-scale — выше. Для скорости — отдельный масштаб, и он не обязан совпадать по структуре: у нас velo-scale = коэффициент скоростного контура × передача.
4. CSV вместо CSP. setp cia402.0.csp-mode 0 — и компонент работает по скорости. Нужно для шпинделя и для схем, где позиционный контур замыкает LinuxCNC через PID.
5. generic против basic_cia402. Начинайте с generic и ручного маппинга, как в примере: час рутины покупает понимание, которое окупится на первой же нестыковке. basic_cia402 хорош, когда приводов много и они одинаковые.
Чего не расскажет ни один README
Всё ниже — [замерено] на живых приводах, с датами в наших рабочих журналах.
1. «Стандартный» объект, который у каждого свой
Объект 0x60FD (digital inputs) — стандартный: биты концевиков, home-датчика, свободные DI. С середины июля у нас в нём горело 0x18000000 - биты 27 и 28, которые обе таблицы мануала помечают как NA. Три недели это было фоновой загадкой.
Разгадка: раскладка битов 60FD у IS620N зависит от параметра H0C-41 (SDO 0x200C:2A) — и фактическая карта смещена на +4 против таблицы мануала. Биты 27/28 — это концевики P-OT/N-OT, приехавшие не туда, где им положено быть по документации. Проверено переключением параметра: поставили 0 — регистр обнулился, 1 — тоже ноль, вернули 2 — биты 27/28 снова зажглись.
Мораль для generic-компонента: он не может знать даже, что означает бит в стандартном объекте. Это знание живёт только в паре «ваш привод + ваш параметр».
2. Прочитать параметр ≠ он действует
Электронный редуктор в CiA 402 — объект 0x6091. Он у IS620N есть, читается, пишется. А действующий редуктор живёт в вендорных параметрах H05-07/09, и 0x6091 может быть заполнен и мёртв — зависит от режима. Наша формула масштаба однажды почти затянула в конфиг редуктор, который не действует.
Разрешилось только физикой: приводы в ESTOP, крутим вал руками, смотрим 0x6063 (сырые counts) и 0x6064 (позиция). Оборот вала дал Δ6063 = 8 368 033 → энкодер 2²³ = 8 388 608 counts/об. Записали редуктор 2:1 — 6063 не дрогнул, 6064 поделился вдвое: значит 6064 отдаётся после редуктора. Побочный вывод: стена int32 наступает через 2³¹ / 2²³ = 256 оборотов — планируйте счёт заранее.
3. Записи по шине — навсегда
Параметр H0C-13 (SDO 0x200C:0E) управляет тем, сохраняются ли SDO-записи в EEPROM. На нашем стенде он оказался = 3: каждая запись по шине уходит в энергонезависимую память. «Примерить параметр и откатить перезагрузкой» — невозможно, пока не переключишь режим. У EEPROM ещё и ресурс записи конечен.
Бонус-ловушка — терминология: 200C-0E — это адрес, а не код параметра. Правило соответствия у Inovance: Hgg-pp ↔ 0x2gg:(pp+1). Пока мы называли адреса кодами, чуть не завели себе ложное «исключение из правила адресации».
4. Знаковые SDO
ethercat upload печатает значение сначала в hex. Наш парсер брал первый токен и делал int(tok, 0) — беззнаково. Момент -0.4% превращался в 6553%, маленькая отрицательная позиция — в 511 оборотов. На стоящем станке. Лечится two’s complement по битности типа:
_SIGNED_BITS = {"int8": 8, "int16": 16, "int32": 32} if bits and v >= 1 << (bits - 1): v -= 1 << bits
Стыдно? Да. Но если у вас на неподвижной оси осциллограф показывает момент в тысячи процентов - вы теперь знаете, куда смотреть.
5. Отставание — это число, а не «дёргается»
Пока не начнёшь мерить, любое «станок дёргается» нерасчленимо: в нём слиты отставание, шум квантования и реальные рывки. Мы написали скрипт, который слушает телеметрию и ничего не двигает. Результат: following error пропорционален скорости и одинаков у всех осей — F3000 (50 мм/с) → 1.34 мм (p95), F750 → 0.34 мм. Это Kv ≈ 37 с⁻¹ — свойство П-регулятора позиционного контура, а не дефект. Знать своё Kv надо до того, как судить привод.
6. HAL, ссылающийся на мёртвый слейв, не падает
Самое коварное: если HAL адресует слейв, которого нет в XML (или нет на шине), LinuxCNC стартует «успешно». Просто слейв навсегда остаётся в PREOP, ось мертва, ошибки нет. Мы теперь сверяем тройку «HAL ↔ XML ↔ живая шина» автоматически до старта — после того как одна ось «не поехала» именно так.
Так почему же никто не пишет свой hal-cia402?
Теперь ответ очевиден, и он в трёх пунктах:
1. Всё обобщаемое уже обобщено. Машина состояний, выравнивание цели перед включением, скейлы, homing — одинаковы у всех приводов профиля. Это пара сотен строк логики, и они написаны.
2. Всё остальное обобщить нельзя. Карта битов 60FD, число TxPDO, две шкалы редуктора, EEPROM-политика — это не «нюансы», это половина работы наладки, и она вендор-специфична. В компонент её не положишь: она живёт в вашем XML, ваших setp и — если вы себе не враг — в ваших автотестах.
3. Некому. Пересечение множеств «пользуется LinuxCNC», «дошёл до EtherCAT» и «пишет тексты» — исчезающе мало. 9 коммитов и 48 звёзд — это, по сути, весь публичный корпус знания по теме.
Компонент не «заброшен». Он ровно того размера, какого должен быть.
Когда и этого мало: путь мимо всех слоёв
Есть фаза, которую канонический стек не покрывает: наладка. Станка ещё нет, конфига нет, а ось двигать уже надо — замерить ход, проверить фазировку, снять холостой момент. Поднимать ради этого весь LinuxCNC — как возить микроскоп на тележке.
Мы для этого держим отдельный, подвальный путь: джог прямо по пинам lcec из скрипта — без cia402.comp и без LinuxCNC вообще. Машину состояний при этом проходим сами (ровно та последовательность 6 → 7 → 15, что в разделе про halrun, плюс выравнивание цели). После наладки те же оси живут через нормальный стек - подвал остаётся для подвала.
Чего мы до сих пор не знаем
Куда легли физические DI при
H0C-41 = 2— ожидаем биты 24-26 (мануал говорит 20-22), но подтверждения перемычкой ещё нет [не знаем].Потолок оборотов привода Wecon, который у нас за шпиндель — в конфиге стоит замеренное значение, документального подтверждения нет [не знаем].
Это нормальное состояние: половина наладки — планомерное превращение «не знаем» в «замерено». Опасно не «не знаю», опасно «наверное так» — за эти месяцы каждое «наверное» обошлось дороже честного пробела.
Вместо заключения
Мы пришли к этому стенду за продуктом, а нашли ничью землю: между красивым CAM сверху и профилем CiA 402 снизу лежит слой, который никем толком не описан — его каждый проходит заново, теряя одни и те же недели на одних и тех же граблях. Именно на этих неделях наладки мы и решили, что компанию стоит строить здесь: наша платформа генерирует из цифрового двойника станка и G-code, и вот эти самые .hal/ethercat-conf.xml из статьи — чтобы слой, о котором вы только что прочитали, собирался автоматически, а не руками в три ночи.
Если тема зайдёт — следующей напишу разбор «принял ≠ сделал»: почему подтверждение команды в станочных (и не только) системах ничего не значит, и как мы научились не верить ack’ам. Вопросы про EtherCAT/CiA 402 — в комментарии, отвечу с замерами.
Конфиги
Файлы, о которых шла речь, выложены целиком: https://github.com/SyncTwin/linuxcnc-ethercat-configs
Там описание шины и HAL для трёх осей IS620N вместе с каплером Omron NX-ECC202, минимальный пример на одну ось с ini, и рядом с каждым набором README с граблями: один TxPDO у IS620N, Er.E08 снимается только питанием, масштаб 838 860,8 импульса на миллиметр. Отдельно лежит методика замеров штатными средствами: cyclictest, снятие f-error через halsampler, счётчики ошибок портов.
Реестр устройств, где у каждого факта помечен источник, стенд или ESI: https://synctwin.ru/ethercat
Конфиги под GPL-2.0, реестр под CC BY. Файлы ESI производителей не выкладываем, это их произведения.
Комментарии (16)

pvvv
08.08.2026 15:20возможно глупый вопрос, но если приводы "умные", которым надо только сказать куда ехать и с какой скоростью и энкодер с pid регулятором "внутри", то зачем им именно собственно езеркат (ну разве что для синхронизации часов у нескольких осей, но наверное должен быть и ряд других способов). А если тупые - (а-ля условный linuxCNC / LPT / Mesa) step/dir драйвер шаговика в i/o клемму и чтение обратно энкодера через езеркат, то вроде как 1мс это не сказать что сильно уж быстро, чтобы этот регулятор в ПК вынести, да и просто для step/dir маловато 1кГц?

synctwin Автор
08.08.2026 15:20Вопрос совсем не глупый, наоборот, его задают чаще всего. Если одной фразой: EtherCAT нужен не приводу, а станку.
Одной оси и правда хватит чего угодно. В CiA 402 для этого есть режим PP: послал цель и скорость, привод сам построил разгон и торможение, приехал. Шина тут вырождается в канал команд, справится и Modbus.
А станок режет контур. Дугу, скругленный угол, фаску. Траекторию считает интерполятор наверху, сразу по всем осям, с look ahead по будущим кадрам G кода и с ограничением подачи по вектору. В CSP каждой оси каждый такт приезжает не куда ехать, а очередная точка этой общей кривой. Привод такое не построит в принципе, он не знает что в эту миллисекунду делают соседи. Отсюда и требование к синхронности. На F3000, то есть 50 мм/с, ось за такт проходит 50 мкм. Разъехались оси на такт, и на дуге вылезла видимая ступенька. DC ровно про это, а не приятная мелочь.
Ну и третье, за что EtherCAT в итоге любишь. Обратка достается даром. Кадр все равно проходит слейва насквозь, так что позиция, скорость, момент, statusword и ошибка слежения капают каждую миллисекунду сами собой. Из этого потом бесплатно вырастает touch off по моменту без щупа, following error в виде числа (у нас Kv вышло примерно 37 в минус первой, у всех осей одинаково), осциллограф приводов без осциллографа и работающая фолт цепь. Плюс оси, шпиндель, дискретка и петля E-STOP едут одним шлейфом.
Теперь про 1 мс маловато для step/dir. Тут, по моему, склеились два разных такта.
Импульсы вообще никогда не генерятся на servo-thread, ни в одной схеме. Софтовый stepgen через LPT крутится на base-thread 20 до 50 кГц. У Mesa шаги делает FPGA. У EtherCAT клемм step/dir (EL2521 и родня) импульсы генерит сама клемма, ей по шине едет частота или цель, а не отдельные шажки. Сотни кГц она выдаст независимо от того, что цикл шины 1 кГц.
А 1 кГц это темп обновления команды позиционному контуру, а не частота движения. Ровно на нем LinuxCNC десятилетиями замыкает позицию с Mesa по энкодеру. Это отраслевая норма, а не компромисс. Ваша схема step/dir в клемму плюс энкодер обратно по EtherCAT это та же самая классика, просто на другой физике.
И в CSP сглаживание вообще не наша забота. Между присланными точками привод интерполирует сам до своего внутреннего цикла, а токовый и скоростной контуры у него крутятся на десятках кГц. Разделение получается честное: планирование сверху на 1 кГц, сервоконтуры внизу на своей частоте. Выносить PID в ПК как раз не нужно, и мы не выносим. Это только для CSV схем, где позицию замыкает сам LinuxCNC, и там 1 кГц опять же штатный темп.

pvvv
08.08.2026 15:20Хорошо, пусть step импульсы генерятся пачками снаружи, но если у нас 1мс, и те самые 50мкм на 50мм/c между измерениями положения и соответственно до команды на следующую пачку импульсов, на какую ошибку траектории тогда можно рассчитывать. 50мкм? Да, есть feedforward и с идеальной механикой можно и без обратной связи по линейному энкодеру жить, но если механика говно, то чтобы её регулятором держать по энкодеру где положено разве не надо чтобы "период" регулятора помноженный на максимальную скорость соответствовал ошибке?

HungryLion
08.08.2026 15:20Вопрос не мне, но если позволите. "период регулятора помноженный на максимальную скорость" это было бы если бы нужно было на ходу начать регулировать привод несущийся на максимальной скорости. А мы же регулируем движение с самого начала. Поэтому получится "период регулятора помноженный на максимальную ошибку между заданной скоростью и реальной". С поправкой, что в CSP эту заданную скорость привод посчитает сам из разницы между новым положением и старым.

synctwin Автор
08.08.2026 15:20Тут важно разделить два случая, потому что у нас с вами контур замыкается в разных местах.
У нас CSP: позиционный контур живет внутри привода, а по шине едет цель. Наши цифры со стенда, три оси IS620N, F3000 то есть 50 мм/с: joint.N.f-error по p95 = 1.34 мм [замерено]. На F750 = 0.34 мм. В покое ноль. Kv вышел примерно 37 в минус первой, одинаковый у X, Y и Z.
Обратите внимание: это в 27 раз больше вашей оценки в 50 мкм. Работает не e = такт * скорость, а e = v / Kv. Квант такта в этом бюджете просто теряется на фоне отставания петли.
А вот в вашей схеме, где позицию замыкает ПК на 1 кГц, вы правы по сути, но механизм другой. Ограничивает не квант дискретизации, а устойчивость. Полосу замкнутого контура выше примерно десятой доли частоты дискретизации не поднять, то есть при 1 кГц это сотня герц в самом хорошем случае. А Kv упирается в эту полосу. В LinuxCNC на servo-thread 1 кГц реальные Kv это десятки, редко за сотню. Подставьте: при Kv 100 и 50 мм/с получится 0.5 мм. Снова не 50 мкм, и снова по формуле v / Kv, просто Kv низкий именно из-за периода. То есть период регулятора действительно главный ограничитель у вас, но он входит в ошибку через предел устойчивости, а не через путь за один такт.
Теперь про механику, и это самое интересное место в вашем вопросе.
Если механика плохая, потолок ставит не такт шины, а резонанс самой механики. Первый резонанс винта с муфтой это обычно десятки герц, и полосу контура выше него поднимать нельзя, иначе вместо жесткости получите возбуждение. Механика упрется раньше, чем упрется 1 кГц. У нас это не мерили, говорю из общей практики.
И отдельно про люфт: его регулятором не держат в принципе. На реверсе он дает мертвую зону, где датчик не видит движения, и высокий Kv эту зону не убирает, а превращает в автоколебания. Тут только механика или линейный энкодер.
Теперь собственно ответ на ваш вопрос про ошибку траектории. Отставание оси и ошибка формы это разные числа.
На прямой ошибка формы около нуля. Все оси отстают одинаково, значит меняется только фаза, инструмент идет на 1.3 мм позади того места, где должен, а деталь получается той же.
На остром угле вылезает целиком, примерно на величину e. У нас это 1.3 мм на F3000 и 0.34 мм на F750. Отсюда цеховое правило про то, что углы едут медленнее, получает численный вид.
На дуге меньше, чем кажется: при одинаковом Kv у осей радиус просто сжимается, примерно на v^2 / (2 R Kv^2). Для R10 и 50 мм/с это около 0.09 мм. Это уже [не мерили], только формула.
И честность про наши 1.34 мм. Это как из коробки. Velocity feedforward в приводах мы не трогали ни разу, а он ровно про то, чтобы снять пропорциональное скорости отставание. Так что число это не потолок железа, а замер нашей ненастроенности. Линейных энкодеров на стенде нет, поэтому сколько там на самом деле по столу, а не по мотору, мы не знаем.

HungryLion
08.08.2026 15:20И дополню про то, что EtherCAT нужен не приводу а станку.
Тут же не только в приводах дело. Сейчас куча оборудования работает по EtherCAT. Входы/выходы, переходники для шин типа CAN, датчики всякие, лазерные головки, штамповка/высечка, шпиндели и т. д. и т. п. Плюс там же можно обычный Ethernet пустить (многие привода так настраиваются) и не надо второго подключения.
А синхронизировать несколько приводов если они ни чем не объединены я вообще не представляю как. Хотя может и есть способы. Но тут всё сразу есть.
Если это что-то простое, то вы сами решаете как и что делать, а если это CNC для управления разными станками где будет неизвестно что подключаться, то без EherCAT сложно будет. Ну, то есть, наверное есть другие технологии, просто я про них не знаю.

ValeriyS
08.08.2026 15:20Стабильность Distributed Clock (DC) нельзя использовать для оценки джиттера пакетов, т.к. коррекция периода DC выполняется одним фиксированным значением (20 нс, по-моему) в зависимости только от знака разницы периода пришедшего от мастера фрейма.
Если мы захотим разогнать ECAT до 10-40 КГц, то величина джиттера станет главным лимитирущим фактором. Кроме того, в большинстве real-time приложений задержка (latency) должна быть стабильной от мастера к слейв и обратно. Если джиттер большой, то это трудно выполнить.

synctwin Автор
08.08.2026 15:20Согласен, поправка по делу. Разница DC это выход петли подстройки часов, джиттер прихода она сглаживает, а не показывает. Мы его мерили на мастере, cyclictest headless: max 15 мкс на разгруженной машине.
Про 10-40 кГц тоже согласен. На 1 кГц окно до sync0 в 1000 мкс против 15 мкс джиттера, запас такой, что шина не ограничивает ничего. На 25 мкс окна нет вовсе. Мы туда не идём по другой причине: замеренное отставание оси 1.34 мм на F3000 в 27 раз больше кванта такта, и замкнуты мы по энкодеру мотора, так что люфт и кручение винта регулятор не видит. Пока это так, разгон шины съест механика.

synctwin Автор
08.08.2026 15:20Выложил конфиги со стенда: github.com/SyncTwin/linuxcnc-ethercat-configs
Описание шины и HAL для трёх осей IS620N с каплером Omron, минимальный пример на одну ось, README с граблями рядом с каждым набором. Отдельно методика замеров: cyclictest, f-error через halsampler, счётчики ошибок портов.
ValeriyS
Интересная и полезная статья, спасибо! Работаю с EtherCAT уже как лет 5, для отладки в основном пользуюсь TwinCAT + PLC. Пробовали ли вы измерять jitter пакетов? На обычной Win 11 и Intel Ethernet карточках, поддерживаемых TwinCAT в режиме real-time, получается добиться < 120 мкс. На специально вылизанных Windows < 20 мкс. Особенно круто было бы получить стек с малым jitter под WSL.
synctwin Автор
Спасибо! Про jitter отвечу честно, но с оговоркой: мы меряем не совсем то, что вы.
Кадры на проводе мы не снимали, ни TAP, ни осциллограф под это не подкладывали. Меряем джиттер пробуждения RT задачи. cyclictest -p80 -i1000 при цикле 1 мс дает max 15 мкс, avg 5. Еще смотрим lcec.read-all.tmax, там 72 мкс, но это сколько функция работает, а не разброс. Так что с вашими 120 и 20 мкс это сравнивать нельзя, величины разные.
Зато вместо метрики у нас есть индикатор, который не соврет. Distributed clocks это самый нежный потребитель на шине. Не уложился в окно, и слейв просто не доходит до OP, виснет в SAFEOP, а в логе Unexpected realtime delay on task 0 with period 1000000. Три привода в OP при sync0 каждый цикл значит с джиттером все нормально. Один раз это стрельнуло уже на боевом прогоне: RT выброс уронил DC sync, привод Z защелкнул SYNC аварию и честно свалил станок в E-STOP. Неприятно, но приятнее чем ось молча мертва.
И вот что интересно. Цифры нам дал не период и не выбор HAL. До тюнинга на том же железе, с той же картой и тем же стеком 1 кГц вообще не держался, машина параллельно тащила homelab, load average под 12. Вылечилось скучным: governor в performance, глубокие C-states запретить (/dev/cpu_dma_latency=0 держим отдельным юнитом, иначе не переживает ребут), isolcpus плюс пиннинг vCPU. После этого LA 3, те самые 15 мкс и ноль realtime delays. Двойку миллисекунд ставить не пришлось. То есть в линуксе джиттер это в первую очередь про C-states и про то, кто еще живет на твоих ядрах, а не про какой то особый real-time драйвер сетевухи.
Теперь про WSL. Тут порадовать нечем, и сразу по двум причинам. Сеть в WSL2 это виртуализованный адаптер под Hyper-V, а IgH нужен сырой доступ к карте (у нас DEVICE_MODULES generic на i226 и igc). Прокинуть физический порт туда нельзя, PCI passthrough в WSL2 нет, usbipd закрывает только USB. А если бы и прокинули, Hyper-V подкинет своих выбросов поверх винды, и никакого детерминизма.
Но EtherCAT из виртуалки сам по себе живет прекрасно, просто гипервизор нужен другой. У нас стенд так и устроен: KVM, физический порт целиком проброшен в гостя через VFIO (igc уходит в vfio-pci, с хоста карта пропадает), внутри Debian с RT ядром, vCPU запинены на изолированные ядра. Из VM поднимается вся шина разом: 3 штуки IS620N, Wecon VD3E, Mitsubishi MR-J4-20TM и каплер Omron. Windows рабочее место при этом никуда не девается, просто ходит на бокс по сети, а не крутит мастер у себя.
Если есть способ померить джиттер прямо на проводе без спецжелеза, расскажите. Прогоню на стенде и выложу цифры, самому интересно.
HungryLion
Как вы и написали, джиттер зависит от системы, которая обеспечивает real-time и от драйвера сетевой карты. Если есть возможность написать/модифицировать драйвер карты, то Intel i210 и тому подобные поддерживают 802.1Qav где есть функция Launch time. То есть вы для пакета задаете время отправки по внутреннему таймеру карты. Тогда джиттер будет измеряться в наносекундах, может в десятках наносекунд. Но кроме модификации драйвера карты нужно ещё иметь возможность обращаться к нему напрямую из real-time системы.
synctwin Автор
О, спасибо, про launch time не думал в эту сторону. Полез читать, и да, у i210 это есть, шаг таймера 32 нс по даташиту. В линуксе оно даже не требует своего драйвера с нуля: SO_TXTIME на сокете плюс qdisc etf с offload, igb это умеет в ванильном ядре начиная с 4.20. У i225 и i226 (igc) то же самое плюс Qbv, а у нас на стенде как раз i226 стоит, так что железо под рукой.
Но с EtherCAT есть засада ровно там, где вы говорите про прямое обращение из RT системы. IgH ходит к карте двумя способами. Либо native драйвер, а это форк старого ядерного драйвера, который лезет в кольца напрямую и весь сетевой стек с его qdisc обходит стороной, значит etf там просто не по пути. Либо generic режим через обычный сокет, и вот тут ETF теоретически применим, но сам IgH никакого SO_TXTIME на пакет не ставит. У нас как раз generic на i226. То есть допиливать все равно придется, только не драйвер карты, а мастер, и это, пожалуй, честнее звучит.
И второй момент, из-за которого мы за наносекундами не гонимся. В EtherCAT с distributed clocks синхронность осей не зависит от того, когда кадр физически приехал. Слейв защелкивает данные по своему sync0, а кадру достаточно просто успеть до него. То есть launch time убирает джиттер отправки, а у нас узкое место было не там: RT выбросы измерялись сотнями микросекунд и миллисекундами, кадр банально опаздывал за окно целиком. Наносекундная точность отправки от такого не спасет, ее лечит только разгрузка ядер и запрет C-states.
Где launch time реально бы выстрелил, так это в схемах без DC и там, где EtherCAT делит провод с другим трафиком, то есть в сторону TSN. Вот там детерминированное окно отправки решает, а не улучшает.
Если руки дойдут собрать generic путь с SO_TXTIME и померить до и после на i226, напишу отдельно. Заодно наконец появится тот самый замер на проводе, которого мне не хватило в ответе выше.
HungryLion
Про отсутствие необходимости наносекунд и узкое место в RT я с вами полостью согласен. У нас RT вообще самописный и из за борьбы с Windows и дошли до всего этого.
А для схем с другим траффиком есть ещё одна полезная вещь в карте: Multiple Transmit Queues. У нас, наример, циклические телеграммы идут через RT в драйвер напрямую в приоритетную очередь, а управляющие телеграмы, mailbox, EoE и т.п в другую, можно даже через обычный сетевой стек.
synctwin Автор
Про самописный RT из-за Windows - сильно. У нас та же логика, только мы сбежали в другую сторону, в PREEMPT_RT, потому что писать своё не потянули бы.
Multiple Transmit Queues полез смотреть сразу, у нас i226 и там это есть, mqprio и taprio ложатся штатно. Но с IgH в generic режиме упираешься в то же место, что и с launch time. Мастер шлёт все кадры одним сокетом и сам не различает, что тут domain, а что mailbox. Ручки выставить priority на конкретный кадр я сходу в мастере не нашёл. То есть очереди в карте есть, а раскладывать по ним пока нечего, надо править мастер.
И вторая честность, из-за которой выигрыш у нас будет меньше вашего. У вас в очереди разный трафик, а у нас по проводу всё равно одна линия и кадры идут гуськом. Очередь в карте поменяет только порядок выхода на провод, а не разведёт потоки. Хотя порядок это ровно то, что и надо: mailbox не должен пролезать перед циклическим кадром.
Место, где нам это откликнется, известно. Мы читаем полный паспорт привода, три сотни параметров по SDO, и делаем это на живой шине. Джиттер под этой нагрузкой не мерили ни разу. Спасибо, добавили в список замеров.
HungryLion
С кадрами в очередях к нас всё так же: кадры гуськом, только порядок выхода. Для нас их основная польза в другом: они аппаратно независимы. Поэтому в то время пока одной очередью пользуется стандартная часть драйвера под управлением Windows, другой очередью управляет наш RT, который крутится вообще на изолированном ядре. И не надо думать про синхронизацию. Собственно всё это не от хорошей жизни, а из-за борьбы с Windows.