TwinCAT3 - Неправильные значения метки времени при чтении из потока данных ADS с Matlab - PullRequest
1 голос
/ 26 марта 2020

Я пытаюсь прочитать поток данных ADS из проекта TwinCAT3.

Написанная мной функция должна читать поток данных всякий раз, когда CycleCount (поступающий из SPS) меняет свое значение - поэтому CycleCount является триггером для Функция обратного вызова и проверяется на изменение каждую миллисекунду.

Поток данных, который должен быть прочитан, состоит из структуры, содержащей два значения: «nCycleCount» (DWORD-4Bytes) и «TStamp» (ULINT-8Bytes). Поэтому весь поток содержит 12 байтов данных.

Один цикл в TwinCAT настроен как 0,5 мс, поэтому переменная CycleCount должна изменяться 2 раза в секунду (если время цикла PL C -задач равно одному цикл-тик). Поскольку моя программа каждую миллисекунду проверяет, изменилась ли переменная CycleCount, функцию обратного вызова следует вызывать каждую миллисекунду и записывать метку времени в буфер («myBuffer»). Но я заметил, что за 2 секунды работы я получаю только 1000 значений (вместо ожидаемых 2000) и не могу найти причину, почему?

Задание PL C в TwinCAT3, кажется, показывает правильное значения, но при чтении их с MatLab значения меток времени являются неправильными и не каждую миллисекунду, как указано ранее:

enter image description here

Это некоторые выходы из Matlab, где CycleCounter записывается в столбец 1, а метка времени записывается в столбец 2:

enter image description here

Я использую следующие коды в 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

Надеюсь, вы можете помочь мне с моими проблемами - заранее спасибо!

Ответы [ 2 ]

1 голос
/ 26 марта 2020

Насколько я вижу, ваша программа работает правильно.

1)

Поскольку уведомления асинхронные, они могут поступать после истечения времени ожидания. В это время вы уже удалил уведомление.

Чтобы проверить, верна ли эта теория, добавьте таймер в вашу программу Twincat.

Объявление:

fbTimer : TON;

Реализация:

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

Убедитесь, что ваша программа matlab уже запущена, прежде чем запускать plc, и увеличьте время паузы в Matlab до 120 с.

Если вы получаете 2000 значений, то вы знаете, что проблема связана с асинхронным характером связи.

2)

Ошибка преобразования происходит из метода ReadInt64, который:

читает 8-байтовое целое число со знаком из текущего потока, которое увеличивает текущую позицию потока на восемь байтов.

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

Вы должны использовать ReadUInt64 вместо.

* 1 039 *

Чтобы посмотреть, смогу ли я воспроизвести ваше поведение, я создал небольшую 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);
            }


        }
    }
}
0 голосов
/ 02 апреля 2020

Я нашел решение, которое, похоже, работает как 12-часовой тест с 43 миллионами наборов данных.

Теперь я добавляю свою структуру (содержащую значения для чтения) к массиву структур с размер 10.000. Как только массив заполнится, моя переменная уведомлений запускает функцию обратного вызова для чтения всего массива (1.000 * 40 байт).

Но, похоже, это работает только с массивами большого размера. При использовании меньших массивов размером 100 или 1000 я заметил, что существует более высокая вероятность ошибочных значений, вызванных неправильным чтением.

Структура:

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
...