ETAS CAL SUITE

Exporting C-Code for FMU Generation

Comprehensive guide for exporting simulation models as C-code from multiple tools — with and without full licenses — for use with the Cal Suite FMU Generator and ASCMO-MOCA

Overview & Workflow

The ETAS Cal Suite FMU Generator compiles C-code from any modeling environment into FMI 2.0 Co-Simulation FMUs that are directly loadable in ASCMO-MOCA for model-based calibration optimization.

Your Model
(Simulink, Modelica, Python, C...)
→ Export C-Code
(this guide)
→ Upload to
Cal Suite
→ Auto-generates
FMI Wrapper + XML
→ Compile
Multi-Platform
→ .fmu
MOCA-ready

What the Cal Suite Needs from You

ComponentRequired?Purpose
Model C source (.c)YesContains model equations and step function
Header files (.h)OptionalFor multi-file models, shared type/extern declarations
initialize() functionRecommendedCalled at FMU instantiation for state reset
calculate(double dt) functionYesCalled each co-simulation step
inp_* / out_* / par_* namingYesEnables automatic FMI variable detection
Alternative naming: ETAS naming conventions (Ve*_read, Ve*_write, Ke*) are also auto-detected.

4. MATLAB Coder (No Simulink Required)

$$ Requires: MATLAB + MATLAB Coder (cheaper than full Simulink + Embedded Coder)

Write Model as MATLAB Function

function [out_exhaust_temp, out_wall_temp] = exhaust_model(...
    inp_engine_speed, inp_load, inp_ambient_temp, inp_mass_flow, ...
    par_thermal_mass, par_heat_coeff, par_pipe_length, par_pipe_diam)
%#codegen    ← Required pragma for code generation

    persistent state_gas state_wall
    if isempty(state_gas)
        state_gas = 25.0;
        state_wall = 25.0;
    end
    
    dt = 0.001;  % Must match FMU step size
    T_comb = 400 + inp_load * 6.0 + (inp_engine_speed - 800) / 52;
    A_pipe = pi * par_pipe_diam * par_pipe_length;
    cp = 1100.0;
    
    Q_in = inp_mass_flow * cp * (T_comb - state_gas);
    Q_loss = par_heat_coeff * A_pipe * (state_gas - state_wall);
    
    state_gas = state_gas + (Q_in - Q_loss) / par_thermal_mass * dt;
    state_wall = state_wall + Q_loss / (par_thermal_mass * 2) * dt;
    
    out_exhaust_temp = state_gas;
    out_wall_temp = state_wall;
end

Generate C Code

cfg = coder.config('lib');
cfg.TargetLang = 'C';
cfg.GenCodeOnly = true;     % Let Cal Suite compile
cfg.GenerateReport = true;

% Define input types (all double scalars)
args = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
codegen -config cfg exhaust_model -args args

% Output: codegen/lib/exhaust_model/
%   ├── exhaust_model.c       ← Upload this
%   ├── exhaust_model.h       ← Upload this
%   ├── exhaust_model_types.h
%   └── rtwtypes.h
Key advantage: MATLAB Coder license is significantly cheaper than the full Simulink + Embedded Coder bundle. The generated C-code is production-quality and Cal Suite handles the FMI wrapping.

5. Without Any MATLAB/Simulink License

FREE No MathWorks license needed

If you have no MATLAB/Simulink license at all, here are your options:

Option A: Manual C Translation

If you have the model equations (from documentation, papers, or reference code), implement directly in C following Cal Suite naming conventions. See Section 9.

Option B: Open-Source Tools

  • OpenModelica — Full Modelica environment with C export (Section 6)
  • Scilab/Xcos — Simulink-like block diagrams with C generation (Section 7)
  • Python + Cython — Compile Python math to C (Section 8)

Option C: GNU Octave (Partial Compatibility)

GNU Octave runs many MATLAB scripts but does NOT generate C-code. Use it to:

  1. Validate model equations against reference data
  2. Extract calibration parameter values from .mat files
  3. Then manually translate the validated equations to C
% In Octave — extract cal values for your C implementation
pkg load io;
data = load('model_BCAL_val.mat');
% Write parameter arrays to a .h file for C inclusion
fid = fopen('model_params.h', 'w');
fprintf(fid, 'const double par_spark_map[120] = {\n');
for i = 1:numel(data.spark_map)
    fprintf(fid, '  %.6f,\n', data.spark_map(i));
end
fprintf(fid, '};\n');
fclose(fid);

Option D: AI-Assisted Translation

The Cal Suite Agent can help translate MATLAB/Simulink model descriptions to C:

Usage: Describe your model equations in natural language or paste MATLAB code into the Cal Suite chat. The agent will generate Cal Suite-compatible C code with proper inp_*/out_*/par_* naming and build the FMU.

6. OpenModelica (Free, Open-Source)

FREE Full Modelica environment with FMU and C-code export

Write Model in Modelica

model ExhaustTempModel
  // Inputs → become FMI inputs
  input Real inp_engine_speed(start=800) "Engine speed [rpm]";
  input Real inp_engine_load(start=0)    "Engine load [%]";
  input Real inp_ambient_temp(start=25)  "Ambient temp [degC]";
  input Real inp_mass_flow(start=0.01)   "Exhaust flow [kg/s]";
  
  // Outputs → become FMI outputs
  output Real out_exhaust_temp(start=25) "Exhaust gas temp [degC]";
  output Real out_wall_temp(start=25)    "Pipe wall temp [degC]";
  
  // Parameters → become FMI tunable parameters
  parameter Real par_thermal_mass = 850   "Thermal mass [J/K]";
  parameter Real par_heat_coeff = 45      "Heat transfer [W/(m2.K)]";
  parameter Real par_pipe_length = 1.2    "Pipe length [m]";
  parameter Real par_pipe_diameter = 0.06 "Pipe diameter [m]";
  
  // State variables
  Real T_gas(start=25);
  Real T_wall(start=25);
  
equation
  der(T_gas) = (inp_mass_flow * 1100 * (400 + inp_engine_load*6 
               + (inp_engine_speed-800)/52 - T_gas)
               - par_heat_coeff * Modelica.Constants.pi * par_pipe_diameter 
               * par_pipe_length * (T_gas - T_wall)) / par_thermal_mass;
               
  der(T_wall) = (par_heat_coeff * Modelica.Constants.pi * par_pipe_diameter
                * par_pipe_length * (T_gas - T_wall)
                - par_heat_coeff * Modelica.Constants.pi * par_pipe_diameter
                * par_pipe_length * 0.3 * (T_wall - inp_ambient_temp))
                / (par_thermal_mass * 2);
  
  out_exhaust_temp = T_gas;
  out_wall_temp = T_wall;
end ExhaustTempModel;

Option A: Direct FMU Export (quick but may need post-processing)

# Command line FMU export
omc --simCodeTarget=fmu ExhaustTempModel.mo
# → ExhaustTempModel.fmu

# Test in FMPy first:
python3 -c "from fmpy import simulate_fmu; simulate_fmu('ExhaustTempModel.fmu')"
Note: OpenModelica FMUs may include extra variables or non-standard XML elements that confuse MOCA. If MOCA can't load it, use Option B instead.

Option B: Export C-Code (recommended for Cal Suite)

# Generate C source only (no FMU packaging)
omc --simCodeTarget=C ExhaustTempModel.mo

# Output: ExhaustTempModel/
#   ├── ExhaustTempModel.c
#   ├── ExhaustTempModel_functions.c  
#   ├── ExhaustTempModel_records.c
#   └── ExhaustTempModel.h

# Then simplify for Cal Suite:
# Extract the equations into a single .c file with inp_/out_/par_ globals
# (Cal Suite Agent can help with this transformation)
Tip: If your Modelica model uses input Real inp_* and parameter Real par_* naming, OpenModelica preserves these names in the generated C code, making Cal Suite detection work with minimal post-processing.

7. Scilab/Xcos (Free, Open-Source)

FREE Simulink-like block diagram modeling with C generation

Create Model in Xcos

Use the Xcos block diagram editor (similar to Simulink). Key blocks:

Generate C Code from Xcos

// Scilab 6.1+ with Code Generation module
importXcosDiagram('exhaust_model.zcos');

// Configure for standalone C generation
CodeGen = scicos_codegeneration();
CodeGen.target = 'C';
CodeGen.solver = 'Fixed';     // Required for FMU
CodeGen.step_size = 0.001;
CodeGen.gen_standalone = %T;

// Generate
do_code_generation('exhaust_model', CodeGen);
// Output: exhaust_model/exhaust_model.c

Post-Process for Cal Suite

# Scilab generates generic variable names (u_1, y_1, rpar_1...)
# Rename to Cal Suite convention:
sed -i '' 's/u\[0\]/inp_engine_speed/g' exhaust_model.c
sed -i '' 's/u\[1\]/inp_engine_load/g' exhaust_model.c
sed -i '' 's/y\[0\]/out_exhaust_temp/g' exhaust_model.c
sed -i '' 's/rpar\[0\]/par_thermal_mass/g' exhaust_model.c
# ... rename all variables, then upload to Cal Suite

8. Python Models

FREE For models implemented in Python/NumPy

Option A: Cython (Compile Python → C)

# File: exhaust_model.pyx
# cython: boundscheck=False, wraparound=False

# Inputs (public = visible as C globals)
cdef public double inp_engine_speed = 800.0
cdef public double inp_engine_load = 0.0
cdef public double inp_ambient_temp = 25.0
cdef public double inp_mass_flow = 0.01

# Outputs
cdef public double out_exhaust_temp = 25.0
cdef public double out_wall_temp = 25.0

# Parameters (tunable in MOCA)
cdef public double par_thermal_mass = 850.0
cdef public double par_heat_coeff = 45.0
cdef public double par_pipe_length = 1.2
cdef public double par_pipe_diameter = 0.06

# Internal state
cdef double state_gas = 25.0
cdef double state_wall = 25.0

cpdef void initialize():
    global state_gas, state_wall
    state_gas = 25.0
    state_wall = 25.0

cpdef void calculate(double dt):
    global state_gas, state_wall
    cdef double T_comb, A_pipe, Q_in, Q_loss
    
    T_comb = 400.0 + inp_engine_load * 6.0 + (inp_engine_speed - 800.0) / 52.0
    A_pipe = 3.14159265 * par_pipe_diameter * par_pipe_length
    Q_in = inp_mass_flow * 1100.0 * (T_comb - state_gas)
    Q_loss = par_heat_coeff * A_pipe * (state_gas - state_wall)
    
    state_gas = state_gas + (Q_in - Q_loss) / par_thermal_mass * dt
    state_wall = state_wall + Q_loss / (par_thermal_mass * 2.0) * dt
    
    out_exhaust_temp = state_gas
    out_wall_temp = state_wall
# Generate C from Cython (no Python runtime dependency):
cython --embed exhaust_model.pyx
# → exhaust_model.c   (upload to Cal Suite)

Option B: Manual Translation (Recommended for Simple Models)

For Python models that are primarily arithmetic (NumPy math without complex library calls), translate directly to C. The Cal Suite Agent can assist with this automatically.

Option C: Cal Suite AI Translation

# Simply upload your Python model to the Cal Suite chat:
#   "Build an FMU from this Python model"
#   [attach: exhaust_model.py]
#
# The agent will:
# 1. Parse your Python code
# 2. Translate to C with inp_/out_/par_ naming
# 3. Generate the FMI wrapper
# 4. Compile and validate
# 5. Return the .fmu file

9. Hand-Written C (Most Reliable Path)

FREE Full control, guaranteed Cal Suite compatibility

Template

/* ═══════════════════════════════════════════════════════════
 * Cal Suite FMU Generator — C Model Template
 * 
 * RULES:
 *   1. Use inp_* for inputs, out_* for outputs, par_* for parameters
 *   2. Declare all as GLOBAL doubles at file scope
 *   3. Implement initialize() and calculate(double dt)
 *   4. Only use: math.h, string.h, stdlib.h (no stdio, no POSIX)
 * ═══════════════════════════════════════════════════════════ */
#include <math.h>

/* ═══ INPUTS (set by MOCA each step) ═══ */
double inp_engine_speed = 800.0;    /* rpm */
double inp_engine_load = 0.0;       /* % */
double inp_ambient_temp = 25.0;     /* degC */
double inp_mass_flow = 0.01;        /* kg/s */

/* ═══ OUTPUTS (read by MOCA each step) ═══ */
double out_exhaust_temp = 25.0;     /* degC */
double out_wall_temp = 25.0;        /* degC */
double out_heat_loss = 0.0;         /* W */

/* ═══ PARAMETERS (tunable by MOCA optimizer) ═══ */
double par_thermal_mass = 850.0;    /* J/K */
double par_heat_coeff = 45.0;       /* W/(m2.K) */
double par_pipe_length = 1.2;       /* m */
double par_pipe_diameter = 0.06;    /* m */
double par_bias_offset = 0.0;       /* degC — correction term */

/* ═══ INTERNAL STATE (not visible to FMI) ═══ */
static double s_gas_temp = 25.0;
static double s_wall_temp = 25.0;

void initialize(void) {
    s_gas_temp = inp_ambient_temp;
    s_wall_temp = inp_ambient_temp;
    out_exhaust_temp = inp_ambient_temp;
    out_wall_temp = inp_ambient_temp;
    out_heat_loss = 0.0;
}

void calculate(double dt) {
    /* Guard against bad inputs */
    double rpm = fmax(0.0, fmin(8000.0, inp_engine_speed));
    double load = fmax(0.0, fmin(100.0, inp_engine_load));
    double mdot = fmax(0.001, inp_mass_flow);  /* avoid div-by-zero */
    
    /* Physics */
    double T_comb = 400.0 + load * 6.0 + (rpm - 800.0) / 52.0;
    double A_pipe = 3.14159265 * par_pipe_diameter * par_pipe_length;
    double cp = 1100.0;
    
    double Q_in = mdot * cp * (T_comb - s_gas_temp);
    double Q_gas_wall = par_heat_coeff * A_pipe * (s_gas_temp - s_wall_temp);
    double Q_wall_amb = par_heat_coeff * A_pipe * 0.3 * (s_wall_temp - inp_ambient_temp);
    
    /* Euler integration */
    s_gas_temp += (Q_in - Q_gas_wall) / par_thermal_mass * dt;
    s_wall_temp += (Q_gas_wall - Q_wall_amb) / (par_thermal_mass * 2.0) * dt;
    
    /* Clamp to physical range */
    s_gas_temp = fmax(-40.0, fmin(1200.0, s_gas_temp));
    s_wall_temp = fmax(-40.0, fmin(900.0, s_wall_temp));
    
    /* Set outputs */
    out_exhaust_temp = s_gas_temp + par_bias_offset;
    out_wall_temp = s_wall_temp;
    out_heat_loss = Q_gas_wall + Q_wall_amb;
}
Upload this single .c file to Cal Suite and it will automatically generate the FMI wrapper, modelDescription.xml, compile for all platforms, and produce a validated .fmu ready for MOCA.

10. ASCET-DEVELOPER (ETAS Tool Chain)

ETAS ETAS native tool with production-quality C generation

Export Steps

  1. Open your component/module in ASCET-DEVELOPER
  2. Right-click → Generate Code → C Language
  3. Target: Generic C (not ECU-specific)
  4. Interface mode: Global variables (for Cal Suite compatibility)
  5. Export Data Dictionary alongside code

Generated File Structure

ASCET_Output/
├── Module.c             ← Model implementation (step function)
├── Module.h             ← Interface declarations
├── Module_CalPrm.c      ← Calibration parameter definitions
├── Module_CalPrm.h      ← Parameter declarations (extern)
└── Module_Impl.c        ← Internal implementation details

ASCET Naming → Cal Suite Mapping

ASCET ElementASCET Name PatternFMI CausalityCal Suite Detection
Message (received)Ve<Module>_<signal>inputAuto (Ve* without _write)
Message (sent)Ve<Module>_<signal>_writeoutputAuto (Ve*_write)
Calibration ParameterKe<Module>_<param>parameter (tunable)Auto (Ke* prefix)
System ConstantKe<Module>_<const>parameter (fixed)Auto (const qualifier)
Characteristic CurveKe<Module>_<curve>[N]parameter (tunable)Auto (array)
Characteristic MapKe<Module>_<map>[N*M]parameter (tunable)Auto (2D array)
ASCET-generated code is natively compatible with the Cal Suite FMU Generator — no renaming or post-processing needed. Just upload the generated .c and .h files.

11. GT-SUITE / AVL CRUISE M

GT-SUITE

$$$ 1D simulation tool for powertrain, thermal, and fluid systems

Option A: Direct FMU Export (may need license at runtime)

GT-SUITE → File → Export → FMU (Co-Simulation)
  FMI Version: 2.0
  Type: Co-Simulation
  Solver: Built-in (GT's solver embedded in FMU)
Limitation: GT-SUITE FMUs bundle the GT solver engine, require a runtime license, and produce 100+ MB files. Parameters may not all be tunable in MOCA.

Option B: Reduced-Order Model → C (recommended)

  1. In GT-SUITE, create a Neural Network ROM or Response Surface from your full physics model
  2. Export ROM as standalone C: GT-SUITE → ROM → Export → C Source
  3. Rename variables to Cal Suite convention (inp_*, out_*, par_*)
  4. Upload to Cal Suite

AVL CRUISE M

$$$ System simulation for vehicle and powertrain

  1. Direct FMU: Model → Export → FMU 2.0 (Co-Simulation) — mark parameters as "External" for tunability
  2. Modelica export: CRUISE M can export as Modelica source → compile with OpenModelica → C-code
  3. Test first: Load exported FMU in FMPy to verify parameter access before sending to MOCA

13. Naming Convention Reference

Primary Convention (Recommended)

PrefixFMI RoleFMI AttributesExample
inp_*Input signalcausality="input" variability="continuous"double inp_engine_speed = 800.0;
out_*Output signalcausality="output" variability="continuous"double out_exhaust_temp = 25.0;
par_*Tunable parametercausality="parameter" variability="tunable" initial="exact"double par_heat_coeff = 45.0;

Alternative: ETAS Convention (Auto-Detected)

PatternFMI RoleExample
Ve*_read or Ve* (no _write)Inputdouble VeRPMR_n_EngSpd;
Ve*_writeOutputdouble VeEXHR_T_UsCatTemp_write;
Ke*Parameterdouble KeEXHR_T_InitialTemp;

Critical Rules

Required Functions

FunctionSignatureWhen Called
initializevoid initialize(void)fmi2ExitInitializationMode — reset states
calculate / stepvoid calculate(double dt)fmi2DoStep — execute one time step
The function names calculate, step, update, compute, and run are all auto-detected. The Cal Suite scans for void <name>(double) patterns.

14. What Cal Suite Does Automatically

Upload
.c / .h files
→ Parse
inp_/out_/par_
→ Generate
FMI Wrapper
→ Generate
XML
→ Compile
4 Platforms
→ Validate
5-Point Check
→ .fmu
Download

Auto-Generated Components

  1. FMI 2.0 Wrapper — fmi2Instantiate, fmi2DoStep, fmi2GetReal, fmi2SetReal, and all required API functions
  2. modelDescription.xml — Correct causality, variability, ModelStructure, CoSimulation element
  3. Multi-platform binaries — darwin64 (.dylib), linux64 (.so), win64 (.dll), win32 (.dll)
  4. FMU package — Valid ZIP with proper directory layout

5-Point Validation

#CheckWhat It Verifies
1ZIP StructuremodelDescription.xml present, binaries present
2Causality CorrectnessInputs/outputs/parameters properly classified
3MOCA ReadinessHas ≥1 input, ≥1 output, ≥1 parameter; CoSimulation element present
4Simulation Smoke TestInstantiate → step → outputs are not NaN
5Fidelity CheckAll detected source variables present in FMU (100% coverage)

15. Troubleshooting

SymptomRoot CauseFix
"No Input available in FMU Model" in MOCANo inp_* globals foundDeclare inputs as double inp_* at file scope (global)
"No Output available in FMU Model"No out_* globals foundDeclare outputs as double out_* at file scope
Parameters not tunable in MOCAParameters declared inside functions or with staticMove to global scope: double par_*
Cross-compilation fails (win64)Code uses POSIX headers (unistd.h, sys/*)Only include math.h, string.h, stdlib.h
Outputs are all zerocalculate() not writing to out_* globalsEnsure step function assigns to global out_* variables
Parameter changes have no effectParameters read once in initialize() onlyRead par_* globals inside calculate() each step
NaN/Inf in outputsDivision by zero or unbounded growthAdd input clamping (fmax/fmin) and guards
FMU works in FMPy but not MOCAVendorAnnotations, wrong variability, missing CoSim elementLet Cal Suite generate XML (don't hand-edit)
MOCA crashes on load<VendorAnnotations> in XMLRemove all VendorAnnotations (Cal Suite never adds them)
Array parameter naming mismatch1-based indexing in namesUse 0-based: par_map[0,0] not [1,1]
"GUID mismatch" errorXML rebuilt without recompiling binaryRegenerate both XML and binary from same build
Model runs but gives wrong valuesLookup table axis mismatch or wrong unitsVerify axis values and interpolation order (row vs col major)

Compilation Restrictions

Do NOT include in your model code:
  • #include <stdio.h> — printf/scanf fail on cross-compile
  • #include <unistd.h> — POSIX only, not available on Windows
  • #include <sys/time.h> — platform-specific
  • malloc() / free() — avoid dynamic allocation in model code
  • File I/O (fopen, fread) — not available in FMU sandbox
Safe to use: math.h, string.h, stdlib.h (for abs()), float.h, limits.h, stdint.h

16. Comparison Table — All Export Paths

Source Tool License Export Method Post-Processing Cal Suite Ready
Simulink + Embedded Coder$$$ERT code generationNone (use inp_/out_/par_ naming)★★★★★
Simulink + SWIFT$$$SWIFT wrapper + headersNone (auto-parsed)★★★★★
Simulink stub/cal files$$Export .m filesNone (auto-parsed)★★★★★
MATLAB Coder$$MATLAB function → CMinimal (naming)★★★★☆
ASCET-DEVELOPERETASNative C generationNone (ETAS naming)★★★★★
dSPACE TargetLink$$$TargetLink code genFlatten structs to globals★★★★☆
GT-SUITE (ROM)$$$ROM → C exportRename variables★★★☆☆
AVL CRUISE M$$$FMU or Modelica exportMay need rebuild via Cal Suite★★★☆☆
OpenModelicaFREEModelica → C or FMUSimplify generated C★★★☆☆
Scilab/XcosFREEXcos code generationRename variables (sed)★★★☆☆
Python (Cython)FREECython → CMinimal★★★★☆
Hand-written CFREEDirect implementationNone★★★★★
AI Translation (Cal Suite)FREEDescribe model → CNone (agent handles it)★★★★★