Showing posts with label upper_bound. Show all posts
Showing posts with label upper_bound. Show all posts

August 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();

July 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 массива.