You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:45:26