Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

November 17, 2020

Переменная через указатель, через ADS

Значение переменной можно прочитать через указатель по ADS.

PROGRAM MAIN
VAR
    state:  UINT;
    pState: POINTER TO UINT;
END_VAR

pState := ADR(state);


Прочитаем значение переменной `state` через ее указатель `pState`. Нужно разыменовать указатель, добавив спец. символ `^` к имени указателя. Получим `pState^`. Программа на C# для текущей версии 4.4.10 библиотеки Beckhoff.TwinCAT.Ads:

ushort state = (ushort) tcAdsClient.ReadSymbol( "MAIN.pState^", typeof( ushort ), false );


Значение переменной читается. Теперь возьмем пробную версию новой библиотеки 5.0.1-preview.12 под .NET Core 3.1.0. Код поподробнее:


Программа читает переменную и в зависимости от ее значения рисует замысловатые узоры. Имея указатель можно вытягивать значение переменной. Это полезное свойство и дополнительные телодвижения здесь не нужны.


Для новой библиотеки под .NET Core требуется создать файл роутинга `StaticRoutes.xml`. В этом файле настраиваются соединения между целевым контроллером и ПК. Если контроллер  и ПК находятся на одной и той же машине, то настраивать этот файл не нужно. Можно просто удалить его.

October 21, 2019

Linux + TwinCAT 3 + .Net Core 3 + ADS 5

Система реального времени по прежнему работает под Windows, но реализовать клиентскую сторону, визуализацию или массивную обработку данных (дата саенс, ага) под Linux уже можно.

Не то чтобы раньше нельзя было. Например, родные Бекхоффские реализации протокола ADS на C++, с неродным интерфейсом на QT. Python+Ads для обработки данных, например в блокнотах Jupiter и AdsClient (.NET Standart) для C# под гуем из WPF. Протокол открытый, почему бы и нет?

Теперь же Бекхофф выкатил в публичное превью библиотеки для работы с ADS под .NET Core 3. В первую очередь это означает кросс-платформенность для приложений на C# под Linux, Windows, Mac. О последнем я говорить не хочу, а для первых двух вырисовывается полный стек интересных технологий (или интересный стек полных технологий):
  • .NET Core 3.0
  • AvaloniaUI
  • Beckhoff.TwinCAT.Ads 5

Я еще раз подчеркну, сразу для двух операционных систем: Windows - Linux. Одно и то же приложение. Возможно необходимо пересобрать, тыкнув мышой в выбор операционки, но переписывать код уже не нужно. Выглядеть интерфейс будет одинаково на обеих операционках. Это благодаря Avalonia, которая почти как WPF.


TwinCAT.Ads 5.0.0-preview1


Итак, не далее как неделю назад, Бекхофф выложила превью библиотек. Нас ждут интриги, расследования и гонки на багги. "This is a prerelease version of Beckhoff.TwinCAT.Ads", — говорят нам RaHe, sveno, MichaelKn и Beckhoff со страниц NuGet. Далее идут инструкции как со всем этим жить, но это не точно. Потому что инструкции не работают. Как "нада жить" будет позже, а сейчас про состав выложенного:
  • TwinCAT.Ads 5.0.0-preview1
    Сборка для доступа к данным ПЛК через ADS. Совместима с предыдущими версиями библиотеки. Состав прежний: Client, Session, Read-Write Any Symbol, и тому подобное. Появились асинхронные операции. TcAdsClient переименован в AdsClient; AdsStream заменен на модные и современные Span+Memory.
  • Ads.AdsRouterConsole
    Под Linux нет рантайма и ADS-роутера соответственно. Поэтому Бекхофф предлагает нам нечто вроде выполняющее функции роутера. Запускается как консоль отдельно и самостоятельно, в том числе и под Linux.
  • Ads.TcpRouter
    А это уже не самостоятельная штука, зато можно засунуть себе в программу. Это как бы переносной роутер, который будет внутри вашей программы. Вокруг этой же штуки построена консоль AdsRouterConsole.
  • Ads.Reactive
    Тоже сборка для доступа к данным, но доступ "реактивным" способом.
  • AdsServer + Abstractions
    Сервер и всё что сборки делят между собой. Про сервер как-нибудь в другой раз: где еще делать сервер как не под Linux?


Запускаем AdsRouterConsole


Сборка консоли роутера будет лежать в 
%USERPROFILE%\.nuget\packages\beckhoff.twincat.ads.adsrouterconsole\5.0.0-preview1\lib\netcoreapp3.0\TwinCAT.Ads.AdsRouterConsole.dll

...но(!) в инструкции, которая лежит в разделе Documentation NuGet, есть опечатка. На самом деле запускать нужно так:
dotnet TwinCAT.Ads.AdsRouterConsole.dll

...но(!) опять-таки  не запустится, так как этой сборке для запуска не хватает других сборок. Поэтому мы установим сборку TwinCAT.Ads.AdsRouterConsole в свой проект, и тогда .dll файл сборки будет лежать в папке /bin/Debug/netcoreapp3/, а в ней уже автоматом будут все необходимые для запуска консоли роутера сборки.

...но(!) при запуске мы получим следующую ошибку:
A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in '...\bin\Debug\netcoreapp3\'.
Failed to run as a self-contained app. If this should be a framework-dependent app, add the ...\TwinCAT.Ads.AdsRouterConsole.runtimeconfig.json file specifying the appropriate framework.
Это связано с безопасностью в системе, поэтому возвращаемся к
%USERPROFILE%\.nuget\packages\beckhoff.twincat.ads.adsrouterconsole\5.0.0-preview1\lib\netcoreapp3.0
(да-да, именно для этого он нам и нужен был) и кладем файл TwinCAT.Ads.AdsRouterConsole.runtimeconfig.json рядом со сборкой консоли роутера, то есть в каталог /bin/Debug/netcoreapp3/ или /bin/Release/netcoreapp3/ проекта. Выбор конечного пути зависит от этапа разработки и конфигурации проекта.

Файл можно создать самостоятельно, вот его содержимое:

{
    "runtimeOptions": {
        "tfm": "netcoreapp3.0",
        "framework": {
            "name": "Microsoft.NETCore.App",
            "version": "3.0.0"
        }
    }
}

После этого, может запуститься, а может не запуститься и вылететь с ошибкой:
Error: An attempt was made to access a socket in a way forbidden by its access permissions.
...и это всё потому, что у вас уже запущен роутер. Приложение кросплатформенное, можно запускать как под Linux так и под Windows. Прибить работающий роутер можно остановив сервис TcSysSrv из командной строки PowerShell от имени Администратора или через Computer Management.


Таблица роутинга


На самом деле и сейчас не запустится: любому роутеру нужны настройки роутинга. Со стороны ПЛК придется настроить самостоятельно и вручную, а со стороны ПК сформировать XML файл StaticRoutes.xml, расположив его рядом со сборкой консоли роутера:

<?xml version="1.0" encoding="utf-8"?>
<TcConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="C:\TwinCAT3\Config\TcConfig.xsd">
  <Local>
      <Name>RALFH03</Name>
      <NetId>172.17.60.215.1.1</NetId>
  </Local>
  <RemoteConnections>
    <Route>
      <Name>CX-1CD661</Name>
      <Address>169.254.116.30</Address>
      <NetId>5.28.214.97.1.1</NetId>
      <Type>TCP_IP</Type>
    </Route>
  </RemoteConnections>
</TcConfig>

Секция Local — это имя и локальный Ams NetId адрес ПК. Выбираются произвольно, но в роутерах с обеих сторон, в записях касающихся ПК, этот адрес должен совпадать с обеих сторон, то есть во всех роутерах сразу. RemoteConnections — это про удаленную сторону, то есть о ПЛК. Сравним с настройками роутера на ПЛК.

Зайдем на ПЛК через web-конфигуратор: http://169.254.112.234/Config. Далее: TwinCAT → Connectivity → Add TwinCAT Route. Внизу списка жмем кнопки [+] и создаем запись роутинг для удаленного ПК (или ноутбука разработчика), удаленного теперь уже относительно ПЛК:

Route Name: RALFH03
AMS Net ID: 172.17.60.215.1.1
Transport Type: TCP/IP
Address: 169.254.7.118
Connection Timeout (ms): 3000

Сравните "тут" и "там" и станет немного понятнее.


Virtual Box


Я тестировал под виртуальной машиной на x64 Ubuntu 19.04. Для Virtual Box необходимо... краткое объяснение на пальцах — обязательно выставить все три сетевые параметра сетевого коннекта Убунты: IP адрес, маску и обязательно gateway (гейтвей можно произвольный, я выставлял 192.168.0.1). Не забыть установить исполняемому файлу атрибут исполняемого фалйа: chmod +x имя-исп-файла. Установить в системе dotnet core 3 (читайте справку Майкрософта).

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


Багги


А дальше начинаются скачки по дюнам и прочим неровностям превью бета версии библиотек. На любых функциях типа client.ReadSymbol("MAIN.Counter") я получал ошибку:
Stack overflow.
Aborted (core dumped)
...и приложение падает. Интересно, что точно такую же ошибку я получал когда пытался использовать независимую сборку независимого разработчика AdsClient. Видимо, корень проблемы одинаковый.

В то же время можно создать хэндл client.CreateVarHandle("MAIN.Counter"), а затем нормально работать на функциях чтения-записи через хэндлы. Также нормально работает чтение по имени переменной через реактивную подписку, которая к тому же неплохо ложится на реактивность Avalonia:

var valueObserver = Observer.Create(val => {
    Dispatcher.UIThread.InvokeAsync(() => {
        var mwvm = (MainWindowViewModel)DataContext;
        mwvm.Counter = val.ToString();
    });
});

IDisposable subscription =
    session.Connection
        .WhenNotification("MAIN.Counter", NotificationSettings.Default)
        .Subscribe(valueObserver);


То есть как-то работать уже можно и что-то рабочее уже вырисовывается:



Среда разработки


Теперь попробуем поработать под Linux — посмотрим в сторону среды разработки.

Как таковой среды еще нет, зато есть кроссплатформенный редактор Visual Studio Code, а к нему расширение vscode-st за авторством Сергея Романова. Расширение помогает работать с кодом на ST. Проект открытый, исходный код есть на GitHub. Самое главное, что проект не заброшен и развивается: я добавил туда пару новых штук, и внезапно разработчик принял пул-реквест, и так же мгновенно пересобрал расширение.


Подсветка синтаксиса, подсказка по переменным и ФБ, снипеты и прочие плюшки. Еще раз: это не отладчик и не среда разработки, а редактор кода, но это уже что-то. Остается дождаться чего-нибудь подобного TcXaeMgmt, ведь PowerShell Core уже работает под Linux, а роутероподобное ПО мы только что потрогали палочкой. Да что там, TcXaeMgmt уже можно установить, но работать командлеты не будут: отсутствует TwinCAT Automation Interface который построен на COM объектах.

Поэтому — ждем. Редактор файлов уже есть, остается прослойка для работы с загрузкой-выгрузкой файлов-проектов и пуск-стопом рантаймов, а там и до дебага недалеко (на самом деле, далеко).

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



February 2, 2017

События под максимальной нагрузкой

Насколько сильно нагружают процессор ПЛК асинхронные функции TwinCAT.ADS API? Стоит ли пользоваться подпиской на переменные ПЛК-задачи и сидеть в ожидании уведомлений об изменившихся данных или сразу написать polling-велосипед? Я уже измерял скорость передачи данных через ADS.API под .NET, теперь посмотрим как нагружают систему самые тяжелый асинхронные функции событийной модели.

TcAdsClient client = new TcAdsClient();
client.Connect("5.24.134.118.1.1", 801);


Имя для области памяти


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

PROGRAM MAIN
VAR
    a : WORD;
END_VAR

[...]

a := a + 1;
a := a AND 16#00FF; (* А *)

Наращиваем переменную MAIN.а типа WORD каждый цикл на единицу, а старшую часть обнуляем (* А *) чтобы она оставалась неизменной. Для начала подпишемся на всю переменную целиком и убедимся, что система работает.


Адрес переменной


На переменную можно подписаться через ее имя, а можно через адрес в памяти (индекс группы + смещение). И прежде, чем начинать подписываться, я хочу выяснить как мне узнать адрес переменной или хотя бы, где вообще лежат переменные ПЛК-задачи. Для этого воспользуемся функцией ReadSymbolInfo:

ITcAdsSymbol info = client.ReadSymbolInfo("MAIN.a");
Console.WriteLine($"Group:Offset = {info.IndexGroup}:{info.IndexOffset}");

Как результат получим адрес Group : Offset = 16448 : 0. В моем тестовом примере переменная в программе одна единственная, поэтому она расположена в начале области памяти, и поэтому ее смещение равно нулю.
В статье справочной системы "Index-Group/Offset" Specification of the PLC services можно найти упоминание, что память ПЛК-задачи начинается с адреса 0x00004040 = 16448.
Теперь, обращаясь через байтовые смещения, мы можем адресовать только часть переменной. Заменим имя этой переменной на адрес области памяти — Group: 0x4040, Offset: 0, длина 16 разрядов (тип ushort или WORD):

client.AdsNotificationEx += Client_AdsNotificationEx;
client.AddDeviceNotificationEx(0x4040, 0, AdsTransMode.OnChange, 0, 0, null, typeof(ushort));

[...]

private void Client_AdsNotificationEx(object sender, AdsNotificationExEventArgs e)
{
    Log(e.Value);
}


Система работает — как и было задумано: уведомления приходят в метод Client_AdsNotificationEx, значение переменной доступно через поле e.Value.


Часть вторая


Подпишемся теперь на младший байт переменной MAIN.a, то есть на ту половину переменной, которая увеличивается каждый цикл. Для этого мы изменим тип данных с ushort (16-разрядов — эквивалент WORD) на byte (8 разрядов):

client.AddDeviceNotificationEx(0x4040, 0, AdsTransMode.OnChange, 0, 0, null, typeof(byte));

Уведомления по прежнему приходят.

Теперь подпишемся на старшую половину переменной. Для этого достаточно увеличить смещение (Offset) на единицу, то есть перейти на один байт дальше:

client.AddDeviceNotificationEx(0x4040, 1, AdsTransMode.OnChange, 0, 0, null, typeof(byte));

Старший байт переменной искусственно обнуляется каждый цикл (* А *), поэтому старшая часть переменной остается неизменной. И всё — сообщения больше не приходят, кроме самого первого после подписки.
Так было задумано разработчиками — всегда приходит первое сообщение. До подписки мы не знали значение переменной, поэтому для нас оно было неопределенным. После подписки неизвестное значение сразу же может стать известным, поэтому система отправляет нам первое (и возможно единственное) сообщение о текущем значении переменной.

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


Подписываемся и замеряем


В качестве испытуемого возьмем CX9020. Начнем проверку с TwinCAT 2, а чуть позже сравним с третьей версией. Сейчас же идея такая: подписываемся на пару сотен переменных, а затем смотрим через System Manager нагрузку на процессор. Пишем код:

PROGRAM MAIN
VAR
    Arrgh : ARRAY [1..10000] OF WORD;
    i     : INT;
END_VAR

[...]

FOR i := 1 TO 1000 DO
    Arrgh[i] := Arrgh[i] + 1;
END_FOR


Подписываемся:

for (int i = 0; i < 200; i++)
    client.AddDeviceNotificationEx(0x4040, i * 2, AdsTransMode.OnChange, 0, 0, null, typeof(ushort));


Замеряем:


Очень много для программы, которая практически ничего не делает. Давайте попробуем приблизиться к заявленному пределу в 550 переменных (Асинхронные функции TwinCAT.Ads API):


Здесь 500 переменных. Результат ужасный.


Полл Шред


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

[...]

Прошел час. Пропустим несколько итераций эксперимента и сразу рассмотрим опрос 10 000 переменных. Для этого нам просто необходим отдельный поток:

CancellationTokenSource cancelTokenSource;

private void StartButton_Click(object sender, EventArgs e)
{
    cancelTokenSource = new CancellationTokenSource();
    SynchronizationContext uiContext = SynchronizationContext.Current;
    Thread pollThread = new Thread(() => PollThread(cancelTokenSource.Token, uiContext));
    pollThread.Start();

[...]

private void PollThread(CancellationToken token, object uiContext)
{
    using (var client = new TcAdsClient())
    {
        client.Connect("5.24.134.118.1.1", 801);

        while (!token.IsCancellationRequested)
        {
            for (int i = 0; i < 10000; i++)
            {
                if (token.IsCancellationRequested)
                    goto EndOfThread;

                ushort a = (ushort)client.ReadAny(0x4040, i * 2, typeof(ushort));
            }

            Thread.Sleep(0);
        }

        EndOfThread:;
    }
}


Старт. Результат:



Это был регулярный опрос 10 000 переменных за раз. Если понравилось — давайте попробуем регулярно считывать целиком весь массив. Для этого заменим цикл for на чтение массива целиком:

ushort[] buf = new ushort[10000];
buf = (ushort[])client.ReadAny(0x4040, 0, buf.GetType(), new int[] { buf.Length });


Результат аналогичный — нагрузка не превышает 20%.


Грузите апельсины бочками


Асинхронные функции сильно нагружают систему контроллера. Для старших моделей ПЛК слово "сильно" можно заменить на "значительно", но легче от этого не станет. Тем не менее, если нужно следить за десятком переменных, то можно обойтись асинхронными функциями. Если же переменных много, то есть два выхода: сделать опрос вручную или объединить переменные в структуру/массив.

Рассмотрим второй вариант. Я изменил код и подписался сразу на целый массив из 10 000 ячеек типа WORD. Теперь, чтобы пришло сообщение, достаточно изменить значение только одной ячейки массива (или одного поля структуры, если мы объединили переменные в структуру):

ushort[] buf = new ushort[10000];
client.AddDeviceNotificationEx("Main.Arrgh", AdsTransMode.OnChange, 0, 0, null, buf.GetType(), new int[] { buf.Length});


Результат:



Вывод: перед отправкой упаковывайте данные в структуры и отслеживайте их как единое целое.


TwinCAT 3.1


В третьем твинкате все замечательно — можно смело выкинуть огорчения из предыдущих пунктов и смело использовать подписку на переменные. И хотя нагрузка на ПЛК снизилась в разы, асинхронные функции по прежнему дают ощутимую нагрузку на процессор ПЛК.

Подписываемся на 1000 переменных (контроллер тот же — CX9020):



Но(!) по прежнему нельзя подписаться на 10 000 переменных — большое количество подписок приведет к ошибке: "Ads-Error 0x751 : A hashtable overflow has occured". Это внутренняя ошибка переполнения словаря переменных в библиотеке TwinCAT.Ads, то есть по прежнему имеем ограничение на количество переменных. Мелочь, а неприятно.


Замыкаем


Пора найти плюсы и минусы и замкнуться в сферах применения:

Подписка на события (событийная модель):
+ Удобство использования: методы обработки данных вызываются только тогда, когда переменная изменилась.
+ Регулярность сообщений не зависит от программы на ПК.
+ Снижение нагрузки на сеть: обменом данными занимаются AMS-роутеры, которые отправляют только измененные данные.
– Ограничение на частоту уведомлений (не быстрее 1 миллисекунды).
– Ограниченное количество переменных: 550 штук — TwinCAT 2, несколько тысяч в TwinCAT 3.
– Чем больше переменных, тем выше нагрузка на процессор контроллера.

Регулярный опрос (поллинг):
+ Неограниченное количество переменных.
+ Низкая нагрузка на процессор ПЛК.
+ Максимальная скорость обмена данными (до 50 мкс, быстрее TwinCAT просто не умеет).
– Требуется вручную обмениваться и обрабатывать значения переменных.
– Дополнительная нагрузка на сеть. Запросы и ответы ходят по сети регулярно, независимо от того — изменились данные или нет.
– Дополнительная нагрузка на программу ПК и сложность в синхронизации данных в многопоточном приложении.

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

И в любом случае TwinCAT 3 работает быстрее и легче.

June 7, 2016

Работа с CANopen из C# программы

Большинство модулей расширения на шине EtherCAT внутри себя работают через собственную шину: CANopen, SERCOS, и т. п. Чтобы напрямую обратиться к внутренним регистрам модуля, необходимо выполнить ряд подготовительных операций, а точнее, правильно сформировать AmsNetId, индекс группы и смещение в функциях чтения через протокол ADS. Включать ADS-сервер шины EtherCAT при этом не требуется.
Про работу с SERCOS можно прочитать в статье S и P параметры сервоусилителей.

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



Передать адрес модуля в программу на C# можно через ПЛК-программу и выделенную переменную с заранее известным именем, либо извлечь через TwinCAT Automation Interface. Первый вариант — проще, второй — универсальный.

Для демонстрации я взял модуль инкрементального энкодера EL5151. Просто потому, что у него красивый артикул и внутри шина CANopen.

Индекс группы сервиса ADS, отвечающего за работу с CANopen SDO:
EC_ADS_IGRP_CANOPEN_SDO = 0xF302;
Индекс смещения формируется как двойное слово (32-разряда), состоящее из индекса и субиндекса адреса CANopen:
SHL( WORD_TO_DWORD(nIndex), 16 ) OR BYTE_TO_WORD( nSubIndex )

В качестве примера я хочу прочитать 1018:02 (Identity : Product code), т. е. я хочу прочитать из модуля его артикул:

using System;
using TwinCAT.Ads;

class Program
{
    static long EC_ADS_IGRP_CANOPEN_SDO = 0xF302;

    static void Main(string[] args)
    {
        const string ETHERCAT_BUS_AMSNETID = "192.168.1.18.2.1";
        const int EL5151_AMSPORT = 1002;

        TcAdsClient client = new TcAdsClient();
        client.Connect(
            ETHERCAT_BUS_AMSNETID,
            EL5151_AMSPORT);

        // CANopen SDO Identity:Product code = 1018:02
        uint cansdo_Index = 0x1018;
        uint cansdo_SubIndex = 0x02;

        long CanSdoOffset = cansdo_Index << 16 | cansdo_SubIndex;

        uint Identity_ProductCode =
            (uint)client.ReadAny(
                EC_ADS_IGRP_CANOPEN_SDO, 
                CanSdoOffset,
                typeof(uint));

        Console.WriteLine(
             string.Format(
                "1018:02 = 0x{0} ({1})",
                Identity_ProductCode.ToString("X2"),
                Identity_ProductCode));

        Console.ReadKey();
    } // Main
} // class


Сравниваем полученный результат:



Аналогично можно записывать параметры, при условии, что они доступны для записи. На картинке выше колонка Flags отображает доступность регистров: RO — только для чтения, RW — чтение/запись.