When opening any communication instrument in matlab, you can set the object with a BytesAvailableFcn. i.e. u.BytesAvailableFcn = #bytesAvailable_callback;
Every time the callback is called, it clears the buffer completely.
For example, with a udp client:
function bytesAvailable_callback(udp_obj, event)
bytesAvailable = get(udp_obj, 'BytesAvailable');
fprintf('callback - bytesAvailable = %d\n',bytesAvailable);
while bytesAvailable
data = fread(udp_obj, bytesAvailable, 'int32');
fprintf('rec = %d\n',data(1));
bytesAvailable = get(udp_obj, 'BytesAvailable');
end
end
what happens next is the problem: the buffer has been cleared but the BytesAvailableFcn is still in the call stack and will be called many time, only to return nothing.
an example running the above callback with udp:
clc
echoudp('on', 8000)
%
u = udp('127.0.0.1', 8000);
u.OutputBufferSize = 10000;
u.InputBufferSize = 10000;
u.BytesAvailableFcn = #bytesAvailable_callback;
u.BytesAvailableFcnCount = 10;
u.BytesAvailableFcnMode = 'byte';
fopen(u);
%
for i = 1:2
fprintf('send = %d\n',i );
fwrite(u, [i ones(1,10)], 'int32');
end
pause(1)
%%
fclose(u);
delete(u);
clear u
echoudp('off');
the results are:
send = 1
send = 2
callback - bytesAvailable = 88
rec = 1
rec = 2
callback - bytesAvailable = 0
callback - bytesAvailable = 0
callback - bytesAvailable = 0
callback - bytesAvailable = 0
callback - bytesAvailable = 0
callback - bytesAvailable = 0
callback - bytesAvailable = 0
We can easlly see the unwanted behaviour - callback called when the InputBuffer is empty.
The above can be very demanding in a busy program where every callback counts.
Is there a way to "flush" the above unwanted callback calls?
Using drawnow did not help - it executed the callbacks, so still accesses the function.
I have the same problem but found a workaround. I wrote a program that would read 1 byte at a time until it reached a terminator (matlab did not automatically recognize the terminators. After adding a line like this:
if u.bytesavailable == 0
fprintf(['function was called but buffer is epty' 13])
end
I noticed it would be called once for every byte I read -1. Apparently every event changing the size of the buffer (e.g. reading a byte) creates a call to your readout function again. What I found worked was to read not the entire buffer at once but the size that triggers the event. So for instance:
fread(u,BytesAvailableFcnCount)
should leave you with no more extra function calls at the cost of speed. It works for me for small amounts of data, but I am using a serial connection, not a udp one.
I tried disabling the bytesavailablefcn inside the callback function itself, as to disable any function calls shile the function is reading but I still got a load of function calls with an empty buffer (I don't know why).
Ron
Related
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!
As far as I can see your program is behaving correctly.
1)
Because the notifications are asynchronous, they may arrive after your wait time is over.At that time though you already disposed the notification.
To test if this theory is correct, add a timer in your Twincat progam.
Declaration:
fbTimer : TON;
Implementation:
fbTimer(IN:=TRUE,PT:=T#2s);
IF NOT fbTimer.Q
THEN
cc_struct.nCycleCount := _TaskInfo[1].CycleCount;
END_IF
Make sure that your matlab program is already started before you start the plc
and raise your pause time in Matlab to 120s.
If you get 2000 values then you know the problem derives from the asynchronous nature of the communication.
2)
The conversion error derives from the ReadInt64 method which:
Reads an 8-byte signed integer from the current stream and advances
the current position of the stream by eight bytes.
https://learn.microsoft.com/en-us/dotnet/api/system.io.binaryreader.readint64?redirectedfrom=MSDN&view=netframework-4.8#System_IO_BinaryReader_ReadInt64
You should use ReadUInt64 instead.
To see if I could reproduce your same behaviour, I created a small c# test program.
The test program behaved normally and I was able to receive the correct amount of notifications.
Here the ST code :
Declaration:
PROGRAM MAIN
VAR
fbTimer: TON;
nCycleCount : DWORD;
END_VAR
Implementation:
fbTimer(IN:=TRUE,PT:=T#2S);
IF NOT fbTimer.Q
THEN
nCycleCount := _TaskInfo[1].CycleCount;
END_IF
Here the C# code :
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);
}
}
}
}
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
How can I chane the "timeout" value in UDP communication?
I get this warning:
Warning: Unsuccessful read: The specified amount of data was not returned within the Timeout period.
and I want to increase the timeout value so I will not expireance this warning...
To set a timeout, you can use:
u.Timeout=99;
In this case the timeout property is not documented, at least I am unable to find any documentation. When working with unknown or not fully documented objects, I typically try get methods and properties to examine them:
>> methods(x)
Methods for class udp:
Contents eq fscanf instrhelp ne size
binblockread fclose fwrite instrhwinfo obj2mfile stopasync
binblockwrite fgetl get instrnotify open subsasgn
class fgets horzcat instrument openvar subsref
close fieldnames icinterface isa propinfo udp
ctranspose flushinput igetfield isequal query vertcat
delete flushoutput inspect isetfield readasync
disp fopen instrcallback isvalid record
display fprintf instrfind length scanstr
end fread instrfindall methods set
>> properties(x)
No properties for class udp or no class udp.
>> get(x)
ByteOrder = bigEndian
BytesAvailable = 0
BytesAvailableFcn =
BytesAvailableFcnCount = 48
BytesAvailableFcnMode = terminator
BytesToOutput = 0
ErrorFcn =
InputBufferSize = 512
Name = UDP-127.0.0.1
ObjectVisibility = on
OutputBufferSize = 512
OutputEmptyFcn =
RecordDetail = compact
RecordMode = overwrite
RecordName = record.txt
RecordStatus = off
Status = open
Tag =
Timeout = 3
TimerFcn =
TimerPeriod = 1
TransferStatus = idle
Type = udp
UserData = []
ValuesReceived = 0
ValuesSent = 0
UDP specific properties:
DatagramAddress =
DatagramPort = []
DatagramReceivedFcn =
DatagramTerminateMode = on
InputDatagramPacketSize = 512
LocalHost =
LocalPort = 52148
LocalPortMode = auto
OutputDatagramPacketSize = 512
ReadAsyncMode = continuous
RemoteHost = 127.0.0.1
RemotePort = 9090
Terminator = LF
Note that get is not always supported. In case you receive Error using get Conversion to double from A is not possible. the other two methods display everything accessible.
In simulink, I made some model using "MATLAB function"block
but I met error message here.
here is code and error message.
function [VTAS,postVTAS]=fcn(mode,initialVTAS,a,t,preVTAS)
if mode == 1
VTAS = initialVTAS + (a * t) ;
postVTAS = VTAS;
elseif mode == 2
datasize = length(preVTAS);
lastvalue = preVTAS(datasize);
VTAS = lastvalue + 0;
postVTAS = VTAS;
end
end
Output argument 'VTAS' is not assigned on some execution paths.
Function 'MATLAB Function' (#36.25.28), line 1, column 26:
"fcn"
Launch diagnostic report.
I think there is no problem about output "VTAS"
please teach me what is a problems.
As the compiler tells you, under some circumstances there is no output value assigned to VTAS. The reason is that you only assign values to that output if mode is 1 or 2. The compiler doesn't know what values are feasible for mode. To remedy this, you need to make sure that VTAS is assigned under any and all circumstances.
This could be accomplished by, e.g. adding an else construct, like so:
function [VTAS,postVTAS]=fcn(mode,initialVTAS,a,t,preVTAS)
if mode == 1
VTAS = initialVTAS + (a * t) ;
postVTAS = VTAS;
elseif mode == 2
datasize = length(preVTAS);
lastvalue = preVTAS(datasize);
VTAS = lastvalue + 0;
postVTAS = VTAS;
else
VTAS = NaN;
postVTAS = NaN;
end
end
Edit:
Additionally, it would be good practice for the else case to throw an error. This would be helpful for debugging.
As a minor note, for every case, postVTAS is equal to VTAS, so essentially it is superfluous to return both from the function.
Here is my code. The intent is I have a Wireshark capture saved to a particularly formatted text file. The MATLAB code is supposed to go through the Packets, dissect them for different protocols, and then make tables based on those protocols. I currently have this programmed for ETHERNET/IP/UDP/MODBUS. In this case, it creates a column in MBTable each time it encounters a new register value, and each time it comes across a change to that register value, it updates the value in that line of the table. The first column of MBTable is time, the registers start with the second column.
MBTable is preallocated to over 100,000 Rows (nol is very large), 10 columns before this code is executed. The actual data from a file I'm pulling into the table gets to about 10,000 rows and 4 columns and the code execution is so slow I have to stop it. The tic/toc value is calculated every 1000 rows and continues to increase exponentially with every iteration. It is a large loop, but I can't see where anything is growing in such a way that it would cause it to run slower with each iteration.
All variables get initialized up top (left out to lessen amount of code.
The variables eth, eth.ip, eth.ip.udp, and eth.ip.udp.modbus are all of type struct as is eth.header and eth.ip.header. WSID is a file ID from a .txt file opened earlier.
MBTable = zeros(nol,10);
tval = tic;
while not(feof(WSID))
packline = packline + 1;
fl = fl + 1;
%Get the next line from the file
MBLine = fgetl(WSID);
%Make sure line is not blank or short
if length(MBLine) >= 3
%Split the line into 1. Line no, 2. Data, 3. ASCII
%MBAll = strsplit(MBLine,' ');
%First line of new packet, if headers included
if strcmp(MBLine(1:3),'No.')
newpack = true;
newtime = false;
newdata = false;
stoppack = false;
packline = 1;
end
%If packet has headers, 2nd line contains timestamp
if newpack
Ordered = false;
if packline == 2;
newtime = true;
%MBstrs = strsplit(MBAll{2},' ');
packno = int32(str2double(MBLine(1:8)));
t = str2double(MBLine(9:20));
et = t - lastt;
if lastt > 0 && et > 0
L = L + 1;
MBTable(L,1) = t;
end
%newpack = false;
end
if packline > 3
dataline = int16(str2double(MBLine(1:4)));
packdata = strcat(packdata,MBLine(7:53));
end
end
else
%if t >= st
if packline > 3
stoppack = true;
newpack = false;
end
if stoppack
invalid = false;
%eth = struct;
eth.pack = packdata(~isspace(packdata));
eth.length = length(eth.pack);
%Dissect the packet data
eth.stbyte = 1;
eth.ebyte = eth.length;
eth.header.stbyte = 1;
eth.header.ebyte = 28;
%Ethernet Packet Data
eth.header.pack = eth.pack(eth.stbyte:eth.stbyte+27);
eth.header.dest = eth.header.pack(eth.header.stbyte:eth.header.stbyte + 11);
eth.header.src = eth.header.pack(eth.header.stbyte + 12:eth.header.stbyte + 23);
eth.typecode = eth.header.pack(eth.header.stbyte + 24:eth.header.ebyte);
if strcmp(eth.typecode,'0800')
eth.type = 'IP';
%eth.ip = struct;
%IP Packet Data
eth.ip.stbyte = eth.header.ebyte + 1;
eth.ip.ver = eth.pack(eth.ip.stbyte);
%IP Header length
eth.ip.header.length = 4*int8(str2double(eth.pack(eth.ip.stbyte+1)));
eth.ip.header.ebyte = eth.ip.stbyte + eth.ip.header.length - 1;
%Differentiated Services Field
eth.ip.DSF = eth.pack(eth.ip.stbyte + 2:eth.ip.stbyte + 3);
%Total IP Packet Length
eth.ip.length = hex2dec(eth.pack(eth.ip.stbyte+4:eth.ip.stbyte+7));
eth.ip.ebyte = eth.ip.stbyte + max(eth.ip.length,46) - 1;
eth.ip.pack = eth.pack(eth.ip.stbyte:eth.ip.ebyte);
eth.ip.ID = eth.pack(eth.ip.stbyte+8:eth.ip.stbyte+11);
eth.ip.flags = eth.pack(eth.ip.stbyte+12:eth.ip.stbyte+13);
eth.ip.fragoff = eth.pack(eth.ip.stbyte+14:eth.ip.stbyte+15);
%Time to Live
eth.ip.ttl = hex2dec(eth.pack(eth.ip.stbyte+16:eth.ip.stbyte+17));
eth.ip.typecode = eth.pack(eth.ip.stbyte+18:eth.ip.stbyte+19);
eth.ip.checksum = eth.pack(eth.ip.stbyte+20:eth.ip.stbyte+23);
%eth.ip.src = eth.pack(eth.ip.stbyte+24:eth.ip.stbyte+31);
eth.ip.src = ...
[num2str(hex2dec(eth.pack(eth.ip.stbyte+24:eth.ip.stbyte+25))),'.', ...
num2str(hex2dec(eth.pack(eth.ip.stbyte+26:eth.ip.stbyte+27))),'.', ...
num2str(hex2dec(eth.pack(eth.ip.stbyte+28:eth.ip.stbyte+29))),'.', ...
num2str(hex2dec(eth.pack(eth.ip.stbyte+30:eth.ip.stbyte+31)))];
eth.ip.dest = ...
[num2str(hex2dec(eth.pack(eth.ip.stbyte+32:eth.ip.stbyte+33))),'.', ...
num2str(hex2dec(eth.pack(eth.ip.stbyte+34:eth.ip.stbyte+35))),'.', ...
num2str(hex2dec(eth.pack(eth.ip.stbyte+36:eth.ip.stbyte+37))),'.', ...
num2str(hex2dec(eth.pack(eth.ip.stbyte+38:eth.ip.stbyte+39)))];
if strcmp(eth.ip.typecode,'11')
eth.ip.type = 'UDP';
eth.ip.udp.stbyte = eth.ip.stbyte + 40;
eth.ip.udp.src = hex2dec(eth.pack(eth.ip.udp.stbyte:eth.ip.udp.stbyte + 3));
eth.ip.udp.dest = hex2dec(eth.pack(eth.ip.udp.stbyte+4:eth.ip.udp.stbyte+7));
eth.ip.udp.length = hex2dec(eth.pack(eth.ip.udp.stbyte+8:eth.ip.udp.stbyte+11));
eth.ip.udp.checksum = eth.pack(eth.ip.udp.stbyte+12:eth.ip.udp.stbyte+15);
eth.ip.udp.protoID = eth.pack(eth.ip.udp.stbyte+20:eth.ip.udp.stbyte+23);
if strcmp(eth.ip.udp.protoID,'0000')
eth.ip.udp.proto = 'MODBUS';
%eth.ip.udp.modbus = struct;
eth.ip.udp.modbus.stbyte = eth.ip.udp.stbyte+16;
eth.ip.udp.modbus.transID = eth.pack(eth.ip.udp.modbus.stbyte:eth.ip.udp.modbus.stbyte+3);
eth.ip.udp.modbus.protoID = eth.ip.udp.protoID;
eth.ip.udp.modbus.length = int16(str2double(eth.pack(eth.ip.udp.modbus.stbyte + 8:eth.ip.udp.modbus.stbyte + 11)));
eth.ip.udp.modbus.UID = eth.pack(eth.ip.udp.modbus.stbyte + 12:eth.ip.udp.modbus.stbyte + 13);
eth.ip.udp.modbus.func = hex2dec(eth.pack(eth.ip.udp.modbus.stbyte + 14:eth.ip.udp.modbus.stbyte+15));
eth.ip.udp.modbus.register = eth.pack(eth.ip.udp.modbus.stbyte + 16: eth.ip.udp.modbus.stbyte+19);
%Number of words to a register, or the number of registers
eth.ip.udp.modbus.words = hex2dec(eth.pack(eth.ip.udp.modbus.stbyte+20:eth.ip.udp.modbus.stbyte+23));
eth.ip.udp.modbus.bytes = hex2dec(eth.pack(eth.ip.udp.modbus.stbyte+24:eth.ip.udp.modbus.stbyte+25));
eth.ip.udp.modbus.data = eth.pack(eth.ip.udp.modbus.stbyte + 26:eth.ip.udp.modbus.stbyte + 26 + 2*eth.ip.udp.modbus.bytes - 1);
%If func 16 or 23, loop through data/registers and add to table
if eth.ip.udp.modbus.func == 16 || eth.ip.udp.modbus.func == 23
stp = eth.ip.udp.modbus.bytes*2/eth.ip.udp.modbus.words;
for n = 1:stp:eth.ip.udp.modbus.bytes*2;
%Check for existence of register as a key?
if ~isKey(MBMap,eth.ip.udp.modbus.register)
MBCol = MBCol + 1;
MBMap(eth.ip.udp.modbus.register) = MBCol;
end
MBTable(L,MBCol) = hex2dec(eth.ip.udp.modbus.data(n:n+stp-1));
eth.ip.udp.modbus.register = dec2hex(hex2dec(eth.ip.udp.modbus.register)+1);
end
lastt = t;
end
%If func 4, make sure it is the response, then put
%data into table for register column
elseif false
%need code to handle serial to UDP conversion box
else
invalid = true;
end
else
invalid = true;
end
else
invalid = true;
end
if ~invalid
end
end
%end
end
%Display Progress
if int64(fl/1000)*1000 == fl
for x = 1:length(mess);
fprintf('\b');
end
%fprintf('Lines parsed: %i',fl);
mess = sprintf('Lines parsed: %i / %i',fl,nol);
fprintf('%s',mess);
%Check execution time - getting slower:
%%{
ext = toc(tval);
mess = sprintf('\nExecution Time: %f\n',ext);
fprintf('%s',mess);
%%}
end
end
ext = toc - exst;
Update: I updated my code above to remove the overloaded operators (disp and lt were replaced with mess and lastt)
Was asked to use the profiler, so I limited to 2000 lines in the table (added && L >=2000 to the while loop) to limit the execution time, and here are the top results from the profiler:
SGAS_Wireshark_Parser_v0p7_fulleth 1 57.110 s 9.714 s
Strcat 9187 29.271 s 13.598 s
Blanks 9187 15.673 s 15.673 s
Uigetfile 1 12.226 s 0.009 s
uitools\private\uigetputfile_helper 1 12.212 s 0.031 s
FileChooser.FileChooser>FileChooser.show 1 12.085 s 0.006s
...er>FileChooser.showPeerAndBlockMATLAB 1 12.056 s 0.001s
...nChooser>FileOpenChooser.doShowDialog 1 12.049 s 12.049 s
hex2dec 44924 2.944 s 2.702 s
num2str 16336 1.139 s 0.550 s
str2double 17356 1.025 s 1.025 s
int2str 16336 0.589 s 0.589 s
fgetl 17356 0.488 s 0.488 s
dec2hex 6126 0.304 s 0.304 s
fliplr 44924 0.242 s 0.242 s
It appears to be strcat calls that are doing it. I only explicitly call strcat on one line. Are some of the other string manipulations I'm doing calling strcat indirectly?
Each loop should be calling strcat the same number of times though, so I still don't understand why it takes longer and longer the more it runs...
also, hex2dec is called a lot, but is not really affecting the time.
But anyway, are there any other methods I can use the combine the strings?
Here is the issue:
The string (an char array in MATLAB) packdata was being resized and reallocated over and over again. That's what was slowing down this code. I did the following steps:
I eliminated the redundant variable packdata and now only use eth.pack.
I preallocated eth.pack and a couple "helper variables" of known lengths by running blanks ONCE for each before the loop ever starts
eth.pack = blanks(604);
thisline = blanks(47);
smline = blanks(32);
(Note: 604 is the maximum possible size of packdata based on headers + MODBUS protocol)
Then I created a pointer variable to point to the location of the last char written to packdata.
pptr = 1;
...
dataline = int16(str2double(MBLine(1:4)));
thisline = MBLine(7:53); %Always 47 characters
smline = [thisline(~isspace(thisline)),blanks(32-sum(~isspace(thisline)))]; %Always 32 Characters
eth.pack(pptr:pptr+31) = smline;
pptr = pptr + 32;
The above was inside the 'if packline > 3' block in place of the 'packdata =' statement, then at the end of the 'if stoppack' block was the reset statement:
pptr = 1; %Reset Pointer
FYI, not surprisingly this brought out other flaws in my code which I've mostly fixed but still need to finish. Not a big issue now as this loop executes lightning fast with these changes. Thanks to Yvon for helping point me in the right direction.
I kept thinking my huge table, MBTable was the issue... but it had nothing to do with it.
I am currently working on a GUI-based application that records serial data from an embedded system that is transmitted at 50Hz. The problem is that MATLAB appears incapable of recording this data reliably, and drops samples. The format of the transmitted data is as follows:
4918 22279 29342 3161 0 24953 29814 5319 1 0
4919 22279 29348 2997 1 24953 29838 5037 0 0
4920 22279 29357 2682 0 24953 29853 4544 0 0
Each row is terminated by a CR/LF. I have written a test function to try and figure out what is going on - the code for this is below.
function stack_stream(time)
column_count = 10;
serial_object = create_serial_object;
fopen(serial_object);
date_vector = clock;
file_name = datestr(date_vector,30);
file_name = [file_name '.txt'];
file_identifier = fopen(file_name,'w');
tic;
while (toc < time)
if (serial_object.UserData.is_new_data == true)
raw_chunk = serial_object.UserData.data;
serial_object.UserData.is_new_data = false;
data_chunk = sscanf(raw_chunk,'%d');
data_chunk_length = length(data_chunk);
if (mod(data_chunk_length,column_count) == 0)
data_column_count = data_chunk_length/column_count;
data = reshape(data_chunk,column_count,data_column_count);
fprintf(file_identifier,...
'%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\r\n',data);
end
end
end
fclose(file_identifier);
fclose(serial_object);
delete(serial_object);
end
function serial_object_callback(object,event)
new_data = fscanf(object,'%c',object.BytesAvailable);
if (object.UserData.is_new_data == false)
object.UserData.data = new_data;
object.UserData.is_new_data = true;
else
object.UserData.data = [object.UserData.data new_data];
end
end
function serial_object = create_serial_object()
serial_object = serial('COM2');
serial_object.BaudRate = 57600;
serial_object.DataBits = 8;
serial_object.FlowControl = 'none';
serial_object.StopBits = 1;
serial_object.Terminator = 'CR/LF';
serial_object.InputBufferSize = 2^18;
serial_object.BytesAvailableFcnMode = 'terminator';
serial_object.BytesAvailableFcn = {#serial_object_callback};
serial_object.UserData.data = [];
serial_object.UserData.is_new_data = false;
serial_object.UserData.response = [];
serial_object.UserData.is_new_response = false;
end
In essence, this function writes time seconds worth of data to file, using a callback to transfer data between the serial object buffer and a user buffer. Using stack_stream I find that I miss around 10 rows of data for every 3000 received - ie about 10 a minute.
Anecdotal evidence would lead me to believe that MATLAB is capable of handling serial data in the kHz range, so I am at somewhat of a loss to determine why I cannot get it to work properly at a paltry 50Hz. Is there anything I can do to improve performance without having to resort to the real-time target for windows or the like?