среда, 30 ноября 2016 г.

TcHVAC: Структура библиотеки и проекта

Функционал управления отоплением, вентиляцией и кондиционированием (ОВК, ОВиК, HVAC: Heating, Ventilation, & Air Conditioning) в TwinCAT оформлены в виде библиотеки TcHVAC. Библиотека стоит денег и обозначена артикулом TS8000. В справочной системе можно найти в разделе: TS8000 | TwinCAT PLC Lib: HVAC.


Структура справки и структура каталогов в библиотеке различается, поэтому будем отталкиваться от структуры библиотеки, так как она ближе к практике. Следует сразу учесть, что библиотека строилась с учетом специфики разработки в виде функциональных блоков (FBD, CFC) и на ST временами будет выглядеть уродливо: за универсальность приходится платить.

Как правило, простые системы HVAC строятся исходя из релейной логики — "тумблеры и лампы": нажали кнопку, собралась цепочка, загорелась лампа (включился мотор, открылся вентиль и т. п.). Для полного описания такой системы составляют таблицу входов/выходов, оптимизируют ее (например, вручную с помощью карт Карно) и только затем переносят в виде LD-диаграмм или блоков FBD|CFC. На практике так никто не делает. Поэтому библиотека построена так, что следуя ее логике, цепочка функциональных блоков сложится сама: библиотека просто настроена на это — ставите один блок, за ним неминуемо тянутся другие.

Управление объектами строится на базе одного или каскада ПИД-регуляторов. Причем есть как классические (универсальные) регуляторы, так и специализированные, заточенные под управление конкретными объектами ОВиК, что позволяет объединять управляемые объекты в цепочки типа заслонка-рекуператор/подмешивание-подогреватель-увлажнитель-вентилятор-итп (смотри картинку выше).


Каталоги библиотеки


  • Actuators — насосы, моторы, одно-, двух-, трех-скоростные, резервирование и авторотация основной-резервный по времени наработки. Под мотором можно понимать целый исполнительный механизм, например, вентилятор.
  • Analog modules — преобразование аналоговых данных в обе стороны. Исполнительные механизмы с аналоговым управлением и датчики контролируемых параметров: температура PT100/1000, давление, управляющие сигналы 0-10В, 4-20мА или сигналы обратной связи и т. п. Параметры можно масштабировать в обе стороны.
  • BackupVar NOVRAM — автосохранение и автовосстановление переменных и настраиваемых параметров функциональных блоков библиотеки в NOVRAM памяти контроллера.
  • BackupVat Persistent — аналогично предыдущему пункту, но сохранение производится на флешку ПЛК.
  • Controller — различные регуляторы: ПИД, двухточечные, цепочки регуляторов, регуляторы заточенные под конкретный исполнительный механизм.
  • Room functions — специализированные функции для управления параметрами в жилых помещениях: климат-контроль, освещение, засветка Солнцем.
  • Setpoint modules — формируют кривые и рампы задания, основываясь на времени суток, летнем-зимнем сезоне и т. п.
  • Special functions — вспомогательные функции, помогающие преобразовывать типы данных, контролировать фронты входов/выходов или просто универсальные функции-обертки, позволяющие строить универсальные алгоритмы, подходящие для ПЛК различных типов. Самый простой пример — функция Blinker для мигания лампочкой с интервалом в одну секунду.
  • System — обще-системные функции: чтение/установка часов операционной системы, создание копий параметров функциональных блоков, получать сведения о циклах ПЛК-задачи и немного других системных функций.
  • Time schedule — планировщик действий по расписанию на один день, неделю, месяц, по праздникам и будням и т. д.


Структура проекта


Входная точка управления вентиляцией (и всем остальным) расположена в недрах функционального блока FB_HVACStartAirConditioning. Основная роль этого ФБ — быть диспетчером или дирижером всего оркестра устройств. Именно к нему прикрепляются все остальные функциональные блоки, именно он дает разрешение в виде сигнала Enable на блоки управления заслонками, вентиляторами, насосами и т. п.

Параллельно с блоком StartAirConditioning, каждый цикл вызывается ряд блоков, отвечающих за системные функции. Типичный набор этих функций выглядит так:
  • Считывание уличной температуры с компенсацией, FB_HVACOutsideTempDamped.
  • Мигалка, формирователь однополярного меандра, FB_HVACBlink.
  • Сохранение энергонезависимых данных, FB_HVACPersistentDataHandling.
  • Чтение системного времени, FB_HVACGetSystemTime.
  • Чтение параметров ПЛК-задачи, FB_HVACSystemTaskInfo.
  • Дополнительно: трансляция данных наружу через один или несколько протоколов передачи данных (например, Modbus).

Не все из них требуют вызова каждый цикл, но некоторые особо чувствительны к этому. Например, FB_HVACSystemTaskInfo. Казалось бы, система TwinCAT — это система жесткого реального времени; достаточно прочитать параметры задачи (Task) один единственный раз и больше они не изменятся. Проблема в том, что время цикла фиксировано, но ПЛК-задача может выполняться быстрее отведенного для нее времени цикла. В то же время функции для работы с ПИД-регуляторами требуют точного значения времени прошедшего с момента последнего вызова ФБ. Специально для этого в ФБ FB_HVACSystemTaskInfo есть поле tCycleTime, содержащее значение количества времени затраченного на выполнение ПЛК-задачи в предыдущем цикле — как долго выполнялся предыдущий цикл.

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

В ответ ФБ StartAirConditioning выдает сигналы на специализированные ФБ, которые управляют исполнительными механизмами: воздушные заслонки, вентили подогревателя, увлажнители, вентиляторы и другие устройства.

Если такая структура вас не устраивает — можно самостоятельно разработать логику управления. Все "большие" блоки построены на основе других, более специализированных функций библиотеки.

четверг, 24 ноября 2016 г.

Взгляд со стороны клиента ModbusTCP

Копнем чуть глубже — как выглядит сервер ModbusTCP с точки зрения клиента — по каким адресам модбаса нужно стучаться и что мы там найдем. Для свежести в памяти — обзор катушек и холдинг регистров и настройка сервера Modbus TCP.

После установки сервера ModbusTCP у нас уже есть настроенный конфигурационный файл, которым заранее удобно пользоваться. Все адреса десятеричные и все это можно позднее переделать под себя в том же конфигурационном файле. Пока же воспользуемся параметрами заданными по умолчанию в конфигурационном файле. Сетевой порт TCP = 502, полезная справочная документация = Mapping between Modbus and ADS.


Работа со стандартными массивами


Немецкие инженеры о нас позаботились — четыре заранее сформированные массива открывают доступ к данным Modbus. Причем открывают без какой-либо головоломки с расчетом индексов, упаковкой битов в слова и других отвлекающих преобразований.

После установки на ПЛК сервера ModbusTCP, последний автоматически ищет четыре глобальные переменные с заранее заданными именами.
Эти имена (или пара индекс-смещения) задаются в конфигурационном файле в разделах <VarName> или парой <IndexGroup> // <IndexOffset>.
Еще раз: в самой ПЛК-программе, вам ничего добавлять не нужно: сервер сам попытается найти эти имена среди глобальных переменных, и если найдет — автоматически начнет трансляцию данных по шине Modbus. Включать в проект дополнительные библиотеки не требуется, просто добавляем в глобальные переменные четыре массива:

.mb_Input_Coils [0..255] OF BOOL
.mb_Output_Coils  [0..255] OF BOOL
.mb_Input_Registers [0..255] OF WORD
.mb_Output_Registers  [0..255] OF WORD

С точки зрения ModbusTCP массивы получают адреса начиная с 3276810 (или 0x800016). Расчет полного адреса ведется с учетом индекса в массиве:

.mb_Input_Coils [0..255] OF BOOL
.mb_Output_Coils  [0..255] OF BOOL

Адрес Modbus = 32768 + индекс в массиве (0..255)

Одна катушка или дискретный порт ввода-вывода соответствует одной ячейке массива типа BOOL. Это очень удобно, так как не требуется упаковка битов в "слово". Аналогично с регистрами:

.mb_Input_Registers [0..255] OF WORD
.mb_Output_Registers  [0..255] OF WORD

Адрес Modbus = 32768 + индекс в массиве (0..255)

В каждом массиве всего 256 элементов, поэтому адресация этих массивов заканчивается для Modbus на числе 33023. Полный диапазон 32768..33023.


Переменные ввода/вывода


Дополнительно к массивам, автоматически транслируется область переменных ввода-вывода, то есть также одновременно транслируются те переменные, которые связаны с физическими входами/выходами или, иначе говоря, переменные, которые обозначены как AT %I* и AT %Q*. Простые правила помогут запомнить что-с-чем ассоциируется:
  • Катушки (coils) можно намагничивать и размагничивать — поэтому они могут менять свое значение и поэтому они связаны с AT %Q*. Аналогичное правило для регистров хранения (holding registers): в них тоже можно сохранять и читать.
  • Inputs — это входа и они только для ввода данных, поэтому записывать нельзя — AT %I*.

Чтение с помощью функций модбаса:
  • Катушки %QX — функция #1, Read Coils.
  • Регистры хранения %QB, %QW,... — #3, Read Holding Registers.
  • Дискретные входа %IX — функция #2, Read Discrete Inputs.
  • Дискретные регистры ввода %IB%IW,... — #4, Read Input Registers.

Запись доступна только для катушек и регистров хранения, по одному за раз, но вообще можно и несколько за раз (функции 15 и 16):
  • Катушки %QX — функция #5, Write/Force Single Coil.
  • Регистры хранения %QB, %QW,... — #6, Write/Preset Single Register.

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

Адрес Modbus для переменных ввода данных (AT %I) лежит в диапазоне 0..32767. Для переменных ввода/вывода (AT %Q, катушки и регистры хранения) верхняя граница занижена 0..12287, иначе они будут пересекаться с меркерной памятью (читай ниже).

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

%IX#.@ (где # — номер байта, @ — номер бита в байте)
%QX#.@

Адрес модбас = # * 8 + @


Пример:

bSomeVar AT %IX6.2 : BOOL;

Адрес модбас = 6 * 8 + 2 = 50


Для байтовых входов/выходов все проще:

%IB# (где # — номер байта)
%QB#

Адрес модбас = #


"Слова" состоят из двух байт, поэтому идут с шагом два. Например, для %IW и %QW:

iSomeVar0 AT %QW0 : WORD;
iSomeVar1 AT %QW2 : WORD;
iSomeVar2 AT %QW4 : WORD;
[...]
iSomeVarN AT %QW* : WORD;

Адрес модбас = N * 2


Меркерная память


Для шины Modbus эта область памяти (AT %M*) лежит в диапазоне адресов 12288..24575. Адреса строятся аналогично переменным ввода/вывода из предыдущего пункта.
Обратите внимание, что адреса Modbus для меркерной памяти пересекаются с адресами переменных ввода и в то же время не пересекаются с адресами переменных ввода/вывода. Это происходит по причине доступности переменных %M как на чтение, так и на запись, аналогично переменным ввода/вывода %Q. Именно поэтому M- и Q-области памяти  выглядят одинаково с точки зрения Modbus, а значит и доступны с помощью одних и тех же функций Modbus.


Порядок слов


Modbus как таковой ничего о типах данных не знает и перегоняет битовые единицы упакованными в "слово", а регистры просто как "слова" (WORD). При этом протокол ничего не знает ни о знаке числа (для него все числа целые), ни о плавающей запятой, ни о порядке байт в слове.

Например, я сейчас включил ПЛК и при использовании типа WORD получил следующую картину:

%MB0 — младший байт
%MB1 — старший байт

%MW0 — 12288 (%MB1 : %MB0)
%MW2 — 12289 (%MB3 : %MB2)
%MW4 — 12290 (%MB5 : %MB4)

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


Загадочные области памяти


На самом деле — нет, но начиная с 24576 идет область данных, где хранятся значения переменных ПЛК-программы. Оттуда можно вытащить через Modbus значения вообще любых переменных ПЛК-программы, но особого смысла ковыряться в ней — нет. Зачем? Когда есть все выше изложенное.

четверг, 3 ноября 2016 г.

Системный диалог даты-времени в Windows CE

Как быстро сделать ввод системной даты/времени при создании визуализации TwinCAT 2 под Windows CE? Нужно воспользоваться системным диалогом из панели управления — открыть  проводник explorer.exe, а дальше оператор сам найдет, исправит, настроит, испортит. Чтобы избежать такого поведения можно открывать только панель даты/времени.

1. Из меню Start запускаем notepadce.
2. Аккуратно вводим одну строчку, внимательно следя за пробелами и другими символами:
35#"ctlpnl.exe" \Windows\cplmain.cpl,13
35# — это количество символов в строке. Между ..ctlpnl.exe" и \Windows\... стоит пробел.

3. Файл сохраняем как datetime.lnk во флэш памяти контроллера (имя файла произвольное, если-что). Сохранить можно где-нибудь в \Hard Disk\datetime.lnk

4. Для элемента, при нажатии на который будет открываться панель настройки даты/времени, выставляем параметр визуализации (кавычки там обязательны):
Input → Execute program: "\Hard Disk\datetime.lnk"

Теперь при нажатии на элемент можно отображать только системную панель настройки даты/времени:



Можно открывать и другие апплеты панели управления:


PC Connection – 35#"ctlpnl.exe" \Windows\cplmain.cpl,0
Dialing – 35#"ctlpnl.exe" \Windows\cplmain.cpl,1
Keyboard – 35#"ctlpnl.exe" \Windows\cplmain.cpl,2
Password – 35#"ctlpnl.exe" \Windows\cplmain.cpl,3
Owner – 35#"ctlpnl.exe" \Windows\cplmain.cpl,4
Power – 35#"ctlpnl.exe" \Windows\cplmain.cpl,5
System – 35#"ctlpnl.exe" \Windows\cplmain.cpl,6
Display – 35#"ctlpnl.exe" \Windows\cplmain.cpl,7
Mouse – 35#"ctlpnl.exe" \Windows\cplmain.cpl,8
Stylus – 35#"ctlpnl.exe" \Windows\cplmain.cpl,9
Volume & Sounds – 35#"ctlpnl.exe" \Windows\cplmain.cpl,10
Input Panel – 35#"ctlpnl.exe" \Windows\cplmain.cpl,11
Remove Programs –  35#"ctlpnl.exe" \Windows\cplmain.cpl,12
Date/Time – 35#"ctlpnl.exe" \Windows\cplmain.cpl,13
Certificates – 35#"ctlpnl.exe" \Windows\cplmain.cpl,14
Accessibility – 35#"ctlpnl.exe" \Windows\cplmain.cpl,15


Для открытия CX Configuration или полностью Beckhoff CX Configuration Tool, нужно запустить другой апплет:
35#"ctlpnl.exe" \Windows\CxConfigCpl.cpl

Для юных исследователей доступен каталог \Windows, который может содержать (и содержит) другие апплеты панели управления в виде файлов с расширением .cpl

вторник, 18 октября 2016 г.

Я сделаю свой NC с рампами и траекториями

В частотных приводах AX5000 и сервомодулях EL72xx изначально заложена возможность прямого управления сервоусилителями: кидаете задание Velocity command value, формируете слово управления Master control word и постоянно следите за словом состояния Drive status word. По сути вам необходимо самостоятельно обрабатываете все, что есть в AT|MDT ветках сервоусилителя. Просто и дешево, но:
  • Не умеет масштабировать величины. Все значения задаются в инкрементах. Хотя можно реализовать пересчетные функции (как уже сделано в NC).
  • Только базовый набор функций: вкл/откл силового питания, вращение с заданной скоростью, абсолютное позиционирование, контроль тока, ограничение момента.
  • Все управление идет из ПЛК-задачи: если в программе тех. процесса допустить ошибку, то она отразится в том числе и на работе сервоусилителя, так как они все вместе работают внутри одной и той же задачи (в отличии от NC, которая работает как независимая подсистема TwinCAT).
  • Зависимость от времени цикла ПЛК-задачи. NC не зависит от времени цикла задачи, что положительно сказывается на скорости реакции, плавности/точности траекторий и пр. 
  • Нет разгонно-тормозных рамп, но можно реализовать их самостоятельно.
  • Зависимость от режима работы сервоусилителя: нельзя задать скорость вращения в режиме позиционирования и обратная ситуация — нельзя выйти в абсолютную позицию в режиме управления по скорости.
  • Нет ни гарантии, ни тех. поддержки: умер разработчик — умерла система.

Должны быть плюсы:
  • Дешево.
  • Будет работает на всем, что может обеспечить EtherCAT-мастер, в том числе и на контроллерах CX8010 + EK1110.


Что-то подобное уже всплывало в зачем нужен NC PTP и что он себе позволяет.

вторник, 11 октября 2016 г.

SIL-3

EL6900 и желтые Safety-терминалы соответствуют системному уровню надежности SIL-3. Будет ли разрабатываемая система считаться безопасной или нужны дополнительные процедуры?

В документации по желтым модулям постоянно встречается упоминание SIL-3, а в конце тех. документации есть изображение сертификатов, которые к тому же можно скачать отдельным pdf-документом. В дополнение к ним можно посмотреть на сертификаты таможенного союза (Россия, Белоруссия, Казахстан). Там же есть соответствия требованиям промышленной безопасности, которое говорит, что модули можно использовать в промышленности и они будут соответствовать бюрократическим требованиям.

В общем, не важно когда начинать разрабатывать программную и safety-логики: до согласования или после сертификации. Модули уже соответствуют SIL-3. Это означает, что они готовы и могут обеспечить соответствующие требования МЭК/TUV. Правда соответствие модулей не означает, что вся система будет соответствовать требованиям безопасности только потому, что в ней все модули безопасности соответствуют SIL-3. Да и сам SIL-3 говорит только о степени надежности отдельных модулях, а не о безопасности всей системы в целом.

Еще раз: модули соответствуют требованиям SIL-3. Функционал модулей достаточен для обеспечения соответствующего уровня надежности системы. Что в итоге "вылепит" разработчик — никому не известно.

Как итог, приемку системы в целом, будет осуществлять заказчик по своим нормам и правилам или делегирует в какую-либо сертификационную/проверочную организацию. Лучше с самого начала узнать у заказчика, что он собственно подразумевает, когда просит SIL-3 и работать в соответствии с этим.

Хорошая статья в тему: SIL — это не сложно.

вторник, 4 октября 2016 г.

Как добавить точку в ломаную линию?

В преддверии новой визуализации на html5 и css3 поковыряем нюансы старой-доброй визуализации под TwinCAT 2.

Начнем с простого — надписью может быть любой элемент, достаточно отключить у него бордюр: Colors → No frame color.

Кнопкой может быть любой элемент, но элемент Button (кнопка) ведет себя как кнопка и не требует дополнительных телодвижений от разработчиков. Правда она не так удобна в плане изменения своего внешнего вида, зато всегда ведет себя как кнопка, а не как ХЗ-что.

Поле Variables → Invisible — это в прямом смысле "невидимость". TRUE → элемент невидим, FALSE → элемент отображается, то есть его видно.

Значение параметров фигуры (цвет, размер и пр.) могут задаваться в нескольких местах одновременно и тогда одни значения начинают перекрывать другие. Можно задать величину параметра как статическое значение (например, цвет в поле Colors). Это значение перекроется значением, заданным в виде переменной  из раздела ColorVariables. Переменную, в свою очередь, может перекрыть программная структура из раздела Programmability. При отладке программы, значения переменных введенные через Watch-n-Recipe перекрывают вообще все.

Можно добавить новую точку в ломаную линию или многоугольник: удерживая клавишу Ctrl, тянем левой кнопкой мыши за уже существующую точку и вытягиваем из нее новую точку. Ctrl + Shift + клик — удаляет уже существующую точку.

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

Motion absolute:
  • Углы задаются в градусах.
  • Масштабирование, оно же Scale: 1000 = 100% размера фигуры. Масштабирование касается всей фигуры в целом, в том числе и расстояния до центра фигуры.
  • Y-offset, X-offset — сдвинуть всю фигуру влево-вправо / вверх-вниз.
  • Angle — поворот относительно центра (кружок с перекрестием), но(!): Shape (прямоугольники и эллипсы) вращаются так, что верх фигуры остается всегда вверху. Polygon (многоугольники, контуры, кривые) вращаются так, что все точки совершают вращение относительно центра фигуры. Если вам нужен квадрат вращающийся относительно своего центра — нарисуйте его из ломаной поли-линии.

Можно добавить один экран визуализации на другой экран в виде элемента меньшего размера. Размер такого элемента определяется типом масштабирование пропорций:
  • Anisotropic — произвольные пропорции и размер.
  • Isotropic — фиксированные пропорции, но произвольный размер.
  • Fixed — фиксированные пропорции и размер.
  • Fixed and scrollable — как и Fixed, но можно прокручивать (не работает в Windows CE, см. раздел про ограничения).

Пунктирные, штрихпунктирные и прочие тире-тире-точки можно получить, задав ColorVariables → Frame flag отличный от нуля. Тип линии меняется только во время работы визуализации и не меняется во время разработки:
  • 0 сплошная ____
  • 1 пунктир - - - -
  • 2 точки ......
  • 3 тире-точка -.-.-
  • 4 тире-точка-точка -..-..-
  • 8 невидимая, но ее толщина учитывается

Input → Execute Program — это целый отдельный мир программирования. Здесь можно создавать макросы (Macro). Например, при нажатии кнопки Сохранить на экране задания параметров, сменяется текущая визуализация (Input → Zoom to viz.). Можно сделать так, что одновременно со сменой визуализации будет задаваться значение какой-либо переменной (как глобальной, так и локальной в подпрограмме). Таким образом можно отследить, что пользователь уходит с экрана настроек и необходимо сохранить persisitent-переменные, если он нажал ОК, и не сохранять, если он ушел по кнопке Отмена.

Programmability → Object name, если установить галку и ввести имя переменной, то после перекомпиляции проекта появляется новая глобальная переменная-структура типа VisualObjectType. Объявление этой переменной не видно в проекте, но она как суслик все-равно есть и доступна для Intellisense по F2.


Все эти вопросы неплохо рассмотрены в справочной системе, но на английском языке (или немецком) и все равно всплывают раз-за-разом:

среда, 28 сентября 2016 г.

Вебинар. Новое поколении TwinCAT HMI

Много клиентов, много серверов, много рантаймов и много всего остального. Основной акцент на слово "много". Сводный обзор по возможностям и полезностям визуализаций в TwinCAT 3 провел Дэймон Томпсон (Daymon Thompson). По окончании на вопросы ответил глобальный продукт-манагер Свен Обершмидт (Sven Oberschmidt). По прежнему хочется "мяса" из инженеров.



Все это уже было, но теперь подводятся какие-то итоги и обещают уже в начале следующего года англоязычную версию.

Основные черты продукта:
  • Основан на технологиях пришедших из интернета: html5, css3, javascript.
  • Клиенты из-под браузеров (в том числе и Edge) и клиенты на мобильных системах Android и iOS, ведь там тоже есть браузеры, только мобильные.
  • Система рассчитана на продолжительный срок жизни: придет и останется с нами надолго.
  • Всё такое масштабируемое, модульное и независимое, векторное и веб-ориентированное. Если кратко и без патетики — должно стать удобнее как в разработке, так и в применении.


TwinCAT HMI Creator


Визуальный редактор: не нужно программировать, просто рисуйте и редактируйте готовые шаблоны.
  • Интеграция с Visual Studio.
  • Интеграция с TwinCAT.
  • Редактор с упором на визуальную разработку.
  • Использование систем контроля версий (tfs, svn, git, ...).
  • Менеджер пакетов.
  • Готовые шаблоны проектов.
  • Темы оформления.
  • Графическая библиотека готовых элементов: SVG, сложные и составные элементы.


TwinCAT HMI Framework


  • Готовые контролы (элементы управления интерфейсом):
    • стандартные;
    • графики, чарты, события/логи/журналы (events);
    • пользовательские (самодельные) контролы.
  • Применение сложных типов данных (структуры, дата/время и пр.).
  • Интернационализация:
    • переключение языков;
    • национальные единицы измерения, меры длины, денежные знаки и т. п.
  • Расширябильность засчет html5 и javascritpt.


TwinCAT HMI Server


Веб-сервер написанный Бекхоффом (фирмой, а не человеком). Обещают, что будет работать на всем, что начинается с CX9020. В том числе и на Windows CE.

  • ADS — что позволяет использовать рантайм как от TwinCAT 3, так и от TwinCAT 2.
  • OPC UA — что позволит использовать вообще все что угодно.
  • Цифровой осциллограф Scope. Пока не понятно куда и как он будет встраиваться, но возможно имеется в виду его серверная часть.
  • Безопасность данных (https, tls и прочий эS).
  • Уровни доступа пользователей (роли пользователей, login | logout).
  • Предварительно подготовленные данные для тестирования и отладки (recipe management).
  • Логирование событий.
  • Сбор данных.
  • Сможет расширяться модулями на C++ / C#.


Разработка


  • Сплошная интеграция с Visual Studio.
  • Живое взаимодействие с визуализацией во время разработки. Будет сразу видно, что и как работает.
  • Графический редактор с набором стандартных и не очень элементов.
  • Предварительно настроенные шаблоны, которые можно доработать. Обещают современный внешний вид с анимацией и прочими свистелками.
  • Контролы пользователя расширяются с помощью javascript. Серверная часть расширяется на C++ / C#. Можно подготовить заранее темы в корпоративном стандарте под брендбук, а затем использовать во всех остальных проектах.

Про архитектуру уже было в Новый HMI для TwinCAT 3 (8 декабря 2015). Про сценарии применения было в Визуализации в TwinCAT 3 (24 марта 2016), но если кратко:

  • Наиболее распространенный сценарий "локальный клиент": визуализация и браузер работают на одном контроллере, а графическая панель подключается через DVI.
  • Много клиентов через интернет (например, мобильные телефоны и планшеты) подключаются к локальному клиенту.
  • Много клиентов подключается к одной машине с сервером TwinCAT HMI, который собирает (агрегирует) данные с нескольких ADS-серверов (умных коплеров или младших моделей CX).
  • Много TwinCAT HMI серверов, которые подключены к одной машине, которая отображает сводные данные со всех других серверов (сервер серверов).

При этом сохраняется мультипротокольность: сервер общается с коплерами по любым протоколам, клиенты подключаются к серверу через http(s) и браузеры.


Лицензирование


  • Стоимость не зависит от количеств тегов. 
  • Инструменты разработчика бесплатны, включая обновление, сопровождение и поддержку.
  • Лицензирование касается только сервера. Клиенты работают бесплатно и не лицензируются.

Стоимость лицензии строится исходя из:
  • уровня платформы на которой будет работать TwinCAT HMI Server;
  • количества клиентов и целевых систем (target runtimes).

Пример


TF2000 HMI Server — рассчитан на 1 клиента (браузер) и  одну целевую систему (netId), то есть в базовой версии один клиент — один таргет.

Можно добавить клиентов или таргетов:
- TF20x0 Clients Pack 1/3/10/25 (пакет клиентов — один, три, десять дополнительных клиентов).
- TF20x0 Targets Pack 1/3/10/25 (пакет таргетов).

Можно добавить расширения визуализации HMI Extension (Scope, OPC UA) — это расширения для добавления протоколов или дополнительных функций.

В итоге будет сформирована лицензия TwinCAT 3 HMI License.


Итого


  • Быстрая разработка, где мало разработки и больше рисования.
  • HTML5, javascript.
  • Клиент не зависит от платформы. 
  • Модные веб-технологии.
  • Гибкая архитектура.
  • Модульность.


Вопросы-ответы


  • Разница между TwinCAT PLC HMI и TwinCAT HMI? Первый будет только патчиться, новые фичи будут появляться только в TwinCAT HMI.
  • Какие версии Visual Studio? Пока что 2013-2015. Дальше будет видно, а пока этого достаточно.
  • Локальные переменные будут доступны (транслируются) в контекст браузера. Надо смотреть что там имелось в виду.
  • Когда? В этом году (2016) немецкая версия, в начале следующего года для всех остальных, кто говорит на английском.
  • Веб-сессии (sessions) поддерживаются, информация о веб-клиенте доступна.
  • PLC HMI контролы и модные HMI контролы, да и другие элементы тоже, смешивать и перемещать туда-сюда нельзя. Вполне возможно, хотя и маловероятно, в будущем появится конвертер первого во второй, но не обратно.


Полный вебинар на английском языке TwinCAT HMI | The next HMI generation.

четверг, 8 сентября 2016 г.

Пропорционально-интегрально-дифференцирующий

С параметрами ПИД-регулятора можно играться как с кошкой — до бесконечности: пока в колебательный процесс не впадет или не надоест.

Несмотря на тонны матана, прячущиеся за тройкой входных параметров, около 90% регуляторов в мире основано на ПИД (гугл, википедия, нормальное распределение). Именно благодаря простому интерфейсу, скрывающему интегралы-дифуры, использовать ПИД-регулятор невероятно просто, главное правильно отстроить и не умереть от скуки.

TcControllerToolbox.lib — это основная профессиональная библиотека TwinCAT для работы с регуляторами, генераторами и прочими алгоритмами автоматизации. Там много разных штук, но мы посмотрим на ПИД-регуляторы, как самые распространенные и востребованные.


PLC Controller Toolbox


В прайсе эта библиотека обозначена артикулом TF4100. Это для TwinCAT 3. Если вам нужно модернизировать старую систему на TwinCAT 2, ищите TS4100. Библиотека платная, но построена профессионально — с учетом всего того, что просто необходимо учитывать.
Например: все ФБ библиотеки контролируют циклы и тайминги программных циклов. В случае повторного вызова ФБ за один и тот же цикл или если будет пропуск цикла — ФБ адекватно обработает эту ситуацию. Даже если будет пропущен не один цикл или не один раз.

Кроме непосредственно регулирования, блоки ПИД-регулятора умеют дополнительные опции.

FB_CTRL_PI
FB_CTRL_PID
  • Контроль интегрального насыщения, верхний и нижний пределы.
  • Ручная уставка задания в обход автоматической: можно вмешаться в работу регулятора.
  • Ручное отключение интегральной составляющей.
  • Жесткий контроль циклов ПЛК-задачи и таймингов.

FB_CTRL_PID_EXT
  • Зона нечувствительности для входного значения. При нахождении в ней значение выхода регулятора постоянно.
  • Зона нечувствительности для нулевой величины выхода.
  • Окно для отключения интегральной составляющей (она постоянна или равна нулю).
  • Окно для уменьшения масштаба интегральной составляющей.

FB_CTRL_PID_SPLITRANGE
  • Аналогично FB_CTRL_PID, но можно задать разные наборы коэффициентов "p-i-d" для охлаждения (температура ниже нуля) и для нагрева (температура выше нуля).
  • Имеет два различных выхода, но активен только один. В зависимости от величины выхода — больше нуля или меньше нуля, он запускает нагрев или охлаждение, обнуляя значение противоположного выхода. Кусок кода, для разнообразия:
fOut := fY;

IF fOut > 0.0 THEN
    fOutPos := fOut;
    fOutNeg := 0.0;
ELSE
    fOutNeg := fOut;
    fOutPos := 0.0;
END_IF

FB_CTRL_PID_EXT_SPLITRANGE
  • Всё и сразу: FB_CTRL_PID + _EXT + _SPLITRANGE.


Простой ПИД-регулятор


К тому же бесплатный. Можно было бы перевести дословно — "примитивный", но он работает и это — самое главное.

В бесплатной библиотеке TcUtilities.lib, есть встроенный функциональный блок FB_BasicPID, который умеет три параметра и — все. Больше он ничего не умеет (плюс демпфирование диф.-составляющей Td, но это у разработчика случайно получилось).

Главное отличие от профессиональной библиотеки — это:

FB_BasicPID
  • нет контроля интегрального насыщения;
  • нет контроля повторного или пропущенного вызова за цикл. Вызывайте строго один раз за цикл — иначе пожалеете.

С циклом более менее понятно, а вот отсутствие контроля за интегральным насыщением (integral windup, reset windup, integral saturation) может сыграть злую шутку. Мы сейчас попробуем поиграться с этим эффектом, и заодно убедимся, что функция регулятора действительно примитивная.

Да собственно уже играемся — длинная портянка графика справа показывает, как мы сначала нагрели нечто до 27 градусов, а затем пытаемся охладить, но что-то никак не получается...
Зеленый — задание-уставка.
Синий — выход ПИД-регулятора.
Красный — актуальное, текущее значение на выходе исполнительного механизма.
... А все потому, что не хватает возможности исполнительного механизма — он не справляется, а ПИД-регулятор все топит и топит педальку тормоза-охлаждения. И график ползет вниз...

Чтобы хоть как-то исправить ситуацию, мы попробуем ограничить выход регулятора в соответствии с возможностями нашего исполнительного механизма. Предположим, что он может нагреть до 60 и охладить до 18 условных градусов, условного цельсия:

PID(
    SetpointValue := TargetValue,
    ActualValue   := ActualValue,
    Kp := kp,
    Ti := ti,
    Tv := tv,
    Td := td,
    MaxCtrlOutput := Model.LimitHi,
    MinCtrlOutput := Model.LimitLow,
    CtrlOutput    => SetpointValue);

IF SetpointValue < 18 THEN
    SetpointValue := 18;
END_IF

IF SetpointValue > 60 THEN
    SetpointValue := 60;
END_IF

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

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



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

// расчет интегральной части
IF is_Ipart THEN
    IF (maxLimReached AND (e >= 0.0)) OR (minLimReached AND (e <= 0.0)) THEN
        yi := yi1;                    // замораживаем интегральную часть
    ELSE
        yi := yi1 + di * (e + e1);    // интегрируем
    END_IF
ELSE
    yi := 0.0;
END_IF;

[...]

y := yp + yi + yd;    // суммируем П-И-Д части для расчета выхода регулятора

// проверяем выход на ограничения
IF y < MinCtrlOutput THEN
    y := MinCtrlOutput;
    minLimReached := TRUE;
    maxLimReached := FALSE;
ELSIF y > MaxCtrlOutput THEN
    y := MaxCtrlOutput;
    minLimReached := FALSE;
    maxLimReached := TRUE;
ELSE
    minLimReached := maxLimReached := FALSE;
END_IF

CtrlOutput := y;      // формируем выход функционального блока


Проверяем:



Модель процесса


Это абстрактная модель "какого-то" объекта, имитирующая "какой-то" слегка нелинейный процесс. Чтобы придать модели линейность — уберите в расчете переменной delta часть под квадратным корнем (вместе с самим корнем SQRT) и оставьте только Grow и CycleTime.

PROGRAM Model
VAR_INPUT
    Run       : BOOL;
    Setpoint  : LREAL;
    Grow      : LREAL  := 0.1;    // градусы за секунду
    CycleTime : LREAL  := 0.01;   // секунды
    LimitHi   : LREAL  := 70;
    LimitLow  : LREAL  := 18;
END_VAR
VAR_OUTPUT
    Actual : LREAL;
END_VAR
VAR
    delta  : LREAL;
END_VAR

[...]

IF Setpoint > LimitHi THEN
    Setpoint := LimitHi;
END_IF

IF Setpoint < LimitLow THEN
    Setpoint := LimitLow;
END_IF
 
delta := Grow * CycleTime * SQRT(ABS(Actual * Actual - Setpoint * Setpoint));

IF Actual > Setpoint THEN
    Actual := Actual - delta;
ELSE
    Actual := Actual + delta;
END_IF

Если у вас есть модель получше — присылайте почтой, комментариями и пр.


Про диф.-составляющие


Чаще всего используется ПИ-регулятор. Дальше можете не читать.

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

Чтобы включить ПИД-регулятор в FB_BasicPID необходимо выставить оба диф. коэффициента в значения отличные от нуля. Причем основным диф. параметром является Tv (он расположен в числителе). Td — это время демпфирования и находится в знаменателе диф. составляющей. Его значение можно выставить равным единице и тогда останется только основная Tсоставляющая диф. компоненты ПИД-регулятора.

среда, 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();