- ADS: протокол обмена данными с TwinCAT устройствами (C++). Для разработчиков, которые хотят наладить обмен данными из не-Windows операционных систем с контроллерами Beckhoff. Это исходный код библиотеки ADS Client Library.
- CCAT: драйвер ядра Linux для Beckhoff CCAT FPGA (Си), предоставляющий доступ к EBus интерфейсу CX50x0, CX51x0 и CX20x0.
- BBAPI: драйвер Beckhoff BIOS API Linux (C++). Linux Kernel драйвер для Beckhoff BIOS API, для доступа к информации об аппаратном обеспечении, а также для управления нестандартным "железом" (например, дисплей CX2100).
July 12, 2015
Открытые разработки Beckhoff на GitHub
Появился официальный GitHub аккаунт Бекхофф -- Open Source Projects of Beckhoff Automation GmbH & Co. KG
July 1, 2015
Количество и качества Ethernet-интерфейсов
При выборе CX-контроллера нужно помнить, что бывают контроллеры с одним Ethernet-интерфейсом, а бывает с двумя. Больше, не значит лучше.
Для интереса, посмотрим на контроллеры CX1030, CX2030. Это очень разные контроллеры, хотя артикулы отличаются всего-лишь одной цифрой. У первого два ethernet-разъема, но один сетевой интерфейс, у второго -- два сетевых интерфейса и два сетевых разъема, зато у первого есть встроенный аппаратный ethernet-свитч (hardware ethernet-switch).
Встроенный аппаратный ethernet-свитч на два сетевых разъема позволяет строить цепочку мастер-контроллеров (например, daisy chain) без использования дополнительных устройств.
Два сетевых интерфейса позволяют строить отказоустойчивые системы с резервированием каналов (TwinCAT Redundancy). При необходимости можно включить программный роутинг между интерфейсами, но аппаратная реализация все-равно будет лучше.
Кроме того, два сетевых интерфейса позволяют переключить один из них в режим работы EtherCAT-шины, и ожидаемо получить еще одну EtherCAT-шину (собственно это и происходит в отказоустойчивом режиме). При этом контроллер можно сконфигурировать как с одной шиной (отказоустойчивость, два физических канала связи), так и с двумя независимыми EtherCAT-шинами.
Еще раз, если есть требование в отказоустойчивости, то необходимо выбирать контроллер с двумя сетевыми интерфейсами. В крайнем случае, можно будет включить программный роутинг и получить как-бы-свитч. В случае с одним интерфейсом, он так и остается одним: получить второй интерфейс невозможно.
Поэтому, выбирая контроллер, обратите внимание на раздел Technical Data → Interfaces:
Встроенный аппаратный ethernet-свитч на два сетевых разъема позволяет строить цепочку мастер-контроллеров (например, daisy chain) без использования дополнительных устройств.
Два сетевых интерфейса позволяют строить отказоустойчивые системы с резервированием каналов (TwinCAT Redundancy). При необходимости можно включить программный роутинг между интерфейсами, но аппаратная реализация все-равно будет лучше.
Кроме того, два сетевых интерфейса позволяют переключить один из них в режим работы EtherCAT-шины, и ожидаемо получить еще одну EtherCAT-шину (собственно это и происходит в отказоустойчивом режиме). При этом контроллер можно сконфигурировать как с одной шиной (отказоустойчивость, два физических канала связи), так и с двумя независимыми EtherCAT-шинами.
Еще раз, если есть требование в отказоустойчивости, то необходимо выбирать контроллер с двумя сетевыми интерфейсами. В крайнем случае, можно будет включить программный роутинг и получить как-бы-свитч. В случае с одним интерфейсом, он так и остается одним: получить второй интерфейс невозможно.
Поэтому, выбирая контроллер, обратите внимание на раздел Technical Data → Interfaces:
June 17, 2015
Машина состояний EtherCAT
Машина состояний EhterCAT (EtherCAT State machine (ESM) -- каждое устройство EtherCAT может и обязано находиться в одном из предопределенных состояний. В каждом из состояний мастером шины производятся определенные действия. Даже в случае ошибки или аварии устройство переходит в заранее предусмотренное состояние и выставляет флаг ошибки. Если устройство не поддерживает аппаратный μС-контроллер, то оно должно эмулировать машину состояний EtherCAT.
Часто используются сокращения или если хотят показать направление переключения:
Для инициализации подчиненного устройства, достаточно выполнить проход по состояниям: Init → Pre-Op → Safe-Op → Op (что сокращенно будет выглядеть как "I-P-S-O"). Здесь состояние Safe-Op не обязательно, но в стандарте фигурирует.
Для переконфигурации устройства необходимо вернуть устройство в состояние Init, а затем выполнить всю цепочку сначала: I-P-S-O. В результате прохода по этой последовательности, мастер заново переконфигурирует подчиненное устройство и гарантированно сбросит все ошибки устройства (если причина ошибки была устранена).
Смена состояния подчиненного устройства (или когда мастер управляет сам собой) происходит не сразу, а посредством управляющего регистра, т. е. сначала мастер запрашивает требуемое состояние, и только затем подчиненный какое-то время пытается изменить свое состояние, по результату изменяя значение регистра состояния. Если подчиненный не смог переключиться в требуемое состояние, то в итоге получится разное значение для запрошенного состояния подчиненного (Requested State) и текущего состояния подчиненного (Current State). Этот процесс хорошо отслеживается на закладке "Online" в System Manager.
Множество тонких настроек поведения EtherCAT-устройства доступны во вкладке "EtherCAT", диалог "Advanced Settings...".
Библиотека входит в стандартный комплект TwinCAT и содержит множество низкоуровневых и чуть-выше функций для работы по шине EtherCAT:
Для переключения состояния подчиненного можно воспользоваться функциями FB_Ec[Get|Req|Set]SlaveState. Для мастера -- аналогично, только Slave нужно заменить на Master.
Функция Get вернет текущее состояние в виде структуры ST_EcSlaveState, которая состоит из двух битовых полей:
В библиотеке доступны следующие константы состояний: EC_DEVICE_STATE_INIT, EC_DEVICE_STATE_PREOP, EC_DEVICE_STATE_xxx, ...
Перед определением текущего состояния необходимо обработать поле deviceState битовой маской EC_DEVICE_STATE_MASK. Недостаточно просто вытащить из структуры поле и сравнить его с константой состояния! Например, выясняем, что в настоящий момент устройство не способно полноценно работать:
Кроме текущего состояния, поле deviceState содержит биты-флаги ошибок. Для них специальной маски нет, поэтому проверить устройство на ошибки можно следующим образом:
Подробнее о состоянии устройства можно прочитать в описании структуры ST_EcSlaveState.
Изменение состояния подчиненного устройства
Для изменения состояния устройства, в библиотеке существуют два набора функций: FB_EcReqSlaveState и FB_EcSetSlaveState. Разница в том, что Req отправляет запрос и заканчивает свою работу. Set же будет ожидать, пока устройство не переключится в необходимое состояние и, если этого не произойдет в течение времени tTimeout, выбросит ошибку: 16#745; (*ADSERR_DEVICE_CLIENT_TIMEOUT*)
- Init (I) - нет синхронного обмена PDO; мастер может писать в информационные регистры подчиненных.
- Pre-Operational (Pre-Op, PREOP, P) -- нет синхронного обмена PDO; мастер конфигурирует подчиненных; обмен данными через асинхронные почтовые слоты.
- Safe Operational (Safe-Op, SAFEOP, S) - обмен данными через асинхронные почтовые слоты; синхронный обмен PDO -- только входа; выхода в безопасном состоянии ("Safe State").
- Operational (OP, O) -- полностью рабочий режим; синхронный обмен данными через PDO: как входа, так и выхода.
- Bootstrap (B) -- опциональный режим для "заливки" новых прошивок.
Часто используются сокращения или если хотят показать направление переключения:
- IP: Init → Pre-Op
- PI: Pre-Op → Init
- OI: Op → Init
- SO: SAFEOP → OP
Для инициализации подчиненного устройства, достаточно выполнить проход по состояниям: Init → Pre-Op → Safe-Op → Op (что сокращенно будет выглядеть как "I-P-S-O"). Здесь состояние Safe-Op не обязательно, но в стандарте фигурирует.
Для переконфигурации устройства необходимо вернуть устройство в состояние Init, а затем выполнить всю цепочку сначала: I-P-S-O. В результате прохода по этой последовательности, мастер заново переконфигурирует подчиненное устройство и гарантированно сбросит все ошибки устройства (если причина ошибки была устранена).
Смена состояния подчиненного устройства (или когда мастер управляет сам собой) происходит не сразу, а посредством управляющего регистра, т. е. сначала мастер запрашивает требуемое состояние, и только затем подчиненный какое-то время пытается изменить свое состояние, по результату изменяя значение регистра состояния. Если подчиненный не смог переключиться в требуемое состояние, то в итоге получится разное значение для запрошенного состояния подчиненного (Requested State) и текущего состояния подчиненного (Current State). Этот процесс хорошо отслеживается на закладке "Online" в System Manager.
Множество тонких настроек поведения EtherCAT-устройства доступны во вкладке "EtherCAT", диалог "Advanced Settings...".
Библиотека TcEtherCAT.lib
Библиотека входит в стандартный комплект TwinCAT и содержит множество низкоуровневых и чуть-выше функций для работы по шине EtherCAT:- CoE Interface (CAN over EtherCAT) -- работа с параметрами подчиненных устройств у которых внутренняя шина -- CAN: многие модули расширения, дискретные и аналоговый входа/выхода и пр.
- SoE Interface (SERCOS over EtherCAT) -- функции работы с параметрами SERCOS-устройств; например, сервоусилители серии AX5000.
- FoE Interface (File over EtherCAT) -- функции заливки прошивки в устройства.
- Множество других функций: диагностика рабочих счетчиков (WcState), подсчет контрольных сумм, функции конвертеры для распределенных часов и т. п.
Для переключения состояния подчиненного можно воспользоваться функциями FB_Ec[Get|Req|Set]SlaveState. Для мастера -- аналогично, только Slave нужно заменить на Master.
Функция Get вернет текущее состояние в виде структуры ST_EcSlaveState, которая состоит из двух битовых полей:
- deviceState - состояние подчиненного устройства.
- linkState - состояние линии связи.
Перед определением текущего состояния необходимо обработать поле deviceState битовой маской EC_DEVICE_STATE_MASK. Недостаточно просто вытащить из структуры поле и сравнить его с константой состояния! Например, выясняем, что в настоящий момент устройство не способно полноценно работать:
IF ( curState AND EC_DEVICE_STATE_MASK ) <> EC_DEVICE_STATE_OP THEN
Кроме текущего состояния, поле deviceState содержит биты-флаги ошибок. Для них специальной маски нет, поэтому проверить устройство на ошибки можно следующим образом:
IF ( currState.deviceState AND 16#70 ) > 0 THEN
Подробнее о состоянии устройства можно прочитать в описании структуры ST_EcSlaveState.
Изменение состояния подчиненного устройства
Для изменения состояния устройства, в библиотеке существуют два набора функций: FB_EcReqSlaveState и FB_EcSetSlaveState. Разница в том, что Req отправляет запрос и заканчивает свою работу. Set же будет ожидать, пока устройство не переключится в необходимое состояние и, если этого не произойдет в течение времени tTimeout, выбросит ошибку: 16#745; (*ADSERR_DEVICE_CLIENT_TIMEOUT*)
Если в проекте не требуются нюансы работы с EtherCAT-шиной, то лучше воспользоваться функциями Set.Не нужно изобретать свою функцию, когда уже есть готовая Set: она использует Req как основу, организуя внутри себя опрос состояния подчиненного устройства.
June 14, 2015
OPC-сервер
OPC-сервер покупается на каждый контроллер отдельно, затем устанавливается. Существует два вида:
- OPC DA/XML-DA -- только для настольных операционных систем: XP, 7, Embedded Standart, и т. п. [TS6120]. Для его работы необходим DCOM и пр. системные сервисы, и это единственная причина, почему его нет для компактных операционок.
- OPC UA (IEC 62541) -- не только для настольных [TS6100], но и для компактных: CE, Emb. Compact, ... [TS6100-0030]
Если тип OPC-сервера не важен, то стоит выбрать OPC UA.Обе версии настраиваются через XML-файл. Существует конфигуратор, который умеет создавать этот XML-файл и активировать его на ПЛК. Немного про настройку можно прочитать в "OPC сервер: настройка и тестирование".
June 9, 2015
Форматы времени и даты
Посмотрим на полный список форматов времени и даты. Немного об этом было в ST и C#, или туда и обратно, теперь рассмотрим подробнее.
Длительность или промежуток времени в миллисекундах. Аналогом в C# можно считать TimeSpan.
Формат:
:= T#521h33m23s231ms;
:= TIME#521h33m23s231ms;
Только дата — как число секунд, прошедших с 1 января 1970 года.
Формат:
:= D#1993-06-12;
:= DATE#1993-06-12;
Дата и время — как число секунд, прошедших с 1 января 1970 года. В C# можно использовать DateTime, не забывая, что в ST дата-время начинаются в Unix-стиле с 1970 года, а в C# с нулевого года.
Формат:
:= DT#1993-06-12-15:36:55;
:= DATE_AND_TIME#1993-06-12-15:36:55;
Дата-время в строку: DT_TO_STRING(DT#1998-02-13-14:20);
Результат типа STRING: 'DT#1998-02-13-14:20'
Несложно избавиться в строке от префиксов T#, DT#, TOD# и т. п. Функция DELETE входит в стандартную библиотеку STANDART.lib. Удаляем слева n-символов:
Время дня: количество миллисекунд, прошедших начиная от начала дня — от ноля часов и ноля минут (00:00).
Формат:
:= TOD#12:34:56.123;
:= TIME_OF_DAY#12:34:56.123;
Время дня в строку: TOD_TO_STRING(TOD#14:01:05.123);
Результат типа STRING: 'TOD#14:01:05.123'
Структура из двух DWORD (udint) полей. 64-х разрядное целое, содержит число 100-наносекундных интервалов с 1 января 1601 года (UTC). Для вменяемой работы с этим форматом (распаковки), существуют вспомогательные функции FILETIME_TO_DT, FILETIME_TO_SYSTEMTIME, и в обратную сторону [...]_TO_FILETIME.
Удобная структура с большим количеством удобных 16-разрядных полей типа WORD (uint):
Существует функция для преобразования строки в системное время: STRING_TO_SYSTEMTIME( 'YYYY-MM-DD-hh:mm:ss.xxx' ). На выходе получаем TIMESTRUCT.
Формат входной строки жестко фиксирован: через разделители '-' и ':' должны быть введены все цифры, а недостающие — заменены нулями:
Все переменные типа даты, времени, промежутков, длительности и т. п. хранятся в ПЛК как 32-х разрядные целые, беззнаковые.
TIME
Длительность или промежуток времени в миллисекундах. Аналогом в C# можно считать TimeSpan.
Формат:
:= T#521h33m23s231ms;
:= TIME#521h33m23s231ms;
Время в строку: TIME_TO_STRING(T#12ms);
Результат типа STRING: 'T#12ms'
Строка во время: STRING_TO_TIME('T#127ms');
Результат типа TIME: T#127ms
DATE
Формат:
:= D#1993-06-12;
:= DATE#1993-06-12;
Дата в строку: DATE_TO_STRING(D#2002-08-18);
Результат типа STRING: 'D#2002-08-18'
DATE_AND_TIME или DT
Дата и время — как число секунд, прошедших с 1 января 1970 года. В C# можно использовать DateTime, не забывая, что в ST дата-время начинаются в Unix-стиле с 1970 года, а в C# с нулевого года.
Формат:
:= DT#1993-06-12-15:36:55;
:= DATE_AND_TIME#1993-06-12-15:36:55;
Дата-время в строку: DT_TO_STRING(DT#1998-02-13-14:20);
Результат типа STRING: 'DT#1998-02-13-14:20'
Несложно избавиться в строке от префиксов T#, DT#, TOD# и т. п. Функция DELETE входит в стандартную библиотеку STANDART.lib. Удаляем слева n-символов:
str := DELETE(str, 2, 1); (* T#... TIME_TO_STRING *) str := DELETE(str, 3, 1); (* DT#... DT_TO_STRING *) str := DELETE(str, 4, 1); (* TOD#... TOD_TO_STRING *)
TIME_OF_DAY или TOD
Формат:
:= TOD#12:34:56.123;
:= TIME_OF_DAY#12:34:56.123;
Время дня в строку: TOD_TO_STRING(TOD#14:01:05.123);
Результат типа STRING: 'TOD#14:01:05.123'
FILETIME (T_FILETIME)
Структура из двух DWORD (udint) полей. 64-х разрядное целое, содержит число 100-наносекундных интервалов с 1 января 1601 года (UTC). Для вменяемой работы с этим форматом (распаковки), существуют вспомогательные функции FILETIME_TO_DT, FILETIME_TO_SYSTEMTIME, и в обратную сторону [...]_TO_FILETIME.
SYSTEMTIME (TIMESTRUCT)
Удобная структура с большим количеством удобных 16-разрядных полей типа WORD (uint):
- wYear — год, 1970..2106;
- wMonth — месяц, 1..12 (1 - январь, 2 - февраль и т. д);
- wDayOfWeek — день недели, 0..6 (0 - воскресенье (!), 1 - понедельник, ...);
- wDay — день месяца, 1..31;
- wHour — час, 0..23;
- wMinute — минуты, 0..59;
- wSecond — секунды, 0..59;
- wMilliseconds — миллисекунды, 0..999.
Существует функция для преобразования строки в системное время: STRING_TO_SYSTEMTIME( 'YYYY-MM-DD-hh:mm:ss.xxx' ). На выходе получаем TIMESTRUCT.
Формат входной строки жестко фиксирован: через разделители '-' и ':' должны быть введены все цифры, а недостающие — заменены нулями:
- YYYY — год,1601..9999;
- MM — месяц, 01..12;
- DD — день, 01..31;
- hh — час, 00..23;
- mm — минуты, 00..59;
- ss — секунды, 00..59;
- xxx — миллисекунды, 000..999.
Например:
ts := STRING_TO_SYSTEMTIME( '2012-01-30-05:12:09.567' );
Обновлено: 5 февраля 2017 г.
#
date
,
DATE_AND_TIME
,
DELETE
,
DT
,
FILETIME
,
STANDARD.lib
,
STRING_TO_SYSTEMTIME
,
SYSTEMTIME
,
T_FILETIME
,
time
,
TIME_OF_DAY
,
TimeSpan
,
TIMESTRUCT
,
TOD
,
программирование
June 8, 2015
Энергонезависимые переменные
Рано или поздно контроллер выключается. Иногда - внезапно, тогда содержимое оперативной памяти теряется. Для таких случаев существует несколько видов специальных переменных, способных сохранять (и восстанавливать) данные в моменты без электричества.
RETAIN
PERSISTENT
Несколько видов переменных существует из-за ограниченности флэш-памяти на количество записей. Жесткие диски, внешние базы данных, сетевые переменные и пр. - хорошая альтернатива, но не всегда доступны.
По сути применимы только для серии BC. Можно и в других случаях, но неудобно, т. к. сохраняют свое значение только при корректном завершении работы TwinCAT. Не спасут при внезапном пропадании электричества (*). Совершенно не экономят ресурс флэш-памяти. На контроллерах BC сохраняют свое значение при изменении значения переменной и могут удлинить время цикла (если нужно).
PERSISTENT
Аналогично RETAIN, но сохраняются при вызове специального функционального блока (FB_WritePersistentData из TcUtilities.lib), либо при корректном выключении TwinCAT (*). Экономят ресурс флэш-памяти.
При использовании RETAIN и PERSISTENT данных, значения переменных по прежнему сбрасываются на флэш-диск. TwinCAT самостоятельно восстанавливает данные и может отследить нарушение целостности сохраненных данных (об этом можно узнать из специальной системной переменной: SYSTEMINFO). Остальные подробности - в справочной системе: TX1200 | TwinCAT PLC.
NOV/DP-RAM
Nonvolatile DualPort RAM - энергонезависимая двухпортовая память.
Память двухпортовая, т. е. состоит из двух компонентов: оперативная память и долговременная память, плюс - собственный источник питания. Все это, в совокупности, позволяет сохранять переменные в любой момент, сколько угодно раз, а при отключении питания, успеть сбросить и хранить данные в долговременной памяти за счет собственного источника питания. Автоматически.
Память двухпортовая, т. е. состоит из двух компонентов: оперативная память и долговременная память, плюс - собственный источник питания. Все это, в совокупности, позволяет сохранять переменные в любой момент, сколько угодно раз, а при отключении питания, успеть сбросить и хранить данные в долговременной памяти за счет собственного источника питания. Автоматически.
Но(!) не все контроллеры поддерживают этот вид памяти.
Настраивается и конфигурируется в системной конфигурации линковкой выходных переменных ПЛК-проекта с выходными переменными NovRAM. Дальше работает автоматически, поэтому на младших контроллерах постоянная работа с переменными этой памяти может вызвать ощутимую нагрузку на процессор ПЛК. Для снижения нагрузки существует специальный функциональный блок, для записи блоков данных только при необходимости (FB_NovRamReadWriteEX).
Односекундные бесперебойники
Теперь вернемся к звездочке в скобках (*): ограничение на аккуратное завершение работы TwinCAT снимается "односекундным бесперебойником" (S-UPS: capacitive seconds UPS). За счет энергии, накопленной в ионисторах, эта опция позволяют подпитывать контроллер некоторое время, достаточное для сброса данных во флэш-память. Для работы этой функции необходимо обрабатывать функцию FB_S_UPS из библиотеки TcSUPS.Lib.
Но(!), опять-таки, не все контроллеры и не для всех.
Для всех видов переменных, при использовании файловых фильтров EWF или FBWF, необходимо разрешить на запись каталог Boot. Это происходит автоматически при включении фильтров, но проверить, не помешает.
June 1, 2015
Free Run не нужен
Со временем выработалось правило: "Free Run не нужен", но пытливые умы все-равно пытаются приспособить что-то незнакомое, под что-нибудь полезное.
Устройства при включенной опции Free Run пытаются оставаться в состоянии OP даже в режиме конфигурации, и часто им это удается, но задачи (Tasks) системы реального времени конечно же не работают. Поэтому единственное применение этой опции - подергать за выхода в режиме конфигурации при еще не написанной ПЛК-программе.
Устройства при включенной опции Free Run пытаются оставаться в состоянии OP даже в режиме конфигурации, и часто им это удается, но задачи (Tasks) системы реального времени конечно же не работают. Поэтому единственное применение этой опции - подергать за выхода в режиме конфигурации при еще не написанной ПЛК-программе.
Кстати, иногда модули расширения реагируют на такое безобразие "как-то не так", что заставляет разработчика искать несуществующую ошибку.Кроме того, существует режим синхронизации "free run", но это уже другая история, не имеющая никакого отношения к этому окошку.
Как подергать за входа без Free Run?
Если "Free Run не нужен", но хочется подергать за аппаратные выхода, можно воспользоваться веткой "Additional Tasks". Она позволяет создавать задачи, которые умеют перегонять данные, и только, что собственно и нужно, чтобы в рабочем режиме (Run Mode) обеспечить активность шины, избегая такого сообщения:
Создаем дополнительную задачу, а в ней какую-нибудь переменную, которую затем линкуем с переменной состояния EtherCAT-шины:
При создании дополнительной задачи, не забываем сразу же выставить опцию "Auto start", т. к. в отличии от ПЛК-задач, дополнительные задачи автоматически не стартуют.
После этого можно активировать конфигурацию и наслаждаться полноценным рабочим режимом (Run Mode) с активной шиной и устройствами.
May 25, 2015
ActiveX в HMI
В HMI визуализацию можно добавить компонент ActiveX. Если вы не умеете работать с ActiveX компонентами, не знаете C/C++ и не умеете создавать DLL-файлы, можете забыть про эту возможность и просто написать свое собственное приложение, которое будет "выгребать" данные через ADS.API. В общем, именно так и нужно поступить, так как ActiveX морально устарел.
Нюансы вставки ActiveX-компонента:
На вкладке Methodcalls сосредоточены настройки вызова различных методов интерфейса ActiveX-компонента:
Вкладка для компонентов, поддерживающих интерфейс IWebBrowser:
DLL-библиотека должна экспортировать функцию ExecuteActiveXCall:
Данный пример показывает, как может выглядеть простейшая DLL-библиотека, позволяющая вызывать другие методы ActiveX-компонента. Здесь GoBack и GoForward позволяют переходить на страницу вперед или на страницу назад, при условии, что компонент поддерживает интерфейс IWebBrowser.
Названия IWebBrowser|GoBack и IWebBrowser|GoForward никак не связаны с названиями методов компонента. Разработчик может использовать произвольные, удобные для него названия:
Функция добавления ActiveX-компонента - это стандартная функция CoDeSys PLC Control; она может использоваться и в контроллерах других производителей.
Нюансы вставки ActiveX-компонента:
- Только пассивный элемент, только отображение.
- Только настольные операционные системы: Windows XP, 7 и старше.
- Не работает под Windows CE и другие Сompact версии.
- Основная цель - вставка компонента отображения HTML-страниц (Microsoft WebBrowser), но можно и другие.
На вкладке Methodcalls сосредоточены настройки вызова различных методов интерфейса ActiveX-компонента:
Webbrowser
Вкладка для компонентов, поддерживающих интерфейс IWebBrowser:
- Variable for URL - переменная, содержащая строку адреса веб-сайта (URL).
- Condition for call - старт загрузки html-страницы по переднему фронту TRUE, заданной переменной. Если переменная не задана - компонент будет обновляться каждый цикл ПЛК! Лучше - задать.
Additional Call
Позволяет настроить вызов других методов ActiveX-компонента:
- Dll for Call - полный путь к DLL-файлу, который в свою очередь будет вызывать какие-то методы компонента.
- Methodidentification - строка, содержащая название метода, который будет вызываться внутри DLL. Это не само название метода, а подсказка для внутренностей DLL-библиотеки - что необходимо делать (см. пример ниже).
- Parameter - дополнительные параметры для передачи в DLL-библиотеку.
- Condition for call - старт вызова по переднему фронту заданной переменной.
DLL-библиотека должна экспортировать функцию ExecuteActiveXCall:
void ExecuteActiveXCall(IUnknown* pUnk, char* pszId, char* pszParam, char* pszReturnBuffer, int nReturnBufferSize, DWORD* pdwReturnFlag);
- pszId - строка из переменной Methodidentification.
- pszParam - строка из переменной Parameter.
- pUnk - ссылка на интерфейс для работы с методами ActiveX-компонента.
- pszReturnBuffer, nReturnBufferSize и pdwReturnFlag - не используются.
Пример DLL-библиотеки
Данный пример показывает, как может выглядеть простейшая DLL-библиотека, позволяющая вызывать другие методы ActiveX-компонента. Здесь GoBack и GoForward позволяют переходить на страницу вперед или на страницу назад, при условии, что компонент поддерживает интерфейс IWebBrowser.
Названия IWebBrowser|GoBack и IWebBrowser|GoForward никак не связаны с названиями методов компонента. Разработчик может использовать произвольные, удобные для него названия:
#include "stdafx.h" #include <unknwn.h> #include <exdisp.h> BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } extern "C" __declspec (dllexport) void ExecuteActiveXCall(IUnknown* pUnk, char* pszId, char* pszParam, char* pszReturnBuffer, int nReturnBufferSize, DWORD* pdwReturnFlag) { if (strcmp(pszId, "IWebBrowser|GoBack") == 0) { IUnknown* pNewUnk; IWebBrowser* pwb; pUnk->QueryInterface(IID_IWebBrowser, (void**) &pNewUnk); pwb = (IWebBrowser*) pNewUnk; if (pwb) { pwb->GoBack(); pwb->Release(); } } else { if (strcmp(pszId, "IWebBrowser|GoForward") == 0) { IUnknown* pNewUnk; IWebBrowser* pwb; pUnk->QueryInterface(IID_IWebBrowser, (void**) &pNewUnk); pwb = (IWebBrowser*) pNewUnk; if (pwb) { pwb->GoForward(); pwb->Release(); } } } }
Функция добавления ActiveX-компонента - это стандартная функция CoDeSys PLC Control; она может использоваться и в контроллерах других производителей.
May 8, 2015
Обмен данными через ADS.API, ST и C#
При работе с ADS.API и.NET необходимо учитывать, что у данных есть длина: причем ПЛК-задачи используют 16-разрядное слово, а C# 32-х. В справочной системе есть краткая таблица соответствия типов. Сделаем ее длинношеее.
Ничего сложного, для базовых типов нужно учитывать разницу в длине слов. Специфические типы данных DT и TIME требуют особого подхода. STRING и ARRAY - особого подхода с элементами эквилибристики. Работа со структурами STRUCT - тема отдельного разговора.
TIME в языке ST - это промежуток времени (длина), DT - дата и время события (момент). Соответственно в C# для TIME подходит TimeSpan, для DT - DateTime.
TIME хранится в памяти ПЛК как 32-х разрядное, целое, беззнаковое число, содержащее количество миллисекунд:
Я специально использовал тип данных UInt32, чтобы был виден размер данных. По желанию его можно заменить на uint.
С DT немного сложнее, но не сильно - это опять-таки 32-х разрядное, целое, беззнаковое, содержащее количество секунд, прошедшее с 1 января 1970 года:
Я немного расширил пример выводом даты и времени в консоль.
Сразу отмечу, что строки - это одномерные массивы символов. Нет никакой разницы между работой со строками и массивами.
При записи строк - длину указывать необходимо, т. к. можно передавать только часть строки, а вот при записи массивов наоборот - длину передавать не только не нужно, но и нельзя:
Существует возможность прочитать список всех переменных ПЛК-программы: получить исчерпывающую информацию об окружении переменной, ее типе, группе, смещении в памяти, родительской структуре и пр. В терминологии TwinCAT - это называется "Symbol Information". Даже комментарий, оставленный в строке с объявленной переменной, будет доступен через информацию окружения.
Name и ShortName ничем, к сожалению, не отличаются, хотя должны. Поля Parent и xxxSubSymbol (в примере выше его нет) организуют переменные в иерархию.
Подробнее про назначение полей можно прочитать в справочной системе "TcAdsSymbolInfo Members".
| ST | C# | Знак. | Длина |
| BYTE, USINT | byte | + | 8 бит |
| INT | short | ± | 16 бит |
| WORD, UINT | ushort | + | 16 бит |
| DINT | int | ± | 32 бита |
| DWORD, UDINT | uint | + | 32 бита |
| REAL | float | ± | 32 бита |
| LREAL | double | ± | 64 бита |
Ничего сложного, для базовых типов нужно учитывать разницу в длине слов. Специфические типы данных DT и TIME требуют особого подхода. STRING и ARRAY - особого подхода с элементами эквилибристики. Работа со структурами STRUCT - тема отдельного разговора.
Дата и время
TIME в языке ST - это промежуток времени (длина), DT - дата и время события (момент). Соответственно в C# для TIME подходит TimeSpan, для DT - DateTime.
TIME хранится в памяти ПЛК как 32-х разрядное, целое, беззнаковое число, содержащее количество миллисекунд:
int htime = client.CreateVariableHandle("MAIN.t"); UInt32 ttime = 0; ttime = (UInt32)client.ReadAny(htime, ttime.GetType()); TimeSpan span = TimeSpan.FromMilliseconds(ttime);
Я специально использовал тип данных UInt32, чтобы был виден размер данных. По желанию его можно заменить на uint.
С DT немного сложнее, но не сильно - это опять-таки 32-х разрядное, целое, беззнаковое, содержащее количество секунд, прошедшее с 1 января 1970 года:
int hdate = client.CreateVariableHandle("MAIN.td"); int tdate = 0; tdate = (int)client.ReadAny(hdate, tdate.GetType()); DateTime dt = new DateTime(1970, 1, 1).AddSeconds(tdate); Console.WriteLine(string.Concat("DT td = ", dt.ToLongDateString(), " / ", dt.ToLongTimeString()));
Я немного расширил пример выводом даты и времени в консоль.
Массивы данных
Сразу отмечу, что строки - это одномерные массивы символов. Нет никакой разницы между работой со строками и массивами.
В старых версиях TwinCAT необходимо было учитывать, что в конце строки стоит символ '\0' и соответственно увеличивать на единицу размер массива под строку. Начиная со второй версии, про нулевой символ и увеличение размера можно забыть: просто передавайте длину строки в символах.Для чтения и записи массивов используется дополнительный параметр, передающий количество элементов массива. Для одномерного массива - число элементов, для двумерного - число строк, затем число столбцов.
VAR str : STRING := 'abcdef'; strT : T_MaxString := '01234abcdef'; arr : ARRAY [1..5] OF WORD; arr2D : ARRAY [1..3, 1..5] OF WORD; END_VAR
string str = ""; string strT = ""; ushort[] arr = new ushort[5]; ushort[,] arr2d = new ushort[3, 5]; str = (string) (client.ReadAny(h_str, str.GetType(), new int[] { 80 })); strT = (string) (client.ReadAny(h_strT, strT.GetType(), new int[] { 255 })); arr = (ushort[]) (client.ReadAny(h_arr, arr.GetType(), new int[] { 5 })); arr2d = (ushort[,])(client.ReadAny(h_arr2D, arr2d.GetType(), new int[] { 3, 5 }));
При записи строк - длину указывать необходимо, т. к. можно передавать только часть строки, а вот при записи массивов наоборот - длину передавать не только не нужно, но и нельзя:
client.WriteAny(h_str, str, new int[] { str.Length }); client.WriteAny(h_arr, arr); client.WriteAny(h_arr2D, arr2d);
Все переменные сразу
Существует возможность прочитать список всех переменных ПЛК-программы: получить исчерпывающую информацию об окружении переменной, ее типе, группе, смещении в памяти, родительской структуре и пр. В терминологии TwinCAT - это называется "Symbol Information". Даже комментарий, оставленный в строке с объявленной переменной, будет доступен через информацию окружения.
// Загружаем информацию о всех доступных переменных TcAdsSymbolInfoLoader symbolLoader = client.CreateSymbolInfoLoader(); TcAdsSymbolInfoCollection infoCollection = symbolLoader.GetSymbols(true); foreach (TcAdsSymbolInfo symInfo in infoCollection) { string name = symInfo.Name; string shortName = symInfo.ShortName; string type = symInfo.Type; int size = symInfo.Size; string comment = symInfo.Comment; long iGroup = symInfo.IndexGroup; long iOffset = symInfo.IndexOffset; TcAdsSymbolInfo parentInfo = symInfo.Parent; Console.WriteLine(string.Concat( name, Environment.NewLine, shortName, Environment.NewLine, type, Environment.NewLine, size, Environment.NewLine, comment, Environment.NewLine, iGroup, ":", iOffset, Environment.NewLine )); }
Name и ShortName ничем, к сожалению, не отличаются, хотя должны. Поля Parent и xxxSubSymbol (в примере выше его нет) организуют переменные в иерархию.
Подробнее про назначение полей можно прочитать в справочной системе "TcAdsSymbolInfo Members".
May 7, 2015
Как готовить синхронные функции ADS.API
Сборник рецептов как корректно обращаться с базовыми синхронными функциями ADS.API.
...
...
Подключаемся к ПЛК-рантайму №1.
Для TwinCAT2 порт = 801, для TwinCAT3 порт = 851.
Как таковой связи еще нет, мы всего-лишь подключились к локальному роутеру. Поэтому, перед созданием дескрипторов переменных, необходимо как-то поймать момент, когда переменные ПЛК уже доступны. Произойдет это после запуска ПЛК-задачи контроллера:
Такой кусок кода нужно запустить в отдельном потоке и дождаться сообщения о благополучном запуске задачи. В C#.NET 4.5 удобно использовать конструкции async/await.
Создаем дескриптор для переменной из контроллера
Немного исправим способ из справочной системы.
(*) typeof(short) лучше превратить в plcVar.GetType(), это заставит разработчика проинициализировать переменную до ее первого использования.
(**) ReadAny() возвращает object который всегда не-null, иначе выбрасывается исключение, т. е. null не бывает никогда, поэтому можно избежать использования nullable-типов (short?) с дальнейшей проверкой на null или более простой конструкции "??".
Так как до- или во время чтения/записи может произойти обрыв связи или другие неприятности, необходима обработка исключений.
Но, здесь нет анализа ошибки.
Вместо анализа ошибки, закидает пользователя и рабочий стол сообщениями об ошибке.
Вариант правильный, но очень длинный. На каждое чтение/запись такое не напишешь. Поэтому для чтения данных мастерят "обертки", берущие на себя всю рутину обработки ошибок и пр.
Инициализация
PROGRAM MAIN VAR PLCVar : INT; END_VAR
private int hVar; private TcAdsClient adsClient; private short plcVar;
TcAdsClient adsClient = new TcAdsClient();
Подключаемся к ПЛК-рантайму №1.
Для TwinCAT2 порт = 801, для TwinCAT3 порт = 851.
adsClient.Connect("172.16.3.217.1.1", 801); // TwinCAT 2 adsClient.Connect("172.16.3.217.1.1", 851); // TwinCAT 3
Подключение
Как таковой связи еще нет, мы всего-лишь подключились к локальному роутеру. Поэтому, перед созданием дескрипторов переменных, необходимо как-то поймать момент, когда переменные ПЛК уже доступны. Произойдет это после запуска ПЛК-задачи контроллера:
StateInfo info = new StateInfo(AdsState.Invalid, 0); while (info.AdsState != AdsState.Run) { try { info = adsClient.ReadState(); } catch { } Thread.Sleep(100); // ждем 100мс перед следующей проверкой } // OnConnected(); // отправляем в родительский поток сообщение о подключении
Такой кусок кода нужно запустить в отдельном потоке и дождаться сообщения о благополучном запуске задачи. В C#.NET 4.5 удобно использовать конструкции async/await.
Синхронное чтение
Создаем дескриптор для переменной из контроллера
try
{
hVar = adsClient.CreateVariableHandle("MAIN.PLCVar");
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
Немного исправим способ из справочной системы.
plcVar = (short) adsClient.ReadAny(hVar, typeof(short)); // Было plcVar = (short) adsClient.ReadAny(hVar, plcVar.GetType()); // Стало * plcVar = (short)(adsClient.ReadAny(hVar, plcVar.GetType()) ?? 0); // Избыточный вариант **
(*) typeof(short) лучше превратить в plcVar.GetType(), это заставит разработчика проинициализировать переменную до ее первого использования.
(**) ReadAny() возвращает object который всегда не-null, иначе выбрасывается исключение, т. е. null не бывает никогда, поэтому можно избежать использования nullable-типов (short?) с дальнейшей проверкой на null или более простой конструкции "??".
Обработка ошибок
Так как до- или во время чтения/записи может произойти обрыв связи или другие неприятности, необходима обработка исключений.
try
{
plcVar = (short)adsClient.ReadAny(hVar, plcVar.GetType());
}
catch {}
Но, здесь нет анализа ошибки.
try
{
plcVar = (short)adsClient.ReadAny(hVar, plcVar.GetType());
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
Вместо анализа ошибки, закидает пользователя и рабочий стол сообщениями об ошибке.
AdsErrorCode errCode = AdsErrorCode.NoError; try { plcVar = (short)adsClient.ReadAny(hVar, plcVar.GetType()); } catch(AdsErrorException ex) { errCode = ex.ErrorCode; } catch(Exception genex) { // Произошло что-то не связанное с ADS.API } if (errCode != AdsErrorCode.NoError) { switch(ex.ErrorCode) { case AdsErrorCode.ClientSyncTimeOut: // Обрыв связи // OnDisconnected(); break; case AdsErrorCode.DeviceSymbolNotFound: // Переменная пропала (например, была стерта программы) // OnInvalidData(); break; default: // Все, что не требует обработки // OnGeneralError(); break; } }
Вариант правильный, но очень длинный. На каждое чтение/запись такое не напишешь. Поэтому для чтения данных мастерят "обертки", берущие на себя всю рутину обработки ошибок и пр.
Subscribe to:
Posts
(
Atom
)










