среда, 17 августа 2016 г.

Тысячи TON

Иногда нужно подождать несколько миллисекунд или переждать час-другой. В таком случае на помощь приходит стандартный блок TON. Так ли он прост или есть нюансы?

Погружаясь в глубины TcBase.lib, туда, где живет наш функциональный блок, мы вскоре ударяемся о дно: внутренности блока TON прячутся в толще TwinCAT, а хвост ведет к ядру операционной системы. Как же нам узнать каким образом отсчитываются интервалы времени: банальный счетчик или что-то посерьезнее? У меня есть догадки, и я попробую подойти к вопросу медленно и с другой стороны.

Нафантазируем систему в которой необходимо много таймеров с разными интервалами срабатывания и различными временами старта. Под словом "много" я принимаю порядки 100, 1000, 10'000, ... и чуть позже станет понятно зачем мне необходимо так много.

Для испытаний возьмем CX9020 (ARM, WinCE) и TwinCAT 2, а затем попробуем старшего брата — CX5010 (x86, WinCE) и наконец CX2030 с настольной операционной системой и TwinCAT 3. Для всех тестов настройки будут по умолчанию: базовое время = 1мс, цикл ПЛК-программы = 10мс.

Загрузим тестовую программу в которой каждый цикл обрабатывается очень много таймеров (одновременно):

PROGRAM MAIN
VAR CONSTANT
    HOW_MANY_TONS : UDINT := 100;
    START_TIME    : TIME  := t#100ms;
    NEXT_PLUS     : TIME  := t#1ms;
END_VAR
VAR
    i      : UDINT;
    pt     : TIME := START_TIME;
    timers : ARRAY [1..HOW_MANY_TONS] OF TON;
    state  : UINT;
END_VAR

[...]

CASE state OF
0:
    FOR i := 1 TO HOW_MANY_TONS DO
        timers[i](IN:= FALSE, PT := pt);
        pt := pt + NEXT_PLUS;
    END_FOR

    state := 100;

100:
    FOR i := 1 TO HOW_MANY_TONS DO
        timers[i](IN:= TRUE);
        IF timers[i].Q THEN
            timers[i](IN := FALSE);
        END_IF
    END_FOR
END_CASE

Делаем срез производительности системы при нулевой активности (программа приостановлена). Нагрузка — максимум 18%. На графике плохо видны пики-выбросы, поэтому кажется что нагрузка чуть меньше, но она есть:



Запускаем 100 таймеров — нагрузка возрастает до 23%:



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




Может создаться впечатление, что вызывается слишком много ФБ за один цикл, но на самом деле это не так, и для опровержения я создал ФБ, который интерфейсом повторяет TON, но внутри просто наращивает переменную каждый цикл. Результат совершенно другой: при вызове 1000 имитаций нагрузка не растет, а остается на прежнем уровне 21%.

Вывод: TON не так прост внутри, как снаружи. Внутри ФБ производится какая-то тяжелая работа.


Старшие братья и TwinCAT 3


Можно предположить, что такое поведение свойственно только младшим контроллерам или происходит только на ARM-платформе, но и это не так.

Как более производительный, CX5010 отваливается на 10'000 таймерах. Правда он при этом выдает более ровную нагрузочную кривую, но нам интересно не это. Для CX2030 и настольной операционной системы, можно ожидать лучшего, но там процессор мощнее, поэтому есть рост количества таймеров, но нагрузка все равно ощущается как нечто постороннее. Зато TwinCAT 3 наконец-то не отжирает время у операционной системы. На картинке ниже виден переход от порядка 10'000 к 100'000; система реального времени уже не справляется, но терпеливо держит планку на заданном ограничении 80%:



Объяснение


Похоже, что TON ведет в глубины операционной системы к системным таймерам и может быть даже к высокоточному таймеру HPET. Так что же делать?


Что делать?


Во первых, если от таймеров нужна не сверхточность, а их количество — можно сделать один "продолжительный" таймер и всюду использовать его внутреннее поле ElapsedTime, которое показывает сколько времени прошло с начала запуска таймера... Или может быть сделать свой простой таймер со счетчиком-инкрементом?

Во вторых, зачем вам в проекте столько таймеров? Не забывайте про этот пункт. Я пытаюсь понять из чего сделан TON, а не улучшить его, разработав готовое решение для своих проектов.


Все-таки делаем свой таймер


Попробуем доработать нашу имитацию до полноценного таймера. Я назвала его TONLi, потому что он облегченный (light) и потому, что есть LTON (long, еще более длинный).

FUNCTION_BLOCK TONLi
VAR_INPUT
    IN        : BOOL;
    PT        : TIME;
    TaskIndex : BYTE := 1;
END_VAR
VAR_OUTPUT
    Q         : BOOL;
END_VAR
VAR
    state     : BYTE;
    endCount  : UDINT;
END_VAR

[...]

IF IN THEN
    IF state = 0 THEN
        Q := FALSE;
        endCount :=
            SystemTaskInfoArr[TaskIndex].cycleCount +
            TIME_TO_UDINT(PT) * 10000 / SystemTaskInfoArr[TaskIndex].cycleTime;
        state := 10;
    END_IF
ELSE
    state := 0;
END_IF

IF state = 10 THEN
    IF SystemTaskInfoArr[TaskIndex].cycleCount >= endCount THEN
        Q := TRUE;
        state := 255;
    END_IF
END_IF

Интерфейс почти такой же: на выходе потеряли ElapsedTime (с ним будет чуть медленнее из-за дополнительных расчетов); на входе теперь нужно уточнять номер текущей задачи. Ее можно узнать с помощью функции GETCURTASKINDEX. Об этом чуть позже.
В TwinCAT 3 нужно заменить SystemTaskInfoArr на _TaskInfo и можете убрать ссылку на TcSystem.lib. Подробнее читайте в справочной системе о PlcTaskSystemInfo.

Я все еще сделать немного сложнее, чем можно было: если взять обычный счетчик, то придется вызывать таймер каждый цикл и малейший пропуск собьет весь счет времени. Если же использовать .сycleCount — вы всегда будете знать сколько циклов уже прошло (длина цикла фиксирована еще на этапе настройки системы и не меняется во время работы). В этом наш таймер похож на стандартный TON.


_TaskInfo


Чтобы в случае пропуска циклов, не терять кусок прошедшего времени, нам нужен некий независимый отсчет времени. Его мы возьмем из информации о ПЛК-задаче. В большинстве (но не во всех!) CX-системах эта информация сохраняется в структуре, расположенной в меркерной области памяти. Обратите внимание на комментарий — настоящий адрес структуры может отличаться от приведенного (the real address may differ!):

SystemTaskInfoArr AT %MB32832 (*The real address may differ!*) : ARRAY [1..4] OF SYSTEMTASKINFOTYPE;

Что в этой структуре поможет нам сейчас или пригодится в будущем:
  • firstCycle : BOOL — задача запущена и отрабатывает свой первый цикл.
  • cycleTime : UDINT — длина цикла заданной ПЛК-задачи в 100 наносекундных отрезках. Если разделить это число на 10'000 — получится значение в миллисекундах.
  • lastExecTime : UDINT — количество 100нс интервалов отработанных предыдущим циклом. Не длина предыдущего цикла (эта длина для всех одинакова), а сколько реально он работал. Задача простаивала остаток времени.
  • cycleCount : UDINT — необходимый нам счетчик циклов, прошедших с момента старта ПЛК-задачи.


Новый таймер под микроскопом


Будем проводить испытания на CX9020 и 10'000 таймерах:


Все еще работает, а ведь раньше срезался на 1000 штуках. Еще раз: было 100 — стало 10'000. Проверим на CX5010, а лучше сравним TON (левая половина графика) и TONLi (правая половина):



Вряд ли кому-то понадобятся тысячи TON таймеров, но теперь мы точно знаем, что они откусывают по кусочку от нашей производительности.

четверг, 4 августа 2016 г.

Доступ к портам ввода/вывода

TwinCAT позволяет превратить обычный ПК в PC-based Control System, то есть в итоге мы получим обычный ПК, который сможет управлять промышленными объектами. В общем, так и есть, но сам ПК по прежнему с нами и все его "плюшки" по прежнему готовы к употреблению.

Начнем с простого — графическая система. ПК позволяет запускать "тяжелые" графические системы: вы можете взять Unity3D, нарисовать "тяжелую", трехмерную, интерактивную визуализацию, а затем через TwinCAT.Ads API связать ее с ПЛК-программой реального времени. Ну, или просто запустить игру и убедиться, что она работает как на обычном десктопе:



Убедившись, что это работает, спустимся ниже — куда-нибудь ближе к железу ПК. Здесь TwinCAT предлагает нам, как специалистам умелым и разумным, прямой путь к железу ПК через порты/ввода.
Выдержка из справочной системы: прямой доступ к железу — не проблема, пока вы только читаете из портов. Проблемы начнутся, когда вы начнете писать в порты ввода/вывода железа.


Порты ввода/вывода


Доступ к железу ПК начинается с портов/ввода вывода, через которые мы общаемся с периферией. Там на системную шину выставляются адреса, затем данные, но если вы собрались работать с портами, то уже должны все это знать.

В справочной системе, для примера, приводится работа с "пищалкой" pc-speaker'ом; для работы которого сначала нужно записать значение делителя для системного таймера, а затем установить бит включения спикера (или сбросить его, когда он надоест своим пронзительным верещанием).

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


Готовая библиотека


Чтобы было проще, я обернул функцию спикера в библиотечную функцию FB_PcSpeaker. Саму библиотеку Tc3_PcSpeaker (с единственной функцией) можно скачать и установить в систему.
Как устанавливать библиотеки расписано в нюансах библиотек в TwinCAT 3.

Единственная функция проста до приличия:

Speaker : FB_PcSpeaker;
F, L, D : REAL;
M       : REAL := 1; // duration multiplier

[...]

Speaker(
    Freq    := REAL_TO_DWORD( F ),
    Length  := REAL_TO_TIME ( L * M ),
    Delay   := REAL_TO_TIME ( D ),
    Execute := TRUE );

IF NOT Speaker.Busy THEN
    Speaker(Execute:= FALSE);
    // Next beep, please...
END_IF


Исполняем мелодии


Для исполнения мелодий нам нужны ноты. Вычленим их из миди-файлов и запишем в глобальную переменную в виде массива троек чисел: частота, длительность, пауза после ноты (стандартная последовательность для утилиты beep из мира Linux). Так как все мелодии разные и было бы странным ожидать от них одинаковой длины, то сигналом стоп будет служить тройка нулей.

VAR_GLOBAL CONSTANT
(* Star Wars Imperial March *) 
    Beeps : ARRAY[0..201] OF REAL := [392, 350, 100, 392, 350, 100, (* [...] *), 0, 0, 0];
END_VAR


Для извлечения нот из массива произвольной длины, будем извлекать ноты через... указатели, копируя значения до тех пор, пока не встретится набор из трех нулей. Почти как тип STRING, оканчивающийся нулем /0, только три нуля.
Можно было бы воспользоваться функцией UPPER_BOUND для получения размера массива, но использовать мощный, промышленный контроллер для воспроизведения мелодий уже не оптимально, так что продолжаем развлекаться...

pNotes : PVOID;
szR    : UDINT := SIZEOF(REAL);

[...]

MEMCPY(ADR(F), pNotes, szR);
pNotes := pNotes + szR;
MEMCPY(ADR(L), pNotes, szR);
pNotes := pNotes + szR;
MEMCPY(ADR(D), pNotes, szR);
pNotes := pNotes + szR;


Вот и все, разве что выключить спикер напоследок. Воспользуемся экшеном:

Speaker.SwitchOff();

вторник, 2 августа 2016 г.

Упорядоченные структуры

В TwinCAT 3 система типов данных Data Type Unit (DUT) наконец-то эволюционировала во что-то вменяемое. Теперь она похожа на настоящую и ею удобно пользоваться.


Псевдонимы


ALIAS — позволяет называть другими именами стандартные типы данных.

TYPE FLOAT : REAL;
END_TYPE

или
TYPE DOUBLE : LREAL;
END_TYPE


Структуры


STRUCT — структуры, составные данные. Раньше нельзя было включать в них переменные ввода/вывода:


...а теперь можно:

TYPE ST_DATA :
STRUCT
    In  AT %I* : INT;
    Out AT %Q* : INT;
END_STRUCT
END_TYPE


Структуры стали ближе к классам — теперь их можно расширять:

TYPE ST_DATA_EX EXTENDS ST_DATA :
STRUCT
    Status : INT;
END_STRUCT
END_TYPE

Это сокращает количество кода и позволяет дополнять библиотечные типы данных без излишней копи-пасты:

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


Битовый поля


В структуры можно включать битовые поля, доступные по имени. Для этого существует специальный тип данных BIT. Он занимает ровно 1 бит. На переменную типа BIT нельзя ссылаться указателями и ссылками. Также нельзя использовать их в массивах.


Перечисления


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



Каждое значение имеет свое собственное имя. Имя самого перечисления (E_PI) стало пространством имен этих значений. Теперь нельзя употреблять имена значений (HISTORICAL) без имени перечисления (E_PI).

CASE pi OF
    E_PI.HISTORICAL:
        DrawCircle();

    E_PI.PRECISE:
        DrawRoundCircle();

    E_PI.WARTIME:
        DrawRectangleFast();
END_CASE


Можно задать базовый тип для перечисления, но на выбор только целые типы данных (byte, word, uint, int, и т. п.). Также нельзя использовать псевдонимы (ALIAS) в качестве базовых типов.

TYPE E_PI :
(
    HISTORICAL := 3,
    PRECISE    := 314,
    WARTIME    := 4
) INT;
END_TYPE

По умолчанию, в качестве базового типа выступает INT. Значение первого поля = 0, остальные последовательно увеличиваются на единицу.


Объединение


UNION — для неопределившихся в типе переменной.

TYPE VARIANT :
UNION
    Integer : INT;
    Float   : REAL;
    Double  : LREAL;
    Text    : STRING(4);
END_UNION
END_TYPE

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

v : VARIANT;

[...]

v.Integer := 4;
v.Double  := 3.14;
v.Text    := 'PI';
v.Float   := 1.7;

пятница, 29 июля 2016 г.

Вебинар. Механизм распределенного времени

5 июля прошел вебинар по механизмам распределенного времени (EtherCAT | Distributed Clocks: Functional principle, application and diagnostics). Это простая в использовании технология и просто гигантская по объему, если копнуть чуть глубже. Как и вайфай — все пользуются, но мало кто знает как это работает внутри. По сути, вебинар пересказывает содержимое справочной системы, раздел Distributed Clocks, где кратко даны выдержки из документации по EtherCAT. Пришлось разбавить краткость дополнительными материалами.


Основы


В каждом устройстве поддерживающем DC, есть те или иные счетчики, ведущие отсчет времени или временных интервалов. Устройств много и у каждого свое время, но все они находятся на одной шине. Мастер шины, в нашем случае — это ПК/ПЛК с TwinCAT, старается циклически и как можно чаще отправлять пакет данных с сигналом о синхронизации.
Не все подчиненные должны, и не все поддерживают часы распределенного времени (DC [ди́-си́], Distributed Clock). Просто не всем оно нужно и наличие его — не делает модуль лучше.

Для начала, кто-то из подчиненных становится эталоном распределенных часов (reference clock). Это просто одно из подчиненных устройств (даже не мастер шины) с достаточно точным и стабильным источником временных отсчетов. Обычно, это первое устройство на шине, поддерживающее DC. Остальные устройства с поддержкой DC синхронизируются с этими часами (включая мастера), то есть они отстраивают свои часы согласно этому эталонному устройству.
Распределенное время — это не какое-то всемирное время или время дня, суток или время прошедшее с момента старта устройства, а просто некий счетчик для DC-часов, позволяющий синхронизировать временные интервалы. 
Плюс-минус 50-100 наносекунд — это обычная точность распределенных часов. Счетчик 64-разрядный, его должно хватить где-то лет на 500. Тикает он, согласно спецификации, с 10 наносекундным интервалом.


Железо


Внутри EthreCAT ASIC-чипа есть специальный блок ESC (EtherCAT Slave Controller) в том числе отвечающий за DC. Запуск системы часов распределенного времени состоит их нескольких фаз, первой из которых является измерение задержки распространения (propagation delays).

Каждый из подчиненных внутри себя имеет входную и выходную отметки времени, по которым можно легко вычислить на сколько задерживается пакет на каждом из подчиненных. Этот процесс контролирует мастер шины, отправляя одну или несколько широковещательных команд BWR (Broadcast Write). Эта команда адресует специальный регистр DC Receive Time Port, расположенный по адресу 0x0900.

После сбора всех временных отметок (time stamps), мастер шины, который отлично знает топологию шины, может рассчитать задержки распространения для каждого сегмента кольца шины. Затем, для каждого подчиненного, который умеет DC, мастер индивидуально записывает в специальный регистр System Time Offset смещение между эталонным временем и локальным отсчетом времени. Размер регистра те самые 8 байт или 64 разряда расположенные по адресу 0x0920. Теперь каждый DC-подчиненный может узнать системное время просто сложив свое локальное время (хранящееся в регистре Local Time) со сдвигом (offset).

Со временем локальные часы могут "уплыть" в большую или меньшую сторону, поэтому мастеру необходимо постоянно корректировать это отклонение (drift). Для этого постоянно отправляются специальные команды ARMW (Auto Increment Read Multiple Write) или FRMW с эталонным временем.
Эта команда всегда отправляется в самой быстрой задаче.
Подчиненное устройство (терминал) получив эту команду, сверяет значение локального времени и ускоряет или замедляет свои локальные часы.


Режимы работы


На вкладке DC → Operation Mode, можно увидеть следующие варианты режимов работы распределенных часов:
  • Oversampling — с оверсамплингом, можно задать частоту отправки SyncUnit и частоту семплера, который для получения дополнительных отсчетов работает быстрее SyncUnit.
  • DC Latch — только с DC, не работает без него.
  • FreeRun / SM-Synchron — синхронизация по фреймам EtherCAT.
  • DC-Synchron / DC-Synchron (input based) — TwinCAT делит устройства на группу ввода и группы вывода (плюс информационная зеленая группа). DC может синхронизироваться или там, или там. Этот вариант позволяет выбрать откуда подчиненное устройство будет брать DC-сигнал.


Тонкая настройка


I/O → devices → EtherCAT шина → EtherCAT - Advanced Settings... → Distributed Clocks

DC ModeAutomatic DC Mode Selection — по умолчанию, установлен и означает автоматический выбор эталонных часов. Обычно, этот выбор правильный.

SettingsContinuous Run-Time Measuring — регулярно пересчитывать карту временных задержек. Применимо в случаях частой замены кабелей связи, либо при использовании групп горячего подключения (кабель может быть другой и он будет другой длины).
→ Sync Windwos Monitoring → Sync Window (мкс) — уровень предупреждения при выходе за пределы.
→  Show DC System Time — это копия системного времени, а не локальное DC-время! Отображается в желтой закладки Input, но(!) отстает на один цикл от точного значения, так как необходимо время на получение и отображение значения. В ПЛК-программе лучше использовать множество  функций из библиотек TcEthercat, которые дадут точное время.

SYNC Shift Time — сдвиг времени, обычно автоматически определяется правильно при старте системы. При условии, что система работает и построена правильно.


Диагностика


Diagnosis — у нас есть два отсчета DC-времени: системное — эталонное и локальное. Они немного отличаются. Даже в жесткой цикличности все-равно есть небольшие отклонения (deviation), которые тем не менее не должны привести к сбою синхронизации. Нужно постоянно подстраиваться. Здесь мы можем посмотреть на срез подстройки: в самом низу, проценты не должны убегать далеко от 50%.

Slave Diagnosis — диагностика подчиненного, появилась в TwinCAT 3. Здесь можно тестировать и смотреть графики, а затем регулировать SYNC Shift Time.


Хозяйке на заметку


  • Wc-Error (working counter) — возможно необходимо поднастроить SyncUnit или подчиненный вообще выпал из рабочего режима (не в том режиме).
  • Теряете фреймы (Lost frames) — проверьте CRC.
  • Возможно, не хватает питания на EtherCAT-шине.
  • Проверьте целостность кабелей и разъемов.
  • Возможно неправильно отрегулированы DC-часы.
  • Для оптоволокна (EK1122, EK1100) — проверьте правильно ли выбраны порты: часто путают входные-выходные.
  • Проверьте DC Diagnosis: deviation должен быть рядом с 50% (51% / 48%).
  • Идем глубже: в онлайн режиме проверьте у подчиненного ESC регистр 0x0910.
  • Также проверить работает ли отсчет SYNC0/1 в регистре 0x0990?
  • Если вдруг и внезапно все показатели равны 4.2 секунды, то возможно у вас модуль с оверсемплингом и вы получили переполнение 32-разрядных регистров.
  • Слишком малое время цикла. Учитывайте, что на пересылку 1400-байтного пакета EtherCAT требуется порядка 125 микросекунд.
  • Большой jitter — включите IO at task begin.
  • Для перезапуска DC — мастер должен побывать в INIT состоянии.
  • Кто эталон? Всегда хороший выбор — это коплер Бекхофф EK1100, так как в нем установлен ASIC-чип Beckhoff, а он очень стабильный и красивый.
  • Большое количество Queued Frames — все очень плохо, систему нужно переконфигурировать или перестроить с начала и совсем.
  • CU2508 поддерживает до 4-х колец с резервированием линий и DC.

вторник, 26 июля 2016 г.

Вебинар. Оптимизация нагрузки многоядерных систем TwinCAT 3

30 июня Кнут Гётель провел вебинар на тему оптимизации нагрузки многоядерных систем в TwinCAT 3 (TwinCAT 3 | Optimised utilisation of multi-core features). Большая часть нюансов многозадачности в TwinCAT 3 уже рассматривалась, поэтому далее только крошки.


Как обстоят дела в TwinCAT 2:
  • Рантайм может исполнятся только на одном ядре.
  • Рантайм работает только в 32-х разрядных системах.
  • Выбор ядра на котором будет выполняться рантайм производится через реестр (можно считать, что 98% пользователей об этом не знает).

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

Как обстоят дела в TwinCAT 3:
  • Отзывчивость (Latency) меньше одной микросекунды (на тестовом Intel Core i5).
  • Настраиваемый уровень предупреждений превышения уровня отзывчивости: если вероятен сильный джиттер (jitter, дрожание, отклонение) — можно задать уровень паники. Будет вылезать мутная табличка, что на этапе наладки скрасит скудность средств отладки производительности.
  • Вроде бы гарантируют не превышение отведенного времени для TwinCAT. Вероятно, в случае, когда TwinCAT нагружен чересчур сильно, он перестанет отнимать время у бедного Windows. И по косвенным результатам, этому уже есть подтверждения.


Нюансы многоядерности


Задачи цифрового управления движением NC SAF и NC SVB должны быть закреплены за одним ядром. Для CNC такого ограничения нет.

Для запуска TwinCAT и Windows на одном и том же ядре в 64-х разрядной системе необходима поддержка процессором VT-x технологии. Во всех остальных случаях (AMD, старые процессоры, виртуальные машины и т. п.), придется запускать TwinCAT на отдельном ядре. Причем для TwinCAT, ядро нужно изолировать от Windows, т. е. уделить TwinCAT 100% процессорного времени.

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


Взаимодействие ПЛК-модулей


Когда ПЛК-задачи запускаются на разных ядрах, у нас все еще остается общая память и ряд других общих ресурсов. Поэтому возможны несколько способов обмена данными: одни быстрее, другие — надежнее.

1. Маппинг данных между операционными образами.
  • Доступно для всех языков.
  • Гарантируется целостность данных.
  • Передача выполняется синхронно с тактами задачи (пульс задачи, task pulse).

2. Указатель на область данных.
  • Не гарантирует целостности данных.
  • Прямой синхронный доступ из программы (данные никуда не копируются, прямой доступ к ним).

3. Вызов методов через интерфейс.
  • Прямой, синхронный доступ.
  • Не гарантируется целостность данных

4. Протокол ADS.
  • Асинхронный и событийный способы доступа.
  • Клиент-сервер.
  • Вертикальное и горизонтальное применение для обмена данными.
  • Доступен обмен между ядрами.
  • Доступен обмен по локальной/глобальной сети (например, через TCP/IP).
  • Целостность данных — гарантируется.

вторник, 5 июля 2016 г.

Ссылки и указатели

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


Указатели


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

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

Массивы — это тоже указатели, только завуалированные; и это была первая попытка человечества перестать отстреливать себе конечности, так как для массивов есть проверка на выход за границы массива.
Указатель на массив повторно разрешает членовредительство.

Чтобы подобраться ближе к данных на которые указывает указатель, используют операцию ^  -разыменование указателя. Указатель от этого не портится и не разыменовывается, мы просто записываем данные в ту ячейку, на которую указывает указатель.

PROGRAM MAIN
VAR
    pt_a    : POINTER TO INT;
    pt_pt_a : POINTER TO POINTER TO INT;
    fbTest  : FB_Test;
    pt_fb   : POINTER TO FB_Test;

// [...]

a       := 123;
pt_a    := ADR(a);             // pt_a указывает на 'a'
pt_pt_a := ADR(pt_a);          // pt_pt_a указывает на pt_a который указывает на 'a'
pt_a^   := 67;                 // 'a' теперь равно 67
pt_fb   := ADR(fbTest);

fbTest(inp := a, in_out := a);
pt_fb^(inp := a, in_out := a); // разыменование указателя на ФБ и запуск ФБ


Ссылки


REFERENCE TO — ссылаться можно только на данные (и структуры, они тоже данные) и указатели (получим второе имя для указателя). Ссылки с арифметикой не дружат, поэтому они надежнее: выйти за границы приличия не получится.

Можно считать, что ссылка это та же самая переменная только с другим именем: ячейка памяти одна — имени два (или три, или четыре, сколько пожелаете).

PROGRAM MAIN
VAR
    a         : INT;
    ref_a     : REFERENCE TO INT;
    ref_pt_a  : REFERENCE TO POINTER TO INT;
    pt_ref_a  : POINTER TO INT;

// [...]

a         :=    123;
ref_a     REF=  a;
pt_ref_a  :=    ADR(ref_a); // указатель на ссылку
ref_a     :=    67;         // 'a' теперь равно 67
ref_a     REF=  0;          // ссылка больше ни на что не указывает
pt_ref_a^ :=    44;         // 'a' теперь равно 44


Для получения ссылки используется оператор REF=. Слитно три буквы и знак равно, все без пробелов. Это и есть оператор получения ссылки. На картинке ниже видно как переменная и ссылка на нее принимают одинаковое значение = 67:



Стоит обратить внимание на указатель pt_ref_a, который хоть и пытается указывать на ссылку ref_a, но мы то уже знаем, что ref_a это всего-лишь второе название для переменной 'a'. Поэтому в итоге указатель указывает на переменную 'a' типа INT.



После присваивания ссылке нуля (в предпоследней строке), указатель pt_ref_a по прежнему указывает на переменную 'a', хотя ссылка ref_a уже никуда не ссылается. Это подтверждает что мы просто удаляем "второе" название переменной 'а', в то время как сама переменная остается на месте (соответственно, сохраняется и указатель на нее).


Зачем ссылки?


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

Давайте воспользуемся ссылкой, ведь для нее арифметика не работает! Все правильно, и за нас это уже сделали.

Любой ФБ, кроме входных параметров VAR_INPUT и VAR_OUTPUT имеет набор VAR_IN_OUT. Если первые два принимают входные данные по значению, то последний IN_OUT принимает входной параметр как ссылку. Изменяя ее значение внутри ФБ, мы сможем изменять значение переменной снаружи ФБ, ведь ссылка это второе имя переменной.

FUNCTION_BLOCK FB_Test
VAR_INPUT
    inp    : INT;

VAR_OUTPUT
    out    : INT;

VAR_IN_OUT
    in_out : INT;

// [...]

inp    := 44;
out    := 55;
in_out    := 77;


Запускаем и смотрим в отладчике на результат:



ФБ изнутри изменил значение внешней переменной 'a', сменив ее значение на = 77. Изменение входного параметра inp внутри ФБ на = 44, никак не повлияло на значение переменной 'a', так как он принимает параметр по значению (просто создает локальную копию значения).


Массивы переменной длины


Мы разрабатываем ФБ, который как-то там должен изменять содержимое массива (например, заполнять латинскими лорем-ипсумами или нулями). Внимание, вопрос — как передавать в ФБ массивы разной длины?

FUNCTION_BLOCK POUPIUPOW
VAR_IN_OUT
    VarArray : ARRAY [*] OF INT;
END_VAR
VAR
    start    : DINT;
    end      : DINT;
END_VAR

// [...]

start := LOWER_BOUND(VarArray, 1);
end   := UPPER_BOUND(VarArray, 1);


Работает только для VAR_IN_OUT параметров (иначе как бы мы влияли на внешние к ФБ массивы).
Совместимо с третьей редакцией стандарта МЭК.
Для определения габаритов или просто начала и конца массива используются функции LOWER_BOUND и UPPER_BOUND. Второй параметр этих функций — это номер размерности: 1 — получить длину одномерного массива; 1..2 — длину строки или кол-во столбцов для двумерного массива; 1..3 — длина строки, столбца или глубины для 3D массива.

четверг, 9 июня 2016 г.

Нюансы библиотек в TwinCAT 3

В TwinCAT 3 изменился подход к библиотекам. Он усложнился.

Новые библиотеки бывают двух видов:
  • .library — с исходными кодами и собранной библиотекой. Занимает больше места и не скрывает интеллектуальную собственность.
  • .compiled-library — только собранная библиотека, без исходных кодов. Очень небольшого размера и зашифрована. Восстановить исходный код невозможно.

Эти файлы, как сейчас стало модно, являются zip-архивами с человеко-невменяемым содержимым.

Стандартный репозиторий (хранилище библиотек) расположен здесь:
x:\TwinCAT\3.1\Components\Plc\Managed Libraries.

Далее идут: организация разработчик → имя библиотеки → версия → и непосредственно сама библиотека. В итоге для библиотеки Tc2_EtherCAT получается следующий путь:
x:\TwinCAT\3.1\Components\Plc\Managed Libraries\Beckhoff Automation GmbH\Tc2_EtherCAT\3.3.7.0\Tc2_EtherCAT.compiled-library-ge33

Рядом с библиотекой лежат три файла (ничего интересного):
  • projectinfo — бинарная информация о библиотеке;
  • dependencies — текстовый файл со списком библиотек от которых зависит данная библиотека;
  • browsercache — вспомогательная системная информация в виде xml-файл.

Если библиотека поставляется с исходными кодами, то вместо файла .compiled-library-ge33 будет лежать файл .library.


де / Инсталляция


Нельзя просто так взять и удалить библиотеку. Добавить тоже не просто так.
Вообще-то можно, но TwinCAT будет ругаться и последствия могут быть произвольными.

Теперь недостаточно просто скопировать файл библиотеки, его необходимо установить в локальное хранилище (репозиторий). Для это ткнуть правой кнопкой мыши ветку References и выбрать Library repository... Затем воспользоваться кнопкой Install... Аналогично для деинсталляции, только кнопка Uninstall.


Плейсхолдеры вместо библиотек


На самом деле в References проекта добавляются не библиотеки, а плейсхолдеры (placeholders), которые служат оберткой вокруг библиотеки и позволяют всегда загружать необходимую версию библиотеки для разного оборудования и разных версий TwinCAT.

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


Разработка


Для того чтобы создать новую библиотеку необходимо добавить к ветке PLC → Empty PLC Project. После окончания разработки функций библиотеки, достаточно ткнуть правой кнопкой мыши экземпляр PLC-проекта: ветка PLC → <имя проекта> Project, и выбрать Save as library... или Save as library and install... чтобы еще и установить сразу же в локальный репозиторий.


Чуть больше подробностей для разработчиков библиотек можно почерпнуть в статьях Intro PLC Libraries и Library Management.

вторник, 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:
SHLWORD_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 — чтение/запись.

вторник, 24 мая 2016 г.

Облачный коплер EK9160

Теперь в станок можно установить коплер EK9160, который будет автоматически собирать сигналы и регулярно жаловаться в облако на фрезеровщика Федора, когда тот в очередной раз запорет фрезу. В конце же месяца региональный менеджер выведет на "плазму" тренд аналитики и все поймет.

  • Он тупой (я про коплер) и без мастер-контроллера во главе, зато умеет собирать сигналы и отдавать данные сразу в облако. Программированию коплер не поддается.
  • Легко конфигурируется сам и предоставляет доступ к своим модулям на шине через веб-страничку.
  • Существует возможностью сохранять и восстанавливать настройки модуля из локального файла.
  • Поддерживает EL, EP и другие подчиненные модули шины EtherCAT.
  • Отзывчивый и быстрый, со свичом для организации ромашки (сосиски, daisy chain) из роутеров.
  • Есть физическая переключалка для простой настройки IP-адреса.
  • На борту установлена микро-SD карта объемом 2 гигабайта. Сюда коплер складывает данные, когда облако не доступно.
  • Батарейки входят в комплект. Благодаря чему, всегда тикают часы реального времени (RTC). Синхронизация происходит через протокол SNTP. Питание ему все равно необходимо: батарейка подпитывает только часы.
  • К коплеру можно подключить коммуникационные модули (например, CAN и/или PROFIBUS) и построить гетерогенную сеть.
  • Push-технология избавляет от входящих подключений — это позволяет безопасно и удобно интегрировать коплер в существующую сетевую инфраструктуру. Достаточно просто подключить его к локальной сети с выходом в интернет.

Еще раз — этот коплер независим (standalone). Ему не нужны мастера и другие контроллеры. Именно поэтому его очень удобно встраивать в отдельно стоящие станки, исполнительные механизмы и другие объекты.

четверг, 19 мая 2016 г.

Новые функции TwinSAFE

На международной выставке Hannover Messe 2016 рассказали о новых функциях в TwinSAFE-логике и контроллерах безопасности EL6910 / EJ6910:
  • До 212 TwinSAFE подключений
  • 512 функциональных блоков.
  • 128 TwinSAFE групп.
  • До 40 пользователей в политике безопасности.
  • Резервные копии/восстановление.
  • Диагностическая история хранится в модуле TwinSAFE-логики.


Связь


Модули безопасности могут связываться посредством:
  • Safety-over-EtherCAT — стандартный способ инъекции пакетов безопасности с максимально высоким приоритетом между пакетами EtherCAT.
  • TwinSAFE SC (TwinSAFE Single Channel, одноканальный) — модуль одновременно выступает и как модуль ввода/вывода обычных сигналов, и как модуль ввода/вывода сигналов безопасности.
  • Передача пакетов безопасности через PROFISafe (как мастер, так и подчиненный). При использовании гетерогенных сетей.


PROFISafe (мастер/подчиненный):
  • Работает только на встроенных и системных шинах (backplane|sub-system busses), через драйвер ПК работать не будет.
  • Работает только на шинах PROFIBUS/PROFINET.
  • Требует наличия терминатора-сегмента PROFISafe EL9930.


Функции безопасности


Обновлен ряд ФБ. В частности разрешили использовать таймеры с точностью от 1 мсек. до 600 сек.

ФБ масштабирования целочисленных значений (safeScaling):
  • масштабирование с коэффициентом и со смещением;
  • округление деления;
  • между аналоговым входом и выходом помещен модуль масштабирования;
  • установка флага ошибки при выходе за пределы во время операции масштабирования;
  • сторожевой таймер для контроля за тупиковыми ситуациями на аналоговом входе.

Специализированный блок инкремента с фиксатором (safeSLI) позволяет зафиксировать значение позиции и контролировать дельту фиксированной величины и текущей реальной, с контролем диапазона в плюс/минус.

safeViolationCNT — счетчик ошибок с регулируемым значением инкремента/декремента и пределом счета.
safeEnvelope — контроль спада огибающей сигнала
safeCamMonitor — ФБ для контроля сигналов безопасности прессов.


Редактор логики

  • Разрешили использовать сторонние FSoE устройства (с помощью ESI-файлов)
  • Упрощена работа с контрольными суммами CRC.
  • Упрощена линковка ФБ и TwinSAFE групп.
  • Можно выгружать проект TwinSAFE-логики по частям.
  • Более продвинутое управление правами безопасности пользователей: кто и что может, более детально.
  • Можно активировать/деактивировать TwinSAFE группы: как временно, так и постоянно.
  • Пассивные TwinSAFE группы. В основном предназначаются для аварийной остановки с мобильных панелей.
  • Просмотр диагностической истории, хранящейся прямо в модулях.


Safety PLC


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

Язык программирования поддерживает следующие типы данных:
  • Булевый.
  • Целочисленные вплоть до 32-разрядных (Int8, Int16, Int32, UInt8, UInt16, UInt32).
  • Безопасные и небезопасные типы данных (вероятно имеется в виду с контролем и "без" выхода за разрядную сетку?).
  • Структуры из атомарных типов.

С данными можно проводить следующие операции: сложение, вычитание, умножение, деление, булевы операции, битовые операции, сравнения (==, !=, <, >, <=, >=), приведение типа.

Управляющие структуры:
  • If-Else
  • While
  • For
  • Switch/case

Файлы Safety C организованы по принципу одна TwinSAFE-группа  = 1 исходному файлу + заголовочный файл.