Показано с 1 по 10 из 44

Тема: Пример: Убыстрение опроса модулей Мх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

Похожие темы

  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

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

Ваши права

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