迁移Simulink的Fuzzy 模型为 C/C++ 工程 并在VS2013和Qt5中使用
写在前面:
之所以会用到这个功能,很多时候还是因为开发设备或者硬件的时候,用C/C++语言调用接口进行控制更为直接,但是相应的控制算法核心要使用C/C++编写,各有各的难处,否则你也不会看到这篇文章。
这项工作最早是以另一个形式展开:最开始想在c++里调用Matlab里面的模糊控制底层的fis.c文件,涉及很多问题,包括fis.c本身结构体定义就错误、语法太老,诸多不兼容以及安全警告,修改到文件编译通过以后,项目依然无法正常执行。
后来尝试了很多工具和网上的一些简单的fuzzy模块,都并不太理想,尤其是在模糊控制的细节实现中,硬写非常考验功力。
最后还是回到了标题上,成功之后,发现通过直接迁移Simulink模型到C++这个方法实现的配置是最简单的,调用接口也是最友好的。
一、 基本环境
windows 10、windows 7
Matlab R2017b
Visual Stadio 2013
Qt 5.9
二、 配置提醒
在开始进行生成之前,首先确认完成以下步骤:
1.首先保存simulink文件,并且确保起了个合适的文件名,不然生成C++文件类名会变成untitled;迁移使用的模型就是下面这个
2.在Matlab命令行检查
mex -setup
检查编译器,确定有没有我们所需要的例如Visual Studio 2017、2015、2013对应版本编译器,还可以根据输出的帮助,通过命令更改编译器版本并且指定C/C++语言
没有编译器的话需要完成补丁工作,给Matlab装相应的C语言编译器或者联系电脑中已存在的VS编译器,具体可以参考https://blog.csdn.net/qq_36964645/article/details/80429814
否则在输出阶段会报错没有对应的编译器。
三、 simulink 生成代码
全过程均可参考
https://www.doc88.com/p-8931310627521.html
一个需要注意的问题是,simulink文件和当前活动目录都不能在Matlab2017b/bin 这种源文件目录下,最好放置在其他目录,否则输出code时也会报错不能在源文件目录操作。
四、VS 项目中的配置和调用:
通过上面的工程我们生成的是一个可用的VS解决方案,点进msvc就能看见.sln文件,能够打开这个工程。
主要的依赖文件只有两类,就是生成的.h文件和生成的.cpp文件,都在 ***_ert_rtw 文件夹内,”******“是保存的文件名。
可以看以下这个工程的文件配置结构:
其中ert_main.cpp是主函数,主要写明了我们Simulink模型的调用方法;下面四个是依赖文件,具体的模型结构和计算实现都写在这里面。
如果迁移到其他c++项目中,就把这些依赖文件拷进项目根目录,再直接添加现有项,导入依赖头文件xxx.h和源文件xxx.cpp。
最后,在主函数进行调用我们的控制模型,需要研究一下ert_main.cpp的结构,主要要注意的就是下面四个部分
- 包含头文件
#include <stddef.h>
#include <iostream>
#include <stdio.h> // This ert_main.c example uses printf/fflush
#include "untitled.h" // Model's header file (模型头文件,就是以simulink文件名命名的类头文件
#include "rtwtypes.h" // 类型定义
//具体的项目包含头文件以生成的main文件自己写的包含为准,迁移的时候直接把这一段复制就行
-
定义控制器对象
实例化一个untitledModelClass的类对象
static untitledModelClass rtObj; // Instance of model class
-
模型单次调用函数
void rt_OneStep(void)就是单次调用函数,它的模板是无重载无返回的,但是可以自行更改,模型输入输出的位置都在调用函数里预留好了,根据提示补充就行。
void rt_OneStep(void);
void rt_OneStep(void)
{
static boolean_T OverrunFlag = false;
// Disable interrupts here
// Check for overrun
if (OverrunFlag) {
rtmSetErrorStatus(rtObj.getRTM(), "Overrun");
}
OverrunFlag = true;
// Save FPU context here (if necessary)
// Re-enable timer or interrupt here
// Set model inputs here
//在这里定义模型输入
// Step the model
rtObj.step();
// Get model outputs here
//在这里获得模型输出
///Indicate task complete
OverrunFlag = false;
// Disable interrupts here
// Restore FPU context here (if necessary)
// Enable interrupts here
}
- 主函数中的模型初始化和调用
// The example "main" function illustrates what is required by your
// application code to initialize, execute, and terminate the generated code.
// Attaching rt_OneStep to a real-time clock is target specific. This example
// illustrates how you do this relative to initializing the model.
//模型的使用非常简单,就是首先把对象初始化,其次调用一次单次实现函数
int_T main(int_T argc, const char *argv[])
{
// Unused arguments
(void)(argc);
(void)(argv);
// Initialize model 首先初始化对象
rtObj.initialize();
// Attach rt_OneStep to a timer or interrupt service routine with
// period 0.02 seconds (the model's base sample time) here. The
// call syntax for rt_OneStep is
//
//再单次调用对象
rt_OneStep();
//这一段printf在实际使用的时候可以直接注释掉
printf("Warning: The simulation will run forever. "
"Generated ERT main won't simulate model step behavior. "
"To change this behavior select the 'MAT-file logging' option.\n");
fflush((NULL));
while (rtmGetErrorStatus(rtObj.getRTM()) == (NULL)) {
// Perform other application tasks here
}
// Disable rt_OneStep() here
return 0;
}
//
// File trailer for generated code.
//
// [EOF]
//
依据个人需要,更改函数和调用方法(仅供参考)
-
添加输入输出和函数返回值
具体输入和输出成员变量需要检查一下类文件里面的结构体如何定义in和out,模型参数修改同理
double* rt_OneStep(double input1,double input2); double* rt_OneStep(double input1, double input2) { double *res=new double[2]; static boolean_T OverrunFlag = false; // Disable interrupts here // Check for overrun if (OverrunFlag) { rtmSetErrorStatus(rtObj.getRTM(), "Overrun"); return res; } OverrunFlag = true; // Save FPU context here (if necessary) // Re-enable timer or interrupt here // Set model inputs here(填写输入变量) rtObj.rtU.In1 = input1; rtObj.rtU.In2 = input2; // Step the model rtObj.step(); // Get model outputs here(填充输出数组) res[0] = rtObj.rtY.Out1; res[1] = rtObj.rtY.Out2; ///Indicate task complete OverrunFlag = false; // Disable interrupts here // Restore FPU context here (if necessary) // Enable interrupts here return res; }
-
调用函数
// The example "main" function illustrates what is required by your // application code to initialize, execute, and terminate the generated code. // Attaching rt_OneStep to a real-time clock is target specific. This example // illustrates how you do this relative to initializing the model. // int_T main(int_T argc, const char *argv[]) { // Unused arguments (void)(argc); (void)(argv); // Initialize model rtObj.initialize(); // Attach rt_OneStep to a timer or interrupt service routine with // period 0.02 seconds (the model's base sample time) here. The // call syntax for rt_OneStep is // double x = 0.2; double y = 0.5; double a = 0; double b= 0; double *k=new double[2]; k= rt_OneStep(x,y); std::cout <<k[0] << " plus " << k[1]<< std::endl; //printf("Warning: The simulation will run forever. " // "Generated ERT main won't simulate model step behavior. " // "To change this behavior select the 'MAT-file logging' option.\n"); fflush((NULL)); while (rtmGetErrorStatus(rtObj.getRTM()) == (NULL)) { // Perform other application tasks here } // Disable rt_OneStep() here return 0; } // // File trailer for generated code. // // [EOF] //
五、Qt项目中的配置和调用
https://www.cnblogs.com/Pan-Z/p/7676310.html
与在VS中的使用方法完全一致,只需要把相应的依赖文件添加到Qt项目中包含就行,
主要也是这些步骤
-
添加依赖文件
Qt的依赖文件可以直接在.pro文件中包含,也非常方便,把所需要的头文件和源文件拷到Qt项目的根目录下,再在.pro文件直接添加即可
-
包含头文件
-
实例化模型对象
-
改写onestep调用函数
-
初始化对象
-
后续使用时直接调用onestep函数即可