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:
These are some outputs from Matlab where the CycleCounter is written to column 1 and timestamp is written to column 2:
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!
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