TwinCAT3 - 使用 Matlab 从 ADS 数据流读取时时间戳的错误值

TwinCAT3 - Wrong values for timestamp when reading from ADS datastream with Matlab

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

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

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

TwinCAT 中的一个周期配置为 0.5ms,因此变量 CycleCount 应该每秒变化 2 次(如果 PLC-tasks 周期时间是一个 cycle-tick)。由于我的程序每毫秒检查一次变量 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

希望你能帮我解决我的问题 - 提前致谢!

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

1)

由于通知是异步的,它们可能会在您的等待时间 over.At 之后到达,尽管您已经处理了通知。

要测试这个理论是否正确,请在您的 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 方法,该方法:

Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.

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);
            }


        }
    }
}

我找到了一个似乎有效的解决方案,因为对 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