Hello.
I am happy to post my code here.
Of course, as always, with the note that this is my first CODESYS project and that there is certainly a better way to do it. But it works.
I have split it into two files, one FB_Picomag for the flow meter. Naturally, this is a Picomag from Endress+Hauser.
FUNCTION_BLOCK FB_Picomag EXTENDS FbIOL_Base
VAR_IN_OUT
xResetTotalizer: BOOL;
END_VAR
VAR_OUTPUT
rConductivity: REAL;
rFlow: REAL;
rTotalizer: REAL;
rTemperature: REAL;
xIOLinkOK: BOOL;
END_VAR
VAR
RTrigReset: R_TRIG;
bytesConv: U_BytesToReal;
bResetWritten: BYTE;
END_VAR
SUPER^();
IF THIS^.Prop_xInDataValid THEN
// Conductivity
bytesConv.abBytes[3] := aDeviceInData[0];
bytesConv.abBytes[2] := aDeviceInData[1];
bytesConv.abBytes[1] := aDeviceInData[2];
bytesConv.abBytes[0] := aDeviceInData[3];
rConductivity := bytesConv.rValue;
// Flow
bytesConv.abBytes[3] := aDeviceInData[8];
bytesConv.abBytes[2] := aDeviceInData[9];
bytesConv.abBytes[1] := aDeviceInData[10];
bytesConv.abBytes[0] := aDeviceInData[11];
// Scaling by 60, as we apparently receive the value as liters/s.
rFlow := bytesConv.rValue * 60.0;
// Totalizer
bytesConv.abBytes[3] := aDeviceInData[4];
bytesConv.abBytes[2] := aDeviceInData[5];
bytesConv.abBytes[1] := aDeviceInData[6];
bytesConv.abBytes[0] := aDeviceInData[7];
rTotalizer := bytesConv.rValue;
// Temperature
rTemperature := (INT_TO_REAL(aDeviceInData[12]) * 256.0 + INT_TO_REAL(aDeviceInData[13])) / 10.0;
xIOLinkOK := TRUE;
RTrigReset(CLK := xResetTotalizer);
IF RTrigReset.Q THEN
THIS^.IOL_Call_WriteWord(wIOL_Index := 362, bIOL_Subindex := 0, wValue := 1, bLen => bResetWritten);
xResetTotalizer := FALSE;
END_IF
ELSE
xIOLinkOK := FALSE;
END_IF
Running as a separate task is this main program PRG_IOLink:
PROGRAM PRG_IOLink
VAR
xExecuteMain: BOOL := TRUE;
xDoneConfig: BOOL;
xBusyConfig: BOOL;
xErrorConfig: BOOL;
oStatusConfig: FbResult;
aPort: ARRAY [1..4] OF typIOLinkPortConfigBasic;
aIOLPort: ARRAY[1..4] OF typIOL_Device;
aIOLDevice: ARRAY [1..4] OF typIOLinkDeviceInfo;
xEnableDiagnosis: BOOL := TRUE;
xResetDiag: BOOL;
aDiagBuffer: ARRAY[0..IOL_DIAG_BUFFER] OF typDiag;
aDiagTimeBuffer: ARRAY[0..IOL_DIAG_BUFFER] OF DATE_AND_TIME;
IOL_Config_1657: WagoAppIOLink.FbIOL_MasterConfiguration_1657;
currentDT: DATE_AND_TIME;
RTCResult: RTS_IEC_RESULT;
xDiagResult: BOOL;
n : INT;
emptyPipeDetected: BOOL;
// Picomag
PicomagAnPort1: FbIOL_PortData;
PicoMagObj: FB_Picomag;
bytesConv: U_BytesToReal;
pOutBuffer: ARRAY[0..2] OF BYTE;
pInBuffer: ARRAY[0..14] OF BYTE;
xTxTriggerPicomag: BOOL := FALSE;
xCommResetPicomag: BOOL:= FALSE;
xValidPicomag: BOOL;
xBusyPicomag : BOOL;
xErrorPicomag: BOOL;
udiRxBytesPicomag: UDINT;
bRefreshCounterPicomag: BYTE;
oStatusPicomag: FbResult;
xInit: BOOL := FALSE;
END_VAR
IF NOT xInit THEN
xInit := TRUE;
// At the start of the controller, we assume a full pipe.
// After all, "EMPTY PIPE" is an error that is reported to us or not.
GVL_IO.Filterpumpe_LeitungVoll := TRUE;
END_IF
aPort[1].bRXSize := 15;
aPort[1].eIOL_PortMode := eIOL_PortMode.IOL_Sensor;
aPort[1].xFragmented := FALSE;
aPort[2].eIOL_PortMode := eIOL_PortMode.Deactivated;
aPort[3].eIOL_PortMode := eIOL_PortMode.Deactivated;
aPort[4].eIOL_PortMode := eIOL_PortMode.Deactivated;
currentDT := UDINT_TO_DT(SysTimeRtcGet(pResult := RTCResult));
IOL_Config_1657(
xEnable := xExecuteMain,
I_Port := IoConfig_Globals._750_1657_24,
eModeStartUp := eMasterConfigModeStartUp.AutoConfig,
aPort := aPort,
xDone => xDoneConfig,
xBusy => xBusyConfig,
xError => xErrorConfig,
oStatus => oStatusConfig,
aIOL_Port => aIOLPort,
aIOL_DeviceInfo => aIOLDevice,
dtTime := currentDT,
);
xDiagResult := IOL_Config_1657.GetDiagnosisBuffer(
xEnable := xEnableDiagnosis,
xReset := xResetDiag,
aDiagBuffer := aDiagBuffer,
aDiagTimeBuffer := aDiagTimeBuffer);
emptyPipeDetected := FALSE;
FOR n := 0 TO IOL_DIAG_BUFFER DO
IF NOT emptyPipeDetected AND aDiagBuffer[n].EventCode = 6158 THEN
emptyPipeDetected := TRUE;
IF aDiagBuffer[n].DiagQualifier.DiagMode = eDiagQualMode.MBX_DiagAppears THEN
GVL_IO.Filterpumpe_LeitungVoll := FALSE;
ELSE
GVL_IO.Filterpumpe_LeitungVoll := TRUE;
END_IF
xResetDiag := TRUE;
END_IF
END_FOR
PicoMagObj(
typIOL_Sensor := aIOLPort[1],
typIOLinkDeviceInfo := aIOLDevice[1],
oStatus => oStatusPicomag,
xError => xErrorPicomag,
xResetTotalizer := GVL_IO.Filterpumpe_ZaehlerReset_Ausgang,
);
GVL_IO.Filterpumpe_Conductivity := PicoMagObj.rConductivity;
GVL_IO.Filterpumpe_Flow := PicoMagObj.rFlow;
GVL_IO.Filterpumpe_Totalizer := PicoMagObj.rTotalizer;
GVL_IO.Filterpumpe_Temperature := PicoMagObj.rTemperature;
GVL_IO.Filterpumpe_IOLinkOK := PicoMagObj.xIOLinkOK;
A special feature is that the “Empty Pipe” message does not come from the device itself but from the IO-Link diagnosis buffer. For example, the LED on the terminal also lights up red as soon as the Picomag reports an Empty Pipe.