Why did my code break after adding a class definition?

I have some code that interfaces with external hardware via loadlibrary to a vendor-supplied DLL. After getting everything working the way I wanted, I decided to refactor to make future maintainability easier. As part of this process, I defined a class params.m that holds experimental parameters (e.g. image height, image width, number of frames,...). Previously, I had just made a params struct and added stuff to it as needed (without a class definition). I also added arguments to my function definitions, e.g.:
function configure(inParams, boardNumber)
arguments
inParams (1,1) params
boardNumber (1,1) double {mustBeInteger, mustBePositive}
end
% call hardware functions from DLL
end
In doing so, this broke my experiment. The Matlab code is shared between two systems, one that has a single board and one that has two boards so configure is called in a for loop:
for boardId = 1 : numBoards
configure(inParams,boardId);
end
% do stuff
The code refactor broke my experiment, but only on the system that has two boards. While debugging, I noticed that inserting an arbitrary pause made the two-board system work every other time the code was run (which is better than never, but still 50%). After that realization, I tried commenting out the arguments definition:
function configure(inParams, boardNumber)
% arguments
% inParams (1,1) params
% boardNumber (1,1) double {mustBeInteger, mustBePositive}
% end
% call hardware functions from DLL
end
Which removed the need for the arbitrary pause, but still only got the two-board system to work correctly on every other function call (always works on first try, and from then on any odd-numbered run will work). It seems as though there is some kind of under-the-hood optimization(?) that is ocurring after I added a class definition that breaks things.
Does anyone have other ideas what the issue might be and/or how I can disable optimizations for that one function call (which appears to be impossible based on my reading). I recognize this is an exceptionally odd error and I'm happy to provide more information if that would be useful. I have also reached out to the hardware manufacturer to see if they have any suggestions for what might be causing this.
---------- EDIT ------------
After getting in touch with the hardware vendor, they instructed me to enable some debug features on the board(s) to help diagnose it from that side. This apparently fixed the issue as now it works reliably (even with debugging turned off again). This is somewhat unsatisfying to me as the code would work reliably with the old (pre-class) version of the code and fail reliably with the new version, so I don't think it was exclusively a hardware problem.

8 Comments

Is it correct that you created a handle class, rather than a value class ?
For reference, here is params.m (or at least, an abbreviated version of it):
classdef params
properties
boards boardInfo % note: this is a handle class as it interfaces with the boards
controlBox control % also a handle class, it communicates over serial with an arduino
numBoards (1,1) {mustBePositive, mustBeInteger} = 1
imageHeightPix (1,1) {mustBePositive, mustBeInteger} = 512
imageWidthPix (1,1) {mustBePositive, mustBeInteger} = 512
%... other settings, all of which are standard types (double, int,
% char,...) no more custom classes
end
end
I did try making params a handle class, just to see if that helped (it did not).
What does "broke" mean in the context of "The code refactor broke my experiment, but only on the system that has two boards."?
  • Do you receive warning and/or error messages? If so the full and exact text of those messages (all the text displayed in orange and/or red in the Command Window) may be useful in determining what's going on and how to avoid the warning and/or error.
  • Does it do something different than what you expected? If so, what did it do and what did you expect it to do?
  • Did MATLAB crash? If so please send the crash log file (with a description of what you were running or doing in MATLAB when the crash occured) to Technical Support so we can investigate.
Thanks for the questions -- when I say "broke" I mean it runs the code without error, but does not produce the expected result. In my case, the boards are high-speed digitizers and the settings that do not get appropriately set are anything related to how they should trigger. Note that this only happens on every other run of the code (so 2nd, 4th,... all silently fail). This is not the case on the other system I have that only has one digitizer in it -- it works correctly every time even after the code refactor.
For example, if I set the trigger to be on a rising edge of at least 100 mV with a 5 second timeout, and then supply an appropriate hardware trigger the board never triggers and acts as though it didn't receive the signal. I know that it should work, because if I use my old version of the code it works correctly with the same hardware setup.
Okay, how do you create the inParams variable that you pass into the configure method? Is it an instance of the params class? Or is it data that you hope/expect to be used to construct a params class inside the arguments block?
Those properties of the boards that are not set appropriately are set using some function from a DLL that the board manufacturer or MathWorks has provided to interface with that board?
Ah, reading more closely your params class makes me think I know something that may be contributing to or causing the behavior you're seeing. The boards and controlBox properties contain handle objects. The "Expression Evaluation in Handle and Value Classes" section on this documentation page shows what happens when you define a property whose default value is a value class and a handle class. Make sure the behavior it describes when a property's default value is a handle class is the behavior you expect for your class.
The inParams variable is created via an app UI panel and then passed to a function that runs the rest of the experiment. In the app's code, I assign the appropriate values to the params like this:
% inside a Start Button callback:
inParams = params;
inParams.imageWidthPix = app.ImageHeightPixelsEditField.Value;
inParams.boards = boardInfo;
inParams.boards.numBoards = 2;
% ... set all the stuff
runAcquisition(inParams); % this calls "configure" and several other functions
runAcquisition looks like:
function runAcquisition(inParams)
arguments
inParams (1,1) params
end
inParams = setBoardParams(inParams); % note that this also calls some of the hardware DLLs (seemingly successfully)
% ... other stuff, no other DLL calls
for id = 1 : numBoards
inParams = configure(inParams, id);
end
data = acquireData(inParams); % has DLL calls that seem to work as it appears as though the measurement "starts" but with the wrong configuration
% plot data or whatever
end
  • I do not do any construction inside of the arguments block
  • The DLL's were provided by the vendor of the hardware (Alazar Tech) and they have Matlab examples (which I followed successfully, up until I introduced the class)
  • I think I'm missing how that documentation is relevant here as I don't do any evaluation, nor do I have a default value set for either of the handle classes. They both get replaced after the params object is created.
I've checked that the handles to both the serial port and the boards are correct when they are called. For example, inside configure I have paused in the debugger and called a library function that just identifies what board I'm talking to based on a pointer (stored in a handle class). This always succeeds, so for example inside configure(inParams, 1):
boardHandle = inParams.boards(boardNumber).handle;
% Vendor DLL call to check if boardHandle is valid and which board it
% points to. Runs successfully for both boards.
identifyBoard(boardHandle);
configure(inParams,boardId);
Your class is a value class. Any changes to the class that you make will get discarded, as you are not saving the resulting object back to inParams .
I can't say I am completely sure what happens if you just change handle objects within the value class.. I guess that would work?
Nothing should be changing inside that function (not even the handles classes). Parameters from inParams are passed to the board via the vendor DLL to configure the acquisition.

Sign in to comment.

Answers (0)

Categories

Find more on Loops and Conditional Statements in Help Center and File Exchange

Products

Release

R2021b

Asked:

on 10 Sep 2024

Edited:

on 13 Sep 2024

Community Treasure Hunt

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

Start Hunting!