Word to float codesys

I do this quite a bit. I use a union, and the ROL function depending on endianness.

The union. I called it ‘TypeCast’ because that was the equivalent function in LabVIEW.

TYPE TypeCastDWReal :
UNION
    dw  :   DWORD;
    rl  :   REAL;
END_UNION
END_TYPE

Then I have two function that use the union depending on the direction you want the conversion done.

FUNCTION TCRealSwapped : REAL
VAR_INPUT
    dw  :   DWORD;
END_VAR
VAR
    MyUnion :   TypeCastDWReal;
END_VAR

MyUnion.dw := ROL(dw,16);       //  Word swap dword input because it's big endian on ModbusTCP
TCRealSwapped := MyUnion.rl;    //  Function (via union) returns real
FUNCTION TCDWordSwapped : DWORD
VAR_INPUT
    rl  :   REAL;
END_VAR
VAR
    MyUnion :   TypeCastDWReal;
END_VAR

MyUnion.rl  :=  rl;                         //  Input to function is REAL
TCDWordSwapped  := ROL(MyUnion.dw,16);      //  Function returns DWORD, needs word swap for ModbusTCP register space

Then I step through the modbus data array via pointer and byte offset, and pass the dwords/reals into the function block.

This is reading a real input to my server.

pDword  :=  ADR(awEMS) + 12;    dsEMS.i.rRealPowerDemandkW          :=  TCRealSwapped(pDword^); 

This is writing real into the data array outbound on my client…

pDWord  :=  ADR(awEMS) + 2014;  pDWord^ :=  TCDWordSwapped(dsEMS.q.rRatedACVoltage);

Мы продолжаем изучать программирование ПЛК ОВЕН в универсальной среде программирования 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.

Где это встречается?

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

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

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

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

Битовое представление числа с плавающей точкой<br />
Битовое представление числа с плавающей точкой

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

  • 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#800000;
(* Складываем два слова в одно двойное *)
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. С его помощью вы можете быстро проверить свое решение.

Старый
04.09.2006, 19:56

 

#1
 

Участник

Аватар для ivas

?
DWORD to FLOAT


Есть Binary значение размером 4 байта полученное из внешнего источника там это число с плавающей точкой, как в Аксапте его разшифровать?

Старый
05.09.2006, 09:03

 

#2
 

Участник

1C

Цитата:

Сообщение от ivas

Есть Binary значение размером 4 байта полученное из внешнего источника там это число с плавающей точкой, как в Аксапте его разшифровать?

Что значит расшифровать? Если Вы получили значение типа Double, то:

X++:

Binary b = new Binary();

b.double();

Если нужно передать Double как параметр, то


Последний раз редактировалось Lucky13; 05.09.2006 в 09:57.

Старый
05.09.2006, 11:36

 

#3
 

Участник

Аватар для ivas

С Double проблем нет прекрасно работает проблема именно с Float длинна 4 байта

Старый
05.09.2006, 11:44

 

#4
 

Участник

Аватар для ivas

код:

PHP код:



        case (#SQL_FLOAT) :
            
axType  Types::Real;
            if (
set)
                
binaryValue.dword(0_value);
            else
            {
                
axValue binaryValue.dWord(0); // само собой не верно вопрос что тут писать :)
            
}
            break;
        case (
#SQL_DOUBLE) :
        
case (#SQL_D_FLOAT) :
            
axType  Types::Real;
            if (
set)
                
binaryValue.double(0_value);
            else
                
axValue binaryValue.double(0);
            break; 




Старый
05.09.2006, 14:08

 

#5
 

Участник

КОРУС Консалтинг

 

Регистрация: 13.01.2004

Записей в блоге: 3

Посмотри класс COMVariant. У него есть свойство Float.

Чтобы сказать что-то более определенное надо знать как получаешь и как передаешь значение

Старый
05.09.2006, 14:50

 

#6
 

Участник

Аватар для ivas

Код:

    
     binary b = new binary(4);
     ;
     
     _record.binary(#DataOffset, b); // запись адреса b в другой binary: _record

      fetch.call(_record); // вызов функции которая заполняет _record
      //теперь в b лежит значение типа float 4 байта вот его то мне и нужно получить

Старый
05.09.2006, 14:56

 

#7
 

Участник

Аватар для ivas

Цитата:

Сообщение от Владимир Максимов

Посмотри класс COMVariant. У него есть свойство Float.

Чтобы сказать что-то более определенное надо знать как получаешь и как передаешь значение

супер!
COMVariant спас
спасибо!

Старый
05.09.2006, 14:58

 

#8
 

Участник

Аватар для ivas

Код:

    COMVariant f = new COMVariant(COMVariantInOut::In, COMVariantType::VT_R4);
    ;
 
    f.long(b.dWord(0));
    axValue = f.float();

Старый
05.09.2006, 16:04

 

#9
 

Moderator

 

Регистрация: 24.01.2006

Адрес: Санкт-Петербург

Записей в блоге: 19

В качестве P.S. До кучи в коллекцию. В классе ССADOField метод value содержит шикарный шаблон switch почти на все случаи жизни.

Правда, конкретно Ваш float (VT_R4) упрятан в double, но подправить недолго.

Код:

anytype value()
{
    COMVariant  value;
    value = field.value();
 
    switch (value.variantType())
    {
        case COMVariantType::VT_I2:
            return value.byte();
 
        case COMVariantType::VT_I4:
            return value.int();
 
        case COMVariantType::VT_R4, COMVariantType::VT_R8:
            return value.double();
 
        case COMVariantType::VT_CY:
            return value.currency();
 
        case COMVariantType::VT_DATE:
            return value.date();
 
        case COMVariantType::VT_BSTR:
            return value.bStr();
 
        case COMVariantType::VT_BOOL:
            return value.boolean();
 
        case COMVariantType::VT_DECIMAL:
            return value.decimal();
 
        case COMVariantType::VT_I1:
            return value.byte();
 
        case COMVariantType::VT_UI1:
            return value.uInt();
 
        case COMVariantType::VT_UI2:
            return value.uShort();
 
        case COMVariantType::VT_UI4:
            return value.uLong();
    }
    return '';
}
  1. Обязательно представиться на русском языке кириллицей (заполнить поле «Имя»).
  2. Фиктивные имена мы не приветствуем. Ивановых и Пупкиных здесь уже достаточно.
  3. Не писать свой вопрос в первую попавшуюся тему — вместо этого создать новую тему.
  4. За поиск, предложение и обсуждение пиратского ПО и средств взлома — бан без предупреждения.
  5. Рекламу и частные объявления «куплю/продам/есть халтура» мы не размещаем ни на каких условиях.
  6. Перед тем как что-то написать — читать здесь и здесь.

RoninX

здесь недавно
здесь недавно
Сообщения: 8
Зарегистрирован: 08 авг 2018, 21:27
Имя: Павел
Страна: Россия
город/регион: Москва
Благодарил (а): 3 раза

Склеить два Word и получить Real

Сообщение

RoninX » 08 авг 2018, 21:46

Здравствуйте!

Измеритель выдает значение влажности в формате Real IEEE754 двумя Word. Связь с измерителем по интерфейсу RS485 Modbus RTU. Контроллер S7-1200. Как в TIA portal преобразовать эти два регистра WORD в REAL? (CD AB). На контроллерах Овен задача решается путем создания типа FLoat в списке регистров Модбас и контроллер сам преобразовывает два регистра в Реал. Как данная задача решается в tia portal v4.2?

pkl58

не первый раз у нас
не первый раз у нас
Сообщения: 347
Зарегистрирован: 19 мар 2012, 20:04
Имя: Павел
Страна: Россия
Благодарил (а): 5 раз
Поблагодарили: 43 раза

Склеить два Word и получить Real

Сообщение

pkl58 » 09 авг 2018, 12:41

Как у Сименса не знаю. Но в общем случае есть команды преобразования целых чисел в вещественные. Возможно потребуется поменять местами исходные WORD.

NewOrdered

осмотрелся
осмотрелся
Сообщения: 153
Зарегистрирован: 24 янв 2018, 11:18
Имя: Антон
Страна: Россия
город/регион: Калининград
Благодарил (а): 25 раз
Поблагодарили: 25 раз

Склеить два Word и получить Real

Сообщение

NewOrdered » 09 авг 2018, 13:49

RoninX писал(а): ↑08 авг 2018, 21:46
Здравствуйте!

Измеритель выдает значение влажности в формате Real IEEE754 двумя Word. Связь с измерителем по интерфейсу RS485 Modbus RTU. Контроллер S7-1200. Как в TIA portal преобразовать эти два регистра WORD в REAL? (CD AB). На контроллерах Овен задача решается путем создания типа FLoat в списке регистров Модбас и контроллер сам преобразовывает два регистра в Реал. Как данная задача решается в tia portal v4.2?

Определите область памяти длиной 4 байта как REAL. В первые два байта загрузите 1 слово, во вторые — второе слово. Далее работайте с вашим значением в формате REAL.

RoninX

здесь недавно
здесь недавно
Сообщения: 8
Зарегистрирован: 08 авг 2018, 21:27
Имя: Павел
Страна: Россия
город/регион: Москва
Благодарил (а): 3 раза

Склеить два Word и получить Real

Сообщение

RoninX » 14 авг 2018, 10:30

NewOrdered писал(а): ↑09 авг 2018, 13:49
Определите область памяти длиной 4 байта как REAL. В первые два байта загрузите 1 слово, во вторые — второе слово. Далее работайте с вашим значением в формате REAL.

Добрый день! Получилось, спасибо!

djonm

новенький
новенький
Сообщения: 1
Зарегистрирован: 07 фев 2018, 15:04
Имя: Евгений
город/регион: Ростов

Склеить два Word и получить Real

Сообщение

djonm » 22 авг 2018, 13:53

RoninX писал(а): ↑14 авг 2018, 10:30
Добрый день! Получилось, спасибо!

У меня похожая задача и что то не фига не получается. По modbus читаю значение измеренного напряжения float32 (real). MB_Master читает в переменную REAL, но значение получаются совсем левые. Может надо читать Word- дами а потом переставлять биты и конвертировать в REAL?

Аватара пользователя

petr2off

эксперт
эксперт
Сообщения: 1356
Зарегистрирован: 06 янв 2016, 19:45
Имя: Петров В.Л.
Страна: Россия
город/регион: Красноярск
Благодарил (а): 56 раз
Поблагодарили: 139 раз

Склеить два Word и получить Real

Сообщение

petr2off » 22 авг 2018, 16:33

Какая разница как читать, Вы уже считали 4 байта. К этой области памяти можно обратится как 4-х байтовому полю, как к 2 16-ти битовым словам и как к 4 байтовым адресам. А еще добавляя точку можно и к битам обратится. Как вариант поменяйте слова местами. Очень часто при чтении Modbas 16 разрядные слова не в том порядке принимаются.

RoninX

здесь недавно
здесь недавно
Сообщения: 8
Зарегистрирован: 08 авг 2018, 21:27
Имя: Павел
Страна: Россия
город/регион: Москва
Благодарил (а): 3 раза

Склеить два Word и получить Real

Сообщение

RoninX » 24 дек 2018, 14:44

[/quote] У меня похожая задача и что то не фига не получается. По modbus читаю значение измеренного напряжения float32 (real). MB_Master читает в переменную REAL, но значение получаются совсем левые. Может надо читать Word- дами а потом переставлять биты и конвертировать в REAL?
[/quote]

Попробуйте применить функцию SWAP

Serg_G

здесь недавно
здесь недавно
Сообщения: 59
Зарегистрирован: 04 авг 2018, 07:02
Имя: Сергей
Благодарил (а): 8 раз
Поблагодарили: 2 раза

Склеить два Word и получить Real

Сообщение

Serg_G » 21 янв 2019, 18:45

Попробуйте так. В классике это работало. Только надо правильно выбрать байты из слова.

У вас нет необходимых прав для просмотра вложений в этом сообщении.

Вернуться в «Simatic TIA Portal»


Перейти

  • Работа форума
  • База знаний (Knowledge Exchange)
  • ↳   Eplan Electric P8
  • ↳   Общий F.A.Q.
  • ↳   Общие вопросы
  • ↳   Новости
  • ↳   Ошибки
  • ↳   Проект
  • ↳   Изделия
  • ↳   Устройства
  • ↳   Соединения
  • ↳   Кабели
  • ↳   Клеммы
  • ↳   ПЛК
  • ↳   Компоновка 2D
  • ↳   Макросы
  • ↳   Eplan API
  • ↳   Сценарии (Только готовые решения)
  • ↳   Внешняя обработка
  • ↳   ProPanel
  • ↳   Инструкции ProPanel (Только готовые решения)
  • ↳   Прочие направления Eplan
  • ↳   FieldSys (Топология)
  • ↳   Preplanning
  • ↳   Harness proD
  • ↳   EEC One
  • ↳   Advantech
  • ↳   F.A.Q., Инструкции
  • ↳   Allen Bradley
  • ↳   Общие вопросы
  • ↳   ПЛК
  • ↳   Операторские панели
  • ↳   B&R Automation
  • ↳   F.A.Q.
  • ↳   Danfoss
  • ↳   DEIF A/S
  • ↳   Общие вопросы
  • ↳   UNI-LINE
  • ↳   MULTI-LINE
  • ↳   MULTI-LINE 300
  • ↳   Emerson
  • ↳   Общие вопросы
  • ↳   КИП и регуляторы
  • ↳   DeltaV
  • ↳   ОВЕН
  • ↳   Прософт-Системы
  • ↳   Общие вопросы
  • ↳   ПЛК REGUL
  • ↳   Schneider Electric
  • ↳   Общие вопросы
  • ↳   ПЛК
  • ↳   Панели оператора
  • ↳   SCADA
  • ↳   Электротехника
  • ↳   Приводная техника
  • ↳   SIEMENS
  • ↳   Общие вопросы
  • ↳   LOGO!
  • ↳   ПЛК SIMATIC (S7-200, S7-1200, S7-300, S7-400, S7-1500, ET200)
  • ↳   Simatic Step7
  • ↳   Simatic TIA Portal
  • ↳   Simatic PCS 7
  • ↳   Операторские панели
  • ↳   WinCC
  • ↳   Приводная техника (Sinamics, Micromaster, Masterdrive, Simoreg, Simotics)
  • ↳   SmartGen
  • ↳   Общие вопросы
  • ↳   Промышленные (береговые) контроллеры
  • ↳   Морские контроллеры и устройства
  • ↳   WEINTEK (операторские панели)
  • ↳   F.A.Q., Инструкции
  • ↳   Архив
  • ↳   Микроконтроллеры и электроника
  • ↳   Arduino
  • ↳   Raspberry
  • ↳   Другие микроконтроллеры
  • ↳   Электроника
  • Общие вопросы АСУТП
  • ↳   Общие вопросы
  • ↳   Вопросы от студентов
  • ↳   Литература
  • ↳   Новости и отчётность
  • ↳   Нормативы, ГОСТы, стандарты
  • ↳   Информационная безопасность
  • ↳   Проектирование и САПР
  • ↳   Системная интеграция
  • ↳   Разбор полетов
  • ↳   Работа
  • ↳   Заготовки для базы знаний
  • ↳   Производство и технология
  • ↳   MES — Системы автоматизации управления производством
  • ↳   Метрология, КИП и датчики
  • ↳   Исполнительные устройства, регуляторы
  • ↳   Средний уровень автоматизации (управляющий)
  • ↳   Алгоритмы
  • ↳   Операторские панели
  • ↳   Верхний уровень автоматизации (отображение)
  • ↳   GE iFix
  • ↳   Wonderware Intouch
  • ↳   MasterScada
  • ↳   SCADA+
  • ↳   Альфа платформа
  • ↳   Интерфейсы, протоколы, связь
  • ↳   Радиосвязь
  • ↳   Полезное ПО
  • ↳   Электротехника, энергетика и электропривод
  • ↳   Генераторы, электростанции и силовые агрегаты
  • ↳   Теплотехника
  • ↳   Подбор аналогов
  • F.A.Q. (краткая выжимка из некоторых сообщений форума)
  • ↳   Документация (вариант 1)
  • ↳   Документация (вариант 2)
  • ↳   Электротехника и электроэнергетика
  • ↳   F.A.Q. по программируемым логическим контроллерам (PLC)
  • ↳   Обсуждение F.A.Q. по PLC
  • ↳   F.A.Q. по выбору PLC
  • ↳   F.A.Q. по аппаратной части PLC
  • ↳   F.A.Q. по языкам программирования
  • ↳   F.A.Q. по структуре программ
  • ↳   F.A.Q. по взаимодействию PLC с HMI
  • О жизни
  • ↳   Для дома, для семьи
  • ↳   Комната смеха
  • ↳   Электродвижение

В программной среде CoDeSyS перестановка байт требуется для правильных показаний датчиков при сопряжении двух устройств (Master и Slave), или когда на одном устройстве один порядок байт в слове, а втором другой. Компьютеры и оборудование, как и люди, разговаривают на разных языках. Одни хранят данные «слева направо», другие «справа налево». трудности возникают, когда требуется считать данные.

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

Всё приходится делать своими силами, и доходить приходится самому. Так что, где-то я могу ошибаться, но это ничего страшного. итак продолжим…

Циферки, числа, данные

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

Возьмём число 10. Десять пальцев, десять яблок, десять корзинок, булочек и т.д. Понятие этого числа не изменяется, изменяется только его представление, римская цифра Х, машинная цифра в двоичной системе исчисления 1010 тоже «10».

А данные — это набор чисел, а так же и символов. Это некое машинное письмо. Ведь десять можно выразить и символами IO, и в форме записи на любом языке, или всё-таки значение в цифрах.

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

Процессоры говорят на разных языках, они имеют различные способы хранения абстрактного понятия числа «10».

Что хранит ПЛК?

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

  1. Самая первая и маленькая единица это Бит, он имеет два состояния 0 или 1.
  2. Байт это последовательность из 0 и 1. Байт состоит из 8 битов. То есть двоичная последовательность 10 является вот такой 00001010.

Биты нумеруются справа налево Бит 0 является младшим — это крайний правый, а бит 7 является старшим — это крайний левый.

А дальше мы с байтами поступаем как хотим, можно получить число Word — это два байта, или число Float — это 4 байта и т.д.

Машины отлично понимают, где находится «0» байт. И в каком порядке они начинают считаться. Ведь вроде бы все понятно с однобайтовыми системами. Но нет никакого соглашения в какой последовательности машины будут считать — в прямой или обратной.

Понятие указателя

Указатели являются ключевой частью программирования. Указатель представляет собой число, являющееся адресом в памяти. И это зависит только от нас (программистов), как интерпретировать данные по этому адресу.

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

p1: POINTER TO BYTE;(*указатель на результат функции*)

p2: POINTER TO BYTE;(*указатель на массив регистров*)

Это просто объявление указателя, теперь программист говорит ПЛК, что нужно указать на начало функции, и данные по этому адресу нужно интерпретировать как один символ (1 байт).

p1:=ADR(two_word_to_real);(*указатель на начало результата функции (1й байт)*)

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

Таким образом мы можем преобразовать два Word’a (2 байта) в Float (4 байта).

Давайте сначала объявим переменные:

FUNCTION two_word_to_real : REAL

VAR_INPUT

IN_Data: POINTER TO ARRAY[0..1] OF WORD;(*Указатель на массив регистров для Float*)

END_VAR

VAR

p1: POINTER TO BYTE;(указатель на результат функции)

p2: POINTER TO BYTE;(*указатель на массив регистров*)

END_VAR

Основная программа перестановки байт:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

p1:=ADR(two_word_to_real);(*указатель на начало результата функции (1й байт)*)

p2:=ADR(IN_Data^[1]);(*указатель на второй элемент массива (3й байт) *)

p1^:=p2^;(*копируем содержимое по адресу второго указателя

                                            в адрес первого указателя *)

p1:=p1+1;(*смещаем адрес на 1 байт, теперь он

                                      указывает на 2й байт результата*)

p2:=p2+1;(*смещаем адрес на 1 байт, теперь он

                                         указывает на 4й байт массива*)

p1^:=p2^;(*копируем содержимое по адресу второго

                                  указателя в адрес первого указателя *)

p1:=p1+1;(*смещаем адрес на 1 байт, теперь он

                                      указывает на 3й байт результата*)

p2:=ADR(IN_Data^[0]);(*указатель на первый элемент массива (1й байт) *)

p1^:=p2^;(*копируем содержимое по адресу второго

                                  указателя в адрес первого указателя *)

p1:=p1+1;(*смещаем адрес на 1 байт, теперь он

                                      указывает на 4й байт результата*)

p2:=p2+1;(*смещаем адрес на 1 байт, теперь он

                                         указывает на 2й байт массива*)

p1^:=p2^;(*копируем содержимое по адресу второго

                                  указателя в адрес первого указателя *)

В общем, как-то так. Если будут какие-то дополнения, пишите в комментариях.

Как я написал статью? Надеюсь понятно. Мне, честно говоря, тяжело даются эти понятия.

С уважением, Гридин Семён

Old
May 1st, 2021, 11:10 AM

 
#1

Member

United States

Intelduopower is offline

 

Join Date: Aug 2017

Location: Michigan

Posts: 2

Codesys convert word to bytes


Hello,

I am new to Codesys 3.5 and I am trying to figure out how to convert a word into 2 bytes to send to another plc via ethernet/ip. Could someone share copy of code that will make this work please?

Thank you
Intelduopower

 

Reply With Quote

Old
May 1st, 2021, 06:59 PM

 
#3

Lifetime Supporting Member

Japan

AlfredoQuintero is offline

 

Join Date: Feb 2015

Location: Yokohama

Posts: 1,303

Quote:

Originally Posted by Intelduopower
View Post

Hello,

I am new to Codesys 3.5 and I am trying to figure out how to convert a word into 2 bytes to send to another plc via ethernet/ip. Could someone share copy of code that will make this work please?

Thank you
Intelduopower

Hello and welcome to the forum. What is the other PLC brand?
It is important to understand this because EtherNet/IP has different mechanisms for communication. Codesys has EtherNet/IP scanner functionality with the basic license; so you can talk to a PLC which supports EtherNet/IP adapter functionality. It has an optional license for EtherNet/IP adapter with which it can communicate with an EtherNet/IP scanner. If the other PLC is a Logix processor, which supports CIP symbolic messaging, you can write a program that allows the Codesys program to read or write Logix tags directly. This is not trivial endeavour but fortunately there is a post in which some of us cracked this problem and may be useful for you.
It is a long post so you may want to go to page 5 directly.
http://www.plctalk.net/qanda/showthr…=127087&page=5

 

Reply With Quote

Old
May 2nd, 2021, 05:12 AM

 
#4

Lifetime Supporting Member

Israel

(8{)} ( .) is offline

 

(8{)} ( .)'s Avatar

 

Join Date: Apr 2004

Location: Israel

Posts: 616

Quote:

Originally Posted by Intelduopower
View Post

Hello,

I am new to Codesys 3.5 and I am trying to figure out how to convert a word into 2 bytes to send to another plc via ethernet/ip. Could someone share copy of code that will make this work please?

Thank you
Intelduopower

Not sure I understand the issue here. The data is stored in bytes regardless. All you need to do is to provide a pointer to your source data and reference the data in byte format in your destination. With atomic data the only issue you will encounter is arrays of BOOL data which is stored as DWORDs (each DWORD = 32 bits) in ControlLogix PLCs. If you’re reading data structures you’ll have to take padding into account.

Hope this helps,

(8{)} (: .)
(Yosi)

 

Reply With Quote

Old
May 2nd, 2021, 09:38 AM

 
#5

Member

United States

Intelduopower is offline

 

Join Date: Aug 2017

Location: Michigan

Posts: 2

I am sorry I forgot to add the second PLC. I am trying to get a Codesys 3.5 to pass an analog value over to a Automation Direct Productivity 2000 plc. I have attached a jpeg of the code that takes 2 bytes and converts them into a word. I had help from Eaton on this code but was trying to figure out on my own how to take a word and convert it to 2 bytes. The P2000 requires a array of bytes. All of the I/O is in Codesys so I am pass the data only one way. I have already got the two plc’s communicating and can send bool’s back and forth so I know that is working.

 

Reply With Quote

Old
May 2nd, 2021, 09:47 AM

 
#6

Lifetime Supporting Member

United States

drbitboy is online now

 

drbitboy's Avatar

 

Join Date: Dec 2019

Location: Rochester, NY

Posts: 6,521

Does Codesys have unions? If the byte order is the same, it means the conversion is done for you via declarations.

Here is one approach: http://www.plctalk.net/qanda/showthr…332#post464332

Here is another, using bits and a word but the concept is the same: https://forge.codesys.com/forge/talk…ad/c408834957/

__________________
_
Brian T. Carcich
i) Take care of the bits, and the bytes will take care of themselves.
ii) There is no software problem that cannot be solved with another layer of indirection.
iii) Measurement is hard.
iv) I solemnly swear that I am up to no good
v) I probably have the highest ratio of forum posts to actual applications in the field (∞).
vi) Hakuna matata.
vii) Bookkeeping.


Last edited by drbitboy; May 2nd, 2021 at 09:51 AM.

 

Reply With Quote

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

Первые программируемые контроллеры появились в 60-70 годах прошлого века для замены электромеханических реле и аналоговых регуляторов. Тогда для разработки программ было достаточно двух основных типов данных: логического – для представления дискретных сигналов и целочисленного – для представления аналоговых сигналов. Эволюция ПЛК расширила спектр выполняемых задач, что потребовало введения новых типов данных, одним из которых стали строки.

Строки могут использоваться для задач:

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

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

Строка – это массив чисел, каждое из которых соответствует определенному символу. Соответствие между числами и символами называется кодировкой. В CODESYS V3.5 присутствуют два типа строк – STRING и WSTRING. Основные характеристики типов строк 

Параметр

STRING

WSTRING

Кодировка

ASCII

UCS-2 (Unicode)

Размер символа

1 байт

2 байта

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

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

‘hello, world’

“привет, мир”

Выбор типа зависит от решаемой задачи. Например, для отображения строк в визуализации контроллеров ОВЕН следует использовать только тип 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)

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

FIND (STR1, STR2)

Производит поиск подстроки в строке

INSERT (STR1, STR2, POS)

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

LEFT (STR, SIZE)

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

LEN (STR)

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

MID (STR, LEN, POS)

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

REPLACE (STR1, STR2, LEN, POS)

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

RIGHT (STR, SIZE)

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

Примеры использования этих функций

sVar1 := ‘Hello, ’;
sVar2 := ‘world’;
// sVar3 теперь имеет значение ‘Hello, world’
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#2019-04-02-08:11:30;
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
sRecord: STRING := '123;456;789';
sSeparatorChar: STRING := ';';
auiSeparatorPos: ARRAY [0..10] OF INT;
i: INT;
j: INT;
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#45;

Заключение

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

Like this post? Please share to your friends:
  • Word to fb2 to online converter
  • Word to excel with formatting
  • Word to excel linking
  • Word to excel i love pdf
  • Word to epub microsoft