Существует библиотека SysLibCom для CodeSys 3.5 ?
Если да, то где ее скачать?
Вид для печати
Существует библиотека SysLibCom для CodeSys 3.5 ?
Если да, то где ее скачать?
В состав CODESYS 3.5 входит несколько библиотек для работы с COM-портом - в частности, SysCom (аналог SysLibCom c приблизительно тем же набором функций/ФБ) и CAA SerialCom. Скачивать ничего не надо, просто добавьте их в Менеджере библиотек.
Пример работы с CAA SerialCom приведен в документе СПК. Реализация нестандартных протоколов:
http://ftp.owen.ru/index.html/CoDeSy...cols_v.1.0.pdf
Спасибо за подсказку
Здравствуйте.
А подскажите, как работать с дескриптором открываемого порта на языке ST? (библиотека CAA SerialCom).
При открытии порта мы должны получить дескриптор:
myComOpen(usiListLength := SIZEOF(arrParams)/SIZEOF(COM.PARAMETER), pParameterList := ADR(arrParams));
А при отправке в порт дескриптор использовать:
myComWrite(xExecute := TRUE, udiTimeOut := 1000, hCom := myComOpen.hCom, pBuffer := ADR(arrMassive), szSize := 5);
Правильно ли я его использую? В порт ничего не пишется.
Что я делаю не так?
UP: Почему этот hCom всегда равен 0? Пробовал разные порты.
Скрин:
Вложение 62107
Потому что вы не дожидаетесь открытия порта (myComOpen.xDone).
Изучите этот документ:
https://ftp.owen.ru/CoDeSys3/11_Docu...cols_v.3.0.pdf
Хм... Странно.
Реализация:Скрытый текст:
Я нажимаю на клавишу на экране визуализации, которая устанавливает флаг xOpen.Код:IF xOpen THEN
xOpen := FALSE;
myComOpen(usiListLength := SIZEOF(arrParams)/SIZEOF(COM.PARAMETER), pParameterList := ADR(arrParams));
END_IF
myHandle := myComOpen.hCom;
xError := myComOpen.xError;
myError := myComopen.eError;
xSucces := myComOpen.xDone;
А далее долго-долго наблюдаю за 4-я параметрами. И они не меняются...
Скрин:
Вложение 62112
Но ничего так и не происходит...
Пробовал перебирать порты - результата нет.
Может есть проект на ST для образца?
См. ссылку в моем прошлом посте.Цитата:
Может есть проект на ST для образца?
В общем тестировал библиотеку CAA SerialCom, перебирал порты, курил доки Овена.
Попытка открыть порт приводит к состоянию флагов:
Вложение 62118
Что мы видим в этом окне:
xDone - никогда не устанавливается,
xError - никогда не устанавливается,
xBusy - всегда установлен.
Какой мы вывод делаем:
Открытие порта не заканчивается
Ошибок не возникает
Процесс идет бесконечно долго.
Я могу порекомендовать только внимательнее "курить доки".
У вас xOpen взводится на один цикл и myComOpen тоже вызывается только один цикл.Код:IF xOpen THEN
xOpen := FALSE;
myComOpen(usiListLength := SIZEOF(arrParams)/SIZEOF(COM.PARAMETER), pParameterList := ADR(arrParams));
END_IF
За один цикл порт не успевает открыться, и блок зависает в xBusy.
Обычно делают так:
Или можно использовать USR_COM_CONTROL из документа - это обертка над COM.Open / COM.Close, которая будет существенно более проста в использовании для начинающих программистов, чем эти блоки.Код:myComOpen(xExecute := xOpen, usiListLength := SIZEOF(arrParams)/SIZEOF(COM.PARAMETER), pParameterList := ADR(arrParams));
IF myComOpen.xDone THEN
myHandle := myComOpen.hCom;
END_IF
Ну да ладно. Отправка байтов налажена. Теперь есть проблемы с приемником. Пишем в программе:
Сую в порт одиночные символы / не сую в порт одиночные символы.Код:myComRead(xExecute := TRUE, hCom := myComOpen.hCom, pBuffer := ADR(arrMassiveReceive), szBuffer := 10, udiTimeOut := 1000);
xDone всегда отключен. xBusy всегда включен. Что можете посоветовать начинающему программисту?
Как и раньше - внимательно изучить документ по ссылке из поста #5.
Нет.
У меня вопрос!
В блоке Read есть параметр udiTimeOut. Он определяет время (толи в mS, толи в uS) через которое произойдет таймаут по приему.
Я записал туда число 10 000 000.
Что произойдет через этих 10 миллионов попугаев? Какой флаг должен установиться через это время? Что должно измениться?
Правильно ли я понимаю, что функциональные блоки Write и Read реализованы по разному (что затрудняет их понимание и читаемость кода)?
1. Ф-блок Write не отдаёт xDone до тех пор, пока физически не отдаст последний байт линию COM-порта. Я пробовал на скорости 1200 отдать 255 байтов. Ф-блок Write блокирует программу более 2-х секунд. Реализовано хорошо: исключается наложение отдаваемых пакетов (потому-что блокируется программа).
2. Ф-блок Read отдаёт xDone сразу же, как только физически добрался до буфера. Придется сталкиваться с пустыми массивами, закольцованными массивами (придется проверять это). Параметр udiTimeOut выполняет непонятную своим смыслом функцию. Этот параметр должен был блокировать программу на указанное время, а после отдавать xDone, размер принятых байт (szSize) и принятый буфер (arrBuffer)! И ненужно было бы вводить свои кастомные таймера для таймаута, код был бы понятнее.
Ну а если нельзя блокировать программу, то зачем это сделано в ф-блоке Write? Он тоже мог бы тупо перекидывать в буфер и сразу же отдавать xDone? Пусть байты вылетают из порта своим ходом (хардовым способом).
В общем: Write блочит прогу, Read не блочит прогу.
PS: А еще обратите внимание, что параметр szSize отдает не количество принятых байт, а указывает на последний байт в кольцевом (!) буфере приема (arrBuffer). И если принятая посылка будет больше буфера, то вы получите недостоверное значение.
Можно, наверное, и так.Цитата:
Придется сталкиваться с пустыми массивами, закольцованными массивами (придется проверять это).
Или можно почитать документ, посмотреть пример из него - и обойтись без всех этих столкновений.
Впрочем, я понимаю, что вы не ищите легких путей.
Должен кому?Цитата:
Этот параметр должен был блокировать программу на указанное время, а после отдавать xDone, размер принятых байт (szSize) и принятый буфер (arrBuffer)!
Библиотека CAA SerialCom включает в себя асинхронные ФБ, которые не блокируют задачу, в которой вызываются.
В этом их преимущество - сохранение стабильного времени цикла без лишних джиттеров.
Для любителей "блокировать программу" - есть библиотека SysCom.
Тогда другой пользователь спрашивал бы: почему после отправки запроса я читаю приемный буфер и вижу в нем свои же отправленные байты?Цитата:
Он тоже мог бы тупо перекидывать в буфер и сразу же отдавать xDone? Пусть байты вылетают из порта своим ходом (хардовым способом).
В общем, так себе решение.
А если читать документацию и разумно подходить к написанию кода - то таких проблем не будет.Цитата:
И если принятая посылка будет больше буфера, то вы получите недостоверное значение.
Ок. Спасибо за разъяснения.
Получается, что эта библиотека асинхронная. По задумке не блокирует программу.
Но отправка пакета реализована с блокировкой из-за того, что-бы не ловить свои-же байты.
Евгений, здравствуйте.
А можете объяснить почему не срабатывает такая конструкция при открытии порта?
Правильно ли я понимаю, что из-за того, что я блокирую программу?Код:WHILE NOT myComOpen.xDone DO
myComOpen(xExecute := TRUE, usiListLength := SIZEOF(arrParams)/SIZEOF(COM.PARAMETER), pParameterList := ADR(arrParams));
END_WHILE
Привет.
Помогите решить такую проблему:
Мне нужно получить посылку длинной 39 байт. Посылка должная прийти асинхронно. Т.е. чёрт знает когда.
Мне приходится постоянно циклически слушать порт:
Пакет принимается, но иногда поломанный. Я записывал длину каждого принятого пакета в массив и увидел вот что:Код:CASE bStep OF
00: ...
01: // Принимаем пакет
myComRead(xExecute := TRUE, hcom := hCom, pBuffer := ADR(arrReceive), szBuffer := 255, udiTimeOut := 0);
IF myComRead.xDone THEN
bStep : = 2;
END_IF
02: // Проверяем длину принятого пакета
IF myComRead.szSize = 0 THEN // Нулевая длина: уходим в начало
myComRead(xExecute := FALSE);
bStep := 1;
ELSE
bStep := 4;
END_IF
04: ...
END_CASE
arrBufferLen[] = 0, 0, 29, 10, 0, 0, 0, 0, 0, 0, 0
Т.е. принимаемые 39 байт разбились на отдельные пакеты длиной 29 и 10 байт.
Очевидно, что это происходит из-за рассинхронизации процессов.
Как этого избежать? При этом длина входного пакета всегда разная.
ВладОвен Мне Евгений Кислов помогал с подобным примером.
Судя по этому примеру (и реальной жизни) такой принцип работы - норма (а я тогда как глупый как раз и думал, что все мои байты придут за один раз, и из-за мой код не хотел работать).
В примере Евгения Кислова был такой алгоритм-принцип: сколько байт получили - столько и склеиваем в один общий массив байт, пока не наберём нужную длину посылки.
После этого посылка идёт на обработку (проверка корректности и прочего).
Если посылка верная - то что-то с ней делаем.
Если ошибочная (или в течение таймаута не пришло заданное число байт) - обнуляем буфер, счётчик принятых байт, и начинаем всё сначала.
Добавил: если там длина неизвестна - то так по байтам и клеить и парсить на лету (искать заголовок или какую-то инфу, которая покажет о том, что началась новая посылка данных). Скажем, так: приняли байт - запустили таймер TOF. Если были байты, пока он ещё считает - то значит это наша посылка. А если байтов не было - то посылка кончилась.
Привет.
Решил эту задачу. Пока работает нормально.
Я просто каждый раз после полученного пакета N1 (длинна не равна 0) сразу же принимаю второй пакет и если его длинна N2 не равна 0 , то я их склеиваю:
Код:CASE bStep OF
01: ...
02: // Ожидаем пакет (N1)
myComRead(xExecute := TRUE, hcom := hCom, pBuffer := ADR(arrBufferN1), szBuffer := 255, udiTimeOut := 0);
IF myComRead.xDone THEN
bStep := 3;
END_IF
03: // Проверяем длину принятого пакета (N1)
IF myComRead.szSize = 0 THEN // Нулевая длина
myComRead(xExecute := FALSE);
bStep := 2; // Повторно ожидаем пакет (N1)
END_IF
ELSE
udiSizeN1 := myComRead.szSize; // Длинна пакета (N1) была не нулевой
myComRead(xExecute := FALSE);
bStep := 04;
END_IF
04: // Принимаем пакет (N2)
myComRead(xExecute := TRUE, hcom := hCom, pBuffer := ADR(arrBufferN2), szBuffer := 255, udiTimeOut := 0);
IF myComRead.xDone THEN
bStep := 5;
END_IF
06: // Проверяем длину принятого пакета (N2)
IF myComRead.szSize <> 0 THEN // Длина не нулевая. Значит пакет был разбил на два отдельных. Склеиваем их.
udiSizeN2 := myComRead.szSize;
MEM.MemMove(ADR(arrBufferN2), ADR(arrBufferN1) + udiSizeN1, UDINT_TO_UINT(udiSizeN2));
END_IF
myComRead(xExecute := FALSE);
bStep := 5;
05: ...
END_CASE
ВладОвен Приятно посмотреть на код: везде комментарии и, если со стороны читать, то всё понятно. Я такое люблю.
Ты не занимаешься уходом от магических чисел (это программистский термин)? Не хочу навязывать, но я все шаги именую через константы, то есть:
Так код и Case выглядят лучше, чем цифирки: с цифирками легко запутаться, когда добавляешь или удаляешь шаги.Код:VAR_CONSTANT
wStep_Wait : WORD := 0;
wStep_Recieve : WORD := 1;
wStep_Process : WORD := 2;
...
END_VAR
А ещё хотел спросить: у тебя в коде есть защита от ухода за границы буфера?
Вот в этом месте
у тебя есть защита от того, чтобы этот адрес не ушёл за границы длины arrBufferN1?Код:ADR(arrBufferN1) + udiSizeN1
Про такие вещи надо думать, иначе потом они могут вызывать сбой программы.
Ещё удобно использовать перечисления (создать пользовательский тип данных) например:
При этом с типом enStateMotor можно работать как с INTКод:TYPE enStateMotor : (
gc_iMotorStopped := 0 ,
gc_iMotorStopping := 1 ,
gc_iMotorBlocked := 2 ,
gc_iMotorErrStart := 3 ,
gc_iMotorWork := 4 ,
gc_iMotorStarting := 5 ,
gc_iMotorNoFeedback := 6
);
END_TYPE
1exan Ага, я про перечисления знаю, но не хочу их использовать, так как перечисления внутри FB не объявишь, а у меня чаще всего автоматы состояний находятся и работают в FB. Мне хочется, чтобы FB был максимально самодостаточен и при копировании его по другим проектам не надо было ещё и DUT тащить.
Поэтому внутри FB мне удобнее константы сделать.
Если я не прав и внутри FB можно перечисление объявить - прошу меня поправить.
Есть такая недокументированная (на данный момент) фича:
https://owen.ru/forum/showthread.php...l=1#post369251
Можно, это же получается такой-же тип данных как BOOL, WORD - можно использовать и внутри блоков.
Но тут надо не забывать если переносишь блок в другую программу, то и перечисление, используемое внутри тоже надо переносить. (если через импорт-экспорт надо выделить и блок и свой тип данных при экспорте).
UPD:
Можно - это я конечно имел в виду использование, а не объявление перечисления. Про фичу не знал.
Перечисления удобны, если переменная состояния FB используется и снаружи FB.
1exan Вот, сначала пишем, потом читаем =) Я говорил о FB, так как снаружи это наоборот - не должно быть доступно.
Евгений Кислов О! СПАСИБО!
Хм... как бы уже придумать то, как этот FAQ распечатать со всех страниц сразу в PDF. Там сведений куча, а не все упомнишь!
Сделал пока себе скриншот ответа!
Да. Этот способ тоже хорош. Но в этом проекте я решил пока от этого отказаться.
Потому что последовательность очень длинная - на 100 шагов. Я просто боюсь, что потом запутаюсь в последовательности действий.
Всё таки: числа дают последовательность перечисления, а слова - нет.
Т.е. мы всегда знаем, что после шага 56 идет шаг 57, а потом и шаг 58.
И наоборот, если со словами: после startZeroOperation идет waitCountOffset? Или waitCoeffView? Или, может быть, waitJeater? А, вспомнил! Потом идет goStableFlow! (но это не точно...).
Но с числами есть недостаток: если понадобится вставить доп.промежуточный шаг в будущем, то придется сдвигать все числа вручную. И это неудобно.
Наверное, оптимально делать всё же перечисление, но такого формата:
10_startZeroOperation,
20_waitCountOffset,
30_waitCoeffView,
40_waitJeater,
50_goStableFlow.
В этом случае и понятна последовательность шагов (за счет чисел), и смысл шага (за счет слов), и вставить доп.промежуточный шаг можно за счет предварительного пропуска чисел (например, 35_smokeDocsOwen).
Я конечно не знаю как у вас организована программа, но я в таких случаях делаю так:
Если мне надо добавить шаг, я просто вставляю его в нужное место перечисления и перенумеровываю значения заново (в екселе - делается за 1 сек если разделители - табуляция):
Например:
При этом операторКод:Было
TYPE enStateMotor : (
gc_iMotorStopped := 0 ,
gc_iMotorStopping := 1 ,
gc_iMotorBlocked := 2 ,
gc_iMotorErrStart := 3 ,
gc_iMotorWork := 4 ,
gc_iMotorStarting := 5 ,
gc_iMotorNoFeedback := 6
);
END_TYPE
Стало:
TYPE enStateMotor : (
gc_iMotorStopped := 0 ,
gc_iMotorStopping := 1 ,
gc_iMotorBlocked := 2 ,
gc_iMotor_NEW_STATE := 3 ,
gc_iMotorErrStart := 4 ,
gc_iMotorWork := 5 ,
gc_iMotorStarting := 6 ,
gc_iMotorNoFeedback := 7
);
END_TYPE
и конструкции типаКод:CASE State OF
gc_iMotorStopped..gc_iMotorWork, gc_iMotorNoFeedback:
...
ну и т.д. не перестают работатьКод:State > gc_iMotorStopped AND State < gc_iMotorNoFeedback
ВладОвен, я имел ввиду что и в CASE шаги тоже называются по имени. Ща, сделаю скриншот, покажу как у меня в FB окучено.
Вложение 62361 Вложение 62362
А если ты хочешь прикалываться, то так тебе надо не под каждый пакет свой шаг мутить, а, ИМХО, в этом конечном автомате ловить сам пакет, а потом отсылать его на какую-нить функцию типа "ProcessData()", которая и будет соображать, что там за данные пришли и что с ними сделать.
В случае чего можно даже очередь пакетов сделать =)
Привет.
Скажите, а как будут работать два устройства, которые висят на одном порту, но одно из них работает на ModBus-Slave, а другое - по нестандартному протоколу (библиотека SysLibCom)?
Будут ли конфликты?
Так и есть: порт не может быть открыт.
Привет.
Можно ли организовать обмен информацией с двумя устройствами на одной шине RS485, НО меняя частоту обмена на лету: 115200 и 9600 ?
Так получается, потому что у меня есть один датчик, и у него частота только 9600, а все другие устройства работают на максимальной - 115200.
а) Для стандартного компонента ModBus.
б) Для нестандартного протокола.
Нет.
Имеется ввиду: устройства с протоколом ModBus на одной шине, но один тип устройств (8шт.) работает только на скорости 9600, а другой тип устройств (16шт.) - на скорости 115200.
Но, давайте еще рассмотрим аналогичный вопрос: устройства с нестандартным протоколом на одной шине, но один тип устройств (8шт.) работает только на скорости 9600, а другой тип устройств (16шт.) - на скорости 115200.