Страница 1 из 4 123 ... ПоследняяПоследняя
Показано с 1 по 10 из 32

Тема: Пример: Убыстрение опроса модулей Мх110 в CodeSys v2.3 через модули STRING[]

  1. #1

    Cool Пример: Убыстрение опроса модулей Мх110 в CodeSys v2.3 через модули STRING[]

    Во многих темах камрад Валенок постоянно говорит о том, что если мы хотим читать (или писать) много регистров подряд в CodeSys v2.3 по Modbus, то надо использовать модули STRING. Когда я про это читал, то я примерно понял о чём идёт речь - читать кучку байтов, и собирать из неё значения переменных.
    На форуме эта мысль проскакивает в куче тем, но никто не даёт рабочего (или понятного) примера. В одной из тем я обещался написать пост на блоге, когда разберусь. До поста - далеко, поэтому пишу кратко на форум.

    В чём особенность работы CodeSys v2.3?
    Основная её особенность в том, что каждый элемент, который мы добавляем в Конфигурацию ПЛК, всегда опрашивается (читается/пишется) одним отдельным запросом.
    Поэтому если мы для 8 каналов модуля аналоговых вводов добавим в конфигурацую ПЛК 8 значений FLOAT, и ещё и для статуса измерения канала добавим 8 WORD - то CodeSys будет забрасывать этот модуль 8 + 8 = 16 отдельными запросами.
    Если поглядеть в спецификацию протокола ModBus RTU, то мы увидим, что на уровне байтиков, которые передаются по линии данных, то один запрос на чтение регистра занимает (ориентируюсь на эту статью из инета https://ipc2u.ru/articles/prostye-re.../modbus-rtu/):
    * 1 Байт = Адрес устройства
    * 1 Байт = Команда (шо делать)
    * 2 Байта = Начальный адрес регистра (для чтения данных) - меняется только он
    * 2 Байта = Сколько штук регистров читать
    * 2 Байта = CRC (проверка верности данных)
    То есть, на чтение одного регистра нам надо пихнуть в линию запрос длиной 1 + 1 + 2 + 2 + 2 = 8 байтов.

    Ответ модуля на один наш запрос будет представлять такую кучу байтов:
    * 1 Байт = Адрес устройства
    * 1 Байт = Команда или код ошибки
    * 1 Байт = Длина данных дальше - у нас будет равно 2, так как один регистр модуля = 2 байтам
    * 2 Байта = Значение регистра, который мы читаем
    * 2 Байта = CRC (проверка верности данных)
    То есть, один ответ модуля с одним регистром займёт 1 + 1 + 1 + 2 + 2 = 7 байтов.

    А теперь множим это на число регистров (16 штук): 16 * 8 + 16 * 7 = 128 + 112 = 240 курвичных байтов у нас уходит на то, чтобы обменяться инфой с модулем!
    И байты-то ладно. А в линии ещё есть и разные паузы, и работает передача данных так (очень условно): Пауза - Запрос - Пауза - Ответ - Пауза. И эти паузы сжирают время, которое мы могли бы потратить на опрос других модулей.

    Как народ выходит из проблемы? Ведь CodeSys v2.3 не поддерживает групповое чтение регистров?
    В CodeSys v2.3 есть возможность читать байты (не регистры!) как STRING[79].
    В конфигурацию ПЛК можно добавить "String input module" или "String output module", в котором указать начальный адрес регистра и число байт для чтения. Один регистр занимает два байта.
    В этом случае такой STRING[] будет прочитан одним запросом Modbus целиком.
    Ограничение этого STRING[] - в 79 байтов. Больше прочитать нельзя и в этом случае вам придётся разбивать ваши регистры на два или более STRING[].

    Применить такой метод получится только если все регистры модуля IO идут подряд (или с небольшими пропусками): 0, 1, 2, 3, 4... и так далее.
    Есть некоторые устройства (например датчики WirenBoard WB-MSW), которые имеют пропуски в карте регистров, условно: 0, 1... 3, 4, 5... 8, 9, 10, 11. Если такие устройства позволяют читать несуществующие регистры (WirnBoard - нет), то даже в этом случае можно запросить подряд регистры с 0 до 11 в моём примере.

    Как рассчитывать длину байт и регистров?
    Один регистр WORD/INT занимает 2 байта
    Один регистр DWORD/LONG/FLOAT занимает 4 байта

    Вот пример моего подсчёта для одного канала модуля аналоговых входов (МВ110-224.8А):
    Код:
    	Байт		Назначение						Порядок Байт
    	1		= Положение десятичной точки	= (LSB)
    	2		= Положение десятичной точки	= (HSB)
    
    	3		= Целое значение измерения	= (LSB)
    	4		= Целое значение измерения	= (HSB)
    
    	5		= Статус измерения канала	= (LSB)
    	6		= Статус измерения канала	= (HSB)
    
    	7		= Циклическое время измерения	= (LSB)
    	8		= Циклическое время измерения	= (HSB)
    
    	9		= Измеренное значение Float32	= Старшая часть (LSB)
    	10		= Измеренное значение Float32	= Старшая часть (HSB)
    	11		= Измеренное значение Float32	= Младшая часть (LSB)
    	12		= Измеренное значение Float32	= Младшая часть (HSB)
    То есть, на один канал модуля МВ110-224.8А нам надо 12 байт.
    Значит, если мы хотим прочитать все 8 каналов этого модуля, нам понадобится 12 х 8 = 96 байт.
    У нашего STRING[] имеется ограничение в 79 байт. Значит, в данном случае мы разделим наши каналы на два STRING[]
    Я разделил так, чтобы один STRING[] был максимально заполнен: 6 каналов (72 байта) + 2 канала (24 байта).
    К этим каналам STRING прямо в конфигурации ПЛК мы привязываем переменные, и дальше работаем с ними.

    Как обрабатывать такие данные?
    В приложенном примере я написал свою версию обработки с подробными комментариями.
    Особенность моей версии в следующем. Из-за странных преобразований типов в CodeSys нельзя просто так взять и обращаться к переменной, которая приязана к такому каналу: она видится как строка, а нам нужны байты.
    Народ на форуме делает ручную привязку, объявляя в коде программы массив байтов и привязывая его через команду AT %QB...
    Мне этот способ не нравится, так как я не хочу следить за изменением адресов в конфигурации ПЛК, если я туда решу что-то добавить или изменить.
    Поэтому я (по аналогии со старым добрым языком СИ) использовать копирование буферов в память при помощи функции SysMemCpy() из библиотеки SysMem. Я копирую заданный мне буфер в свой, и разбираю его побайтно, забирая столько байтов, сколько мне необходимо:
    Код:
    VAR_INPUT
    	pData		: DWORD;					(* Указатель (ADR) на переменную начала буфера данных *)
    	pDataSize	: DWORD;					(* Максимальная длина данных от модуля - 12 байт *)
    END_VAR
    
    SysMemCpy(ADR(pBuffer), pData, pDataSize);
    Далее я использую операции с битами (SHL, SHR) и побитные операторы (AND, OR) для того, чтобы склеить число из двух байтов.
    Например, чтобы получить WORD из двух байтов, я делаю так:
    Код:
    CSParseMV8A.ValDigPoint		:= ((BYTE_TO_WORD(pBuffer[1]) OR SHL(BYTE_TO_WORD(pBuffer[2]), 8)));
    Младший байт (pBuffer[1]) идёт здесь первым, поэтому я его просто склеиваю через OR
    Старший байт идёт здесь вторым, поэтому его надо сдвинуть влево на 8 бит (из 16#0012 превратить в 16#1200) при помощи оператора SHR и снова склеить с нужным нам числом.
    Вот так склеивается Float32 (REAL):
    Код:
    VAR
    	pFloat32Val	: ARRAY [1..4] OF BYTE;		(* Буфер для сборки переменной типа Float32 побайтно = 4 байта *)
    END_VAR
    
    (* Теперь клеим Float32 по схеме из таблички выше *)
    (* Просто пихаем в наш буфер нужные байты в нужном порядке [3] [4] [1] [2] *)
    pFloat32Val[1] := pBuffer[11];
    pFloat32Val[2] := pBuffer[12];
    pFloat32Val[3] := pBuffer[9];
    pFloat32Val[4] := pBuffer[10];
    
    (* А теперь копируем этот кусочек памяти в нашу переменную REAL - длина = 4 байта (размер REAL) *)
    SysMemCpy(ADR(CSParseMV8A.ValFloat), ADR(pFloat32Val), 4);
    Я завёл ещё один массив из 4 байтов, в который подставляю байты для сборки Float в нужном порядке (он указан в коде в комментариях), а потом при помощи SysMemCpy представляю этот массив как 4 байта памяти и копирую его в значение типа Float.

    Аналогичным образом я делаю запись в модуль аналогового вывода МУ110-224.6У.
    Я разбираю число WORD (от 000,0% до 100,0% - от 0 до 1000) на два байта (старший и младший), а потом пихаю их в буфер из массва байтов.А уже этот буфер снова копирую в переменную, привязанную к каналу String Output module:
    Код:
    VAR
    	(* Тест записи в каналы модуля МУ110-224.6У *)
    	testBuffer	: ARRAY [1..12] OF BYTE;	(* Буфер, который мы будем передавать данные для каналов модуля *)
    	testValueCh1	: WORD := 1000;	(* 100,0% для канала *)
    	testValueCh2	: WORD := 1000;
    	testValueCh3	: WORD := 1000;
    END_VAR
    
    testBuffer[1] := WORD_TO_BYTE(testValueCh1 AND 16#00FF);		(* Младший байт *)
    testBuffer[2] := WORD_TO_BYTE(SHR(testValueCh1 AND 16#FF00, 8));	(* Старший байт *)
    
    testBuffer[3] := WORD_TO_BYTE(testValueCh2 AND 16#00FF);		(* Младший байт *)
    testBuffer[4] := WORD_TO_BYTE(SHR(testValueCh2 AND 16#FF00, 8));	(* Старший байт *)
    
    SysMemCpy(ADR(TestAQ), ADR(testBuffer), 12);
    Для того, чтобы было удобно работать, я написал функции, которые всю работу делают за меня. Им надо только подпихнуть ссыку на переменную канала STRING[] и длину байт:
    Код:
    (* Это для аналоговых входов *)
    (* Здесь каждый кусочек данных одного входв занимат 12 байт
    Поэтому номер канала можно вычислить, если домножать 12 на номер канала, считая с нуля
    НЕ забываем о том, что наши каналы разбиты на два куска STRING[]: с 1 по 6 и с 7 по 8!
     *)
    TestAIModule1 := CSParseMV8A(ADR(TestAI1) + (12 * 0), 12, FALSE); (* Канал 1 *)
    TestAIModule2 := CSParseMV8A(ADR(TestAI1) + (12 * 5), 12, FALSE); (* Канал 6 *)
    
    TestAIModule3 := CSParseMV8A(ADR(TestAI2) + (12 * 1), 12, FALSE); (* Канал 8 *)
    
    (* А тут ещё скучнее и проще: один модуль ввода параметров электросети = один кусок STRING[] *)
    TestPhaseL1 := CSParseME1Ch(ADR(testMEML1), 42);
    TestPhaseL2:= CSParseME1Ch(ADR(testMEML2), 42);
    TestPhaseL3:= CSParseME1Ch(ADR(testMEML3), 42);
    Кое-где моя логика несовершенна и немного накручена. Возможно, позже я что-то исправлю и переделаю более изящно.
    Однако сейчас весь пример работает (что видно на скриншотах), и даже на отрицательных значениях Float32 парсится корректно.
    Кому надо - пользуйтесь и дорабатывайте под себя!
    Изображения Изображения
    Вложения Вложения
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell

  2. #2

    По умолчанию

    Цитата Сообщение от Валенок Посмотреть сообщение
    Можно. Но проктологически ипользуя косяк КДС ))
    Но поюзав syslibcom - желание парится с конфигурацией пропадает
    Не, режим "Это не баг, а фича" не используем =)
    Ага, я ща изучаю его на CDS v3 для ускорения опроса. Кое-что получается ужо.
    На CDS 2.3 у меня проекты чаще всего простые по конфигурации - DI/DO, AI (температуры смерить).
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell

  3. #3

    По умолчанию

    Так могу ли я попросить тебя его тут подробно расписать? Я даже не в курсе про баг и фичу.
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell

  4. #4

    По умолчанию

    Зачем тогда про это всё упоминать? Знаю, да не скажу?
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell

  5. #5
    Пользователь Аватар для capzap
    Регистрация
    25.02.2011
    Адрес
    Киров
    Сообщений
    9,716

    По умолчанию

    Ну и говнокодище в красивой упаковке. То введение в заблуждение, то масло масленное, то перечисление всего подряд, чтоб видимо если начнут докапываться, сказать что опечатался. Детский сад какой то
    Bad programmers worry about the code. Good programmers worry about data structures and their relationships

    среди успешных людей я не встречала нытиков
    Барбара Коркоран

  6. #6

    По умолчанию

    capzap Критикуя - предлагай ©
    Что не так? Я знаю один момент - что я указатели на адреса на валидность не проверя.
    Там, где МНЕ непонятно (про байты или буферы) - я писал и буду писать подробные комментарии.

    А дополнительно я скажу вот что.
    Когда я в некоторых случаях (SysCom) просил помощи - то 2/3 участников форума много говорили, но мало кто давал нормальные (откомментированные, с понятными названиями и назначениями переменных) примеры. Большинство давали или огрызки, или спагетти-код.
    Поэтому мне сейчас стало давно плевать. Я обещал, что как сделаю свой рабочий пример, выложу его. Я - выложил. А большинство из вас - нет.
    Последний раз редактировалось Алексеев Савр; 18.07.2021 в 20:02.
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell

  7. #7
    Пользователь Аватар для capzap
    Регистрация
    25.02.2011
    Адрес
    Киров
    Сообщений
    9,716

    По умолчанию

    Вы так усиленно делаете упор на комментарии, так сами то их прочтите, если коммент не отражает назначение переменной это разве правильный код, он уже попахивает
    Bad programmers worry about the code. Good programmers worry about data structures and their relationships

    среди успешных людей я не встречала нытиков
    Барбара Коркоран

  8. #8

    По умолчанию

    capzap Что? Где? Я не понял, о чём речь.
    Если кто-то потрудился посмотреть код, то пусть дотрудится до конца и укажет на ошибки или неточности.
    В отличие от богов и тех, кто мнит себя ими, я не идеален.
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell

  9. #9
    Пользователь Аватар для capzap
    Регистрация
    25.02.2011
    Адрес
    Киров
    Сообщений
    9,716

    По умолчанию

    Смотрите первый пост, яи проект не открывал
    Bad programmers worry about the code. Good programmers worry about data structures and their relationships

    среди успешных людей я не встречала нытиков
    Барбара Коркоран

  10. #10

    По умолчанию

    Цитата Сообщение от Cs-Cs Посмотреть сообщение
    Критикуя - предлагай ©
    Я - выложил. А большинство из вас - нет.
    Как по мне, то код должен быть простым и понятным, даже для новичков. Как вариант, уже предлагал считывать все параметры МВ 8А не байтами, а массивом WORD, что для многих будет проще, так как почти все параметры и читаются в этом виде и не собирать их из байтов. Как известно все регистры занимают 96 байт, разбиваем их пополам, для удобства (не надо высчитывать номера всех регистров, а только половины). В конфигурацию добавляем два String и присваиваем им имена Str14 с 0 адреса, длиной 48 байт и Str58 с 24 адреса и тоже 48 байт.Стринги.jpg
    А что бы уйти от ручной привязки, по команде AT %QB..., можно объявить указатель на массив WORD по 24 регистра (48 байт) и сделать это в глобальных, чтобы был доступ из любого ФБ. Ну и конечно, тут же объявим нужные нам переменные. Целочисленные без смещения точки, объявляем как INT, чтобы не потерять знак.
    Код:
    VAR_GLOBAL
    	w_1  : POINTER TO ARRAY [0..23] OF WORD;   (* Указатели на массив WORD   *)
    	w_2  : POINTER TO ARRAY [0..23] OF WORD;
    
    	w1,w2,w3,w4,w5,w6,w7,w8 : INT ;     (* Целочисленное значение без смещения  INT   *)
    	d1,d2,d3,d4,d5,d6,d7,d8 : WORD;     (* Смещение точки WORD   *)
    	r1,r2,r3,r4,r5,r6,r7,r8 : REAL;     (*Значения  REAL   *)
            r_1 : REAL;  
    END_VAR
    Далее создаём ФБ, например "Opros_MVA", в котором будет только присвоение начальных адресов массивов, ну и если понадобиться присвоение нужных нам переменных.
    Код:
    FUNCTION_BLOCK Opros_MVA
    VAR
    END_VAR
    ____________________________________________________________________________________________________________
    w_1:=ADR(Str14);     (*Начальный адрес  в массиве  WORD *)
    w_2:=ADR(Str58);
    Осталось только объявить в PLC_PRG и в его теле, наш функциональный блок (ФБ). Добавил ещё в примечании, все нужные нам регистры МВА8, для наглядности, пример вызова некоторых переменных из массивов и расчёт REAL из целочисленного значения.
    Код:
    PROGRAM PLC_PRG
    VAR
    (* Модули МВА_8А  нужны  регистры -  0,6,12,18,24,30,36,42 -  положение десятичной точки,  1,7,13,19,25,31,37,43  - измеренное целое значение без смещения, 
     2,8,14,20,26,32,38,44 - статус канала,           3,9,15,21,27,33,39,45 -   циклическое время,         4-5,  10-11 , 16-17,  22-23,  28-29,  34-35,   40-41,   46-47  -  значение REAL  *)
    
    	Opros_MVA:Opros_MVA;
    
    END_VAR
    _____________________________________________
    
    Opros_MVA();
    
    
     (* Смещение точки WORD   *)
    d1:=w_1^[0];
    
       (* Целочисленное значение без смещения  INT   *)
    w1:=w_1^[1];
    
    (* Расчёт REAL   из целочисленного значения   *)
    r_1:=w1/10.0;
    
    (* Склейка    2 _ WORD для получения REAL     *)
    TWO_WORD_TO_REAL(wIn1:=w_1^[5] , wIn2:=w_1^[4] , rOut=>r1 );
    Запускаем программу и видим такую простыню из значений в глобальных, далее каждый выберет, то что ему нужно. Глобальные.jpg
    Ниже выложил пример проекта. Если кому то будет мало одного знака после запятой, добавил ещё ФБ для склейки REAL из двух WORD и пример его использования. Кстати таким же образом можно считать и массивы из REAL, правда WORD будут перепутаны и остальные данные потеряем. Проект написан для ПЛК 154УМ, при желании можно поменять на любой ПЛК, достаточно добавить в конфиг. UMD с вашим адресом МВА и два String.
    Весь код без объявления переменных и примера вызова занял 4 строки!!! Это конечно, не такой красивый код, как у Валенка, но простой и понятный.
    Вложения Вложения
    Последний раз редактировалось kondor3000; 19.07.2021 в 22:52.

Страница 1 из 4 123 ... ПоследняяПоследняя

Похожие темы

  1. модули ввода/выводв Мх110 [М01]
    от yurya в разделе Мх110
    Ответов: 4
    Последнее сообщение: 06.07.2019, 14:03
  2. Модули МХ110 для CODESYS 3.5
    от Осинский Алексей в разделе СПК2хх
    Ответов: 429
    Последнее сообщение: 13.12.2017, 13:53
  3. Шаблоны модулей МХ110 для CODESYS 3.5
    от Александр Приходько в разделе СПК2хх
    Ответов: 91
    Последнее сообщение: 24.04.2015, 18:29
  4. Ответов: 4
    Последнее сообщение: 10.02.2015, 16:12
  5. Ответов: 7
    Последнее сообщение: 30.11.2010, 10:02

Метки этой темы

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •