我必须从 Beckhoff PLC 管理的模拟器中记录 JobNo、处理时间、JobID、过程编号等数据,并将这些数据以 CSV 或 excel 文件的形式写入。 例如,如果 JobNo 为 1,则在处理完所有数据后,应将其记录在相应的列下,当遇到下一个作业时,将数据记录在下一行。以下应该是 excel 文件的样子
职位号 | 处理时间 | 职位编号 | 工艺编号 |
---|---|---|---|
1 | 20 | 100 | 200 |
2 | 40 | 101 | 320 |
到目前为止,我已经关注了 Beckhoff 的在线资源,甚至联系了他们的支持团队,但是我从他们那里得到的代码导致了某种错误。我也在一些论坛上写下了我的问题,但我在那里得到的答案太复杂和高级了。如果你能提供一个文件写入程序的例子会很有帮助 以下是我为此目的使用的所有 DUT 和 GVL 以及主要代码: 名为 ST_data
的 DUTTYPE ST_data :
STRUCT
JOB_ID : STRING(255);
Processing_Time : STRING(255);
Setup_Time : STRING(255);
Due_Date : STRING(255);
END_STRUCT
END_TYPE
GVL 名为 GVL
{attribute 'qualified_only'}
VAR_GLOBAL
MACHINE : ARRAY[1..50] OF ST_data;
sen5 : BOOL;
sen2 : BOOL;
state : BOOL;
count :INT;
hit: BOOL;
tdelta : ULINT;
tstart : ULINT;
tend : ULINT;
setuptime : LREAL;
END_VAR
MAIN 程序代码 局部变量--------------------------------------------
PROGRAM MAIN
VAR
FB_FileOpen: FB_FileOpen;
FB_FileWrite: FB_FileWrite;
FB_FileClose: FB_FileClose;
hFile: UINT;
sPathName: T_MaxString;
sWriteBuffer : STRING(5000);
sBufferTemp : STRING(1000); // Temporary string that will hold the string that needs to be added to the full string
bBuffTemp : BOOL ; //check if the strings have been concatinated
sFormat : STRING(255);
state: INT;
bWrite: BOOL;
NT_GetTime: NT_GetTime;
bFill: BOOL;
i: INT;
FB_FormatString2: FB_FormatString2;
trigger: BOOL;
END_VAR
代码-------------------------------------------- ------------------
//counter code
//==========================================================================================
IF GVL.sen5 = FALSE AND GVL.hit = FALSE THEN
GVL.count := GVL.count +1;
GVL.hit := TRUE;
ELSIF GVL.sen5 = TRUE AND GVL.hit = TRUE THEN
GVL.hit := FALSE;
END_IF
IF gvl.sen5= TRUE THEN
GVL.tstart := F_GetSystemTime();
END_IF
IF GVL.sen2 = TRUE THEN
GVL.tend := F_GetsystemTime();
//trigger := TRUE; // this will trigger the file writing commands
END_IF
GVL.tdelta := GVL.tend - GVL.tstart;
GVL.setuptime := GVL.tdelta*EXPT(10,-7);
//Entering some value in array
//==========================================================================================
IF bFill THEN
FOR i := 1 TO 50 BY 1 DO
GVL.MACHINE[i].Due_Date :='10823'; //WORD_TO_STRING(NT_GetTime.TIMESTR.wDayOfWeek);
GVL.MACHINE[i].JOB_ID := INT_TO_STRING(GVL.count) ;//INT_TO_STRING(i);
GVL.MACHINE[i].Processing_Time := '125';//WORD_TO_STRING(NT_GetTime.TIMESTR.wMinute);
GVL.MACHINE[i].Setup_Time := LREAL_TO_STRING(GVL.setuptime);//WORD_TO_STRING(NT_GetTime.TIMESTR.wSecond);
END_FOR
bFill := FALSE;
END_IF
//Functioin block to get local time
//===========================================
IF NT_GetTime.start AND NOT NT_GetTime.BUSY THEN
NT_GetTime.START :=FALSE;
ELSE
NT_GetTime.START := TRUE;
END_IF
NT_GetTime(
NETID:='' ,
START:= ,
TMOUT:= ,
BUSY=> ,
ERR=> ,
ERRID=> ,
TIMESTR=> );
//CASE STATEMENT to handle writing
//===========================================
CASE state OF
0:
IF bWrite THEN // switch to true or false to control the execution
State :=5;
bWrite := FALSE;
END_IF
5:// creating the file
sPathName := CONCAT('C:\Users\Manjot Sanghera\Desktop\CSVRECORD',WORD_TO_STRING(NT_GetTime.TIMESTR.wDay));
sPathName := CONCAT(sPathName,'.');
sPathName := CONCAT(sPathName,WORD_TO_STRING(NT_GetTime.TIMESTR.wHour));
//sPathName := CONCAT(sPathName,'.');
//sPathName := CONCAT(sPathName,WORD_TO_STRING(NT_GetTime.TIMESTR.wMinute));
sPathName := CONCAT(sPathName,'_TestFile.csv');
State := 10;
FB_FileOpen.bExecute := TRUE;
10:
FB_FileOpen.bExecute := TRUE;
IF NOT FB_FileOpen.bBusy AND NOT FB_FileOpen.bError THEN
FB_FileOpen.bExecute := FALSE;
State := 15;
END_IF
15 :
sWriteBuffer :='Due_Date, Job_ID, Processin_Time,Setup_Time $n';
sFormat :='%s, %s, %s, %s $n';
FOR i := 1 TO 50 BY 1 DO // loop is required so that you can loop through each line/row/arrayelement
FB_FormatString2(
pFormatString:= ADR(sFormat),
arg1:= F_STRING(GVL.MACHINE[i].Due_Date),
arg2:= F_STRING(GVL.MACHINE[i].JOB_ID),
arg3:= F_STRING(GVL.MACHINE[i].Processing_Time),
arg4:= F_STRING(GVL.MACHINE[i].Setup_Time),
pDstString:= ADR(sBufferTemp) ,
nDstSize:= SIZEOF(sBufferTemp),
bError=> ,
nErrId=> );
bBuffTemp := CONCAT2( pSrcString1 := ADR(sWriteBuffer),//main string
pSrcString2 := ADR(sBufferTemp), //String to be added
pDstString := ADR(sWriteBuffer),//destinatioin of the string
nDstSize := SIZEOF(sWriteBuffer));//size of the final string
END_FOR
//sWriteBuffer := 'Job_ID__|__Processing_Time__|__SetupTime';
State := 20;
FB_FileWrite.bExecute := TRUE;
20:
FB_FileWrite.bExecute := TRUE;
IF NOT FB_FileWrite.bBusy AND NOT FB_FileWrite.bError THEN
FB_FileWrite.bExecute := FALSE;
State := 30;
FB_FileClose.bExecute := TRUE;
END_IF
30:
IF NOT FB_FileClose.bBusy AND NOT FB_FileClose.bError THEN
FB_FileClose.bExecute := FALSE;
State:= 0;
END_IF
END_CASE
//OPEN, WRITE, CLOSE FILE
//===========================================
FB_FileOpen(
sNetId:= '',
sPathName:= sPathName ,
nMode:= FOPEN_MODEWRITE OR FOPEN_MODEPLUS,
ePath:= PATH_GENERIC,
bExecute:= ,
tTimeout:= ,
bBusy=> ,
bError=> ,
nErrId=> ,
hFile=> hFile);
FB_FileWrite(
sNetId:= '',
hFile:= hFile,
pWriteBuff:= ADR(sWriteBuffer) ,
cbWriteLen:= SIZEOF(sWriteBuffer),
bExecute:= ,
tTimeout:= ,
bBusy=> ,
bError=> ,
nErrId=> ,
cbWrite=> );
FB_FileClose(
sNetId:= '',
hFile:= hFile ,
bExecute:= ,
tTimeout:= ,
bBusy=> ,
bError=> ,
nErrId=> );
程序的预期功能 counter 对对象进行计数,并将 JobID、Process No 和其他值的值填充到表的第一行中,并且仅在第二个作业的这些值可用后才起作用
根据评论,这是一个程序,其中包含用于写入文件的 FB 和实现演示以将新数据发送到文件的 MAIN。数据以 CSV 格式写入。您可以修改要写入文件的内容,什么文件,数据格式是什么,......我希望这应该足以解决问题。
由于你是新手,这里是文件夹结构的图像,因为我使用了方法。以下是关于 Object Oriented Programming with TwinCAT 3
的更多信息文件写入功能块实现: 变量声明:
FUNCTION_BLOCK FB_FileWriter
VAR_INPUT
sFilePath : T_MaxString;
sFileName : T_MaxString;
sNetId : STRING; // NetId of the target, leave empty for local
END_VAR
VAR
fbFileOpen : FB_FileOpen;
fbFilePuts : FB_FilePuts;
fbFileClose : FB_FileClose;
nFileHandle : UINT;
arrBuffer : ARRAY[0..100] OF T_MaxString;
eFileWriteState : (IDLE, OPEN_FILE, WRITE_TO_FILE, CLOSE_FILE, ERROR);
END_VAR
身体:
CASE eFileWriteState OF
IDLE:
// Make sure all the file writing FBs are ready for use (trigger on execute)
Init();
// We have pending data to be written
IF arrBuffer[0] <> '' THEN
eFileWriteState := OPEN_FILE;
END_IF
OPEN_FILE:
// Opens a file for writing at the end of the file (append).
// If the file does not exist, a new file is created.
fbFileOpen(
bExecute := TRUE,
sNetId := sNetId,
sPathName := CONCAT(sFilePath, sFileName),
nMode := FOPEN_MODEAPPEND);
IF fbFileOpen.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFileOpen.bBusy THEN
nFileHandle := fbFileOpen.hFile;
fbFileOpen(bExecute := FALSE);
eFileWriteState := WRITE_TO_FILE;
END_IF
WRITE_TO_FILE:
fbFilePuts(
bExecute := TRUE,
sNetId := sNetId,
hFile := nFileHandle,
sLine := arrBuffer[0]);
IF fbFilePuts.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFileClose.bBusy THEN
// Sucess, data was written
// Rotate the buffer and close the file
RotateBuffer();
eFileWriteState := CLOSE_FILE;
END_IF
CLOSE_FILE:
fbFileClose(
bExecute := TRUE,
sNetId := sNetId,
hFile := nFileHandle);
IF fbFileClose.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFileClose.bBusy THEN
nFileHandle := 0;
eFileWriteState := IDLE;
END_IF
ERROR:
// Error, clear the handle and go back to idle
nFileHandle := 0;
eFileWriteState := IDLE;
END_CASE
方法:
初始化所有FB的方法:
METHOD PRIVATE Init : BOOL
fbFileClose(bExecute := FALSE);
fbFileOpen(bExecute := FALSE);
fbFilePuts(bExecute := FALSE);
向缓冲区插入新数据的方法
METHOD PRIVATE InsertToBuffer
VAR_INPUT
value : STRING;
END_VAR
VAR
nBufferIndex : INT;
END_VAR
FOR nBufferIndex := 0 TO 100 BY 1 DO
IF arrBuffer[nBufferIndex] = '' THEN
// We found the free spot in the buffer, insert the value to this place
arrBuffer[nBufferIndex] := value;
EXIT;
END_IF
END_FOR
当前数据片写入后轮换缓冲区中数据的方法。
METHOD PRIVATE RotateBuffer
VAR
nIndex : int := 0;
END_VAR
FOR nIndex := 0 TO 99 BY 1 DO
// We can exit when we reached the empty slot in the buffer
IF arrBuffer[nIndex] = '' THEN
EXIT;
END_IF
// FIFO, removing first element in the array copying the next, etc...
arrBuffer[nIndex] := arrBuffer[nIndex+1];
END_FOR
被调用以写入目标文件的公共方法
// Write to the destination file
METHOD WriteToFile : BOOL
VAR_INPUT
message : T_MaxString;
END_VAR
InsertToBuffer(value := message);
结构定义:
TYPE ST_MyData :
STRUCT
Id : DINT;
Value : DINT;
Timestamp : T_MaxString;
END_STRUCT
END_TYPE
主要变量减速:
PROGRAM MAIN
VAR
fbWriteFile : FB_FileWriter;
arrTestData : ARRAY[0..10] OF ST_MyData;
nIteration : INT;
bStartNewJob : BOOL;
fbFormatString : FB_FormatString;
fbGetCurrentTaskIndex : GETCURTASKINDEX;
END_VAR
VAR CONSTANT
cHeader : STRING := 'ID;VALUE;TIMESTAMP';
END_VAR
身体:
fbWriteFile(
sFileName := 'Test.txt',
sFilePath := 'C:\',
sNetId := '');
// Handle this differently, but just creating the header on first PLC cycle for demo purpose
fbGetCurrentTaskIndex();
IF _TaskInfo[fbGetCurrentTaskIndex.index].FirstCycle THEN
fbWriteFile.WriteToFile(message := cHeader);
END_IF
IF bStartNewJob THEN
// Making up some fake data to store it
arrTestData[nIteration].Id := nIteration; // just some made up id
arrTestData[nIteration].Value := 5*nIteration; // just some made up value
arrTestData[nIteration].Timestamp := '2023.27.04_10:00'; // just some fake timestamp
// Create the full line by using the FB_FormatString, way easier than CONCAT
fbFormatString(
sFormat := '%d;%d;%s$n',
arg1 := F_DINT(arrTestData[nIteration].Id),
arg2 := F_DINT(arrTestData[nIteration].Value),
arg3 := F_STRING(arrTestData[nIteration].Timestamp));
// Send the created line to the file writer
fbWriteFile.WriteToFile(message := fbFormatString.sOut);
// Increasing the counter for new, different data
IF nIteration <10 THEN
nIteration := nIteration + 1;
ELSE
nIteration := 0;
END_IF
// And finally, clear the trigger
bStartNewJob := FALSE;
END_IF
最终结果。