Есть на 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 в скоростном режиме, дискретное 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-cia402cd hal-cia402sudo halcompile --install cia402.comp
ethercat-conf.xml
В примере слейв объявлен как generic (vid/pid 0x26C/0x3C — привод автора; у вас будут свои), и все объекты замапплены руками:
|
Объект |
Что это |
HAL-пин |
Направление |
|---|---|---|---|
|
|
controlword |
|
→ привод |
|
|
режим работы |
|
→ привод |
|
|
целевая позиция |
|
→ привод |
|
|
целевая скорость |
|
→ привод |
|
|
statusword |
|
← привод |
|
|
режим (факт) |
|
← привод |
|
|
позиция (факт) |
|
← привод |
|
|
скорость (факт) |
|
← привод |
|
|
момент (факт) |
|
← привод |
Плюс distributed clocks: sync0Cycle="*1" — синхроимпульс каждый цикл.
Обратите внимание: момент 0x6077 автор замаппил сразу. Правильно сделал — ниже расскажу, почему у нас с этим вышла история.
cia402.hal
loadrt [KINS]KINEMATICSloadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTSloadrt lcecloadrt cia402 count=3loadrt pid names=x-pid,y-pid,z-pidaddf lcec.read-all servo-threadaddf cia402.0.read-all servo-threadaddf cia402.1.read-all servo-threadaddf cia402.2.read-all servo-threadaddf motion-command-handler servo-threadaddf motion-controller servo-threadaddf x-pid.do-pid-calcs servo-threadaddf y-pid.do-pid-calcs servo-threadaddf z-pid.do-pid-calcs servo-threadaddf cia402.0.write-all servo-threadaddf cia402.1.write-all servo-threadaddf cia402.2.write-all servo-threadaddf lcec.write-all servo-thread
Связки для оси X (для Y/Z — то же с другими индексами):
# шина → компонентnet x-statusword lcec.0.0.cia-statusword => cia402.0.statuswordnet x-opmode-display lcec.0.0.opmode-display => cia402.0.opmode-displaynet x-drv-act-pos lcec.0.0.actual-position => cia402.0.drv-actual-positionnet x-drv-act-velo lcec.0.0.actual-velocity => cia402.0.drv-actual-velocity# компонент → шинаnet x-controlword cia402.0.controlword => lcec.0.0.cia-controlwordnet x-modes-of-operation cia402.0.opmode => lcec.0.0.opmodenet x-drv-target-pos cia402.0.drv-target-position => lcec.0.0.target-positionnet 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.enablenet x-amp-fault => joint.0.amp-fault-in <= cia402.0.drv-faultnet x-pos-cmd <= joint.0.motor-pos-cmd => cia402.0.pos-cmdnet x-pos-fb => joint.0.motor-pos-fb <= cia402.0.pos-fbnet x-home-index <= joint.0.index-enable => cia402.0.homesetp cia402.0.csp-mode 1setp 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.0HOME_LATCH_VEL = 0.2HOME_USE_INDEX = TRUE
SEARCH_VEL = 0 — концевик не ищем, сразу ловим индекс на малой скорости. Отдельная приятность абсолютных энкодеров: физический поиск не нужен вовсе, homing просто защёлкивает флаг от известной позиции — ось «находит ноль», не шевельнувшись.
halrun: пощупать привод без станка
Секрет, который экономит дни: не поднимайте весь LinuxCNC, пока не увидели привод живым в halrun.
Предохранитель: серво под питанием может поехать. Ось — без нагрузки и без инструмента, E-STOP — под рукой, руки — не в зоне хода.
Сначала — что вообще на шине:
$ ethercat slaves0 0:0 PREOP + IS620N (COE)$ ethercat pdos -p0 # фактический маппинг PDO$ ethercat upload -p0 -t uint16 0x6041 0 # statusword руками
Теперь мини-стенд в halrun — только шина, без motion:
$ halrunhalcmd: loadusr -W lcec_conf ethercat-conf.xmlhalcmd: loadrt lcechalcmd: loadrt threads name1=servo-thread period1=1000000halcmd: addf lcec.read-all servo-threadhalcmd: addf lcec.write-all servo-threadhalcmd: starthalcmd: show pin lcec.0.0.cia-statusword
И главный трюк — ручной проход машины состояний, голыми setp, без всякого компонента:
halcmd: setp lcec.0.0.opmode 8 # CSPhalcmd: setp lcec.0.0.cia-controlword 6 # Shutdownhalcmd: show pin lcec.0.0.cia-statusword # ждём 0x0021 (по маске 0x006F)halcmd: setp lcec.0.0.cia-controlword 7 # Switch on → 0x0023halcmd: setp lcec.0.0.cia-controlword 15 # Enable op → 0x0027
Если на любом шаге statusword не отвечает ожидаемой маской — дальше идти бессмысленно. Вы только что сэкономили себе отладку «почему станок не едет» на уровне, где видно, почему: неисправность, невзведённое питание силовой части, незакрытый STO — всё проявляется здесь, а не в загадочном поведении GUI.
Одна практическая тонкость [замерено на своей шкуре]: в CSP перед Enable operation целевая позиция должна равняться фактической. Иначе в момент включения привод прыгнет в накопленную цель — рывком, на полной динамике. cia402.comp делает это выравнивание сам; если ходите по машине состояний руками — сначала прочитайте 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), но подтверждения перемычкой ещё нет [не знаем]. -
Потолок оборотов шпинделя по шильдику — в конфиге стоит замеренное значение, документального подтверждения нет [не знаем].
Это нормальное состояние: половина наладки — планомерное превращение «не знаем» в «замерено». Опасно не «не знаю», опасно «наверное так» — за эти месяцы каждое «наверное» обошлось дороже честного пробела.
Вместо заключения
Мы пришли к этому стенду за продуктом, а нашли ничью землю: между красивым CAM сверху и профилем CiA 402 снизу лежит слой, который никем толком не описан — его каждый проходит заново, теряя одни и те же недели на одних и тех же граблях. Именно на этих неделях наладки мы и решили, что компанию стоит строить здесь: наша платформа генерирует из цифрового двойника станка и G-code, и вот эти самые .hal/ethercat-conf.xml из статьи — чтобы слой, о котором вы только что прочитали, собирался автоматически, а не руками в три ночи.
Если тема зайдёт — следующей напишу разбор «принял ≠ сделал»: почему подтверждение команды в станочных (и не только) системах ничего не значит, и как мы научились не верить ack’ам. Вопросы про EtherCAT/CiA 402 — в комментарии, отвечу с замерами.
ссылка на оригинал статьи https://habr.com/ru/articles/1068132/