Datalogging in IEC 61131-3: The basic principle

Print

PLCnext Technology offers different possibilities to capture data.

Some examples:

If you want to build something specific for your application, this blog is what you need.

We are going to build up a PLCnext Engineer project which stores data on the flash memory.

What are the different steps for this task?

If you are interested in the complete PLCnext Engineer project, just let me know. (This email address is being protected from spambots. You need JavaScript enabled to view it.)

First, we are going to create a custom datatype for our memory structure.

strDataSampleTimestamp: // Timestamp for each dataset
        STRUCT   
            iDay:       INT;
            iMonth:     INT;
            iYear:      INT;
            iHour:      INT;
            iMinute:    INT;
            iSecond:    INT;
        END_STRUCT;

strDataSample: // Dataset including timestamp
        STRUCT
            strTimestamp:   strDataSampleTimestamp;
            iValue_1:       INT;
            xValue_2:       BOOL;
            rValue_3:       REAL;
            sValue_4:       STRING;
        END_STRUCT;

arr_1_20_strDataSample:     ARRAY[1..20] OF strDataSample; // In total we have a buffer for 20 datasets

strDataMemory: // The complete structure including pointers for read and write actions
        STRUCT
            iWritePointer:      INT;
            iReadPointer:       INT;
            iDataCount:         INT;
            arrData:            arr_1_20_strDataSample;
        END_STRUCT;

 

Second step is to build up some PLC code:

 

// *** Function block code: ***

CASE iLogDataSetToFile OF

0:  IF strDataBuffer.iDataCount > 0 THEN /* Data available */
        iLogDataSetToFile:= 1;
    END_IF;

1:  /* Convert all datapoints to a string */

    IF strDataBuffer.iReadPointer = 0 THEN /* This memory location does not exist */
        strDataBuffer.iReadPointer:= 1;
    END_IF;

    iTempReadPointer:= strDataBuffer.iReadPointer;

    sTempInput_1:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].iValue_1,'{0:d}');
    IF strDataBuffer.arrData[iTempReadPointer].xValue_2 THEN
        sTempInput_2:= 'TRUE';
    ELSE
        sTempInput_2:= 'FALSE';
    END_IF;
    sTempInput_3:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].rValue_3,'{0:f2}');
    sTempInput_4:= strDataBuffer.arrData[iTempReadPointer].sValue_4;

    iLogDataSetToFile:= 2;

2:  /* Build up the timestamp */

    sTimeStampValue:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].strTimestamp.iDay,'{0:d2}');
    sTempText:= sTimeStampValue;
    sTempText:= CONCAT(sTempText,'/');
    sTimeStampValue:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].strTimestamp.iMonth,'{0:d2}');
    sTempText:= CONCAT(sTempText,sTimeStampValue);
    sTempText:= CONCAT(sTempText,'/');
    sTimeStampValue:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].strTimestamp.iYear,'{0:d}');
    sTempText:= CONCAT(sTempText,sTimeStampValue);
    sTempText:= CONCAT(sTempText,' ');  
    sTimeStampValue:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].strTimestamp.iHour,'{0:d2}');
    sTempText:= CONCAT(sTempText,sTimeStampValue);
    sTempText:= CONCAT(sTempText,':');
    sTimeStampValue:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].strTimestamp.iMinute,'{0:d2}');
    sTempText:= CONCAT(sTempText,sTimeStampValue);
    sTempText:= CONCAT(sTempText,':');
    sTimeStampValue:= TO_STRING(strDataBuffer.arrData[iTempReadPointer].strTimestamp.iSecond,'{0:d2}');
    sTempText:= CONCAT(sTempText,sTimeStampValue);
    sTempText:= CONCAT(sTempText,' ');     

    iLogDataSetToFile:= 3;

3:  /* Build up the complete text */

    sTempText:= CONCAT(sTempText,sTempInput_1);
    sTempText:= CONCAT(sTempText,';');
    sTempText:= CONCAT(sTempText,sTempInput_2);
    sTempText:= CONCAT(sTempText,';');   
    sTempText:= CONCAT(sTempText,sTempInput_3);
    sTempText:= CONCAT(sTempText,';');      
    sTempText:= CONCAT(sTempText,sTempInput_4);

    iLengthTempText:= LEN(sTempText);

    iLogDataSetToFile:= 4;

4:  /* Convert to a byte array */

    xConvert:= TRUE;
    diLengthTempText:= TO_DINT(iLengthTempText);
    iLogDataSetToFile:= 5;

5:  IF xDoneConvert THEN
        xConvert:= FALSE;
        iTempPointer:= iLengthTempText + 1;
        arrBuffer_FW[iTempPointer]:= BYTE#16#0D; /* Carriage return */
        iTempPointer:= iTempPointer + 1;
        arrBuffer_FW[iTempPointer]:= BYTE#16#0A; /* Line feed */
        udiLength_FW:= TO_UDINT(iTempPointer);
        iLogDataSetToFile:= 6;
    END_IF;

6:  /* Open a file */

    sName_FO:= 'ExampleMakersBlog.txt';
    xExecute_FO:= TRUE;
    iLogDataSetToFile:= 7;

7:  IF xDone_FO THEN
        uiHandleMemory:= uiHandle_FO;
        xExecute_FO:= FALSE;
        iLogDataSetToFile:= 8;
    END_IF;

8:  /* Move the current file pointer to a new position, in our case the end of the file */

    xExecute_FS:= TRUE;
    uiHandle_FS:= uiHandleMemory;
    diPosition_FS:= DINT#0;
    uiMode_FS:= UINT#2;
    iLogDataSetToFile:= 9;

9:  IF xDone_FS THEN
        xExecute_FS:= FALSE;
        iLogDataSetToFile:= 100;
    END_IF;

10: /* Write data in the file */

    xExecute_FW:= TRUE;
    uiHandle_FW:= uiHandleMemory;
    iLogDataSetToFile:= 11;

11: IF xDone_FW THEN
        xExecute_FW:= FALSE;
        iLogDataSetToFile:= 12;
    END_IF;    

12: /* Close the file */

    xExecute_FC:= TRUE;
    uiHandle_FC:= uiHandleMemory;
    iLogDataSetToFile:= 13;

13: IF xDone_FC THEN
        xExecute_FC:= FALSE;

        strDataBuffer.iReadPointer:= strDataBuffer.iReadPointer + 1;
        IF strDataBuffer.iReadPointer > 20 THEN
            strDataBuffer.iReadPointer:= 1;
        END_IF;

        strDataBuffer.iDataCount:= strDataBuffer.iDataCount - 1;

        iLogDataSetToFile:= 0;
    END_IF;      

END_CASE;

/* Converts the string variable to a byte array */

ConvertString_To_Buf(Req := xConvert,
Buf_Format := FALSE,
Buf_Offs := DINT#0,
Buf_Cnt := diLengthTempText,
Done => xDoneConvert,
Error => xErrorConvert,
Status => iStatusConvert,
SRC := sTempText,
BUFFER := arrBuffer_FW);    

/* Opens an existing file in the parameterization memory with a specific name or creates a new file. */

FileOpenLogging(Execute := xExecute_FO,
Name := sName_FO,
Done => xDone_FO,
Handle => uiHandle_FO,
Error => xError_FO,
ErrorID => uiErrorID_FO);

/* Moves the current file pointer to a new position. */

FileSeekLogging(Execute := xExecute_FS,
Handle := uiHandle_FS,
Position := diPosition_FS,
Mode := uiMode_FS,
Done => xDone_FS,
Error => xError_FS,
ErrorID => uiErrorID_FS);

/* Writes data to a file that was opened previously. */

FileWriteLogging(Execute := xExecute_FW,
Handle := uiHandle_FW,
Done => xDone_FW,
LengthWritten => udiLengthWritten_FW,
Buffer := arrBuffer_FW,
Length := udiLength_FW,
Error => xError_FW,
ErrorID => uiErrorID_FW);

/* Closes a file in the parameterization memory that has been opened using the FILE_OPEN function block. */

FileCloseLogging(Execute := xExecute_FC,
Handle := uiHandle_FC,
Done => xDone_FC,
Error => xError_FC,
ErrorID => uiErrorID_FC);

 

// *** Method code: ***

IF strDataBuffer.iWritePointer = 0 THEN /* This memory location does not exist */
    strDataBuffer.iWritePointer:= 1;
END_IF;

IF strDataBuffer.iDataCount < 20 THEN /* Free space in buffer */

    iTempWritePointer:= strDataBuffer.iWritePointer; /* Select current write position */

    /* Data */

    strDataBuffer.arrData[iTempWritePointer].iValue_1:= iInput_1;
    strDataBuffer.arrData[iTempWritePointer].xValue_2:= xInput_2;
    strDataBuffer.arrData[iTempWritePointer].rValue_3:= rInput_3;   
    strDataBuffer.arrData[iTempWritePointer].sValue_4:= sInput_4;

    /* Timestamp */

    strDataBuffer.arrData[iTempWritePointer].strTimestamp.iDay:= RTC.DAY;
    strDataBuffer.arrData[iTempWritePointer].strTimestamp.iMonth:= RTC.MONTH;
    strDataBuffer.arrData[iTempWritePointer].strTimestamp.iYear:= TO_INT(RTC.YEAR);
    strDataBuffer.arrData[iTempWritePointer].strTimestamp.iHour:= RTC.HOURS;
    strDataBuffer.arrData[iTempWritePointer].strTimestamp.iMinute:= RTC.MINUTES;
    strDataBuffer.arrData[iTempWritePointer].strTimestamp.iSecond:= RTC.SECONDS;

    strDataBuffer.iWritePointer:= strDataBuffer.iWritePointer + 1; /* Select next position */
    IF strDataBuffer.iWritePointer > 20 THEN
        strDataBuffer.iWritePointer:= 1;
    END_IF;

    strDataBuffer.iDataCount:= strDataBuffer.iDataCount + 1; /* 1 extra dataset available in the buffer */

    iNumberOfSamples:= iNumberOfSamples + 1; /* Increment number of samples */

END_IF;

 

// *** Program code: ***

/* FB call */
RetainDataBuffer(iNumberOfSamples => iMyLoggingSamples);

/* Log 1 dataset in retain memory */

IF xLogSample THEN
    xLogSample:= FALSE;

    RetainDataBuffer.InsertDataSample(55,FALSE,2.3,'This is text information - Log 1 sample');

END_IF;

/* Log 4 datasets in retain memory */

IF xLogFourSample THEN
    xLogFourSample:= FALSE;

    RetainDataBuffer.InsertDataSample(3,FALSE,2.3,'This is text information - Log 1/4 sample');
    RetainDataBuffer.InsertDataSample(17,TRUE,8.9,'This is text information - Log 2/4 sample');
    RetainDataBuffer.InsertDataSample(888,FALSE,5.233,'This is text information - Log 3/4 sample');
    RetainDataBuffer.InsertDataSample(482,TRUE,777.85,'This is text information - Log 4/4 sample');

END_IF;