Writing S-Functions | ![]() ![]() |
The Direct-Index Lookup Table Example
This section shows how to improve the lookup table by inlining a direct-index S-function with a TLC file. Note that this direct-index lookup table S-function doesn't require a TLC file to work with the Real-Time Workshop. Here the example uses a TLC file for the direct-index lookup table S-function to reduce the code size and increase efficiency of the generated code.
Implementation of the direct-index algorithm with inlined TLC file requires the S-function main module, sfun_directlook.c
(see page 8-28), and a corresponding lookup_index.c
module (see page 8-37). The lookup_index.c
module contains the GetDirectLookupIndex
routine that is used to locate the index in the XData
for the current x
input value when the XData
is unevenly spaced. The GetDirectLookupIndex
routine is called from both the S-function and the generated code. Here the example uses the wrapper concept for sharing C code between Simulink MEX-files and the generated code.
If the XData
is evenly spaced, then both the S-function main module and the generated code contain the lookup algorithm (not a call to the algorithm) to compute the y-value of a given x-value, because the algorithm is short. This demonstrates the use of a fully inlined S-function for generating optimal code.
The inlined TLC file, which performs either a wrapper call or embeds the optimal C code, is sfun_directlook.tlc
(see page 8-39).
Error Handling
In this example, the mdlCheckParameters
routine on page 8-31 verifies that:
XData
and YData
are vectors of the same length containing real finite numbers.
XDataEvenlySpaced
is a scalar.
XData
vector is a monotonically increasing vector and evenly spaced if needed.
Note that the mdlInitializeSizes
routine explicitly calls mdlCheckParameters
after it verifies that the number of parameters passed to the S-function is correct. After Simulink calls mdlInitializeSizes
, it then calls mdlCheckParameters
whenever you change the parameters or there is a need to reevaluate them.
User Data Caching
The mdlStart
routine on page 8-34 illustrates how to cache information that does not change during the simulation (or while the generated code is executing). The example caches the value of the XDataEvenlySpaced
parameter in UserData
, a field of the SimStruct. The line
in mdlInitializeSizes
tells Simulink to disallow changes to the XDataEvenlySpaced
parameter. During execution, mdlOutputs
accesses the value of XDataEvenlySpaced
from UserData
rather than calling the mxGetPr
MATLAB API function. This results in a slight increase in performance.
mdlRTW Usage
The Real-Time Workshop calls the mdlRTW
routine while it (the Real-Time Workshop) generates the model
.rtw
file. You can add information to the model
.rtw
file about the mode in which your S-Function block is operating to produce optimal code for your Simulink model.
This example adds the following information to the model
.rtw
file:
XData
and YData
S-function parameters can change during execution and are written using the function ssWriteRTWParameters
.
XDataEvenlySpaced
S-function parameter cannot change during execution (ssSetSFcnParamTunable
was specified as false (0
) for it in mdlInitializeSizes
). This example writes it out as a parameter setting (XSpacing
) using the function ssWriteRTWParamSettings
.
Example Model
Before examining the S-function and the inlined TLC file, consider the generated code for the following model.
When creating this model, you need to specify the following for each S-Function block.
set_param(`sfun_directlook_ex/S-Function','SFunctionModules','lookup_index') set_param(`sfun_directlook_ex/S-Function1','SFunctionModules','lookup_index')
This informs the Real-Time Workshop build process that the module lookup_index.c
is needed when creating the executable.
The generated code for the lookup table example model is
<Generated header for sfun_directlook_ex model> #include <math.h> #include <string.h> #include "sfun_directlook_ex.h" #include "sfun_directlook_ex.prm" /* Start the model */ void mdlStart(void) { /* (no start code required) */ } /* Compute block outputs */ void mdlOutputs(int_T tid) { /* local block i/o variables */ real_T rtb_Sine_Wave; real_T rtb_buffer2; /* Sin Block: <Root>/Sine Wave */ rtb_Sine_Wave = rtP.Sine_Wave.Amplitude * sin(rtP.Sine_Wave.Frequency * ssGetT(rtS) + rtP.Sine_Wave.Phase); /* S-Function Block: <Root>/S-Function */ { real_T *xData = &rtP.S_Function.XData[0]; real_T *yData = &rtP.S_Function.YData[0]; real_T spacing = xData[1] - xData[0];if ( rtb_Sine_Wave <= xData[0] ) { rtb_buffer2 = yData[0]; } else if ( rtb_Sine_Wave >= yData[20] ) { rtb_buffer2 = yData[20]; } else { int_T idx = (int_T)( ( rtb_Sine_Wave - xData[0] ) / spacing ); rtb_buffer2 = yData[idx]; } } /* Outport Block: <Root>/Out1 */ rtY.Out1 = rtb_buffer2; /* S-Function Block: <Root>/S-Function1 */ { real_T *xData = &rtP.S_Function1.XData[0]; real_T *yData = &rtP.S_Function1.YData[0]; int_T idx;
idx = GetDirectLookupIndex(xData, 5, rtb_Sine_Wave); rtb_buffer2 = yData[idx]; } /* Outport Block: <Root>/Out2 */ rtY.Out2 = rtb_buffer2; } /* Perform model update */ void mdlUpdate(int_T tid) { /* (no update code required) */ } /* Terminate function */ void mdlTerminate(void) { /* (no terminate code required) */ } #include "sfun_directlook_ex.reg" /* [EOF] sfun_directlook_ex.c */
matlabroot/simulink/src/sfun_directlook.c
/* * File : sfun_directlook.c * Abstract: * * Direct 1-D lookup. Here we are trying to compute an approximate * solution p(x) to an unknown function f(x) at x=x0, given data point * pairs (x,y) in the form of an x data vector and a y data vector. For a * given data pair (say the ith pair), we have y_i = f(x_i). It is * assumed that the x data values are monotonically increasing. If the * x0 is outside of the range of the x data vector, then the first or * last point will be returned. * * This function returns the "nearest" y0 point for a given x0. No * interpolation is performed. * * The S-function parameters are: * XData - double vector * YData - double vector * XDataEvenlySpacing - double scalar 0 (false) or 1 (true) * The third parameter cannot be changed during simulation. * * To build: * mex sfun_directlook.c lookup_index.c * * Copyright (c) 1990-1998 by The MathWorks, Inc. All Rights Reserved. * */ #define S_FUNCTION_NAME sfun_directlook #define S_FUNCTION_LEVEL 2 #include <math.h> #include "simstruc.h" #include <float.h> /*=========* * Defines * *=========*/ #define XVECT_PIDX 0 #define YVECT_PIDX 1 #define XDATAEVENLYSPACED_PIDX 2 #define NUM_PARAMS 3 #define XVECT(S) ssGetSFcnParam(S,XVECT_PIDX) #define YVECT(S) ssGetSFcnParam(S,YVECT_PIDX) #define XDATAEVENLYSPACED(S) ssGetSFcnParam(S,XDATAEVENLYSPACED_PIDX) /*==============* * misc defines * *==============*/ #if !defined(TRUE) #define TRUE 1 #endif #if !defined(FALSE) #define FALSE 0 #endif /*===========* * typedef's * *===========*/ typedef struct SFcnCache_tag { boolean_T evenlySpaced; } SFcnCache; /*===================================================================* * Prototype define for the function in separate file lookup_index.c * *===================================================================*/ extern int_T GetDirectLookupIndex(const real_T *x, int_T xlen, real_T u); /*=========================* * Local Utility Functions * *=========================*/ /* Function: IsRealVect ======================================================== * Abstract: * Verify that the mxArray is a real vector. */ static boolean_T IsRealVect(const mxArray *m) { if (mxIsNumeric(m) && mxIsDouble(m) && !mxIsLogical(m) && !mxIsComplex(m) && !mxIsSparse(m) && !mxIsEmpty(m) && mxGetNumberOfDimensions(m) == 2 && (mxGetM(m) == 1 || mxGetN(m) == 1)) { real_T *data = mxGetPr(m); int_T numEl = mxGetNumberOfElements(m); int_T i; for (i = 0; i < numEl; i++) { if (!mxIsFinite(data[i])) { return(FALSE); } } return(TRUE); } else { return(FALSE); } } /* end IsRealVect */ /*====================* * S-function routines * *====================*/ #define MDL_CHECK_PARAMETERS /* Change to #undef to remove function */ #if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE) /* Function: mdlCheckParameters ================================================ * Abstract: * This routine will be called after mdlInitializeSizes, whenever * parameters change or get reevaluated. The purpose of this routine is * to verify that the new parameter settings are correct. * * You should add a call to this routine from mdlInitializeSizes * to check the parameters. After setting your sizes elements, you should: * if (ssGetSFcnParamsCount(S) == ssGetNumSFcnParams(S)) { * mdlCheckParameters(S); * } */ static void mdlCheckParameters(SimStruct *S) { if (!IsRealVect(XVECT(S))) { ssSetErrorStatus(S,"1st, X-vector parameter must be a real finite vector"); return; } if (!IsRealVect(YVECT(S))) { ssSetErrorStatus(S,"2nd, Y-vector parameter must be a real finite " "vector"); return; } /* * Verify that the dimensions of X and Y are the same. */ if (mxGetNumberOfElements(XVECT(S)) != mxGetNumberOfElements(YVECT(S)) || mxGetNumberOfElements(XVECT(S)) == 1) { ssSetErrorStatus(S,"X and Y-vectors must be of the same dimension " "and have at least two elements"); return; } /* * Verify we have a valid XDataEvenlySpaced parameter. */ if (!mxIsNumeric(XDATAEVENLYSPACED(S)) || !(mxIsDouble(XDATAEVENLYSPACED(S)) || mxIsLogical(XDATAEVENLYSPACED(S))) || mxIsComplex(XDATAEVENLYSPACED(S)) || mxGetNumberOfElements(XDATAEVENLYSPACED(S)) != 1) { ssSetErrorStatus(S,"3rd, X-evenly-spaced parameter must be scalar " "(0.0=false, 1.0=true)"); return; } /* * Verify x-data is correctly spaced. */ { int_T i; boolean_T spacingEqual; real_T *xData = mxGetPr(XVECT(S)); int_T numEl = mxGetNumberOfElements(XVECT(S)); /* * spacingEqual is TRUE if user XDataEvenlySpaced */ spacingEqual = (mxGetScalar(XDATAEVENLYSPACED(S)) != 0.0); if (spacingEqual) { /* XData is 'evenly-spaced' */ boolean_T badSpacing = FALSE; real_T spacing = xData[1] - xData[0]; real_T space; if (spacing <= 0.0) { badSpacing = TRUE; } else { real_T eps = DBL_EPSILON; for (i = 2; i < numEl; i++) { space = xData[i] - xData[i-1]; if (space <= 0.0 || fabs(space-spacing) >= 128.0*eps*spacing ){ badSpacing = TRUE; break; } } } if (badSpacing) { ssSetErrorStatus(S,"X-vector must be an evenly spaced " "strictly monotonically increasing vector"); return; } } else { /* XData is 'unevenly-spaced' */ for (i = 1; i < numEl; i++) { if (xData[i] <= xData[i-1]) { ssSetErrorStatus(S,"X-vector must be a strictly " "monotonically increasing vector"); return; } } } } } #endif /* MDL_CHECK_PARAMETERS */ /* Function: mdlInitializeSizes ================================================ * Abstract: * The sizes information is used by Simulink to determine the S-function * block's characteristics (number of inputs, outputs, states, etc.). */ static void mdlInitializeSizes(SimStruct *S) { ssSetNumSFcnParams(S, NUM_PARAMS); /* Number of expected parameters */ /* * Check parameters passed in, providing the correct number was specified * in the S-function dialog box. If an incorrect number of parameters * was specified, Simulink will detect the error since ssGetNumSFcnParams * and ssGetSFcnParamsCount will differ. * ssGetNumSFcnParams - This sets the number of parameters your * S-function expects. * ssGetSFcnParamsCount - This is the number of parameters entered by * the user in the Simulink S-function dialog box. */ #if defined(MATLAB_MEX_FILE) if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) { mdlCheckParameters(S); if (ssGetErrorStatus(S) != NULL) { return; } } else { return; /* Parameter mismatch will be reported by Simulink */ } #endif ssSetNumContStates(S, 0); ssSetNumDiscStates(S, 0); if (!ssSetNumInputPorts(S, 1)) return; ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED); ssSetInputPortDirectFeedThrough(S, 0, 1); ssSetInputPortTestPoint(S, 0, FALSE); ssSetInputPortOverWritable(S, 0, TRUE); if (!ssSetNumOutputPorts(S, 1)) return; ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED); ssSetOutputPortTestPoint(S, 0, FALSE); ssSetNumSampleTimes(S, 1); ssSetSFcnParamTunable(S, XDATAEVENLYSPACED_PIDX, 0); ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE); } /* mdlInitializeSizes */ /* Function: mdlInitializeSampleTimes ========================================== * Abstract: * The lookup inherits its sample time from the driving block. */ static void mdlInitializeSampleTimes(SimStruct *S) { ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0); } /* end mdlInitializeSampleTimes */ #define MDL_START /* Change to #undef to remove function */ #if defined(MDL_START) /* Function: mdlStart ========================================================== * Abstract: * Here we cache the state (true/false) of the XDATAEVENLYSPACED parameter. * We do this primarily to illustrate how to "cache" parameter values (or * information that is computed from parameter values) that do not change * for the duration of the simulation (or in the generated code). In this * case, rather than repeated calls to mxGetPr, we save the state once. * This results in a slight increase in performance. */ static void mdlStart(SimStruct *S) { SFcnCache *cache = malloc(sizeof(SFcnCache)); if (cache == NULL) { ssSetErrorStatus(S,"memory allocation error"); return; } ssSetUserData(S, cache); if (mxGetScalar(XDATAEVENLYSPACED(S)) != 0.0){ cache->evenlySpaced = TRUE; }else{ cache->evenlySpaced = FALSE; } } #endif /* MDL_START */ /* Function: mdlOutputs ======================================================== * Abstract: * In this function, we compute the outputs of our S-function * block. Generally outputs are placed in the output vector, ssGetY(S). */ static void mdlOutputs(SimStruct *S, int_T tid) { SFcnCache *cache = ssGetUserData(S); real_T *xData = mxGetPr(XVECT(S)); real_T *yData = mxGetPr(YVECT(S)); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); real_T *y = ssGetOutputPortRealSignal(S,0); int_T ny = ssGetOutputPortWidth(S,0); int_T xLen = mxGetNumberOfElements(XVECT(S)); int_T i; /* * When the XData is evenly spaced, we use the direct lookup algorithm * to calculate the lookup */ if (cache->evenlySpaced) { real_T spacing = xData[1] - xData[0]; for (i = 0; i < ny; i++) { real_T u = *uPtrs[i]; if (u <= xData[0]) { y[i] = yData[0]; } else if (u >= xData[xLen-1]) { y[i] = yData[xLen-1]; } else { int_T idx = (int_T)((u - xData[0])/spacing); y[i] = yData[idx]; } } } else { /* * When the XData is unevenly spaced, we use a bisection search to * locate the lookup index. */ for (i = 0; i < ny; i++) { int_T idx = GetDirectLookupIndex(xData,xLen,*uPtrs[i]); y[i] = yData[idx]; } } } /* end mdlOutputs */ /* Function: mdlTerminate ====================================================== * Abstract: * Free the cache that was allocated in mdlStart. */ static void mdlTerminate(SimStruct *S) { SFcnCache *cache = ssGetUserData(S); if (cache != NULL) { free(cache); } } /* end mdlTerminate */ #define MDL_RTW /* Change to #undef to remove function */ #if defined(MDL_RTW) && (defined(MATLAB_MEX_FILE) || defined(NRT)) /* Function: mdlRTW ============================================================ * Abstract: * This function is called when the Real-Time Workshop is generating the * model.rtw file. In this routine, you can call the following functions * which add fields to the model.rtw file. * * Important! Since this S-function has this mdlRTW routine, it must have * a corresponding .tlc file to work with the Real-Time Workshop. You will find * the sfun_directlook.tlc in the same directory as sfun_directlook.dll. */ static void mdlRTW(SimStruct *S) { /* * Write out the [X,Y] data as parameters, i.e., these values can be * changed during execution. */ { real_T *xData = mxGetPr(XVECT(S)); int_T xLen = mxGetNumberOfElements(XVECT(S)); real_T *yData = mxGetPr(YVECT(S)); int_T yLen = mxGetNumberOfElements(YVECT(S)); if (!ssWriteRTWParameters(S,2, SSWRITE_VALUE_VECT,"XData","",xData,xLen, SSWRITE_VALUE_VECT,"YData","",yData,yLen)) { return; /* An error occurred which will be reported by Simulink */ } } /* * Write out the spacing setting as a param setting, i.e., this cannot be * changed during execution. */ { boolean_T even = (mxGetScalar(XDATAEVENLYSPACED(S)) != 0.0); if (!ssWriteRTWParamSettings(S, 1, SSWRITE_VALUE_QSTR, "XSpacing", even ? "EvenlySpaced" : "UnEvenlySpaced")){ return;/* An error occurred which will be reported by Simulink */ } } } #endif /* MDL_RTW */ /*=============================* * Required S-function trailer * *=============================*/ #ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */ #include "simulink.c" /* MEX-file interface mechanism */ #else #include "cg_sfun.h" /* Code generation registration function */ #endif /* [EOF] sfun_directlook.c */
matlabroot/simulink/src/lookup_index.c
/* File : lookup_index.c * Abstract: * * Contains a routine used by the S-function sfun_directlookup.c to * compute the index in a vector for a given data value. * * Copyright (c) 1990-1998 by The MathWorks, Inc. All Rights Reserved. * */ #include "tmwtypes.h" /* * Function: GetDirectLookupIndex ============================================== * Abstract: * Using a bisection search to locate the lookup index when the x-vector * isn't evenly spaced. * * Inputs: * *x : Pointer to table, x[0] ....x[xlen-1] * xlen : Number of values in xtable * u : input value to look up * * Output: * idx : the index into the table such that: * if u is negative * x[idx] <= u < x[idx+1] * else * x[idx] < u <= x[idx+1] */ int_T GetDirectLookupIndex(const real_T *x, int_T xlen, real_T u) { int_T idx = 0; int_T bottom = 0; int_T top = xlen-1; /* * Deal with the extreme cases first: * * i] u <= x[bottom] then idx = bottom * ii] u >= x[top] then idx = top-1 * */ if (u <= x[bottom]) { return(bottom); } else if (u >= x[top]) { return(top); } /* * We have: x[bottom] < u < x[top], onward * with search for the appropriate index ... */ for (;;) { idx = (bottom + top)/2; if (u < x[idx]) { top = idx; } else if (u > x[idx+1]) { bottom = idx + 1; } else { /* * We have: x[idx] <= u <= x[idx+1], only need * to do two more checks and we have the answer. */ if (u < 0) { /* * We want right continuity, i.e., * if u == x[idx+1] * then x[idx+1] <= u < x[idx+2] * else x[idx ] <= u < x[idx+1] */ return( (u == x[idx+1]) ? (idx+1) : idx); } else { /* * We want left continuity, i.e., * if u == x[idx] * then x[idx-1] < u <= x[idx ] * else x[idx ] < u <= x[idx+1] */ return( (u == x[idx]) ? (idx-1) : idx); } } } } /* end GetDirectLookupIndex */ /* [EOF] lookup_index.c */
matlabroot/toolbox/simulink/blocks/tlc_c/sfun_directlook.tlc
%% File : sfun_directlook.tlc %% Abstract: %% Level-2 S-function sfun_directlook block target file. %% It is using direct lookup algorithm without interpolation. %% %% Copyright (c) 1994-1998 by The MathWorks, Inc. All Rights Reserved. %% %implements "sfun_directlook" "C" %% Function: BlockTypeSetup ==================================================== %% Abstract: %% Place include and function prototype in the model's header file. %% %function BlockTypeSetup(block, system) void %% Add this external function's prototype in the header of the generated %% file. %% %openfile buffer extern int_T GetDirectLookupIndex(const real_T *x, int_T xlen, real_T u); %closefile buffer %<LibCacheFunctionPrototype(buffer)> %endfunction %% Function: mdlOutputs ======================================================== %% Abstract: %% Direct 1-D lookup table S-function example. %% Here we are trying to compute an approximate solution p(x) to an %% unknown function f(x) at x=x0, given data point pairs (x,y) in the %% form of an x-data vector and a y-data vector. For a given data pair %% (say the ith pair), we have y_i = f(x_i). It is assumed that the x %% data values are monotonically increasing. If the first or last x is %% outside of the range of the x data vector, then the first or last %% point will be returned. %% %% This function returns the "nearest" y0 point for a given x0. %% No interpolation is performed. %% %% The S-function parameters are: %% XData %% YData %% XEvenlySpaced: 0 or 1 %% The third parameter cannot be changed during execution and is %% written to the model.rtw file in XSpacing field of the SFcnParamSettings %% record as "EvenlySpaced" or "UnEvenlySpaced". The first two parameters %% can change during execution and show up in the parameter vector. %% %function Outputs(block, system) Output /* %<Type> Block: %<Name> */ { %assign rollVars = ["U", "Y"] %% %% Load XData and YData as local variables %% real_T *xData = %<LibBlockParameterAddr(XData, "", "", 0)>; real_T *yData = %<LibBlockParameterAddr(YData, "", "", 0)>; %assign xDataLen = SIZE(XData.Value, 1) %% %% When the XData is evenly spaced, we use the direct lookup algorithm %% to locate the lookup index. %% %if SFcnParamSettings.XSpacing == "EvenlySpaced" real_T spacing = xData[1] - xData[0]; %roll idx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars %assign u = LibBlockInputSignal(0, "", lcv, idx) %assign y = LibBlockOutputSignal(0, "", lcv, idx) if ( %<u> <= xData[0] ) { %<y> = yData[0]; } else if ( %<u> >= yData[%<xDataLen-1>] ) { %<y> = yData[%<xDataLen-1>]; } else { int_T idx = (int_T)( ( %<u> - xData[0] ) / spacing ); %<y> = yData[idx]; } %% %% Generate an empty line if we are not rolling, %% so that it looks nice in the generated code. %% %if lcv == "" %endif %endroll %else %% When the XData is unevenly spaced, we use a bisection search to %% locate the lookup index. int_T idx; %assign xDataAddr = LibBlockParameterAddr(XData, "", "", 0) %roll idx = RollRegions, lcv = RollThreshold, block, "Roller", rollVars %assign u = LibBlockInputSignal(0, "", lcv, idx) idx = GetDirectLookupIndex(xData, %<xDataLen>, %<u>); %assign y = LibBlockOutputSignal(0, "", lcv, idx) %<y> = yData[idx]; %% %% Generate an empty line if we are not rolling, %% so that it looks nice in the generated code. %% %if lcv == "" %endif %endroll %endif } %endfunction %% EOF: sfun_directlook.tlc
![]() | The Direct-Index Lookup Table Algorithm | Creating Code-Reuse-Compatible S-Functions | ![]() |