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

Posted

技术标签:

【中文标题】TwinCAT3 - 使用 Matlab 从 ADS 数据流读取时时间戳的错误值【英文标题】:TwinCAT3 - Wrong values for timestamp when reading from ADS datastream with Matlab 【发布时间】: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
            myBufferbufflen+1,1 = nCycleCount;

            %Read & Append Timestamp to Buffer
            tstamp = binRead.ReadInt64;             %Read tstamp from dataStream and shift reading position by 8bytes (int64)        
            myBufferbufflen+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】:

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

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)        
               myBufferbufflen+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)        
                   myBufferbufflen+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 太好了,谢谢!能够修复它 - 请参阅我编辑的主循环。但是我仍然不明白为什么我的并行函数会影响我的回调函数的成功?...

以上是关于TwinCAT3 - 使用 Matlab 从 ADS 数据流读取时时间戳的错误值的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TwinCAT3 中获取 unix 时间戳?

TwinCAT3提示找不到TcPch.h错误解决

卸载TwinCat3之后vs未能正确加载包错误解决

为什么TwinCAT3老是已停止工作

通过 ADS.Net 将数组从 C# 发送到 TwinCat 3

MATLAB教程案例24基于matlab的有参图像质量评价仿真与分析,包括MSE,PSNR,NK,AD,SC,MD,NAE