Makers Blog Archive

Datalogging in IEC 61131-3: Create your own file manager

Sven Lemmens 19 December 2019 min. read
453 views 0 comments

Following on my previous blog “Datalogging in IEC 61131-3: The basic principle”, I want to show you something extra.
To read and write data, you only need to implement some basic commands.

In this blog, we are going to create the following methods:

  • Create a file
  • Create a file and directly write a header line
  • Delete a file
  • Read a line
  • Write a line

Please note that some extra error handling is needed for real life applications.

If you are interested in the complete PLCnext Engineer project, just let me know. (slemmens@phoenixcontact.be)

First, we are going to create one custom datatype, for reading and writing data. (arr_Byte_1_500 : Array[1..500] Of Byte)

Then, we need the implementation of our methods. In fact they only transfer some data towards the function block. We don’t use a return type because the complete process takes more than one program cycle.

// *** Method code: ***

/* Create a file */
sRequestedFileName:= sFileName;

IF iFileManager = 0 THEN
    iFileManager:= 20;
END_IF;

/* Create a file and directly write a header line */
sRequestedFileName:= sFileName;
sRequestedText:= sHeader;

IF iFileManager = 0 THEN
    iFileManager:= 10;
END_IF;

/* Delete a file */
sRequestedFileName:= sFileName;

IF iFileManager = 0 THEN
    iFileManager:= 30;
END_IF;

/* Read a line */
sRequestedFileName:= sFileName;

IF xFirstLine THEN
    diMemoryCursor:= DINT#0;
END_IF;    

IF iFileManager = 0 THEN
    iFileManager:= 40;
END_IF;

/* Write a line */
sRequestedFileName:= sFileName;
sRequestedText:= sText;

IF iFileManager = 0 THEN
    iFileManager:= 10;
END_IF;

The following function block implements the real program behavior.

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

xIdle:= iFileManager = 0;

CASE iFileManager OF

10: // Determine the length of the string and convert it to a byte array

    diLengthTempText:= TO_DINT(LEN(sRequestedText));
    sTempText:= sRequestedText;

    xConvert:= TRUE;
    iFileManager:= 11;

11: IF xDoneConvert THEN
        xConvert:= FALSE;

        iTempPointer:= TO_INT(diLengthTempText) + 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);

        iFileManager:= 150; // Open file
        iReturnStep:= 12;
    END_IF;

12: // 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;
    iFileManager:= 13;

13: IF xDone_FS THEN
        xExecute_FS:= FALSE;
        iFileManager:= 14;
    END_IF;

14: // Write data in the file

    xExecute_FW:= TRUE;
    uiHandle_FW:= uiHandleMemory;
    iFileManager:= 15;

15: IF xDone_FW THEN
        xExecute_FW:= FALSE;
        iFileManager:= 100; // Close file
    END_IF;    

20: iFileManager:= 150; // Open file
    iReturnStep:= 100; // Close file

30: // Delete a file
    sName_FRe:= sRequestedFileName;
    xExecute_FRe:= TRUE;
    iFileManager:= 31;

31: IF xDone_Fre THEN
        xExecute_FRe:= FALSE;
        iFileManager:= 0;
    END_IF;

40: iFileManager:= 150; // Open file
    iReturnStep:= 42;

42: // Move the current file pointer to a new position

    xExecute_FS:= TRUE;
    uiHandle_FS:= uiHandleMemory;
    diPosition_FS:= diMemoryCursor;
    uiMode_FS:= UINT#0;
    iFileManager:= 43;

43: IF xDone_FS THEN
        xExecute_FS:= FALSE;
        iFileManager:= 44;
    END_IF;

44: // Read data

    xExecute_FR:= TRUE;
    uiHandle_FR:= uiHandleMemory;
    udiMaxLength_FR:= UDINT#100; // We expect a line with a maximum length of 80 char
    iFileManager:= 45;   

45: IF xDone_FR OR (xError_FR & uiErrorID_FR = UINT#10) THEN
        xExecute_FR:= FALSE;

        iAmountOfData:= TO_INT(udiLengthRead_FR);

        IF iAmountOfData > 0 THEN

            FOR iLoop:= 1 TO iAmountOfData
                DO
                    IF arrBuffer_FR[iLoop] = BYTE#16#0D THEN /* Carriage return */
                        iLengtLine:= iLoop - 1;
                        diLengtLine:= TO_DINT(iLengtLine);
                        diMemoryCursor:= diMemoryCursor + diLengtLine + DINT#2;
                        EXIT;
                    END_IF;
            END_FOR;

            iFileManager:= 46;   
        ELSE
            diMemoryCursor:= DINT#0;
            sData:= '';
            iFileManager:= 100; // Close file  
        END_IF;

    END_IF;

46: xConvertRead:= TRUE;
    iFileManager:= 47;

47: IF xDoneConvertRead THEN
        xConvertRead:= FALSE;
        iFileManager:= 100; // Close file
    END_IF;

// Close file

100:    xExecute_FC:= TRUE;
        uiHandle_FC:= uiHandleMemory;
        iFileManager:= 101;

101:    IF xDone_FC THEN
            xExecute_FC:= FALSE;
            iFileManager:= 0;
        END_IF;

// Open file

150:    sName_FO:= sRequestedFileName;
        xExecute_FO:= TRUE;
        iFileManager:= 151;

151:    IF xDone_FO THEN
            uiHandleMemory:= uiHandle_FO;
            xExecute_FO:= FALSE;
            iFileManager:= iReturnStep;
        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);

// Converts a byte array to a string variable

ConvertBuf_To_String(REQ := xConvertRead,
BUF_FORMAT := FALSE,
BUF_OFFS := DINT#0,
BUF_CNT := diLengtLine,
DONE => xDoneConvertRead,
ERROR => xErrorConvertRead,
STATUS => iStatusConvertRead,
BUFFER := arrBuffer_FR,
DST := sData);

// 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);

// Reads data from a file that has been opened using the FILE_OPEN function block.

FileReadLogging(Execute := xExecute_FR,
Handle := uiHandle_FR,
Done => xDone_FR,
LengthRead => udiLengthRead_FR,
Buffer := arrBuffer_FR,
MaxLength := udiMaxLength_FR,
Error => xError_FR,
ErrorID => uiErrorID_FR);

// 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);

// Deletes a file with a specific name.

FileRemoveLogging(Execute := xExecute_FRe,
Name := sName_FRe,
Done => xDone_FRe,
Error => xError_FRe,
ErrorID => uiErrorID_FRe);

The following program gives you an overview how you can access our ‘file manager’. Please note that we don’t have a buffer implemented. This means that you cannot give multiple commands at once.

// *** Program code: ***

FileManager(xIdle => xFileManagerReady,
sData => sResult);

IF xCreateFileWithHeader & xFileManagerReady THEN
    xCreateFileWithHeader:= FALSE;

    FileManager.CreateFileWithHeader('ThisIsMyFirstFile.txt','Timestamp;Value_1;Value_2');
END_IF;

IF xCreateFile & xFileManagerReady THEN
    xCreateFile:= FALSE;

    FileManager.CreateFile('ThisIsMySecondFile.txt');
END_IF;

IF xDeleteFile & xFileManagerReady THEN
    xDeleteFile:= FALSE;

    FileManager.DeleteFile('ThisIsMySecondFile.txt');
END_IF;

IF xWriteLine & xFileManagerReady THEN
    xWriteLine:= FALSE;

    FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 09:34:12;78.3;1285');
END_IF;

CASE iWriteMultipleLine OF

0:  IF xWriteMultiLine THEN
        xWriteMultiLine:= FALSE;
        iWriteMultipleLine:= 1;
    END_IF;

1:  IF xFileManagerReady THEN
        FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 10:34:12;78.3;1285');    
        iWriteMultipleLine:= 2;
    END_IF;

2:  IF xFileManagerReady THEN
        FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 11:45:12;7.3;185');    
        iWriteMultipleLine:= 3;
    END_IF;    

3:  IF xFileManagerReady THEN
        FileManager.WriteLine('ThisIsMyFirstFile.txt','19/12/2019 12:04:08;35.8;17777');    
        iWriteMultipleLine:= 0;
    END_IF;

END_CASE;

IF xReadFirstLine & xFileManagerReady THEN // Output is written to variable 'sResult'
    xReadFirstLine:= FALSE;

    // Read the first line from this file, the cursor position is set to zero
    FileManager.ReadLine('ThisIsMyFirstFile.txt',TRUE);
END_IF;

IF xReadNextLine & xFileManagerReady THEN // Output is written to variable 'sResult'
    xReadNextLine:= FALSE;

    // Read the next line from this file, the cursor position is shifted by the program
    // When the end of the file is reached, you will receive an empty string
    // The next attempt, you will receive the first line again
    FileManager.ReadLine('ThisIsMyFirstFile.txt',FALSE);
END_IF;

Note:

The Makers Blog shows applications and user stories of community members that are not tested or reviewed by Phoenix Contact. Use them at your own risk.

Discussion

Please login/register to comment

Login/Register

Leave a Reply

Newsletter
Never miss a new article
Sign up for the newsletter
Never miss news about PLCnext Technology
Get interesting content via newsletter four times a year
Receive exclusive information before all other users