TwinCat写入CSV文件时变量值不更新的问题排查求助
问题分析与修正方案
核心错误点
1. 数组填充逻辑完全错误
bFill触发时的1000万次循环会在单个PLC扫描周期内把整个arrLog数组填充为同一个值——因为rTimestamp、rCurrent、rActPos在循环内没有更新,且1000万次循环会直接导致PLC扫描周期超时(PLC扫描周期通常仅几毫秒到几十毫秒),实际循环根本执行不完,数组元素要么全是初始值,要么只有部分被赋值为同一数据。
修正:
改为逐扫描周期填充单条数据,避免超时,同时用计数器作为数组索引:
IF bFill THEN // 确保索引不超过数组上限 IF nCounter <= 10000000 THEN GVL.arrLog[nCounter].rTimestamp := rTimestamp; GVL.arrLog[nCounter].rCurrent := (nCounter+1)/100; GVL.arrLog[nCounter].rActPos := (nCounter+1)/200; nCounter := nCounter + 1; END_IF END_IF
2. 读取数组时索引未循环更新
状态30的循环中始终使用GVL.arrLog[i],但i是填充数组时的最终值(10000000)或初始值0,导致每次都读取同一个数组元素,写入重复内容。
修正:
用循环变量作为数组索引,遍历不同元素:
// 写入前1000条数据,可根据需求调整 FOR i := 0 TO MIN(nCounter-1, 999) BY 1 DO // 先将单条数据格式化为临时缓冲区 fbFormatString2( pFormatString:= ADR(sFormat), arg1:= F_LREAL(GVL.arrLog[i].rTimestamp), arg2:= F_LREAL(GVL.arrLog[i].rCurrent), arg3:= F_LREAL(GVL.arrLog[i].rActPos), pDstString:= ADR(sBufferTemp), nDstSize:= SIZEOF(sBufferTemp), bError=> , nErrId=> ); // 将临时内容追加到主写入缓冲区 CONCAT2(pSrcString1 := ADR(sWriteBuffer), pSrcString2 := ADR(sBufferTemp), pDstString:= ADR(sWriteBuffer), nDstSize := SIZEOF(sWriteBuffer)); END_FOR
3. 状态30的循环逻辑混乱
- 复用
nTimeMilli作为当前毫秒数和循环变量,导致变量被覆盖、逻辑混乱; CONCAT2参数错误,将空的sBufferTemp与sWriteBuffer拼接,无法累加内容;- 条件
WORD_TO_STRING(fbGetTime.TIMESTR.wMinute) <> INT_TO_STRING(40)完全不符合需求,属于无效判断。
修正:
- 单独使用循环变量,不复用时间变量;
- 先将单条数据格式化为临时缓冲区,再追加到主缓冲区;
- 替换无效条件,改为判断数据填充量是否达到写入标准。
4. 文件打开模式错误
使用FOPEN_MODEAPPEND OR FOPEN_MODEPLUS不符合注释中“文件存在则销毁内容”的需求,FOPEN_MODEAPPEND是追加模式,会保留原有内容。
修正:
使用覆盖模式FOPEN_MODEWRITE:
nMode:= FOPEN_MODEWRITE, // 创建新文件,若文件存在则覆盖内容
5. fbGetTime参数未正确连接
调用fbGetTime时未传递必要参数,导致时间信息无法更新,文件名的时间部分始终为初始值。
修正:
补全参数连接:
fbGetTime( NETID:= '', START:= fbGetTime.START, TMOUT:= T#5S, BUSY=> , ERR=> , ERRID=> , TIMESTR=> fbGetTime.TIMESTR);
6. 文件写入长度参数错误
fbFileWrite的cbWriteLen使用SIZEOF(sWriteBuffer)会写入大量空字符,应该用字符串实际长度。
修正:
cbWriteLen:= LEN(sWriteBuffer), // 使用实际字符串长度
关键部分修正后的代码片段
// Input values //------------------------------------------------------------------------------------------------------------------------ rTimestamp:= ULINT_TO_LREAL(F_GetSystemTime()); IF bFill THEN // 逐周期填充数组,避免扫描周期超时 IF nCounter <= 10000000 THEN GVL.arrLog[nCounter].rTimestamp := rTimestamp; GVL.arrLog[nCounter].rCurrent := (nCounter+1)/100; GVL.arrLog[nCounter].rActPos := (nCounter+1)/200; nCounter := nCounter + 1; END_IF END_IF // Function Block for Current Date and Time //------------------------------------------------------------------------------------------------------------------------ IF fbGetTime.BUSY THEN fbGetTime.START := FALSE; ELSE fbGetTime.START := TRUE; END_IF fbGetTime( NETID:= '', START:= fbGetTime.START, TMOUT:= T#5S, BUSY=> , ERR=> , ERRID=> , TIMESTR=> fbGetTime.TIMESTR); //Case Statements that will handle sequence of writing //------------------------------------------------------------------------------------------------------------------------ CASE nState OF 0: //Wait for write trigger IF bWrite THEN nState := 10; bWrite := FALSE; END_IF 10: //Create file path and file using date sPathName := CONCAT('D:\\Data\\', WORD_TO_STRING(fbGetTime.TIMESTR.wYear)); sPathName := CONCAT(sPathName,'_'); sPathName := CONCAT(sPathName,WORD_TO_STRING(fbGetTime.TIMESTR.wMonth)); sPathName := CONCAT(sPathName,'_'); sPathName := CONCAT(sPathName,WORD_TO_STRING(fbGetTime.TIMESTR.wDay)); sPathName := CONCAT(sPathName,'_'); sPathName := CONCAT(sPathName,WORD_TO_STRING(fbGetTime.TIMESTR.wHour)); sPathName := CONCAT(sPathName,'_'); sPathName := CONCAT(sPathName,WORD_TO_STRING(fbGetTime.TIMESTR.wMinute)); sPathName := CONCAT(sPathName,'_Datalog.csv'); nState:= 20; fbFileOpen.bExecute := TRUE; 20: //Open and wait for file to open fbFileOpen.bExecute := TRUE; IF NOT fbFileOpen.bBusy AND NOT fbFileOpen.bError THEN fbFileOpen.bExecute := FALSE; nState := 30; // 初始化写入缓冲区,写入CSV表头 sWriteBuffer := 'Timestamp, fCurrentScaled, fActPos $n'; sFormat := '%F, %F, %F $n'; END_IF 30: // Write contents in file // 写入已填充的数组数据,最多写1000条 FOR i := 0 TO MIN(nCounter-1, 999) BY 1 DO // 先将单条数据格式化为临时缓冲区 fbFormatString2( pFormatString:= ADR(sFormat), arg1:= F_LREAL(GVL.arrLog[i].rTimestamp), arg2:= F_LREAL(GVL.arrLog[i].rCurrent), arg3:= F_LREAL(GVL.arrLog[i].rActPos), pDstString:= ADR(sBufferTemp), nDstSize:= SIZEOF(sBufferTemp), bError=> , nErrId=> ); // 追加到主写入缓冲区 CONCAT2(pSrcString1 := ADR(sWriteBuffer), pSrcString2 := ADR(sBufferTemp), pDstString:= ADR(sWriteBuffer), nDstSize := SIZEOF(sWriteBuffer)); END_FOR // 数据拼接完成,进入写入状态 nState := 40; fbFileWrite.bExecute := TRUE; 40: // Write data in file and Wait for writing in the file fbFileWrite.bExecute := TRUE; IF NOT fbFileWrite.bBusy AND NOT fbFileWrite.bError THEN fbFileWrite.bExecute := FALSE; nState := 50; fbFileClose.bExecute := TRUE; END_IF 50: // close file and wait for it to close fbFileClose.bExecute := TRUE; IF NOT fbFileClose.bBusy AND NOT fbFileClose.bError THEN fbFileClose.bExecute := FALSE; nState:= 0; // 重置计数器,准备下一次填充 nCounter := 0; END_IF END_CASE // FunctionBlocks for OPEN, WRITE and CLOSE //------------------------------------------------------------------------------------------------------------------------ fbFileOpen( sNetId:= '', sPathName:= sPathName, nMode:= FOPEN_MODEWRITE, // 覆盖模式,符合注释需求 ePath:= PATH_GENERIC, bExecute:= fbFileOpen.bExecute, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> , hFile=> hFile); fbFileClose( sNetId:= '', hFile:= hFile, bExecute:= fbFileClose.bExecute, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> ); fbFileWrite( sNetId:= '', hFile:= hFile, pWriteBuff:= ADR(sWriteBuffer), cbWriteLen:= LEN(sWriteBuffer), // 使用实际字符串长度 bExecute:= fbFileWrite.bExecute, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> , cbWrite=> );
额外注意事项
- 严格控制单扫描周期内的运算量,避免PLC扫描周期超时进入故障状态;
- 确保
GVL.arrLog的实际长度与代码中使用的索引范围一致,防止数组越界; - 可添加错误处理逻辑,捕获
fbFileOpen、fbFileWrite等函数块的错误状态,便于调试。
内容的提问来源于stack exchange,提问作者Ray9090
相关产品推荐
相关产品推荐

