Можно одновременно использовать механизм уведомлений и обычные синхронные операции.Инициатива по созданию событий-уведомлений (Device Notification) принадлежит программе созданной на языке высокого уровня (в нашем случае — C#, ПК), но сам механизм уведомлений находится в роутере контроллера. Не рекомендуется создавать более 550 уведомлений для одного устройства, т. к. каждая новая подписка дополнительно нагружает контроллер. Для увеличения нагрузочной способности лучше упаковать или организовать данные в структуры и обмениваться пачками данных. Для снижения нагрузки на клиентское приложение, можно ввести ограничение на частоту возникновения уведомлений, а точнее, на промежуток между отправкой уведомлений.
Всегда приходит первое уведомление. После подписки приемник считает, что значение переменной не определено, поэтому сразу же приходит первое уведомление с актуальным значением переменной.
Использование дополнительных задач
Начнем сразу со сложного: создаем дополнительную задачу (Additional Task), оставляем порт равным 301 по умолчанию, и затем связываем переменные задачи с модулями расширения физических входов/выходов на шине EtherCAT:
Создавать и загружать программу ПЛК-задачи не требуется. В этом основное преимущество фиктивных, дополнительных задач: при минимуме телодвижений, можно сразу же начать читать данные с шины, т. е. с модулей ввода/вывода.
Подписка
Переходим к программе на C#. Для подписки на события используется метод AddDeviceNotification класса TcAdsClient:
TcAdsClient client301; int h_client301; AdsStream adsStream; private void MainForm_Load(object sender, EventArgs e) { client301 = new TcAdsClient(); client301.Connect("5.2.100.109.1.1", 301); adsStream = new AdsStream(32); try { h_client301 = clientPlc.AddDeviceNotification( "Task 2.Inputs.EL1018", adsStream, 0, sizeof(byte), AdsTransMode.OnChange, 100, 10000, null); } catch (AdsErrorException adsExc) { // LOG: adsExс.Message; client301.Dispose(); client301 = null; } if (client301 != null) { client301.AdsNotification += Client301_AdsNotification; } [...]
В метод передается имя переменной на изменения которой мы подписываемся. Затем указывается поток входных данных AdsStream. Необходимо создать поток с буфером, размер которого достаточен для размещения значений переменной. Кроме этого, задается смещение в потоке и размер данных переменной в байтах (подробнее — ниже).
AdsTransMode определяет тип опроса переменной. На выбор предоставляется несколько вариантов, но в данный момент реализовано только два. Cyclic — задает периодический опрос значения переменной с интервалом заданным параметром cycleTime (в примере выше, это 100 миллисекунд). OnChange — уведомления приходят только при изменении значения переменной.
Параметр maxDelay задает длительность интервала между двумя уведомлениями. По его истечении друг за другом придут все те уведомления, что произошли в этот промежуток времени, т. е. роутер накапливает их, а затем выдает пулеметной очередью друг за другом.
В последний параметр userData разработчик может передать какие-либо свои данные. Они будут доступны при получении уведомления.
Освобождение ресурсов
Уведомления создаются на стороне контроллера, поэтому по отношению к программе клиенту они являются внешними и соответственно неуправляемыми ресурсами (umanaged resources). По окончании работы лучше освободить их.
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { if (client301 != null) { try { client301.DeleteDeviceNotification(h_client301); } catch (AdsErrorException adsExс) { // LOG: adsExс.Message; } finally { client301.Dispose(); client301 = null; } } [...]
В принципе, финализаторы библиотеки самостоятельно справятся с освобождением занятого, но во избежании внезапных протечек памяти лучше сделать это самостоятельно.
Уведомления
Когда-нибудь наступит тот самый момент и придет уведомление. Первое, что необходимо сделать при получении, это сдвинуть курсор чтения на N-байт. На сколько его необходимо сдвинуть мы задаем при подписке на уведомления.
private void Client301_AdsNotification(object sender, AdsNotificationEventArgs e) { e.DataStream.Position = e.Offset; byte el1008 = (byte)e.DataStream.ReadByte();
В общем случае, можно создать единственный поток и писать все данные в него. Тогда при подписке нужно задать смещение для переменной т. е. сериализовать ее значение в поток с заданным смещением. По приходу уведомления, задаем соответствующее для данной переменной значение смещения (десериализуем из потока). Если вам это не нужно — просто создайте для каждой переменной отдельный поток и задайте смещение равное нулю.
Чтение данных EtherCAT-шины
Включаем ADS-сервер, который будет предоставлять данные с шины.
Включенный сервер не является потребителем данных. Без задач, читающих данные с шины, вы получите спящую шину и по прежнему неработающий ADS-сервер: Device 'Device 3 (EtherCAT)' needs sync master (at least one variable linked to a task variable). Создайте хотя бы одну дополнительную задачу и слинкуйте хотя бы одну переменную.
Подписываемся аналогично, за исключением номера порта — 27907:
clientECat = new TcAdsClient(); clientECat.Connect("5.2.100.109.1.1", 27907); h_clientECat = clientECat.AddDeviceNotification( "Inputs.Frm0InputToggle", adsStream, 1, sizeof(ushort), AdsTransMode.OnChange, 10, 0, null);
Работа со структурами
TYPE ST_DataPack : STRUCT Index : UINT; Name : STRING(10); Speed : REAL; END_STRUCT END_TYPE PROGRAM MAIN VAR Pack : ST_DataPack := (Index := 0, Name := '', Speed := 0); state : UINT; END_VAR CASE state OF 0: (* Инициализация *) Pack.Index := 1; Pack.Name := 'LightSpeed'; Pack.Speed := 0; state := 100; 100: (* Ускорение *) Pack.Speed := Pack.Speed + 0.1; Pack.Index := REAL_TO_UINT(Pack.Speed / 100); END_CASE
На стороне клиента объявляется аналогичная структура.
В примере ниже предлагается почленное заполнение полей структуры, поэтому нет необходимости в строгой последовательности полей и применении атрибутов типа StructLayout и т. п.Следует помнить о несоответствии размеров типов данных TwinCAT и C#, а также правильно вычислить размер буфера для потока. Для строковых переменных необходимо указывать максимальное количество символов (по умолчанию TwinCAT задает его равным 80 символам).
public struct DataPack { public ushort Index; public string Name; public float Speed; } DataPack backpack = new DataPack(); TcAdsClient clientPlc; int h_clientPlc; AdsStream adsStream; private void MainForm_Load(object sender, EventArgs e) { clientPlc = new TcAdsClient(); clientPlc.Connect("5.2.100.109.1.1", (int)AmsPort.PlcRuntime1); adsStream = new AdsStream(32); try { h_clientPlc = clientPlc.AddDeviceNotification(
"MAIN.Pack",
adsStream, 3, sizeof(ushort) + sizeof(float) + 10,
AdsTransMode.OnChange, 100, 0,
null);
} catch (AdsErrorException adsExc) { // LOG: adsExс.Message; clientPlc.Dispose(); clientPlc = null; } if (clientPlc != null) { clientPlc.AdsNotification += ClientPlc_AdsNotification; } [...]
Для удобства чтения полей структуры создается AdsBinaryStream, который умеет десериализовать в самые распространенные типы данных.
private void ClientPlc_AdsNotification(object sender, AdsNotificationEventArgs e) { e.DataStream.Position = e.Offset; var reader = new AdsBinaryReader(e.DataStream); backpack.Index = reader.ReadUInt16(); backpack.Name = new string(reader.ReadChars(10)); backpack.Speed = reader.ReadSingle(); }
Запись структуры производится точно так же как и запись обычной переменной.
backpack.Index = 0; backpack.Name = "PC written"; backpack.Speed = 300000; clientPlc.WriteSymbol("MAIN.Pack", backpack, false);
No comments
Post a Comment
Note: Only a member of this blog may post a comment.