【问题标题】:TwinCAT3 - Wrong values for timestamp when reading from ADS datastream with MatlabTwinCAT3 - 使用 Matlab 从 ADS 数据流读取时时间戳的错误值
【发布时间】:2020-03-26 15:42:36
【问题描述】:

我正在尝试从 TwinCAT3 项目中读取 ADS 数据流。

每当 CycleCount(来自 SPS)改变其值时,我编写的函数应该读取数据流 - 因此 CycleCount 是回调函数的触发器,并且每毫秒检查一次更改。

要读取的数据流由一个结构组成,该结构包含两个值“nCycleCount”(DWORD-4Bytes)和“TStamp”(ULINT-8Bytes)。因此整个流包含 12 个字节的数据。

TwinCAT 中的一个周期被配置为 0.5 毫秒,因此变量 CycleCount 应该每秒改变 2 次(如果 PLC 任务的周期时间是一个周期记号)。由于我的程序每毫秒检查变量 CycleCount 是否发生变化,因此应每毫秒调用回调函数并将时间戳写入缓冲区(“myBuffer”)。 但我注意到,在 2 秒的运行时间中,我只收到 1000 个值(而不是预期的 2000 个),我找不到原因?

TwinCAT3 中的 PLC 任务似乎显示了正确的值,但是当使用 MatLab 读取它们时,时间戳值不正确,而不是如前所述的每毫秒:

这些是 Matlab 的一些输出,其中 CycleCounter 写入第 1 列,时间戳写入第 2 列:

我在 TwinCAT 中使用以下代码来定义结构和主程序:

结构:

   TYPE ST_CC :
   STRUCT
    nCycleCount       : DWORD;              //4Bytes
    TStamp            : ULINT;              //8Bytes
                                            //Stream with 12Bytes total     
   END_STRUCT
   END_TYPE

MAIN_CC(用于 PlcTask):

   PROGRAM MAIN_CC
   VAR
     CC_struct : ST_CC;
   END_VAR;

   CC_struct.nCycleCount := _TaskInfo[1].CycleCount;    
   CC_struct.TStamp :=  IO_Mapping.ulint_i_TimeStamp; 

在通知上读取流的 Matlab 代码:

    function ReadTwinCAT()

    %% Import Ads.dll
    AdsAssembly = NET.addAssembly('D:\TwinCat3\AdsApi\.NET\v4.0.30319\TwinCAT.Ads.dll');
    import TwinCAT.Ads.*;

    %% Create TcAdsClient instance
    tcClient = TcAdsClient;

    %% Connect to ADS port 851 on the local machine
    tcClient.Connect(851);

    %% ADS Device Notifications variables

    % ADS stream
    dataStream = AdsStream(12); %12Bytes necessary 

    % reader
    binRead = AdsBinaryReader(dataStream);

    % Variable to trigger notification
    CCount = 'MAIN_CC.CC_struct.nCycleCount';

    %% Create unique variable handles for structure
    try
        st_handle = tcClient.CreateVariableHandle('MAIN_CC.CC_struct');
    catch err
        tcClient.Dispose();
        msgbox(err.message,'Fehler beim Erstellen des Variablenhandles','error');
        error(err.message);
    end

    %% Create buffer for values
         myBuffer = {};
         MAXBUFFLEN = 1000;

    %% Register ADS Device
    try   
        % Register callback function
        tcClient.addlistener('AdsNotification',@OnNotification);

        % Register notifications 
    %   %AddDeviceNotification( variableName As String,
    %                           dataStream As AdsStream,
    %                           offset As Integer,
    %                           length As Integer (in Byte),
    %                           transMode As AdsTransMode,
    %                           cycleTime As Integer,
    %                           maxDelay As Integer,
    %                           userData As Object)

        % Notification handle
        hConnect = tcClient.AddDeviceNotification(CCount,dataStream,0,4,AdsTransMode.OnChange,1,0,CCount);

        % Listen to ADS notifications for x seconds
        pause(2);
    catch err
        msgbox(err.message,'Error reading array via ADS','error');
        disp(['Error registering ADS notifications: ' err.message]);
    end


    %% Delete ADS notifications
    for idx=1:length(hConnect)
        tcClient.DeleteDeviceNotification(hConnect(idx));
    end

    %% Dispose ADS client
    tcClient.Dispose();


    %% MatlabAdsSample_Notification: OnNotification
    function OnNotification(sender, e)

        e.DataStream.Position = e.Offset; %Startposition = 0                

        %% load variables from workspace
        hConnect = evalin('caller','hConnect');
        binRead = evalin('caller','binRead');

        %% assign to ADS variable and convert to string
        if( e.NotificationHandle == hConnect )

            %% Read timestamp and encodervalues & append to Buffer

            tcClient.Read(st_handle, dataStream);   %Read structure from stream       

            %nCycleCount
            nCycleCount = binRead.ReadInt32;
            [bufflen, ~] = size(myBuffer);          %Get current buffer length
            myBuffer{bufflen+1,1} = nCycleCount;

            %Read & Append Timestamp to Buffer
            tstamp = binRead.ReadInt64;             %Read tstamp from dataStream and shift reading position by 8bytes (int64)        
            myBuffer{bufflen+1,2} = tstamp;   

            if bufflen < MAXBUFFLEN-1
                return;
            else
                assignin('base','myBuffer', myBuffer);
                disp("buffer assigned in workspace")
                myBuffer = {};                                      %empty Buffer
            end                     

        else
            %do nothing
        end

    end

希望您能帮助我解决我的问题 - 在此先感谢!

【问题讨论】:

    标签: matlab plc twincat twincat-ads


    【解决方案1】:

    据我所知,您的程序运行正常。

    1)

    由于通知是异步的,它们可能会在您的等待时间结束后到达。那时虽然您已经处理了通知。

    要测试这个理论是否正确,请在您的 Twincat 程序中添加一个计时器。

    声明:

    fbTimer : TON;
    

    实施:

    fbTimer(IN:=TRUE,PT:=T#2s);
    IF NOT fbTimer.Q
    THEN
     cc_struct.nCycleCount := _TaskInfo[1].CycleCount;
    END_IF
    

    在启动 plc 之前,请确保您的 matlab 程序已经启动 并将您在 Matlab 中的暂停时间提高到 120 秒。

    如果您得到 2000 个值,那么您就知道问题源于通信的异步性质。

    2)

    转换错误源自 ReadInt64 方法:

    从当前流中读取一个 8 字节的有符号整数并前进 流的当前位置 8 个字节。

    https://docs.microsoft.com/en-us/dotnet/api/system.io.binaryreader.readint64?redirectedfrom=MSDN&view=netframework-4.8#System_IO_BinaryReader_ReadInt64

    您应该改用 ReadUInt64


    为了查看是否可以重现您的相同行为,我创建了一个小型 c# 测试程序。 测试程序运行正常,我能够收到正确数量的通知。

    这里是 ST 代码:

    声明:

    PROGRAM MAIN
    VAR
        fbTimer: TON;
        nCycleCount : DWORD;
    END_VAR
    

    实施:

    fbTimer(IN:=TRUE,PT:=T#2S);
    IF NOT fbTimer.Q
    THEN
     nCycleCount := _TaskInfo[1].CycleCount;
    END_IF
    

    这里是 C# 代码:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using TwinCAT.Ads;
    
    namespace AdsNotificationTest
    {
        class Program
        {
            static TcAdsClient tcClient;
            static int hConnect;
            static AdsStream dataStream;
            static BinaryReader binReader;
            static uint uVal, huValHandle;
            static int counter = 0;
    
            static void Main(string[] args)
            {
                tcClient = new TcAdsClient();
                dataStream = new AdsStream(31);
    
                binReader = new BinaryReader(dataStream, System.Text.Encoding.ASCII);
                tcClient.Connect(851);
                try
                {
                    hConnect = tcClient.AddDeviceNotification("MAIN.nCycleCount", dataStream, 0, 4, AdsTransMode.OnChange, 1, 0, huValHandle);
                    tcClient.AdsNotification += new AdsNotificationEventHandler(OnNotification);
                }
                catch (Exception err)
                {
                    Console.WriteLine("Exception.");
                }
    
                Console.ReadKey();
    
                tcClient.DeleteDeviceNotification(hConnect);
                tcClient.Dispose();
    
            }
    
            private static void OnNotification(object sender, AdsNotificationEventArgs e)
            {
    
                if (e.NotificationHandle == hConnect)
                {
                    counter += 1;
                    uVal = binReader.ReadUInt32();
                    Console.WriteLine(counter + ": " + uVal);
                }
    
    
            }
        }
    }
    

    【讨论】:

    • 感谢您的回答! 1) 但是使用“pause(2)”我会听 ADS 通知 2 秒,对吧?因此,如果我理解正确,我应该得到 4000 个 plc 循环滴答的 2000 个值。 2)我还尝试在 Matlab 中将其转换为无符号长整数,但这些似乎只是随机数。当使用相同的 ReadInt64 方法读取时间戳和其他值(不是 CycleCount)时,我得到了正确的时间戳值 - 所以我猜这不是转换问题。
    • 您正在读取一个 8 字节有符号整数:docs.microsoft.com/en-us/dotnet/api/…
    • 请张贴您的任务配置图片。
    • 现在我尝试了 ReadUInt64,但我仍然没有得到 TwinCAT 中显示的确切内容。这里可以看到 Matlab-Output 和 Task 的配置:link
    • 如果实现 fbTimer,我的 nCycleCount 不会更新(静态) - 因此我在 Matlab 中没有收到任何值,因为没有触发回调函数。
    【解决方案2】:

    我找到了一个解决方案,该解决方案似乎成功了,对 4300 万个数据集进行了 12 小时的测试。

    我现在的做法是将我的结构(包含要读取的值)附加到一个大小为 10.000 的结构数组中。一旦数组已满,我的通知变量就会触发回调函数读取整个数组(1.000 * 40 字节)。

    但这似乎只适用于大尺寸的数组。当使用大小为 100 或 1.000 的较小数组时,我注意到错误读取导致错误值的可能性更高。

    结构:

    TYPE ST_ENC :
      STRUCT    
        TStamp            : ULINT;              //8Bytes
        EncRAx1           : DINT;               //4Bytes
        EncRAx2           : DINT;               //4Bytes    
        EncRAx3           : DINT;               //4Bytes
        EncRAx4           : DINT;               //4Bytes
        EncRAx5           : DINT;               //4Bytes
        EncRAx6           : DINT;               //4Bytes
        EncEAx1           : DINT;               //4Bytes
        EncEAx2           : DINT;               //4Bytes
      END_STRUCT
    END_TYPE
    

    主要:

    PROGRAM MAIN_Array
    VAR
       encVal : ST_ENC; //Structure of encoder values and timestamp
       arr2write : ARRAY [0..9999] OF ST_ENC; //array of structure to write to
       arr2read  : ARRAY [0..9999] OF ST_ENC; //array of structure to read from
       ARR_SIZE : INT := 9999;
       counter : INT := 0; //Counter for arraysize
    END_VAR;
    

    // --Timestamp & Encoderwerte
    encVal.TStamp   :=  IO_Mapping.ulint_i_TimeStamp; 
    encVal.EncRAx1  :=  IO_Mapping.dint_i_EncoderValue_RAx1; 
    encVal.EncRAx2  :=  IO_Mapping.dint_i_EncoderValue_RAx2;
    encVal.EncRAx3  :=  IO_Mapping.dint_i_EncoderValue_RAx3;
    encVal.EncRAx4  :=  IO_Mapping.dint_i_EncoderValue_RAx4;
    encVal.EncRAx5  :=  IO_Mapping.dint_i_EncoderValue_RAx5;
    encVal.EncRAx6  :=  IO_Mapping.dint_i_EncoderValue_RAx6;
    encVal.EncEAx1  :=  IO_Mapping.dint_i_EncoderValue_EAx1;
    encVal.EncEAx2  :=  IO_Mapping.dint_i_EncoderValue_EAx2;
    
    //Append to array 
    IF counter < ARR_SIZE
    THEN
        arr2write[counter] := encVal;
        counter := counter + 1;
    ELSE
        arr2write[ARR_SIZE] := encVal; //Write last Bufferentry - otherwise 1 cycle of struct missing   
        arr2read := arr2write;  
        counter := 0;
    END_IF
    

    MATLAB

    function ReadTwinCAT() 
    
       %% Import Ads.dll
       AdsAssembly = NET.addAssembly('D:\TwinCat3\AdsApi\.NET\v4.0.30319\TwinCAT.Ads.dll');
       import TwinCAT.Ads.*;
    
       %% Initialize POOL
       pool = gcp();
       disp("Worker pool for parallel computing initalized");
    
       %% Create TcAdsClient instance
       tcClient = TcAdsClient;
    
       %% Connect to ADS port 851 on the local machine
       tcClient.Connect(851);
    
       %% ADS Device Notifications variables
       % ADS stream
       ARR_SIZE = 10000; %Groesse des auszulesenden Arrays of Struct
       STREAM_SIZE = 40; %in Byte 
    
       dataStream = AdsStream(ARR_SIZE * STREAM_SIZE); %40Bytes per entry
    
       % Binary reader
       binRead = AdsBinaryReader(dataStream);
    
       % Variable to trigger notification
       arr2read = 'MAIN_Array.arr2read[0].TStamp'; %Notification handle = first TStamp entry
    
       %% Create unique variable handles for encoder-array
       try
           arr_handle = tcClient.CreateVariableHandle('MAIN_Array.arr2read');
       catch err
           tcClient.Dispose();
           msgbox(err.message,'Fehler beim Erstellen des Variablenhandles','error');
           error(err.message);
       end
    
       %% Create buffer for values
       myBuffer = {}; %Creates empty buffer
       buffcount = 0; %Nur fuer Workspace-Ausgabe
    
       %% Register ADS Device
       try   
           % Register callback function
           tcClient.addlistener('AdsNotification',@OnNotification);
    
           % Notification handle
           hConnect = tcClient.AddDeviceNotification(arr2read,dataStream,0,8,AdsTransMode.OnChange,1,0,arr2read);
    
           % Listen to ADS notifications for x seconds
           pause(15);
    
       catch err
           msgbox(err.message,'Error reading array via ADS','error');
           disp(['Error registering ADS notifications: ' err.message]);
       end
    
       %% Delete ADS notifications
       tcClient.DeleteDeviceNotification(hConnect);
    
       %% Dispose ADS client
       tcClient.Dispose();
    
       %% MatlabAdsSample_Notification: OnNotification
       function OnNotification(sender, e)
    
           e.DataStream.Position = e.Offset; 
    
           %% load variables from workspace
           hConnect = evalin('caller','hConnect');
           binRead = evalin('caller','binRead');
    
           %% assign to ADS variable and convert to string
           if( e.NotificationHandle == hConnect )
    
              %% Read timestamp and encodervalues & append to Buffer
    
               tcClient.Read(arr_handle, dataStream);   %Read structure from stream
    
               for idx=1:ARR_SIZE               
    
                   %Read & Append Timestamp to Buffer
                   [bufflen, ~] = size(myBuffer);           %Get current buffer length
                   tstamp = binRead.ReadUInt64;             %Read tstamp from dataStream and shift reading position by 8bytes (int64)        
                   myBuffer{bufflen+1,1} = tstamp; 
    
                   %Read & Append Encodervalues to Buffer
                   for n=1:8
                       encval = binRead.ReadInt32;             %Read tstamp from dataStream and shift reading position by 8bytes (int64)        
                       myBuffer{bufflen+1,n+1} = encval; 
                   end
    
               end
    
               Assign arraybuffer
               buffname = 'myBuffer';
               buffcount = buffcount+1;
               buffcount_str = num2str(buffcount);
               assignin('base',strcat(buffname, buffcount_str), myBuffer);
               myBuffer = {}; %empty Buffer for next array
               disp("buffer assigned")         
    
           else
               %do nothing
           end
       end
    end
    

    【讨论】:

    • 我认为,如果您的数组太小,您会在 plc 数组中的值被 ads-function 读取之前覆盖它们。每 10000 个周期一次,您不会存储您的 encValues --> arr2write [计数器] := encVal;
    • 是的,覆盖理论可能是正确的。 “不存储 encValues”是什么意思?在此屏幕截图中,它看起来在 TwinCat 中正确完成:imgur.com/a/ib5XQSJ 但我现在遇到了另一个问题:我尝试通过计算机器人的正向运动学对接收到的编码器值进行后处理。这是在我的并行函数中完成的,因此它不应该影响 ADS-Stream 的读取,但它确实会影响,因为我有时会再次读取错误的值......
    • 看看你的 if 语句,当你的计数器通过 ARR_SIZE 时,你没有存储 encVal
    • 太好了,谢谢!能够修复它 - 请参阅我编辑的主循环。但是我仍然不明白为什么我的并行函数会影响我的回调函数的成功?...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-11
    • 1970-01-01
    • 2015-12-28
    • 2020-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多