C примеры S-функции MEX

О примерах S-функции

Все примеры основаны на S-шаблонах-функций MEX C sfuntmpl_basic.c и sfuntmpl_doc.c. Открытый sfuntmpl_doc.c. для детального обсуждения S-шаблона-функции.

Непрерывные состояния

Пример csfunc.c показывает, как смоделировать непрерывную систему с состояниями с помощью S-функции MEX C. Следующая модель Simulink® использует эту S-функцию.

sfcndemo_csfunc

В непрерывном интегрировании состояния решатели Simulink интегрируют набор непрерывных состояний с помощью следующих уравнений.

S-функции, которые содержат непрерывные состояния, реализуют уравнение пространства состояний. Метод mdlOutputs содержит выходной фрагмент, и метод mdlDerivatives содержит производный фрагмент уравнения пространства состояний. Чтобы визуализировать, как интегрирование работает, см. блок-схему во Взаимодействии Engine Simulink с S-функциями C. Выходное уравнение соответствует mdlOutputs в главном временном шаге. Затем, пример вводит раздел интегрирования блок-схемы. Здесь механизм Simulink выполняет много незначительных временных шагов, во время которых он вызывает mdlOutputs и mdlDerivatives. Каждая из этих пар вызовов упоминается как этап интегрирования. Интегрирование возвращается с непрерывными обновленными состояниями, и время симуляции продвинулось. Время продвинуто в максимально возможной степени, если той ошибке допуски в состоянии соответствуют. Максимальный временной шаг подвергается ограничениям дискретных событий, таким как фактическое время остановки симуляции и наложенный пользователями предел.

Пример csfunc.c указывает, что входной порт имеет прямое сквозное соединение. Это вызвано тем, что матричный D инициализируется к ненулевой матрице. Если D установлен равный нулевой матрице в представлении пространства состояний, входной сигнал не используется в mdlOutputs. В этом случае прямое сквозное соединение может быть установлено в 0, который указывает, что csfunc.c не требует входного сигнала при выполнении mdlOutputs.

matlabroot/toolbox/simulink/simdemos/simfeatures/src/csfunc.c

S-функция csfunc.c начинается с операторов #define для S-имени-функции и уровня, и оператора #include для заголовка simstruc.h. После этих операторов S-функция может включать или задать любые другие необходимые заголовки, данные, и т.д. пример csfunc.c задает переменную U как указатель на сигнал первого входного порта и инициализирует статические переменные для матриц пространства состояний.

/*  File    : csfunc.c
 *  Abstract:
 *
 *      Example C S-function for defining a continuous system.  
 *
 *      x' = Ax + Bu
 *      y  = Cx + Du
 *
 *      For more details about S-functions, see simulink/src/sfuntmpl_doc.c.
 * 
 *  Copyright 1990-2013 The MathWorks, Inc.
 */

#define S_FUNCTION_NAME csfunc
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (*uPtrs[element])  /* Pointer to Input Port0 */

static real_T A[2][2]={ { -0.09, -0.01 } ,
                        {  1   ,  0    } 
                      };

static real_T B[2][2]={ {  1   , -7    } ,
                        {  0   , -2    } 
                      };

static real_T C[2][2]={ {  0   , 2     } ,
                        {  1   , -5    } 
                      };

static real_T D[2][2]={ { -3   , 0     } ,
                        {  1   , 0     } 
                      };

Необходимый метод S-функции mdlInitializeSizes затем настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции, чтобы обнулить.

  • ssGetSFcnParamsCount определяет, сколько параметров пользователь на самом деле ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров не совпадает с номером, возвращенным ssGetNumSFcnParams, ошибки S-функции.

  • Если количество S-параметра-функции передает, mdlInitializeSizes определяет номер непрерывных и дискретных состояний с помощью ssSetNumContStates и ssSetNumDiscStates, соответственно. Этот пример имеет два непрерывных состояния и нулевые дискретные состояния.

  • Затем, метод конфигурирует S-функцию, чтобы иметь один порт ввода и вывода, каждого с шириной два, чтобы совпадать с размерностями матриц пространства состояний. Метод передает значение 1 к ssSetInputPortDirectFeedThrough, чтобы указать, что входной порт имеет прямое сквозное соединение.

  • ssSetNumSampleTimes инициализирует один шаг расчета, который функция mdlInitializeSampleTimes конфигурирует позже.

  • S-функция указывает, что никакие не работают, векторы используются путем передачи значения 0 к ssSetNumRWork, ssSetNumIWork, и т.д. Можно не использовать эти строки, потому что нуль является значением по умолчанию для всех них макросы. Однако для ясности, S-функция явным образом определяет номер векторов работы.

  • Наконец, ssSetOptions устанавливает любые применимые опции. В этом случае единственной опцией является SS_OPTION_EXCEPTION_FREE_CODE, который предусматривает, что код является свободным исключением.

Функцию mdlInitializeSizes для этого примера показывают ниже.

/*====================*
 * S-function methods *
 *====================*/

/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    Determine the S-function block's characteristics:
 *    number of inputs, outputs, states, etc.
 */
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, 2);
    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);

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

Необходимый метод S-функции mdlInitializeSampleTimes задает частоты дискретизации S-функции. Значение, которое CONTINUOUS_SAMPLE_TIME передал макросу ssSetSampleTime, указывает, что первая частота дискретизации S-функции непрерывна. ssSetOffsetTime затем задает время смещения нуля для этой частоты дискретизации. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели.

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *    Specify that we have a continuous sample time.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
}

Дополнительный метод S-функции mdlInitializeConditions инициализирует непрерывный вектор состояния. Оператор #define перед этим методом требуется для механизма Simulink вызвать эту функцию. В примере ниже, ssGetContStates получает указатель на непрерывный вектор состояния. Цикл for затем инициализирует каждое состояние, чтобы обнулить.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ========================================
 * Abstract:
 *    Initialize both continuous states to zero.
 */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *x0 = ssGetContStates(S);
    int_T  lp;

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

Необходимая функция mdlOutputs вычисляет выходной сигнал этой S-функции. Начало функции получает указатели на первый выходной порт, непрерывные состояния и первый входной порт. S-функция использует данные в этих массивах, чтобы решить выходное уравнение y=Cx+Du.

/* Function: mdlOutputs =======================================================
 * Abstract:
 *      y = Cx + Du 
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    real_T            *y    = ssGetOutputPortRealSignal(S,0);
    real_T            *x    = ssGetContStates(S);
    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);
}

Функция mdlDerivatives вычисляет непрерывные производные состояния. Поскольку эта функция является дополнительным методом, оператор #define должен предшествовать функции. Начало функции получает указатели на S-функцию непрерывные состояния, производные состояния и первый входной порт. S-функция использует эти данные, чтобы решить уравнение dx=Ax+Bu.

#define MDL_DERIVATIVES
/* Function: mdlDerivatives =================================================
 * Abstract:
 *      xdot = Ax + Bu
 */
static void mdlDerivatives(SimStruct *S)
{
    real_T            *dx   = ssGetdX(S);
    real_T            *x    = ssGetContStates(S);
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

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

Необходимая функция mdlTerminate выполняет любые действия, такие как освобождение памяти, необходимой в конце симуляции. В этом примере функция пуста.

/* Function: mdlTerminate =====================================================
 * Abstract:
 *    No termination needed, but we are required to have this routine.
 */
static void mdlTerminate(SimStruct *S)
{
    UNUSED_ARG(S); /* unused input argument */
}

Необходимый трейлер S-функции включает файлы, необходимые для симуляции или генерации кода, можно следующим образом.

#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

Примечание

mdlOutputs и функции mdlTerminate используют макрос UNUSED_ARG, чтобы указать, что входной параметр, которого требует коллбэк, не используется. Этот дополнительный макрос задан в simstruc_types.h. Если используется, необходимо вызвать этот макрос однажды для каждого входного параметра, который не использует коллбэк.

Дискретные состояния

Пример dsfunc.c показывает, как смоделировать дискретную систему в S-функции MEX C. Следующая модель Simulink использует эту S-функцию.

sfcndemo_dsfunc

Дискретные системы могут быть смоделированы следующей системой уравнений.

Пример dsfunc.c реализует дискретное уравнение пространства состояний. Метод mdlOutputs содержит выходной фрагмент, и метод mdlUpdate содержит фрагмент обновления дискретного уравнения пространства состояний. Чтобы визуализировать как моделирование, см. блок-схему во Взаимодействии Engine Simulink с S-функциями C. Выходное уравнение выше соответствует mdlOutputs в главном временном шаге. Предыдущее уравнение обновления соответствует mdlUpdate в главном временном шаге. Если ваша модель не содержит непрерывные элементы, механизм Simulink пропускает фазу интегрирования, и время продвинуто к следующему дискретному демонстрационному хиту.

matlabroot/toolbox/simulink/simdemos/simfeatures/src/dsfunc.c

S-функция dsfunc.c начинается с операторов #define для S-имени-функции и уровня, наряду с оператором #include для заголовка simstruc.h. После этих операторов S-функция может включать или задать любые другие необходимые заголовки, данные, и т.д. пример dsfunc.c задает U как указатель на сигнал первого входного порта и инициализирует статические переменные для матриц пространства состояний.

/*  File    : dsfunc.c
 *  Abstract:
 *
 *      Example C S-function for defining a discrete system.  
 *
 *      x(n+1) = Ax(n) + Bu(n)
 *      y(n)   = Cx(n) + Du(n)
 *
 *      For more details about S-functions, see simulink/src/sfuntmpl_doc.c.
 * 
 *  Copyright 1990-2013 The MathWorks, Inc.
 */

#define S_FUNCTION_NAME dsfunc
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (*uPtrs[element])  /* Pointer to Input Port0 */

static real_T A[2][2]={ { -1.3839, -0.5097 } ,
                        {  1     ,  0      }
                      };
 
static real_T B[2][2]={ { -2.5559,  0      } ,
                        {  0     ,  4.2382 }
                      };
 
static real_T C[2][2]={ {  0     ,  2.0761 } ,
                        {  0     ,  7.7891 }
                      };
 
static real_T D[2][2]={ { -0.8141, -2.9334 } ,
                        {  1.2426,  0      }
                      };

Необходимый метод S-функции mdlInitializeSizes затем настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции, чтобы обнулить.

  • ssGetSFcnParamsCount определяет, сколько параметров пользователь на самом деле ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров не совпадает с номером, возвращенным ssGetNumSFcnParams, ошибки S-функции.

  • Если количество S-параметра-функции передает, mdlInitializeSizes затем определяет номер непрерывных и дискретных состояний с помощью ssSetNumContStates и ssSetNumDiscStates, соответственно. Этот пример имеет нулевые непрерывные состояния и два дискретных состояния.

  • Затем, метод конфигурирует S-функцию, чтобы иметь один порт ввода и вывода, каждого с шириной два, чтобы совпадать с размерностями матриц пространства состояний. Метод передает значение 1 к ssSetInputPortDirectFeedThrough, чтобы указать, что входной порт имеет прямое сквозное соединение.

  • ssSetNumSampleTimes инициализирует один шаг расчета, который функция mdlInitializeSampleTimes конфигурирует позже.

  • S-функция указывает, что никакие не работают, векторы используются путем передачи значения 0 к ssSetNumRWork, ssSetNumIWork, и т.д. Можно не использовать эти строки, потому что нуль является значением по умолчанию для всех них макросы. Однако для ясности, S-функция явным образом определяет номер векторов работы.

  • Наконец, ssSetOptions устанавливает любые применимые опции. В этом случае единственной опцией является SS_OPTION_EXCEPTION_FREE_CODE, который предусматривает, что код является свободным исключением.

Функцию mdlInitializeSizes для этого примера показывают ниже.

/*====================*
 * S-function methods *
 *====================*/

/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    Determine the S-function block's characteristics:
 *    number of inputs, outputs, states, etc.
 */
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, 2);

    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);

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

Необходимый метод S-функции mdlInitializeSampleTimes задает частоты дискретизации S-функции. Вызов ssSetSampleTime устанавливает этот первый демонстрационный период S-функции на 1,0. ssSetOffsetTime затем задает время смещения нуля для первой частоты дискретизации. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели.

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *    Specify a sample time 0f 1.0.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, 1.0);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
}

Дополнительный метод S-функции mdlInitializeConditions инициализирует вектор дискретного состояния. Оператор #define перед этим методом требуется для механизма Simulink вызвать эту функцию. В примере ниже, ssGetRealDiscStates получает указатель на вектор дискретного состояния. Цикл for затем инициализирует каждое дискретное состояние одному.

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

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

Необходимая функция mdlOutputs вычисляет выходной сигнал этой S-функции. Начало функции получает указатели на первый выходной порт, дискретные состояния и первый входной порт. S-функция использует данные в этих массивах, чтобы решить выходное уравнение y=Cx+Du.

/* Function: mdlOutputs =======================================================
 * Abstract:
 *      y = Cx + Du 
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    real_T            *y    = ssGetOutputPortRealSignal(S,0);
    real_T            *x    = ssGetRealDiscStates(S);
    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);
}

Механизм Simulink вызывает функцию mdlUpdate один раз в главный временной шаг интегрирования, чтобы обновить значения дискретных состояний. Поскольку эта функция является дополнительным методом, оператор #define должен предшествовать функции. Начало функции получает указатели на дискретные состояния S-функции и первый входной порт. S-функция использует данные в этих массивах, чтобы решить уравнение dx=Ax+Bu, который хранится во временной переменной tempX прежде чем быть присвоенным в вектор дискретного состояния x.

#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       = ssGetRealDiscStates(S);
    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];
}

Необходимая функция mdlTerminate выполняет любые действия, такие как освобождение памяти, необходимой в конце симуляции. В этом примере функция пуста.

/* Function: mdlTerminate =====================================================
 * Abstract:
 *    No termination needed, but we are required to have this routine.
 */
static void mdlTerminate(SimStruct *S)
{
    UNUSED_ARG(S); /* unused input argument */
}

Необходимый трейлер S-функции включает файлы, необходимые для симуляции или генерации кода, можно следующим образом.

#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

Примечание

mdlOutputs и функции mdlTerminate используют макрос UNUSED_ARG, чтобы указать, что входной параметр, которого требует коллбэк, не используется. Этот дополнительный макрос задан в simstruc_types.h. Если используется, необходимо вызвать этот макрос однажды для каждого входного параметра, который не использует коллбэк.

Непрерывные и дискретные состояния

Пример mixedm.c показывает гибрид (комбинация непрерывных и дискретных состояний) система. Пример mixedm.c комбинирует элементы csfunc.c и dsfunc.c. Следующая модель Simulink использует эту S-функцию.

sfcndemo_mixedm

Если у вас есть гибридная система, метод mdlDerivatives вычисляет производные непрерывных состояний вектора состояния, x, и метод mdlUpdate содержит уравнения, используемые, чтобы обновить вектор дискретного состояния, xD. Метод mdlOutputs вычисляет S-функцию выходные параметры после проверки демонстрационные хиты, чтобы определить, в какой точке называется S-функция.

В форме Диаграммы Simulink S-функция mixedm.c похож

который реализует непрерывный интегратор, сопровождаемый дискретной единичной задержкой.

matlabroot/toolbox/simulink/simdemos/simfeatures/src/mixedm.c

S-функция mixedm.c начинается с операторов #define для S-имени-функции и уровня, наряду с оператором #include для заголовка simstruc.h. После этих операторов S-функция может включать или задать любые другие необходимые заголовки, данные, и т.д. пример mixedm.c задает U как указатель на сигнал первого входного порта.

/*  File    : mixedm.c
 *  Abstract:
 *
 *      An example S-function illustrating multiple sample times by implementing
 *         integrator -> ZOH(Ts=1second) -> UnitDelay(Ts=1second) 
 *      with an initial condition of 1.
 *	(e.g. an integrator followed by unit delay operation).
 *
 *      For more details about S-functions, see simulink/src/sfuntmpl_doc.c
 *
 *  Copyright 1990-2007 The MathWorks, Inc.
 */

#define S_FUNCTION_NAME mixedm
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (*uPtrs[element])  /* Pointer to Input Port0 */

Необходимый метод S-функции mdlInitializeSizes затем настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции, чтобы обнулить.

  • ssGetSFcnParamsCount определяет, сколько параметров пользователь на самом деле ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров не совпадает с номером, возвращенным ssGetNumSFcnParams, ошибки S-функции.

  • Если количество S-параметра-функции передает, mdlInitializeSizes затем определяет номер непрерывных и дискретных состояний с помощью ssSetNumContStates и ssSetNumDiscStates, соответственно. Этот пример имеет одно непрерывное состояние и одно дискретное состояние.

  • S-функция инициализирует один вектор работы с плавающей точкой путем передачи значения 1 к ssSetNumRWork. Никто другой не работает, векторы инициализируются.

  • Затем, метод использует ssSetNumInputPorts и ssSetNumOutputPorts, чтобы сконфигурировать S-функцию, чтобы иметь один порт ввода и вывода, каждого с шириной одной. Метод передает значение 1 к ssSetInputPortDirectFeedThrough, чтобы указать, что входной порт имеет прямое сквозное соединение.

  • Эта S-функция присваивает шаги расчета с помощью гибридного основанного на блоке и основанного на порте метода. macro ssSetNumSampleTimes инициализирует два основанных на блоке шага расчета, которые функция mdlInitializeSampleTimes конфигурирует позже. Макросы ssSetInputPortSampleTime и ssSetInputPortOffsetTime инициализируют входной порт, чтобы иметь время непрерывной выборки со смещением нуля. Точно так же ssSetOutputPortSampleTime и ssSetOutputPortOffsetTime инициализируют шаг расчета выходного порта к 1 со смещением нуля.

  • Наконец, ssSetOptions устанавливает две опции S-функции. SS_OPTION_EXCEPTION_FREE_CODE предусматривает, что код является свободным исключением, и SS_OPTION_PORT_SAMPLE_TIMES_ASSIGNED указывает на комбинацию основанных на блоке и основанных на порте шагов расчета.

Функцию mdlInitializeSizes для этого примера показывают ниже.

*====================*
 * S-function methods *
 *====================*/

/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    Determine the S-function block's characteristics:
 *    number of inputs, outputs, states, etc.
 */
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, 1);
    ssSetNumDiscStates(S, 1);
    ssSetNumRWork(S, 1);  /* for zoh output feeding the delay operator */

    if (!ssSetNumInputPorts(S, 1)) return;
    ssSetInputPortWidth(S, 0, 1);
    ssSetInputPortDirectFeedThrough(S, 0, 1);
    ssSetInputPortSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
    ssSetInputPortOffsetTime(S, 0, 0.0);

    if (!ssSetNumOutputPorts(S, 1)) return;
    ssSetOutputPortWidth(S, 0, 1);
    ssSetOutputPortSampleTime(S, 0, 1.0);
    ssSetOutputPortOffsetTime(S, 0, 0.0);

    ssSetNumSampleTimes(S, 2);

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

} /* end mdlInitializeSizes */

Необходимый метод S-функции mdlInitializeSampleTimes задает основанные на блоке s-function частоты дискретизации. Первый вызов ssSetSampleTime указывает, что первая частота дискретизации непрерывна с последующим вызовом ssSetOffsetTime, обнуляющего смещение. Второй вызов этой пары макросов устанавливает второй шаг расчета на 1 со смещением нуля. S-функция основанный на порте набор шагов расчета в mdlInitializeSizes должна все быть указана как основанный на блоке шаг расчета. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели.

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *    Two tasks: One continuous, one with discrete sample time of 1.0.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);

    ssSetSampleTime(S, 1, 1.0);
    ssSetOffsetTime(S, 1, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
} /* end mdlInitializeSampleTimes */

Дополнительный метод S-функции mdlInitializeConditions инициализирует векторы непрерывного и дискретного состояния. Оператор #define перед этим методом требуется для механизма Simulink вызвать эту функцию. В этом примере ssGetContStates получает указатель на непрерывный вектор состояния, и ssGetRealDiscStates получает указатель на вектор дискретного состояния. Метод затем устанавливает начальные условия всех состояний одному.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ==========================================
 * Abstract:
 *    Initialize both continuous states to one.
 */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *xC0 = ssGetContStates(S);
    real_T *xD0 = ssGetRealDiscStates(S);

    xC0[0] = 1.0;
    xD0[0] = 1.0;

} /* end mdlInitializeConditions */

Необходимая функция mdlOutputs выполняет вычисления на основе текущей задачи. Макро-ssIsContinuousTask проверяет, выполняется ли непрерывная задача. Если этот макрос возвращает true, ssIsSpecialSampleHit затем проверяет, выполняется ли дискретная частота дискретизации также. Если этот макрос также возвращает true, метод устанавливает значение вектора работы с плавающей точкой к текущему значению непрерывного состояния, через указатели, полученные с помощью ssGetRWork и ssGetContStates, соответственно. Метод mdlUpdate более позднее использование вектор работы с плавающей точкой как вход к нулевому порядку содержит. Обновление вектора работы в mdlOutputs гарантирует, что правильные значения доступны во время последующих вызовов mdlUpdate. Наконец, если S-функция запускается на ее дискретном уровне, т.е. вызов ssIsSampleHit возвращает true, метод устанавливает вывод на значение дискретного состояния.

/* Function: mdlOutputs =======================================================
 * Abstract:
 *      y = xD, and update the zoh internal output.
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    /* update the internal "zoh" output */
    if (ssIsContinuousTask(S, tid)) {
        if (ssIsSpecialSampleHit(S, 1, 0, tid)) {
            real_T *zoh = ssGetRWork(S);
            real_T *xC  = ssGetContStates(S);
            *zoh = *xC;
        }
    }

    /* y=xD */
    if (ssIsSampleHit(S, 1, tid)) {
        real_T *y   = ssGetOutputPortRealSignal(S,0);
        real_T *xD  = ssGetRealDiscStates(S);
        y[0]=xD[0];
    }


} /* end mdlOutputs */

Механизм Simulink вызывает функцию mdlUpdate один раз в главный временной шаг интегрирования, чтобы обновить значения дискретных состояний. Поскольку эта функция является дополнительным методом, оператор #define должен предшествовать функции. Вызов ssIsSampleHit гарантирует, что тело метода выполняется только, когда S-функция действует на ее дискретном уровне. Если ssIsSampleHit возвращает true, метод получает указатели на дискретное состояние S-функции и вектор работы с плавающей точкой и обновляет значение дискретного состояния с помощью значения, сохраненного в векторе работы.

#define MDL_UPDATE
/* Function: mdlUpdate ======================================================
 * Abstract:
 *      xD = xC
 */
static void mdlUpdate(SimStruct *S, int_T tid)
{
    UNUSED_ARG(tid); /* not used in single tasking mode */

    /* xD=xC */
    if (ssIsSampleHit(S, 1, tid)) {
        real_T *xD = ssGetRealDiscStates(S);
        real_T *zoh = ssGetRWork(S);
        xD[0]=*zoh;
    }
} /* end mdlUpdate */

Функция mdlDerivatives вычисляет непрерывные производные состояния. Поскольку эта функция является дополнительным методом, оператор #define должен предшествовать функции. Функция получает указатели на S-функцию, непрерывный производный и первый входной порт состояния затем устанавливает непрерывную производную состояния, равную значению первого входа.

#define MDL_DERIVATIVES
/* Function: mdlDerivatives =================================================
 * Abstract:
 *      xdot = U
 */
static void mdlDerivatives(SimStruct *S)
{
    real_T            *dx   = ssGetdX(S);
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

    /* xdot=U */
    dx[0]=U(0);

} /* end mdlDerivatives */

Необходимая функция mdlTerminate выполняет любые действия, такие как освобождение памяти, необходимой в конце симуляции. В этом примере функция пуста.

/* Function: mdlTerminate =====================================================
 * Abstract:
 *    No termination needed, but we are required to have this routine.
 */
static void mdlTerminate(SimStruct *S)
{
    UNUSED_ARG(S); /* unused input argument */
}

Трейлер S-функции включает файлы, необходимые для симуляции или генерации кода, можно следующим образом.

#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

Примечание

mdlUpdate и функции mdlTerminate используют макрос UNUSED_ARG, чтобы указать, что входной параметр, которого требует коллбэк, не используется. Этот дополнительный макрос задан в simstruc_types.h. Если используется, необходимо вызвать этот макрос однажды для каждого входного параметра, который не использует коллбэк.

Переменный шаг расчета

S-функция в качестве примера vsfunc.c использует шаг расчета переменного шага. Следующая модель Simulink использует эту S-функцию.

sfcndemo_vsfunc

Переменные функции неродного размера требуют вызова mdlGetTimeOfNextVarHit, который является стандартной программой S-функции, которая вычисляет время следующего демонстрационного хита. S-функции, которые используют шаг расчета переменного шага, могут использоваться только с решателями переменного шага. Примером vsfunc.c является дискретная S-функция, которая задерживает ее первый вход количеством времени, определенным вторым входом.

Пример vsfunc.c выводит вход u, задержанный переменным количеством времени. mdlOutputs устанавливает вывод y, равный утверждать x. mdlUpdate устанавливает вектор состояния x, равный u, входному вектору. Этот пример вызывает mdlGetTimeOfNextVarHit, чтобы вычислить и установить время следующего демонстрационного хита, то есть, время, когда vsfunc.c затем называется. В mdlGetTimeOfNextVarHit макро-ssGetInputPortRealSignalPtrs получает указатель на вход u. Затем этот вызов выполняется:

ssSetTNext(S, ssGetT(S) + U(1));

Макро-ssGetT получает время симуляции t. Второй вход к блоку, U(1), добавляется к t, и макро-ssSetTNext устанавливает время следующего хита, равного t+U(1), задерживая вывод набором количества времени в (U(1)).

matlabroot/toolbox/simulink/simdemos/simfeatures/src/vsfunc.c

S-функция vsfunc.c начинается с операторов #define для S-имени-функции и уровня, наряду с оператором #include для заголовка simstruc.h. После этих операторов S-функция может включать или задать любые другие необходимые заголовки, данные, и т.д. пример vsfunc.c задает U как указатель на сигнал первого входного порта.

/*  File    : vsfunc.c
 *  Abstract:
 *
 *      Variable step S-function example.
 *      This example S-function illustrates how to create a variable step
 *      block.  This block implements a variable step delay
 *      in which the first input is delayed by an amount of time determined
 *      by the second input:
 *
 *      dt      = u(2)
 *      y(t+dt) = u(t)
 *
 *      For more details about S-functions, see simulink/src/sfuntmpl_doc.c.
 *
 *  Copyright 1990-2007 The MathWorks, Inc.
 */

#define S_FUNCTION_NAME vsfunc
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define U(element) (*uPtrs[element])  /* Pointer to Input Port0 */

Необходимый метод S-функции mdlInitializeSizes затем настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции, чтобы обнулить.

  • ssGetSFcnParamsCount определяет, сколько параметров пользователь на самом деле ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров не совпадает с номером, возвращенным ssGetNumSFcnParams, ошибки S-функции.

  • Если количество S-параметра-функции передает, mdlInitializeSizes затем определяет номер непрерывных и дискретных состояний с помощью ssSetNumContStates и ssSetNumDiscStates, соответственно. Этот пример не имеет никаких непрерывных состояний и одного дискретного состояния.

  • Затем, метод использует ssSetNumInputPorts и ssSetNumOutputPorts, чтобы сконфигурировать S-функцию, чтобы иметь один порт ввода и вывода. Вызовы ssSetInputPortWidth и ssSetOutputPortWidth присваивают ширины этим портам ввода и вывода. Метод передает значение 1 к ssSetInputPortDirectFeedThrough, чтобы указать, что входной порт имеет прямое сквозное соединение.

  • ssSetNumSampleTimes затем инициализирует один шаг расчета, который функция mdlInitializeSampleTimes конфигурирует позже.

  • S-функция указывает, что никакие не работают, векторы используются путем передачи значения 0 к ssSetNumRWork, ssSetNumIWork, и т.д. Можно не использовать эти строки, потому что нуль является значением по умолчанию для всех них макросы. Однако для ясности, S-функция явным образом определяет номер векторов работы.

  • Затем, ssGetSimMode проверяет, запускается ли S-функция в симуляции или продуктом Simulink Coder™. Если ssGetSimMode возвращает SS_SIMMODE_RTWGEN, и ssIsVariableStepSolver возвращает false, указывая на использование с продуктом Simulink Coder и решателем фиксированного шага, то ошибки S-функции.

  • Наконец, ssSetOptions устанавливает любые применимые опции. В этом случае единственной опцией является SS_OPTION_EXCEPTION_FREE_CODE, который предусматривает, что код является свободным исключением.

Функцию mdlInitializeSizes для этого примера показывают ниже.

/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    Determine the S-function block's characteristics:
 *    number of inputs, outputs, states, etc.
 */
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, 1);

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

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

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

    if (ssGetSimMode(S) == SS_SIMMODE_RTWGEN && !ssIsVariableStepSolver(S)) {
        ssSetErrorStatus(S, "S-function vsfunc.c cannot be used with RTW "
                         "and Fixed-Step Solvers because it contains variable"
                         " sample time");
    }

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

Необходимый метод S-функции mdlInitializeSampleTimes задает частоты дискретизации S-функции. Входной параметр, который VARIABLE_SAMPLE_TIME передал ssSetSampleTime, указывает, что эта S-функция имеет шаг расчета переменного шага, и ssSetOffsetTime задает время смещения нуля. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели. Поскольку S-функция имеет шаг расчета переменного шага, vsfunc.c должен вычислить время следующего демонстрационного хита в методе mdlGetTimeOfNextVarHit, показанном позже.

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *    Variable-Step S-function
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, VARIABLE_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
}

Дополнительный метод S-функции mdlInitializeConditions инициализирует вектор дискретного состояния. Оператор #define перед этим методом требуется для механизма Simulink вызвать эту функцию. В примере метод использует ssGetRealDiscStates, чтобы получить указатель на вектор дискретного состояния и обнуляет начальное значение состояния.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ========================================
 * Abstract:
 *    Initialize discrete state to zero.
 */
static void mdlInitializeConditions(SimStruct *S)
{
    real_T *x0 = ssGetRealDiscStates(S);

    x0[0] = 0.0;
}

Дополнительный метод mdlGetTimeOfNextVarHit вычисляет время следующего демонстрационного хита. Поскольку этот метод является дополнительным, оператор #define предшествует ему. Во-первых, этот метод получает указатель на использование первого входного порта сигнала ssGetInputPortRealSignalPtrs. Если второй элемент входного сигнала положителен, макро-ssGetT получает время симуляции t. Макро-ssSetTNext устанавливает время следующего хита, равного t+(*U[1]), задерживая вывод количеством времени, заданным вторым элементом входа (*U[1]).

#define MDL_GET_TIME_OF_NEXT_VAR_HIT
static void mdlGetTimeOfNextVarHit(SimStruct *S)
{
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
    
    /* Make sure input will increase time */
    if (U(1) <= 0.0) {
        /* If not, abort simulation */
        ssSetErrorStatus(S,"Variable step control input must be "
                         "greater than zero");
        return;
    }
    ssSetTNext(S, ssGetT(S)+U(1));
}

Необходимая функция mdlOutputs вычисляет выходной сигнал S-функции. Функция получает указатели на первый выходной порт и дискретное состояние и затем присваивает текущее значение состояния выводу.

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

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

Функция mdlUpdate обновляет значение дискретного состояния. Поскольку этот метод является дополнительным, оператор #define предшествует ему. Функция сначала получает указатели на дискретное состояние S-функции, и первый входной порт затем присваивает значение первого элемента первого сигнала входного порта к состоянию.

#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    = ssGetRealDiscStates(S);
    InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);

    x[0]=U(0);
}

Необходимая функция mdlTerminate выполняет любые действия, такие как освобождение памяти, необходимой в конце симуляции. В этом примере функция пуста.

/* Function: mdlTerminate =====================================================
 * Abstract:
 *    No termination needed, but we are required to have this routine.
 */
static void mdlTerminate(SimStruct *S)
{
}

Необходимый трейлер S-функции включает файлы, необходимые для симуляции или генерации кода, можно следующим образом.

#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

Вводы и выводы массивов

sfun_matadd.c S-функции в качестве примера демонстрирует, как реализовать матричный блок сложения. Следующая модель Simulink использует эту S-функцию.

sfcndemo_matadd

S-функция добавляет сигналы различных размерностей к значению параметров, вводимому в S-функцию. S-функция принимает и 2D выходные параметры или сигналы n-D.

matlabroot/toolbox/simulink/simdemos/simfeatures/src/sfun_matadd.c

S-функция sfun_matadd.c начинается с операторов #define для S-имени-функции и уровня, наряду с оператором #include для заголовка simstruc.h. После этих операторов S-функция включает или задает любые другие необходимые заголовки, данные, и т.д. Этот пример задает дополнительные переменные для количества S-параметров-функции, значения S-параметра-функции и флага EDIT_OK, который указывает, может ли значение параметров быть отредактировано во время симуляции.

/* SFUN_MATADD matrix support example. 
 *   C MEX S-function for matrix addition with one input port,
 *   one output port, and one parameter.
 *
 *  Input Signal:  2-D or n-D array
 *  Parameter:     2-D or n-D array
 *  Output Signal: 2-D or n-D array
 *
 *  Input   parameter    output
 *  --------------------------------
 *  scalar   scalar      scalar
 *  scalar   matrix     matrix     (input scalar expansion)
 *  matrix   scalar     matrix     (parameter scalar expansion)
 *  matrix   matrix     matrix
 *
 *  Copyright 1990-2007 The MathWorks, Inc.
 */
#define S_FUNCTION_NAME  sfun_matadd
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

enum {PARAM = 0, NUM_PARAMS};

#define PARAM_ARG ssGetSFcnParam(S, PARAM)

#define EDIT_OK(S, ARG) \
  (!((ssGetSimMode(S) == SS_SIMMODE_SIZES_CALL_ONLY) \
  && mxIsEmpty(ARG)))

S-функция затем реализует метод mdlCheckParameters, чтобы подтвердить диалоговые параметры S-функции. Оператор #ifdef проверяет, что S-функция скомпилирована как файл MEX, вместо для использования с продуктом Simulink Coder. Поскольку mdlCheckParameters является дополнительным, код S-функции содержит оператор #define, чтобы указать метод. Тело функции проверяет, что значение S-параметра-функции не пусто. Если проверка параметра перестала работать, ошибки S-функции с вызовом ssSetErrorStatus.

#ifdef MATLAB_MEX_FILE 
#define MDL_CHECK_PARAMETERS 
/* Function: mdlCheckParameters ================================

 * Abstract:
 *    Verify parameter settings.
 */
static void mdlCheckParameters(SimStruct *S)
{
    if(EDIT_OK(S, PARAM_ARG)){
        /* Check that parameter value is not empty*/
        if( mxIsEmpty(PARAM_ARG) ) {
          ssSetErrorStatus(S, "Invalid parameter specified. The"
                              "parameter must be non-empty");
          return;
        }      
    }
} /* end mdlCheckParameters */
#endif 

Необходимый метод S-функции mdlInitializeSizes затем настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции одному, как задано переменной NUM_PARAMS.

  • Если эта S-функция скомпилирована как файл MEX, ssGetSFcnParamsCount определяет, сколько параметров пользователь на самом деле ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров совпадает с номером, возвращенным ssGetNumSFcnParams, вызовы метода mdlCheckParameters, чтобы подтвердить вводимые пользователями данные. В противном случае, ошибки S-функции.

  • Если проверка параметра передает, S-функция указывает, что все S-параметры-функции являются настраиваемым использованием ssSetSFcnParamTunable.

  • S-функция затем вызывает ssAllowSignalsWithMoreThan2D, чтобы позволить S-функции принимать сигналы n-D.

  • Затем, ssSetNumOutputPorts и ssSetNumInputPorts указывают, что S-функция имеет один выходной порт и один входной порт.

  • S-функция использует ssSetInputPortDimensionInfo, чтобы указать, что входной порт динамически измерен. В этом случае S-функция должна реализовать метод mdlSetInputPortDimensionInfo, чтобы установить фактическую входную размерность.

  • Выходные размерности зависят от размерностей S-параметра-функции. Если параметр является скаляром, вызов ssSetOutputPortDimensionInfo указывает, что размерности выходного порта динамически измерены. Если параметр является матрицей, размерности выходного порта инициализируются к размерностям S-параметра-функции. В этом случае макро-DECL_AND_INIT_DIMSINFO инициализирует структуру dimsInfo. S-функция присваивает ширину, размер и размерности S-параметра-функции в структуру dimsInfo и затем передает эту структуру ssSetOutputPortDimensionInfo в порядке установить размерности выходного порта соответственно.

  • S-функция указывает, что входной порт имеет прямое сквозное соединение путем передачи значения 1 к ssSetInputPortDirectFeedThrough.

  • ssSetNumSampleTimes инициализирует один шаг расчета, чтобы быть сконфигурированным позже в методе mdlInitializeSampleTimes.

  • Наконец, ssSetOptions устанавливает любые применимые опции. В этом случае SS_OPTION_EXCEPTION_FREE_CODE предусматривает, что код является свободным исключением, и SS_OPTION_WORKS_WITH_CODE_REUSE показывает, что эта S-функция совместима с функцией повторного использования кода подсистемы продукта Simulink Coder.

/* Function: mdlInitializeSizes ================================
 * Abstract:
 *   Initialize the sizes array
 */
static void mdlInitializeSizes(SimStruct *S)
{
    ssSetNumSFcnParams(S, NUM_PARAMS);

   #if defined(MATLAB_MEX_FILE)
      if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
         return; }
      mdlCheckParameters(S); 
      if (ssGetErrorStatus(S) != NULL) return; 
   #endif

   {
      int iParam = 0;
      int nParam = ssGetNumSFcnParams(S);

      for ( iParam = 0; iParam < nParam; iParam++ )
        {
          ssSetSFcnParamTunable( S, iParam, SS_PRM_TUNABLE );
        }
    }
 
   /* Allow signal dimensions greater than 2 */
   ssAllowSignalsWithMoreThan2D(S);
    
   /* Set number of input and output ports */
   if (!ssSetNumInputPorts( S,1)) return;
   if (!ssSetNumOutputPorts(S,1)) return;

   /* Set dimensions of input and output ports */
   {
      int_T pWidth = mxGetNumberOfElements(PARAM_ARG);
      /* Input can be a scalar or a matrix signal. */
      if(!ssSetInputPortDimensionInfo(S,0,DYNAMIC_DIMENSION)) {
          return; }

      if( pWidth == 1) { 
       /* Scalar parameter: output dimensions are unknown. */
       if(!ssSetOutputPortDimensionInfo(S,0,DYNAMIC_DIMENSION)){
           return; }
       }
      else{
         /* 
          * Non-scalar parameter: output dimensions are the same
          * as the parameter dimensions. To support n-D signals,
          * must use a dimsInfo structure to specify dimensions.
          */
          DECL_AND_INIT_DIMSINFO(di); /*Initializes structure*/
          int_T      pSize = mxGetNumberOfDimensions(PARAM_ARG);
          const int_T *pDims = mxGetDimensions(PARAM_ARG);
          di.width   = pWidth;
          di.numDims = pSize;
          di.dims    = pDims;
          if(!ssSetOutputPortDimensionInfo(S, 0, &di)) return;
          }
    }
    ssSetInputPortDirectFeedThrough(S, 0, 1);

    ssSetNumSampleTimes(S, 1);
    ssSetOptions(S,
                 SS_OPTION_WORKS_WITH_CODE_REUSE |
                 SS_OPTION_EXCEPTION_FREE_CODE);
} /* end mdlInitializeSizes */

Необходимый метод S-функции mdlInitializeSampleTimes задает частоты дискретизации S-функции. Указывать, что эта S-функция наследовала свой шаг расчета от ее ведущего блока, S-вызовы-функции ssSetSampleTime с входным параметром INHERITED_SAMPLE_TIME. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели.

/* Function: mdlInitializeSampleTimes ==========================
 * Abstract:
 *    Initialize the sample times array.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
} /* end mdlInitializeSampleTimes */

S-вызовы-функции метод mdlSetWorkWidths, чтобы указать его параметры периода выполнения. Поскольку mdlSetWorkWidths является дополнительным методом, оператор #define предшествует ему. Метод сначала инициализирует имя для параметра периода выполнения и затем использует ssRegAllTunableParamsAsRunTimeParams, чтобы указать параметр периода выполнения.

/* Function: mdlSetWorkWidths ==================================
 * Abstract:
 *    Set up run-time parameter.
 */
#define MDL_SET_WORK_WIDTHS
static void mdlSetWorkWidths(SimStruct *S)
{
    const char_T    *rtParamNames[] = {"Operand"};
    ssRegAllTunableParamsAsRunTimeParams(S, rtParamNames);
} /* end mdlSetWorkWidths */

S-функция метод mdlOutputs использует цикл for, чтобы вычислить вывод как сумму входа и S-параметра-функции. S-указатели-на-функцию n-D массивы данных с помощью одного индекса в массив.

/* Function: mdlOutputs ========================================
 * Abstract:
 *   Compute the outputs of the S-function.
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    InputRealPtrsType uPtr = ssGetInputPortRealSignalPtrs(S,0);
    real_T            *y   = ssGetOutputPortRealSignal(S,0);
    const real_T      *p   = mxGetPr(PARAM_ARG);
    
    int_T             uWidth = ssGetInputPortWidth(S,0);
    int_T             pWidth = mxGetNumberOfElements(PARAM_ARG);
    int_T             yWidth = ssGetOutputPortWidth(S,0);
    int               i;
    
    UNUSED_ARG(tid); /* not used in single tasking mode */
    
    /*
     * Note1: Matrix signals are stored in column major order.
     * Note2: Access each matrix element by one index not two
     *        indices. For example, if the output signal is a 
     *        [2x2] matrix signal,
     *        -          -
     *       | y[0]  y[2] |
     *       | y[1]  y[3] |
     *       -           -
     *       Output elements are stored as follows:
     *           y[0] --> row = 0, col = 0
     *           y[1] --> row = 1, col = 0
     *           y[2] --> row = 0, col = 1
     *           y[3] --> row = 1, col = 1
     */
    
    for (i = 0; i < yWidth; i++) {
        int_T uIdx = (uWidth == 1) ? 0 : i;
        int_T pIdx = (pWidth == 1) ? 0 : i;
        
        y[i] = *uPtr[uIdx] + p[pIdx];
    }
} /* end mdlOutputs */

Во время распространения сигнала, S-вызовов-функции дополнительный метод mdlSetInputPortDimensionInfo с размерностями входного порта кандидата, сохраненными в dimsInfo. Оператор #if defined проверяет, что S-функция скомпилирована как файл MEX. Поскольку mdlSetInputPortDimensionInfo является дополнительным методом, оператор #define предшествует ему. В mdlSetInputPortDimensionInfo S-функция использует ssSetInputPortDimensionInfo, чтобы установить размерности входного порта к размерностям кандидата. Если вызов этого макроса успешно выполняется, S-функция дальнейшие проверки размерности кандидата, чтобы гарантировать, что входной сигнал является или 2D скаляром или матрицей. Если это условие соблюдают, и размерности выходного порта все еще динамически измерены, S-вызовы-функции ssSetOutputPortDimensionInfo, чтобы установить размерность выходного порта к тем же размерностям кандидата. Макрос ssSetOutputPortDimensionInfo не может изменить размерности выходного порта, если они уже заданы.

#if defined(MATLAB_MEX_FILE)
#define MDL_SET_INPUT_PORT_DIMENSION_INFO
/* Function: mdlSetInputPortDimensionInfo ======================
 * Abstract:
 *    This routine is called with the candidate dimensions for
 *    an input port with unknown dimensions. If the proposed 
 *    dimensions are acceptable, the routine should go ahead and
 *    set the actual port dimensions. If they are unacceptable
 *    an error should be generated via ssSetErrorStatus.
 *    Note that any other input or output ports whose dimensions
 *    are implicitly defined by virtue of knowing the dimensions
 *    of the given port can also have their dimensions set.
 */
static void mdlSetInputPortDimensionInfo(SimStruct        *S,
   int_T            port,
   const DimsInfo_T *dimsInfo)
{
    int_T  pWidth          = mxGetNumberOfElements(PARAM_ARG);
    int_T  pSize           = mxGetNumberOfDimensions(PARAM_ARG);
    const int_T  *pDims    = mxGetDimensions(PARAM_ARG);
    
    int_T  uNumDims = dimsInfo->numDims;
    int_T  uWidth   = dimsInfo->width;
    int_T  *uDims   = dimsInfo->dims;
    
    int_T numDims;
    boolean_T  isOk = true;
    int iParam = 0;
    int_T outWidth = ssGetOutputPortWidth(S, 0);
    
    /* Set input port dimension */
    if(!ssSetInputPortDimensionInfo(S, port, dimsInfo)) return;
    
    /*
     * The block only accepts 2-D or higher signals. Check 
     * number of dimensions. If the parameter and the input
     * signal are non-scalar, their dimensions must be the same.
     */
    isOk = (uNumDims >= 2) && (pWidth == 1 || uWidth == 1 || 
       pWidth == uWidth);
    numDims = (pSize != uNumDims) ? numDims : uNumDims;
    
    if(isOk && pWidth > 1 && uWidth > 1){
        for ( iParam = 0; iParam < numDims; iParam++ ) {
            isOk = (pDims[iParam] == uDims[iParam]);
            if(!isOk) break;
        }
    }
    
    if(!isOk){
        ssSetErrorStatus(S,"Invalid input port dimensions. The "
        "input signal must be a 2-D scalar signal, or it must "
        "be a matrix with the same dimensions as the parameter "
        "dimensions.");
        return;
    }
    
    /* Set the output port dimensions */
    if (outWidth == DYNAMICALLY_SIZED){
      if(!ssSetOutputPortDimensionInfo(S,port,dimsInfo)) return;
    }
} /* end mdlSetInputPortDimensionInfo */

Во время распространения сигнала, если какие-либо выходные порты имеют неизвестные измерения, S-вызовы-функции дополнительный метод mdlSetOutputPortDimensionInfo. Поскольку этот метод является дополнительным, оператор #define предшествует ему. В mdlSetOutputPortDimensionInfo S-функция использует ssSetOutputPortDimensionInfo, чтобы установить размерности выходного порта к размерностям кандидата dimsInfo. Если вызов этого макроса успешно выполняется, S-функция дальнейшие проверки размерности кандидата, чтобы гарантировать, что входной сигнал является или 2D или n-D матрицей. Если это условие не соблюдают, ошибки S-функции с вызовом ssSetErrorStatus. В противном случае, S-вызовы-функции ssSetInputPortDimensionInfo, чтобы установить размерность входного порта к тем же размерностям кандидата.

# define MDL_SET_OUTPUT_PORT_DIMENSION_INFO
/* Function: mdlSetOutputPortDimensionInfo =====================
 * Abstract:
 *    This routine is called with the candidate dimensions for
 *    an output port with unknown dimensions. If the proposed
 *    dimensions are acceptable, the routine should go ahead and
 *    set the actual port dimensions. If they are unacceptable
 *    an error should be generated via ssSetErrorStatus.
 *    Note that any other input or output ports whose dimensions
 *    are implicitly defined by virtue of knowing the dimensions
 *    of the given port can also have their dimensions set.
 */
static void mdlSetOutputPortDimensionInfo(SimStruct        *S,
   int_T            port,
   const DimsInfo_T *dimsInfo)
{
    /*
     * If the block has scalar parameter, the output dimensions
     * are unknown. Set the input and output port to have the
     * same dimensions.
     */
    if(!ssSetOutputPortDimensionInfo(S, port, dimsInfo)) return;
    
    /* The block only accepts 2-D or n-D signals.
     * Check number of dimensions.
     */
    if (!(dimsInfo->numDims >= 2)){
        ssSetErrorStatus(S, "Invalid output port dimensions. "
        "The output signal must be a 2-D or n-D array (matrix) "
        "signal.");
        return;
    }else{
       /* Set the input port dimensions */
       if(!ssSetInputPortDimensionInfo(S,port,dimsInfo)) return;
    }
} /* end mdlSetOutputPortDimensionInfo */

Поскольку S-функция имеет порты, которые динамически измерены, она должна предоставить метод mdlSetDefaultPortDimensionInfo. Механизм Simulink вызывает этот метод во время распространения сигнала, когда это не может определить размерность сигнала, соединенного с входным портом блока. Эта ситуация может произойти, например, если входной порт не связан. В этом примере метод mdlSetDefaultPortDimensionInfo устанавливает размерности портов ввода и вывода на скаляр.

# define MDL_SET_DEFAULT_PORT_DIMENSION_INFO
/* Function: mdlSetDefaultPortDimensionInfo ====================
 *    This routine is called when the Simulink engine is not able
 *    to find dimension candidates for ports with unknown dimensions.
 *    This function must set the dimensions of all ports with 
 *    unknown dimensions.
 */
static void mdlSetDefaultPortDimensionInfo(SimStruct *S)
{
    int_T outWidth = ssGetOutputPortWidth(S, 0);
    /* Input port dimension must be unknown. Set it to scalar.*/
    if(!ssSetInputPortMatrixDimensions(S, 0, 1, 1)) return;
    if(outWidth == DYNAMICALLY_SIZED){
        /* Output dimensions are unknown. Set it to scalar. */
        if(!ssSetOutputPortMatrixDimensions(S, 0, 1, 1)) return;
    }
} /* end mdlSetDefaultPortDimensionInfo */
#endif

Необходимая функция mdlTerminate выполняет любые действия, такие как освобождение памяти, необходимой в конце симуляции. В этом примере функция пуста.

/* Function: mdlTerminate ======================================
 * Abstract:
 *    Called when the simulation is terminated.
 */
static void mdlTerminate(SimStruct *S)
{
    UNUSED_ARG(S); /* unused input argument */
    
} /* end mdlTerminate */

Необходимый трейлер S-функции включает файлы, необходимые для симуляции или генерации кода.

#ifdef	MATLAB_MEX_FILE
  #include "simulink.c"
#else
  #include "cg_sfun.h"
#endif

/* [EOF] sfun_matadd.c */

Примечание

mdlOutputs и функции mdlTerminate используют макрос UNUSED_ARG, чтобы указать, что входной параметр, которого требует коллбэк, не используется. Этот дополнительный макрос задан в simstruc_types.h. Необходимо вызвать этот макрос однажды для каждого входного параметра, который не использует коллбэк.

Обнаружение пересечения нулем

sfun_zc_sat.c S-функции в качестве примера демонстрирует, как реализовать блок Saturation. Следующая модель Simulink использует эту S-функцию.

sfcndemo_sfun_zc_sat

S-функция работает или с фиксированным шагом или с решателями переменного шага. Когда эта S-функция наследовала время непрерывной выборки и использует решатель переменного шага, она использует алгоритм нулевых пересечений, чтобы определить местоположение точных мест, в которых происходит насыщение.

matlabroot/toolbox/simulink/simdemos/simfeatures/src/sfun_zc_sat.c

S-функция sfun_zc_sat.c начинается с операторов #define для S-имени-функции и уровня, наряду с оператором #include для заголовка simstruc.h. После этих операторов S-функция включает или задает любые другие необходимые заголовки, данные, и т.д. Этот пример задает различные параметры, сопоставленные с верхними и более низкими границами насыщения.

/*  File    : sfun_zc_sat.c
 *  Abstract:
 *
 *      Example of an S-function which has nonsampled zero crossings to
 *      implement a saturation function. This S-function is designed to be
 *      used with a variable or fixed step solver.
 *
 *  A saturation is described by three equations
 *
 *    (1)     y = UpperLimit
 *    (2)     y = u
 *    (3)     y = LowerLimit
 *
 *  and a set of inequalities that specify which equation to use
 *
 *    if                          UpperLimit < u    then   use (1)
 *    if       LowerLimit <= u <= UpperLimit        then   use (2)
 *    if   u < LowerLimit                           then   use (3)
 *
 *  A key fact is that the valid equation 1, 2, or 3, can change at
 *  any instant.  Nonsampled zero crossing support helps the variable step
 *  solvers locate the exact instants when behavior switches from one equation
 *  to another.
 *
 *  Copyright 1990-2007 The MathWorks, Inc.
 */


#define S_FUNCTION_NAME  sfun_zc_sat
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

/*========================*
 * General Defines/macros *
 *========================*/

/* index to Upper Limit */
#define I_PAR_UPPER_LIMIT 0

/* index to Lower Limit */
#define I_PAR_LOWER_LIMIT 1

/* total number of block parameters */
#define N_PAR             2

/*
 *  Make access to mxArray pointers for parameters more readable.
 */
#define P_PAR_UPPER_LIMIT  ( ssGetSFcnParam(S,I_PAR_UPPER_LIMIT) )
#define P_PAR_LOWER_LIMIT  ( ssGetSFcnParam(S,I_PAR_LOWER_LIMIT) )

Эта S-функция затем реализует метод mdlCheckParameters, чтобы проверять валидность диалоговых параметров S-функции. Поскольку этот метод является дополнительным, оператор #define предшествует ему. Оператор #if defined проверяет, что эта функция скомпилирована как файл MEX, вместо для использования с продуктом Simulink Coder. Тело функции выполняет основные проверки, чтобы гарантировать, что пользователь ввел вектора действительных чисел равной длины для верхних и более низких пределов насыщения. Если параметр проверяет сбой, ошибки S-функции.

#define     MDL_CHECK_PARAMETERS
#if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)

  /* Function: mdlCheckParameters =============================================
   * Abstract:
   *   Check that parameter choices are allowable.
   */
  static void mdlCheckParameters(SimStruct *S)
  {
      int_T      i;
      int_T      numUpperLimit;
      int_T      numLowerLimit;
      const char *msg = NULL;

      /*
       * check parameter basics
       */
      for ( i = 0; i < N_PAR; i++ ) {
          if ( mxIsEmpty(    ssGetSFcnParam(S,i) ) ||
               mxIsSparse(   ssGetSFcnParam(S,i) ) ||
               mxIsComplex(  ssGetSFcnParam(S,i) ) ||
               !mxIsNumeric( ssGetSFcnParam(S,i) ) ) {
              msg = "Parameters must be real vectors.";
              goto EXIT_POINT;
          }
      }

      /*
       * Check sizes of parameters.
       */
      numUpperLimit = mxGetNumberOfElements( P_PAR_UPPER_LIMIT );
      numLowerLimit = mxGetNumberOfElements( P_PAR_LOWER_LIMIT );

      if ( ( numUpperLimit != 1             ) &&
           ( numLowerLimit != 1             ) &&
           ( numUpperLimit != numLowerLimit ) ) {
          msg = "Number of input and output values must be equal.";
          goto EXIT_POINT;
      }

      /*
       * Error exit point
       */
  EXIT_POINT:
      if (msg != NULL) {
          ssSetErrorStatus(S, msg);
      }
  }
#endif /* MDL_CHECK_PARAMETERS */

Необходимый метод S-функции mdlInitializeSizes настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции к два, как задано ранее в переменной N_PAR.

  • Если этот метод скомпилирован как файл MEX, ssGetSFcnParamsCount определяет, сколько параметров пользователь на самом деле ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров совпадает с номером, возвращенным ssGetNumSFcnParams, вызовы метода mdlCheckParameters, чтобы проверять валидность вводимых пользователями данных. В противном случае, ошибки S-функции.

  • Если проверка параметра передает, S-функция определяет максимальное количество элементов, в которые вводят или верхний или более низкий предельный параметр насыщения. Этот номер необходим позже, чтобы определить соответствующий вывод width.

  • Затем, номер непрерывных и дискретных состояний определяется с помощью ssSetNumContStates и ssSetNumDiscStates, соответственно. Этот пример не имеет никаких непрерывных или дискретных состояний.

  • Метод указывает, что S-функция имеет один выходной порт с помощью ssSetNumOutputPorts и устанавливает ширину этого выходного порта с помощью ssSetOutputPortWidth. Ширина выходного порта является или максимальным количеством элементов в верхнем или более низком пределе насыщения или динамически измерена. Подобный код задает один входной порт и указывает, что входной порт имеет прямое сквозное соединение путем передачи значения 1 к ssSetInputPortDirectFeedThrough.

  • ssSetNumSampleTimes инициализирует один шаг расчета, который функция mdlInitializeSampleTimes конфигурирует позже.

  • S-функция указывает, что никакие не работают, векторы используются путем передачи значения 0 к ssSetNumRWork, ssSetNumIWork, и т.д. Можно не использовать эти строки, потому что нуль является значением по умолчанию для всех них макросы. Однако для ясности, S-функция явным образом определяет номер векторов работы.

  • Метод инициализирует обнаружение пересечения нулем, работают векторы с помощью ssSetNumModes и ssSetNumNonsampledZCs. Метод mdlSetWorkWidths задает длину этих динамически размерных векторов позже.

  • Наконец, ssSetOptions устанавливает любые применимые опции. В этом случае SS_OPTION_EXCEPTION_FREE_CODE предусматривает, что код является свободным исключением, и SS_OPTION_ALLOW_INPUT_SCALAR_EXPANSION разрешает скалярное расширение входа, не имея необходимость обеспечивать функцию mdlSetInputPortWidth.

Функцию mdlInitializeSizes для этого примера показывают ниже.

/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *   Initialize the sizes array.
 */
static void mdlInitializeSizes(SimStruct *S)
{
    int_T numUpperLimit, numLowerLimit, maxNumLimit;

    /*
     * Set and Check parameter count
     */
    ssSetNumSFcnParams(S, N_PAR);

#if defined(MATLAB_MEX_FILE)
    if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) {
        mdlCheckParameters(S);
        if (ssGetErrorStatus(S) != NULL) {
            return;
        }
    } else {
        return; /* Parameter mismatch reported by the Simulink engine*/
    }
#endif

    /*
     * Get parameter size info.
     */
    numUpperLimit = mxGetNumberOfElements( P_PAR_UPPER_LIMIT );
    numLowerLimit = mxGetNumberOfElements( P_PAR_LOWER_LIMIT );

    if (numUpperLimit > numLowerLimit) {
        maxNumLimit = numUpperLimit;
    } else {
        maxNumLimit = numLowerLimit;
    }

    /*
     * states
     */
    ssSetNumContStates(S, 0);
    ssSetNumDiscStates(S, 0);

    /*
     * outputs
     *   The upper and lower limits are scalar expanded
     *   so their size determines the size of the output
     *   only if at least one of them is not scalar.
     */
    if (!ssSetNumOutputPorts(S, 1)) return;

    if ( maxNumLimit > 1 ) {
        ssSetOutputPortWidth(S, 0, maxNumLimit);
    } else {
        ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);
    }

    /*
     * inputs
     *   If the upper or lower limits are not scalar then
     *   the input is set to the same size.  However, the
     *   ssSetOptions below allows the actual width to
     *   be reduced to 1 if needed for scalar expansion.
     */
    if (!ssSetNumInputPorts(S, 1)) return;

    ssSetInputPortDirectFeedThrough(S, 0, 1 );

    if ( maxNumLimit > 1 ) {
        ssSetInputPortWidth(S, 0, maxNumLimit);
    } else {
        ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED);
    }

    /*
     * sample times
     */
    ssSetNumSampleTimes(S, 1);

    /*
     * work
     */
    ssSetNumRWork(S, 0);
    ssSetNumIWork(S, 0);
    ssSetNumPWork(S, 0);


    /*
     * Modes and zero crossings:
     * If we have a variable-step solver and this block has a continuous
     * sample time, then
     *   o One mode element will be needed for each scalar output
     *     in order to specify which equation is valid (1), (2), or (3).
     *   o Two ZC elements will be needed for each scalar output
     *     in order to help the solver find the exact instants
     *     at which either of the two possible "equation switches"
     *     One will be for the switch from eq. (1) to (2);
     *     the other will be for eq. (2) to (3) and vice versa.
     * otherwise
     *   o No modes and nonsampled zero crossings will be used.
     *
     */
    ssSetNumModes(S, DYNAMICALLY_SIZED);
    ssSetNumNonsampledZCs(S, DYNAMICALLY_SIZED);

    /*
     * options
     *   o No mexFunctions and no problematic mxFunctions are called
     *     so the exception free code option safely gives faster simulations.
     *   o Scalar expansion of the inputs is desired.  The option provides
     *     this without the need to  write mdlSetOutputPortWidth and
     *     mdlSetInputPortWidth functions.
     */
    ssSetOptions(S, ( SS_OPTION_EXCEPTION_FREE_CODE |
                      SS_OPTION_ALLOW_INPUT_SCALAR_EXPANSION));

} /* end mdlInitializeSizes */

Необходимый метод S-функции mdlInitializeSampleTimes задает частоты дискретизации S-функции. Входной параметр, который INHERITED_SAMPLE_TIME передал ssSetSampleTime, указывает, что эта S-функция наследовала свой шаг расчета от ее ведущего блока. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели.

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *    Specify that the block is continuous.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);
}

Дополнительный метод mdlSetWorkWidths инициализирует размер обнаружения пересечения нулем, работает векторы. Поскольку этот метод является дополнительным, оператор #define предшествует ему. Оператор #if defined проверяет, что S-функция компилируется как файл MEX. Обнаружение пересечения нулем может быть сделано только, когда S-функция запускается на уровне непрерывной выборки с помощью решателя переменного шага. Оператор if использует ssIsVariableStepSolver, ssGetSampleTime и ssGetOffsetTime, чтобы определить, соблюдают ли это условие. Если так, метод определяет номер режимов, равных ширине первого выходного порта и количеству невыбранных нулевых пересечений к дважды этой сумме. В противном случае метод обнуляет оба значения.

#define     MDL_SET_WORK_WIDTHS
#if defined(MDL_SET_WORK_WIDTHS) && defined(MATLAB_MEX_FILE)
/* Function: mdlSetWorkWidths ===============================================
 *   The width of the Modes and the ZCs depends on the width of the output.
 *   This width is not always known in mdlInitializeSizes so it is handled
 *   here.
 */
static void mdlSetWorkWidths(SimStruct *S)
{
    int nModes;
    int nNonsampledZCs;

    if (ssIsVariableStepSolver(S) &&
        ssGetSampleTime(S,0) == CONTINUOUS_SAMPLE_TIME &&
        ssGetOffsetTime(S,0) == 0.0) {

        int numOutput = ssGetOutputPortWidth(S, 0);

        /*
         * modes and zero crossings 
         *    o One mode element will be needed for each scalar output
         *      in order to specify which equation is valid (1), (2), or (3).
         *    o Two ZC elements will be needed for each scalar output
         *      in order to help the solver find the exact instants
         *      at which either of the two possible "equation switches"
         *      One will be for the switch from eq. (1) to (2);
         *      the other will be for eq. (2) to (3) and vice versa.
         */
        nModes         = numOutput;
        nNonsampledZCs = 2 * numOutput;
    } else {
        nModes         = 0;
        nNonsampledZCs = 0;
    }
    ssSetNumModes(S,nModes);
    ssSetNumNonsampledZCs(S,nNonsampledZCs);
}
#endif /* MDL_SET_WORK_WIDTHS */

После объявления переменных для сигналов ввода и вывода функции mdlOutputs используют оператор if-else, чтобы создать блоки кода, используемые, чтобы вычислить выходной сигнал на основе того, использует ли S-функция решатель переменного шага или фиксированный шаг. Оператор if запрашивает длину невыбранного вектора пересечения нулем. Если длина, установленная в mdlWorkWidths, является нулем, то никакое обнаружение пересечения нулем не сделано, и выходные сигналы вычисляются непосредственно от входных сигналов. В противном случае, использование функции, которое режим работает вектор, чтобы определить, как вычислить выходной сигнал. Если симуляция на главном временном шаге, т.е. ssIsMajorTimeStep возвращает true, mdlOutputs определяет, какой режим симуляция запускает в, или насыщаемый в верхнем пределе, насыщаемом в нижнем пределе, или не насыщаемая. Затем и для главных и для незначительных временных шагов, функция вычисляет вывод на основе этого режима. Если режим изменился между шагом предыдущего и текущего времени, то нулевое пересечение произошло. Функция mdlZeroCrossings, не mdlOutputs, указывает на это пересечение к решателю.

/* Function: mdlOutputs =======================================================
 * Abstract:
 *
 *  A saturation is described by three equations
 *
 *    (1)     y = UpperLimit
 *    (2)     y = u
 *    (3)     y = LowerLimit
 *
 *  When this block is used with a fixed-step solver or it has a noncontinuous
 *  sample time, the equations are used as it
 *
 *  Now consider the case of this block being used with a variable-step solver
 *  and it has a continuous sample time. Solvers work best on smooth problems.
 *  In order for the solver to work without chattering, limit cycles, or
 *  similar problems, it is absolutely crucial that the same equation be used
 *  throughout the duration of a MajorTimeStep. To visualize this, consider
 *  the case of the Saturation block feeding an Integrator block.
 *
 *  To implement this rule, the mode vector is used to specify the
 *  valid equation based on the following:
 *
 *    if                          UpperLimit < u    then   use (1)
 *    if       LowerLimit <= u <= UpperLimit        then   use (2)
 *    if   u < LowerLimit                           then   use (3)
 *
 *  The mode vector is changed only at the beginning of a MajorTimeStep.
 *
 *  During a minor time step, the equation specified by the mode vector
 *  is used without question.  Most of the time, the value of u will agree
 *  with the equation specified by the mode vector.  However, sometimes u's
 *  value will indicate a different equation.  Nonetheless, the equation
 *  specified by the mode vector must be used.
 * 
 *  When the mode and u indicate different equations, the corresponding
 *  calculations are not correct.  However, this is not a problem.  From
 *  the ZC function, the solver will know that an equation switch occurred
 *  in the middle of the last MajorTimeStep.  The calculations for that
 *  time step will be discarded.  The ZC function will help the solver
 *  find the exact instant at which the switch occurred.  Using this knowledge,
 *  the length of the MajorTimeStep will be reduced so that only one equation
 *  is valid throughout the entire time step.
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    InputRealPtrsType uPtrs     = ssGetInputPortRealSignalPtrs(S,0);
    real_T            *y        = ssGetOutputPortRealSignal(S,0);
    int_T             numOutput = ssGetOutputPortWidth(S,0);
    int_T             iOutput;

    /*
     * Set index and increment for input signal, upper limit, and lower limit 
     * parameters so that each gives scalar expansion if needed.
     */
    int_T  uIdx          = 0;
    int_T  uInc          = ( ssGetInputPortWidth(S,0) > 1 );
    const real_T *upperLimit   = mxGetPr( P_PAR_UPPER_LIMIT );
    int_T  upperLimitInc = ( mxGetNumberOfElements( P_PAR_UPPER_LIMIT ) > 1 );
    const real_T *lowerLimit   = mxGetPr( P_PAR_LOWER_LIMIT );
    int_T  lowerLimitInc = ( mxGetNumberOfElements( P_PAR_LOWER_LIMIT ) > 1 );

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

    if (ssGetNumNonsampledZCs(S) == 0) {
        /*
         * This block is being used with a fixed-step solver or it has
         * a noncontinuous sample time, so we always saturate.
         */
        for (iOutput = 0; iOutput < numOutput; iOutput++) {
            if (*uPtrs[uIdx] >= *upperLimit) {
                *y++ = *upperLimit;
            } else if (*uPtrs[uIdx] > *lowerLimit) {
                *y++ = *uPtrs[uIdx];
            } else {
                *y++ = *lowerLimit;
            }

            upperLimit += upperLimitInc;
            lowerLimit += lowerLimitInc;
            uIdx       += uInc;
        }

    } else {
        /*
         * This block is being used with a variable-step solver.
         */
        int_T *mode = ssGetModeVector(S);

        /*
         * Specify indices for each equation.
         */
        enum { UpperLimitEquation, NonLimitEquation, LowerLimitEquation };

        /*
         * Update the Mode Vector ONLY at the beginning of a MajorTimeStep
         */
        if ( ssIsMajorTimeStep(S) ) {
            /*
             * Specify the mode, ie the valid equation for each output scalar.
             */
            for ( iOutput = 0; iOutput < numOutput; iOutput++ ) {
                if ( *uPtrs[uIdx] > *upperLimit ) {
                    /*
                     * Upper limit eq is valid.
                     */
                    mode[iOutput] = UpperLimitEquation;
                } else if ( *uPtrs[uIdx] < *lowerLimit ) {
                    /*
                     * Lower limit eq is valid.
                     */
                    mode[iOutput] = LowerLimitEquation;
                } else {
                    /*
                     * Nonlimit eq is valid.
                     */
                    mode[iOutput] = NonLimitEquation;
                }
                /*
                 * Adjust indices to give scalar expansion if needed.
                 */
                uIdx       += uInc;
                upperLimit += upperLimitInc;
                lowerLimit += lowerLimitInc;
            }

            /*
             * Reset index to input and limits.
             */
            uIdx       = 0;
            upperLimit = mxGetPr( P_PAR_UPPER_LIMIT );
            lowerLimit = mxGetPr( P_PAR_LOWER_LIMIT );

        } /* end IsMajorTimeStep */

        /*
         * For both MinorTimeSteps and MajorTimeSteps calculate each scalar
         * output using the equation specified by the mode vector.
         */
        for ( iOutput = 0; iOutput < numOutput; iOutput++ ) {
            if ( mode[iOutput] == UpperLimitEquation ) {
                /*
                 * Upper limit eq.
                 */
                *y++ = *upperLimit;
            } else if ( mode[iOutput] == LowerLimitEquation ) {
                /*
                 * Lower limit eq.
                 */
                *y++ = *lowerLimit;
            } else {
                /*
                 * Nonlimit eq.
                 */
                *y++ = *uPtrs[uIdx];
            }

            /*
             * Adjust indices to give scalar expansion if needed.
             */
            uIdx       += uInc;
            upperLimit += upperLimitInc;
            lowerLimit += lowerLimitInc;
        }
    }
} /* end mdlOutputs */

Метод mdlZeroCrossings определяет, произошло ли нулевое пересечение между шагом предыдущего и текущего времени. Метод получает указатель на входной сигнал с помощью ssGetInputPortRealSignalPtrs. Сравнение значения этого сигнала к значению верхних и более низких пределов насыщения определяет значения для элементов невыбранного вектора пересечения нулем. Если какой-либо элемент невыбранных переключателей вектора пересечения нулем от отрицательного до положительного, или положительный отрицанию, нулевое пересечение произошло. В случае нулевого пересечения механизм Simulink изменяет размер шага и повторно вычисляет выходные параметры, чтобы попытаться определить местоположение точного нулевого пересечения.

#define     MDL_ZERO_CROSSINGS
#if defined(MDL_ZERO_CROSSINGS) && (defined(MATLAB_MEX_FILE) || defined(NRT))

/* Function: mdlZeroCrossings =================================================
 * Abstract:
 *  This will only be called if the number of nonsampled zero crossings is
 *  greater than 0 which means this block has a continuous sample time and the
 *  model is using a variable-step solver.
 *
 *  Calculate zero crossing (ZC) signals that help the solver find the
 *  exact instants at which equation switches occur:
 *
 *    if                          UpperLimit < u    then   use (1)
 *    if       LowerLimit <= u <= UpperLimit        then   use (2)
 *    if   u < LowerLimit                           then   use (3)
 *
 *  The key words are help find. There is no choice of a function that will
 *  direct the solver to the exact instant of the change. The solver will
 *  track the zero crossing signal and do a bisection style search for the
 *  exact instant of equation switch.
 *
 *  There is generally one ZC signal for each pair of signals that can
 *  switch.  The three equations above would break into two pairs (1)&(2)
 *  and (2)&(3).  The  possibility of a "long jump" from (1) to (3) does
 *  not need to be handled as a separate case.  It is implicitly handled.
 *
 *  When ZCs are calculated, the value is normally used twice.  When it is
 *  first calculated, it is used as the end of the current time step.  Later,
 *  it will be used as the beginning of the following step.
 *
 *  The sign of the ZC signal always indicates an equation from the pair.  For
 *  S-functions, which equation is associated with a positive ZC and which is
 *  associated with a negative ZC doesn't really matter.  If the ZC is positive
 *  at the beginning and at the end of the time step, this implies that the
 *  "positive" equation was valid throughout the time step.  Likewise, if the
 *  ZC is negative at the beginning and at the end of the time step, this
 *  implies that the "negative" equation was valid throughout the time step.
 *  Like any other nonlinear solver, this is not foolproof, but it is an
 *  excellent indicator.  If the ZC has a different sign at the beginning and
 *  at the end of the time step, then a equation switch definitely occurred
 *  during the time step.
 *
 *  Ideally, the ZC signal gives an estimate of when an equation switch
 *  occurred.  For example, if the ZC signal is -2 at the beginning and +6 at
 *  the end, then this suggests that the switch occurred 
 *  25% = 100%*(-2)/(-2-(+6)) of the way into the time step.  It will almost
 *  never be true that 25% is perfectly correct.  There is no perfect choice
 *  for a ZC signal, but there are some good rules.  First, choose the ZC
 *  signal to be continuous.  Second, choose the ZC signal to give a monotonic
 *  measure of the "distance" to a signal switch; strictly monotonic is ideal.
 */
static void mdlZeroCrossings(SimStruct *S)
{
    int_T             iOutput;
    int_T             numOutput = ssGetOutputPortWidth(S,0);
    real_T            *zcSignals = ssGetNonsampledZCs(S);
    InputRealPtrsType uPtrs      = ssGetInputPortRealSignalPtrs(S,0);

    /*
     * Set index and increment for the input signal, upper limit, and lower 
     * limit parameters so that each gives scalar expansion if needed.
     */
    int_T  uIdx          = 0;
    int_T  uInc          = ( ssGetInputPortWidth(S,0) > 1 );
    real_T *upperLimit   = mxGetPr( P_PAR_UPPER_LIMIT );
    int_T  upperLimitInc = ( mxGetNumberOfElements( P_PAR_UPPER_LIMIT ) > 1 );
    real_T *lowerLimit   = mxGetPr( P_PAR_LOWER_LIMIT );
    int_T  lowerLimitInc = ( mxGetNumberOfElements( P_PAR_LOWER_LIMIT ) > 1 );

    /*
     * For each output scalar, give the solver a measure of "how close things
     * are" to an equation switch.
     */
    for ( iOutput = 0; iOutput < numOutput; iOutput++ ) {

        /*  The switch from eq (1) to eq (2)
         *
         *    if                          UpperLimit < u    then   use (1)
         *    if       LowerLimit <= u <= UpperLimit        then   use (2)
         *
         *  is related to how close u is to UpperLimit.  A ZC choice
         *  that is continuous, strictly monotonic, and is
         *    u - UpperLimit 
         *  or it is negative.
         */
        zcSignals[2*iOutput] = *uPtrs[uIdx] - *upperLimit;

        /*  The switch from eq (2) to eq (3)
         *
         *    if       LowerLimit <= u <= UpperLimit        then   use (2)
         *    if   u < LowerLimit                           then   use (3)
         *
         *  is related to how close u is to LowerLimit.  A ZC choice
         *  that is continuous, strictly monotonic, and is
         *    u - LowerLimit.
         */
        zcSignals[2*iOutput+1] = *uPtrs[uIdx] - *lowerLimit;

        /*
         * Adjust indices to give scalar expansion if needed.
         */
        uIdx       += uInc;
        upperLimit += upperLimitInc;
        lowerLimit += lowerLimitInc;
    }
}

#endif /* end mdlZeroCrossings */

S-функция завершает необходимой функцией mdlTerminate. В этом примере функция пуста.

/* Function: mdlTerminate =====================================================
 * Abstract:
 *    No termination needed, but we are required to have this routine.
 */
static void mdlTerminate(SimStruct *S)
{
    UNUSED_ARG(S); /* unused input argument */
}

Необходимый трейлер S-функции включает файлы, необходимые для симуляции или генерации кода, можно следующим образом.

#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

Примечание

mdlOutputs и функции mdlTerminate используют макрос UNUSED_ARG, чтобы указать, что входной параметр, которого требует коллбэк, не используется. Этот дополнительный макрос задан в simstruc_types.h. Если используется, необходимо вызвать этот макрос однажды для каждого входного параметра, который не использует коллбэк.

Разрывы в непрерывных состояниях

S-функция в качестве примера stvctf.c демонстрирует изменяющуюся во времени непрерывную передаточную функцию. Следующая модель Simulink использует эту S-функцию.

sfcndemo_stvctf

S-функция демонстрирует, как работать с решателями так, чтобы симуляция поддержала непротиворечивость, что означает, что блок поддерживает сглаженные и сопоставимые сигналы для интеграторов несмотря на то, что уравнения, которые интегрируются, изменяются.

matlabroot/toolbox/simulink/simdemos/simfeatures/src/stvctf.c

S-функция stvctf.c начинается с операторов #define для S-имени-функции и уровня, наряду с оператором #include для заголовка simstruc.h. После этих операторов S-функция включает или задает любые другие необходимые заголовки, данные, и т.д. Этот пример задает параметры для числителя и знаменателя передаточной функции, которые вводятся в диалоговое окно S-функции. Комментарии в начале этой S-функции предоставляют дополнительную информацию о цели векторов работы в этом примере.

/*
 * File : stvctf.c
 * Abstract:
 *      Time Varying Continuous Transfer Function block
 *
 *      This S-function implements a continuous time transfer function
 *      whose transfer function polynomials are passed in via the input
 *      vector.  This is useful for continuous time adaptive control
 *      applications.
 *
 *      This S-function is also an example of how to use banks to avoid
 *      problems with computing derivatives when a continuous output has 
 *      discontinuities. The consistency checker can be used to verify that 
 *      your S-function is correct with respect to always maintaining smooth 
 *      and consistent signals for the integrators. By consistent we mean that
 *      two mdlOutputs calls at major time t and minor time t are always the 
 *      same. The consistency checker is enabled on the diagnostics page of the
 * 		Configuraion parameters dialog box. The update method of this S-function
 *      modifies the coefficients of the transfer function, which cause the
 *      output to "jump." To have the simulation work properly, we need to let
 *      the solver know of these discontinuities by setting 
 *      ssSetSolverNeedsReset and then we need to use multiple banks of 
 *      coefficients so the coefficients used in the major time step output 
 *      and the minor time step outputs are the same. In the simulation loop 
 *      we have:
 *        Loop:
 *          o Output in major time step at time t
 *          o Update in major time step at time t
 *          o Integrate (minor time step):
 *              o Consistency check: recompute outputs at time t and compare
 *                with current outputs.
 *              o Derivatives at time t
 *              o One or more Output,Derivative evaluations at time t+k
 *                where k <= step_size to be taken.
 *              o Compute state, x
 *              o t = t + step_size
 *            End_Integrate
 *        End_Loop
 *      Another purpose of the consistency checker is to verify that when 
 *      the solver needs to try a smaller step_size, the recomputing of 
 *      the output and derivatives at time t doesn't change. Step size 
 *      reduction occurs when tolerances aren't met for the current step size.
 *      The ideal ordering would be to update after integrate. To achieve 
 *      this we have two banks of coefficients. And the use of the new 
 *      coefficients, which were computed in update, is delayed until after 
 *      the integrate phase is complete.
 *
 *	This block has multiple sample times and will not work correctly
 *	in a multitasking environment. It is designed to be used in
 *	a single tasking (or variable step) simulation environment.
 *	Because this block accesses the input signal in both tasks,
 *	it cannot specify the sample times of the input and output ports
 *	(SS_OPTION_PORT_SAMPLE_TIMES_ASSIGNED).
 *
 * See simulink/src/sfuntmpl_doc.c.
 *
 * Copyright 1990-7 The MathWorks, Inc.
 */

#define S_FUNCTION_NAME  stvctf
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

/*
 * Defines for easy access to the numerator and denominator polynomials
 * parameters
 */
#define NUM(S)  ssGetSFcnParam(S, 0)
#define DEN(S)  ssGetSFcnParam(S, 1)
#define TS(S)   ssGetSFcnParam(S, 2)
#define NPARAMS 3

Эта S-функция реализует метод mdlCheckParameters, чтобы проверять валидность диалоговых параметров S-функции. Поскольку этот метод является дополнительным, оператор #define предшествует ему. Оператор #if defined проверяет, что эта функция скомпилирована как файл MEX, вместо для использования с продуктом Simulink Coder. Тело функции выполняет основные проверки, чтобы гарантировать, что пользователь ввел вектора действительных чисел для числителя и знаменателя, и что знаменатель имеет высший порядок, чем числитель. Если проверка параметра перестала работать, ошибки S-функции.

#define MDL_CHECK_PARAMETERS
#if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)
  /* Function: mdlCheckParameters =============================================
   * Abstract:
   *    Validate our parameters to verify:
   *     o The numerator must be of a lower order than the denominator.
   *     o The sample time must be a real positive nonzero value.
   */
  static void mdlCheckParameters(SimStruct *S)
  {
      int_T i;

      for (i = 0; i < NPARAMS; i++) {
          real_T *pr;
          int_T   el;
          int_T   nEls;
          if (mxIsEmpty(    ssGetSFcnParam(S,i)) ||
              mxIsSparse(   ssGetSFcnParam(S,i)) ||
              mxIsComplex(  ssGetSFcnParam(S,i)) ||
              !mxIsNumeric( ssGetSFcnParam(S,i)) ) {
              ssSetErrorStatus(S,"Parameters must be real finite vectors");
              return;
          } 
          pr   = mxGetPr(ssGetSFcnParam(S,i));
          nEls = mxGetNumberOfElements(ssGetSFcnParam(S,i));
          for (el = 0; el < nEls; el++) {
              if (!mxIsFinite(pr[el])) {
                  ssSetErrorStatus(S,"Parameters must be real finite vectors");
                  return;
              }
          }
      }

      if (mxGetNumberOfElements(NUM(S)) > mxGetNumberOfElements(DEN(S)) &&
          mxGetNumberOfElements(DEN(S)) > 0  && *mxGetPr(DEN(S)) != 0.0) {
          ssSetErrorStatus(S,"The denominator must be of higher order than "
                           "the numerator, nonempty and with first "
                           "element nonzero");
          return;
      }

      /* xxx verify finite */
      if (mxGetNumberOfElements(TS(S)) != 1 || mxGetPr(TS(S))[0] <= 0.0) {
          ssSetErrorStatus(S,"Invalid sample time specified");
          return;
      }
  }
#endif /* MDL_CHECK_PARAMETERS */

Необходимый метод S-функции mdlInitializeSizes затем настраивает следующие характеристики S-функции.

  • ssSetNumSFcnParams определяет номер ожидаемых диалоговых параметров S-функции к три, как задано ранее в переменной NPARAMS.

  • Если этот метод скомпилирован как файл MEX, ssGetSFcnParamsCount определяет, сколько параметров пользователь ввел в диалоговое окно S-функции. Если количество заданных пользователями параметров совпадает с номером, возвращенным ssGetNumSFcnParams, вызовы метода mdlCheckParameters, чтобы проверять валидность вводимых пользователями данных. В противном случае, ошибки S-функции.

  • Если проверка параметра передает, S-функция задает количество непрерывных и дискретных состояний с помощью ssSetNumContStates и ssSetNumDiscStates, соответственно. Этот пример не имеет никаких дискретных состояний и определяет номер непрерывных состояний на основе количества коэффициентов в знаменателе передаточной функции.

  • Затем, ssSetNumInputPorts указывает, что S-функция имеет один входной порт и устанавливает его ширину на одну плюс дважды длина знаменателя с помощью ssSetInputPortWidth. Метод использует значение, обеспеченное третьим диалоговым параметром S-функции как шаг расчета входного порта. Этот параметр указывает на уровень, на котором передаточная функция изменяется во время симуляции. S-функция указывает, что входной порт имеет прямое сквозное соединение путем передачи значения 1 к ssSetInputPortDirectFeedThrough.

  • ssSetNumOutputPorts указывает, что S-функция имеет один выходной порт. Метод использует ssSetOutputPortWidth, чтобы установить ширину этого выходного порта, ssSetOutputPortSampleTime указывать, что выходной порт имеет время непрерывной выборки и ssSetOutputPortOffsetTime, чтобы обнулить время смещения.

  • ssSetNumSampleTimes затем инициализирует два шага расчета, которые функция mdlInitializeSampleTimes конфигурирует позже.

  • Метод передает значение четыре раза количества коэффициентов знаменателя к ssSetNumRWork в порядке установить длину вектора работы с плавающей точкой. ssSetNumIWork затем устанавливает длину целочисленного вектора работы к два. Векторы RWork хранят два банка коэффициентов передаточной функции, в то время как вектор IWork указывает, какой банк в векторе RWork используется в настоящее время. S-функция устанавливает длину всех других векторов работы обнулять. Можно не использовать эти строки, потому что нуль является значением по умолчанию для них макросы. Однако для ясности, S-функция явным образом определяет номер векторов работы.

  • Наконец, ssSetOptions устанавливает любые применимые опции. В этом случае SS_OPTION_EXCEPTION_FREE_CODE предусматривает, что код является свободным исключением.

Функцию mdlInitializeSizes для этого примера показывают ниже.

/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    Determine the S-function block's characteristics:
 *    number of inputs, outputs, states, etc.
 */
static void mdlInitializeSizes(SimStruct *S)
{
    int_T nContStates;
    int_T nCoeffs;

    /* See sfuntmpl_doc.c for more details on the macros below. */
 
    ssSetNumSFcnParams(S, NPARAMS);  /* Number of expected parameters. */
#if defined(MATLAB_MEX_FILE)
    if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) {
        mdlCheckParameters(S);
        if (ssGetErrorStatus(S) != NULL) {
            return;
        }
    } else {
        return; /* Parameter mismatch reported by the Simulink engine*/
    }
#endif


    /*
     * Define the characteristics of the block:
     *
     *   Number of continuous states:     length of denominator - 1
     *   Inputs port width                2 * (NumContStates+1) + 1
     *   Output port width                1
     *   DirectFeedThrough:               0 (Although this should be computed.
     *                                       We'll assume coefficients entered
     *                                       are strictly proper).
     *   Number of sample times:          2 (continuous and discrete)
     *   Number of Real work elements:    4*NumCoeffs
     *                                    (Two banks for num and den coeff's:
     *                                     NumBank0Coeffs
     *                                     DenBank0Coeffs
     *                                     NumBank1Coeffs
     *                                     DenBank1Coeffs)
     *   Number of Integer work elements: 2 (indicator of active bank 0 or 1
     *                                       and flag to indicate when banks
     *                                       have been updated).
     *
     * The number of inputs arises from the following:
     *   o 1 input (u)
     *   o the numerator and denominator polynomials each have NumContStates+1
     *     coefficients
     */
    nCoeffs     = mxGetNumberOfElements(DEN(S));
    nContStates = nCoeffs - 1;

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

    if (!ssSetNumInputPorts(S, 1)) return;
    ssSetInputPortWidth(S, 0, 1 + (2*nCoeffs));
    ssSetInputPortDirectFeedThrough(S, 0, 0);
    ssSetInputPortSampleTime(S, 0, mxGetPr(TS(S))[0]);
    ssSetInputPortOffsetTime(S, 0, 0);

    if (!ssSetNumOutputPorts(S,1)) return;
    ssSetOutputPortWidth(S, 0, 1);
    ssSetOutputPortSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
    ssSetOutputPortOffsetTime(S, 0, 0);

    ssSetNumSampleTimes(S, 2);

    ssSetNumRWork(S, 4 * nCoeffs);
    ssSetNumIWork(S, 2);
    ssSetNumPWork(S, 0);

    ssSetNumModes(S, 0);
    ssSetNumNonsampledZCs(S, 0);

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

} /* end mdlInitializeSizes */

Необходимый метод S-функции mdlInitializeSampleTimes задает частоты дискретизации S-функции. Первый вызов ssSetSampleTime указывает, что первая частота дискретизации непрерывна, и последующий вызов ssSetOffsetTime обнуляет смещение. Второй вызов этой пары макросов устанавливает второй шаг расчета на значение третьего S-параметра-функции со смещением нуля. Вызов ssSetModelReferenceSampleTimeDefaultInheritance говорит решателю использовать правило по умолчанию, чтобы определить, могут ли модели, на которые ссылаются, содержащие эту S-функцию, наследовать свои шаги расчета от родительской модели.

/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *      This function is used to specify the sample time(s) for the
 *      S-function.  This S-function has two sample times.  The
 *      first, a continuous sample time, is used for the input to the
 *      transfer function, u.  The second, a discrete sample time
 *      provided by the user, defines the rate at which the transfer
 *      function coefficients are updated.
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
    /*
     * the first sample time, continuous
     */
    ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
    ssSetOffsetTime(S, 0, 0.0);

    /*
     * the second, discrete sample time, is user provided
     */
    ssSetSampleTime(S, 1, mxGetPr(TS(S))[0]);
    ssSetOffsetTime(S, 1, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S);


} /* end mdlInitializeSampleTimes */

Дополнительный метод S-функции mdlInitializeConditions инициализирует непрерывный вектор состояния и начальный числитель и векторы знаменателя. Оператор #define перед этим методом требуется для механизма Simulink вызвать эту функцию. Функция инициализирует непрерывные состояния, чтобы обнулить. Числитель и коэффициенты знаменателя инициализируются от первых двух S-параметров-функции, нормированных первым коэффициентом знаменателя. Функция устанавливает значение, сохраненное в векторе IWork, чтобы обнулить, указать, что первый банк числителя и коэффициентов знаменателя, сохраненных в векторе RWork, используется в настоящее время.

#define MDL_INITIALIZE_CONDITIONS
/* Function: mdlInitializeConditions ==========================================
 * Abstract:
 *      Initialize the states, numerator and denominator coefficients.
 */
static void mdlInitializeConditions(SimStruct *S)
{
    int_T  i;
    int_T  nContStates = ssGetNumContStates(S);
    real_T *x0           = ssGetContStates(S);
    int_T  nCoeffs       = nContStates + 1;
    real_T *numBank0     = ssGetRWork(S);
    real_T *denBank0     = numBank0 + nCoeffs;
    int_T *activeBank    = ssGetIWork(S);

    /*
     * The continuous states are all initialized to zero.
     */
    for (i = 0; i < nContStates; i++) {
        x0[i]       = 0.0;
        numBank0[i] = 0.0;
        denBank0[i] = 0.0;
    }
    numBank0[nContStates] = 0.0;
    denBank0[nContStates] = 0.0;

    /*
     * Set up the initial numerator and denominator.
     */
    {
        const real_T *numParam   = mxGetPr(NUM(S));
        int          numParamLen = mxGetNumberOfElements(NUM(S));

        const real_T *denParam   = mxGetPr(DEN(S));
        int          denParamLen = mxGetNumberOfElements(DEN(S));
        real_T       den0        = denParam[0];

        for (i = 0; i < denParamLen; i++) {
            denBank0[i] = denParam[i] / den0;
        }

        for (i = 0; i < numParamLen; i++) {
            numBank0[i] = numParam[i] / den0;
        }
    }

    /*
     * Normalize if this transfer function has direct feedthrough.
     */
    for (i = 1; i < nCoeffs; i++) {
        numBank0[i] -= denBank0[i]*numBank0[0];
    }

    /*
     * Indicate bank0 is active (i.e. bank1 is oldest).
     */
    *activeBank = 0;

} /* end mdlInitializeConditions */

Функция mdlOutputs вычисляет выходные сигналы S-функции, когда S-функция моделирует в непрерывной задаче, т.е. ssIsContinuousTask является true. Если симуляция также на главном временном шаге, проверки mdlOutputs, если числитель и коэффициенты знаменателя должны быть обновлены, как обозначено переключателем в активном банке, сохраненном в векторе IWork. И на главных и на незначительных временных шагах, S-функция вычисляет вывод с помощью коэффициентов числителя, сохраненных в активном банке.

/* Function: mdlOutputs =======================================================
 * Abstract:
 *      The outputs for this block are computed by using a controllable state-
 *      space representation of the transfer function.
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    if (ssIsContinuousTask(S,tid)) {
        int               i;
        real_T            *num;
        int               nContStates = ssGetNumContStates(S);
        real_T            *x          = ssGetContStates(S);
        int_T             nCoeffs     = nContStates + 1;
        InputRealPtrsType uPtrs       = ssGetInputPortRealSignalPtrs(S,0);
        real_T            *y          = ssGetOutputPortRealSignal(S,0);
        int_T             *activeBank = ssGetIWork(S);

        /*
         * Switch banks because we've updated them in mdlUpdate and we're no 
         * longer in a minor time step.
         */
        if (ssIsMajorTimeStep(S)) {
            int_T *banksUpdated = ssGetIWork(S) + 1;
            if (*banksUpdated) {
                *activeBank = !(*activeBank);
                *banksUpdated = 0;
                /*
                 * Need to tell the solvers that the derivatives are no
                 * longer valid.
                 */
                ssSetSolverNeedsReset(S);
            }
        }
        num = ssGetRWork(S) + (*activeBank) * (2*nCoeffs);

        /*
         * The continuous system is evaluated using a controllable state space
         * representation of the transfer function.  This implies that the
         * output of the system is equal to:
         *
         *     y(t) = Cx(t) + Du(t)
         *          = [ b1 b2 ... bn]x(t) + b0u(t)
         *
         * where b0, b1, b2, ... are the coefficients of the numerator 
         * polynomial:
         *
         *    B(s) = b0 s^n + b1 s^n-1 + b2 s^n-2 + ... + bn-1 s + bn
         */
        *y = *num++ * (*uPtrs[0]);
        for (i = 0; i < nContStates; i++) {
            *y += *num++ * *x++;
        }
    }

} /* end mdlOutputs */

Несмотря на то, что этот пример не имеет никаких дискретных состояний, метод все еще реализует функцию mdlUpdate, чтобы обновить коэффициенты передаточной функции на каждом главном временном шаге. Поскольку этот метод является дополнительным, оператор #define предшествует ему. Метод использует ssGetInputPortRealSignalPtrs, чтобы получить указатель на входной сигнал. Значения входного сигнала становятся новыми коэффициентами передаточной функции, которые S-функция хранит в банке неактивного вектора RWork. Когда функция mdlOutputs позже вызвана на этом главном временном шаге, она обновляет активный банк, чтобы быть этим обновленным банком коэффициентов.

#define MDL_UPDATE
/* Function: mdlUpdate ========================================================
 * Abstract:
 *      Every time through the simulation loop, update the
 *      transfer function coefficients. Here we update the oldest bank.
 */
static void mdlUpdate(SimStruct *S, int_T tid)
{
    if (ssIsSampleHit(S, 1, tid)) {
        int_T             i;
        InputRealPtrsType uPtrs        = ssGetInputPortRealSignalPtrs(S,0);
        int_T             uIdx         = 1;/*1st coeff is after signal input*/
        int_T             nContStates  = ssGetNumContStates(S);
        int_T             nCoeffs      = nContStates + 1;
        int_T             bankToUpdate = !ssGetIWork(S)[0];
        real_T            *num         = ssGetRWork(S)+bankToUpdate*2*nCoeffs;
        real_T            *den         = num + nCoeffs;

        real_T            den0;
        int_T             allZero;


        /*
         * Get the first denominator coefficient.  It will be used
         * for normalizing the numerator and denominator coefficients.
         *
         * If all inputs are zero, we probably could have unconnected
         * inputs, so use the parameter as the first denominator coefficient.
         */
        den0 = *uPtrs[uIdx+nCoeffs];
        if (den0 == 0.0) {
            den0 = mxGetPr(DEN(S))[0];
        }

        /*
         * Grab the numerator.
         */
        allZero = 1;
        for (i = 0; (i < nCoeffs) && allZero; i++) {
            allZero &= *uPtrs[uIdx+i] == 0.0;
        }

        if (allZero) { /* if numerator is all zero */
            const real_T *numParam   = mxGetPr(NUM(S));
            int_T        numParamLen = mxGetNumberOfElements(NUM(S));
            /*
             * Move the input to the denominator input and
             * get the denominator from the input parameter.
             */
            uIdx += nCoeffs;
            num += nCoeffs - numParamLen;
            for (i = 0; i < numParamLen; i++) {
                *num++ = *numParam++ / den0;
            }
        } else {
            for (i = 0; i < nCoeffs; i++) {
                *num++ = *uPtrs[uIdx++] / den0;
            }
        }

        /*
         * Grab the denominator.
         */
        allZero = 1;
        for (i = 0; (i < nCoeffs) && allZero; i++) {
            allZero &= *uPtrs[uIdx+i] == 0.0;
        }

        if (allZero) {  /* If denominator is all zero. */
            const real_T *denParam   = mxGetPr(DEN(S));
            int_T        denParamLen = mxGetNumberOfElements(DEN(S));

            den0 = denParam[0];
            for (i = 0; i < denParamLen; i++) {
                *den++ = *denParam++ / den0;
            }
        } else {
            for (i = 0; i < nCoeffs; i++) {
                *den++ = *uPtrs[uIdx++] / den0;
            }
        }

        /*
         * Normalize if this transfer function has direct feedthrough.
         */
        num = ssGetRWork(S) + bankToUpdate*2*nCoeffs;
        den = num + nCoeffs;
        for (i = 1; i < nCoeffs; i++) {
            num[i] -= den[i]*num[0];
        }

        /*
         * Indicate oldest bank has been updated.
         */
        ssGetIWork(S)[1] = 1;
    }

} /* end mdlUpdate */

Функция mdlDerivatives вычисляет непрерывные производные состояния. Функция использует коэффициенты от активного банка, чтобы решить управляемое представление пространства состояний передаточной функции.

#define MDL_DERIVATIVES
/* Function: mdlDerivatives ===================================================
 * Abstract:
 *      The derivatives for this block are computed by using a controllable 
 *      state-space representation of the transfer function.
 */
static void mdlDerivatives(SimStruct *S) 
{
    int_T             i;
    int_T             nContStates = ssGetNumContStates(S);
    real_T            *x          = ssGetContStates(S);
    real_T            *dx         = ssGetdX(S);
    int_T             nCoeffs     = nContStates + 1;
    int_T             activeBank  = ssGetIWork(S)[0];
    const real_T      *num        = ssGetRWork(S) + activeBank*(2*nCoeffs);
    const real_T      *den        = num + nCoeffs;
    InputRealPtrsType uPtrs       = ssGetInputPortRealSignalPtrs(S,0);

    /*
     * The continuous system is evaluated using a controllable state-space
     * representation of the transfer function.  This implies that the
     * next continuous states are computed using:
     *
     *     dx = Ax(t) + Bu(t)
     *        = [-a1 -a2 ... -an] [x1(t)] + [u(t)]
     *          [  1  0  ...   0] [x2(t)] + [0]
     *          [  0  1  ...   0] [x3(t)] + [0]
     *          [  .  .  ...   .]   .     +  .
     *          [  .  .  ...   .]   .     +  .
     *          [  .  .  ...   .]   .     +  .
     *          [  0  0  ... 1 0] [xn(t)] + [0]
     *
     * where a1, a2, ... are the coefficients of the numerator polynomial:
     *
     *    A(s) = s^n + a1 s^n-1 + a2 s^n-2 + ... + an-1 s + an
     */
    dx[0] = -den[1] * x[0] + *uPtrs[0];
    for (i = 1; i < nContStates; i++) {
        dx[i] = x[i-1];
        dx[0] -= den[i+1] * x[i];
    }

} /* end mdlDerivatives */

Необходимая функция mdlTerminate выполняет любые действия, такие как освобождение памяти, необходимой в конце симуляции. В этом примере функция пуста.

/* Function: mdlTerminate =====================================================
 * Abstract:
 *      Called when the simulation is terminated.
 *      For this block, there are no end of simulation tasks.
 */
static void mdlTerminate(SimStruct *S)
{
    UNUSED_ARG(S); /* unused input argument */
} /* end mdlTerminate */

Необходимый трейлер S-функции включает файлы, необходимые для симуляции или генерации кода, можно следующим образом.

#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

Примечание

Функция mdlTerminate использует макрос UNUSED_ARG, чтобы указать, что входной параметр, которого требует коллбэк, не используется. Этот дополнительный макрос задан в simstruc_types.h. Если используется, необходимо вызвать этот макрос однажды для каждого входного параметра, который не использует коллбэк.

Смотрите также

| |

Похожие темы