我正在使用 Beckhoff 的 PLC 并在 TwinCat3 中工作。
我一直在尝试实现逻辑,以在测试完成后将实验室测试的试验数据写入 csv。目前,我尝试执行此操作的方法是首先将数据存储在内存中(在变量
aTrialDataBuffer
中,如下所示),然后测试完成后,将该数据以 .csv 格式写入磁盘。
我当前的问题是测试数据完美地存储在内存缓冲区中,没有任何跳过的迭代,但是在事后查看 .csv 时,只有大约每 4-6 行最终被记录。我知道让
FB_FileOpen
、FB_FileClose
和 FB_FilePuts
正常工作有一些微妙之处,并且一直在遵循有关 SO 的一些先前提出的问题的指南:
如何在 TwinCAT 中以 Excel 或 CSV 格式保存 PLC 中的数据?
但是,我一生都无法弄清楚为什么会发生这种跳行。我也将程序置于调试模式,并注意到
FB_FilePuts
似乎被调用而没有跳过任何迭代(通过手动检查字符串参数)。我怀疑我仍然错过了如何正确使用 bExecute
标志的一个小技巧,但未能解决该问题。任何帮助将不胜感激。代码在下面。
FUNCTION_BLOCK CalibrationBlockLogger
VAR_INPUT
sFileName : STRING;
END_VAR
VAR_OUTPUT
bBusy : BOOL := FALSE;
bError : BOOL := FALSE;
END_VAR
VAR CONSTANT
nMaxBufferSize : UDINT := 6000000;
END_VAR
VAR
cDefaultHeader : T_MaxString := 'TimeLow, TimeHigh, EncoderDegrees, CommandVoltage, CommandTorque, GripForce, GripThreshold, SequenceState, CurrentTestIndex, TestIsLive $n';
sDefaultDirectory : T_MaxString := 'C:\Users\njanne\Documents\DDTestLogs\';
bWriteToDisk : BOOL := FALSE;
eFileWriteState : (IDLE, OPEN_FILE, WAIT_FOR_OPEN, WRITE_TO_FILE, WAIT_FOR_WRITE, CLOSE_FILE, WAIT_FOR_CLOSE, ERROR);
fbFileOpen : FB_FileOpen;
fbFilePuts : FB_FilePuts;
fbFileClose : FB_FileClose;
fbFormatString : FB_FormatString2;
nFileHandle : UINT;
bHeaderWriteComplete : BOOL := FALSE;
nWriteIndex : UDINT := 0;
// For formatting data into a string.
sDataFormatStr : STRING := '%d, %d, %f, %f, %f, %f, %f, %s, %d, %d $n';
sTempString : STRING(600);
aTrialDataBuffer : ARRAY[0..nMaxBufferSize] OF ST_CalibTrialStep;
nStepsSoFar : UDINT := 0; // Current number of steps in the database.
END_VAR
--------------------------------------
IF bWriteToDisk THEN // externally set to TRUE once the test is complete, and is set FALSE once the file closes in this state machine.
// If it's time to write to disk, we need to empty the buffer.
CASE eFileWriteState OF
IDLE:
// Get everything ready.
nFileHandle := 0;
bBusy := TRUE;
eFileWriteState := OPEN_FILE;
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 := FALSE);
fbFileOpen(
bExecute := TRUE,
sNetID := '',
sPathName := CONCAT(sDefaultDirectory, sFilename),
nMode := FOPEN_MODEAPPEND OR FOPEN_MODETEXT);
eFileWriteState := WAIT_FOR_OPEN;
WAIT_FOR_OPEN:
fbFileOpen(bExecute := FALSE);
IF fbFileOpen.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFileOpen.bBusy THEN
nFileHandle := fbFileOpen.hFile;
eFileWriteState := WRITE_TO_FILE;
END_IF
WRITE_TO_FILE:
// First check if we need to write the header.
IF NOT bHeaderWriteComplete THEN
fbFilePuts(bExecute := FALSE);
fbFilePuts(
bExecute := TRUE,
sNetId := '',
hFile := nFileHandle,
sLine := cDefaultHeader);
eFileWriteState := WAIT_FOR_WRITE;
bHeaderWriteComplete := TRUE;
ELSE
// If the header is already written, then we're going for data.
// We need to first format the string.
fbFormatString(
pFormatString := ADR(sDataFormatStr),
arg1 := F_UDINT(aTrialDataBuffer[nWriteIndex].nTimestepLow),
arg2 := F_UDINT(aTrialDataBuffer[nWriteIndex].nTimeStepHigh),
arg3 := F_REAL(aTrialDataBuffer[nWriteIndex].rEncoderDegrees),
arg4 := F_REAL(aTrialDataBuffer[nWriteIndex].rCommandVoltage),
arg5 := F_REAL(aTrialDataBuffer[nWriteIndex].rCommandTorque),
arg6 := F_REAL(aTrialDataBuffer[nWriteIndex].rGripForce),
arg7 := F_REAL(aTrialDataBuffer[nWriteIndex].rCurrentGripThreshold),
arg8 := F_STRING(aTrialDataBuffer[nWriteIndex].sSequenceStateString),
arg9 := F_INT(aTrialDataBuffer[nWriteIndex].nCurrentTestIndex),
//arg10 := F_INT(aTrialDataBuffer[nWriteIndex].nTestIsLive),
arg10 := F_UDINT(nWriteIndex), // TODO: DELETE
pDstString := ADR(sTempString),
nDstSize := SIZEOF(sTempString));
fbFilePuts(bExecute := FALSE);
fbFilePuts(
bExecute := TRUE,
sNetId := '',
hFile := nFileHandle,
sLine := sTempString);
// Update the next iteration if we need to write more.
nWriteIndex := nWriteIndex + 1;
eFileWriteState := WAIT_FOR_WRITE;
END_IF
WAIT_FOR_WRITE:
fbFilePuts(bExecute := FALSE);
IF fbFilePuts.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFileClose.bBusy THEN
// Success, data was written.
// Check if we need to write anything more.
IF nWriteIndex = nStepsSoFar THEN
eFileWriteState := CLOSE_FILE;
ELSE
eFileWriteState := WRITE_TO_FILE; // Go back to writer.
END_IF
END_IF
CLOSE_FILE:
fbFileClose(bExecute := FALSE);
fbFileClose(
bExecute := TRUE,
sNetId := '',
hFile := nFileHandle);
eFileWriteState := WAIT_FOR_CLOSE;
WAIT_FOR_CLOSE:
fbFileClose(bExecute := FALSE);
IF fbFileClose.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFileClose.bBusy THEN
nFileHandle := 0;
eFileWriteState := IDLE;
// Also reset everything so that a new test can overwrite.
nStepsSoFar := 0;
nWriteIndex := 0;
bHeaderWriteComplete := FALSE;
bBusy := FALSE;
bWriteToDisk := FALSE;
END_IF
ERROR:
// Error, clear the handle and go back to idle.
nFileHandle := 0;
eFileWriteState := IDLE;
bError := TRUE;
END_CASE
END_IF
FWIW,新数据被添加到
aTrialDataBuffer
中,并且 nStepsSoFar
通过在测试期间调用的一个小方法来递增:
METHOD addStep : BOOL
VAR_INPUT
stNewStep : ST_CalibTrialStep; // The incoming step to be added to memory
END_VAR
-------------------------
// We need to simply copy over all the fields into the new position.
// And then, update the counter.
MEMCPY(
destAddr := ADR(aTrialDataBuffer[nStepsSoFar]),
srcAddr := ADR(stNewStep),
n := SIZEOF(ST_CalibTrialStep));
nStepsSoFar := nStepsSoFar + 1;
我也在父程序中调用连续写入的功能块,以满足状态机的更新。
您需要监视
bBusy
的 fbFilePuts
位以了解写入何时完成以及可以继续。我想也许你在 fbFileClose.bBusy
中错误地使用了 WAIT_FOR_WRITE
?
更正代码:
WAIT_FOR_WRITE:
fbFilePuts(bExecute := FALSE);
IF fbFilePuts.bError THEN
eFileWriteState := ERROR;
ELSIF NOT fbFilePuts.bBusy THEN
// ^^^^^^^^^^^^^^^^
// Success, data was written.
// Check if we need to write anything more.
IF nWriteIndex = nStepsSoFar THEN
eFileWriteState := CLOSE_FILE;
ELSE
eFileWriteState := WRITE_TO_FILE; // Go back to writer.
END_IF
END_IF