Обзор типов данных в Word для CoDeSys

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

 

Мой опыт работы с ПЛК составляет 3 года. Я разрабатывал под системы ПЛК: Beckhoff CX series, SE Modicon M221, WAGO 750 series. Используемые среды разработки: TwinCAT 3, EcoStruxure Machine Expert-Basic, CODESYS V2.3. Наибольшее время я посвятил работе с ST+TwinCAT 3, который основан на CODESYS и стандарте IEC 61131. Материал этой статьи был написан в связи с моим переходом из OT в IT.

 

Я хотел бы поделиться своим опытом, чтобы эти три года не прошли зря.

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

Если часто приходиться комментировать части кода — то узнайте какое сочетание клавиш позволит вам это сделать, это сэкономит много времени. В TwinCAT XAE Shell для комментирования выделенного кода: Ctrl+K+C и Ctrl+K+U для расскомментирования. Обезвредьте кнопку Stop, чтобы случайно не остановить ПЛК , иногда такое случайное нажатие может привести к нежелательным последствиям. В TwinCAT XAE Shell можно выбрать какие кнопки выводить на toolbar. После локальной отладки программы рекомендую скрыть кнопку остановки ПЛК .

Structured Text

STRING vs WSTRING

В TwinCAT 3 имеется возможность работы с Unicode строками. Они могут быть полезны для передачи уникальных символов, но в случае отсутствия необходимости, лучше избегать использования WSTRING.

STRINGWSTRING
ФорматASCIIUnicode
Размер символаBYTE (1 байт)WORD (2 байта)
ЗавершениеNull символ0

Date and time

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

F_GetSystemTime() (Функция из модуля Tc2_System)

Эта функция может быть использована для считывания метки времени операционной системы. Временная метка представляет собой 64-разрядное целое значение с точностью до 100 нс. Помимо прочего, его можно использовать для синхронизации задач или измерения времени. Одна единица соответствует 100 нс.

Время измеряется в количестве интервалов по 100 нс, начиная с 1 января 1601 года. Эти отметки сохраняются в переменных типа ULINT. Учитывая эти данные, мы без особых усилий можем вычислять временные интервалы с точностью до 100 нс! Все, что нужно сделать, это определить разницу между отметками. К сожалению, не удалось найти стандартные функции для конвертации временной отметки в тип DATETYPE, поэтому пришлось разработать такую функцию самостоятельно:

(* :Description: Преобразует время с 1 Января 1601 года в единицах 100 нс в DATE_AND_TIME (Convert time since 1 January 1601 in 100 ns to DATE_AND_TIME) :Usability: Преобразует временную метку в дату и время :Note: проверьте, что nSystemType больше 01.01.1970 00:00:00 История версий: Кожемякин Е. А. Создание 16.08.2021; *) FUNCTION F_SystemTimeToDT : DT VAR CONSTANT SECONDS_BETWEEN_1601_AND_1970 : ULINT := 11_644_473_600; END_VAR VAR_INPUT nSystemTime : ULINT; // Одна единица — 100 нс с 1 Января 1601 года END_VAR VAR nSeconds : ULINT; END_VAR
nSeconds := (nSystemTime / 10_000_000) — SECONDS_BETWEEN_1601_AND_1970; F_SystemTimeToDT := ULINT_TO_DT(nSeconds);

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

(* :Описание: Возвращает текущую дату и время в формате DATE_AND_TIME (DT) :Применение: Для получения текущей даты и времени в формате DATE_AND_TIME (DT) История версий: Кожемякин Е. А. Создание 16.08.2021; *) FUNCTION F_DateTimeNow : DT
F_DateTimeNow := F_SystemTimeToDT(F_GetSystemTime());

Функция для определения прошедшего времени в формате TIME

(* :Описание: Прошедшее время с момента tStart (Прошло времени с tStart) :Применение: Если нужно проверить, сколько времени прошло История версий: Кожемякин Е. А. Создание 16.08.2021; *) FUNCTION F_TimePassed : TIME VAR_INPUT tStart: ULINT; (* Время начала в 100нс с 01.01.1601, текущее время в этом формате предоставляет функция F_GetSystemTime()*) END_VAR
F_TimePassed := ULINT_TO_TIME((F_GetSystemTime() — tStart) / 10000);

Числовые константы

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

##значение

Пример: DINT#16#A1 Числовые значения могут быть двоичными числами, восьмеричными числами, десятичными числами или шестнадцатеричными числами. Если целое значение не является десятичным числом, его основание должно быть записано перед целочисленной константой, за которой следует символ хэша (#). Для шестнадцатеричных чисел цифры для чисел от 10 до 15, как обычно, представлены буквами A-F. Типом этих числовых значений может быть BYTE, WORD, DWORD, SINT, USINT, INT, UINT, DINT, UDINT, REAL или LREAL.

ANY type

В языках программирования со статической типизацией довольно сложно делать универсальные функции/функциональные блоки. Когда мне поставили задачу собирать и анализировать различные данные, я решил, что копировать функциональные блоки и изменять в них только тип входного значения — не лучший вариант. Тогда появилась идея приводить все типы к одному и по объективным причинам это тип LREAL. При реализации функции или метода вы можете объявлять входные данные (VAR_INPUT) как переменные с типом данных ANY. Далее вы можете получить указатель на значение, тип данных и размер переданной на этот вход переменной. Структура типа данных ANY

TYPE AnyType : STRUCT // тип актуального параметра typeclass : __SYSTEM.TYPE_CLASS ; // указатель на актуальный параметр pvalue : POINTER TO BYTE; // размер данных, к которым указывает указатель diSize : DINT; END_STRUCT END_TYPE

Кроме типа ANY существуют также дочерние типы: Хочу обратить внимание что на вход типа ANY не может быть подана константа, поэтому в некоторых случаях придётся создавать дополнительную переменную. Зная про этот тип мне удалось реализовать функцию, которая приводила данные разных типов к LREAL. Функция по преобразованию числовых типов в LREAL

(* :Описание: Преобразование ANY_NUM и ANY_BIT в LREAL :Применимость: Для разработки универсальных функций :Примечание: Действительными типами являются: ANY_NUM: — ANY_REAL: REAL, LREAL — ANY_INT: USINT, UINT, UDINT, ULINT, SINT, INT, DINT, LINT ANY_BIT: — BYTE, WORD, DWORD, LWORD История версий: Кожемякин Е. А. Создание 01.06.2021; Кожемякин Е. А. TO_LREAL 03.11.2021; *) ФУНКЦИЯ F_AnyNumToLREAL : LREAL VAR_INPUT AnyNum: ANY; // Переменная для преобразования, должна иметь адрес END_VAR VAR pReal : УПОТРЕБЛЕНИЕ REAL; // указатель на переменную типа REAL pLReal : УПОТРЕБЛЕНИЕ LREAL; // указатель на переменную типа LREAL pUSInt : УПОТРЕБЛЕНИЕ USINT; // указатель на переменную типа USInt pUInt : УПОТРЕБЛЕНИЕ UINT; // указатель на переменную типа UInt pUDInt : УПОТРЕБЛЕНИЕ UDINT; // указатель на переменную типа UDInt pULInt : УПОТРЕБЛЕНИЕ ULINT; // указатель на переменную типа ULInt pSInt : УПОТРЕБЛЕНИЕ SINT; // указатель на переменную типа SInt pInt : УПОТРЕБЛЕНИЕ INT; // указатель на переменную типа Int pDInt : УПОТРЕБЛЕНИЕ DINT; // указатель на переменную типа DInt pLInt : УПОТРЕБЛЕНИЕ LINT; // указатель на переменную типа LInt pByte : УПОТРЕБЛЕНИЕ BYTE; // указатель на переменную типа Byte pWord : УПОТРЕБЛЕНИЕ WORD; // указатель на переменную типа Word pDWord : УПОТРЕБЛЕНИЕ DWORD; // указатель на переменную типа DWord pLWord : УПОТРЕБЛЕНИЕ LWORD; // указатель на переменную типа LWord END_VAR VAR_OUTPUT OrginalType: __SYSTEM.TYPE_CLASS; bInvalidType: BOOL := FALSE; END_VAR
// Действительные числа IF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_REAL) THEN pReal := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_REAL; F_AnyNumToLREAL := TO_LREAL(pReal^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_LREAL) THEN pLReal := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_LREAL; F_AnyNumToLREAL := pLReal^; // Битовые числа ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_BYTE) THEN pByte := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_BYTE; F_AnyNumToLREAL := TO_LREAL(pByte^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_WORD) THEN pWord := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_WORD; F_AnyNumToLREAL := TO_LREAL(pWord^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_DWORD) THEN pDWord := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_DWORD; F_AnyNumToLREAL := TO_LREAL(pDWord^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_LWORD) THEN pLWord := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_LWORD; F_AnyNumToLREAL := TO_LREAL(pLWord^); // Беззнаковые целые ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_USINT) THEN pUSInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_USINT; F_AnyNumToLREAL := TO_LREAL(pUSInt^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_UINT) THEN pUInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_UINT; F_AnyNumToLREAL := TO_LREAL(pUInt^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_UDINT) THEN pUDInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_UDINT; F_AnyNumToLREAL := TO_LREAL(pUDInt^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_ULINT) THEN pULInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_ULINT; F_AnyNumToLREAL := TO_LREAL(pULInt^); // Знаковые целые ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_SINT) THEN pSInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_SINT; F_AnyNumToLREAL := TO_LREAL(pSInt^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_INT) THEN pInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_INT; F_AnyNumToLREAL := TO_LREAL(pInt^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_DINT) THEN pDInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_DINT; F_AnyNumToLREAL := TO_LREAL(pDInt^); ELSIF (AnyNum.TypeClass = __SYSTEM.TYPE_CLASS.TYPE_LINT) THEN pLInt := AnyNum.pValue; OrginalType := __SYSTEM.TYPE_CLASS.TYPE_LINT; F_AnyNumToLREAL := TO_LREAL(pLInt^); // Неверный тип ELSE F_AnyNumToLREAL := 0; bInvalidType := TRUE; END_IF

REFERENCE

  • Ссылки проще в использовании: ссылку не нужно разыменовывать (с помощью ^), чтобы получить доступ к содержимому объекта, на который ссылается ссылка.
  • Более чистый синтаксис для передачи значений: Если вход является ссылкой, то нет необходимости писать ADDR(value).
  • В отличие от указателей, для ссылок компилятор проверяет типы данных при передаче значений.
  • Стоит отметить, что не всегда ссылкой можно заменить указатель, но когда это возможно, то сделайте это.

    Pragmas

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

  • Message pragmas
  • Attribute pragmas
  • Conditional pragmas
  • Region pragma
  • Pragmas for warning suppression
  • Union

    Union — тип структуры, который позволяет представлять значение в разных типах данных. Данная структура полезна при отладке кода а также при обработке входных значений.

    В случае, если нужно обращаться к битам, то это можно сделать через точку. Но у этого способа я вижу огромный недостаток: нет возможности итерироваться по битам. Если нужно разобрать переменную на байты или по 16-бит или другим сложным образом, то вместо написания сложных функций попробуйте сначала сделать это с помощью Union.

    SEL, MIN, MAX, LIMIT

    Многим программистам ПЛК часто не хватает синтаксического сахара, которого много в других языках программирования. На примере функции SEL хочется показать, что возможно этот "сахар" в виде тернарного оператора не особо нужен.

    Если требуется выбрать значение в зависимости от заданного условия, это можно сделать в одной строке:

    value := SEL(condition, if false, if true);

    Если вам нужно ограничить значение сверху и/или снизу, это также можно сделать в одну строку:

    value := MIN(value, верхний_предел); value := MAX(value, нижний_предел); или value := ОГРАНИЧИТЬ(нижний_предел, value, верхний_предел);

    Многие функции и операторы, которых нам не хватает уже написаны — нужно только поискать.

    Работа со строками в CODESYS V3.5


    Стандарт МЭК 61131-3 определяет типы данных при программировании ПЛК. Они делятся на четыре основных группы: биты, числа, строки и временные типы. В статье описывается работа со строками в среде CODESYS V3.5, применяемой для программирования контроллеров ОВЕН СПК1хх с Ethernet и ПЛК210. Первые программируемые контроллеры появились в 60-70 годах прошлого века для замены электромеханических реле и аналоговых регуляторов. Тогда для разработки программ было достаточно двух основных типов данных: логического – для представления дискретных сигналов и целочисленного – для представления аналоговых сигналов. Эволюция ПЛК расширила спектр выполняемых задач, что потребовало введения новых типов данных, одним из которых стали строки. Строки могут использоваться для задач:

    • визуализации (формирование таблиц рецептов, сообщений о тревогах и т.д.);
    • записи данных в файлы в понятной человеку форме (в формате CSV, JSON и т.д.);
    • реализации строковых протоколов обмена (DCON, MQTT и т.д.);
    • работы с SMS;
    • хранения паролей, серийных номеров и т.д.

    Типы строк в CODESYS V3.5

    Параметр

    STRING

    WSTRING

    Пример записи литерала

    (важен тип кавычек)

    Тип выбирается в зависимости от поставленных задач. К примеру, для отображения строк в визуализации контроллеров ОВЕН оптимальным будет использование типа WSTRING. В случае работы с SMS более практичным будет применение STRING, поскольку при составлении AT-команд для модемов используется кодировка ASCII.

    Длина и размер строки

    В CODESYS V3.5 при объявлении строки задается ограничение числа ее символов. Если число символов не указано, то по умолчанию используется значение 80. Ограничение максимального числа символов строки в явном виде отсутствует. Фактически длина строки ограничена только объемом памяти, выделенной под проект.

    В CODESYS используются нуль-терминированные строки (как в языке С), то есть каждая строка завершается NUL-символом с кодом «0». Память под этот символ выделяется автоматически, и он не учитывается при объявлении переменной.

    VAR // Максимально допустимая длина – 40 символов // Занимаемая память – 41 байт sMessage: STRING(40) := ‘test’; // Максимально допустимая длина – 80 символов (по умолчанию) // Занимаемая память – 162 байта wsTitle: WSTRING := “test”; END_VAR

    Базовые функции работы со строками

    Значение строковой переменной можно присвоить не только при ее объявлении, но и в коде программы. Однако одного присваивания недостаточно. Для реализации алгоритмов требуются дополнительные операции, например, объединение нескольких строк в одну, поиск в строке нужного символа и т.д. Для этих операций используются базовые функции из библиотеки Standard. Список этих функций с кратким описанием

    Функция

    Краткое описание

    CONCAT (STR1, STR2)

    Объединяет две строки в одну

    DELETE (STR, LEN, POS)

    Удаляет из строки заданное число символов с нужной позиции

    Ищет заданную подстроку в строке

    INSERT (STR1, STR2, POS)

    Добавляет подстроку в строку с заданной позиции

    Выделяет из строки подстроку заданной длины (начиная с первого символа)

    Вычисляет длину строки

    MID (STR, LEN, POS)

    Выделяет из строки подстроку заданной длины (начиная с нужной позиции)

    REPLACE (STR1, STR2, LEN, POS)

    Заменяет в строке один фрагмент на другой (начиная с нужной позиции)

    Выделяет из строки подстроку заданной длины (начиная с последнего символа)

    Иллюстрации применения данных функций

    sVar1 := ‘Привет, ’; sVar2 := ‘мир’; // теперь sVar3 равно ‘Привет, мир’ sVar3 := CONCAT(sVar1, sVar2); // iLen теперь будет равен 12 iLen := LEN(sVar3);

    Функции из библиотеки Standard могут работать только с переменными типа STRING. Для работы с WSTRING используется библиотека Standard64 с идентичным набором функций, имеющих префикс «W» (WCONCAT, WDELETE и т. д.).

    Расширенные функции работы со строками

    Важно отметить, что функции из библиотек Standard/Standard64 могут работать только со строками, длина которых не превышает 255 символов. Для работы с более длинными строками используется библиотека StringUtils. В ней содержатся функции, которые в качестве аргументов принимают не строки, а указатели на них. Кроме того, библиотека содержит дополнительные функции для перевода строк в верхний/нижний регистр, удаления пробелов и т. д.

    Типы строк STRING и WSTRING предназначены для работы с разными кодировками. Иногда требуется выполнить конвертацию этих типов, например, ввести в визуализацию строку-сообщение типа WSTRING и отправить ее по SMS в виде STRING-значения. Стандартные операторы конверсии STRING_TO_WSTRING/WSTRING_TO_STRING в этом случае не подходят, так как не производят конвертации кодировок, а перекладывают содержимое памяти одной переменной в другую. Решить проблему поможет библиотека OwenStringUtils, разработанная компанией ОВЕН.

  • конвертировать кодировки;
  • работать с подстроками;
  • форматировать вывод переменных типа DATE/TOD/DT/REAL.
  • // некорректная конвертация // wsMessage получит значение "òåñò" wsMessage := TO_WSTRING(‘тест’); // корректная конвертация // wsMessage получит значение "тест" wsMessage := OSU.CP1251_TO_UNICODE(‘тест’); // sDateTime станет ‘02.04.2019 08:11:30’ dtDateTime := DT#; sDateTime := OSU.DT_TO_STRING_FORMAT(dtDateTime, ‘%t[dd.MM.yyyy HH:mm:ss]’);

    Большой набор функций для работы со строками можно найти в библиотеке OSCAT Basic. Часть из них повторяет функционал OwenStringUtils, но присутствуют и уникальные: например, зеркалирование строки и преобразование числа в строку с его HEX-значением. Русскоязычное описание библиотеки доступно на сайте owen.ru в разделе CODESYS V3.

    // переменная sMessage примет значение ‘dbca’ sMessage := MIRROR(‘abcd’); // переменная sMessage теперь станет ‘FF’ sMessage := BYTE_TO_STRH(255);

    Управляющие последовательности

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

    В редакторе CODESYS для ввода спецсимволов используется знак ‘$’. Полный список спецсимволов приведен в документе CODESYS V3.5. Визуализация.

    sMessage := ‘Первый$r$nВторой’;

    Строки и массивы

    Как было сказано в начале статьи, строка представляет собой массив символов. CODESYS V3.5 позволяет осуществлять индексный доступ к строке – как к массиву значений типа BYTE (для STRING) или WORD (для WSTRING). Это удобно при работе с файлами и реализацией протоколов обмена. На рис. 6 приведен пример обработки строки в цикле FOR для определения позиций символов, разделяющих значения.

    Это может потребоваться при чтении информации из файлов формата .csv.

    VAR sЗапись: СТРОКА := ‘123;456;789’; sРазделитель: СТРОКА := ‘;’; auiПозицииРазделителя: МАССИВ [0..10] ИНТ; i: ИНТ; j: ИНТ; END_VAR

    j := 0; FOR i:= 0 TO LEN(sRecord) DO IF sRecord[i] = sSeparatorChar[0] THEN auiSeparatorPos[j] := i; j := j + 1; // TODO: добавить проверку // для верхней границы массива END_IF END_FOR

    В определённых ситуациях необходимо очистить строку. Для этого достаточно установить ей «нулевое» значение. Однако важно помнить, что данная операция не удаляет строку полностью – она лишь записывает символ NUL в первый элемент. На рисунке 7 представлен пример, где переменной изначально присваивается значение ‘ABCD’, а затем оно заменяется на пустую строку.

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

    sMessage := ‘ABCD’; sMessage := ‘’; // Теперь sMessage будет содержать ‘EBCD’ sMessage[0] := 16

    Рассмотрены ключевые моменты работы со строками в среде CODESYS V3.5. Все перечисленные библиотеки доступны для загрузки на сайте owen.ru в разделе CODESYS V3. Подробная информация о работе со строками приведена в документации к этим библиотекам, а В справке среды программирования.

    Издание зарегистрировано Федеральной службой по надзору в сфере связи, информационных технологий и массовых коммуникаций. Свидетельство о регистрации средств массовой информации ПИ № ФС77-68720.

    Число с плавающей запятой в двоичном представлении по стандарту IEE754. Или как вычислить Float из двух Word c Modbus

    Большинство программистов в наше время пишут код без понятия, что происходит там "под капотом", банально как хранятся в памяти те или иные типы переменных и у них это не вызывает никаких проблем. А что будет, если оставить только базовые операторы?

    Дано: некий измерительный преобразователь отправляет float по протоколу modbus в виде двух регистров word, мы их получаем на ПЛК.Задача: полученные два слова сконвертировать обратно в float.

    Float по стандарту IEE754

    Стандарт IEE754 определяет как представлять отрицательные и положительные числа с плавающей точкой. Множество статей в интернете описывает тонкости это стандарта с приложенными математическими формулами и подробным описаниям.

    Самое главное, что нам нужно знать из этого стандарта:

  • float это 32 бита
  • где 1-й бит — знак, следующие 8 — порядок, последние 23 — мантисса
  • зная эти данные и подставив их в формулу можно получить искомое значение
  • Как обычно это решается?

    Обычно это решается быстро и легко через указатели или через встроенные функции конвертации. Что в принципе не требует знаний о стандарте.

    Я же за неимением онного использовал битовые операции.

    Решение с помощью битовых операций

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

    PROGRAM PLC_PRG VAR real_dword, sign, mant, expb, bmask1, bmask2, bmask3: DWORD; word1_input, word2_input: WORD; float_output, signf, expf: REAL; mantf: REAL; END_VAR word1_input := 11047; word2_input := 16964; bmask1 := 16#FF; bmask2 := 16#7FFFFF; bmask3 := 16 (* Объединяем два слова в одно двойное *) real_dword := SHL(WORD_TO_DWORD(word2_input), 16) + WORD_TO_DWORD(word1_input); (* Определяем знак *) sign := SHR(real_dword, 31); IF sign > dword#0 THEN signf := -1.0; ELSE signf := 1.0; END_IF; (* Определяем порядок *) expb := SHR(real_dword, 23) AND bmask1; (* Определяем мантиссу *) IF(expb <> dword#0 ) THEN mant := (real_dword AND bmask2) OR bmask3; ELSE mant := SHL((real_dword AND bmask2), 1); END_IF; (* Подставляем полученные значения в формулу *) mantf := (DWORD_TO_REAL(mant)) * (EXPT(REAL#2.0, DINT#-23)); expf := expt(real#2.0 , dword_to_dint(expb — dword#127)); float_output := signf * expf * mantf;

    Этот код немного отличается от изначального, который я писал для AXC 1050 в PC Worx, из-за невозможности протестировать его на сегодняшний день нигде кроме эмулятора в CodeSys.

    Это несущественно, разница лишь в способе объявления переменных

    Чем можно проверить свое решение

    Пока гуглил все эти стандарты и способы решений, нашел один интересный Excel документ от Schneider Electric. С его помощью вы можете быстро проверить свое решение.

    Оцените статью
    InternetDoc.ru
    Добавить комментарий