Sirius' blog Sirius' blog
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Sirius0v0

怕什么真理无穷,进一寸有一寸的欢喜
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 学习笔记

    • Matlab在数模上的应用——基础打杂篇
    • Matlab在制导控制的应用(科学计算)
    • Matlab在制导控制的应用(线性控制)
    • S-Function入门——基础介绍
    • Level-1 Matlab S-Function编写入门
    • C-Mex S-Function编写入门
      • C-Mex S-Function的调用顺序
      • C-Mex S-Function文件的主要组成
        • 文件表头说明
        • 初始化函数 mdlInitializeSizes
        • 初始化采样时间 mdlInitializeSampleTimes
        • 开始函数 mdlStart
        • 初始化状态变量 mdlInitializeConditions
        • 输出函数 mdlOutputs
        • 更新函数 mdlUpdate
        • 仿真结束函数 mdlTerminate
        • 文件结尾说明
      • 模块化编程与链接静态库
  • 踩坑记录

  • 《MATLAB》与控制
  • 学习笔记
Sirius0v0
2023-08-13
目录

C-Mex S-Function编写入门

# C-Mex S-Function的调用顺序

参考官网文档Simulink引擎如何与C-S函数交互 (opens new window),文中介绍了C-Mex S-Function的调用顺序如下图:

大致可总结为:

  • 设置输入、输出、状态和参数个数(mdlInitializeSizes);
  • 初始化采样时间(mdlInitializeSampleTimes);
  • 仿真开始前调用一次,在此可以填入相关准备工作(mdlStart);
  • 初始化状态变量(mdlInitializeConditions);
  • 进入仿真循环
    • 当参数改变时则调用mdlProcessParameters函数;
    • 当采样时间可变时,调用mdlGetTimeOfNextVarHit;
    • 计算输出(mdlOutputs);
    • 更新离散状态(mdlUpdate);
    • 当系统含有连续状态时调用该子循环,计算连续状态的导数和输出
      • mdlDerivatives;
      • mdlOutputs;
      • mdlDerivatives;
    • 当检测到过零时调用该子循环
      • mdlOutputs;
      • mdlZeroCrossings;
  • 当仿真结束时调用mdlTerminate函数做必要的清理工作。

# C-Mex S-Function文件的主要组成

# 文件表头说明

根据C-Mex S-Function的编写规则,在每个C-Mex S-Function文件头部包含如下宏定义和头文件引用声明:

// 说明函数的等级
#define S_FUNCTION_LEVEL 2
// 定义S-Function的名称
#define S_FUNCTION_NAME  sfun_name
// 包含 SimStruct 类型
#include "simstruc.h"

在此之后可根据需求引用其他头文件,推荐对固定参数如输入输出个数进行常量定义,便于后续对文件的修改和管理,提高可读性与可维护性。

在完成文件的声明后,便可以开始对相关函数进行编写,参考文档可移步至配置C/C++ S-Function功能 (opens new window),下面按照仿真流程顺序对常见的函数进行简要说明。

# 初始化函数 mdlInitializeSizes

详情参阅mdlInitializeSizes (opens new window)

该函数是Simulink调用的第一个函数,此函数执行以下任务:

  • 使用 ssSetNumSFcnParams 函数指定此S-Function支持的参数个数,当参数在仿真期间无法更改时,使用 ssSetSFcnParamTunable(S, paramIdx, 0);
  • 使用 ssSetNumContStates 和 ssSetNumDiscStates 指定函数具有的状态个数;
  • 配置输入端口,包括:
    • 使用 ssSetNumInputPorts 指定S-Function具有的输入端口数;
    • 使用 ssSetInputPortDimensionInfo 指定输入端口的维度;
    • 使用 ssSetInputPortDirectFeedThrough 指定是否具有直接馈通;
  • 配置输出端口,包括:
    • 使用 ssSetNumOutputPorts 指定S-Function具有的输出端口数;
    • 使用 ssSetOutputPortWidth 指定输出端口的维度;
  • 使用 ssSetNumSampleTimes 配置采样次数,(采样时间将会在 mdlInitializeSampleTimes 函数中进行配置);
  • 设置模块工作向量大小,包括:
    • ssSetNumRWork (opens new window),设置浮点指针型向量个数;
    • ssSetNumIWork (opens new window),设置整型向量个数;
    • ssSetNumPWork (opens new window),设置模块的指针型向量个数;
    • ssSetNumModes (opens new window),设置mode vector(?不太懂这个)的个数;
    • ssSetNumNonsampledZCs (opens new window),设置模块检测采样点之间发生过零的状态数;
  • 使用 ssSetOptions 设置此模块的选项。所有选项都形如SS_OPTION_<NAME>,使用按位运算符进行多个选项的设置,有关每个功能的信息,请参阅配置C/C++ S-Function功能 (opens new window)。

给出初始化函数的示例代码:

static void mdlInitializeSizes(SimStruct *S)
{
    int_T nInputPorts  = 1;  /* 输入端口数 */
    int_T nOutputPorts = 1;  /* 输出端口数 */

    // 设置输入端口维度信息
    DECL_AND_INIT_DIMSINFO(nInputDims);
    int_T indims[2];
    nInputDims.numDims = 2;
    indims[0] = MEASURE_DIM;
    indims[1] = 1;
    nInputDims.dims = indims;
    nInputDims.width = MEASURE_DIM;

    // 设置输出端口维度信息
    DECL_AND_INIT_DIMSINFO(nOutputDims);
    int_T outdims[2];
    nOutputDims.numDims = 2;
    outdims[0] = STATE_DIM;
    outdims[1] = 1;
    nOutputDims.dims = outdims;
    nOutputDims.width = STATE_DIM;
    
    int_T needsInput   = 1;  /* 是否具有直接馈通  */

    int_T inputPortIdx  = 0;
    int_T outputPortIdx = 0;

    ssSetNumSFcnParams(S, 0);  /* 期望的额外参数个数 */
    if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
        /* 获取的可选参数与期望的个数不匹配则退出 */
        return;
    }

    ssSetNumContStates(S, 0);  /* 连续状态个数  */
    ssSetNumDiscStates(S, 0);  /* 离散状态个数  */

    // 配置 I/O
    if (!ssSetNumInputPorts(S, nInputPorts)) return;
    if(!ssSetInputPortDimensionInfo(S, inputPortIdx, &nInputDims)) return;
    ssSetInputPortDirectFeedThrough(S, inputPortIdx, needsInput);

    if (!ssSetNumOutputPorts(S, nOutputPorts)) return;
    if(!ssSetOutputPortDimensionInfo(S,outputPortIdx, &nOutputDims)) return;

    ssSetNumSampleTimes(S, 1);  /* 配置采样次数 */

    // 设置工作向量
    ssSetNumRWork(S, 0);   /* real vector    */
    ssSetNumIWork(S, 0);   /* integer vector */
    ssSetNumPWork(S, 1);   /* pointer vector */
    ssSetNumModes(S, 0);   /* mode vector    */
    ssSetNumNonsampledZCs(S, 0);   /* zero crossings */

//     ssSetOperatingPointCompliance(S, USE_CUSTOM_OPERATING_POINT);

    /* 将此S-Function设置为运行时线程安全,以便多核执行 */
    ssSetRuntimeThreadSafetyCompliance(S, RUNTIME_THREAD_SAFETY_COMPLIANCE_TRUE);
    
    ssSetOptions(S,
                 SS_OPTION_WORKS_WITH_CODE_REUSE |
                 SS_OPTION_EXCEPTION_FREE_CODE |
                 SS_OPTION_DISALLOW_CONSTANT_SAMPLE_TIME);

}

# 初始化采样时间 mdlInitializeSampleTimes

static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, 2);
    ssSetOffsetTime(S, 0, 0.0);
    ssSetModelReferenceSampleTimeDefaultInheritance(S); 
}

通常采样时间可以通过设置额外参数作为输入进行设置,如果在 mdlInitializeSizes 中设置了额外参数,则可以获取额外参数进行相关设置:

// ssSetSampleTime(S, 0, 2);
ssSetSampleTime(S, 0, mxGetPr(ssGetSFcnParam(S,0))[0]);

# 开始函数 mdlStart

此函数在模型开始阶段调用一次,如果有应该只初始化一次的状态,应当在这里进行。

#define MDL_START
static void mdlStart(SimStruct *S)
{
    // 在PWork中储存指针对象
    MyEKFilter *filter  = new MyEKFilter(STATE_DIM, U_DIM, MEASURE_DIM);
    ssGetPWork(S)[0] = filter;
}

注意,此处初始化时申请了内存,请务必在模型仿真结束后,使用 mdlTerminate 函数进行内存释放。

# 初始化状态变量 mdlInitializeConditions

#define MDL_INITIALIZE_CONDITIONS   /*Change to #undef to remove */
#if defined(MDL_INITIALIZE_CONDITIONS)
static void mdlInitializeConditions(SimStruct *S)
{
   // 从PWork中拿到储存的指针变量
    MyEKFilter *filter = static_cast<MyEKFilter *>(ssGetPWork(S)[0]);
    auto P0 = kalmans::Matrix<double>::Diag({
        1, 0.1, 0.01,
        1, 0.1, 0.01,
        1, 0.1, 0.01
        });
    kalmans::Matrix<double> x(STATE_DIM, 1);
    x(0) = 1500e3;
    x(3) = 10e3;
    x(7) = -250;
    
    filter->init(x, P0);
}
#endif /* MDL_INITIALIZE_CONDITIONS */

# 输出函数 mdlOutputs

通过 ssGetInputPortRealSignalPtrs 和 ssGetOutputPortRealSignal 获取I/O的数据地址,设置输出的值实现模块输出。需要注意的是,这里用到了输入数据u时应当在初始化时设置存在直接馈通。

static void mdlOutputs(SimStruct *S, int_T tid)
{
    MyEKFilter *filter = static_cast<MyEKFilter *>(ssGetPWork(S)[0]);
    
    // 获取 I/O 的数据地址
    InputRealPtrsType  u = ssGetInputPortRealSignalPtrs(S,0);
               real_T *y = ssGetOutputPortRealSignal(S, 0);

    kalmans::Matrix<double> z(MEASURE_DIM, 1);
    kalmans::Matrix<double> u_(STATE_DIM, 1);
    kalmans::Matrix<double> filter_value(1, STATE_DIM);

    for (int j = 0; j < MEASURE_DIM; ++j)
            z(j) = *u[j];

    filter->step(u_, z);
    filter_value.assign(filter->get_state(), 0);

    for (int j = 0; j < STATE_DIM; ++j)
            y[j] = filter_value(j);
}

# 更新函数 mdlUpdate

在此进行更新离散状态量的操作,使用 ssGetRealDiscStates 获取离散状态变量。

#define MDL_UPDATE 
static void mdlUpdate(SimStruct *S, int_T tid) 
{ 
    real_T *x = ssGetRealDiscStates(S);
    /* 在此添加更新相关代码 */
} 

# 仿真结束函数 mdlTerminate

执行任何结束仿真后需要处理的任务。

static void mdlTerminate(SimStruct *S)
{
    // 获取指针变量并进行释放
    MyEKFilter *filter = static_cast<MyEKFilter *>(ssGetPWork(S)[0]);
    delete filter;
}

# 文件结尾说明

在函数文件的结尾,必须添加如下代码,通常不需要对其进行修改。

// Required S-function trailer
#ifdef  MATLAB_MEX_FILE  /* Is this file being compiled as a MEX-file? */
#include "simulink.c"    /* MEX-file interface mechanism */
#else
#include "cg_sfun.h"     /* Code generation registration function */
#endif

MATLAB_MEX_FILE这个预处理宏会在编译期进行定义,表示当前S-Function是否正在被编译为mex文件。

# 模块化编程与链接静态库

笔者通常习惯于使用C/C++语言编写S-Function时将核心代码打包为一个静态库,然后根据C-Mex S-Function的编写方式链接库函数并实现具体模块。这样做便于程序的调试与管理,便于已有代码的移植,在项目结构方面也更加清楚。

在MATLAB中编译并链接一个库比较简单,通常将源文件与库文件放在同一目录下,使用命令即可:

mex sourcecode.cpp library1.lib library2.lib

当需要设置C++语言标准,需要根据编译器类型设置相应的编译器选项,具体编译器参数可查阅相关说明文档,使用 mex 追加编译器选项需要进行如下设置:

mex COMPFLAGS='$COMPFLAGS /std:c++17' sourcecode.cpp library1.lib library2.lib

其中需要确定变量名称:

  • 要使用 MinGW®、macOS 和 Linux® 编译器编译 C++ 代码,请使用 CXXFLAGS。
  • 要使用 MinGW、macOS 和 Linux 编译器编译 C 代码,请使用 CFLAGS。
  • 对于 Microsoft® Visual Studio® 编译器,请使用 COMPFLAGS。

使用 Visual Studio 编译 MEX 文件时,请指定 C++17 标准。

编辑 (opens new window)
#MATLAB#Simulink
上次更新: 2023/08/14, 19:51:00
Level-1 Matlab S-Function编写入门
MATLAB安装配置MinGW-w64 C/C++编译器

← Level-1 Matlab S-Function编写入门 MATLAB安装配置MinGW-w64 C/C++编译器→

最近更新
01
ipopt优化库配置及使用
07-21
02
ubuntu离线安装包的方法
07-21
03
其它控件的使用
03-05
更多文章>
Theme by Vdoing | Copyright © 2020-2024 Sirius0v0 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式