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

Тема: Пример: Убыстрение опроса модулей Мх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
    Мой рабочий блог со статьями про щиты и автоматику ОВЕН - Cs-Cs.Net | Почта: Info@Cs-Cs.Net | Канал в ТГ @CsCsNetLab

  2. #2

    По умолчанию

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

  3. #3

    По умолчанию

    Так могу ли я попросить тебя его тут подробно расписать? Я даже не в курсе про баг и фичу.
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell
    Мой рабочий блог со статьями про щиты и автоматику ОВЕН - Cs-Cs.Net | Почта: Info@Cs-Cs.Net | Канал в ТГ @CsCsNetLab

  4. #4

    По умолчанию

    Зачем тогда про это всё упоминать? Знаю, да не скажу?
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell
    Мой рабочий блог со статьями про щиты и автоматику ОВЕН - Cs-Cs.Net | Почта: Info@Cs-Cs.Net | Канал в ТГ @CsCsNetLab

  5. #5

    По умолчанию

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

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

  6. #6

    По умолчанию

    capzap Что? Где? Я не понял, о чём речь.
    Если кто-то потрудился посмотреть код, то пусть дотрудится до конца и укажет на ошибки или неточности.
    В отличие от богов и тех, кто мнит себя ими, я не идеален.
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell
    Мой рабочий блог со статьями про щиты и автоматику ОВЕН - Cs-Cs.Net | Почта: Info@Cs-Cs.Net | Канал в ТГ @CsCsNetLab

  7. #7

    По умолчанию

    Цитата Сообщение от Cs-Cs Посмотреть сообщение
    Критикуя - предлагай ©
    Я - выложил. А большинство из вас - нет.
    Пример опроса модуля МВ110-8А через String
    Как по мне, то код должен быть простым и понятным, даже для новичков. Как вариант, уже предлагал считывать все параметры МВ 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 строки!!! Это конечно, не такой красивый код, как у Валенка, но простой и понятный.
    Цитата Сообщение от Cs-Cs Посмотреть сообщение
    На форуме эта мысль проскакивает в куче тем, но никто не даёт рабочего (или понятного) примера.
    petera неоднократно выкладывал примеры записи, чтения через STRING https://owen.ru/forum/showthread.php...l=1#post244022
    Вложения Вложения
    Последний раз редактировалось kondor3000; 31.05.2025 в 10:37.

  8. #8
    Пользователь
    Регистрация
    28.08.2008
    Адрес
    23..93..123
    Сообщений
    1,799

    По умолчанию

    Вот вместо того чтобы освоить работу с библиотеками, ради фейковых упрощений, люди идут на реальное усложнение проекта и непрозрачную его работу. Как это объяснить я не понимаю, те, кто на LD и СFC сидят это тоже не освоят, хотя строку можно "клеить" и "переклеивать" и вырезать с помощью стандартных LEN и прочего.
    Просят показать..., так тут же начинается:-"Оот ничего нипонятна, да каминтариев не хватает, та указатели на валидность не проверены(трудно что-то тупее заявить). Пробуйте сами, при этом не забывая:- "При использовании чужого кода или идеи крайне необходимо использование собственного мозга...Если мозга нет- вперед конюшни чистить...

  9. #9

    По умолчанию

    Цитата Сообщение от Валенок Посмотреть сообщение
    Можно. Но проктологически ипользуя косяк КДС ))

    ---
    Но поюзав syslibcom - желание парится с конфигурацией пропадает
    Валенок, конфигурация для начинающих и простых проектов. Или Вы там ожидали минискаду?
    Тролль-наседка, добрый, нежный и ласковый

  10. #10

    По умолчанию

    kondor3000 Пасибо!
    Да, я про DWORD не догадался. Сработала инерция мышления - что раз у нас массив байт, то работать будем с байтами.
    А регистры же DWORD, точно! =)

    Sergey666 Опять высказывания в духе "у вас изо рта щами пахнет" © Алекс Экслер.
    В смысле, что в них звучит адский апломб, и унижение всех вокруг.
    А вот зачем мне библиотеки, если у меня в проекте шесть модулей дискретного IO, где надо читать только маску входов или маску выходов (один элемент в конфигурации ПЛК всего), и один модуль AI? Ради одного модуля тащить библиотеку?
    А если проект у меня совсем примитивный, которому библиотек не требуется ВООБЩЕ? Тогда у меня библиотека появится ради опроса модулей?
    Нет уж! Мести всех под одного и унижать - это плохая стратегия. Прежде чем всех судить, надо понять людей и то, почему и для чего они так делают.

    Про переклеивать строку. Вот тут я объясню, почему нельзя. Потому что конец строки для функций работы со строками типа LEN, CONCAT в CodeSys, как в СИ - определяется по первому нулевому байту. То есть "Sergey66616#00ляляляля" - конец строки будет на нулевом байте.
    Если этот нулевой байт окажется в начале строки, то строковые функции увидят пустую строку, даже если там есть данные.
    Последний раз редактировалось Алексеев Савр; 21.07.2021 в 16:17.
    Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте. © Steve McConnell
    Мой рабочий блог со статьями про щиты и автоматику ОВЕН - Cs-Cs.Net | Почта: Info@Cs-Cs.Net | Канал в ТГ @CsCsNetLab

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

Похожие темы

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

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

Ваши права

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