matlabplctwincattwincat-ads

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


I am trying to read an ADS datastream from a TwinCAT3 Project.

The function I wrote should read the datastream whenever the CycleCount (coming from the SPS) changes its value - so CycleCount is the trigger for the callback function and is checked for a change every millisecond.

The datastream to be read consists of a structure containing the two values "nCycleCount" (DWORD-4Bytes) and "TStamp" (ULINT-8Bytes). Therefore the whole stream is containing 12 bytes of data.

One cycle in TwinCAT is configured as 0.5ms, so the variable CycleCount should change 2 times per second (if the PLC-tasks cycle time is one cycle-tick). As my program is checking every millisecond if the variable CycleCount changed, the callback function should be called every millisecond and write the timestamp to a Buffer ("myBuffer"). But I noticed that for a runtime of 2 seconds I only receive 1000 values (instead of 2000 expected) and I can't find the reason why?

The PLC task in TwinCAT3 seems to show the correct values, but when reading them with MatLab the timestamp values are incorrect and not every millisecond as stated before:

enter image description here

These are some outputs from Matlab where the CycleCounter is written to column 1 and timestamp is written to column 2:

enter image description here

I use the following Codes in TwinCAT to define the structure and Main-Program:

Structure:

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

MAIN_CC (for 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 Code to read stream on Notification:

    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

Hope you can help me with my problems - thanks in advance!


Solution

  • I found a solution which seems to work as a 12hour test with 43million datasets was successful.

    The way I do it now is appending my structure (containing the values to read) to an array of structs with a size of 10.000. As soon as the array is full, my notification variable triggers the callback function to read the whole array (1.000 * 40 bytes).

    But this only seems to work with arrays of a big size. When using smaller arrays with a size of 100 or 1.000 I noticed that there is a higher chance of faulty values caused by incorrect reading.

    Structure:

    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
    

    MAIN:

    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