MATLAB Answers

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

19 ビュー (過去 30 日間)
rotton
rotton 2018 年 7 月 4 日
編集済み: rotton 2018 年 7 月 9 日

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?

回答 (0 件)

製品


リリース

R2017b

Community Treasure Hunt

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

Start Hunting!

Translated by