# 通过用户自研程序仿真 FMU


在这种应用场景下,用户自行设计 FMU 调用程序,仿真由 Sysplorer 导出的 FMU。这要求用户对 FMI 规范有较为深入的理解,否则调用程序可能存在隐藏错误,从而严重影响仿真的稳定性与准确性。

# #3001 函数调用时机错误

  • 问题现象:调用 FMU 接口函数时报错invalid execution timing

  • 问题分析:FMI 规范定义了ModelExchangeCo-Simulation两类 FMU 的运行状态机,并明确规定了各状态下允许调用的接口函数。如果在错误的运行阶段调用了不允许的接口函数,FMU 就会报出调用时机错误。
    另外,如果上一个接口函数执行失败,FMU 会将内部状态切换为出错模式并返回错误码。若用户程序未检查返回值而继续调用下一个函数,则该函数检测到 FMU 已处于出错模式时,同样会报此错误。

  • 问题定位

    1. 检查每个接口函数的返回值是否正常;
    2. 注释掉报错函数之前调用的可疑函数;若该函数位于循环体内(如while循环),则可临时注释掉其后续不必要的调用,重新仿真验证问题是否消失。
  • 解决方案

    1. 严格遵循 FMI 运行状态机规范,合理设计调用流程;
    2. 在每个函数调用后增加返回值检查和退出判断,避免错误状态被传递。

# 仿真阶段

# 实例化阶段

# #3101 GUID 不一致

  • 问题现象:调用 FMU 实例化函数时报错,提示实参 GUID 与 FMU 内置 GUID 不一致。

  • 问题分析:FMU 的modelDescription.xml文件与 FMU 动态库文件必须保持一致。调用实例化函数时,fmuGUID参数应从modelDescription.xml中读取。如果解压或更新 FMU 时未能同时替换XML文件和动态库(例如只更新了动态库但未更新XML,或手动拷贝时遗漏了部分文件),就会导致两者不匹配。

  • 问题定位

    1. 检查modelDescription.xml文件中的guid属性是否被正确读取并传入实例化函数;
    2. 根据报错信息判断是否为XML文件未同步更新所致。
  • 解决方案

    1. 调用实例化函数时确保传入的guidXML文件中的定义一致;
    2. 重新完整解压 FMU 文件,保证动态库文件与XML文件一并更新。

# #3102 资源路径不合法实例化失败

  • 问题现象:调用 FMU 实例化函数时因资源路径不合法而异常退出。

  • 问题分析:资源路径参数用于指向 FMU 解压后的Resources文件夹,FMI 规范对该字符串格式有严格要求。假设 FMU 被解压到路径C:\temp\MyFMU,则资源路径应为以下形式之一:

    • file:///C:/temp/MyFMU/resources
    • file:/C:/temp/MyFMU/resources
  • 问题定位:严格对照 FMI 规范,检查传入实例化函数的资源路径参数是否符合格式要求。

  • 解决方案:按 FMI 规范修正资源路径字符串,确保路径格式合法。

    提示

    有关实例化函数的资源路径参数的详细信息,请参见:

# #3103 调试运行时动态库加载失败

  • 问题现象:在 Visual Studio 或其他 IDE 中调试运行 FMU 时,动态库无法正常加载。

  • 问题分析:可能原因包括:调用程序的exe未能找到 FMU 动态库或其依赖库文件,或者 IDE 选择的平台位数与 FMU 动态库位数不一致。

  • 问题定位:调用GetLastError()并打印错误信息,以获取更详细的失败原因。

  • 解决方案:将 FMU 解压后的动态库文件拷贝到exe所在路径,确保可被正确加载。

# 初始化阶段

# #3201 调用 Set 函数后仿真失败

  • 问题现象:调用fmiSetXXX函数修改模型变量值后仿真错误退出。

  • 问题分析:FMI 规范严格定义了模型变量的因果性(causality)、可变性(variability)和初始属性(initial),并规定了在不同运行阶段可通过fmiSetXXX函数设置的变量属性组合。例如初始化阶段可设置的变量属性信息如图所示。

  • 问题定位

    1. 对照 FMU 运行状态机确认当前阶段允许设置的变量属性组合;
    2. 遍历调用函数时传入的变量ValueReference,在modelDescription.xml中查找对应变量;
    3. 检查该变量属性组合是否满足调用要求。
  • 解决方案:移除不允许设置的模型变量。

# 连续仿真阶段

# #3301 调用 Get/Set 函数后仿真失败

  • 问题现象:调用fmiSetXXXfmiGetXXX函数后仿真错误退出。

  • 问题分析fmiSetXXXfmiGetXXX函数中的XXX表示数据类型。用户调用这些函数时必须确保变量数据类型与函数一致。例如处理实型变量时,应使用fmiSetRealfmiGetReal

  • 问题定位

    1. 检查每个fmiSetXXXfmiGetXXX函数的返回值是否有误;
    2. 注释这些函数调用后再次仿真,排查是否正常。
  • 解决方案:根据变量的数据类型正确调用 FMU 接口函数,并确保传递给接口函数的参数类型都合法。

    警告

    • 在过往工程实践中,出现过用户给SetReal函数传递的valueReference为 0 的情况,导致 FMU 仿真报错SetReal failed: the 1 th valueReference 0 is invalid。注意,0 一定是非法的valueReference

    • 用户必须确保传入GetXXX/SetXXXvalueReference存在于modelDescription.xml中。

# #3302 重复 Set 后仿真失败

  • 问题现象:同一时刻,通过调用fmiSetBoolean/fmiSetInteger等设置函数重复set同一非实型变量后仿真错误退出。

  • 问题分析:MWORKS 导出的 FMU 不允许在同一时刻重复设置布尔类型、整型、字符串类型等离散外部变量的值,其中离散外部变量包括输入变量(input)和可调参量(tunable parameter)。同一时刻重复设置操作是指:

    1. 通过重复调用fmiSetXXX设置同一变量;
    2. fmiSetXXX函数ValueReference实参中包含重复的变量索引值。
  • 问题定位

    1. 检查调用程序是否存在重复的fmiSetXXX调用;
    2. 检查ValueReference实参中是否有重复的变量索引值;
    3. 注释这些函数调用后再次仿真,排查是否正常。
  • 解决方案:移除不必要的重复设置操作。

# #3303 修改通过 Get 函数获取的字符串仿真报错

  • 问题现象:调用fmiGetString后缓存字符串地址,后续直接编辑这个字符串时出现崩溃问题。

  • 问题分析:FMI 规范对String类型的接口函数特别强调:

    The strings returned by fmi2GetString must be copied in the target environment because the allocated memory for these strings might be deallocated by the next call to any of the fmi2 interface functions or it might be an internal string buffer that is reused。

    该函数返回的字符串是 FMU 内部内存,当函数执行完后可能被释放或被重复使用,外部程序不得更改该字符串内容。

  • 问题定位

    1. 检查调用程序中对字符串变量的处理逻辑;
    2. 注释fmiGetString函数后再次仿真,排查是否正常。
  • 解决方案:调用程序需将fmiGetString返回的字符串变量复制到新的目标内存。

# #3304 V2 FMU 仿真崩溃

  • 问题现象:V2 FMU 仿真过程中异常崩溃。

  • 问题分析:FMU 内部的内存通常通过外部提供的内存分配和释放函数进行管理。如果 FMU 实例化时提供的内存管理函数有误,可能导致 FMU 内部因内存问题而崩溃。FMU 实例化的实参中包含回调函数集合,必须按照 FMI 规范定义的C函数原型提供回调函数,确保函数参数列表与返回类型一致。如果错误提供回调函数,例如提供具有自动内存回收功能的不正确内存分配函数,可能导致 FMU 内部分配的内存被错误释放。

  • 问题定位:严格对照 FMU 实例化函数各参数定义确认实参的正确性。

  • 解决方案:使用C语言标准的callocfree函数。

# #3305 IDE 中运行出现栈溢出

  • 问题现象:在 Visual Studio 或其它 IDE 中调试运行 FMU 时出现栈溢出(Stack Overflow)异常。

  • 问题分析:用户调用程序在执行 FMU 接口函数特定功能时,可能因为函数调用栈过深或局部变量过大,导致栈空间耗尽,从而发生栈溢出异常。

  • 问题定位:进入项目属性 > 链接器 > 系统,调整堆栈保留大小堆栈提交大小,重新调试排查是否正常。

  • 解决方案:适当增加堆栈保留大小堆栈提交大小

# #3306 单步仿真缓慢

  • 问题现象:FMU 单步计算效率低。

  • 问题分析:同 #2301。分析,FMU 模型变量的读写操作会影响单步计算耗时。FMU 接口函数fmiSetXXXfmiGetXXX均支持数组参数,即通过一次函数调用传入所需读写的全部变量,而不是通过for循环重复执行标量形式的读写操作。

  • 问题定位:检查调用程序中的模型变量读写操作是否可优化。

  • 解决方案

    1. 对于Set操作,应在变量值变化后再调用fmiSetXXX,避免不必要的函数运行开销;
    2. 对于Get操作,仅获取模型对外输出变量,减少局部变量访问开销。

    注意

    注意,请勿在每次调用fmiSetXXX后立即调用fmiGetXXX,因为每次Set操作都会重置 FMU 内部计算状态,若立即Get模型变量则会触发模型计算新的变量值。

# #3307 CS FMU 长时间仿真报错

  • 问题现象Co-Simulation类型 FMU 长时间仿真出现异常。

  • 问题分析:外部调用程序每次执行DoStep函数时提供当前通信时刻currentCommunicationPoint与通信步长communicationStepSize,而Co-Simulation类型 FMU 内部维护一个运行时间calculationTime。外部通信时刻与 FMU 内部运行时间可能存在累积误差,导致 FMU 内部计算异常。

  • 问题定位:检查调用程序的通信时刻是否为通信步长的整数倍。

  • 解决方案:外部调用程序计算通信时刻时应避免使用累加方式(如currentCommunicationPoint += communicationStepSize),可采用currentCommunicationPoint = n * communicationStepSize的方式降低数值误差。

# 多线程调用

# #3401 多线程调用崩溃

  • 问题现象:使用多线程调用 FMU 接口函数导致 FMU 内部崩溃。

  • 问题分析:FMI 规范明确说明 FMU 实例不保证线程安全。多线程环境下使用 FMU 必须保证调用次序符合 FMU 运行状态机。例如,在DoStep阶段未完成时不得调用fmiGetXXX读取模型变量。通常,在DoStep完成后可以采用多线程调用fmiGetXXX分别读取不同类型的模型变量,或者将大批量变量分组读取;但其它可能导致 FMU 计算状态变更的操作(如DoStep执行中或fmiSetXXX执行后)都不能多线程调用fmiGetXXX。此外,不允许多线程调用DoStep函数。

  • 问题定位:取消多线程调用后排查是否正常。

  • 解决方案:优化多线程调用程序中不满足调用次序的部分。