Using public class methods and libraries in parfeval()

Hello everyone,
I've been googling the entire day, but nobody seems to have quite the same problem as myself at the moment.
I've programmed a matlab-class to communicate to a USB-SPI Interface Chip via the provided drivers. No Problem here. I can execute everything and read and write Data properly and quickly.
My Problem starts, as soon as I start to put this Code into a parallel pool. Since it's some very active Code (i.e. polling of IO-Pins and such) I need it to run separately from everything else.
My Code:
Firstly, I open the (properly working outside of a parallel pool) Class:
% Open ACCELEROMETER Class
ACC = ADXL372;
% Configure both Channels, as per documentation
[P1,C1]=ACC.ADXL_Config(1);
[P2,C2]=ACC.ADXL_Config(2);
My constructor looks as follows:
function ADXL = ADXL372()
if ~libisloaded( ADXL.Libname )
loadlibrary( ADXL.Libname, 'libMPSSE_spi_matlabFriendly.h');
end
if ~libisloaded( ADXL.Libname2 )
loadlibrary( ADXL.Libname2);
end
clc
% Get the number of SPI channels available.
calllib(ADXL.Libname,'SPI_GetNumChannels',ADXL.pNumchannels);
disp(['Channels Found: ', num2str(get(ADXL.pNumchannels,'value'))]);
% Connect to SPI channel 0. Valid numbers are 0:(Numchannels-1).
E = calllib(ADXL.Libname,'SPI_OpenChannel',0,ADXL.pChannelHandle);
disp(['Channel Opened with Errorcode: ', num2str(E)]);
% Set GPIO for Receiving Interrupt
E=calllib(ADXL.Libname,'FT_WriteGPIO',ADXL.pChannelHandle,0,0);
disp(['GPIO Set with Errorcode: ',num2str(E)]);
% Set Latency Timer to 1ms
E=calllib(ADXL.Libname2,'FT_SetLatencyTimer',ADXL.pChannelHandle,1);
disp(['LatencyTimer Set with Errorcode: ',num2str(E)]);
% Define the channel configuration struct, and initialize the channel.
ChConfig.ClockRate = uint32(10e6); % Clock speed, Hz
ChConfig.LatencyTimer = uint32(1); % Users guide section 3.4, suggested value is 2-255 for all devices
ChConfig.configOptions = uint32(0b100000); % Bit 1 is CPOL, bit 0 is CPHA. Higher order bits configure the chip select.
ChConfig.Pin = uint32(0x00000000);
E = calllib(ADXL.Libname,'SPI_InitChannel',ADXL.pChannelHandle,ChConfig);
disp(['SPI configured with Errorcode: ',num2str(E)]);
end
I've found solutions to make my main file know, that the function exists. Now at least I'm not getting the "Unknown Function ..." Error.
F = parfeval(gcp(),@(varargin) ACC.ReadFIFO(varargin{:}),4,Dauer,10);
with ReadFIFO being:
function [MessungA,MessungB,TVectorA,TVectorB] = ReadFIFO(ADXL,Time,Means)
% Some code that's irrelevant to this question
end
The Problem:
I need to run ReadFIFO in a separate pool for the rest of my program to work properly. First I got errors, that the function was unkown. Now I'm getting Errors that say "Library was not found", as if the pool doesn't receive the loaded libraries.
I can provide more code, but it essentially all looks like this example:
calllib(ADXL.Libname,'SPI_ChangeCS',ADXL.pChannelHandle, uint32(0b100000));

 Accepted Answer

I suspect the problem here is that when you transfer instances of your ADXL372 class to the workers, they are not getting set up correctly. The data transfer mechanism from client to worker when you invoke parfeval is equivalent to saving your objects to disk, and then loading them on the worker (it all happens in memory though). The problem is almost certainly that your loadlibrary calls are not being invoked on the workers.
By default, MATLAB classes do not call the constructor during load (or when they're received by a worker for a parfeval call). See this page in the doc for more details. I think you have two options here:
Here's an example of what I mean for the 2nd point:
% This causes the ADXL372 constructor to be invoked on every worker
adxlConst = parallel.pool.Constant(@ADXL372)
% Pass the Constant to the workers, extract the ".Value" and use that
F = parfeval(gcp(),@(c, varargin) ReadFIFO(c.Value, varargin{:}),4, adxlConst, Dauer, 10);

5 Comments

The errormessages are gone, but now my code doesn't seem to do anything? Usually I'll read out the Outputs of ADXL_Config() but they keep returning 5x1 Cell arrays and are filled with zeroes? Is that an error of reading the Values or does the worker "forget" what happened before?
Can I invoke this
adxlConst = parallel.pool.Constant(@ADXL372)
as "app.adxlConst" and use it globally in my application?
I've moved my code to a Button, to execute it.
% Button pushed function: VerbindenButton
function VerbindenButtonPushed(app, event)
% Status LED
app.StatusLampLabel.Text = "Connecting...";
app.StatusLamp.Color = 'yellow';
% This causes the ADXL372 constructor to be invoked on every worker
adxlConst = parallel.pool.Constant(@ADXL372)
% Load Libraries (again, if Constructor didn't succeed)
F = parfeval(gcp(),@(c, varargin) LoadLibraries(c.Value, varargin{:}),0, adxlConst);
% Setup of SPI Connection
F = parfeval(gcp(),@(c, varargin) SPISetup(c.Value, varargin{:}),0, adxlConst);
% Configuration and readout of Channel 1
F1 = parfeval(gcp(),@(c, varargin) ADXL_Config(c.Value, varargin{:}),2, adxlConst,1);
wait(F1);
Outputs_Cell = fetchOutputs(F1)
P1 = Outputs_Cell(1)
C1 = Outputs_Cell(2)
% Configuration and readout of Channel 2
F2 = parfeval(gcp(),@(c, varargin) ADXL_Config(c.Value, varargin{:}),2, adxlConst,2);
wait(F2);
Outputs_Cell = fetchOutputs(F2)
P2 = Outputs_Cell(1)
C2 = Outputs_Cell(2)
if P2(1,1) == 0xAD
app.StatusLampLabel.Text = "Connected!";
app.StatusLamp.Color = 'green';
else
app.StatusLampLabel.Text = "Error.";
app.StatusLamp.Color = 'red';
end
end
I'm not really sure what's going on here. But one thing that I'm very suspicious about is that you've got two separate parfeval invocations calling SPISetup and ADXL_Config. What happens if these happen on different workers? If SPISetup needs to be run everywhere, you could instead use parfevalOnAll.
F = parfevalOnAll(gcp(),@(c, varargin) SPISetup(c.Value, varargin{:}),0, adxlConst);
One other trivial observation - with parfeval, there is no need to call wait before fetchOutputs - the call to fetchOutputs will wait if necessary.
I thought, that
app.adxlConst = parallel.pool.Constant(@ADXL372);
creates an "instance" of my class, that can be referenced by the workers? SPISetup and ADXL_Config only need to be called once. They set the FTDI and ADXL372 chips up for communication. As long as precisely ONE worker knows the libraries and executes these functions, everything's good. These didn't necessarily needed to be on workers, but I've got no Idea how to tell that to my code.
I fixed the wrong readings from my last comment with this code (unrelated to the topic, but for future reference):
F2 = parfeval(gcp(),@(c, varargin) ADXL_Config(c.Value, varargin{:}),2, app.adxlConst,2);
Outputs_Cell = fetchOutputs(F2);
P2 = Outputs_Cell(1);
C2 = Outputs_Cell(2);
But the wait() AND the fetchOutputs() functions seem to block a timer i've set up to pass values to several gauges. The refresh rate drops from 10ms to ~1s as soon as either of these is waiting for a Future element to be finished. Is there a way to wait for Outputs while simultaneously refreshing the GUI with a timer?
The statement
app.adxlConst = parallel.pool.Constant(@ADXL372);
builds an instance of ADXL372 on each worker. I thought that SPISetup needed to be called by each process to set up state there - but it sounds like that is not the case?
If you want to update a UI on completion of a Future, you might want to use afterEach - this is designed for that sort of situation.
EDIT:
NEVERMIND, I'm dumb.
there was a STOP()-function still in my code, that was being executed, as soon as the accelerometers started. It's even in this comment right between the two StatusLED-function calls.
It works now!
Original Message:
I only need one instance of ADXL372.
Sorry if my information isn't sufficient enough. I've never used either classes or the parallel computing toolbox before but need both for my master's thesis.
In essence:
ADXL372 is a class I programmed, whose sole purpose is setting up and communicating with two ADXL372 accelerometers via a single FTDI USB-SPI interface.
The class's properties consist of all possible configuration bits in mnemonic form, so that later re-configuration is easier.
The class's methods include constructor (which WAS used to load the libraries AND set the SPI Chip up. These two functions have since been moved to separate methods) and readFIFO which, well, reads the FIFO.
readFIFO is polling a Pin on the FTDI-Interface to determine whether data is available and is the sole reason that this whole thing needs to run in parallel. I need to be able to read the FIFO and simultaneously talk to two maxon motors and a read/write serialport (the latter of which I haven't even touched yet).
I've tried afterEach, but it locks everything (i.e. GUI consisting of two gauges and both motors) up completely. Not even the motors move after Starting. This is the Start function:
% Button pushed function: STARTButton
function STARTButtonPushed(app, event)
app.StatusLamp.Color = 'red';
if app.AxialSwitch.Value == "On"
app.Motor1.SetOperationMode( OperationModes.ProfileVelocityMode );
app.Motor1.WaitUntilDone(1000);
app.Motor1.MotionInVelocity( app.GeschwindigkeitUminEditField_2.Value, app.BeschleunigungUminsEditField_2.Value );
end
% ^^^^^ works fine on its own and in combination with Motor2
if app.TransversalSwitch.Value == "On"
app.Motor2.SetOperationMode( OperationModes.ProfileVelocityMode );
app.Motor2.WaitUntilDone(1000);
app.Motor2.MotionInVelocity( app.GeschwindigkeitUminEditField.Value, app.BeschleunigungUminsEditField.Value );
end
% ^^^^^ works fine on its own and in combination with Motor1
if app.ACCSwitch.Value == "On"
j = parfeval(gcp(),@(c, varargin) ReadFIFO(c.Value, varargin{:}),4, app.adxlConst, app.MessungsdauersEditField.Value, app.MovingAveragesEditField.Value);
afterEach(j,@(A,B,tA,tB) app.DisplayACCData(A,B,tA,tB),0);
app.StatusLamp.Color = 'yellow';
STOP(app);
app.StatusLamp.Color = 'green';
end
% ^^^^^ locks GUI and Motors 1&2 up as soon as statement is true and releases
% them as soon as readFIFO is done.
end

Sign in to comment.

More Answers (0)

Categories

Products

Release

R2020b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!