PLCnext Technology offers different possibilities to capture data.
Some examples:
- PLCnext Engineer library: The function blocks for Data Logging are used to collect data of any data types and store into csv or binary files. In combination with the PLCnextBaseLibrary it is possible to copy the data to an FTP server.
- Datalogger: A service component of the PLCnext Technology firmware. This component transfers real-time data from the GDS to a database for recording and storage purposes. When starting and stopping the PLCnext Technology firmware, the DataLogger is started and stopped automatically. XML configuration files are used for configuration of the DataLogger. The DataLogger can record data from any IN or OUT ports and variables.
- App available in the PLCnext Store: DataLogger powered by FourZero
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?
- Log data in retain memory and add a timestamp
- Convert this data to a byte array
- Create a new file, write our data and close the file again
If you are interested in the complete PLCnext Engineer project, just let me know. (slemmens@phoenixcontact.be)
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:
- a function block which is able to write data on the flash memory
- a method for the above function block to insert data
- a program which calls the above method
// *** 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;
Leave a Reply
You must be logged in to post a comment.