I can't post my entire workaround for this, as it is many lines of code and that's goes a bit against the SO regulations. I will, however, try my best to explain how it works so that you can do something similar. My life while coding this was certainly made easier by the fact that I have a set of classes that allow you to write a Level-2 M-S function inside a class, but you could certainly do it without.
Basically your best bet is to create a mask for your block. Each time you modify the mask parameters, Simulink will call your onSetup
callback - where you would normally setup channels / register other M-S function callbacks. It is in this function that I create my DAQ session, if it's not already created, update the channel count, sample rate, and channel mode (and any other session info). You can then set the UserData
property of the block to either your DAQ session interface, or a struct or an instance of some handle class that has a reference to your DAQ session.
You will also need some way of queueing the incoming data. You could get a queue class off the FileExchange (I think there are a few), although I'm not sure about the speed of these. I created a queue class that uses a circular buffer as its data storage, as there is probably no need to go for a full implementation of a linked list here, and this is likely to be faster (no need to delete / instantiate a class every time a new sample comes in). The class errors if you try to pop an empty queue and warns with a 'queue overrun'-type error if you push onto a full queue. The queue is allocated / deallocated in, respectively, onPostPropagationSetup
and onTerminate
.
When data is received from the DAQ session, the following function is called
function onDaqDataAvailable(self, data)
self.sampleQueue.push(data.Data);
self.updateOutputStatus();
end
function updateOutputStatus(self)
if self.sampleQueue.filledLocs >= 0.5 * self.sampleQueue.queueLength
self.bOutput = true;
else
self.bOutput = false;
end
drawnow update;
end
I'm not sure whether the threshold there is actually necessary, and in fact think it might be a bit too high, but you can fiddle with it yourself. Then, in the onOutputs
callback, we have
function onOutputs(self, block)
if ~self.daqSess.IsRunning
self.daqSess.startBackground();
end
while ~self.bOutput
pause(0.1 * self.frameLength / self.sampleRate);
// Displaying number of samples in queue is useful for debugging here...
// But we should really use a scope instead
disp(self.sampleQueue.filledLocs)
self.updateOutputStatus();
end
// Displaying number of samples in queue is useful for debugging here...
// But we should really use a scope instead
//disp(self.sampleQueue.filledLocs)
samples = self.sampleQueue.pop(self.frameLength);
for nChan = 1:self.nChannels
block.OutputPort(nChan).Data = samples(:, nChan);
end
self.updateOutputStatus();
end
This is the basis for how the block actually works. One note is that you need to set the DAQ session's NotifyWhenDataAvailableExceeds
property to something sensible, so that you get a maximum of 20 calls to onDaqDataAvailable
per second, but once you have a sample queue this is fairly trivial. Note also that the DAQ session is not actually started until the model is REALLY ready. Starting it before hand would result in lots of queue overruns as scopes etc. opened their UIs.
I hope this has explained it enough for you to produce a comparable workaround.