Contents

DWork Vector Examples

General DWork Vector

The S-function sfun_rtwdwork.csfun_rtwdwork.c shows how to configure a DWork vector for use with the Simulink® Coder™ product. The Simulink model sfcndemo_sfun_rtwdworksfcndemo_sfun_rtwdwork uses this S-function to implement a simple accumulator.

The following portion of the mdlInitializeSizes method initializes the DWork vector and all code generation properties associated with it.

ssSetNumDWork(S, 1);
ssSetDWorkWidth(S, 0, 1);
ssSetDWorkDataType(S, 0, SS_DOUBLE);

/* Identifier; free any old setting and update */
id = ssGetDWorkRTWIdentifier(S, 0);
if (id != NULL) {
    free(id);
}
id = malloc(80);
mxGetString(ID_PARAM(S), id, 80);
ssSetDWorkRTWIdentifier(S, 0, id);

/* Type Qualifier; free any old setting and update */
tq = ssGetDWorkRTWTypeQualifier(S, 0);
if (tq != NULL) {
    free(tq);
}
tq = malloc(80);
mxGetString(TQ_PARAM(S), tq, 80);
ssSetDWorkRTWTypeQualifier(S, 0, tq);

/* Storage class */
sc = ((int_T) *((real_T*) mxGetPr(SC_PARAM(S)))) - 1;
ssSetDWorkRTWStorageClass(S, 0, sc);

The S-function initializes the DWork vector in mdlInitializeConditions.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ============================
 * Abstract:
 *    Initialize both continuous states to zero
 */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *x = (real_T*) ssGetDWork(S,0);

    /* Initialize the dwork to 0 */
    x[0] = 0.0;
}

The mdlOutputs method assigns the DWork vector value to the S-function output.

/* Function: mdlOutputs ========================================
 * Abstract:
 *      y = x
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    real_T *y = ssGetOutputPortRealSignal(S,0);
    real_T *x = (real_T*) ssGetDWork(S,0);

    /* Return the current state as the output */
    y[0] = x[0];
}

The mdlUpdate method increments the DWork value by the input.

#define MDL_UPDATE
/* Function: mdlUpdate ============================================
 * Abstract:
 *    This function is called once for every major integration
 *    time step. Discrete states are typically updated here, but
 *    this function is useful for performing any tasks that should 
 *    only take place once per integration step.
 */
static void mdlUpdate(SimStruct *S, int_T tid)
{
    real_T *x = (real_T*) ssGetDWork(S,0);
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
        
    /*
     * Increment the state by the input 
     * U is defined as U(element) (*uPtrs[element])
     */
    x[0] += U(0);
}

DWork Scratch Vector

The following example uses a scratch DWork vector to store a static variable value. The mdlInitializeSizes method configures the width and data type of the DWork vector. The ssSetDWorkUsageType macro then specifies the DWork vector is a scratch vector.

ssSetNumDWork(S, 1);

ssSetDWorkWidth(S, 0, 1);
ssSetDWorkDataType(S, 0, SS_DOUBLE);
ssSetDWorkUsageType(S,0, SS_DWORK_USED_AS_SCRATCH);

The remainder of the S-function uses the scratch DWork vector exactly as it would any other type of DWork vector. The InitializeConditions method sets the initial value and the mdlOutputs method uses the value stored in the DWork vector.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ================================ */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *x = (real_T*) ssGetDWork(S,0);
    /* Initialize the dwork to 0 */
    x[0] = 0.0;
}
/* Function: mdlOutputs ============================================= */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    real_T *y = ssGetOutputPortRealSignal(S,0);
    real_T *x1 = (real_T*) ssGetDWork(S,1);

    x[0] = 2000;
    y[0] = x[0] * 2;
}

If you have Simulink Coder, the Simulink Coder software handles scratch DWork differently from other DWork vectors when generating code for inlined S-function. To inline the S-function, create the following Target Language Compiler (TLC) file to describe the mdlOutputs method.

%implements sfun_dscratch "C"

%% Function: Outputs ==========================================================
%%
/* dscratch Block: %<Name> */
%<LibBlockDWork(DWork[0], "", "", 0)> = 2000.0;
%<LibBlockOutputSignal(0,"","",0)> = %<LibBlockDWork(DWork[0],"","", 0)> * 2;

When the Simulink Coder software generates code for the model, it inlines the S-function and declares the second DWork vector as a local scratch vector. For example, the model outputs function contains the following lines:

/* local scratch DWork variables */
real_T SFunction_DWORK1;
SFunction_DWORK1 = 2000.0;

If the S-function used a general DWork vector instead of a scratch DWork vector, generating code with the same TLC file would have resulted in the DWork vector being included in the data structure, as follows:

sfcndemo_dscratch_DWork.SFunction_DWORK1 = 2000.0;

DState Work Vector

This example rewrites the S-function example dsfunc.cdsfunc.c to use a DState vector instead of an explicit discrete state vector. The mdlInitializeSizes macro initializes the number of discrete states as zero and, instead, initializes one DWork vector.

The mdlInitializeSizes method then configures the DWork vector as a DState vector using a call to ssSetDWorkUsedAsDState. This is equivalent to calling the ssSetDWorkUsageType macro with the value SS_DWORK_USED_AS_DSTATE. The mdlInitializeSizes method sets the width and data type of the DState vector and gives the state a name using ssSetDWorkName.

    Note   DWork vectors configured as DState vectors must be assigned a name for the Simulink engine to register the vector as discrete states. The function Simulink.BlockDiagram.getInitialStates(mdl) returns the assigned name in the label field for the initial states.

static void mdlInitializeSizes(SimStruct *S)
{
    ssSetNumSFcnParams(S, 0);  /* Number of expected parameters */
    if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
        return; /* Parameter mismatch reported by the Simulink engine */
    }

    ssSetNumContStates(S, 0);
    ssSetNumDiscStates(S, 0);

    if (!ssSetNumInputPorts(S, 1)) return;
    ssSetInputPortWidth(S, 0, 2);
    ssSetInputPortDirectFeedThrough(S, 0, 1);

    if (!ssSetNumOutputPorts(S, 1)) return;
    ssSetOutputPortWidth(S, 0, 2);

    ssSetNumSampleTimes(S, 1);
    ssSetNumRWork(S, 0);
    ssSetNumIWork(S, 0);
    ssSetNumPWork(S, 0);
    ssSetNumModes(S, 0);
    ssSetNumNonsampledZCs(S, 0);

    ssSetNumDWork(S, 1);
    ssSetDWorkUsedAsDState(S, 0, SS_DWORK_USED_AS_DSTATE);
    ssSetDWorkWidth(S, 0, 2);
    ssSetDWorkDataType(S, 0, SS_DOUBLE);
    ssSetDWorkName(S, 0, "SfunStates");

    ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}

The mdlInitializeConditions method initializes the DState vector values using the pointer returned by ssGetDWork.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ===============================
 * Abstract:
 *    Initialize both discrete states to one.
 */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *x0 = (real_T*) ssGetDWork(S, 0);
    int_T  lp;

    for (lp=0;lp<2;lp++) { 
        *x0++=1.0; 
    }
}

The mdlOutputs method then uses the values stored in the DState vector to compute the output of the discrete state-space equation.

/* Function: mdlOutputs ========================================
 * Abstract:
 *      y = Cx + Du 
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    real_T            *y    = ssGetOutputPortRealSignal(S,0);
    real_T            *x    = (real_T*) ssGetDWork(S, 0);
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
 
    UNUSED_ARG(tid); /* not used in single tasking mode */

    /* y=Cx+Du */
    y[0]=C[0][0]*x[0]+C[0][1]*x[1]+D[0][0]*U(0)+D[0][1]*U(1);
    y[1]=C[1][0]*x[0]+C[1][1]*x[1]+D[1][0]*U(0)+D[1][1]*U(1);
}

Finally, the mdlUpdate method updates the DState vector with new values for the discrete states.

#define MDL_UPDATE
/* Function: mdlUpdate ============================================
 * Abstract:
 *      xdot = Ax + Bu
 */
static void mdlUpdate(SimStruct *S, int_T tid)
{
    real_T            tempX[2] = {0.0, 0.0};
    real_T            *x       = (real_T*) ssGetDWork(S, 0);
    InputRealPtrsType uPtrs    = ssGetInputPortRealSignalPtrs(S,0);

    UNUSED_ARG(tid); /* not used in single tasking mode */

    /* xdot=Ax+Bu */
    tempX[0]=A[0][0]*x[0]+A[0][1]*x[1]+B[0][0]*U(0)+B[0][1]*U(1);
    tempX[1]=A[1][0]*x[0]+A[1][1]*x[1]+B[1][0]*U(0)+B[1][1]*U(1);
 
    x[0]=tempX[0];
    x[1]=tempX[1];
}

DWork Mode Vector

This example rewrites the S-function sfun_zc.csfun_zc.c to use a DWork mode vector instead of an explicit mode work vector (see Elementary Work Vectors for more information on mode work vectors). This S-function implements an absolute value block.

The mdlInitializeSizes method sets the number of DWork vectors and zero-crossing vectors (see Zero Crossings) to DYNAMICALLY_SIZED. The DYNAMICALLY_SIZED setting allows the Simulink engine to defer specifying the work vector sizes until it knows the dimensions of the input, allowing the S-function to support an input with an arbitrary width.

static void mdlInitializeSizes(SimStruct *S)
{
    ssSetNumSFcnParams(S, 0);
    if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
        return; /* Parameter mismatch reported by the Simulink engine */
    }

    ssSetNumContStates(    S, 0);
    ssSetNumDiscStates(    S, 0);

    if (!ssSetNumInputPorts(S, 1)) return;
    ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED);
    ssSetInputPortDirectFeedThrough(S, 0, 1);

    if (!ssSetNumOutputPorts(S,1)) return;
    ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);

    ssSetNumSampleTimes(S, 1);
    ssSetNumRWork(S, 0);
    ssSetNumIWork(S, 0);
    ssSetNumPWork(S, 0);
    ssSetNumDWork(S, 1);
    ssSetNumModes(S, 0);
    
    /* Initializes the zero-crossing and DWork vectors */
    ssSetDWorkWidth(S,0,DYNAMICALLY_SIZED);
    ssSetNumNonsampledZCs(S, DYNAMICALLY_SIZED);

    /* Take care when specifying exception free code - see sfuntmpl_doc.c */
    ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}

The Simulink engine initializes the number of zero-crossing vectors and DWork vectors to the number of elements in the signal coming into the first S-function input port. The engine then calls the mdlSetWorkWidths method, which uses ssGetNumDWork to determine how many DWork vectors were initialized and then sets the properties for each DWork vector.

#define MDL_SET_WORK_WIDTHS
static void mdlSetWorkWidths(SimStruct *S) {
    int_T numdw = ssGetNumDWork(S);
    int_T i;
    
    for (i = 0; i < numdw; i++) {
        ssSetDWorkUsageType(S, i, SS_DWORK_USED_AS_MODE);
        ssSetDWorkDataType(S, i, SS_BOOLEAN);
        ssSetDWorkComplexSignal(S, i, COMPLEX_NO);
    }
}

The mdlOutputs method uses the value stored in the DWork mode vector to determine if the output signal should be equal to the input signal or the absolute value of the input signal.

static void mdlOutputs(SimStruct *S, int_T tid)
{
    int_T             i;
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
    real_T            *y    = ssGetOutputPortRealSignal(S,0);
    int_T             width = ssGetOutputPortWidth(S,0);
    boolean_T         *mode = ssGetDWork(S,0);

    UNUSED_ARG(tid); /* not used in single tasking mode */

    if (ssIsMajorTimeStep(S)) {
        for (i = 0; i < width; i++) {
            mode[i] = (boolean_T)(*uPtrs[i] >= 0.0);
        }
    }

    for (i = 0; i < width; i++) {
        y[i] = mode[i]? (*uPtrs[i]): -(*uPtrs[i]);
    }
}

Level-2 MATLAB S-Function DWork Vector

The example S-function msfcn_varpulse.mmsfcn_varpulse.m models a variable width pulse generator. The S-function uses two DWork vectors. The first DWork vector stores the pulse width value, which is modified at every major time step in the Update method. The second DWork vector stores the handle of the pulse generator block in the Simulink model. The value of this DWork vector does not change over the course of the simulation.

The PostPropagationSetup method, called DoPostPropSetup in this S-function, sets up the two DWork vectors.

function DoPostPropSetup(block)

% Initialize the Dwork vector
block.NumDworks = 2;

% Dwork(1) stores the value of the next pulse width
block.Dwork(1).Name            = 'x1';
block.Dwork(1).Dimensions      = 1;
block.Dwork(1).DatatypeID      = 0;      % double
block.Dwork(1).Complexity      = 'Real'; % real
block.Dwork(1).UsedAsDiscState = true;

% Dwork(2) stores the handle of the Pulse Geneator block
block.Dwork(2).Name            = 'BlockHandle';
block.Dwork(2).Dimensions      = 1;
block.Dwork(2).DatatypeID      = 0;      % double
block.Dwork(2).Complexity      = 'Real'; % real
block.Dwork(2).UsedAsDiscState = false;

The Start method initializes the DWork vector values.

function Start(block)

% Populate the Dwork vector
block.Dwork(1).Data = 0;

% Obtain the Pulse Generator block handle
pulseGen = find_system(gcs,'BlockType','DiscretePulseGenerator');
blockH = get_param(pulseGen{1},'Handle');
block.Dwork(2).Data = blockH;

The Outputs method uses the handle stored in the second DWork vector to update the pulse width of the Pulse Generator block.

function Outputs(block)

% Update the pulse width value
set_param(block.Dwork(2).Data, 'PulseWidth', num2str(block.InputPort(1).data));

The Update method then modifies the first DWork vector with the next value for the pulse width, specified by the input signal to the S-Function block.

function Update(block)

% Store the input value in the Dwork(1)
block.Dwork(1).Data = block.InputPort(1).Data;

%endfunction
Was this topic helpful?