Привод, которого нет в каталоге lcec, заводится через generic — картой PDO, набранной руками в XML: у нашего стенда это 236 строк, и ошибка в одном индексе означает «слейв не дошёл до OP», больше лог не скажет ничего. Свой драйвер CiA 402 заменяет эту простыню на строку <slave type="VD3E" name="s"/> и занимает двадцать содержательных строк. Ниже — зачем его писать и когда не надо, что нужно узнать про прибор, где это взять и что ломается по дороге. Маршрут: как устроена связка и где в ней lcec → что у пяти приводов одинаково, а что у каждого своё → как снять с незнакомого привода пять нужных фактов четырьмя командами → сам драйвер целиком, с разбором → что ломается и как это выглядит в логе.
Что такое lcec и где он стоит
LinuxCNC про EtherCAT ничего не знает. Он умеет одно: раз в такт, у нас раз в миллисекунду, прочитать и записать значения на пинах HAL — своей внутренней шины сигналов. Интерполятор решил, что ось X должна оказаться на отметке 100,5 мм, и положил это число на пин. Дальше кто-то должен доставить его в привод.
Доставкой занимаются два слоя, и их полезно не путать.
Мастер IgH EtherCAT — драйвер сетевой карты, который гоняет по витой паре кадр с жёстким периодом. Про станки он не знает ничего: его дело — довезти набор байтов до каждого устройства на шине и забрать набор байтов обратно. Ставится отдельно от LinuxCNC, живёт своей службой, проверяется своей утилитой ethercat.
linuxcnc-ethercat, он же lcec — HAL-компонент, склейка между ними. Он заводит пины в HAL, знает, в каком месте кадра у конкретного привода лежит целевая позиция, и раз в такт перекладывает одно в другое. Он же поднимает шину при старте, проводя каждое устройство по лестнице INIT → PREOP → SAFEOP → OP, где OP означает «участвует в циклическом обмене». Половина всех бед выглядит как «слейв не дошёл до OP».
Цепочка целиком:
G-код → интерполятор LinuxCNC → пины HAL → lcec → мастер IgH → кадр EtherCAT → привод → мотор
Статья про третье звено. Первые два ставятся штатно и работают.
Как это выглядит в конфиге станка
lcec — обычный HAL-компонент, который LinuxCNC загружает при старте машины; отдельной службы у него нет. В .hal-файле это четыре строки:
loadusr -W lcec_conf ethercat-conf.xml # прочитать описание шины, дождаться готовности loadrt lcec # загрузить сам компонент в реальном времени addf lcec.read-all servo-thread # забрать входы кадра в начале такта addf lcec.write-all servo-thread # отдать выходы в конце такта
Порядок здесь не косметика: read-all стоит первым в такте, write-all последним, между ними работает интерполятор. Если поменять местами — команда уедет в привод на такт позже, чем посчитана, и ось получит лишнюю миллисекунду запаздывания в контуре.
ethercat-conf.xml — описание шины: какой мастер, с каким периодом, какие устройства и в каком порядке стоят в цепи. Именно этот файл раздувается до двухсот строк, когда драйвера нет, и схлопывается в четыре тега, когда он есть.
После загрузки привод виден как набор пинов HAL, и ось привязывается к нему обычными net:
net x-pos-cmd joint.0.motor-pos-cmd => lcec.0.x.srv-target-position net x-pos-fb lcec.0.x.srv-actual-position => joint.0.motor-pos-fb net x-enable joint.0.amp-enable-out => lcec.0.x.srv-enable
Всё, что даёт драйвер, — это правильные имена слева и правильные байты справа. Больше LinuxCNC от EtherCAT ничего и не нужно.
Как устроен сам репозиторий lcec
Полезно представлять, куда вы будете класть свой файл.
src/lcec_main.c,src/lcec_conf.c— ядро: разбор XML, лестница состояний, такт. Его вы не трогаете.src/devices/lcec_*.c— карточки устройств, по файлу на вендора или семейство. Это и есть «драйверы»; ваш будет соседом.src/lcec_class_cia402.c— класс: готовая реализация профиля CiA 402, общая для всех приводов. Она собирает карту PDO, заводит пиныsrv-*, разворачивает слово состояния. Ради неё всё и затевается: карточка устройства сводится к декларации «вот мои цифры, вот мои лимиты, включи вот эти режимы». Классы есть и другие — например, для цифрового ввода-вывода.documentation/*.md— страница на устройство: пины, модпараметры, грабли. В апстриме это часть вклада, а не приложение к нему.
Слово «драйвер» здесь стоит понимать буквально как «описание прибора». Кода, который что-то вычисляет, в нём обычно нет вовсе — вычисляет класс.
PDO — договор о том, что лежит в кадре
Кадр EtherCAT — это байты без подписей. Устройство и мастер обязаны заранее условиться: первые два байта — управляющее слово, следующие четыре — целевая позиция, дальше режим работы. Такой договор называется PDO (process data object), а список «какой объект лежит на каком месте» — картой PDO, или маппингом.
Объекты у привода названы номерами из CiA 402 — профиля приводов, который EtherCAT унаследовал от CANopen. 0x6040 — управляющее слово, 0x6041 — слово состояния, 0x607a — целевая позиция, 0x6064 — фактическая. Тот же стандарт описывает режимы: csp — циклическая позиция, csv — скорость, cst — момент. Для фрезера обычно нужен csp на осях и csv на шпинделе.
Договор кто-то должен составить. Либо вы пишете его руками в XML, либо это делает драйвер устройства. Ровно об этом дальше.
Вашего привода в каталоге, скорее всего, нет
Каталог linuxcnc-ethercat на день замера: 272 устройства, девять вендоров, 209 карточек Beckhoff — 77 процентов списка. Полноценных семейств CiA 402-сервоприводов там четыре: Omron G5, Delta ASDA, Stöber и частично RTelligent. Остальное — терминалы ввода-вывода и шаговые драйверы.
Мы взяли пять вендоров, которых там нет: Inovance IS620N и SV660, Wecon VD3E, Mitsubishi MR-J4-20TM, Schneider LXM28E, плюс каплер Omron NX-ECC202. Все живы, все отвечают по мейлбоксу, драйвера нет ни у одного.
Что у них одинаково, а что различается
Это главный практический результат, и он же — ответ на вопрос «а что вообще придётся узнавать про мой привод». Стандарт покрывает меньше, чем ожидаешь.
Одинаково у всех пяти — на это можно опираться, не проверяя:
номера объектов CiA 402:
0x6040управляющее слово,0x6041состояние,0x607aцелевая позиция,0x6064фактическая,0x6060режим;лестница состояний
INIT → PREOP → SAFEOP → OPи порядок её прохождения;identity в
0x1018: vendor id, product code, revision читаются одинаково;режим
cspесть у всех — циклическая позиция, основной режим для оси станка.
Различается — и это придётся выяснять для каждого прибора:
что |
как расходится |
|---|---|
сколько записей влезает в карту PDO |
VD3E — 10 на объект; MR-J4 приехал с 12 на выход и 14 на вход; у IS620N назначается ровно один TxPDO, а индекса |
отдаёт ли привод свой словарь по SDO-Info |
Mitsubishi — 809 объектов, Wecon — 109, Inovance и Schneider — ноль, молчат |
набор режимов |
у Schneider нет |
ответ на |
у трёх вендоров одинаковый, у Schneider свой |
complete access при записи SDO |
Wecon отвечает отказом, Mitsubishi на том же стенде принимает спокойно |
разрешение энкодера через |
у VD3E этих объектов нет: «object does not exist»; число достаётся вендорным объектом, о котором знает только завод |
доступность ESI |
Wecon — открыто на сайте, Inovance — только через регистрацию на портале |
параметры Distributed Clocks |
|
И одинаково плохо у всех пяти: таблицы кодов ошибок в машинном виде не публикует никто. Оператор у станка видит 0x603F = 0x7305 вместо строки «обрыв фазы энкодера». Один вендор из пяти прислал такую таблицу по прямому запросу — об этом ниже, в разделе про ответ завода; но именно по запросу, а не с сайта.
Отсюда и берётся работа: стандарт даёт общий язык, а всё, что нужно драйверу сверх него, добывается прибором наружу.
На чём всё это проверялось
Чтобы числа можно было сравнить со своими:
версия |
|
|---|---|
ОС |
Debian 12 (bookworm) |
ядро |
6.1.0-51-rt-amd64 (PREEMPT_RT) |
LinuxCNC |
2.9.10, uspace |
мастер EtherCAT |
IgH 1.6.10 ( |
linuxcnc-ethercat |
1.42.2 |
параметры RT |
|
период servo-thread |
1 мс ( |
Прошивки приборов важны: поведение привязано к ревизии, и если у вас те же приводы с другой — числа могут разойтись. Полный список ревизий вынесен в реестр устройств, чтобы не занимать здесь половину экрана.
Как поставить весь стек с нуля
Если EtherCAT вы ещё не заводили, до драйверов нужно пройти три шага. Ниже — то, что делает наш инсталлер на чистой Debian 12; ничего своего в этих командах нет.
1. RT-ядро и LinuxCNC. Репозиторий проекта плюс ядро с PREEMPT_RT:
gpg --no-default-keyring --keyring /usr/share/keyrings/linuxcnc-archive-keyring.gpg \ --keyserver hkps://keyserver.ubuntu.com --recv-keys 3CB9FD148F374FEF echo "deb [signed-by=/usr/share/keyrings/linuxcnc-archive-keyring.gpg] \ https://www.linuxcnc.org bookworm base 2.9-uspace" \ > /etc/apt/sources.list.d/linuxcnc.list apt-get update apt-get install -y linux-image-rt-amd64 linux-headers-rt-amd64 linuxcnc-uspace
2. Мастер IgH. Своих пакетов в Debian у него нет, берётся из репозитория science:EtherLab на OpenSUSE Build Service:
curl -fsSL http://download.opensuse.org/repositories/science:/EtherLab/Debian_12/Release.key \ | gpg --dearmor -o /usr/share/keyrings/etherlab-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/etherlab-archive-keyring.gpg] \ http://download.opensuse.org/repositories/science:/EtherLab/Debian_12 ./" \ > /etc/apt/sources.list.d/etherlab.list apt-get update apt-get install -y ethercat-master ethercat-dkms linuxcnc-ethercat
Грабли на ровном месте. ethercat-dkms собирает модуль под работающее ядро, а не под то, которое вы только что поставили. Без заголовков текущего ядра постинсталл падает, пакеты остаются недонастроенными, и после этого любой следующий apt-get install завершается ошибкой dpkg. Ставьте linux-headers-$(uname -r) до всего остального, а после перезагрузки в RT-ядро — dpkg-reconfigure ethercat-dkms.
3. Указать мастеру сетевую карту — в /etc/ethercat.conf, по MAC-адресу:
MASTER0_DEVICE="00:1b:21:xx:xx:xx" DEVICE_MODULES="generic"
generic означает «через обычный сетевой драйвер ядра». Есть и оптимизированные драйверы под конкретные чипы, но начинать стоит с generic: он работает всегда, а разницу вы увидите только на цикле сильно ниже миллисекунды.
Карта должна быть отдельная и ни для чего другого: мастер забирает её целиком, IP на ней не живёт.
Проверка, что шина поднялась:
systemctl start ethercat ethercat slaves
Каждое устройство отвечает строкой вида 0 0:0 PREOP + IS620N_ECAT_v2.6.8. PREOP на этом этапе — правильно: до OP слейвов доводит уже LinuxCNC с lcec.
Вторая ловушка. Мастер читает шину один раз при старте и не пересканирует её при появлении линка. Включили питание приводов после запуска службы — увидите Slaves: 0 навсегда, пока не сделаете systemctl restart ethercat. Мы на это ловились не раз: выглядит как мёртвая шина, лечится перезапуском службы.
Чем платишь, пока драйвера нет
Привод заводится через generic: карту PDO пишешь руками.
<slave idx="0" type="generic" vid="00100000" pid="000C0108" configPdos="true"> <syncManager idx="2" dir="out"> <pdo idx="1600"> <pdoEntry idx="6040" subIdx="00" bitLen="16" halPin="controlword" halType="u32"/> <pdoEntry idx="607A" subIdx="00" bitLen="32" halPin="target-position" halType="s32"/> … ещё три десятка строк …
Наш рабочий конфиг фрезера: 236 строк XML, 53 записи PDO вручную. Ошибся в одном индексе — слейв не доходит до OP, и лог об этом скажет мало.
Три способа завести привод, и когда свой драйвер не нужен
Способов ровно три, и выбрать стоит осознанно.
способ |
что пишете вы |
когда подходит |
|---|---|---|
|
карту PDO руками в XML, 40–60 строк на привод |
больше нечем: устройство не CiA 402, экзотика, каплер с произвольным набором модулей |
|
одну строку |
привод честного CiA 402, машина одна, конфиг пишется руками один раз |
свой драйвер |
20 строк C один раз, потом |
приводов и машин много, конфиги генерятся, знание о приборе нужно сохранить |
Про средний вариант надо сказать честно и раньше остального: в апстриме живёт basic_cia402 — универсальный драйвер для любого CiA 402-устройства. Он сам собирает маппинг и отдаёт те же единообразные пины srv-*. Три четверти экономии строк даёт именно он, а не наш код.
Один станок, привод честного CiA 402 — начинайте с него и на этом останавливайтесь. Писать C не придётся.
Зачем тогда свой драйвер
Всё дальнейшее — про случай, когда среднего варианта не хватает. Четыре причины, по возрастанию того, насколько больно без них.
1. Конфиг называет прибор именем, а не цифрами. type="VD3E" против type="basic_cia402" с выписанными руками vid и pid. У Inovance product code двух разных приводов различается одним байтом. Ошиблись — драйвер приложился к чужому приводу, и узнаете вы об этом по поведению, а не по сообщению.
2. Универсальный драйвер не знает, чего у прибора нет. У Schneider LXM28E нет csv и cst. Включите их — маппинг не соберётся, слейв встанет на PREOP, и в логе будет код сервисного обмена, а не «этого режима у привода нет». Вендорный драйвер режет такое на входе, потому что кто-то уже выяснил.
3. Знание о приборе перестаёт быть вашим личным. Лимит в десять записей на мапирующий объект, assignActivate = 0x300, отсутствующий 0x608f — всё это добыто экспериментом за вечер. В generic-конфиге оно останется в вашем XML на вашем компьютере. В драйвере — уезжает в апстрим, и следующему человеку с тем же приводом достаётся одна строка вместо вечера.
4. Конфиг становится генерируемым. Пока в XML лежит карта PDO, тот, кто собирает конфиги машин (у нас это делает модель станка), обязан знать вендорские тонкости каждого привода. Как только в XML один тег на прибор, генератор становится тупым, а вендорское знание живёт там, где ему место — в lcec. Для одной машины это неважно. Для десятка — это разница между «поддерживаем» и «не поддерживаем».
То есть вендорный драйвер убирает не столько XML, сколько необходимость выяснять про прибор то, что кто-то уже выяснил.
Мы объясняли себе эту простыню тем, что приводы китайские и драйверов для них никто не напишет. Объяснение неверное. Мешает другое — отсутствие ESI: машинное XML-описание устройства, которое вендор выкладывает (или не выкладывает). У Inovance он на 13.08.2026 доступен только через регистрацию на портале. У Wecon лежит открыто, в разделе загрузок на сайте, — и драйвер пишется за вечер.
Что нужно знать про привод
Возвращаемся к драйверу. Чтобы его написать, нужно знать про прибор пять вещей — и три из них в пользовательской документации отсутствуют: лимиты PDO, параметры DC и список того, чего у привода нет.
1. Identity
ethercat slaves -v # Vendor Id 0x00000eff, Product code 0x0d3e0001, Revision 0x00000073
Две живые ловушки. У Wecon VD5 и VD5L делят один product code и различаются только ревизией. У Inovance наоборот: IS620N и SV660 делят vendor id и префикс 0x000c01, различаясь одним байтом, — сравнение по префиксу выдаёт один привод за другой. Мы на этом уже поскальзывались в собственной платформе.
2. Сколько PDO и сколько записей влезает
Что это за число и на что оно влияет. В каждом такте привод получает и отдаёт фиксированный набор величин. Управляющее слово и целевая позиция нужны всегда — это две записи из бюджета. Дальше начинается выбор: везти ли обратно фактический момент, ошибку слежения, код аварии, состояние входов. Каждая такая величина — ещё одна запись в карте, и лимит прибора говорит, сколько их влезет.
Мало записей — придётся решать, что важнее: видеть момент на шпинделе или код ошибки. Много — можно отдать в HAL всё сразу и не думать. Практически: пяти записей на выход и семи на вход хватает фрезеру с запасом, так что при лимите в десять выбирать не приходится, а при четырёх приходится.
Ограничений на самом деле два, и их путают:
сколько PDO-объектов можно назначить (
0x1c12для выходов,0x1c13для входов) — у большинства приводов ровно один;сколько записей влезает в один такой объект (
SubIndex 000у0x1600,0x1701и подобных) — у VD3E это десять.
Второе число мы сначала прочитали как «столько назначено сейчас» и допускали, что предел выше. Завод ответил, что десять — жёсткий лимит прошивки, а не значение по умолчанию: расширять маппинг некуда. Разница практическая — в первом случае имеет смысл пробовать одиннадцатую запись, во втором это гарантированный отказ на PREOP.
Как узнать. Из ESI, если он есть:
<Object><Index>#x1C12</Index> <!-- RxPDO assign --> <SubItem><Name>SubIndex 000</Name><DefaultData>01</DefaultData>
01 — назначается ровно один RxPDO. В объекте маппинга SubIndex 000 = 0A, то есть до десяти записей.
Если ESI нет, те же два числа читаются прямо с шины, приводом наружу:
ethercat upload -p3 0x1c12 0x00 --type uint8 # сколько PDO можно назначить ethercat upload -p3 0x1600 0x00 --type uint8 # сколько записей в карте ethercat pdos -p3 # что назначено прямо сейчас
Что будет, если ошибиться. Превысили лимит — привод отвечает ошибкой сервисного обмена на переходе PREOP → SAFEOP, и слейв не доходит до OP. В логе будет код обмена, а не «записей слишком много», поэтому число лучше знать заранее, чем искать его методом деления пополам.
Так мы сняли и Mitsubishi: у MR-J4 в RxPDO 12 записей, в TxPDO 14 — заметно больше остальных, потому что рядом со стандартными объектами он мапит свои: 0x2d01..0x2d03 Control DI, 0x2d11..0x2d13 Status DO, 0x2d20 предел скорости. Оговорка: это то, с чем привод приехал, а истинный максимум без ESI взять неоткуда — в драйвере стоят именно наблюдённые числа.
3. Distributed Clocks
<Dc><OpMode><Name>DC</Name><AssignActivate>#x300</AssignActivate>
Если ESI нет — значение не выдумывать. В драйверах Mitsubishi и Schneider мы сознательно не задали DC: пусть его назначает XML, это честнее угадывания.
Кроме assignActivate у DC есть второй параметр, про который в документации приводов молчат, — сдвиг sync0. Импульс SYNC0 говорит приводу «читай данные из своей памяти сейчас», и вопрос в том, в какой момент цикла он должен прийти относительно того, как мастер эти данные положил. Завод Wecon рекомендовал для VD3E половину периода — то есть при цикле 1 мс сдвиг 500 000 нс. Мы это проверили на своей шине 17 августа:
без сдвига |
|
|
|---|---|---|
|
129 023 нс |
— |
через 10 секунд |
ещё сходится |
1 нс |
время до единиц нс |
~20 с |
~10 с |
установившееся значение |
единицы нс |
единицы нс |
Что покупает сдвиг — сходимость на старте, а не более тугой захват в установившемся режиме: через полминуты оба варианта стоят в единицах наносекунд, phase-jitter ноль, приводы в OP. Сказать «стало точнее» было бы неправдой; правда — «раньше перестало быть неточным». Оговорка та же, что и везде в этом разделе: шина при замере стояла, под движением не повторяли.
4. Какие режимы привод умеет
Объект 0x6502, и вот тут был сюрприз.
привод |
|
что это значит |
|---|---|---|
Inovance IS620N |
|
pp, pv, tq, hm, csp, csv, cst |
Wecon VD3E |
|
то же самое |
Mitsubishi MR-J4 |
|
то же самое |
Schneider LXM28E |
|
pp, pv, tq, hm, ip, csp — нет csv и нет cst |
Три вендора из четырёх отвечают байт в байт одинаково — и после третьего совпадения рука тянется считать этот набор универсальным. Четвёртый показывает, что это не так: у Lexium нет ни синхронной скорости, ни синхронного момента, зато есть интерполяция, которой нет у остальных.
Поэтому в драйвере Schneider включён только csp. Попросить у привода режим, которого он не реализует, — это смапить объекты, которых нет.
5. Чего у привода НЕТ
Самый недооценённый пункт. У Wecon VD3E отсутствуют 0x608F (разрешение энкодера) и 0x6092 (feed constant) — на запрос приходит «object does not exist». Стандартными средствами CiA 402 разрешение с шины не читается вовсе; завод это позже подтвердил письмом и назвал свою замену — про неё чуть ниже.
Энкодер 23-битный, то есть 8 388 608 отсчётов по паспорту. Но паспорт лежит отдельно от прибора, а автоматике нужно число здесь и сейчас. Мы измерили его физически: сняли момент, поставили метку, провернули вал рукой на пять оборотов, посчитали приросты 0x6064.
|
прирост за 5 оборотов |
на оборот |
отклонение от 2²³ |
|---|---|---|---|
1:1 |
42 203 264 |
8 440 653 |
+0,6% |
2:1 |
20 787 053 |
4 157 411 |
−0,9% |
Отношение 2,03 — объект делит позицию строго по стандарту, разрешение равно 8 388 608 импульсов на оборот. Шум покоя — ±7 отсчётов из 8,4 миллиона.
Обходной путь всё-таки есть, но он вендорный. Стандартного объекта нет, а свой у завода нашёлся: 0x201E:0x34 (в их системе параметров это U0-52) отдаёт число бит энкодера, из которого разрешение считается сдвигом. Прочитали — 23, то есть 2²³ = 8 388 608, сходится и с паспортом, и с рукой на валу:
ethercat upload -p3 0x201e 0x34 --type uint16 # 23 — бит энкодера ethercat upload -p3 0x6091 0x01 --type uint32 # 1 — числитель редуктора ethercat upload -p3 0x6091 0x02 --type uint32 # 1 — знаменатель
Тип здесь важен: 0x201E:0x34 — uint16, попытка прочитать его как uint32 падает с «type mismatch», и это выглядит как отсутствие объекта. А 0x6091 равный 1:1 означает, что 0x6064 отдаёт сырые отсчёты энкодера — вся миллиметровая шкала остаётся на вашей стороне.
Мораль общая, не про Wecon: если стандартного объекта нет, спросите завод про вендорный. Стандарт описывает, что должно быть у всех; вендорная область 0x2000..0x5FFF описывает, что есть у этого прибора, и туда обычно вынесено ровно то, чего вам не хватает.
Сюда же — мелочь, которая стоит часа отладки: VD3E отказывается принимать запись SDO с флагом complete access. Соседний Mitsubishi на той же шине его принимает спокойно. То есть это свойство прибора, а не шины, и проверяется одной командой:
ethercat download -p3 -c 0x1600 0x00 --type uint8 0 # у VD3E вернётся NAK
Сам драйвер целиком
Вот весь файл lcec_wecon.c, из которого убраны только лицензионная шапка и большой комментарий с фактами про прибор (те, что собирали выше). Осталось семьдесят строк, из них содержательных двадцать.
#include "../lcec.h" #include "lcec_class_cia402.h" static int lcec_wecon_init(int comp_id, lcec_slave_t *slave); /* Модпараметры XML: у этого привода своих нет, но макрос ниже их ждёт. */ static const lcec_modparam_desc_t modparams_perchannel[] = {{NULL}}; static const lcec_modparam_desc_t modparams_base[] = {{NULL}}; static const lcec_modparam_doc_t chan_docs[] = {{NULL}}; static const lcec_modparam_doc_t base_docs[] = {{NULL}}; /* Шаг 1: чем этот драйвер опознаёт прибор на шине. "VD3E" — то, что вы напишете в XML как type=. */ static lcec_typelist_t types[] = { {"VD3E", LCEC_WECON_VID, 0x0d3e0001, 0, NULL, lcec_wecon_init, NULL, 0}, {NULL}, }; ADD_TYPES_WITH_CIA402_MODPARAMS(types, 1, modparams_perchannel, modparams_base, chan_docs, base_docs) static void lcec_wecon_read(lcec_slave_t *slave, long period); static void lcec_wecon_write(lcec_slave_t *slave, long period); typedef struct { lcec_class_cia402_channels_t *cia402; } lcec_wecon_data_t; static int lcec_wecon_init(int comp_id, lcec_slave_t *slave) { lcec_master_t *master = slave->master; lcec_wecon_data_t *hal_data; slave->proc_read = lcec_wecon_read; slave->proc_write = lcec_wecon_write; hal_data = LCEC_HAL_ALLOCATE(lcec_wecon_data_t); slave->hal_data = hal_data; /* Шаг 2: часы. assignActivate взят из ESI, режим DC. Если в XML задан <dcConf>, он победит — это значение по умолчанию. */ if (slave->dc_conf == NULL) { lcec_slave_dc_t *dc = LCEC_HAL_ALLOCATE(lcec_slave_dc_t); dc->assignActivate = 0x300; dc->sync0Cycle = master->app_time_period; slave->dc_conf = dc; } lcec_class_cia402_options_t *options = lcec_cia402_options(); /* Шаг 3: лимиты прибора — те самые два числа, снятые с шины. */ options->rxpdolimit = 10; options->txpdolimit = 10; options->channels = 1; /* Шаг 4: что включаем в карту. Каждая строка = запись в PDO и пин в HAL. Считать против лимита выше. */ options->channel[0]->enable_opmode = 1; options->channel[0]->enable_csp = 1; options->channel[0]->enable_csv = 1; options->channel[0]->enable_cst = 1; options->channel[0]->enable_target_torque = 1; options->channel[0]->enable_actual_torque = 1; options->channel[0]->enable_actual_following_error = 1; options->channel[0]->enable_error_code = 1; /* Шаг 5: дальше всё делает класс cia402 — собирает синк-менеджеры, раскладывает объекты по карте, заводит пины srv-*. */ lcec_syncs_t *syncs = lcec_cia402_init_sync(slave, options); lcec_cia402_add_output_sync(slave, syncs, options); lcec_cia402_add_input_sync(slave, syncs, options); slave->sync_info = &syncs->syncs[0]; hal_data->cia402 = lcec_cia402_allocate_channels(options->channels); hal_data->cia402->channels[0] = lcec_cia402_register_channel(slave, 0x6000, options->channel[0]); return 0; } /* Шаг 6: такт. Своей логики нет — перекладываем через класс. */ static void lcec_wecon_read(lcec_slave_t *slave, long period) { lcec_wecon_data_t *hal_data = (lcec_wecon_data_t *)slave->hal_data; if (!slave->state.operational) return; lcec_cia402_read_all(slave, hal_data->cia402); } static void lcec_wecon_write(lcec_slave_t *slave, long period) { lcec_wecon_data_t *hal_data = (lcec_wecon_data_t *)slave->hal_data; if (!slave->state.operational) return; lcec_cia402_write_all(slave, hal_data->cia402); }
Файл целиком, с комментариями и лицензией: src/devices/lcec_wecon.c.
Читается он так. Первая половина — декларация: вот по каким цифрам узнать прибор, вот его часы, вот его лимиты, вот какие режимы включить. Вторая половина — две функции такта, и в них нет ничего своего: чтение и запись целиком отданы классу cia402 из апстрима. Он собирает маппинг, заводит пины, разворачивает 0x6502 в булевы srv-supports-mode-*. Ваша работа — только верхняя половина, и она целиком состоит из фактов, собранных на предыдущем шаге.
Чего в драйверах нет — touch probe. Объекты 0x60b8..0x60bd есть и у Wecon, и у Mitsubishi, у обоих 0x60b8 стоит в заводском RxPDO. Не включили сознательно: проверить их на железе было не на чем, а неверный маппинг стоит перехода в OP. Пустое место честнее непроверенной строки.
Порядок работы: от незнакомого прибора до OP
Если свести всё вышесказанное в последовательность, получается семь шагов. Они делаются ровно в этом порядке, потому что каждый следующий опирается на факт, добытый предыдущим.
Поднять прибор на шине через
genericилиbasic_cia402и довести доOP. Пока прибор не дошёл доOPхоть как-нибудь, писать драйвер рано: вы не будете знать, чей отказ ловите — свой или прибора.Снять пять фактов — identity, лимиты PDO, параметры DC,
0x6502, список отсутствующих объектов. Четыре команды из конца статьи закрывают почти всё.Завести файл
src/devices/lcec_<вендор>.cи объявить в нём типы: строка на устройство, с vendor id, product code и, если нужно, ревизией.Заполнить декларацию — DC по умолчанию, лимиты, включённые режимы и величины. Здесь же выключается то, чего у прибора нет: лишний
enable_csvу привода безcsvстоит перехода вOP.Собрать, проверить, что тип попал в модуль, и запустить на одном слейве. Один — принципиально: на пяти вы не поймёте, чей отказ.
Написать страницу в
documentation/— пины, модпараметры, чего у прибора нет, на какой прошивке проверяли. Это не бюрократия: через полгода вы сами будете читать её как чужую.Заполнить
TestingStatusчестно. «Опознан на шине» и «крутили мотор» — разные вещи, и следующий человек принимает решение по этой строке.
Пункт про «не CiA 402» отдельной строкой: если прибор не сервопривод — каплер ввода-вывода, частотник со своим профилем, — класса под него может не быть вовсе, и тогда карточка устройства пишется руками: свои структуры пинов, свои функции такта, ручное описание синк-менеджеров. Это уже не двадцать строк, а несколько сотен, и решение писать такое надо принимать осознанно. Для CiA 402-привода — двадцать.
Как это собрать у себя
Файл кладётся в src/devices/, вендор дописывается строкой в src/lcec.h, регистрация — тем самым макросом ADD_TYPES_WITH_CIA402_MODPARAMS в конце файла типов. Отдельно в Makefile ничего вносить не надо: сборка забирает devices/*.c по маске.
sudo apt-get install -y build-essential pkg-config git libexpat1-dev \ libethercat-dev linuxcnc-uspace-dev git clone -b synctwin/bench https://github.com/SyncTwin/linuxcnc-ethercat.git cd linuxcnc-ethercat make configure && make build && make test sudo make install # LinuxCNC при этом должен быть остановлен
Проверка, что модуль действительно содержит ваш тип:
strings /usr/lib/linuxcnc/modules/lcec.so | grep -x VD3E
Класс cia402 есть только в свежем апстриме. Если у вас lcec из дистрибутива или старый форк sittner, пинов srv-* не появится вовсе, и вы потратите вечер, выясняя почему. Ориентир — ветка linuxcnc-ethercat версии 1.42 и новее.
Прогон
Сначала один слейв. Результат: OP, все пины живые. И сразу — ошибка в логе:
Failed to get reference clock time: Input/output error
Опорные часы живут на первом DC-способном слейве цепи. Если описать в конфиге только третий слейв, часам не на что опереться. Слейв в OP при этом выходит — DC просто не работает.
Дальше — весь конфиг фрезера, переведённый с generic на драйверы.
было |
стало |
|
|---|---|---|
|
236 строк |
83 |
записей PDO руками |
53 |
10 |
Оставшиеся десять — каплер Omron: для него драйвера нет, и его карта зависит от набора модулей в стеке, так что тут ручное описание оправдано.
<slave idx="0" type="IS620N" name="x"/> <slave idx="1" type="IS620N" name="y"/> <slave idx="2" type="IS620N" name="z"/> <slave idx="3" type="VD3E" name="s"/>
Результат:
0 0:0 OP + IS620N_ECAT_v2.6.8 1 0:1 OP + IS620N_ECAT_v2.6.8 2 0:2 OP + IS620N_ECAT_v2.6.8 3 0:3 OP + Wecon VD3E EtherCAT Servo v1.15 4 0:4 OP + NX-ECC202 EtherCAT coupler V1.2 lcec.0.all-op TRUE lcec.0.x.srv-opmode-display 8 (CSP) lcec.0.s.srv-opmode-display 9 (CSV, шпиндель) lcec.0.dc-sync-diff 0x64 = 100 нс
Что сломалось по дороге
Тип пина меняется вместе с драйвером
Это стоило запуска и было самым неочевидным:
HAL: ERROR: type mismatch 'sampler.0.pin.3' <- 'x-drv-ferr' ./machine.hal:148: link failed
В generic мы сами объявляли following-error как s32. Класс cia402 экспортирует srv-actual-following-error как u32 — и HAL узнаёт об этом только в момент связывания, то есть при запуске. Лечится на приёмной стороне: каналы sampler переведены в u32.
Мораль: при переходе на драйвер сверяйте не только имена пинов, но и типы. halcmd show pin lcec показывает и то, и другое.
Имена пинов теперь называет драйвер
было |
стало |
|---|---|
|
|
|
|
|
|
Заодно меняется адресация: вместо номера слейва подставляется name из XML. Это лучше — переставили привод в цепи, имена пинов не поехали.
refClockSyncCycles решает, будут ли часы вообще
Замер на одной и той же шине — три IS620N, VD3E и каплер:
|
|
|
|---|---|---|
часы сошлись |
нет |
да |
|
1 175 551 нс |
103 нс |
|
22 634 |
0 |
-1 означает, что мастер подстраивается под опорные часы шины, а не тянет их за собой. С положительным значением часы расходятся линейно и не сходятся никогда. Атрибут пишется в строке <master> того же XML, рядом с appTimePeriod.
Это, кстати, первый случай, когда сотню наносекунд внутри одной шины мы показываем собственным замером, а не ссылкой на литературу. Оговорка: шина при этом стояла — под движением осей замер не повторяли. И phase-jitter здесь в наносекундах; ровный ноль означает, что за окно наблюдения мастер не сдвинул фазу ни разу. Что пин при этом жив, видно по соседним счётчикам: они менялись.
apt поставил чужой пакет и отчитался успехом
Это ждёт любого, кто собирает свой пакет linuxcnc-ethercat и при этом держит подключённым репозиторий EtherLab из openSUSE — а его подключают почти все, там живёт сам IgH.
Мы собрали пакет версии 1.42.2-1+synctwin1, поставили, всё отчиталось успехом. Драйверов в модуле не оказалось: приехал снапшот 1.42.2.g7fffa62-0 из того самого стороннего репозитория, потому что dpkg ранжирует snapshot-суффикс выше обычной версии. Лечится Debian-эпохой (1: перед версией) — она старше любого снапшота.
Отсюда правило: «пакет установился» и «установился НАШ пакет» — разные факты. Проверять надо по содержимому:
strings /usr/lib/linuxcnc/modules/lcec.so | grep -x VD3E
Подменить lcec.so под работающей машиной нельзя
Очевидно, когда написано, и совершенно неочевидно в момент, когда вы просто пересобрали драйвер и хотите проверить правку. Модуль загружен в работающий процесс, установка кладёт файл поверх — а дальше поведение зависит от того, что именно успело прочитаться.
Две команды перед установкой:
pgrep -a linuxcncsvr # LinuxCNC работает? ethercat master | grep -E "Phase|Active" # мастер в Operation?
Если ответили — сначала остановить, потом ставить. У нас это знание год жило фразой в переписке, пока не превратилось в проверку перед установкой.
Чего мы так и не поняли
На малых оборотах VD3E мгновенная скорость гуляет: ±12% на 0,01 об/с и около ±5% на 0,2 об/с, с периодом порядка двух секунд. Считали по приростам счётчика положения, первую секунду разгона отбросив. Средняя скорость при этом верна с точностью 0,01%, так что это не масштаб и не шкала.
Что это — нормальное зубцовое усилие на малой скорости или следствие настроек контура? Написали производителю. Ответ пришёл: завод называет это нормальным явлением для VD3E и параметров тюнинга не предложил. То есть вопрос «почему» остаётся открытым — механическую природу нам не объяснили, — но практический вывод теперь есть: на ползучих подачах судить о таком приводе надо по средней скорости, мгновенная у него шумит штатно, и гоняться за этим шумом настройками контура бесполезно.
Заодно они закрыли ещё несколько мест этой статьи, и на это стоит посмотреть отдельно.
Что ответил завод Wecon
Это, пожалуй, самый неожиданный для нас результат всей работы, и он не про код.
Мы отправили производителю одного из приводов девять вопросов — те самые, что не закрывались ни документацией, ни шиной: лимиты PDO, минимальный цикл, сдвиг sync0, отсутствующие объекты, расшифровка 0x603F, температура, поведение на малых оборотах, привязка ESI к прошивке, машинный ESI на каплер. Через четыре дня ответил их технический инженер, поимённо, разобрав вопросы по одному.
Что из догадок стало фактом:
было в наших заметках |
стало словом завода |
|---|---|
«десять записей — похоже, столько назначено» |
десять — жёсткий лимит прошивки, расширять некуда |
«крутим шину на 1 мс, вероятно, можно быстрее» |
1 мс — минимум, ниже стабильность не гарантируется |
« |
сдвиг 50 % периода — рекомендация завода (замер выше) |
« |
есть вендорный |
«feed constant отсутствует» |
замены нет вовсе, есть только редуктор |
«температуру бы вывести оператору» |
температуры нет ни в одном объекте — обещать мониторинг нельзя |
«ripple на 0,01 об/с — гейны?» |
нормальное явление, параметров тюнинга не предложили |
«как декодировать |
значение в десятичном виде и есть номер кода |
И отдельно — таблица кодов ошибок: 66 строк, где у каждого кода есть класс, причина, признак «сбрасывается ли», требуемое действие и порядок устранения. Тот самый третий пункт из списка «чем вендоры отличаются», который, как мы написали выше, не делает почти никто. Прислали по прямому запросу, файлом, в первом же ответе. Публиковать её в открытом драйвере мы пока не можем — спросили разрешение и ждём; но в нашей панели оператор с этого дня видит Er.34 · перегрузка двигателя с причиной и действием вместо голого 0x22.
Слово завода мы всё равно проверили по шине, и это стоит делать всегда: тип 0x201E:0x34 оказался uint16, а не uint32, шкала напряжения звена постоянного тока в объекте не заявлена вовсе — 3157 при поданном силовом читается как 315,7 В только потому, что это похоже на выпрямленные 220 В, и в самом объекте эта шкала нигде не заявлена. Прежде чем показывать такие вольты оператору, их надо сверить мультиметром.
Вывод из всего этого простой и немного стыдный: девять вопросов, четыре дня и одно письмо закрыли больше, чем неделя экспериментов на стенде. Мы полгода исходили из того, что до инженеров китайского завода не достучаться, и не проверяли этого допущения. Оно оказалось ложным. Единственное, что нам понадобилось, — задать вопросы в виде, в котором на них можно ответить: номера объектов, а не «плохо крутится».
Что в итоге лежит открыто
Четыре драйвера, GPL v2, в нашей ветке апстрима — ссылки ведут прямо на файлы, каждый читается за пять минут:
драйвер |
устройства |
статус |
|---|---|---|
IS620N, SV660 |
оба доведены до |
|
VD3E |
доведён до |
|
MR-J4 ™ |
опознан на шине, до |
|
LXM28E |
опознан на шине, до |
Последние два написаны по данным с живой шины, но проверить их до конца нечем — на этих приводах нет моторов.
Что было бы, если бы драйверы были у всех приводов
Вопрос не риторический, потому что ровно так устроен мир Beckhoff внутри TwinCAT: там каталог полон, и никто никогда не видел карты PDO.
Сегодня запуск незнакомого привода в открытом стеке стоит вечера: снять пять фактов, написать XML, поймать отказ на PREOP, разобраться, повторить. Умножьте на число приводов в цеху и на число людей, которые делают это независимо друг от друга, каждый в своём файле.
Если бы карточки были на всё, изменилось бы три вещи.
Конфиг машины стал бы списком приборов. Не карта PDO, а перечисление: вот три оси, вот шпиндель, вот модуль ввода-вывода. Такой конфиг можно сгенерировать из модели станка, показать человеку на экране, проверить автоматически — с простынёй XML ничего этого не сделать.
Смена вендора перестала бы быть проектом. Пины srv-* одинаковы у всех драйверов класса cia402. Приехал другой привод — меняется одна строка type=, а .hal-файл станка остаётся прежним. Сейчас смена вендора означает переписать маппинг и заново выяснить, чего у нового прибора нет.
Знание перестало бы теряться. Всё, что мы выясняли экспериментом, у кого-то уже написано в блокноте — просто в чужом и недоступном. Карточка устройства в общем каталоге и есть форма, в которой это знание переживает конкретного интегратора.
Именно поэтому мы отправляем драйверы в апстрим, а не держим в своём форке. Ценность не в четырёх файлах — их можно написать за неделю. Ценность в том, чтобы следующий человек с тем же приводом их не писал.
Чем вендоры отличаются, если драйвер писать тебе
Пять приводов на одном столе дали побочный результат, которого мы не искали: стало видно, по каким признакам выбирать вендора, если поддержку в открытом стеке делать придётся самому. Факты — в таблице различий выше, здесь порядок по тому, во сколько работы обходится каждый пункт:
ESI лежит на сайте открыто, без регистрации. Ноль затрат для завода, дни работы для интегратора, если файла нет.
Словарь читается из самого прибора по SDO-Info. Прибор, который молчит, приходится описывать вручную и хранить это описание вечно.
Таблица кодов ошибок в машинном виде. Самый дешёвый пункт списка и самый заметный для конечного оператора.
Живой инженер, до которого можно дойти с техническим вопросом. Всё остальное про файлы, этот — про людей, и он же обычно решает, чей привод встанет в следующую машину.
Первые два — про то, что завод уже сделал или не сделал. Третий и четвёртый в 2026 году не делает почти никто, и именно в них разница между «привод, который заводится» и «привод, который выбирают».
Проверить это удалось буквально: третий и четвёртый пункты у одного из пяти вендоров оказались выполнимы — правда, только по запросу. Значит, дело не в том, что таблицу кодов невозможно отдать наружу или что инженера нельзя выделить. Дело в том, что об этом никто не просит.
Выводы
Работы в вендорном драйвере оказалось меньше, чем мы боялись: двадцать содержательных строк поверх готового класса. Барьер тут информационный, а не технический — надо знать пять вещей о приборе, и три из них приходится добывать экспериментом, потому что в документации их нет.
Но добывать экспериментом — не единственный путь, и это главная поправка, которую мы внесли в собственные представления по ходу. Из пяти нужных фактов два пришли письмом от завода за один заход, причём именно те, которые с шины не читаются в принципе: жёсткий лимит записей в карте и рекомендованный сдвиг sync0. Стенд отвечает на вопрос «что прибор делает». На вопрос «что у прибора можно и чего нельзя» отвечает тот, кто его спроектировал, — и оказалось, что он отвечает, если спросить номерами объектов, а не жалобой. Спрашивать всё равно надо с проверкой: половину присланного мы сверили по SDO в тот же день, и одна шкала при сверке оказалась выведенной нами по здравому смыслу: в объекте она не заявлена.
Куда важнее другое наблюдение. Мастер-стек в открытом мире есть, интерполятор мирового уровня есть, а между ними — человек с XML-файлом и невнятной SDO- ошибкой. Дефицит не в исполнителях, а в конфигураторе.
Пять приводов на одном столе показали, чего стоит слово «стандарт». Общий язык он задаёт: номера объектов, лестницу состояний, режимы. Всё, что нужно для запуска сверх этого, у каждого своё, и добывается прибором наружу. Общий провод и общее время шина даёт, а общее поведение — нет.
А что вы об этом думаете
Первое. У вас есть привод, которого нет в каталоге? Четыре команды снимают почти всё, что нужно драйверу:
ethercat slaves -v ethercat pdos -p<N> ethercat sdos -p<N> | wc -l ethercat upload -p<N> 0x6502 0x00 --type uint32
Пришлите это и ESI, если он существует в машинном виде, — соберём драйвер и отправим наверх с указанием приславшего. Канал любой: issue в linuxcnc-ethercat-configs, issue прямо в апстриме или просто комментарий здесь.
Второе. Кто пишет карту PDO руками до сих пор, зная про basic_cia402, — и почему? Нам интересны случаи, где универсальный драйвер не подошёл: это ровно те места, где вендорная карточка обязана появиться.
Третье, спорное. Правильно ли, что конфигуратора шины в открытом мире нет как класса, — или это как раз та часть, которую каждый пишет под себя?
Четвёртое. Писали ли вы производителю привода технические вопросы — и отвечали ли вам? Это перевернуло представление о том, где брать недостающие факты. Если у вас так же или наоборот — расскажите, чей это был привод.
Ссылки
Драйверы, по ветке на вендора: https://github.com/SyncTwin/linuxcnc-ethercat (
feat/wecon-vd3e,feat/inovance-is620n-sv660,feat/mitsubishi-mr-j4,feat/schneider-lxm28e; веткаsynctwin/bench— все сразу).Подробная инструкция по установке и настройке: https://github.com/SyncTwin/linuxcnc-ethercat-configs/tree/main/drivers
Рабочий конфиг фрезера целиком, с
.halи.ini: https://github.com/SyncTwin/linuxcnc-ethercat-configs/tree/main/mill-3axis-named-driversVD3E с полным списком снятых фактов: https://github.com/SyncTwin/linuxcnc-ethercat-configs/tree/main/wecon-vd3e-driver
Реестр устройств с идентификаторами и граблями: https://synctwin.ru/ethercat/
Апстрим, куда всё это уходит: https://github.com/linuxcnc-ethercat/linuxcnc-ethercat
NutsUnderline
я эпизодически пытаюсь понять "что ты такое" про ethercat, так что статья как бы не моего уровня, поэтому вопрос соответвует: и вот это все для того чтобы движками управлять, задумывалось гибко и универсально, а по факту - как обычно, закрытый клуб и напильник?
synctwin Автор
Для простого - из коробки: базовый профиль CiA 402 одинаков у всех, generic в конфиге и поехали. Напильник за пределами профиля: сколько данных привод пускает в обмен, что он про себя рассказывает, как синхронизируется - у каждой железки своё, и это разово прячется в драйвер устройства. Мы такие драйверы выкладываем открытыми, чтобы следующий с тем же приводом их не писал.
NutsUnderline
про то и речь