4 июля 2019 г.

Загрузка системного модуля NC PTP

В ПЛК на базе WinCE / Compact существует операция подъема уровня лицензии TwinCAT 2: был стандартный уровень PLC, а стал контроллер с NC PTP и возможностью управления движением.

Для подъема уровня TwinCAT используется специальная утилита, которая, конечно же, требует лицензионный ключ. Вообще, такой процедурой занимается сервисная служба, но можно и самому, а итогом послужит следующая картина:


Правда итог не так очевиден из System Manager: ветка NC может отсутствовать в конфигурации. Иными словами, контроллер уже может, а вот System Manager почему-то в упор не видит NC.

На самом деле уровень лицензии подняли, а модуль в TwinCAT еще не загрузили.

TwinCAT 2 — это модульная система. Она состоит из нескольких низкоуровневых драйверов для подсистем: TcIo.sys, TcRouter.sys, TcRTime.sys, TcNc.sys... В случае с WinCE драйверы уже встроены в образ, их просто нужно подключить.

Список модулей TwinCAT 2, загружаемых при старте системы, хранится в \Hard Disk\TwinCAT\DefaultConfig.xml. Открываем этот файл и дописываем загрузку модуля TCNC. Соответственно файл после редактирования будет выглядеть так:

<?xml version="1.0" encoding="UTF-8"?>
<TcBootProject>
    <Drivers>
        <Driver>
            <Type>1</Type>
            <ServiceName>TCIO</ServiceName>
        </Driver>
        <Driver>
            <Type>1</Type>
            <ServiceName>TCPLC</ServiceName>
        </Driver>
        <Driver>
            <Type>1</Type>
            <ServiceName>TCRTIME</ServiceName>
        </Driver>
        <Driver>
            <Type>1</Type>
            <ServiceName>TCNC</ServiceName>
        </Driver>
    </Drivers>
    <InitCmds/>
</TcBootProject>


Перезапускаем ПЛК и смотрим на результат. Было → стало:


3 июля 2019 г.

Дребезг на трех языках

Паттерны и шаблоны можно находить в любой системе и на любом языке программирования. У Скотта Витлока (Scott Whitlock) в блоге про автоматизацию есть любопытная подборка паттернов (или шаблонов) Patterns of Ladder Logic Programming на языке лестничных диаграмм, релейной логики или просто LD. Всё как в большом и кровавом энтерпрайзе с рейтингом R.


Обычно в проектах я использую ST и C# за редким исключением, когда возникает необходимость в других языках, поэтому было особенно интересно посмотреть как будет выглядеть один и тот же алгоритм на трех разных языках программирования МЭК. Я взял паттерн "Дребезг контактов" (Debounce) и переписал его на LD, ST и CFC.

FBD брать не стал, так как есть CFC, а это более продвинутая и современная версия блочных диаграмм. На картинке выше, адаптация паттерна на языке LD под TwinCAT 3. Про объявление переменных чуть позже.

Вообще проблему с дребезгом лучше устранять на уровне железа, но если что, то в модули дискретных входов уже встроены фильтры. Правда они не настраиваются и не отключаются, что заказано, то заказано. Поэтому, чтобы иметь возможность подкрутить время нечувствительности, я воспользуюсь готовым паттерном, и проверю его работу одновременно на трех языках программирования. Сразу и одновременно.


Continuous Flow Chart


Паттерн настолько простой, что перенос не должен вызывать затруднения, разве что расставить последовательность операций, ну и бывает трудно найти панель инструментов с кнопками элементов диаграммы. Она прячется во View → Toolbox (Ctrl + Alt + X). Она же хранит элементы для LD и прочих конструкторов.

Объявление переменных остается прежним. Забегая вперед, оно будет таким же и для LD.


Structured Text


LD легко транслируется на ST. Интересно, что шапка с объявлением переменных и ФБ остается по прежнему одинаковой для всех трех языков:

FUNCTION_BLOCK DebounceLD // DebounceST // DebounceCFC
VAR_INPUT
    IN: BOOL;
    Delay: TIME := T#50MS;
END_VAR
VAR_OUTPUT
    Q: BOOL;
END_VAR
VAR
    DelayOn: TON;
    DelayOff: TOF;
END_VAR

Копируем в программу на ST шапку с объявлением переменных, переименовываем название функционального блока в DebounceST и дописываем тело программы. Текст лаконичнее картинки, редкий случай когда лучше один раз прочитать, чем долго скользить взглядом по картинке. Хотя, на вкус и цвет...

DelayOn(IN := IN, PT := Delay);
DelayOff(IN := IN, PT := Delay);
Q := DelayOn.Q OR (Q AND DelayOff.Q);

Количество строк (равное трем) также совпадает с тремя "ступенями" лестничной диаграммы LD.


Испытания


Подвергнем три функциональных блока различным испытаниям. С помощью визуализации можно на глазок проверить правильно ли они зажигают и гасят лампочки, а с помощью цикла мы внезапно проверим какой же из языков быстрее:

PROGRAM MAIN
VAR
    DebLD : DebounceLD;
    DebST : DebounceST;
    DebCFC: DebounceCFC;

[...]

lastExecTime := _TaskInfo[1].LastExecTime;

Timer(IN := TRUE);
IF Timer.Q THEN
    Timer(IN := FALSE);
    state := SEL(state >= 3, state + 1, 0);
END_IF

FOR i := 0 TO 10000 DO
    CASE state OF
    1:
        DebLD ( IN := in, Delay := delay, Q => outLD  );
        
    2:
        DebCFC( IN := in, Delay := delay, Q => outCFC );

    3:
        DebST ( IN := in, Delay := delay, Q => outST  );
    END_CASE
END_FOR

На самом деле правильность работы я определял по цифровому осциллографу, там же случайно выяснилось, что один из языков незначительно проигрывает в производительности... Это все очень не точно и не имеет большого значения, но все-равно интересно:


Синяя линия отражает значение переменной state, нулевое значение которой соответствует пустому циклу (когда внутри цикла практически ничего не происходит). Заметен небольшой всплеск активности в момент работы CFC функционального блока.

Правда все это крутилось внутри ноутбука, а я сталкивался с ситуацией, когда рантайм настоящего контроллера отличался от рантайма на ноуте. Поэтому я специально перепроверил поведение на 32-х разрядном контроллере CX9020 под WinCE.

Ситуация аналогичная:

Только не воспринимайте эти результаты всерьез: языки все равно равноценны и равнозначны. Пишите на том, который вам нравится или удобен в каждом конкретном случае.

28 июня 2019 г.

Символьная линковка через атрибуты

Данные железа связываются с переменными программной части через конфигурацию контроллера. Иногда это не удобно и хочется идти не от конфигурации, а от кода (Code First). Как пример, я взял линковку переменных, связанных с управлением движением сервоосей NC PTP. И еще, верните, пожалуйста, карту мапинга — она была бессмысленной, но красивой ↘


Атрибуты


Переменной можно дописать атрибут — это специальная строка, которая говорит системе, что с переменно нужно сделать дополнительные телодвижения. Атрибутов было мало — сейчас стало много. В случае сервоосей нас интересует атрибут TcNcAxis:

{attribute 'TcNcAxis' := 'Axis 3'}
axMaster : AXIS_REF;

Здесь атрибут (ключевое слово attribute) заключен в фигурные скобки. Строка атрибута должна идти перед объявлением переменной или экземпляром функционального блока. Далее в кавычках следует одно или несколько(!) имен переменных или ФБ, символ присваивания  := , и наконец значение атрибута. В данном случае значением служит имя NC оси взятое из конфигурации  'Axis 3' . Записывается прямо с пробелами и другими символами, для этого и нужны кавычки.

Добавив атрибут и пересобрав проект, я автоматически получу слинкованную с сервоосью переменную типа AXIS_REF. Остается только реактивировать конфигурацию в ПЛК и все готово. Еще раз:
  1. Добавить строку атрибута или заменить имя оси в значении атрибута на другое.
  2. Пере/собрать проект (Build → Rebuild Solution).
  3. Активировать конфигурацию. Внимание! Активировать конфигурацию!
После активации, слинкованные через атрибут переменные будут подсвечены синим значком линковки (см. FromPlc и ToPlc):


Когда системе не удается слинковать данные, она сообщает об этом большим и внезапным сообщением поперек экрана. Проект в итоге соберется, но данные бегать не будут. Если же все прошло удачно, то после пересборки проекта система сообщит:
Message 06.01.2019 21:56:05 729 ms | 'TwinCAT XAE': Existing NC axis 'Axis 3' linked to instance 'MAIN.axMaster'
Очень удобно, так как теперь можно еще больше сконцентрироваться на коде. Главное не забывать пересобирать проект, а затем активировать конфигурацию. Аналогично можно работать с функциональными блоками.


Ввод/вывод


Переменные ввода/вывода также удобно линковать со стороны кода. Причем, здесь мы как раз увидим тот самый обещанный символьный маппинг во всей красе, так как будем использовать сложный символьный путь к данным.

На этот раз воспользуемся атрибутом TcLinkTo. Вообще, он простой, поэтому я возьму что-нибудь посложнее TcLinkToOSO. Атрибут c -OSO на конце позволяет не просто связать переменную типа AT %IQ* с данными, но также задать количество разрядов тут и там, а также их битовое смещение.

Немногим ранее, я проверял как работает функция быстрого останова в NC. Пришлось сплясать с бубном, чтобы прикрепить булевый флаг к биту частично занятой переменной. Надеюсь, что с атрибутами получится в разы проще.

Напомню задачу: в настройках NC оси, в ветке конфигурации Axis X → Drive → Inputs →... есть поле nState4. Седьмой бит этого поля отвечает за быстрый останов данной сервооси. Пара битов этого поля уже автоматически слинкована с какими-то данными, поэтому действовать нужно аккуратно.
Быстрый стоп не работает на виртуальных осях! Необходимо серво-железо.
Для начала сходим в конфигурацию и посмотрим, где расположены требуемые данные в конфигурации со стороны железа, то есть смотрим во вкладку Variable, текстовое поле Full NameTINC^NC-Task 1 SAF^Axes^Axis 3^Drive^Inputs^In^nState4. Это полный путь к данным переменной nState4 в конфигурации:


Копируем и вставляем его в код программы как значение атрибута

{attribute 'TcLinkToOSO' := '<0,1,7>TINC^NC-Task 1 SAF^Axes^Axis 3^Drive^Inputs^In^nState4'}
cmd_FastStop AT %Q* : BOOL;

Мне нужен всего-лишь один бит, поэтому я расширил путь специальным суффиксом -OSO: <0,1,7>. Последовательно слева-направо:
  • 0 — битовое смещение в переменной ПЛК, то есть с какого бита ПЛК переменной начинать связывать. Можно связать не всю переменную, а только ее часть и не обязательно с начала и до конца: с помощью -OSO можно выхватить кусочек из середины.
  • 1 — количество связываемых бит.
  • 7 — битовое смещение в переменной на стороне железа (в моем случае в переменной nState4). Мне нужен бит номер 7, поэтому смещение = 7.

После сборки проекта система сообщает:
Message 06.01.2019 22:11:15 930 ms | 'TwinCAT XAE': Variable 'MAIN.cmd_FastStop' (Offs: 0) linked with 'TINC^NC-Task 1 SAF^Axes^Axis 3^Drive^Inputs^In^nState4' (Offs: 7, Size: 1)

Разбавлю полным листингом программы:

PROGRAM MAIN
VAR
    {attribute 'TcNcAxis' := 'Axis 3'}
    axMaster     : AXIS_REF;
    
    McPower      : MC_Power;
    McVelocity   : MC_MoveVelocity;

    cmd_PowerOn  : BOOL;
    cmd_Velocity : BOOL;
    {attribute 'TcLinkToOSO' := '<0,1,7>TINC^NC-Task 1 SAF^Axes^Axis 3^Drive^Inputs^In^nState4'}
    cmd_FastStop AT %Q* : BOOL;
    
    actVelo      : LREAL;
    state        : INT;
END_VAR

[...]

axMaster.ReadStatus();
actVelo := axMaster.NcToPlc.ActVelo;

CASE state OF
0:
    IF McPower.Status THEN
        cmd_Velocity := TRUE;
        state := 100;
    END_IF

100:
    IF McVelocity.InVelocity THEN
        cmd_Velocity := FALSE;
        state := 200;
    END_IF
    
200:
    IF actVelo < 0.1 OR NOT McPower.Status THEN
        cmd_PowerOn := FALSE;
        state := 300;
    END_IF     
    
300:
    IF NOT McPower.Status THEN
        cmd_FastStop := FALSE;
        state := 0;
    END_IF

END_CASE

McPower(
    Axis            := axMaster, 
    Enable          := cmd_PowerOn, 
    Enable_Positive := TRUE, 
    Enable_Negative := TRUE
);

McVelocity(
    Axis     := axMaster, 
    Execute  := cmd_Velocity, 
    Velocity := 200
);

Теперь если во время работы программы дернуть за булевый флаг cmd_FastStop и установить его в TRUE, мы незамедлительно получим быструю остановку сервооси, а система подскажет нам, что:
Message 06.01.2019 22:23:25 772 ms | 'TCNC' (500): 'Axis 3' (Axis ID: 3) <NOTE>: 'Fast axis stop' triggered by IO interrupt 'Drive->Status4->Bit7' (SignalType=1 (SignalType_RisingEdge), FastDec=0.000000, FastJerk=0.000000, IoState=1, OldIoState=0)!

Мышой проще


А еще есть кнопка Link To PLC... которая никакого отношения к символьной линковке не имеет, зато позволяет присобачить одним кликом мыши переменную AXIS_REF к NC оси, и безо всяких там NcToPlc, PlcToNc, ...


27 июня 2019 г.

Структурированные исключения которые не работают

Версия TwinCAT 3.1.4022.29.


И похоже мы имеем дело с ядрами разных версий на десктопе и в полноценных ПЛК. Под полноценные контроллеры проект собирается без ошибок, но исключения по прежнему не работают.

Исключения эти вроде как расширение стандарта МЭК. Поэтому пока эта штука не работает можно познакомиться с Exceptionhandling in IEC Applikationen mit CODESYS и документацией на CoDeSys.

19 июня 2019 г.

New, Delete и память роутера

Мир готов отказаться от ручного управления динамической памятью, а мы ее только-только получили, в нагрузку к TwinCAT 3. Иначе классы и прочий полиморфизм не работают. Теперь, когда появилась динамическая память, должны появиться и динамические типы данных, а если не появятся — мы их сделаем сами.

Ключевые слова


Во времена TwinCAT 2 динамической памяти не было. Если что-то изменяло свой размер, то под него выделялся буфер максимального размера, рядом ставился счетчик длины или количества элементов, или как-то еще, но в итоге как-то отмечали окончание цепочки символов. Например, нулевой символ \0 в конце строки. Как вариант, можно было создать массив структур, и по мере необходимости добавлять в него указатели, и таким образом нафантазировать себе списки, очереди, стеки, деревья, а также другие типы данных переменной длины.

Теперь же появились новые ключевые слова __NEW и __DELETE. С помощью них можно создавать переменные во время выполнения программы. Место для переменных выделяется в большой, неразмеченной куче памяти, а по окончании работы место возвращается. Можно создавать и снова удалять, и так далее без конца.

PROGRAM NewDeleteRepeatAgain
VAR
    ptr : POINTER TO ARRAY[0..4095] OF BYTE; 
END_VAR

[...]

ptr := __NEW(BYTE, 4096);
__DELETE(ptr);

Оба ключевых слова встроены в язык программирования и являются расширением стандарта IEC 61131-3. Еще раз, это не функциональные блоки, это встроенные средства языка программирования. Обе команды работают с указателями типа POINTER TO ТипДанных. С "голыми" адресами типа DINT и PVOID команды работать не умеют и тем самым достигается какая-никакая безопасность при работе с арифметикой указателей.

Кроме динамических структур, можно на лету создавать экземпляры функциональных блоков. В результате получится указатель на функциональный блок, который затем используется как обычная функция. Чтобы функциональный блок или структуру стали динамически создаваемыми, их необходимо пометить специальным атрибутом {attribute 'enable_dynamic_creation'}.


Память роутера


Память занимается у роутера. Ее объем — это обычная настройка проекта:



Роутеру же память и возвращается — если, конечно, разработчик не забудет вернуть. Если забудет, то память закончится и __NEW начнет возвращать нули вместо указателей. Я пробовал выделять блоки длиной в 4 мегабайта и тут же удалять их, и так каждый ПЛК цикл. Ни каких проблем с этим не возникло. Время работы практически не отличается от работы с фиксированными данными. Выделение и освобождение памяти происходит максимально быстро и практически не заметно. Разве что, чуть-чуть.

В эксперименте принимал участие контроллер С6015 с атомом E3845 на борту (Windows 10 IoT) и CX9020 c ARMv7 и WinCE Emb. Результаты по таймингам идентичные. Более слабый процессор CX9020 позволяет обрабатывать меньшее количество данных за цикл, поэтому размер блоков пришлось сократить до 100 килобайт, но задержки по прежнему аналогичны старшему брату. От разрядности и операционной системы тоже ничего не зависит. Вот за эту универсальность все и любят третью версию TwinCAT.

__DELETE возвращает теперь уже свободное место в кучу. С нулевыми указателями команда не работает: она просто игнорируя их. Указатели в TC3 типизированные, поэтому кроме как трюками с ADS их не испортить. Если не забывать освобождать занятую память, то все должно быть нормально. На картинке ниже, я ежесекундно занимаю по 4 мегабайта из памяти роутера, а затем... не освобождаю ее: когда-нибудь память у роутера закончится.



Самоконтроль


Библиотека Tc2_Utilities предлагает функцию FB_GetRouterStatusInfo, отвечающую за контроль состояния роутера. Поле maxMemAvail выходной структуры ST_TcRouterStatusInfo хранит текущий объем свободной памяти роутера в байтах. Эта же память является "кучей" динамической памяти.

Аналогичную информацию разработчик может получить и без программирования, просто ткнув правой кнопкой мыши в иконку TwinCAT, а затем в Router → Info.


К сожалению, в данный момент (для TwinCAT 3.1.4022.29) единственный способ очистить кучу не отключая контроллер — это перезапустить системный сервис TwinCAT. Перезапуск программы ПЛК, перезаливка конфигурации, холодный или полный сброс переменных, переключение в режим конфигурации, любые другие телодвижения — не скроют следы утечек памяти у неаккуратного разработчика. Бдите!


Производительность


Память занимается и освобождается быстро. Что еще? Копирование динамически созданных блоков памяти происходит с той же скоростью, что и у статических (статически выделенных на этапе сборки проекта переменных). Проверка на массивах большого размера различия не показала.

Как там дела с обработкой элементов массива или динамического списка (vector, List, итп типов данных)? Здесь есть небольшое различие, которые не так чтобы сильно заметны и возможно это просто накладные расходы на работу с указателями, но все-таки чуть медленнее. По вертикали отложены миллисекунды от 4 до 10 мс:


Каждый цикл программа пробегает по всем элементам массива: 8 миллисекунд длится забег по статическому массиву и 9 миллисекунд по динамическому. Резкие провалы вниз — это выделение динамической памяти под массив (которые, как нам теперь известно, почти не занимает времени цикла).

Итого, не сильно большая разница чтобы осторожничать, но проверить стоило.


PS: Копаем глубже


При создании нового экземпляра происходит инициализация объекта и его полей. Если в объекте есть вложенные объекты (структура в структуре), они также будут инициализированы. С классами немного сложнее, так как в них есть конструкторы и деструкторы, но это уже отдельная тема.

30 мая 2019 г.

Обмен данными через ADS между ПЛК-задачами

Уже во втором твинкате была библиотека TcDataExchange и, соответственно сейчас в третьем есть библиотека DataAccess → Tc2_DataExchange. Основное назначение — она умеет отправлять переменные ПЛК через ADS. При этом одновременно можно использовать как имя переменной, так и пару индекс-смещение. Получается этакий комбайн, умеющий все и сразу, и сильно упрощающий жизнь и движение между ПЛК задачами.
И там действительно есть полезный набор функций, но(!) нужно быть осторожным. В случае регулярной или частой пересылки данных, возможны непредвиденные нагрузки на систему. Причина в том, что внутри комбайна прячется комбайнер CASE с рядом скрытых от разработчика действий, которые активно перемалывают системные ресурсы.

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

Функции чтения-записи FB_ReadAdsSymByName и FB_WriteAdsSymByName в зависимости от значения параметра eComMode также могут запоминать и повторно использовать полученный дескриптор, но только один. Соответственно, экземпляр функция с параметром eComMode = E_AdsComMode.eAdsComModeFastCom должен работать только с одной переменной. Это обязательно необходимо учесть, так как по умолчанию используется безопасный режим eComMode : E_AdsComMode := eAdsComModeSecureCom, который фактически становится опасным из-за постоянного передергивания ресурсов системы.

Delta-функции библиотеки TcDataExchange для одной единственной операции будут раз за разом получать, обрабатывать и освобождать системные ресурсы. И это логично, так как они изначально созданы для отправки данных только при условии выхода значения переменной за пределы, заданные разработчиком. Что естественно не должно происходить слишком часто. Насколько часто — разработчик должен определить самостоятельно. Очень похоже на событийную модель.

В итоге, библиотека оказывается действительно полезной, ведь кроме перечисленного выше, на вход функций поступает не сами данные, а указатель на них, следовательно можно отслеживать и передавать не только одиночную переменную, но и строку, и массив и, в конце концов, целую структуру.

28 мая 2019 г.

Git Ignore для TwinCAT

TwinCAT 3 благополучно переполз на текстовые форматы данных. Никаких больше бинарников невнятного формата, которые не сравнить и не выгрузить в системы контроля версий. GitHub, GitLab, Bitbucket и много других бесплатных онлайн сервисов. Правда, если вы не боитесь выгружать свои совершенные проекты в облако.



В интернете и без меня много ютуб учебников, поэтому кратко:
  • git — распределённая система управления версиями. Есть и другие.
  • github и ко — крупнейший веб-сервис для хостинга IT-проектов и совместной разработки.
Не равно: git != github или git <> github.
Когда вы наконец-то освоите последовательность действий: init, commit, push, итд., приходит время для нюансов. Например, не имеет смысл держать в проекте студийный мусор и бинарные библиотеки. Для исключений используется файл .gitignore и он автоматически исключает из проекта ненужное. Или то, что разработчик сочтет ненужным.

Для проектов Visual Studio есть стандартно-универсальный VisualStudio.gitignore. Там есть все, кроме части, ответственной за TwinCAT проекты. Я добавил.


.gitignore для TwinCAT проектов


Файл .gitignore можно отредактировать в любом текстовом редакторе. Добавляете в конец файла:

# Beckhoff TwinCAT3 projects
*.~u
_Libraries/
_Boot/
_CompileInfo/
*.tclrs
*.tclrq
*.tpy
*.tmc
*.bak


И получаете незамусоренный проект. Возможно, вы захотите что-то оставить или что-то удалить. Есть с чего начать: Source Control.

24 мая 2019 г.

Контроль состояния сервотерминалов EL7201

Что если силовое питание не подано, ось не активна и вдруг разорвать сигнал обратной связи? Что если оторвать провод силового питания? Как будет реагировать NC? Возможно ли вообще отследить такой тип аварий и как сбросить ошибку? Копну глубже в контроль состояния компактных сервоусилителей EL72xx, и начну с обратной связи.
Изображение: Beckhoff Automation

Обратная связь


Сервоось недееспособна, если отсутствует сигнал обратной связи. Необходимо регулярно вызывать функцию Axis.ReadStatus(), чтобы понять, что с ней происходит. В момент потери сигнала будет выставлен флаг Axis.Status.DriveDeviceError.

Так как работа сервомотора без обратной связи невозможна, самостоятельно этот флаг не сбросится и не "рассосется". После устранения причины аварии, ошибку нужно будет сбросить с помощью стандартной функции MC_Reset. Но это всё на случай, если ПЛК и сервомодули нельзя обесточивать на время ремонта, обслуживания или замены оборудования. Безопаснее выключить и включить снова.


Силовое питание


Потерю силового питания нельзя рассматривать как аварийную ситуацию, так как питание логики и силового питания подаются раздельно. Силовое питание можно подать когда-нибудь позже, в то время как "мозги" должны быть запитаны с самого начала работы.

Ни один флаг не пошевелится, если силовое питание отсутствует, а NC-ось неактивна. Но стоит только разблокировать ось и дать ей команду на старт, как NC тут же взбрыкнет ошибкой. Необходимо научиться выявлять такие ситуации независимо от состояния оси и наличия или отсутствия команд.

Слово состояния сервомодуля (6010:10 — Statusword) содержит два бита, отвечающие за ошибки и аварии. Это бит #3 Fault и бит #7 Warning: стр. 143, Index 6010 DRV Inputs. Поле Fault отвечает за аварии. Этот бит транслируется подсистемой NC в поле состояния оси DriveDeviceError. Именно его мы отслеживали в главе про обратную связь. Потеря обратной связи — это авария, но(!) отсутствие силового питания — это еще не авария, а просто ситуация требующая особого внимания. Поэтому — Achtung, т. е. Warning.

Флаг Warning также транслируется в NC, но он не доступен через параметры состояния NC-оси. Причина этого для меня не понятна, но я попробую добраться и до этого флага.

Начнем с того, что Statusword передается в PDO сервомодуля. Затем, оно автоматически линкуется с NC параметрами Axis.Drive.Inputs.In.[nState1..nState2], попутно разбиваясь на старший и младший байты. И всё. Далее эти байты используются где-то внутри подсистемы NC и недоступны разработчику.

Чтобы получить доступ к слову состояния, можно непосредственно постучаться в сервомодуль, то есть прочитать параметры через функции CANopen. Можно и по другому. Если немного погуглить (а это более качественный способ поиска информации по справочной системе), то обнаруживается интересная таблица с индексами:

TwinCAT Connectivity → ADS-Device-Documentation → ADS Interface NC → Specification "Index group" for NC ( ID [0x01...0xFF] ) → Specification Drive → "Index offset" specification for cyclic drive process data (Index group 0x7300 + ID).

Из таблицы не совсем понятно зачем всё это необходимо, но судя по названиям...


Читаем другие параметры


Читать будем через ADS. Для чтения параметров, перечисленных в "таблице", мы воспользуемся функцией ADSREAD из библиотеки системных функций Tc2_System. Для запуска функции понадобятся:
  • NETID — пустая строка, если читаем с того же локального ПЛК.
  • PORT — это стандартный порт NC-Task SAF = 501.
  • IDXGRP — индекс группы из таблицы = 0x7300 + ID, где ID - это номер оси NC; нумерация осей начинается с единицы, то есть первая ось получит индекс = 16#7301.
  • IDXOFFS — смещение из таблицы = 16#80.

В таблице доступ к параметру 16#80 помечен как "Write", но все относительно и зависит от точки зрения, поэтому я буду из него "Read". Осталось решить куда прочитать данные. По идее необходима некая структура данных, но есть ли она в стандартных библиотеках мне не известно, поэтому я создал парочку своих собственных DUT. Выглядят они почти как в той самой таблице из справочной системы:

TYPE EL7201_DriveInfoEx :
STRUCT
    nInData1    : DINT;
    nInData2    : DINT;
    StatusWord  : EL7201_StatusWord; // Axis.Drive.Inputs.In.[nState1..nState2]
    nStatus3    : BYTE;
    nStatus4    : BYTE;
    // optional : extended drive info, 40 bytes
    nInData3    : DINT;
    nInData4    : DINT;
    nInData5    : DINT;
    nInData6    : DINT;
    nStatus5    : BYTE;
    nStatus6    : BYTE;
    nStatus7    : BYTE;
    nStatus8    : BYTE;
    Reserved1   : DINT;
    Reserved2   : DINT;
END_STRUCT
END_TYPE

Для удобства использования сразу же разбиваю слово состояния на структуру из битовых полей:

TYPE EL7201_StatusWord  :
STRUCT
    ReadyToSwitchOn     : BIT;
    SwitchedOn          : BIT;
    OperationEnable     : BIT;
    Fault               : BIT;
    Reserved4           : BIT;
    QuickStop           : BIT; // inverse: true when switched off
    SwitchedOnDisabled  : BIT;
    Warning             : BIT; // Ex.: raise when Power Supply lost
    Reserved8           : BIT;
    Reserved9           : BIT;
    TxPDOToggle         : BIT; // selection/deselection via 0x8010:01
    InternalLimitActive : BIT;
    TargetValueIgnored  : BIT;
    Reserved13          : BIT;
    Reserved14          : BIT;
    Reserved15          : BIT;
END_STRUCT
END_TYPE

В таблице есть указание что структура EL7201_DriveInfoEx может быть длинной как в 12 байт, так и расширенная, длинною в 40 байт. В моем случае необходима расширенная структура. Остается прочитать данные:

axis               : AXIS_REF;
axDriveStatus      : EL7201_DriveInfoEx;
AdsReadDriveStatus : ADSREAD;

[...]

AdsReadDriveStatus(
    NETID    := '', 
    PORT     := 501,
    IDXGRP   := 16#7300 + axis.NcToPlc.AxisId,
    IDXOFFS  := 16#80,
    LEN      := SIZEOF(axDriveStatus), 
    DESTADDR := ADR(axDriveStatus), 
    READ     := TRUE);
 
IF NOT AdsReadDriveStatus.Busy THEN
    AdsReadDriveStatus(READ := FALSE);
END_IF


Примечание: Fault и Warning работают и соответственно устанавливаются/сбрасываются независимо друг от друга: один флаг никак не влияет на другой. Warning устанавливается и сбрасывается автоматически, поэтому нет способа повлиять на его состояние.


Уровень силового питания


Значение силового питания (12-50 Вольт) можно прочитать напрямую из сервомодуля. Адрес сервомодуля можно получить с помощью функции MC_ReadDriveAddress и структуры ST_DriveAddress:

Изображение: Beckhoff Automation

Значение уровня постоянно обновляется в параметре CoE 9010:12 — DC link voltage. Значение дается в милливольтах, поэтому 24 вольтам будет соответствовать значение 23932. Почему не 24000? Потому что — не точно.

Следующий кусок кода прочитает значение напряжения прямо из сервомодуля:

dcLinkValue : DINT;
AdsReadCoE  : ADSREAD;

[...]

AdsReadCoE(
    NETID    := '169.254.23.39.4.1', 
    PORT     := 1002,
    IDXGRP   := 16#F302,
    IDXOFFS  := 16#90100012,
    LEN      := SIZEOF(dcLinkValue), 
    DESTADDR := ADR(dcLinkValue), 
    READ     := TRUE);
 
IF NOT AdsReadCoE.Busy THEN
    AdsReadCoE(READ := FALSE);
END_IF


NETID — адрес EtherCAT мастера.
PORT — номер порта устройства, в данном случае — это сервомодуль EL7201.
IDXGRP — индекс группы сервиса ADS, отвечающего за работу с CANopen SDO.
IDXOFFS — индекс и смещение регистра CAN. Для упрощения в примере выше индекс не вычисляется, так как в шестнадцатеричной системе его легко сформировать вручную 9010-0012. Если интересно, чуть более подробно написано в посте Работа с CANopen из C# программы.

Когда пример готов, подключаю цифровой осциллограф к переменной dcLinkValue и получаю график "зарядки-разрядки". Здесь питание контроллера и силовое питание сервомодулей подается от одного 24 вольтового блока питания. Поэтому "потолок" на графике ~ 24000, а сервомодуль работает с половинной мощностью:

Синяя кривая на графике показывает, что модуль разряжается долго или по крайней мере не мгновенно. Стоит учесть это, если вдруг захочется контролировать уровень напряжения из программы.

28 февраля 2019 г.

Профили сервотерминалов MDP и DS

В среде Бекхофф есть два вида сервотерминалов EL72x1-000x. С одной стороны они совершенно одинаковые по электромеханическим параметрам; с другой стороны, они отличаются: во-первых, цифрой в модели, во-вторых, названием профиля: MDP742 или DS402. Что выбрать? Ответ можно найти в статье Profile MDP 742 or DS 402.

Профили относятся к стандарту CANopen. Для полной ясности, EL72x1 внутри себя сидит на шине CAN, данные которой транслируются дальше на шину EtherCAT. Профили определяют номера индексов/смещений, порядок/структуру параметров, а также ряд других свойств словарей объектов CAN. Оба профиля и MDP742, и DS402 содержат одинаковые наборы параметров, отличающиеся индексами и названиями параметров. Я бы такому заявлению про "одинаковость" сильно не доверял, поэтому и полез разбираться.

Вывод простой: оба профиля имеют одинаковый набор функций, но различаются доступом к ним, поэтому и работать с ними придется по разному. Правда TwinCAT сильно скрывает это. Сконфигурировав дерево проекта, на уровне переменных программы ПЛК, вы с параметрами CAN случайно уже не столкнетесь, только намеренно.

DS402 относится к стандарту IEC61800-7-200 (CiA402). Функционально — это то же самое и полностью совместимое вплоть до машины состояния. Поэтому единственная причина его наличия в прайслисте — это совместимость с чужим оборудованием или для работы в составе чужого оборудования.

Если же говорить о выборе между профилями, MDP742 (Modular Device Profile) — это стандартное представление набора параметров CoE объектов EtherCAT модулей Бекхофф. Именно с таким профилем терминалы поставляются с завода Бекхофф. Получается, что если специальные требования совместимости с DS402 отсутствуют и разработчику без разницы какие там параметры и в каком они порядке следуют, нужно выбирать MDP742. Иначе говоря, если вы не работаете напрямую с параметрами CAN — выбирайте MDP. Если вам все-равно — выбирайте MDP.

Вообще, профиль можно сменить, загрузив в сервотерминал другой профиль. После замены профиля необходимо обновить EEPROM, ESI и не забыть про двигатели. Так как описание объектов CoE и операционный образ (process data) профилей различаются, то необходимо также заменить в проекте XML профиль двигателей.

27 февраля 2019 г.

Быстрый стоп и как линковать биты и булы

Есть кнопка Пуск, а есть Стоп. На минуту представьте себе большую и толстую центрифугу, которая разгоняется в течении нескольких минут. Как прервать выполнение команды NC, когда команда все еще пытается, но еще не достигла результата?

Вообще, в большинство команд NC PTP встроен параметр BufferMode, позволяющий стыковать два задания или, проще говоря, создать плавный переход от одного задания к другому. В том числе и для MC_Halt (останов движения), который может быть вторым в цепочке заданий. К сожалению, проблему это не решает — момент остановки по прежнему непредсказуем, а выглядеть это будет, как заторможенная реакция системы на реакцию кнопки "стоп".

Если же вы смелы и отважны, и вас не пугает останов с последующим сбросом ошибки, то можно кое-как выкрутиться и с помощью MC_Halt, но как-то это всё не красиво и хочется нормального решения без ошибок. Давайте определимся с заданием — необходимо остановить "толстую" центрифугу из любого состояния: не мгновенный стоп за кратчайший промежуток времени, а просто быстрый останов без ошибок, из любого состояния системы, независимо от выполняемой команды и прочее, прочее, прочее.


Fast Axis Stop


Ситуация, в которой останов происходит как можно ранее и желательно быстрее, описана в справочной системе Fast Axis Stop. Правда, в статье не хватает описания некоторых возможных побочных эффектов. А всё потому, что их просто — нет! Всё, дальше можно не читать.

В настройках NC оси есть специальный параметр nState4 типа USINT (он же UINT8 или просто BYTE). Седьмой бит (7..0) этого параметра управляет быстрым остановом. Система у нас дискретная, поэтому перед работой можно и нужно определиться с типом управляющего сигнала: передний фронт, задний, активный/неактивный, отключено/не отключено, ... то есть как и когда будем "дергать" бит управления. Все это задается в настройках NC оси:


Быстрая остановка происходит из любого состояния системы. Не важно что там сейчас, в любое время разгона, торможения, работы, останова и вообще в любых других ситуациях. Работает останов даже в режиме электронного редуктора, когда происходит быстрый останов мастера, но(!) не ведомого: ведомый полностью игнорирует функцию быстрого останова и продолжает полностью подчиняться мастеру.


Линковка переменных


В программе необходимо завести переменную ввода-вывода, через которую будем управлять функцией быстрого останова. Для управления достаточно одноразрядной переменной типа BOOL. Затем связываем переменную с параметром NC: FastStop AT %Q* : BOOL;

Здесь появляется первый нюанс — два бита восьмиразрядного параметра nState4 уже связаны с какими-то там переменными. Вы не увидите этого, пока работаете с виртуальными осями и мгновенно столкнетесь, когда будете работать на реальном "железе". Для проверки я буду использовать дополнительные задачи Additional Tasks или просто Tasks в модном TwinCAT 3, но все-равно учту, что линковать нужно несколько переменных одновременно.

В ветке проекта Tasks, создаем дополнительную задачу с произвольным названием: Add new item... → TwinCAT Task With Image. Под веткой Output переменных создаем переменную типа BOOL или BIT. Позже попробуйте поэкспериментировать с многоразрядными типами типа BYTE или WORD: это тоже интересно в плане частичной линковки переменных и параметров.


Теперь нужно добраться до параметра nState4: Motion → SAF → Axes → Axis N → Drive → Inputs → In. Добавляем линк к трём параметрам сразу, и помогает нам в этом клавиша Ctrl, и ряд параметров фильтра переменных (Show Variables и Show Variable Types):


Главная проблема в том, что параметр nState4 типа BYTE (USINT, UINT8) и, следовательно, он восьмиразрядный, а нам от него нужен всего-лишь один бит. Поэтому при линковке параметров разного типа на экран вываливается специальный диалог Variable Type Mismatch (несовпадение типа переменной) и начинается диалог с разработчиком:


  • Size — размер переменной в битах.
  • Offset — смещение в битах от начала переменной (31..23..15..7..0). Обратите внимание, счет ведется от нуля, поэтому в счете фигурируют не восьмёрки, а семёрки.

  • Own Variable — переменная или параметр, в которую разработчик ткнул мышкой. Есть разница с какой переменной начинать.
  • Linked Variable — переменная, к которой мы будем приклеиваться.
  • Overlapped — перекрытие или сколько бит от переменной нужно взять. Не обязательно пристёгивать все разряды, можно ограничиться каким-то определенным числом разрядов.

nState4 — параметр длиной 8 разрядов. Для линковки с переменной FastStop нам нужен только 7-й разряд, соответственно, для WcState — необходим нулевой разряд, для InputToggle — первый бит. Что и отражено в колонке Offset строки Own Variable.

Картинка с Main.fastStop (правый-нижний угол) дана для наглядности. Именно так, оно бы выглядело в реальной программе.


Стоп с блокировкой


Для останова движения в NC PTP есть два вида функциональных блоков MC_Halt и MC_Stop. Первый просто понижает текущую скорость до нуля; второй же, блокирует все операции на оси до тех пор, пока ось не остановит движение, а затем будет ждать пока разработчик не снимет блокировку. Это надежно, но непонятно, как в этом случае поведет себя функция быстрого останова.

А поведет она себя — как обычно: прервет функцию и как-то по своему быстро остановит ось. И никаких ошибок. Первый график показывает обычный стоп с блокировкой оси:


Горб на правом склоне второго графика, живописно демонстрирует процесс быстрого останова.


Уголок антиквара


В TwinCAT 2 всё аналогично: