Bit to word codesys

Сегодня я поставил для себя точку в свое сравнении переменной типа Bit с типoм BOOL. Так что я расскажу что сколько занимает место в структурах и расскажу как, по моему мнению, лучше мапить данные в среде Codesys.

Bit и Bool типы переменных, которые хранят бинарное значение. И дальше у меня была ловушка, которая образовалась после продолжительного программирования контроллеров Siemens. И так, в Siemens BOOL имел доступ как Byte.Bit, что мне казалось логичным. Что мы выделяем целый байт, а потом с него забираем.

Теперь как дела обстоят в Codesys. При объявлении в структуре одной переменной типа Bit или типа Bool структура будет занимать 1 байт.

Далее я объявил по 9 переменных. В моем мозгу данная структура должна занимать была 2 байта в обоих вариантах.

Но увы и ах. Codesys действительно отводит 1 байт под переменную BOOL.

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

Union с Bit и Bool

Теперь как же нам красиво отмапить Byte в BOOL/BIT. И тут мое второе заблуждение. Я думал что массивы BOOL в Union c Byte будут прекрасно мапиться.

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

Короче..

Размер данного объединения составлял 8 байт, но он прекрасно перекинул значения из байта в bool

Но вот при значении 3 уже так радужно не было.

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

Так что если вам надо куда-то сныкать какие-то данные, то… Я вам ничего не говорил.

Как же смапить байт в bit

Так как массив из bit создать нельзя, то нам потребуется где то создать структуру из 8 bit — это полноценный Byte и уже вместе закидывать их в объединение.

Вывод

Многие могут спросить: «Ты что не знал?». Я отвечу, что нет. Я читал что Bool занимает байт, а для красивого маппинга надо сделать структуру из Bit, но я не проверял, так как в моей практике до момента сегодня не было задач с подобным маппингом.

Теперь по выводам. Будем ли менять все BOOL на BIT? И нет и да. Смотрите сами где вам удобнее, а где не особо. В любом случае я не могу представить все варианты использования, но в некоторых моментах я готов пожертвовать лишними байтами ради удобства массивов. Но это касается лишь затрат памяти, а вот как спрофилировать затраты по времени — задача. Буду рад если предложите ответ.

Old
December 28th, 2016, 09:17 AM

 
#1

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Question
Codesys — Convert byte to outputs


Hello experts

I’m new to Codesys (3.5) and I am trying to make a program which takes a number 1-50 (read from some tags UID) and outputs each bits status to output %QX1.0 — %QX1.7

Can anyone please help? I know it should probably be quite easy, but I just can’t find out how…

Best regards // Johan

 

Reply With Quote

Old
December 28th, 2016, 09:27 AM

 
#2

Lifetime Supporting Member + Moderator

United States

boneless is offline

 

Join Date: Feb 2008

Location: OKC

Posts: 1,621

I am not a 100% sure about this, but lets assume your number is in memory %MW0, have you tried moving that directly to your output? QW0 := MW0?

If that won’t work. Can you access the bits in MW0 individually by using MX0.0?

 

Reply With Quote

Old
December 28th, 2016, 09:37 AM

 
#3

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Question


Quote:

Originally Posted by boneless
View Post

I am not a 100% sure about this, but lets assume your number is in memory %MW0, have you tried moving that directly to your output? QW0 := MW0?

If that won’t work. Can you access the bits in MW0 individually by using MX0.0?

Hi boneless,

I have not tried that. I do have the read value in a variable and was thinking about using that to configure the output.
I am actually programming at home now without access to the hardware, so I’m trying to get as much info and as much done as possible before going back into office tomorrow.
I’m sure there’s a way to extract the boolean value of the bits to use for the outputs and I really hope you’re correct. That would save me a lot of time

 

Reply With Quote

Old
December 28th, 2016, 09:41 AM

 
#4

Lifetime Supporting Member + Moderator

United States

boneless is offline

 

Join Date: Feb 2008

Location: OKC

Posts: 1,621

Did you know you can set up a Raspberry Pi to run CodeSYS? That would be a great test bed for home use .

Let us know if it works and welcome to the forum!

 

Reply With Quote

Old
December 28th, 2016, 09:44 AM

 
#5

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Talking


Quote:

Originally Posted by boneless
View Post

Did you know you can set up a Raspberry Pi to run CodeSYS? That would be a great test bed for home use .

Let us know if it works and welcome to the forum!

I actually do know or have heard of that. Wouldn’t help me much right now though since I would have to fit the program to the hardware at work afterwards anyway

And thank you

 

Reply With Quote

Old
December 28th, 2016, 12:29 PM

 
#6

Member

Czech_Republic

Jirrr is offline

 

Join Date: Jan 2009

Location: Czech

Posts: 167

There is Util Library with function BYTE_AS_BIT……

 

Reply With Quote

Old
December 28th, 2016, 02:15 PM

 
#7

Member

Netherlands

shooter is offline

 

shooter's Avatar

 

Join Date: Sep 2002

Location: duketown

Posts: 2,711

yep you can use util and you can use unpack.
have a look at oscat.de for a vast library of functions.

__________________
shooter@home.nl
skype shooter paul.deelen
Computer Shooter
Paul Deelen
J. Wassenaerstraat 29
NL 5224 GG ‘s-Hertogenbosch
+31653300739

 

Reply With Quote

Old
December 29th, 2016, 01:43 AM

 
#8

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Thumbs up


Quote:

Originally Posted by shooter
View Post

yep you can use util and you can use unpack.
have a look at oscat.de for a vast library of functions.

Quote:

Originally Posted by Jirrr
View Post

There is Util Library with function BYTE_AS_BIT……

I will look into that thanks!

 

Reply With Quote

Old
December 29th, 2016, 01:44 AM

 
#9

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Red face


Quote:

Originally Posted by shooter
View Post

yep you can use util and you can use unpack.
have a look at oscat.de for a vast library of functions.

Oscat.de is unfortunately in german which makes it kind of impossible for me to navigate :S But thanks

 

Reply With Quote

Old
December 29th, 2016, 02:12 AM

 
#10

Member

Australia

Geoff White is offline

 

Join Date: Oct 2005

Location: Brisbane

Posts: 446

The Oscat library is also available in English

 

Reply With Quote

Old
December 29th, 2016, 05:00 AM

 
#11

Member

Netherlands

shooter is offline

 

shooter's Avatar

 

Join Date: Sep 2002

Location: duketown

Posts: 2,711

oscat is in english and download the codesys version the manual and also download the text version, that way you have a very nice manual and how it is programmed. It is all open.

__________________
shooter@home.nl
skype shooter paul.deelen
Computer Shooter
Paul Deelen
J. Wassenaerstraat 29
NL 5224 GG ‘s-Hertogenbosch
+31653300739

 

Reply With Quote

Old
December 29th, 2016, 05:01 AM

 
#12

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Thumbs up


Quote:

Originally Posted by shooter
View Post

oscat is in english and download the codesys version the manual and also download the text version, that way you have a very nice manual and how it is programmed. It is all open.

Awesome! Will give it another try

 

Reply With Quote

Old
December 29th, 2016, 09:02 AM

 
#13

Member

Canada

Bohardem is offline

 

Join Date: Dec 2016

Location: LAval

Posts: 2

Hi kallamamran,

You can use a bit of the byte by declaring it like this:

By example;
Your variables
Test_byte : Byte ;
Test_output1 :Bool;

Programm
Test_output1 := Test_byte.0 ;
Test_output2 := Test_byte.1;
etc…

The dot and number identified the number of bit of the byte you want to use.

Hope that help you.

 

Reply With Quote

Old
December 29th, 2016, 09:11 AM

 
#14

Member

Sweden

kallamamran is offline

 

Join Date: Dec 2016

Location: Gothenburg

Posts: 7

Thumbs up


Quote:

Originally Posted by Bohardem
View Post

Hi kallamamran,

You can use a bit of the byte by declaring it like this:

By example;
Your variables
Test_byte : Byte ;
Test_output1 :Bool;

Programm
Test_output1 := Test_byte.0 ;
Test_output2 := Test_byte.1;
etc…

The dot and number identified the number of bit of the byte you want to use.

Hope that help you.

Thank you! Makes perfect sense with whats stored in the back of my head from when I attended a short course several years ago

 

Reply With Quote

Old
December 29th, 2016, 12:17 PM

 
#15

Member

Sweden

Friedrich is offline

 

Join Date: May 2014

Location: Swedistan

Posts: 51

You can simply declare what byte or word you want to control the outputs in the «I/O mapping list» of the output interface. No need to write any code.

 

Reply With Quote

Мы продолжаем изучать программирование ПЛК ОВЕН в универсальной среде программирования CoDeSys. В этой публикации представлены типы данных и переменные в проекте CoDeSys, рассмотрены принципы объявления переменных проекта в CoDeSys. С предыдущей публикацией по программированию ПЛК, посвященной знакомству с общей структурой проекта в CoDeSys можно ознакомиться здесь.

Типы данных

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

Элементарные типы данных

1. Целочисленные переменные отличаются различным диапазоном сохраняемых данных и, естественно, различными требованиями к памяти. Подробно данные характеристики представлены в таблице ниже.

Тип Нижний предел Верхний предел Размер, байты
BYTE 8 бит 1
WORD 16 бит 2
DWORD 32 бита 4
LWORD 64 бита 8
SINT -128 127 1
INT -32768 32767 2
DINT -231 231-1 4
LINT -263 263-1 8
USINT 0 255 1
UINT 0 65535 2
UDINT 0 232-1 4
ULINT 0 264-1 8

2. Логические переменные объявляются ключевым словом BOOL. Они могут принимать только значение логического нуля («0») FALSE (ЛОЖЬ) или логической единицы («1») TRUE (ИСТИНА). При начальной инициализации логическое значение по умолчанию — ЛОЖЬ. Занимает 8 бит памяти, если не задан прямой битовый адрес.

3. Переменные действительного типа (REAL и LREAL) представляют действительные числа в формате с плавающей точкой. Для типа REAL необходимо 32 бита памяти и 64 – для LREAL.
Диапазон значений REAL от: 1.175494351e-38F до 3.402823466e+38F
Диапазон значений LREAL от: 2.2250738585072014e-308 до 1.7976931348623158e+308

4. Время суток и дата типы переменных, выражающие время дня или дату, представляются в соответствии с ISO 8601.

Тип Короткое обозначение Начальное значение Максимальное значение
DATE D 1 января 1970 г. 6 февраля 2106 г.
TIME_OF_DAY TOD 00:00:00 23:59:59.999
DATE_AND_TIME DT 00:00:00 1 января 1970 г. 06:28:15 6 февраля 2106 г.

5. Интервал времени – переменные типа TIME. В отличие от времени суток (TIME_OF_DAY) временной интервал не ограничен максимальным значением в 24 часа. Числа, выражающие временной интервал, должны начинаться с ключевого слова TIME# (в сокращенной форме Т#). Максимальное значение для типа TIME: 49d17h2m47s295ms (4194967295 ms).

6. Тип строковых переменных (STRING) определяет переменные, содержащие текстовую информацию. Размер строки задается при объявлении. Если размер не указан, принимается размер по умолчанию – 80 символов. Размер задается в круглых или квадратных скобках.

Важно:

Длина строки не ограничена в CoDeSys, но библиотека работы со строками и строковые функции способны обращаться со строками от 1 до 255 символов!

Пример объявления строки размером до 35 символов:
str:STRING(35) := ‘Просто строка’;

Пользовательские типы данных

Массивы

Массивы представляют собой множество однотипных элементов с произвольным доступом. Они могут быть одномерными или многомерными. Размерность массива и диапазоны индексов задаются при объявлении.

Синтаксис:

<Имя массива>:ARRAY [<li1>..<hi1>,<li2>..<hi2>,<li3>..<hi3> OF <тип элемента>;

где li1, li2, li3 указывают нижние пределы индексов; hi1, hi2 и hi3 – верхние пределы. Индексы должны быть целого типа и только положительные. Отрицательные индексы использовать нельзя.

Элементарные типы данных могут образовывать одно-, двух-, и трехмерные массивы. Путем вложения массивов можно получить многомерные массивы, но не более 9-мерных (“ARRAY[0..2] OF ARRAY[0..3] OF …”).

Пример:

Card_game: ARRAY [1..13, 1..4] OF INT;

Пример инициализации простых массивов:

arr1 : ARRAY [1..5] OF INT := 1,2,3,4,5;

arr2 : ARRAY [1..2,3..4] OF INT := 1,3(7); (*сокращение для 3 по 7: 1,7,7,7 *)

arr3 : ARRAY [1..2,2..3,3..4] OF INT := 2(0),4(4),2,3; (*сокращение для 0,0,4,4,4,4,2,3 *)

Для доступа к элементам двухмерного массива используется следующий синтаксис:

<Имя_массива>[Индекс1,Индекс2]

Пример: Card_game [9,2]

Структуры

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

Объявление структуры должно начинаться с ключевого слова STRUCT и заканчиваться END_STRUCT.

Синтаксис:

TYPE <имя_структуры>
STRUCT
<переменная_0> ,< переменная _1>, …< переменная _n>
END_STRUCT

END_TYPE

Пример объявления:

TYPE STRUCT1
STRUCT
p1:int;
p2:int;
p3:dword;
END_STRUCT

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

Перечисление позволяет определить несколько последовательных значений переменной и присвоить им наименования. Перечисление доступно в любой части проекта, даже при локальном его объявлении внутри POU. Поэтому наиболее разумно создавать все перечисления на вкладке типы данных (Data types) «Организатора Объектов» (Object Organizer). Объявление должно начинаться с ключевого слова TYPE и заканчиваться строкой END_TYPE.

Синтаксис:

TYPE <Имя_перечисления>:(<Элемент_0> ,< Элемент_1>, …< Элемент_n>); END_TYPE

Переменная типа <Имя_перечисления> может принимать только перечисленные значения. При инициализации переменная получает первое из списка значение. Если числовые значения элементов перечисления не указаны явно, им присваиваются последовательно возрастающие числа, начиная с 0. Фактически элемент перечисления – это число типа INT и работать с ними можно точно также. Можно напрямую присвоить число переменной типа перечисление.

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

Ограничение диапазона значений

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

Создание нового типа выглядит так:

TYPE < Имя > : < Целый тип > (<от>..<до>) END_TYPE;

< Имя> любой допустимый МЭК идентификатор;

<Целый тип> один из типов SINT, USINT, INT, UINT, DINT, UDINT, BYTE, WORD, DWORD (LINT, ULINT, LWORD);

<от> константа, определяющая начало диапазона значений включительно;

<до> константа, определяющая конец диапазона значений включительно.

Переменные

Среди элементов МЭК-языков есть переменные.

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

Переменные принято разделять на глобальные и локальные по области видимости.

Глобальные переменные определяются на уровне ресурсов проекта (VAR_GLOBAL) и доступны для всех программных компонентов проекта.

Локальные переменные описываются при объявлении компонента и доступны только внутри него.

Описание любого программного компонента содержит, как минимум, один раздел объявления локальных переменных VAR, переменных интерфейса VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT и внешних глобальных переменных VAR_EXTERNAL.

Внимание:

Глобальная и локальная переменные могут иметь одинаковое имя. В POU, где объявлена такая локальная переменная, она оказывается «сильнее» одноименной глобальной. Использовать одноименные глобальные переменные нельзя (например, объявленные в конфигурации контроллера и в списке глобальных переменных).

Имя переменной (идентификатор) не должно содержать пробелов и спецсимволов, не должно объявляться более одного раза и не должно совпадать с ключевыми словами. Регистр символов не учитывается, это означает, что VAR1, Var1 и var1 – это одна и та же переменная.

Символ подчеркивания является значимым, т.е. “A_BCD” и “AB_CD” – это разные имена.

Имя должно включать не более одного символа подчеркивания. Ограничений на длину имени нет. Область применения переменной задается ее типом. Список всех объявленных переменных в CoDeSys
доступен через ассистент ввода (Input Assistant).

Системные флаги

Системные флаги – это неявно объявленные переменные, различные для конкретных моделей PLC. Для получения списка доступных системных флагов используйте команду “Insert” “Operand”. В диалоге ассистента ввода (Input Assistant) флаги собраны в разделе System Variable.

Синтаксис доступа к элементам массивов, структур и POU

Элемент двумерного массива:

<ИмяМассива>[Индекс1, Индекс2]

Переменная структуры:

<ИмяСтруктуры>.<ИмяПеременной>

Переменная программы или функционального блока:

<ИмяФункциональногоБлока>.<ИмяПеременной>

Доступ к битам в переменных

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

a : INT;

b : BOOL;

a.2 := b;

В примере значение третьего бита переменной a будет присвоено переменной b.

Если указанный номер бита превышает размер типа, формируется специальное сообщение: «Index ‘<n>’ outside the valid range for variable ‘<var>‘»

Битовая адресация применима для типов: SINT, INT, DINT, USINT, UINT, UDINT, BYTE, WORD, DWORD.

Битовую адресацию нельзя использовать с переменными VAR_IN_OUT.

Битовая адресация через глобальные константы

Если объявить целую глобальную константу, то ее можно будет затем использовать для доступа к битам.

Например, так:

Объявление константы

VAR_CONSTANT GLOBAL

enable: int := 1;

END_VAR

Пример 1, битовая адресация через константу:

Объявление POU:

VAR

xxx: int;

END_VAR

Битовая адресация:

xxx.enable := true; (*установлен в единицу второй бит переменной xxx *)

Пример 2, битовая адресация к элементу структуры:

Объявление структуры stru1:

TYPE stru1 :

STRUCT

bvar: BOOL;

rvar: REAL;

wvar: WORD;

{bitaccess: ‘enable’ 42 ‘Start drive’}

END_STRUCT
END_TYPE

Объявление POU:

VAR
x:stru1;
END_VAR

Битовая адресация:
x.enable := true;

Эта инструкция установит 42-й бит переменной x. Поскольку bvar занимает 8 бит, rvar занимает 32 бита, а битовый доступ обращается ко второму биту переменной wvar, получающей в результате значение 4.

Адреса

Прямое указание адреса дает способ непосредственного обращения к конкретной области памяти. Прямой адрес образуется из префикса “%”, префиксов области памяти и размера, одного или нескольких целых чисел, разделенных точкой.

Префиксы области памяти:

I Входы
Q Выходы
M Память данных

Префиксы размера:

X Один бит
Отсутствует Один бит
B Байт (8 бит)
W Слово (16 бит)
D Двойное слово (32 бит)

Примеры:

%QX7.5 и %Q7.5 бит 7.5 в области выходов
%IW215 215-е слово в области входов
%QB7 байт 7 в области выходов
%MD48 двойное слово в позиции памяти 48
%IW2.5.7.1 зависит от конфигурации PLC

Распределение памяти

Образование прямых адресов зависит от размера адресуемых данных.

Так, например, адрес %MD48 адресует в области памяти двойное слово 48 или байты 192, 193, 194 и 195 (48 * 4 = 192). Нумерация начинается с 0.

Адрес %MX5.0 означает младший бит пятого (считая с нуля) слова памяти.


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

stesl писал(а): ↑23 мар 2021, 10:37
Тип Word — это целочисленный беззнаковый тип данных, в два байта. Диапазон 0-65535. Используется везде, где оказывается нужным.

Не путайте Word с UInt (unsigned integer16), он не относится к целочисленным, так как не кодирует числовые значения и не совместим с математическими операциями.
Потому что:

Sergy6661 писал(а): ↑23 мар 2021, 12:54
Вот для упаковки-распаковки битовых переменных и используется в основном.

Но не в основном, а только для этого. Если конечно в конкретном ПЛК не срабатывает неявное преобразование, из-за которого кажется, что Word — это целое число.

Отправлено спустя 21 минуту 7 секунд:
Не, иначе объясню:
Word — это когда ты в 16 бит записал 16 булевых значений, каждый из которых что-то значит в смысле true/false. Например, при управлении сервоприводом или частотником.
Int16, UInt16 — это числа, отдельные биты не представляют интереса (хотя бывают редкие исключения).
Математические операции умеют работать с числами, то есть Add(), Sub(), Mul(), Div() работают с Int16/UInt16, а с Word работает подозрительно, подсвечивает типа «глянь, что за дрянь ты задумал?», но воспринимает как число 0-65535. Извините, правда, зачем вы складываете слово управления частотника с числом -85?
Зато сдвиговые операции и операции со словами типа ANDW(), ORW(), NOT(W), XORW() работают именно со словами и подозрительно с целыми числами.

Это разные типы данных, хотя все они 16 бит.

Подробности
Категория: Простое и понятное программирование в «CODESYS».

Документальные учебные фильмы. Серия «Программирование на CODESYS».

Первые шаги с CoDeSys

скачать

Руководство пользователя по программированию ПЛК в CoDeSys

скачать

Визуализация CoDeSys.

Дополнение к руководству пользователя по программированию ПЛК в CoDeSys

скачать

Конфигурирование области ввода/вывода ПЛК.

Руководство пользователя  для v2.0

скачать

Конфигурирование области ввода/вывода ПЛК.

Руководство пользователя

скачать

Время на прочтение
8 мин

Количество просмотров 10K

Введение

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

Об опыте автора

Опыт работы с ПЛК: 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. Хочется поделится опытом, чтобы эти 3 года не прошли даром.

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

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

Обезвредьте кнопку Stop, чтобы случайно не остановить ПЛК, иногда такое случайное нажатие может привести к нежелательным последствиям. В TwinCAT XAE Shell можно выбрать какие кнопки выводить на toolbar. После локальной отладки программы рекомендую скрыть кнопку остановки ПЛК.

Structured Text

STRING vs WSTRING

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

STRING

WSTRING

Format

ASCII

Unicode

Size of character

BYTE (1 byte)

WORD (2 bytes)

Terminator

Null character

0

Date and time

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

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

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

Хранятся отметки в переменных типа ULINT. Зная всё это мы можем без труда рассчитывать интервалы времени с точностью до 100нс! Нужно просто найти разность между отметками.

К сожалению, стандартных функций для преобразования отметки в тип DATETYPE я не нашёл, поэтому пришлось реализовать такую функцию самостоятельно:

(*
:Description: Convert time since 1 January 1601 in 100 ns to DATE_AND_TIME  (Преобразует время с 1 Января 1601 года в 100 нс в DATE_AND_TIME)
:Usability: Convert timestamp to datetime

:Note: check then nSystemType more then 01.01.1970 00:00:00

Version history:
Kozhemaykin E. A. | Creating | 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; // One unit is 100 ns since 1 January 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

(*
:Description: Return datetime now in format DATE_AND_TIME (DT)
:Usability: For getting datetime now in format DATE_AND_TIME (DT)

Version history:
Kozhemaykin E. A. | Creating | 16.08.2021;
*)

FUNCTION F_DateTimeNow : DT
F_DateTimeNow := F_SystemTimeToDT(F_GetSystemTime());

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

(*
:Description: Time passed since tStart (Прошло времени c tStart)
:Usability: If need check how long time past

Version history:
Kozhemaykin E. A. | Creating | 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);

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

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

В общем виде задание числовой константы выглядит так:

{datetype}#{numeral system}#value 

Пример: 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
    // the type of the actual parameter
    typeclass : __SYSTEM.TYPE_CLASS ;
    // the pointer to the actual parameter
    pvalue : POINTER TO BYTE;
    // the size of the data, to which the pointer points
    diSize : DINT;
END_STRUCT
END_TYPE

Кроме типа ANY существуют также дочерние типы:

Дерево наследования типов

Дерево наследования типов

Хочу обратить внимание что на вход типа ANY не может быть подана константа, поэтому в некоторых случаях придётся создавать дополнительную переменную.

Зная про этот тип мне удалось реализовать функцию, которая приводила данные разных типов к LREAL.

Функция по преобразованию числовых типов в LREAL

(*
:Description: Convert ANY_NUM and ANY_BIT to LREAL
:Usability: For development universal functions

:Note:
Valid types is:
ANY_NUM:
    - ANY_REAL: REAL, LREAL
    - ANY_INT: USINT, UINT, UDINT, ULINT, SINT, INT, DINT, LINT
ANY_BIT:
    - BYTE, WORD, DWORD, LWORD

Version history:
Kozhemaykin E. A. | Creating | 01.06.2021;
Kozhemaykin E. A. | {CLASS_TO_LREAL -> TO_LREAL | 03.11.2021;
 
*)

FUNCTION F_AnyNumToLREAL : LREAL
VAR_INPUT
    AnyNum: ANY; // Variable for converting, need have address
END_VAR
VAR
    pReal : POINTER TO REAL;   // pointer to a variable of the type REAL
    pLReal : POINTER TO LREAL;  // pointer to a variable of the type LREAL
    
    pUSInt : POINTER TO USINT;   // pointer to a variable of the type USInt
   	pUInt : POINTER TO UINT;  // pointer to a variable of the type UInt
   	pUDInt : POINTER TO UDINT;  // pointer to a variable of the type UDInt
    pULInt : POINTER TO ULINT;   // pointer to a variable of the type ULInt
    
   	pSInt : POINTER TO SINT;  // pointer to a variable of the type SInt
    pInt : POINTER TO INT;   // pointer to a variable of the type Int
   	pDInt : POINTER TO DINT;  // pointer to a variable of the type DInt
    pLInt : POINTER TO LINT;   // pointer to a variable of the type LInt
    
    pByte : POINTER TO BYTE;  // pointer to a variable of the type Byte
    pWord : POINTER TO WORD;   // pointer to a variable of the type Word
   	pDWord : POINTER TO DWORD;  // pointer to a variable of the type DWord
    pLWord : POINTER TO LWORD;   // pointer to a variable of the type LWord

END_VAR
VAR_OUTPUT
    OrginalType: __SYSTEM.TYPE_CLASS;
    bInvalidType: BOOL := FALSE;
END_VAR
// Real numbers
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^;

// Bit's numbers
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^);

// Unsigned integers
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^);

// Signed integers
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^);
    
//Invalid type
ELSE
    F_AnyNumToLREAL := 0;
    bInvalidType := TRUE;
END_IF

REFERENCE

Все знают про указатели (POINTER) и связанные с ними проблемы, так вот многие из них можно избежать, если использовать ссылки(REFERENCE):

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

  • Более чистый синтаксис для передачи значений: Если вход является ссылкой, то нет необходимости писать ADDR(value).

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

Стоит отметить, что не всегда ссылкой можно заменить указатель, но когда это возможно, то сделайте это.

Pragmas

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

Типы 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, max_limit);
value := MAX(value, min_limit);
or
value := LIMIT(min_limit, value, max_limit); 

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

Заключение

В статье описано,то на что лично мне захотелось обратить внимание (ООП решил не трогать). Буду рад если мой опыт принесёт кому-то пользу. Попрошу при использовании предоставленных функций оставлять продолжать version history.

Делитесь своим опытом в комментариях. Чтобы быть в курсе событий и общаться с коллегами предлагаю перейти по ссылкам: тг-канал proPLC, тг-чат proPLC.

I have 4 bytes:

Byte_0=0x31
Byte_1=0x32
Byte_2=0x33
Byte_3=0x34

And I would like to get a word_1=0x1234, where 1 is the conversion from hex to ASCII. Any ideas about how to get this?

SecretAgentMan's user avatar

asked Sep 29, 2022 at 8:30

Soul1986's user avatar

1

In addition to previous suggestion there is another way to convert string to integer and bytes to string.

PROGRAM PLC_PRG
    VAR
        arBt : ARRAY[1..4] OF BYTE := [16#31, 16#32, 16#33, 16#34];
        pstrString : POINTER TO STRING(4);
        iInt : INT;
    END_VAR

    pstrString := ADR(arBt); // now pstrString^ is equal to "1234"
    iInt := STRING_TO_INT(pstrString^); // now iInt = 1234 number
END_PROGRAM

If you have Codesys 2.3 then delete [ brackets in [16#31, 16#32, 16#33, 16#34].

answered Sep 30, 2022 at 6:00

Sergey Romanov's user avatar

Sergey RomanovSergey Romanov

2,9034 gold badges23 silver badges38 bronze badges

There may be an internal function available to translate an ASCII text digit to its numerical value, but you can easily write one yourself:

FUNCTION Ascii_To_Byte : WORD

VAR_INPUT ascii : BYTE

IF ascii>=16#30 AND ascii<=16#39 THEN  // range 0-9
  Ascii_To_Byte := ascii - 16#30;
ELSIF ascii>=16#41 AND ascii<=16#46 THEN  // range A-F
  Ascii_To_Byte := ascii - 16#41 + 16#0A;
ELSE
  Ascii_To_Byte := -1  // error condition, invalid input

Then it’s just a matter of converting the values and bit shifting:

word_1 := SHL(Ascii_To_Byte(Byte_0), 12) + SHL(Ascii_To_Byte(Byte_1), 8) + SHL(Ascii_To_Byte(Byte_2), 4) + Ascii_To_Byte(Byte_3)

(Code written in TwinCAT, may be slightly different in Codesys.)

answered Sep 29, 2022 at 19:31

kolyur's user avatar

kolyurkolyur

4122 silver badges13 bronze badges

Понравилась статья? Поделить с друзьями:
  • Bit by bit another word
  • Biscuit meaning of word
  • Birthday word of the day
  • Birthday party invitation word
  • Birdy not a word