спасибо) думал, что так будет правильнее.
Тем не менее - вопрос всё же открыт. Я понимаю, что работать будет и так - я не собираюсь где-то в PLC_PRG использовать паузы, тормозящие работу считывания в том числе. Но чисто для общего развития хотелось бы, чтоб кто-то ткнул носом в пример, где многозадачность опроса реализована. Чем больше понимаешь - тем спокойнее спится
Пока что у меня есть желание разобраться в более сложных моментах 
Насчёт кода - попробую тут прикрепить основные моменты, но не гарантирую полную работоспособность. Я ещё не до конца разобрался с обработкой ошибок. Грубо говоря - пока всё штатно - значения считываются. Но что будет, если весовой модуль уйдёт в ошибку, или ещё при каких-то условиях - тут уже сам не знаю. Но кому-то как отправная точка для начала общения с прибором - может пригодиться 
Например - если выдернуть шнур подключения тензоатчика - модуль успевает послать рандомное значение массы (которое в сотни раз может превышать реальное), и только потом падает в ошибку, прекращая вещание в порт.
Опять же - не уверен, что моя реализация максимально оптимальна в плане грамотного кода, выделения памяти под переменные и прочего.
Перечислю минусы на всякий случай и в название темы вынесу название прибора, чтоб проще было найти кому-то.
Пример основан на видео уважаемого товарища с канала КодесисВанЛав. Там был пример работы с нестандартным протоколом.
Минусы моей реализации:
1) пока что нормально не обработаны ошибки связи и чтения
2) пока что НЕ реализован сдвиг по накопительному буферу (в идеале - если пакет пришёл частями - нужно слепить всё это в одном буфере, обработать, и не сразу обнулять буфер, а просто сдвинуть указатель на необработанную часть, потому что там тоже может быть начальная часть следующего пакета, ИМХО). А сам накопительный буфер я планирую не обнулять при близости переполнения, а просто сдвигать необработанную часть в начало буфера. Мне кажется - так правильнее
3) этот весовой модуль помимо веса и статуса выплёвывает ещё "байт состояния ламп". У него есть 7 ламп: ST, TARE, NET, HOLD, ZERO, kg, t. Почему-то при изменении свечения ламп HOLD, kg и t ни один бит в этом байте не меняется. Либо эти состояния вообще не передаются по каким-то причинам, либо я чего-то не догнал. Состояние остальных ламп я прекрасно считываю и отображаю у себя на отладочной странице визуализации. А эти три не могу. В поддержке мне сказали, что лампы kg и t вообще особо ни на что не влияют (я так и не понял - при одной и той же нагрузке прибор показывает 4.0 и в режиме kg, и в режиме t, хотя в режиме t я ожидал деления на 1000 как бы).
Итак, полученные и распарсенные данные я собираю в структуру, объявленную в PLC_PRG, и передаваемую в ФБ чтения как IN_OUT (скорее всего, это тоже не очень верно)
Из PLC_PRG я вызываю ФБ чтения, у ФБ чтения есть два метода - парсинг и очистка буферов. Помимо этого в проекте есть несколько перечислений (отвечающих за шаг выполнения процесса чтения порта и за состояния, передаваемые прибором).
Библиотека для работы с ком портом: CAA SerialCom 3.5.3.0,
CodeSys 3.5 SP5 patch 5
Результат выводится вот на такой отладочный экран 
Monosnap Otladko - Google Chrome 2025-02-18 00.16..png
boobs.png
код по порядку из скрина:
Код:
TYPE CAS_NetBrut :
(
HZ_beleberda :=0,
GS_Brutto := 1,
NT_Netto :=2
);
END_TYPE
Код:
TYPE CAS_Status :
(
HZ_beleberda :=0,
US_unstable := 1,
ST_stable :=2,
OL_overload :=3
);
END_TYPE
Код:
TYPE COM_CAS_STATE :
(
//INITIALIZE := 00,
OPEN_PORT := 0,
READ_FROM_PORT :=30,
WRITE_TO_PORT :=40,
CLOSE_PORT := 50
);
END_TYPE
Код:
TYPE S_fromCAS :
STRUCT
DATUS_CORRECTUS: BOOL;
s_Pri4inaOstanovki :WSTRING;
s_ChoDelaetsaYoMoyo :WSTRING;
status: CAS_Status;
itiBrut: CAS_NetBrut;
DevID: USINT;
Lamp_Zero: BOOL;
Lamp_Tare: BOOL;
Lamp_Net: BOOL;
Lamp_Stable: BOOL;
Lamp_Hold: BOOL;
Lamp4: BOOL; // непонятно, за что отвечает этот бит
Lamp5: BOOL; // непонятно, за что отвечает этот бит
Lamp7: BOOL; // непонятно, за что отвечает этот бит
VES: REAL;
END_STRUCT
END_TYPE
Код:
FUNCTION_BLOCK fb_CAS_CI_1560
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
eState: COM_CAS_STATE:=COM_CAS_STATE.OPEN_PORT;
//xAllreadyOpened: BOOL :=false;
aCom232Params : ARRAY [1..7] OF COM.PARAMETER;
//sComSet: COM.COM_Settings;
fb_ComOpen: COM.Open;
fb_ComRead: COM.Read;
fb_ComSend: COM.Write;
//буфер для одного фрагмента пакета
abyResponseBuffer: ARRAY[0..255] OF BYTE;
//буфер для хранения полного пакета
abyPaket_S_Paketami: ARRAY[0..255] OF BYTE;
//полный пакет в строковом виде
//sPaket_S_Paketami: STRING(1024);
//размер заполненности пакета с пакетами
uiCurrentPSP_size: UINT;
//таймер ожидания полного пакета
fbWaitingOfPosylkaTimer: TON;
//таймер терпения отсутствия пакетов
fbWaitingAnyPosylkaTimer: TON;
//флаг "Программа в процессе получения полного пакета" (взодится после получения первого фрагмента пакета)
xRecieveInProgress: BOOL;
//тестовая строка для посылки на модуль
sTestingSlovoToCas: STRING := '1';
rVes: REAL;
udiDlinaPaketa: UDINT :=22;
tTimeToWaitBeforeError: TIME := T#1000MS;
//fb_SET_CAS: SET_CAS;
//i: INT;
//xOpen: BOOL;
//xClose: BOOL;
END_VAR
VAR_IN_OUT
s_ShoTam: S_fromCAS;
END_VAR
CASE eState OF
COM_CAS_STATE.OPEN_PORT:
s_ShoTam.s_ChoDelaetsaYoMoyo:="пробуем открыть порт";
aCom232Params[1].udiParameterId := COM.CAA_Parameter_Constants.udiPort;
aCom232Params[1].udiValue := 2; // the correct Port should be adapted
aCom232Params[2].udiParameterId := COM.CAA_Parameter_Constants.udiBaudrate;
aCom232Params[2].udiValue := 19200;
aCom232Params[3].udiParameterId := COM.CAA_Parameter_Constants.udiParity;
aCom232Params[3].udiValue := INT_TO_UDINT(COM.PARITY.NONE);
aCom232Params[4].udiParameterId := COM.CAA_Parameter_Constants.udiStopBits;
aCom232Params[4].udiValue := INT_TO_UDINT(COM.STOPBIT.ONESTOPBIT);
aCom232Params[5].udiParameterId := COM.CAA_Parameter_Constants.udiTimeout;
aCom232Params[5].udiValue := 0;
aCom232Params[6].udiParameterId := COM.CAA_Parameter_Constants.udiByteSize;
aCom232Params[6].udiValue := 8;
aCom232Params[7].udiParameterId := COM.CAA_Parameter_Constants.udiBinary;
aCom232Params[7].udiValue := 0;
fb_ComOpen(xExecute:=TRUE, usiListLength:=SIZEOF(aCom232Params)/SIZEOF(COM.PARAMETER),pParameterList:= ADR(aCom232Params));
IF fb_ComOpen.xError THEN
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="ошибка соединения с весовым модулем";
END_IF
IF fb_ComOpen.xDone THEN
eState:= COM_CAS_STATE.READ_FROM_PORT;
ELSE
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="ожидание соединения с весовым модулем";
END_IF
COM_CAS_STATE.READ_FROM_PORT:
s_ShoTam.s_ChoDelaetsaYoMoyo:="пробуем читать из порта";
fb_ComRead(xExecute:=TRUE, hCom:= fb_ComOpen.hCom, pBuffer:=ADR(abyResponseBuffer), szBuffer:= SIZEOF(abyResponseBuffer));
fbWaitingOfPosylkaTimer(IN:=xRecieveInProgress, PT:=tTimeToWaitBeforeError);
fbWaitingAnyPosylkaTimer(IN:=TRUE, pt:=tTimeToWaitBeforeError);
// получен пакет или его обрывок
IF fb_ComRead.xDone THEN
IF fb_ComRead.szSize >0 THEN
xRecieveInProgress:= TRUE;
//пихаем обрывок в пакет с пакетами, если там ещё есть место
{warning 'Разобраться, что делать при переполнении'}
IF (uiCurrentPSP_size + fb_ComRead.szSize) <= SIZEOF(abyPaket_S_Paketami) THEN
Mem.MemMove(ADR(abyResponseBuffer), ADR(abyPaket_S_Paketami) + uiCurrentPSP_size, TO_UINT(fb_ComRead.szSize));
uiCurrentPSP_size := uiCurrentPSP_size + TO_UINT(fb_ComRead.szSize);
END_IF
ParsinH();
END_IF //считана не пустота
// сброс блока для последующего перезапуска - даже если в буфере нет данных
fb_ComRead(xExecute := FALSE);
// при вызове ФБ произошла ошибка
ELSIF fb_ComRead.xError THEN
fb_ComRead(xExecute := FALSE);
xRecieveInProgress:= FALSE;
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="ошибка чтения порта";
//eState:= COM_CAS_STATE.OPEN_PORT;
END_IF //окончание чтения
//MEM.MemMove(ADR(abyPaket_S_Paketami), ADR(sPaket_S_Paketami), TO_UINT(udiCurrentPSP_size) );
IF uiCurrentPSP_size > 200 THEN
prvClear();
END_IF
// если за заданное время не получили полного пакета - то начинаем всё заново
IF fbWaitingOfPosylkaTimer.Q THEN
// чистим буферы и сбрасываем переменные
prvClear();
eState:= COM_CAS_STATE.OPEN_PORT;
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="истекло время ожидания пакета от весового модуля";
END_IF
END_CASE
Код:
METHOD ParsinH
VAR_INPUT
END_VAR
VAR
i: UINT;
y: USINT;
ui_NACHALO: UINT;
sTempVes: STRING(8);
usiFirstCifra: USINT;
rWeightValue: REAL;
END_VAR
VAR CONSTANT
c_PROBEL: USINT:=32;
c_ZPT: USINT:=44;
c_CR: USINT:=13;
c_LF: USINT:=10;
END_VAR
{region “Описание протокола”}
// 'ST,GS,A»,- 0.2 kg$R$N- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$N 0.2 kg$R$NST,GS,A»,- 0.2 kg$R$NST,GS,A»,- '
(*
00 S (стаб) U (нестаб) пусто (хз) O (перегруз)
01 T (стаб) S (нестаб) пусто (хз) L (перегруз)
02 , = 44
03 G (брутто) N (нетто)
04 S (брутто) T (нетто)
05 , = 44
06 ID
07 ламповая статусность:
7 1
6 состояние стабильности
5 0
4 взвешивание нестабильных грузов (чот не меняется бит, когда лампочка вроде мигает)
3 передача данных
2 масса нетто
1 выборка массы тары
0 обнуление
08 , = 44
09 данные о массе
10 данные о массе
11 данные о массе
12 данные о массе
13 данные о массе
14 данные о массе
15 данные о массе
16 данные о массе
17 пусто
18 k (единица измерения)
19 g (единица измерения)
20 CR 0x0D = 13
21 LF 0x0A = 10
*)
{endregion}
//предполагаю, что посылка получена с самого начала. Соответвтенно - её конец находится на 20-21 байте
//n:=INT_TO_BYTE(SIZEOF(abyPaket_S_Paketami))-1;
FOR i:=20 TO uiCurrentPSP_size DO
//ищем конец пакета из CR LF
IF abyPaket_S_Paketami[i] = c_CR AND abyPaket_S_Paketami[i+1] = c_LF THEN
// если найден конец пакета - есть смысл поискать весь пакет в предыдущих байтах
IF abyPaket_S_Paketami[i-3]=c_PROBEL AND abyPaket_S_Paketami[i-12]=c_ZPT AND abyPaket_S_Paketami[i-15]=c_ZPT AND abyPaket_S_Paketami[i-18]=c_ZPT THEN
ui_NACHALO:=i-20;
s_ShoTam.DATUS_CORRECTUS:=TRUE;
//определение передаваемого статуса. СТабильно, нестабильно, перегруз
IF abyPaket_S_Paketami[ui_NACHALO] = 83 AND abyPaket_S_Paketami[ui_NACHALO+1] = 84 THEN
s_ShoTam.status:=CAS_Status.ST_stable;
ELSIF abyPaket_S_Paketami[ui_NACHALO] = 85 AND abyPaket_S_Paketami[ui_NACHALO+1] = 83 THEN
s_ShoTam.status:=CAS_Status.US_unstable;
ELSIF abyPaket_S_Paketami[ui_NACHALO] = 79 AND abyPaket_S_Paketami[ui_NACHALO+1] = 76 THEN
s_ShoTam.status:=CAS_Status.OL_overload;
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="ПЕРЕГРУЗ";
ELSE
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="битый статус";
s_ShoTam.status:=CAS_Status.HZ_beleberda;
END_IF
//определение иты брута
IF abyPaket_S_Paketami[ui_NACHALO+3] = 71 AND abyPaket_S_Paketami[ui_NACHALO+4] = 83 THEN
s_ShoTam.itiBrut:=CAS_NetBrut.GS_Brutto;
ELSIF abyPaket_S_Paketami[ui_NACHALO+3] = 78 AND abyPaket_S_Paketami[ui_NACHALO+4] = 84 THEN
s_ShoTam.itiBrut:=CAS_NetBrut.NT_Netto;
ELSE
s_ShoTam.DATUS_CORRECTUS:=FALSE;
s_ShoTam.s_Pri4inaOstanovki:="битое нетто\брутто";
s_ShoTam.itiBrut:=CAS_NetBrut.HZ_beleberda;
END_IF
s_ShoTam.Lamp_Zero:=NOT(abyPaket_S_Paketami[ui_NACHALO+7].0);
s_ShoTam.Lamp_Tare:=NOT(abyPaket_S_Paketami[ui_NACHALO+7].1);
s_ShoTam.Lamp_Net:=abyPaket_S_Paketami[ui_NACHALO+7].2;
s_ShoTam.Lamp_Hold:=NOT(abyPaket_S_Paketami[ui_NACHALO+7].3);
s_ShoTam.Lamp_Stable:=NOT(abyPaket_S_Paketami[ui_NACHALO+7].6);
s_ShoTam.Lamp4:=abyPaket_S_Paketami[ui_NACHALO+7].4;
s_ShoTam.Lamp5:=abyPaket_S_Paketami[ui_NACHALO+7].5; //типа всегда 0
s_ShoTam.Lamp7:=abyPaket_S_Paketami[ui_NACHALO+7].7; // типа всегда 1
//MEM.MemMove(ADR(abyPaket_S_Paketami[ui_NACHALO+8]), ADR(sTempVes),9 );
sTempVes:='HUIPIZDA';
usiFirstCifra:=0;
FOR y:=0 TO 7 DO
sTempVes[y]:=abyPaket_S_Paketami[y+ui_NACHALO+8+1];
IF usiFirstCifra=0 AND y>0 AND sTempVes[y]<>32 THEN
usiFirstCifra:=y;
END_IF
END_FOR
IF sTempVes[0]=45 THEN
sTempVes[0]:=32;
sTempVes[usiFirstCifra-1]:=45;
END_IF
s_ShoTam.VES := -9999;
s_ShoTam.VES := TO_REAL(sTempVes);
//IF abyPaket_S_Paketami[ui_NACHALO] = 83 AND abyPaket_S_Paketami[ui_NACHALO+1] = 84 THEN s_ShoTam.status:=CAS_Status.ST_stable;
//IF abyPaket_S_Paketami[ui_NACHALO] = 83 AND abyPaket_S_Paketami[ui_NACHALO+1] = 84 THEN s_ShoTam.status:=CAS_Status.ST_stable;
END_IF
END_IF
END_FOR
Код:
// метод очистки буферов и сброса переменных после получения запроса или в случае ошибки
METHOD prvClear
VAR_INPUT
END_VAR
VAR
ci: S_fromCAS;
END_VAR
// чистим буферы
Mem.MemFill(ADR(abyResponseBuffer), SIZEOF(abyResponseBuffer), 0);
Mem.MemFill(ADR(abyPaket_S_Paketami), SIZEOF(abyPaket_S_Paketami), 0);
// сбрасывем флаг "Программа в процессе получения пакета" и позицию в буфере
xRecieveInProgress := FALSE;
uiCurrentPSP_size := 0;
ну и в PLC_PRG:
Код:
fbCAS: fb_CAS_CI_1560;
s_ShoTamUcas: S_fromCAS;
fbCAS(s_ShoTam:=s_ShoTamUcas);