Search code examples
cmatlabsimulinks-function

Simulink: How to use a local variable in a level 2 s-function?


I have written a Simulink S-function (Level 2) in C. The resulting block has one output and one parameter. This parameter is stored in a variable, which is defined at file scope, right after setting up the block:

#define NUM_PARAMS 1
#define NUM_INPORTS 0
#define NUM_OUTPORTS 1

unsigned short int MASK_INDEX;

I assign it within mdlInitializeSizes, and do some operations on its value:

static void mdlInitializeSizes(SimStruct *S) {  
    // Check Parameters
    ssSetNumSFcnParams(S, NUM_PARAMS);
    if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
            return;
    }

    MASK_INDEX = *mxGetPr(ssGetSFcnParam(S, 0));

    (...) operations
}

My problem is, that the variable MASK_INDEX seems to be global, and shared among all blocks of the same type. Therefore, it holds the same value for all blocks.

As a workaround, I reload it every time, and re-do the operations, for example:

static void mdlOutputs(SimStruct *S, int_T tid) {
    MASK_INDEX = *mxGetPr(ssGetSFcnParam(S, 0));

    (...) operations
}

How can I get a true "local variable", so that I don't have to repeat all this every time?


Solution

  • You haven't mentioned where you've declared MASK_INDEX, but from your description it sounds like it's at file scope. If so, then yes, this variable will be shared across all instances. This is not isolated to S-Functions in any way, it's how shared libraries on most, if not all, platforms behave. A single instance of the shared library will be loaded by an application, in this case MATLAB; consequently there is only one copy of global variables.

    The easiest option is to use ssGetSFcnParam every time you want to access the parameter. If you dig into those S-Function macros, they're simply accessing fields of the SimStruct, so it's unlikely repeated access will result in performance degradation. I've even seen macros being used to wrap common use cases such as the one you have.

    If you really want to go about caching the dialog parameter, the easiest is probably to use ssSetUserData. Declare a struct containing a MASK_INDEX member (you don't have to use a struct but this approach is more extensible). Dynamically allocate an instance using mxMalloc within mdlStart and assign it to the block's user data. Make sure you set SS_OPTION_CALL_TERMINATE_ON_EXIT in the ssSetOptions call in mdlInitializeSizes. Then define the mdlTerminate function within which you'll access the allocated struct using ssGetUserData and mxFree it. Now you can access the struct members within mdlOutputs using ssGetUserData.

    There are other, more advanced options as well, such as work vectors, probably a PWork vector.

    Another option, if your parameter is tunable, is using runtime parameters, which let you cache, and optionally transform, a block's dialog parameters.

    In your case, I'd just stick with using ssGetSFcnParam every time within mdlOutputs.