MATLAB Answers

How to dynamically manipulate input variables during simulation from within Simulink M-code S-function?

21 views (last 30 days)
rotton
rotton on 4 Jul 2018
Edited: rotton on 9 Jul 2018

In Simulink, I am trying to setup the following problem: An ODE which right-hand side is depending on a discrete input value. (Eventually, this should also work as part of a bigger system.) You can recreate the Simulink sheet with the following code:

% Script to demonstrate a feedback problem in Simulink
fname = 'simpleModelWithFeedback';
outputTime = 0:1:12;
new_system(fname);
add_block('simulink/Sources/From Workspace', [gcs, '/input'],...
    'VariableName', 'inputArray', 'Interpolate', 'Off',...
    'OutputAfterFinalValue', 'Holding final value',...
    'Position', [50 47 175 93]);
add_block('simulink/User-Defined Functions/Level-2 MATLAB S-Function',...
    [gcs, '/myModel'], 'FunctionName', 'simpleAlgebraicModel',...
    'Position', [450 32 575 108]);
%'DialogParameters', {0.37, [0 1]}, read-only! Hence set manually
add_block('simulink/Sinks/Out1' , [gcs, '/results'], 'Position', [650 57 700 83]);
% Connect blocks
add_line(gcs, 'input/1', 'myModel/1');
add_line(gcs, 'myModel/1', 'results/1');
% Set solver parameters
set_param(gcs, 'OutputOption', 'SpecifiedOutputTimes', 'Solver', 'ode15s',...
    'MaxStep', '1e-1');
% Save created system to *.slx files
save_system(fname);
% Discrete input array
inputArray = [0:12; 0 0 1 1 1 1 0 0 1 1 0 0 0]';
%% Run and Plot results in Matlab
figure();
hold on; grid on;
stairs(inputArray(:,1), inputArray(:,2), 'DisplayName', 'Orignal schedule');
[~, ~, volume_1] = sim(fname, outputTime);
plot(outputTime, volume_1, '-^', 'DisplayName', 'Volume_1', 'MarkerSize', 4);
plot(outputTime, 0.15*ones(size(outputTime)), 'DisplayName', 'Lower limit Off');
stairs(inputArray(:,1), inputArray(:,2), '-.', 'DisplayName', 'Adapted schedule');
legend('Location', 'Best');
ylim([min(-0.1, min(volume_1)-0.1) 1.1]);
hold off;

Since 'DialogParameters' is read-only, manually paste the values 0.37, [0 1] into the Arguments field of the mask of myModel. The S-function needed is this:

function simpleAlgebraicModel(block)
%simpleAlgebraicModel Perform some simple calculations
    setup(block);
function setup(block)
    block.NumInputPorts = 1;
    block.NumOutputPorts = 1;
    block.NumDialogPrms = 2;    % Initial value, lower/upper limit
      % Setup functional port properties to dynamically inherited
      block.SetPreCompInpPortInfoToDynamic;
      block.SetPreCompOutPortInfoToDynamic;
      block.InputPort(1).Dimensions = 1;
  %     block.InputPort(2).Dimensions = 1;
      block.OutputPort(1).Dimensions = 1;
      block.DialogPrmsTunable = {'Nontunable', 'Nontunable'};
      block.SampleTimes = [0 0];
      block.NumContStates = 1;
      % Set the block simStateCompliance to default (i.e., same as a built-in block)
      block.SimStateCompliance = 'DefaultSimState';
      block.RegBlockMethod('SetInputPortSamplingMode', @SetInputPortSamplingMode);
      block.RegBlockMethod('InitializeConditions', @InitConditions);
      block.RegBlockMethod('Outputs', @Outputs);
      block.RegBlockMethod('Derivatives', @Derivative);
function SetInputPortSamplingMode(block, idx, fd)
    % Boilerplate code
    block.InputPort(idx).SamplingMode = fd;
    block.OutputPort(1).SamplingMode = fd;
function InitConditions(block)
    % Initialize Dwork
    block.ContStates.Data(1) = block.DialogPrm(1).Data;
function Outputs(block)
	block.OutputPort(1).Data = block.ContStates.Data(1);
function Derivative(block)
    productionRate = 5.5/24;    % Constant value instead of input port
	consumptionRate = 2*productionRate;
	onOff = block.InputPort(1).Data(1);
	dVolume = productionRate - onOff*consumptionRate;
	block.Derivatives.Data(1) = dVolume;
	% For debugging purposes
%     disp([block.CurrentTime, block.ContStates(1).Data, dVolume]);
    % Lower/upper limit
    lowerLimit = block.DialogPrm(2).Data(1);
    upperLimit = block.DialogPrm(2).Data(2);
    lowerLimitOff = 0.15*(upperLimit - lowerLimit);
      if block.OutputPort(1).Data <= lowerLimitOff && onOff
          warning('Lower limit Off reached at %.2f!', block.CurrentTime);
          inputArray = evalin('base', 'inputArray');
          % Overwrite 1s in current ON period with 0s
          currentIndex = find(inputArray(:,1) >= block.CurrentTime, 1, 'first');
          nextOffIndex = find(inputArray(currentIndex:end, 2) == 0, 1, 'first');
          lastOnIndex = nextOffIndex - 2;
          inputArray(currentIndex:currentIndex+lastOnIndex, 2) = 0;
          % Write modified net schedule back to base workspace
          assignin('base', 'inputArray', inputArray);
      end

Save it by its name in the same folder.

Now you can either go to Simulink, enable logging on line 1 and run the simulation (til time = 12) and inspect the results in the Simulation Data Inspector, or use the second part of the script. The result should look like this:

As you can see, the overwriting works, but not dynamically. Hence, the if statement in Derivatives still holds true between 5 <= t <= 7, although the corresponding value of volume_1 should now be on the rise.

What I am trying to achieve is that the S-function actually overwrites the 1s in the second column of inputArray with 0s dynamically, so that volume_1 never falls below the threshold (0.15 in this case). The result would be, for example:

inputArray = [0:12; 0 0 1 1 1 0 0 0 1 1 0 0 0]';

Any ideas how this can be accomplished with Simulink?

Answers (0)

Community Treasure Hunt

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

Start Hunting!