ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Simulink S函数从入门到精通:自定义模块与代码生成实战

Simulink S函数从入门到精通:自定义模块与代码生成实战 1. 项目概述为什么我们需要S函数在Simulink的世界里待久了你会发现一个有趣的现象大多数工程师的建模生涯都是从拖拽那些现成的模块开始的。增益、积分器、传递函数、查表……这些内置模块就像乐高积木能快速搭建出漂亮的系统模型。但当你遇到一个稍微“非主流”的需求时比如要模拟一个具有复杂内部逻辑的控制器、一个用特定算法描述的物理过程或者需要与外部硬件进行实时数据交互时你就会发现这些标准积木突然不够用了。这时Simulink会对你神秘一笑然后指向工具箱深处一个名为“S-Function”的模块。S函数全称System-Function是Simulink为用户打开的一扇后门。它允许你用编程语言最常用的是C/C和MATLAB语言来定义自己的模块行为从而将Simulink从一个图形化建模工具扩展为一个几乎无所不能的仿真平台。你可以把它理解为一个“自定义黑箱”箱子里面的运作逻辑完全由你写的代码决定而箱子外面则提供标准的Simulink接口输入端口、输出端口、参数对话框可以无缝地嵌入到任何模型中。我最初接触S函数是因为一个电机控制项目。客户要求实现一个特定的非线性观测器算法这个算法在Simulink库中根本找不到对应模块用基本模块组合又极其繁琐且效率低下。当时团队里有人建议用MATLAB Function模块但那个模块在处理连续系统状态更新和复杂矩阵运算时调试起来非常痛苦。最终我们选择了用C MEX S函数来实现它不仅执行速度快还能清晰地管理算法的离散状态调试时甚至可以单步跟踪C代码问题一目了然。自那以后S函数就成了我解决复杂建模问题的“瑞士军刀”。所以如果你觉得Simulink内置模块限制了你的创造力或者你手头有成熟的算法代码希望快速集成到仿真中那么学习S函数就是你的必经之路。本文将通过一个极其简单的例子手把手带你完成第一个S函数的编写、编译与集成让你理解其核心工作机制并分享一些从实际项目中总结出来的、教科书上不会写的避坑经验。2. S函数核心机制与工作原理解析在动手写代码之前我们必须先搞清楚S函数是如何与Simulink仿真引擎协同工作的。这就像你要为一家工厂定制一台新机器必须先了解工厂的生产节拍和接口规范。2.1 Simulink仿真引擎的回调机制Simulink的仿真并非一蹴而就而是以固定或可变步长逐步推进的。在每个时间步引擎会按顺序处理模型中的每个模块。对于S函数引擎并非直接执行你的全部代码而是通过调用一系列预定义好的“回调函数”来与你对话。这些回调函数就像是你为自定义模块定义的一组事件处理程序。最重要的几个回调函数包括mdlInitializeSizes: 这是“开工大会”。在此函数中你需要向Simulink引擎汇报你这个模块的基本规格有几个输入端口、几个输出端口它们是标量还是向量有没有需要持续存在的内部状态有哪些参数需要从模块对话框输入mdlInitializeSampleTimes: 告诉引擎你的模块“工作节奏”。采样时间是离散的如0.01秒一次还是连续的是否继承上游信号的采样率这个定义直接影响仿真的效率和准确性。mdlOutputs: 这是模块的“主营业务”。在每个仿真步长引擎会调用这个函数。你的核心任务就是在这里根据当前的输入值u和内部状态x计算出输出值y。y f(u, x)的逻辑就在这里实现。mdlUpdate: 如果你模块有内部状态比如一个离散积分器需要记住上一时刻的值那么在每个步长在计算完输出之后引擎会调用这个函数来更新状态。x_next g(u, x)的逻辑在这里实现。对于纯静态无状态的模块这个函数可以为空。mdlTerminate: 仿真结束时的“收尾工作”用于释放内存、关闭文件等。对于简单例子通常用不到。理解这个回调流程至关重要。Simulink引擎是主导者它按自己的节奏仿真时钟发起调用你的S函数是被调用者响应这些回调并完成指定任务。这种设计保证了所有模块无论是内置的还是自定义的都能在统一的框架下有序运行。2.2 Level-2 MATLAB S-function 与 C MEX S-function 的选择Simulink支持两种主流的S函数实现方式选择哪种取决于你的需求Level-2 MATLAB S-function:形式 一个标准的MATLAB.m文件。优点 开发调试极其方便。无需编译直接在MATLAB环境中编辑和运行。可以利用MATLAB强大的矩阵运算和可视化功能进行快速原型验证。非常适合算法验证、概念性建模以及不追求极致仿真速度的场景。缺点 执行速度慢。因为每个仿真步长都需要在MATLAB解释器中执行对于大型模型或高速仿真可能成为性能瓶颈。也无法用于Simulink Coder的代码生成生成产品级C代码。C MEX S-function:形式 用C或C语言编写的源文件.c或.cpp需要编译成MATLAB可执行的MEX文件在Windows上是.mexw64等。优点 执行速度快。编译后的二进制代码直接运行效率接近内置模块。最关键的是它支持通过Simulink Coder/Embedded Coder生成高效、可移植的嵌入式C代码这是实现模型在硬件上部署如DSP、单片机的关键。缺点 开发门槛稍高需要C语言基础并且涉及编译环节调试过程比MATLAB复杂。实操心得 我的建议是初学者一律从Level-2 MATLAB S-function开始。它的学习曲线平缓能让你快速聚焦于理解S函数的核心逻辑而不是陷入C语言语法和编译错误的泥潭。当你完全掌握了回调机制并且项目确有性能或代码生成需求时再平滑过渡到C MEX S-function。事实上很多算法先用MATLAB版本验证再移植到C版本是一条非常稳妥的路径。2.3 一个简单的例子定义饱和限幅器为了贯穿全文我们定义一个具体且简单的例子实现一个饱和限幅器Saturation。功能 该模块有一个输入u和一个输出y。它有两个参数上限upper_limit和下限lower_limit。输出规则是如果输入u大于upper_limit则输出y等于upper_limit如果u小于lower_limit则输出y等于lower_limit否则y等于u。数学表达y min(max(u, lower_limit), upper_limit)为什么选它 这个功能Simulink自带Saturation模块就能实现。但正因为它简单且结果明确我们才能抛开算法复杂性的干扰专注于学习S函数的“形式”和“流程”。我们最终要实现的就是一个功能上与内置Saturation模块完全一样的自定义模块。3. 手把手实现Level-2 MATLAB S-function让我们从最友好的MATLAB S-function开始。打开MATLAB编辑器新建一个文件命名为my_saturation_sfunc.m。3.1 搭建S-function的骨架Level-2 S-function有一个固定的函数声明和主函数结构。我们先搭好架子function my_saturation_sfunc(block) % 主函数Simulink引擎会调用它并传入一个block对象 % 这个对象代表了我们的S-function模块实例所有操作都通过它进行 setup(block); % ----------------------------------------------------------------- % 本地函数初始化与配置 % ----------------------------------------------------------------- function setup(block) % 1. 注册输入/输出端口数量 block.NumInputPorts 1; % 一个输入端口 block.NumOutputPorts 1; % 一个输出端口 % 2. 配置端口属性 % 设置端口继承采样时间、数据类型和维度 block.SetPreCompInpPortInfoToDynamic; block.SetPreCompOutPortInfoToDynamic; % 明确设置输入端口属性可选但推荐 block.InputPort(1).DatatypeID 0; % 0 代表 double -1 代表继承 block.InputPort(1).Complexity Real; % 实数信号 block.InputPort(1).Dimensions 1; % 标量输入 block.InputPort(1).DirectFeedthrough true; % 关键输出直接依赖于输入 % 明确设置输出端口属性 block.OutputPort(1).DatatypeID 0; % double block.OutputPort(1).Complexity Real; block.OutputPort(1).Dimensions 1; % 标量输出 % 3. 设置模块参数数量 block.NumDialogPrms 2; % 两个对话框参数下限和上限 block.DialogPrmsTunable {Tunable, Tunable}; % 参数可在仿真运行时调整 % 4. 设置采样时间 block.SampleTimes [-1, 0]; % [-1, 0] 表示继承输入信号的采样时间 % 也可以设置为离散时间如 [0.1, 0] 表示0.1秒的固定步长 % 5. 注册回调函数 block.RegBlockMethod(PostPropagationSetup, DoPostPropSetup); % 用于设置状态本例不需要 block.RegBlockMethod(InitializeConditions, InitConditions); % 初始化本例不需要 block.RegBlockMethod(Outputs, Output); % 最重要的输出计算 block.RegBlockMethod(Update, Update); % 状态更新本例不需要 end代码解析与注意事项block对象这是与Simulink引擎通信的唯一接口。所有配置、数据获取都通过它完成。DirectFeedthrough这是一个极易出错的关键属性。它表示该端口的输出是否直接依赖于同一时刻的输入。对于我们的饱和限幅器输出y显然直接由当前输入u计算得出所以必须设为true。如果设为falseSimulink在计算模块执行顺序时可能会出错导致仿真结果异常或失败。对于有状态如积分、延迟的模块输出通常不直接依赖当前输入此项应设为false。NumDialogPrms定义了模块对话框上参数的数量和顺序。这里2个参数将来在模块对话框里会按顺序出现两个输入框。SampleTimes[-1, 0]是最常用的设置表示继承驱动信号的采样时间。如果你要创建离散控制器可以设为[Ts, offset]例如[0.01, 0]表示10ms的固定步长。3.2 实现核心的输出回调函数骨架搭好接下来填充血肉——实现Outputs回调函数。% ----------------------------------------------------------------- % 本地函数输出计算 % ----------------------------------------------------------------- function Output(block) % 1. 从输入端口获取当前输入值 u block.InputPort(1).Data; % 2. 从对话框参数获取上下限 lower_limit block.DialogPrm(1).Data; % 第一个参数是下限 upper_limit block.DialogPrm(2).Data; % 第二个参数是上限 % 3. 实现饱和限幅逻辑 if u upper_limit y upper_limit; elseif u lower_limit y lower_limit; else y u; end % 4. 将计算结果赋给输出端口 block.OutputPort(1).Data y; end这个函数逻辑非常直观就是简单的条件判断。但请注意我们这里使用了if-else语句。对于向量或矩阵输入这个写法就不对了。为了模块的通用性我们应该使用元素级运算function Output(block) u block.InputPort(1).Data; lower_limit block.DialogPrm(1).Data; upper_limit block.DialogPrm(2).Data; % 使用 min 和 max 函数进行元素级操作兼容标量和数组 y min(max(u, lower_limit), upper_limit); block.OutputPort(1).Data y; end这样修改后即使输入u是一个向量模块也能正确地对每个元素进行限幅。3.3 补充其他必要的回调函数对于这个无状态的饱和限幅器PostPropagationSetup、InitializeConditions和Update函数不是必须的但为了结构完整我们提供空实现。% ----------------------------------------------------------------- % 本地函数为离散状态分配内存本例无状态留空 % ----------------------------------------------------------------- function DoPostPropSetup(block) % block.NumDworks 0; % 没有离散状态工作向量 end % ----------------------------------------------------------------- % 本地函数初始化状态本例无状态留空 % ----------------------------------------------------------------- function InitConditions(block) % 如果有状态例如 block.Dwork(1).Data 0; end % ----------------------------------------------------------------- % 本地函数更新状态本例无状态留空 % ----------------------------------------------------------------- function Update(block) % 如果有状态例如 block.Dwork(1).Data new_state; end至此一个完整的Level-2 MATLAB S-function就编写完成了。保存文件。3.4 在Simulink中集成与测试新建模型打开Simulink创建一个新模型。添加S-Function模块在库浏览器中找到“User-Defined Functions”库将“S-Function”模块拖到模型中。配置模块双击S-Function模块打开参数对话框。在“S-function name”一栏填写我们刚写的函数名my_saturation_sfunc不要加.m后缀。点击“S-function parameters”下方的“Edit”按钮会弹出两个输入框这就是我们在代码中定义的NumDialogPrms2的效果。分别输入下限和上限例如-5和5。搭建测试模型添加一个“Sine Wave”信号源振幅设为10频率设为1 rad/s。将信号源连接到S-Function模块的输入。添加一个“Scope”示波器连接到S-Function模块的输出。为了对比可以从“Math Operations”库再拖一个标准的“Saturation”模块进来设置相同的上下限-5, 5将同一信号源也连给它输出接到另一个Scope通道。运行仿真点击运行。在示波器中你应该看到原始的正弦波幅度-10到10被限制在了-5到5之间并且两条曲线完全重合证明我们的自定义S-function工作正常。避坑技巧 调试MATLAB S-function时一个非常实用的方法是在代码中直接使用MATLAB的disp函数或fprintf函数打印中间变量。例如在Output函数里加上fprintf(u%.2f, y%.2f\n, u, y);仿真时在MATLAB命令窗口就能看到实时数据这对于排查逻辑错误非常有效。调试完毕后记得注释掉这些语句。4. 进阶从MATLAB到C MEX S-function当你的模型变得复杂或者需要为产品代码生成做准备时C MEX S-function是更专业的选择。我们同样以实现饱和限幅器为例。4.1 C MEX S-function的模板与结构C MEX S-function同样遵循回调机制但以C语言函数的形式实现。MATLAB提供了一个标准模板文件sfuntmpl_basic.c在命令窗口输入edit sfuntmpl_basic可打开我们可以基于它修改。创建一个新文件my_saturation_c.c。一个最精简的C MEX S-function包含以下必须的回调函数mdlInitializeSizes 相当于MATLAB版的setup。mdlInitializeSampleTimes 定义采样时间。mdlOutputs 输出计算。mdlTerminate 终止函数可简单留空。我们不需要状态更新因此mdlUpdate函数可以省略。4.2 详解C MEX S-function的关键代码段以下是my_saturation_c.c的核心代码解析#define S_FUNCTION_NAME my_saturation_c #define S_FUNCTION_LEVEL 2 #include simstruc.h // 必须包含的头文件定义了SimStruct等数据结构 /* 函数声明 */ static void mdlInitializeSizes(SimStruct *S); static void mdlInitializeSampleTimes(SimStruct *S); static void mdlOutputs(SimStruct *S, int_T tid); static void mdlTerminate(SimStruct *S); /** * S-function methods * **/ /* 1. 初始化模块定义 */ #define MDL_INITIALIZE_SIZES static void mdlInitializeSizes(SimStruct *S) { /* 告知Simulink我们不会直接使用参数个数宏(ssGetNumSFcnParams) 而是通过下面语句明确参数个数 */ ssSetNumSFcnParams(S, 2); /* 两个参数下限和上限 */ if (ssGetNumSFcnParams(S) ! ssGetSFcnParamsCount(S)) { return; /* 参数数量不匹配Simulink会报错 */ } /* 配置输入端口 */ if (!ssSetNumInputPorts(S, 1)) return; ssSetInputPortWidth(S, 0, 1); /* 端口0的宽度为1标量*/ ssSetInputPortRequiredContiguous(S, 0, true); /* 要求输入数据在内存中连续 */ ssSetInputPortDirectFeedThrough(S, 0, 1); /* 关键端口0直通 */ /* 配置输出端口 */ if (!ssSetNumOutputPorts(S, 1)) return; ssSetOutputPortWidth(S, 0, 1); /* 端口0的宽度为1 */ /* 设置工作向量数量状态、离散状态等本例为0 */ ssSetNumContStates(S, 0); ssSetNumDiscStates(S, 0); ssSetNumRWork(S, 0); ssSetNumIWork(S, 0); ssSetNumPWork(S, 0); ssSetNumModes(S, 0); ssSetNumNonsampledZCs(S, 0); /* 指定此S-function可用于代码生成 */ ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE); } /* 2. 初始化采样时间 */ #define MDL_INITIALIZE_SAMPLE_TIMES static void mdlInitializeSampleTimes(SimStruct *S) { /* 继承采样时间 */ ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME); ssSetOffsetTime(S, 0, 0.0); } /* 3. 输出计算函数 */ static void mdlOutputs(SimStruct *S, int_T tid) { /* 获取输入、输出指针 */ const real_T *u (const real_T*) ssGetInputPortSignal(S,0); real_T *y ssGetOutputPortSignal(S,0); /* 获取参数指针。参数在仿真过程中是只读的。 */ const real_T *params mxGetPr(ssGetSFcnParam(S,0)); real_T lower_limit params[0]; real_T upper_limit params[1]; /* 实现饱和限幅逻辑 */ real_T input u[0]; if (input upper_limit) { y[0] upper_limit; } else if (input lower_limit) { y[0] lower_limit; } else { y[0] input; } } /* 4. 仿真终止函数 */ static void mdlTerminate(SimStruct *S) { /* 本例无特殊清理工作 */ } /** * Required S-function trailer * **/ #ifdef MATLAB_MEX_FILE /* 此文件是否被编译为MEX文件 */ #include simulink.c /* MEX文件接口机制 */ #else #include cg_sfun.h /* 代码生成机制 */ #endif关键点解析SimStruct *S 这是C版本中的block对象存储了模块的所有信息。ssSetInputPortDirectFeedThrough 同样这里必须设置为true1。ssGetInputPortSignal和ssGetOutputPortSignal 这是获取输入输出数据指针的标准方法。注意C语言中的数组索引从0开始。mxGetPr(ssGetSFcnParam(S,0)) 这是从Simulink获取对话框参数值的方法。ssGetSFcnParam(S,0)获取第一个参数一个mxArray指针mxGetPr获取其内部实数数据区的指针。重要所有参数在仿真运行时被视为常量存储在一个只读内存区。real_T 这是Simulink中定义的数据类型通常对应C语言的double。如果模型使用单精度single这里应使用real32_T。4.3 编译与集成编译C MEX文件 在MATLAB命令窗口中切换到my_saturation_c.c文件所在目录执行编译命令mex my_saturation_c.c如果编译成功会生成一个my_saturation_c.mexw64Windows 64位之类的文件。这是Simulink可以动态加载的二进制模块。在Simulink中使用 和之前使用MATLAB S-function的步骤完全一样。拖入“S-Function”模块。在参数对话框中S-function name填写my_saturation_c。设置参数例如-5和5。连接信号源和示波器运行仿真。效果应与MATLAB版本完全一致。编译常见问题排查错误编译器未找到 首次使用mex命令需要配置C编译器。运行mex -setup按照提示选择已安装的编译器如Microsoft Visual C。错误无法打开包含文件 ‘simstruc.h’ 确保MATLAB安装正确且路径包含Simulink头文件。通常安装正确就不会有问题。警告或错误与指针、类型相关 仔细检查代码确保获取输入输出指针和参数指针的语法正确特别是数组索引和数据类型转换。5. 调试、优化与高级应用场景掌握了基本实现后我们来看看如何让S函数变得更健壮、更高效以及它能用在哪些更复杂的地方。5.1 调试技巧与错误处理使用ssPrintf 在C MEX S-function中不能直接用printf而要用Simulink提供的ssPrintf函数向MATLAB命令窗口输出信息用法类似。#include stdio.h ... static void mdlOutputs(SimStruct *S, int_T tid) { const real_T *u (const real_T*) ssGetInputPortSignal(S,0); ssPrintf(Current input: %f\n, u[0]); ... }使用调试器 对于复杂的C MEX S-function最强大的调试方法是使用外部IDE如Visual Studio。在MATLAB中先运行mex -g my_saturation_c.c生成带调试信息的MEX文件。在C代码中需要中断的地方设置断点例如在mdlOutputs函数开始处添加__debugbreak();Windows或asm(“int3”)GCC。在Visual Studio中选择“调试”-“附加到进程”找到并附加到MATLAB的进程MATLAB.exe。在Simulink中开始仿真当执行到断点处时程序会中断你就可以在VS中查看变量、单步执行了。这是定位复杂内存错误和逻辑问题的终极手段。参数检查 在mdlInitializeSizes或mdlStart函数中应加入参数有效性检查。例如检查上限是否大于下限。static void mdlStart(SimStruct *S) { const real_T *params mxGetPr(ssGetSFcnParam(S,0)); real_T lower_limit params[0]; real_T upper_limit params[1]; if (lower_limit upper_limit) { ssSetErrorStatus(S, “Error: Lower limit must be less than upper limit.”); return; } }5.2 性能优化考量向量化运算 我们的例子是标量。如果处理向量或矩阵应避免在循环内部调用ssGetInputPortSignal等函数。最好一次性获取所有数据的指针然后使用循环或调用优化过的库函数如BLAS进行计算。减少冗余操作 对于固定参数可以在mdlStart函数中将其从参数区读取并保存到PWork指针工作向量或DWork数据工作向量中这样在每次mdlOutputs调用时就不需要再通过mxGetPr去解析mxArray了直接访问DWork数据更快。选择正确的数据类型 如果模型使用单精度single仿真在C代码中也应使用real32_T并处理好类型转换避免不必要的双精度计算。5.3 高级应用场景举例S函数的真正威力体现在解决以下问题上集成现有C/C算法库 如果你有一个用C语言写好的、经过验证的控制算法或信号处理库可以将其包装在C MEX S-function的mdlOutputs函数中直接调用实现仿真与产品代码的高度一致。硬件在环HIL仿真接口 在mdlOutputs和mdlUpdate函数中可以通过调用操作系统API或硬件驱动如TCP/IP、UDP、CAN、串口、DAQ卡等实现Simulink模型与真实物理硬件如ECU、传感器、执行器的实时数据交换。实现复杂的多模式或事件驱动逻辑 利用mdlInitializeConditions初始化状态在mdlOutputs和mdlUpdate中根据输入和内部状态机决定输出可以模拟有限状态机、顺序逻辑等这用基本模块搭建会非常困难。自定义可视化或记录 在S函数中可以将关键数据实时写入文件、发送到网络端口或调用MATLAB绘图函数进行动态显示实现自定义的监控和记录功能。6. 从仿真到部署S函数与代码生成对于嵌入式开发者而言S函数特指C MEX S-function的终极价值在于支持代码生成。这意味着你可以用Simulink图形化搭建算法框架用S函数集成核心算法代码然后一键生成可在嵌入式处理器如ARM Cortex-M, DSP, FPGA软核上运行的、高效且可读的C代码。6.1 为代码生成准备S函数要使你的C MEX S-function能够顺利通过Simulink Coder生成代码需要遵循一些额外的规范使用标准的simstruc.h接口 确保代码只使用Simulink Coder支持的API。避免使用操作系统特定的调用如malloc,printf或第三方库除非你已为其配置了TLC文件目标语言编译器文件。注意内存分配 动态内存分配malloc/free在生成的代码中通常是禁止的。所有内存都应在mdlInitializeSizes中通过ssSetNum*Work系列函数进行静态声明如DWork,PWork。处理多实例 如果你的模型中有多个相同的S-function模块生成的代码需要保证它们的数据彼此独立。正确使用DWork向量可以为每个模块实例分配独立的内存空间。纯整数运算 如果目标处理器不支持浮点单元需要考虑使用定点数运算。Simulink支持定点数数据类型在S函数中需要使用相应的宏如ssGetInputPortRealSignalPtrs_*系列来访问数据。6.2 一个简单的可生成代码的S函数调整对我们的饱和限幅器使其更适合代码生成主要调整在于参数访问方式。在仿真时参数通过mxGetPr获取在代码生成时参数被直接内联为常量或存储在特定数据结构中。为了兼容两者我们使用ssGetSFcnParam的返回值并在TLC文件中进行配置这是一个进阶话题。对于简单情况Simulink Coder能自动处理大多数标准写法。更关键的一步是创建或修改TLC文件。TLC文件告诉代码生成器如何将你的S-function回调函数翻译成目标代码。对于简单的、无状态的算法S函数Simulink通常可以自动生成内联代码。但对于复杂情况你可能需要编写自定义的TLC文件来精确控制生成的代码结构。6.3 代码生成流程与验证配置模型 在Simulink模型中进入“模型设置”Model Settings在“代码生成”部分选择正确的系统目标文件System Target File例如ert.tlcEmbedded Coder用于生成嵌入式C代码。检查S-function 在代码生成报告生成前Simulink会检查模型中所有S-function的兼容性。确保没有使用不支持的函数。生成代码 点击“生成代码”CtrlB。如果成功你会在当前目录下看到一个以模型名命名的文件夹里面包含了所有的.c和.h文件。验证代码 仔细查看生成的代码特别是你的S-function对应的部分。生成的代码应该将饱和限幅逻辑直接内联在模型的主步进函数中没有任何对mxGetPr等MATLAB环境函数的调用从而成为完全独立的C代码。核心经验 将S-function用于代码生成时务必在最终硬件目标或等效的仿真环境下进行充分的闭环测试。Simulink桌面仿真使用MEX文件和生成的独立C代码在浮点精度、编译器优化等方面可能存在细微差异这些差异在复杂控制算法中可能被放大。因此流程应该是先在Simulink中用MEX S-function完成算法验证和仿真然后生成代码在PC上编译并运行“软件在环”SIL测试对比结果最后再下载到硬件进行“处理器在环”PIL或硬件在环测试。
返回列表