October 29, 2018

Введение PowerShell для TwinCAT

В TwinCAT 3 есть штуки, позволяющие автоматизировать рутинные последовательности действий. Дальше как в известной байке с реддита: "Этично ли будет не сообщать работодателю, что я автоматизировал свою работу"?

В текущем проекте мне необходимо периодически перегружать TwinCAT: остановить сервис TwinCAT, запустить сервис TwinCAT, перевести локальный рантайм в режим конфигурации или в рабочий режим. Вообще, не проблема написать все это на C#, но здесь уместнее будет использовать командную строку и какой-нибудь скриптовый язык программирования. Под Linux есть bash и Python. Под Windows они тоже есть, но это противоестественно для его экосистемы. Поэтому будем экологичны — воспользуемся PowerShell 'ом.

PowerShell строится вокруг инфраструктуры .NET как и C#. Он также имеет удобный доступ к COM-объектам, а на них построены множество сервисов TwinCAT. Дальше хвалить не буду, начнем чистую практику. В идеале, я хочу получить кнопку, которая будет перезапускать TwinCAT в один клик. См. иллюстрацию справа →

Перезагрузить TwinCAT несложно — достаточно остановить и стартовать заново системный сервис TcSysSrv. Сделать это можно вручную или через командную строку. Сначала, стоп: net stop TcSysSrv; затем, рестарт — net start TcSysSrv, но после этого TwinCAT запустится в режиме стоп (красная иконка). Чтобы перевести его в какой‑либо рабочий или полурабочий конфигурационный режим, необходимо использовать функции ADS.API. К сожалению, через командную строку они не доступны: нужно запускать System Manager или XAE.


Инсталляция


Запустить консоль PowerShell можно из меню Пуск: клавиша  с окошком Windows → открывается меню Пуск, начинаете набирать powershe... Там же можно запустить встроенный в Windows редактор PowerShell ISE. При запуске от имени администратора, появляется больше возможностей, но и больше дыр в защите вашего ПК.


Чтобы перезапустить сервис TwinCAT можно использовать команду Restart-Service TcSysSrv -Force. Ключ -Force избавит от лишних вопросов. Причем, заметьте, не просто остановить или запустить, а одной командой остановить и перезапустить. Уже удобнее, чем было, но есть одно "но"(!) — перед запуском необходимо разобраться с правами на запуск командлетов-скриптов PowerShell: Get-ExecutionPolicy и Set-ExecutionPolicy. Погуглите по русски или читайте справочную систему на английском-немецком Check the Powershell Cmdlet Execution policy, это домашнее задание.

Теперь осталось разобраться с функциями ADS.API. Для них есть NuGet-PowerShell пакет TcXaeMgmt. Перед использованием, его необходимо установить командой Install-Module -Name TcXaeMgmt, соглашаясь [Y] со всем, что предложат:


Windows PowerShell
(C) Корпорация Майкрософт (Microsoft Corporation). Все права защищены.

PS C:\Windows\system32> Install-Module -Name TcXaeMgmt

Для продолжения требуется поставщик NuGet
Для взаимодействия с репозиториями на основе NuGet модулю PowerShellGet требуется версия поставщика NuGet "2.8.5.201"
или более новая. Поставщик NuGet должен быть доступен в "C:\Program Files (x86)\PackageManagement\ProviderAssemblies"
или "C:\Users\username\AppData\Local\PackageManagement\ProviderAssemblies". Поставщик NuGet можно также установить,
выполнив команду "Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force". Вы хотите, чтобы модуль
PowerShellGet установил и импортировал поставщик NuGet прямо сейчас?
[Y] Да - Y  [N] Нет - N  [S] Приостановить - S  [?] Справка (значением по умолчанию является "Y"):Y

Ненадежный репозиторий
Идет установка модулей из ненадежного репозитория. Если вы доверяете этому репозиторию, измените его значение
InstallationPolicy, запустив командлет Set-PSRepository. Вы действительно хотите установить модули из "PSGallery"?
[Y] Да - Y  [A] Да для всех - A  [N] Нет - N  [L] Нет для всех - L  [S] Приостановить - S  [?] Справка
(значением по умолчанию является "N"):Y
PS C:\Windows\system32>



Командлет


Для переключения режимов работы TwinCAT из PowerShell, используется команда: Set-AdsState -State Config.

Вообще, после установки пакета TcXaeMgmt, вы сможете делать из командой строки практически всё что угодно:... конкретнее, читайте в about_TcXaeMgmt.help.txt. Только помните — так как вы будете влиять на системные сервисы и другие важные штуки Windows, от вас часто будут требовать права Администратора. Их тоже можно выдавать автоматически из скрипта PowerShell.

Итоговый командлет, большую часть которого составляет выделение админских прав, выгляди так:

if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).
        IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 
{
    Start-Process
        -WindowStyle Hidden
        -Verb RunAs
        powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"";
    exit
}

Restart-Service TcSysSrv -Force
Set-AdsState -State Config -Force


Сохраняем скрипт в файл с расширением .ps1. Файл кладем в C:\TwinCAT\3.1\Target\StartMenuAdmin. Здесь же делаем на него ссылку-ярлык и изменяем имя ярлыка на удобное нам название. Оно тут же появится в системном меню TwinCAT, картинка которого была в начале поста.


October 8, 2018

Сторожевой пес для CX8090

Сторожевой таймер (или watchdog, или вотчдог для краткости в дальнейшем) нужен в критических ситуациях. В щите сбора данных — может быть, а вот в станке, я ставлю его необходимость под сомнение. Тем не менее, вопросы типа "боюсь, что программа зависнет" или более самоуверенные — "что, если зависнет контроллер", задают постоянно. В CX8090 такая функция есть.

И была. Изначально. А вот библиотека для работы с ним, появилась не сразу.
Попробуйте не использовать сторожевой таймер в своих проектах. Неправильное применение сторожевого таймера может привести к бесконечной перезагрузке ПЛК, что в результате приведет к выходу из строя контроллера. Это печально само по себе, и вдвойне грустно, потому что случай не гарантийный.

Все испытания проводились на новом ПЛК CX8090, произведенном 2 августа 2018 года.

История библиотеки


Я беру с полки CD-диск c официальным дистрибутивом TwinCAT 2 за 2013 год и открываю исходный код библиотеки TcSystemCX80xx.lib, предназначенной специально для CX80xx. Функции вотчдога в ней еще нет.

Диск за 2014 год — функции все еще нет. Версия библиотеки 1.0.2. Да собственно чего тянуть, функция была добавлена только в версии 1.0.3, но на диск попасть не успела или просто тестировалась какое-то время. Вот комментарий разработчика:

2014/02/17 | 1.0.3 | V2.11.0 (Build 2239) | ICH | adding F_CX80xxSetWatchdog


Мне, к сожалению, не удалось найти старые библиотеки на сайте Бекхофф, хотя я бы с удовольствием покопался в истории промышленного софтостроения. Так что, конкретно указать на версию TwinCAT я не могу. Начиная с версии TwinCAT 2.11 (build 2249) внезапно появляется версия 1.0.6, которая до сих пор лежит на сайте Бекхофф. И это не самая последняя версия, так как с последними дистрибутивами TwinCAT 2 распространяется версия библиотеки 1.0.7.

За все это время функциональный блок вотчдога не изменялся ни разу.


Как спустить сторожевого пса


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

FUNCTION F_CX80xxSetWatchdog : BYTE
VAR_INPUT
    tTimeOut : TIME; (* Watchdog TimeOut Time *)
    bEnable  : BOOL; (* Enable / Disable Watchdog *)
END_VAR

Это функция. Она не требует инстанциирования как функциональный блок. Просто регулярно вызывайте ее с (bEnable = TRUE), когда нужна функция сторожа; или вызовите блок единственный раз с (bEnable = FALSE), если нужно отключить таймер. Другими словами, блок требует регулярного вызова каждый цикл или по крайней мере не реже, чем это задано в параметре tTimeOut.
По идее функции должны быть сосредоточены сами в себе и ни коим образом не влиять на глобальное окружение. В данном же случае, такое применение уместно, так как: (а) функция всегда существует в единственном экземпляре и всегда требует ввода двух обязательных параметров; (б) другие программные единицы TwinCAT 2 так не умеют. В TwinCAT 3 мы могли бы попробовать шаблон Singleton.
На входе функции всего два параметра: (1) время реакции tTimeOut типа TIME, который может принимать значения в диапазоне от 500 миллисекунд (не меньше) до 127 секунд (не больше); (2) и параметр активациия/деактивации таймера bEnable типа BOOL. Куда уж проще.

После вызова функции, с целью активации сторожевого таймера (bEnable = TRUE), сразу же, в этом же цикле, начиная со следующей строчки после вызова функции, начинается контроль выполнения программы. Теперь, если программа зависнет и цикл не завершится в отведенное ему время, или если за цикл произойдет критическая ошибка, или сработает точка останова(!), если вы просто забудете регулярно вызывать функцию вотчдога — таймер начнет свой отсчет...

...и через заданное время tTimeOut — ПЛК перезагрузится.

После перезагрузки, ПЛК сделает вид, что ничего не произошло. Он будет считать, что загрузился штатным способом и запустится так, как было задано в его текущей системной конфигурации. Если у вас активирован загрузочный проект (boot project), то ПЛК-программа стартует автоматически, запустит вотчдог и, если ваша программа продолжит зависать, то контроллеру наступит бесконечный уроборос: зависание, перезагрузка, автозагрузка программы, зависание...

Из-за постоянной круговерти с загрузкой-переагрузкой, ПЛК рано или поздно может выйти из строя. Почему? Например, потому что скидывает на флэшку PERSISTENT данные или остаются незакрытыми файловые операции операционной системы, или любая другая причина. Не используйте вотчдог, пишите программы аккуратно.

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

(* return enable state *)
nRetVal.0           := bEnabled;
nRetVal.1           := bMinWDTimeAct;
nRetVal.2           := bMaxWDTimeAct;
F_CX80xxSetWatchdog := nRetVal;

Далее номера битов в байте выхода:
  • бит 0 — установлен, когда таймер запущен.
  • бит 1 — установлен, когда время таймаута меньше или равно 500 миллисекундам.
  • бит 2 — установлен, когда время таймаута больше или равно 127 секунд 500 мс.


Что внутри будильника?


Заглянем в недра тундры. Вотчдог хранит время таймаута в 500 миллисекундных интервалах. 127 секунд и 500 миллисекунд или 127 500 — это как раз 255 раз по 500. Затем это значение сдвигается на восемь бит "влево" в старший байт, а в младшем байте выставляются биты разрешения или запрещения.

Выбор таких временных интервалов в 500 мс возможно связан с "медленным" и неточным таймером вотчдога в архитектуре ARM9, который непосредственно встроен в железо. Медленный он в целях энергосбережения и потому что работает сам по себе, обходится без "точных кварцев" и других дорогостоящих электронных компонентов. Это очень топорное и упрощенное объяснение, но его вполне достаточно для текущего уровня поста.

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

Интересно, что вотчдог отключается не каким-то там специальным битом (его установкой или сбросом), а установкой максимально возможного значения временного интервала = 127с 500мс. Именно это происходит, когда мы вызываем функцию со сброшенным флагом разрешения (bEnable = FALSE).


Практика


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

PROGRAM MAIN
VAR
    state, cnt : INT;
    z : int := -1;
END_VAR

(*[...]*)

F_CX80xxSetWatchdog(tTimeOut := T#3s, bEnable := TRUE);

CASE state OF
0:
    F_CX8090_LED_WD(eMode := eLED_RED_OFF);
    F_CX8090_LED_ERR(eMode := eLED_RED_OFF);

    z := z + 1;
    state := 10;
    
10: (* breakpoint *)
    ;
    
20: (* zero division *)
    z := state / z;
    
30: (* inf. loop *)
    F_CX8090_LED_WD(eMode := eLED_RED_FLASHING_200ms);
    WHILE TRUE DO
        ;
    END_WHILE
    F_CX8090_LED_WD(eMode := eLED_RED_OFF);
END_CASE

cnt := cnt + 1;


Помигаем светодиодом на прощанье


На корпусе CX8090 есть индикатор WD (=Watchdog), который в документации заявлен как не используемый в прошивке для CX8080 / CX809x. Тем не менее у нас есть механизм для управления этими лампами: функция F_CX8090_LED_WD управляет светодиодом WD, F_CX8090_LED_ERR — управляет светодиодом ERR (для ошибок). Доступны несколько режимов свечения и мигания с различной скважностью: покопайтесь в типах данных библиотеки.

Касательно отключения индикаторов, то оба индикатора двухцветные. Поэтому не важно какой цвет индикатора выключать: красный (eLED_RED_OFF) или зеленый (eLED_GREEN_OFF), отключается сразу весь светодиод, то есть оба цвета свечения пропадают одновременно.

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

F_CX8090_LED_WD(eMode := eLED_RED_FLASHING_200ms);

WHILE itHaveToContinueThen DO
    ; (* что-то делаем в цикле *)
END_WHILE

F_CX8090_LED_WD(eMode := eLED_RED_OFF);


И еще раз, постарайтесь не использовать сторожевой таймер.

October 4, 2018

Веб-конфигуратор для CX8090

Последнее время Бекхофф встраивает в свои контроллеры веб-интерфейс для начальной настройки ПЛК. Это хорошо, потому что через ADS-роутер можно кое-как выяснить IP и AmsNetId -адреса ПЛК, но для запуска FTP-сервера или сервиса удаленного доступа — этого уже мало. В умный по сути коплер CX8090 просто некуда воткнуть святую троицу монитор-клавиатура-мышь. Разъемы для них не предусмотрены и не поддерживаются. Надо изгаляться... надо было, так как теперь все это делается через веб-интерфейс. По крайней мере первый шаг, который, как известно, чего-то-там для человечества.

Находим контроллер через TC2 System Manager или роутер TwinCAT 3: иконка TC3 в системном лотке (traybar) → Router → Edit Routes → TwinCAT Static Routes. В колонке Address видим IP-адресс ПЛК. Открываем любимый браузер. Для разнообразия, и чтобы показать, что никакой разницы нет, я взял нелюбимый в народе Edge.

Вводим в адресную строку: IP-адресс-ПЛК/config. Логин по умолчанию: webguest, пароль: 1.



Есть на что посмотреть и с чем поработать. И сразу же, чтобы не забыть потом, включаем удаленный доступ для работы с ПЛК через CERHost. А дальше уже не проблема запустить FTP-сервер, "залить" русские шрифты и поправить настройки в реестре Windows CE.


September 2, 2018

Работа с CAM-профилями

Есть ли возможность корректировать CAM-профиль (таблицу кулачкового взаимодействия) на лету? Например, когда ПЛК-программа подставляет новые точки в профиль или изменяет уже имеющийся профиль прямо в процессе отработки.

Да, можно. В процессе отработки профиля, можно изменять не только координаты точек траектории, но и скорость, ускорение и рывок (jerk). Подставлять новые или удалять уже имеющиеся точки — нельзя, но можно исключить имеющиеся точки из траектории. В дальнейшем система будет игнорировать их, что будет выглядеть, как будто мы их удалили.
MC_MotionFunctionPoint .PointType : MC_MotionPointType := MOTIONPOINTTYPE_IGNORE
По мере необходимости, скрытые точки можно реактивировать заново, что можно расценивать, как добавление точек в траекторию. Необходимо заранее предусмотреть максимально необходимое число точек.

А что, если в ПЛК уже сидит какая-то сконфигурированная CAM-таблица, но мы хотим иметь возможность корректировать ее, то есть изменять профиль, причем, не залезая в конфигуратор и не реактивируя конфиг? Например, необходимо организовать полный доступ к CAM-таблице через интерфейс пользователя.

Новые профили (CAM-таблицы) можно создавать из ПЛК-программы в любое время, в том числе и во время исполнения текущей CAM-таблицы. Можно создавать таблицы в редакторе профилей, встраивать их как образцы в конфигурацию, а затем в рантайме редактировать их из ПЛК-программы. Также можно "на лету" переключаться между профилями/таблицами или запускать их друг за другом, выстраивая сложную траекторию из отдельных кусков простых.

Вообще, система не различает таблицы созданные в редакторе и созданные вручную из ПЛК-программы. Система различает таблицы по их номеру CamTableID : MC_CAM_ID. По сути, этот номер просто синоним для числа типа UDINT, который отражает номер таблицы среди всех доступных таблиц. Системе всё равно, где была создана таблица.

TYPE
    MC_CAM_ID : UDINT;
END_TYPE


Возникает вопрос, а нужен ли вообще редактор Cam Design Tools или можно как-нить обойтись и сэкономить? Как-нить можно, но ругают его в основном за стоимость.

Редактор крут и наворочен. Позволяет одним махом нарисовать и отредактировать не только кривую траекторию, но и задать скорости, ускорения, рывки в узловых точках, по сути доступен полный набор производных движения. Траекторию можно сглаживать всякими полиномами пятой степени и загружать их одним кликом, вместе с остальным проектом. Стоит это все 4,5К евро, что для многих ставит жирный крест на рисовании траекторий кулачковых механизмов, отправляя их в пешее путешествии за таблицами в Экселе.

Если нет лицензии на редактор, то таблицы из CAM-дезигнера будут доступны только до первого закрытия студии или PLC Control'а. После повторного открытия конфигурации или проекта, этих таблиц в проекте уже не будет — они пропадут. Аналогично, без лицензии эти таблицы не захотят выгружаться в контроллер. Рисовать можно, грабить караваны координат и тащить их в казематы Экселя — можно, сохранять проект для потомков или в ПЛК-задачу — нельзя.

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


Создание CAM-таблиц


Если вы создаете CAM-таблицу вручную, то вам необходима функция MC_CamTableSelect. Данные, предоставленные вами в функцию, передаются в NC, где создается новая CAM-таблица. Эта функция не нужна, если вы создали таблицу в дизайнере: можно сразу переходить к MC_CamIn.

Чего нет в документации, так это фразы, что функция CamTableSelect по сути создает новую таблицу. Если же таблица с таким номером уже существует, то старая таблица будет удалена и на ее месте создана новая. Вот кусок, отвечающий за этот процесс (находится в библиотеке TcMC2_Camming). Для удобства, я выделил ключевые слова:

(* delete old table (possibly existing)*)
STATE_INTERNAL_DELETE :
    fbAdsWrite( NETID   := TcMcGlobal.NCNETID_TCMC_CAM,
                PORT    := TcMcGlobal.NCPORT_TCNCCAMMING_TABLEFUNCTION,
                IDXGRP  := TcMcGlobal.Table.Functions.IDXGRP + CamTableID,
                IDXOFFS := TcMcGlobal.Table.Functions.IDXOFFS.DELETETAB,
                LEN     := 0,
                SRCADDR := 0,
                WRITE   := TRUE,
                TMOUT   := TcMcGlobal.tADSTimeOut);

    IF NOT fbAdsWrite.BUSY THEN
        IF NOT fbAdsWrite.ERR THEN
            (*next step*)
            iStateInternal := STATE_INTERNAL_CREATE;
        ELSE

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


Активация профиля и наоборот


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

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

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

Если оси двигались до процесса расцепления, то после расцепки они сохранят свою скорость движения, но уже как независимые оси (например, остановка одной оси никак не повлияет на другую ось). Такое поведение очень похоже на поведение блока MC_MoveVelocity.


Последовательность выполнения функций


Для примера, простая последовательность действий выглядит как-то так:
  1. MC_CamTableSelect( CamTableID
  2. MC_SetCamOnlineChangeMode
  3. MC_Power (TRUE, Master
  4. MC_Power (TRUE, Slave
  5. MC_CamIn
  6. MC_MoveVelocity( Master
  7. ...технологический процесс
  8. MC_CamOut
  9. MC_Halt( Master
  10. MC_Halt( Slave
  11. MC_Power (FALSE, Master
  12. MC_Power (FALSE, Slave


Изменение параметров движения


Прежде, чем изменять параметры движения, то есть вносить изменения в CAM-таблицу, нужно договориться с системой — как и когда вы будете это делать. Для переговоров используется функция MC_SetCamOnlineChangeMode. Достаточно выполнить ее один раз с необходимым набором параметров. Например, задать ActivationMode = MC_CAMACTIVATION_ASSOONASPOSSIBLE и данные будут применяться на лету, как можно быстрее и в самый безопасный момент движения.

Перед изменением можно прочитать существующий параметры (если они уже загружены) с помощью функции MC_ReadMotionFunction. Функция прочитает все данные таблицы, либо только заданное количество, а если вы зададите больше, чем есть на самом деле, то не страшно — функция прочитает только то, что будет доступно, ошибок не будет.

Для доступа к данным отдельной конкретной точки, используется функция MC_ReadMotionFunctionPoint. Данные содержат не только координаты, но и много другой полезной информации. Начнем с PointIndex — это порядковый номер (индекс) точки в траектории. Индексы положительны, начинаются с 1 и далее идут без разрывов, с единичным шагом: 1, 2, 3, 4, 5, ... и никаких нулей для стартового индекса.

Индексы последовательны, но порядок обхода точек — произвольный. За это отвечает поле RelIndexNextPoint — это относительный индекс следующей вычисляемой точки траектории. По умолчанию, он равен 1, что означает — выполнять по порядку следования точек. Для последней точки в траектории относительный индекс = 0. Изменять относительный индекс "на лету" нельзя, так как этот параметр важен для предварительного обсчета траектории.

И последнее, но не менее важное поле, FunctionType. Оно задает функцию движения для формирования траектории и расчета промежуточных точек. По умолчанию, используется MOTIONFUNCTYPE_POLYNOM5_MM — гладкая траектория с интерполяцией полиномами 5-й степени. В дизайнере этому соответствует функция Automatic. Типу же Synchron соответствуют сплайны первой степени MOTIONFUNCTYPE_POLYNOM1.

Учитывайте, что при использовании MOTIONFUNCTYPE_POLYNOM5_MM (или Automatic) необходимы как минимум три активные точки в куске траектории, то есть нужна сама точка и по одной точке с каждой стороны от нее. Итого, три точки в куске. Точка считается активной, если она не игнорируется PointType <> MOTIONPOINTTYPE_IGNORE. Это важно, если вы используете механизм, описанный в самом начале поста.


Практикум


Я создал замкнутый, повторяющийся каждые 360° профиль. Для простоты, траектория разбита пятью точками на четыре кусочно-линейные интервала движения. Точка №5 совпадает с точкой №1. Не забывайте, вы ставите точки, а траекторию между ними рассчитывает система.



На самом деле, я сначала нарисовал график в дизайнере, а затем вручную перетащил данные в ПЛК-программу (см. Приложение ниже). В итоге, для подчиненной оси получился следующий график:


До позиции 21000 подчиненный отрабатывает заданный в CAM-таблице график. Я отрезал на картинке "пики" позиций, чтобы не загромождать иллюстрацию.

После 21000 я программно, с помощью MC_WriteMotionFunctionPoint, ставлю в игнор точку 2: следите как изменился график от 21000 до 23500. Начиная с 23500 (вторая зеленая линия), я ставлю в игнор точку 3: с 23500 по 25000 работают только точки 1, 4, 5.
Учтите, что непосредственно точки я не удаляю, а всего-лишь изменяю их тип на PointType := MOTIONPOINTTYPE_IGNORE.
После позиции 25000 я возвращаю сначала точку 2, а где-то ближе к 28000 и точку 3. Затем все повторяется. Прогоните в голове эту последовательность несколько раз и всё встанет на свои места.


Приложение


PROGRAM MAIN
VAR
    AxMaster         : AXIS_REF;
    AxSlave          : AXIS_REF;
    camTable2_Id     : MC_CAM_ID := 2;
    camTable2        : MC_CAM_REF;
    camTable2_Points : ARRAY[1..10000] OF MC_MotionFunctionPoint;
    
[...]

camTable2.ArraySize   := SIZEOF(camTable2_Points);
camTable2.pArray      := ADR(camTable2_Points);
camTable2.TableType   := MC_TABLETYPE_MOTIONFUNCTION;
camTable2.NoOfColumns := 1;
camTable2.NoOfRows    := pointsNumber;

FOR i := 1 TO PointsNumber DO
    camTable2_Points[i].PointIndex := i;
END_FOR

camTable2_Points[1].PointType         := MOTIONPOINTTYPE_MOTION;
camTable2_Points[1].FunctionType      := MOTIONFUNCTYPE_POLYNOM1; // 5_MM;
camTable2_Points[1].RelIndexNextPoint := 1;
camTable2_Points[1].MasterPos         := 0.0;
camTable2_Points[1].SlavePos          := 0.0;

camTable2_Points[2].PointType         := MOTIONPOINTTYPE_MOTION;
camTable2_Points[2].FunctionType      := MOTIONFUNCTYPE_POLYNOM1; //5_MM;
camTable2_Points[2].RelIndexNextPoint := 1;
camTable2_Points[2].MasterPos         := 100.0;
camTable2_Points[2].SlavePos          := 10.0;

camTable2_Points[3].PointType         := MOTIONPOINTTYPE_MOTION;
camTable2_Points[3].FunctionType      := MOTIONFUNCTYPE_POLYNOM1; //5_MM;
camTable2_Points[3].RelIndexNextPoint := 1;
camTable2_Points[3].MasterPos         := 150.0;
camTable2_Points[3].SlavePos          := 100.0;

camTable2_Points[4].PointType         := MOTIONPOINTTYPE_MOTION;
camTable2_Points[4].FunctionType      := MOTIONFUNCTYPE_POLYNOM1; //5_MM;
camTable2_Points[4].RelIndexNextPoint := 1;
camTable2_Points[4].MasterPos         := 250.0;
camTable2_Points[4].SlavePos          := 10.0;

camTable2_Points[5].PointType         := MOTIONPOINTTYPE_MOTION;
camTable2_Points[5].FunctionType      := MOTIONFUNCTYPE_POLYNOM1; //5_MM;
camTable2_Points[5].RelIndexNextPoint := 0; // последняя точка в траектории
camTable2_Points[5].MasterPos         := 360.0;
camTable2_Points[5].SlavePos          := 0.0;


На самом деле, красивее и проще это сделать через массивы или, что еще лучше, через чтение .csv файлов, содержащие данные CAM-таблицы.

August 29, 2018

Вебинар. Интеграция полевых устройств HART через FDT

Не прошло и полугода с момента, когда 10 апреля этого года Бенджамин Брунц и Лауриц Ветцель провели вебинар о подключении устройств HART через FDT. В том числе был небольшой практикум, где в живую показали "как это работает".

Незаметно для нас всё это встроено в TwinCAT. Вы с этим могли встречаться, если были замешаны в перерабатывающей промышленности (Processing Industry, это где одни вещества превращают в другие, а не где рабочие обязаны работать сверх нормы). Как раз в этой промышленности активно используются полевые устройства HART. Небольшую вводную я давал в описании синих, холодных и многобезопасных модулей.

HART — Highway Adressable Remote Transducer. Аналоговый сигнал 4..20мА (рекомендация NAMUR NE43). Широко используется в перерабатывающей промышленности. Позволяет совмещать аналог и цифру, то есть одновременно передавать пропорциональный аналоговый сигнал (амплитуда) и транслировать дискретные цифровые данные (с помощью FSK = Frequency Shift Keying) полнодуплексно и в обе стороны.

DTM — Device Type Manager. Чем-то похож на драйвер устройства. Обеспечивает двусторонний обмен данными между полевыми устройствами (датчиками там всякими) и ПЛК. Он же отвечает за конфигурацию устройства.

FDT — Field Device Tool. Определяет интерфейс и обеспечивает общение между DTM и прикладным уровнем программного обеспечения.

Для работы понадобится TwinCAT 3 build 4022 или новее. Для более старых версий TwinCAT необходимо установить HART-плагин, который можно получить, обратившись в тех. поддержку Бекхофф; ключевые слова: FDT контейнер + Beckhoff ComDTM (PACTware).

Преимущества:
  • Можно использовать существующие кабели рассчитанные на 4..20мА.
  • Двусторонняя, полнодуплексная связь устройств.
  • Возможность простого конфигурирования устройства через DTM.
  • Диагностика устройства и расширенная информация поступающая от устройства (если поддерживает).

Недостатки:
  • Требует дополнительных усилий на изучение.
  • Требуется дополнительное оборудование: соответствующее полевое устройство + модуль расширения (terminal).
  • Низкоскоростная передача: 500-800 миллисекунд на цикл. Правда скорость здесь не особо важна, так как главная цель — это целостность данных и полноценный контроль за целостностью данных.

Бекхофф официально входит в FDT группу, поэтому в прайс Бекхоффа входят EtherCAT терминалы с поддержкой HART: EL3182, ELX3181, ELX4181. Если интересуетесь подробностями HART — почитайте документацию этих модулей, там много интересного. Например, кратко, что из себя представляет модуль EL3182 — это 2-канальный аналоговый вход, 16 бит, ±107%, NAMUR NE43, HART; опционально настраивается через HART-плагин, есть FDT контейнер при использовании Beckhoff ComDTM.


Практическая часть


После сканирования шины и боксов, HART терминалы будут выглядеть как обычные модули расширения (например, как обычные аналоговые входа). Различия проявятся в расширенных настройках — в правой части экрана появятся две новые закладки HART и FDT.


FDT позволяет привязать DTM-драйвер устройства к заданному каналу. HART — настроить настройки. Все выполняется очень просто: сканирование, перетаскивание, выбор параметров из списка. Сложности это не представляет, и хорошо показано на вебинаре (знание английского не требуется).

Циклическая и синхронная передача данных в ПЛК-программу настраивается в закладке HART. Мы можем выбрать активный канал (Active Channel), затем перейти во вкладку отображения измеряемых величин (Measured Values Display) и поставить там галку —  циклически передавать данные (Cyclic Process Data). В конфигурации, рядом с веткой Ch.1 AI Inputs, получим новую длинную ветвь — Ch.1 HART Inputs. Эта ветка содержит данные, получаемые от HART-датчика. Линкуем эти параметры с переменными ПЛК-задачи и циклически получаем свежие данные. В описании терминала EL3182, есть раздел Measured values, где все это описано.

Если есть желание получать данные от случая к случаю, то есть асинхронно и когда захочется — существует сервис ADS: IdxGrp = 0xF302; IdxOffs = код команды. Эта информация также есть в описании модуля расширения, в разделе Acyclic services.


Полный вебинар на английском языке: Integration of HART field devices via FDT.

August 23, 2018

Очень длинный кейс

В продолжение темы экономии на абстракциях, я заметил необычное поведение отладчика TwinCAT 2 при пошаговом прохождении оператора CASE.

Программист, используя оператор CASE, заменяет спагетти из "ифов" (IF's) на красивую таблицу из структурированных блоков кода. Выполнение этих блоков зависит от значения одной единственной переменной. Именно так мы строим машину состояний, конечный автомат или просто разбиваем большой код на небольшие куски кода.

Мы ожидаем, что при входе в кейс, будет прочитано значение переменной, затем будет выполнен шаг с номером, хранящимся в этой переменной. Под дебагером же происходит нечто странное: происходит вход в кейс, а затем отладчик проходит по каждому шагу кейса, пока не дойдет до заданного шага и здесь, ожидаемо, начнет выполнять блок кода. Остальные участки кода он слава богу игнорирует, в том числе и те, что лежат условно "ниже" текущего шага (по тексту программы), но что если у нас кейс состоит из 1000 шагов? А то, что он проскачет по всем шагам, пока не достигнет нужного, и мы уже видели, что даже пустые вызовы подпрограмм, налагают дополнительную нагрузку на процессор ПЛК. Это поведение стоит проверить.

(* Я вынес анимированные иллюстрации в конец поста, чтобы не мелькали перед глазами. *)

Так как замерять производительность нужно на большом количестве вызовов и написать пару тысяч строк мне сложно и просто лень, да и времени на это жалко — я накидал программу на C#, которая генерирует другую программу, но уже на ST. Остается только скопировать из сгенерированного текстового файла output.txt код ST-программы, а затем вставить его в проект.

using System.IO;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var writer = new StreamWriter("output.txt")) {
                writer.WriteLine("CASE i OF");

                for(int i = 0; i < 65000; i++)
                    writer.WriteLine($"{i}: c := c + 1;");

                writer.WriteLine("END_CASE");
            }
        }
    }
}


Выводы

  1. Количество шагов оператора CASE никак не влияют на производительность. Во время выполнения программы, при входе в оператор CASE, управление сразу же передают в текущий шаг кейса. Никаких посторонних прыжков не происходит. Это исключительно заморочки режима отладки, причем, только в TwinCAT 2.
  2. Не надо писать гигантские кейсы из 100500 шагов — отладчик падает, а проект собирается два часа.
  3. В TwinCAT 3 все нормально и с отладкой, и с производительность. Отладчик непосредственно переходит к исполняемому в данный момент шагу, игнорируя остальные блоки. Но отладчик все равно не переваривает 100500 шагов кейса и падает.
  4. Удобство языка программирования ST в его текстовости и в том, что вы можете генерировать его программы с помощью других языков программирования: C#, VB, Python, JS, ... автоматизируя рутинные действия. Похожий прием я использовал в проекте Tc3_PcSpeaker для работы со звуковой-пищалкой x86 контроллеров. Только программа была написана на питоне. 

TwinCAT 2




TwinCAT 3



August 22, 2018

Экономия на абстракциях

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

Что если разбивка на множество подпрограмм привносит дополнительную нагрузку на процессор ПЛК и снижает быстродействие?

Быстродействие важно и любые телодвижения в сторону абстракций, приводят к увеличению количества вызовов подпрограмм (функций, методов, других кусков кода). А ведь нам нужно успеть уложиться в миллисекунды базового времени (Base Time, в первую очередь), и только затем улучшить сопровождаемость (когда-нибудь потом, но лучше прямо сейчас).

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

Итак, исследуем влияние количества вызовов подпрограмм на загруженность процессора. Проверять TwinCAT 2 мне не очень-то и хотелось, но я проверил и не пожалел.

PROGRAM MAIN
VAR
    i: UINT;
END_VAR

FOR i := 0 TO 65000 DO
    PRG2();
END_FOR

(* ********************** *)

PROGRAM PRG2
VAR
    c : UINT;
END_VAR

;
// c := c + 1;
// c := c + 1;

Основная программа MAIN каждый цикл совершает ряд вызовов подпрограммы PRG2. Количество вызовов определяется количеством итераций цикла. Их максимальное число равно 65000 — как в примере выше. Так как нас в основном интересует нагрузка от ветвления программы, то подпрограмма PRG2 пустая. Точнее, она состоит из единственного оператора точка-с-запятой (;). Чтобы не было совсем банально, я разбавил подпрограмму парой строк со счетчиком (и это было правильным решением).

По вертикали (Y) — нагрузка процессора в процентах, по горизонтали (X) — количество вызовов подпрограммы за время одного цикла:
Ничего необычного: чем больше вызовов, тем больше нагрузка на процессор. При этом нужно учесть два факта: первый — подпрограмма PRG2 ничего не делает и нагрузка растет только за счет количества передачи управления из одной программы в другую; и второй — тест синтетический и в реальных ситуациях такого количеств вызовов за один цикл не бывает.

Теперь проверим TwinCAT 3:
...и он показывает более линейную зависимость.

Теперь об аномалиях второй версии TwinCAT. В подпрограмме PRG2 я убрал пустой оператор ";" и раскомментировал одну строку со счетчиком. Зафиксировал количество вызовов на числе 65000. Замерил нагрузку. Раскомментировал вторую строчку со счетчиком (теперь их две). Замерил нагрузку и впал в задумчивость.

Итак, внимание! С одной строкой счетчика, нагрузка составила 72%. С двумя строками счетчика — 43%. Больше кода — меньше нагрузка! Объяснить такое поведение можно только работой некоего оптимизирующего звена в компиляторе кода, но такая неявность, явно вводит в заблуждение. Я немедленно проверил то же самое в TwinCAT 3...

...и он по прежнему оказался линеен и предсказуем: 1 строка — 56%, две строки — 58%.


Выводы

  1. Вызовы подпрограмм добавляют нагрузку на процессор.
  2. Я решил, что не буду обращать на эту нагрузку внимание, так как ощутимой она становится только при каких-то очень больших числах вызовов за время одного цикла. Даже для CX8xxx серии.
  3. TwinCAT 3 стабильнее и предсказуемей, чем его вторая реализация.
  4. Upd. 29 мая 2019 года, TwinCAT 2 v2.11.2302 странности с "меньше кода - больше нагрузка" не наблюдаются. Возможно был баг с выравниванием переменных в памяти. 


P.S.: посчитаем вызовы


А как вообще прикинуть — сколько вызовов происходит в проекте?

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

Для начала, открываем проект и экспортируем его в единый файл экспорта *.exp. Затем, открываем EXP-файл в текстовом редакторе (Notepad++) и автозаменой удаляем все вхождения следующих строк: (*STRING(, IF (. Файл в итоге будет испорчен, но для нас важнее исключить посторонние элементы. Остается сделать глобальный поиск символа открывающей скобки (, который традиционно используется при вызове подпрограмм. Как вариант, поискать закрывающие скобки, а точнее конец вызова функции, что-то похожее на );

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


На картинке выше подсвечены не все скобки, но они учтены и подсчитаны правильно.

В итоге, я нашел 560 вызовов с открывающими скобками и 433 с закрывающими. Это в два раза меньше 1000 (1-2% CPU, как в пустой программе) и совсем далеко от 65000 (56-58% CPU). Еще раз, это максимум, который можно вызвать, но который никогда не будет достигнут: программа разбита на шаги (машина состояний), в каждом из которых выполняется лишь небольшая часть всей технологической программы.

August 10, 2018

Виртуальная машина с Windows CE и TwinCAT

Когда под рукой нет "реального железа" — можно обойтись локальным рантаймом. Когда нужен рантайм под Windows CE (WEC, Embedded Compact) — мы идем за виртуальной машиной. В моем случае — это Oracle VirtualBox. Бекхофф же предлагает образ под Virtual PC и нам это только на руку.

Качаем с сайта Бекхофф образ виртуальной машины для Virtual PC. Образов несколько, они предназначены для различных версий TwinCAT (2, 3) и WEC (6, 7). Я выбрал под WEC 7 с TwinCAT 2.11R3 NCI(!): Beckhoff_VPC_WEC7_HPS_v502a_TC211R3_B2247.zip. Полагаю, что для остальных версий ситуация схожая (upd 24 авг 2018: да, для WEC7 TC3.1 всё аналогично).
Сходу образ не запустился. Пришлось обновить Virtual Box версии 5.1 до последней версии 5.2.16. Возможно проблема исключительно в моих системных настройках, но я предупредил.
Какие настройки Virtual Box необходимо задать? Кстати, не обязательно такие, просто эти проверены, работают и взяты из оригинального образа Virtual PC:

Name (произвольное название): WinCE 7
Operationg System: Other Windows (32-bit)
Base Memory: 512 MB
Video Memory: 8MB (virtualbox будет ругаться, что этого мало для полноэкранного режима)
Network Adapter →
        Attached to: Bridget Adapter
        Advanced → Adapter Type: Intel PRO/1000 MT Desktop (82540em)

Главное правильно задайте сетевой адаптер. В дальнейшем он понадобится для связи с внешним миром, а в качестве жесткого диска используйте .vhd файл из образа виртуальной машины. Выберите при создании пункт: Use an existing virtual hard disk file.


После этого у вас должна нормально запуститься виртуальная машина с Windows Embedded Compact 7 и TwinCAT 2.11R3 NCI. Но(!) без сетевой карты. Печально, что инженеры подложили нам двух свиней: во первых, это специальная версия TwinCAT, которая будет останавливаться каждый сутки (TC Daily Drop); во вторых, не установлены сетевые драйверы, которые позволили бы нам подключить периферию, да и вообще хоть как-то использовать виртуальную машину в качестве ПЛК. Исправляем.

Нам необходимы драйверы сетевой карты Intel e1000 для Windows CE. Вообще, это редкость и всяческий раритет, но гугл в помощь. Я нашел и выкачал комплект драйверов на сайте HPC:Factor в виде единого файла e1000ce5leg.exe. В целях безопасности, мы не будем запускать исполнимые .exe файлы из неизвестных источников (нельзя собирать грибы в незнакомом лесу), а просто распакуем его с помощью 7zip. Ссылка на перепакованный архив в подвале статьи. На поверку, это оказался обычный .cab архив, завернутый в MSZip. Осталось закинуть полученные файла на виртуальную машину.

И теперь мы воспользуемся преимуществом, подаренным нам виртуальной машиной Virtual PC. Образ жесткого диска от виртуальной машины, мы просто подключим к обычной настольной операционке. У меня Windows 10 Home, как дела с более старыми версиями — подсказать не могу.
7-zip версии 18.05 умеет распаковывать .vhd архивы, но не умеет добавлять в них файлы. Возможно другие архиваторы умеют это делать, и вам не понадобятся следующие телодвижения с подключением виртуальных дисков.
Итак, нам нужно "Управление дисками". Правой кнопкой по меню Пуск → "Управление дисками" или правой же кнопкой по Этому компьютеру → "Управление", а дальше разберетесь.

В управление дисками выбрать "Действие" → присоединить виртуальный жесткий диск.

Создайте в корне диска какой-нибудь каталог (temp, net, drivers, произвольные латинские буквы-цифры... главное, название не забудьте). Пусть будет netdrivers. Скопируйте в этот каталог содержимое архива e1000ce5leg.zip. В списке дисков кликните правой кнопкой по виртуальному диску → отключить виртуальный диск. Теперь можно запускать виртуальную машину.

Доступа к сети все еще нет, но теперь у нас есть драйверы. Запустите проводник: Start → Run... → explorer. Перейдите в каталог drivers, выделите все файлы → Edit-Copy. Перейдите в \Hard Disk\System → Edit-Paste. Запустите файл \Hard Disk\System\E1000CE5.reg для активации драйверов. Система предложит перезагрузиться → соглашайтесь.

После загрузки вы получите доступ к локальной сети и все соответствующие плюшки. Можно доустановить недостающие компоненты (подключая-отключая виртуальный жесткий диск), а затем сделать экспорт виртуальной машины и поделиться уже готовым образом через File → Export Appliance... Каталог с распакованными файлами драйвера (netdrivers в моем примере) можно удалить.



Архив с драйверами доступен здесь: e1000ce5leg.zip [77 Кб]

June 5, 2018

Новый билд без ошибок совместимости

Новость недельной давности — Бекхофф выложил новый билд TwinCAT 3.1.4022.20 в котором закрыта ошибка запуска локального рантайма.

Во время установки новой версии, могу посоветовать поставить галку Activate TC Settings. С ним изменится раскладка панелей в Visual Studio (возможно, вам это не понравится), без него — придется ковыряться в дебрях Visual Studio в поисках всех этих кнопок и панелей TwinCAT и вытаскивать их вручную (возможно, это вам не понравится).



После установки нового билда под Windows 10 Home 1803, TwinCAT у меня, несмотря на включенную опцию автостарта сервиса, так и не запустился. Пришлось вручную открыть "Службы" Windows и запустить TcSysSrv (TwinCAT3 System Service). После этого все отлично заработало.

May 25, 2018

Compatibility error Windows 10 v1803

Suddenly, TwinCAT 3.1.4022.16 have got an error of compatibility with the latest update of Windows 10 version 1803. Local runtime of TwinCAT x64 drops down with exception after an attempt to switch into the run mode.


This error occurs both in Home and in Pro versions of Windows 10. The engineering version of the TwinCAT tools still work properly. We are waiting for a new update of TwinCAT.

Ошибка совместимости Windows 10 версии 1803

Свежее обновление Windows 10 до версии 1803 не совместимо с локальным 64-разрядным рантаймом TwinCAT 3.1.4022.16 (или наоборот - TwinCAT не совместим с обновлением Windows).

Тем не менее, после обновления Windows не получится сделать контроллер из локального ПК разработчика: при активации конфигурации с переходом в рабочий режим, рантайм падает с ошибкой и не запускается. Ошибка проявляется на 64-х разрядных версиях Windows 10 как в Home, так и Pro версиях.


Инженерные функции по прежнему работают отлично, можно спокойно конфигурировать и программировать отдельно стоящие контроллеры. Ждем очередного обновления TwinCAT 3.1.

February 18, 2018

История с крышечками

Нельзя просто так взять, и бросить контакты шины неприкрытыми. Для этого существует Bus end cap или Bus end cover — защитная крышка. По сути, это просто кусок промышленной пластмассы, защищающий контакты шины последнего модуля, но есть нюансы.

Во времена перехода от шины K-bus к шине EtherCAT (E-bus), выпускался широкий 12 миллиметровый модуль (Bus End Terminal), похожий на обычный модуль, только без электроники на борту. Заодно обратите внимание на слово Terminal:


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


Соответственно, слева-направо: EL9011, ELM9012 и ELX9012, а в конце пунктирный образец для выпиливания лобзиком и, по совместительству, схема электрическая принципиальная. Несмотря на отсутствие электроники на борту, System Manager как-то эти модули обнаруживает и добавляет в конфигурацию. Черная крышка для измерительных модулей ELM, синяя — для электропожаровзрывобезопасных модулей ELX, серая — для обычных модулей EtherCAT. Желтая крышечка TwinSAFE пока что не существует или еще не прошла сертификацию.

Внезапно, в списке продукции всплывает EL9012, которая выглядит точь-в-точь как EL9011, но если первая называется английским словом cover (укрытие), то вторая всего-лишь cap (крышечка). Какую ставить, и есть ли разница? Давайте сравним...

12-я толще: 8 мм против 5 мм одиннадцатой. Зато 11-я лучше сертифицирована: три сертификата CE, UL, Ex, а у 12-й только один CE. Так как 12-я в три раза дороже 11-й, делаем вывод, что разница в стоимости зависит только от количества пластика. Ну и последнее, 12-я закрывает весь бок терминала целиком и прячет под защиту и шину, и контакты подпитки модулей (cover for power and E-bus contacts), а EL9011 закрывает только контакты шины (cover for the E-bus contacts). Вот и вся разница. Достаточно разместить фото крышек сбоку и разница была бы понятна сразу.

По остальным защитным, разделительным и прочим крышкам можно прочитать в EL9xxx.

February 15, 2018

Categories of TwinCAT Libraries

When you are ready to publish your library, it is a good idea to place it under the one or several categories in the library repository. Later, it helps you and other developers to find your library among the others at the local repository.

But there are no any official category lists – at least, I have not found any others one.

We can extract one or two (maybe more) categories from the one precisely selected library from the local storage at
x:\TwinCAT\3.1\Components\Plc\Managed Libraries. But we should choose carefuly because that source library should contain required categories. Or we can load category list from the "Description File" with *.libcat.xml extension. But (once more) I can’t find this description file – anywhere on local system or in the internet.

Then, I have made this category list myself.



I uploaded this unofficial list on GitHub. You can download latest version from beckhoff-tc31.libcat.xml or you can clone the whole project Tc_LibcatXml to your laptop or to your github repository or to any other place and finally make your library more shiny.

Категории библиотек TwinCAT 3

Предположим, вы разработали библиотеку или портировали чужую, типа библиотеки OSCAT под TwinCAT 3. Наступает ответственный момент оформления библиотеки, вы заполняете графы описания и решаете выбрать категорию библиотеки (Library Categories), чтобы в дальнейшем быстро находить библиотеку в локальном репозитории,

А списка категорий нет.

Можно экстрагировать категории из других библиотек: они лежат в x:\TwinCAT\3.1\Components\Plc\Managed Libraries, а можно загрузить из некоего файла (From Description File...) с расширением *.libcat.xml, но файла также нет. По крайней мере, для оборудования и библиотек Бекхофф, я их так и не нашел.

Поэтому выдавил из библиотек свой собственный и неофициальный список.



Список можно найти на гитхабе, он лежит в открытом виде и готов к использованию — Tc_LibcatXml. По возможности я буду поддерживать его в актуальном состоянии, либо должен найтись способ получать эти категории из официального источника. Сейчас же доступны три не официальные версии — какое-то наследие TwinCAT 2, затем устаревший TwinCAT 3 и актуальная на данный момент версия beckhoff-tc31.libcat.xml.

February 13, 2018

ExST: расширенный ST

Язык ST (Structured Text) получил много полезных расширений, что в последствии назвали Extended ST. И это не только модное ООП. Я свалил все в кучу: и расширения, и просто малоизвестные вещи, к тому же, всё это подходит не только для TwinCAT, но и для CoDeSys-мира вообще. Начнем с мальчика для битья — оператора GOTO...

Язык ST все еще поддерживает оператор GOTO. Не прямо так гоу-ту, а как в ассемблере — JMP куда-то там на метку в коде. Выглядит это так:

label_infinitum:
(* полезный код *)
JMP myLabel_4321;

(* бесполезный кот *)

myLabel_4321:    // это метка для перехода
(* еще полезный код *)
JMP label_infinitum;

Избегайте бесконечно-вечных циклов.


CONTINUE


CONTINUE относится к расширению языка, а EXIT нет. Если вы не знали, то EXIT немедленно прекращает выполнение текущего цикла и выходит из него (текущая итерации FOR, WHILE или REPEAT не будет выполнена до конца). CONTINUE же прекращает текущую итерацию и немедленно переходит к началу цикла, выполнять следующую итерацию:

FOR i := 0 TO 100 BY 2 DO
    IF i MOD 3 = 0 THEN
        CONTINUE; // пропускаем инкремент переменной a и переходим к следующей итерации
    END_IF
    a := a + 1;
END_FOR

Осторожнее с вложенными циклами: EXIT выходит только из одного вложения. Для выхода из нескольких вложенных циклов, помогает команда JMP.

FOR j := 0 TO 100 DO
    FOR i := 0 TO 100 DO
        IF ??? THEN
            EXIT; // завершит цикл FOR i ...
        ELSE
            JMP finita_incantata;
        END_IF
    END_FOR
    // EXIT приведет сюда
END_FOR

finita_incantata:

Оператор MOD находит остаток от деления, как оператор % в C++.


Оператор установки S=


Запись A S= X; аналогична:

IF X THEN
    A := TRUE;
END_IF

Как только A станет равным TRUE, то никакое значение X уже не изменит A. Оператор S= никогда не сможет сбросить A в FALSE.

A        X       A
FALSE S= FALSE → FALSE
FALSE S= TRUE  → TRUE
TRUE  S= FALSE → TRUE
TRUE  S= TRUE  → TRUE


Оператор сброса R=


Запись F R= X; аналогична:

IF X THEN
    F := FALSE;
END_IF

Как только F станет равным FALSE, то никакое значение X уже не изменит F. Оператор R= никогда не сможет установить F в TRUE.

F        X       F
FALSE R= FALSE → FALSE
FALSE R= TRUE  → FALSE
TRUE  R= FALSE → TRUE
TRUE  R= TRUE  → FALSE


Присваивание


Присваивание как выражение (assignment as expression) можно делать так:

IF bVar := (i = 2) THEN
    i := i + 1;
END_IF

bVar — булева переменная, получает значение от выражения (i = 2), то есть будет равна TRUE когда (i = 2). Затем bVar оценивается в IF. Лучше не мудрить и не пихать в одну строку несколько команд сразу. Пишите так, чтобы было проще читать впоследствии. Компилятору все-равно как  вы написали, работать это будет одинаково, а вероятность ошибиться намного выше в сложносочиненной записи:

bVar := (i = 2);

IF bVar THEN
    i := i + 1;
END_IF


Можно присваивать нескольких переменных одновременно:

a:= b:= c:= k + 12;

Но не дайте обмануть себя видом и формой записи — здесь нет присваивания, передающегося справа-налево по цепочке! Аналогичная, но более длинная запись, выглядит так:

a := k + 12;
b := k + 12;
c := k + 12;

Выражение (k + 12) будет вычисляться три раза, поэтому выгоднее записать:

c := k + 12;
a:= b:= c;

Теперь вспомним операторы установки/сброса. Как будет работать следующая строка? Ответ в конце поста.

A S= F R= X;


Типизированные литералы


Звучит жутко умно, но это всего-лишь способ задать точный тип числа.

A := 123; — не говорит нам какого типа число 123 (литерал). Это точно не BOOL и скорее всего не REAL / LREAL (нет десятичной запятой). Возможны SINT, USINT, BYTE, INT, UINT, WORD, DINT, UDINT, DWORD. Так кто же из них?

Обычно тип литерала будет зависеть от типа переменной A. Поэтому можно просто уточнить запись литерала: A := 123.0; и тогда мы получим REAL. Но что если хочется BYTE, а переменная типа UINT?

Для этого тип можно обозначить точно: A := BYTE#127;

Такую запись можно использовать везде, где допустимо использование констант. Если TwinCAT обнаружит потенциальную потерю данных, то выдаст сообщение об ошибке или просто предупреждение, если всё не так страшно.


CASE ... ELSE


Если конкретный шаг CASE для какого-то значения переменной не существует, то CASE игнорируется целиком, за исключением если... Блок ELSE в CASE позволяет задать действие по умолчанию, если номер шага в CASE не задан:

CASE state OF
0:
    DoSomething();
100:
    DoSomething2();
...
2000:
    DoSomethingAgainNAgain();
ELSE
    DontKnowWhatHappenedLastTime();
END_CASE

Если вы знакомы с C++, то это аналогично: switch case ... default.


OR_ELSE


Работает аналогично OR, но если левая часть OR_ELSE уже равна TRUE, то правая часть не проверяется и не выполняется:

VAR
    a, z, b : BOOL;
    i : INT;
END_VAR

z := a OR_ELSE (b := (i = 2));

Если А равно TRUE, то правая часть OR_ELSE не будет выполняться и B никогда не сможет стать TRUE. Можно оптимизировать производительность, исключив лишний вызов функций. И теперь мы знаем, что для "обычного" OR, независимо от значения операндов, вызываются и проверяются обе части: и левая, и правая.


AND_THEN


Как AND, но правая часть AND_THEN обрабатывается только при условии, что левая часть  равна TRUE. Например, это удобно для проверок указателей:

IF (ptr <> 0 AND_THEN ptr ^= 99) THEN ...

Когда указатель не равен нулю и_тогда выполнить сравнение адреса указателя. Это позволяет избежать проблем с нулевым указателем. В обычном AND всегда обрабатываются обе части независимо от их значения.


Ответ


A S= F R= X;

// работает как

A S= X;
F R= X;