基于FMI 2.0.3的C语言FMU Slave创建、初始化及核心函数实现疑问
一、FMU初始化(fmi2Instantiate)相关问题
1. GUID 传入值
每个FMU的modelDescription.xml文件中,<fmi2ModelDescription>标签下的guid属性是该FMU的唯一标识,直接复制该字符串作为参数传入即可。例如:"a1b2c3d4-5678-90ef-ghij-klmnopqrstuv"。
2. 资源路径传入值
资源路径指FMU解压后包含modelDescription.xml及相关资源文件的目录路径。若你的FMU是打包的.fmu文件,需先解压到指定目录,再将该目录的绝对路径(或模拟器可识别的相对路径)传入。比如Windows下用"./fmu_system_unpacked",Linux下用"./fmu_system_unpacked"。
3. 回调函数实现与传入
回调函数是FMU与模拟器交互的桥梁,需实现fmi2CallbackFunctions结构体中的4个核心函数:
fmi2Logger:处理FMU的日志输出,直接打印到控制台即可fmi2AllocateMemory:FMU申请内存时调用,封装标准mallocfmi2FreeMemory:释放FMU内存,封装标准freefmi2StepFinished:异步步结束通知,同步仿真模式下可留空实现
示例代码:
void fmi2Logger(fmi2ComponentEnvironment env, fmi2String instanceName, fmi2Status status, fmi2String category, fmi2String message, ...) { va_list args; va_start(args, message); printf("[%s] %s: ", category, instanceName); vprintf(message, args); va_end(args); } void* fmi2AllocateMemory(size_t nobj, size_t size) { return malloc(nobj * size); } void fmi2FreeMemory(void* obj) { free(obj); } void fmi2StepFinished(fmi2ComponentEnvironment env, fmi2Status status) { // 同步仿真无需处理,留空即可 } // 组装回调结构体 fmi2CallbackFunctions callbacks = { .logger = fmi2Logger, .allocateMemory = fmi2AllocateMemory, .freeMemory = fmi2FreeMemory, .stepFinished = fmi2StepFinished, .componentEnvironment = NULL // 可传入自定义上下文指针,比如模拟器状态 };
调用fmi2Instantiate时,将上述callbacks结构体传入,instanceName可自定义(如"FMU-System"),fmuType选择fmi2CoSimulation(协同仿真模式)。
二、FMU核心函数实现逻辑
1. fmi2Component 的作用
fmi2Component是FMU实例的句柄,本质是指向自定义FMU内部状态结构体的指针。比如针对FMU-system,可定义:
typedef struct { double system_state; // 系统状态变量(如温度、流量) double control_param; // 来自FMU-control的控制参数 fmi2Real current_time; // 当前仿真时间 } MySystemFMU;
在fmi2Instantiate中,用fmi2AllocateMemory分配该结构体内存,返回的指针即为fmi2Component,后续所有FMU函数均通过该句柄访问内部状态。
2. fmi2SetReal / fmi2GetReal 实现
这两个函数负责读写FMU变量,需提前在modelDescription.xml中为每个变量定义唯一的valueReference(VR):
fmi2SetReal:根据传入的VR找到对应内部变量,完成赋值fmi2GetReal:根据VR读取内部变量值,返回给模拟器
FMU-system的示例实现:
fmi2Status fmi2SetReal(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2Real value[]) { MySystemFMU* fmu = (MySystemFMU*)c; for (size_t i = 0; i < nvr; i++) { switch (vr[i]) { case CONTROL_PARAM_VR: // 提前定义的控制参数VR fmu->control_param = value[i]; break; default: return fmi2Error; } } return fmi2OK; } fmi2Status fmi2GetReal(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2Real value[]) { MySystemFMU* fmu = (MySystemFMU*)c; for (size_t i = 0; i < nvr; i++) { switch (vr[i]) { case SYSTEM_STATE_VR: // 提前定义的系统状态VR value[i] = fmu->system_state; break; default: return fmi2Error; } } return fmi2OK; }
3. fmi2DoStep 实现(FMU-system状态计算)
该函数是单次仿真步的核心,流程为:
- 校验当前时间与步长的合法性
- 利用当前控制参数,通过闭式方程计算新的系统状态
- 更新FMU内部的当前时间与状态
示例代码(假设闭式方程为system_state(t+dt) = system_state(t) + control_param * dt):
fmi2Status fmi2DoStep(fmi2Component c, fmi2Real currentCommunicationPoint, fmi2Real communicationStepSize, fmi2Boolean noSetFMUStatePriorToCurrentPoint) { MySystemFMU* fmu = (MySystemFMU*)c; // 执行闭式方程计算 fmu->system_state += fmu->control_param * communicationStepSize; // 更新当前仿真时间 fmu->current_time = currentCommunicationPoint + communicationStepSize; return fmi2OK; }
针对FMU-control,fmi2DoStep的逻辑则是:通过fmi2SetReal获取系统状态后,用闭式方程计算新的控制参数,等待模拟器通过fmi2GetReal读取。
内容的提问来源于stack exchange,提问作者user14018421

