OMCompiler/Compiler/Template/CodegenESP32.tpl
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of OpenModelica. | ||
| 3 | * | ||
| 4 | * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), | ||
| 5 | * c/o Linköpings universitet, Department of Computer and Information Science, | ||
| 6 | * SE-58183 Linköping, Sweden. | ||
| 7 | * | ||
| 8 | * All rights reserved. | ||
| 9 | * | ||
| 10 | * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR | ||
| 11 | * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. | ||
| 12 | * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES | ||
| 13 | * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL | ||
| 14 | * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. | ||
| 15 | * | ||
| 16 | * The OpenModelica software and the OSMC (Open Source Modelica Consortium) | ||
| 17 | * Public License (OSMC-PL) are obtained from OSMC, either from the above | ||
| 18 | * address, from the URLs: | ||
| 19 | * http://www.openmodelica.org or | ||
| 20 | * https://github.com/OpenModelica/ or | ||
| 21 | * http://www.ida.liu.se/projects/OpenModelica, | ||
| 22 | * and in the OpenModelica distribution. | ||
| 23 | * | ||
| 24 | * GNU AGPL version 3 is obtained from: | ||
| 25 | * https://www.gnu.org/licenses/licenses.html#GPL | ||
| 26 | * | ||
| 27 | * This program is distributed WITHOUT ANY WARRANTY; without | ||
| 28 | * even the implied warranty of MERCHANTABILITY or FITNESS | ||
| 29 | * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH | ||
| 30 | * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. | ||
| 31 | * | ||
| 32 | * See the full OSMC Public License conditions for more details. | ||
| 33 | * | ||
| 34 | */ | ||
| 35 | |||
| 36 | // This file defines templates for transforming Modelica models into an | ||
| 37 | // ESP-IDF project that can be built and flashed onto an ESP32 board | ||
| 38 | // (developed for and tested on the ESP32-C6). | ||
| 39 | // | ||
| 40 | // The generated project is self contained: it has no dependency on the | ||
| 41 | // OpenModelica simulation runtime and no dependency on the FMI headers. The | ||
| 42 | // equations, functions and expressions are the same ones the experimental | ||
| 43 | // embedded C target generates, which is why most of the low level templates | ||
| 44 | // are imported from CodegenEmbeddedC instead of being duplicated here. | ||
| 45 | // | ||
| 46 | // Four templates are called by the code generator, one per generated file: | ||
| 47 | // | ||
| 48 | // projectCMakeFile <prefix>_esp32/CMakeLists.txt | ||
| 49 | // componentCMakeFile <prefix>_esp32/main/CMakeLists.txt | ||
| 50 | // sdkconfigFile <prefix>_esp32/sdkconfig.defaults | ||
| 51 | // modelHeaderFile <prefix>_esp32/main/<prefix>_model.h | ||
| 52 | // modelSourceFile <prefix>_esp32/main/<prefix>_model.c | ||
| 53 | // appMainFile <prefix>_esp32/main/main.c | ||
| 54 | // readmeFile <prefix>_esp32/README.md | ||
| 55 | |||
| 56 | package CodegenESP32 | ||
| 57 | |||
| 58 | import interface SimCodeTV; | ||
| 59 | import interface SimCodeBackendTV; | ||
| 60 | |||
| 61 | import CodegenUtil.*; | ||
| 62 | import CodegenUtilSimulation.*; | ||
| 63 | import CodegenEmbeddedC.*; | ||
| 64 | |||
| 65 | ✗ | template projectCMakeFile(SimCode simCode) | |
| 66 | "Top level CMakeLists.txt of the generated ESP-IDF project." | ||
| 67 | ::= | ||
| 68 | let modelNamePrefixStr = modelNamePrefix(simCode) | ||
| 69 | match simCode | ||
| 70 | case simCode as SIMCODE(modelInfo=MODELINFO(__)) then | ||
| 71 | << | ||
| 72 | # Generated by OpenModelica from <%dotPath(modelInfo.name)%> | ||
| 73 | cmake_minimum_required(VERSION 3.16) | ||
| 74 | |||
| 75 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) | ||
| 76 | project(<%modelNamePrefixStr%>_esp32 C) | ||
| 77 | >> | ||
| 78 | end projectCMakeFile; | ||
| 79 | |||
| 80 | ✗ | template componentCMakeFile(SimCode simCode) | |
| 81 | "CMakeLists.txt of the main component of the generated ESP-IDF project." | ||
| 82 | ::= | ||
| 83 | let modelNamePrefixStr = modelNamePrefix(simCode) | ||
| 84 | << | ||
| 85 | # Generated by OpenModelica | ||
| 86 | idf_component_register(SRCS "main.c" "<%modelNamePrefixStr%>_model.c" | ||
| 87 | INCLUDE_DIRS ".") | ||
| 88 | >> | ||
| 89 | end componentCMakeFile; | ||
| 90 | |||
| 91 | ✗ | template sdkconfigFile(SimCode simCode) | |
| 92 | "sdkconfig.defaults of the generated ESP-IDF project." | ||
| 93 | ::= | ||
| 94 | << | ||
| 95 | # Generated by OpenModelica | ||
| 96 | # The chip the model is built for. Override with `idf.py set-target <chip>`. | ||
| 97 | CONFIG_IDF_TARGET="esp32c6" | ||
| 98 | # Print the results on UART0, which is the port the USB-serial bridge of the | ||
| 99 | # devkit is wired to, and therefore the same port the board is flashed over. | ||
| 100 | # Replace this with CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y to print them on the | ||
| 101 | # built in USB-Serial-JTAG port instead, which enumerates as a port of its own. | ||
| 102 | CONFIG_ESP_CONSOLE_UART_DEFAULT=y | ||
| 103 | # The reduced formatting of newlib-nano does not print doubles, and the whole | ||
| 104 | # output of the simulation is doubles. | ||
| 105 | CONFIG_NEWLIB_NANO_FORMAT=n | ||
| 106 | # The model is solved in floating point, so keep the FPU related optimizations | ||
| 107 | # and compile for speed rather than for size. | ||
| 108 | CONFIG_COMPILER_OPTIMIZATION_PERF=y | ||
| 109 | >> | ||
| 110 | end sdkconfigFile; | ||
| 111 | |||
| 112 | ✗ | template modelHeaderFile(SimCode simCode) | |
| 113 | "Public interface of the generated model, used by main.c." | ||
| 114 | ::= | ||
| 115 | let modelNamePrefixStr = modelNamePrefix(simCode) | ||
| 116 | match simCode | ||
| 117 | case simCode as SIMCODE(simulationSettingsOpt=NONE()) then | ||
| 118 | error(sourceInfo(), "Missing simulation settings") | ||
| 119 | case simCode as SIMCODE(modelInfo=MODELINFO(varInfo=varInfo as VARINFO(__)), | ||
| 120 | simulationSettingsOpt=SOME(settings as SIMULATION_SETTINGS(__))) then | ||
| 121 | << | ||
| 122 | /* Generated by OpenModelica from <%dotPath(modelInfo.name)%> */ | ||
| 123 | #ifndef OM_<%modelNamePrefixStr%>_MODEL_H | ||
| 124 | #define OM_<%modelNamePrefixStr%>_MODEL_H | ||
| 125 | |||
| 126 | #define OM_MODEL_NAME "<%dotPath(modelInfo.name)%>" | ||
| 127 | #define OM_START_TIME (<%settings.startTime%>) | ||
| 128 | #define OM_STOP_TIME (<%settings.stopTime%>) | ||
| 129 | #define OM_STEP_SIZE (<%settings.stepSize%>) | ||
| 130 | #define OM_NUM_REAL (<%nVariablesReal(varInfo)%>) | ||
| 131 | |||
| 132 | /* The same scalar types the embedded C target uses, so that the equations and | ||
| 133 | the functions below are generated by the very same templates. They are | ||
| 134 | spelled out here instead of being included from fmi2TypesPlatform.h to keep | ||
| 135 | the generated project free of any OpenModelica include path. */ | ||
| 136 | typedef double fmi2Real; | ||
| 137 | typedef int fmi2Integer; | ||
| 138 | typedef int fmi2Boolean; | ||
| 139 | typedef char fmi2Char; | ||
| 140 | typedef const fmi2Char* fmi2String; | ||
| 141 | |||
| 142 | #define fmi2True 1 | ||
| 143 | #define fmi2False 0 | ||
| 144 | |||
| 145 | typedef enum { | ||
| 146 | fmi2OK = 0, | ||
| 147 | fmi2Warning, | ||
| 148 | fmi2Discard, | ||
| 149 | fmi2Error, | ||
| 150 | fmi2Fatal, | ||
| 151 | fmi2Pending | ||
| 152 | } fmi2Status; | ||
| 153 | |||
| 154 | typedef struct <%symbolName(modelNamePrefixStr,"fmi2Component_s")%>* fmi2Component; | ||
| 155 | |||
| 156 | /* Names of the real variables, in the order <%symbolName(modelNamePrefixStr,"getReal")%> | ||
| 157 | indexes them: the states first, then the state derivatives, then the | ||
| 158 | algebraic and the discrete real variables. */ | ||
| 159 | extern const char * const <%symbolName(modelNamePrefixStr,"realNames")%>[OM_NUM_REAL]; | ||
| 160 | |||
| 161 | /* The single statically allocated instance of the model. There is no heap | ||
| 162 | allocation anywhere in the generated code. */ | ||
| 163 | fmi2Component <%symbolName(modelNamePrefixStr,"instantiate")%>(void); | ||
| 164 | /* Sets the time and evaluates the equations once, so that the variables are | ||
| 165 | consistent before the first step is taken. */ | ||
| 166 | fmi2Status <%symbolName(modelNamePrefixStr,"initialize")%>(fmi2Component comp, fmi2Real startTime); | ||
| 167 | /* One explicit Euler step of size stepSize, taken from currentTime. */ | ||
| 168 | fmi2Status <%symbolName(modelNamePrefixStr,"step")%>(fmi2Component comp, fmi2Real currentTime, fmi2Real stepSize); | ||
| 169 | fmi2Real <%symbolName(modelNamePrefixStr,"getTime")%>(fmi2Component comp); | ||
| 170 | fmi2Real <%symbolName(modelNamePrefixStr,"getReal")%>(fmi2Component comp, int index); | ||
| 171 | |||
| 172 | #endif /* OM_<%modelNamePrefixStr%>_MODEL_H */ | ||
| 173 | >> | ||
| 174 | end modelHeaderFile; | ||
| 175 | |||
| 176 | ✗ | template modelSourceFile(SimCode simCode) | |
| 177 | "The model itself: state, equations and the fixed step solver." | ||
| 178 | ::= | ||
| 179 | let modelNamePrefixStr = modelNamePrefix(simCode) | ||
| 180 | match simCode | ||
| 181 | case simCode as SIMCODE(simulationSettingsOpt=NONE()) then | ||
| 182 | error(sourceInfo(), "Missing simulation settings") | ||
| 183 | case simCode as SIMCODE(modelInfo=MODELINFO(functions=functions, varInfo=varInfo as VARINFO(__), vars=vars as SIMVARS(__)), | ||
| 184 | extObjInfo=extObjInfo as EXTOBJINFO(vars=extObjVars), | ||
| 185 | simulationSettingsOpt=SOME(settings as SIMULATION_SETTINGS(__))) then | ||
| 186 | << | ||
| 187 | /* Generated by OpenModelica from <%dotPath(modelInfo.name)%> */ | ||
| 188 | #include "<%modelNamePrefixStr%>_model.h" | ||
| 189 | |||
| 190 | #include <math.h> | ||
| 191 | #include <stdarg.h> | ||
| 192 | #include <stdio.h> | ||
| 193 | |||
| 194 | void ModelicaFormatMessage(const char *fmt, ...) | ||
| 195 | { | ||
| 196 | va_list args; | ||
| 197 | va_start(args, fmt); | ||
| 198 | vprintf(fmt, args); | ||
| 199 | va_end(args); | ||
| 200 | } | ||
| 201 | |||
| 202 | struct <%symbolName(modelNamePrefixStr,"fmi2Component_s")%> { | ||
| 203 | fmi2Real currentTime; | ||
| 204 | <% match nVariablesReal(varInfo) | ||
| 205 | case 0 then "" | ||
| 206 | case n then 'fmi2Real fmi2RealVars[<%n%>];<%\n%>' | ||
| 207 | %><% match varInfo.numIntAlgVars | ||
| 208 | case 0 then "" | ||
| 209 | case n then 'fmi2Integer fmi2IntegerVars[<%n%>];<%\n%>' | ||
| 210 | %><% match varInfo.numBoolAlgVars | ||
| 211 | case 0 then "" | ||
| 212 | case n then 'fmi2Boolean fmi2BooleanVars[<%n%>];<%\n%>' | ||
| 213 | %><% match varInfo.numStringAlgVars | ||
| 214 | case 0 then "" | ||
| 215 | else error(sourceInfo(), "String variables not supported yet") | ||
| 216 | %><% match varInfo.numParams | ||
| 217 | case 0 then "" | ||
| 218 | case n then 'fmi2Real fmi2RealParameter[<%n%>];<%\n%>' | ||
| 219 | %><% match varInfo.numIntParams | ||
| 220 | case 0 then "" | ||
| 221 | case n then 'fmi2Integer fmi2IntegerParameter[<%n%>];<%\n%>' | ||
| 222 | %><% match varInfo.numBoolParams | ||
| 223 | case 0 then "" | ||
| 224 | case n then 'fmi2Boolean fmi2BooleanParameter[<%n%>];<%\n%>' | ||
| 225 | %><% match listLength(extObjVars) | ||
| 226 | case 0 then "" | ||
| 227 | case n then 'void* extObjs[<%n%>];<%\n%>' | ||
| 228 | %> | ||
| 229 | }; | ||
| 230 | |||
| 231 | static struct <%symbolName(modelNamePrefixStr,"fmi2Component_s")%> <%symbolName(modelNamePrefixStr,"component")%> = { | ||
| 232 | .currentTime = <%settings.startTime%>, | ||
| 233 | <% match nVariablesReal(varInfo) | ||
| 234 | case 0 then "" | ||
| 235 | else | ||
| 236 | << | ||
| 237 | .fmi2RealVars = { | ||
| 238 | <%vars.stateVars |> var => startValue(var) %> | ||
| 239 | <%vars.derivativeVars |> var => startValue(var) %> | ||
| 240 | <%vars.algVars |> var => startValue(var) %> | ||
| 241 | <%vars.discreteAlgVars |> var => startValue(var) %> | ||
| 242 | <%vars.realOptimizeConstraintsVars |> var => startValue(var) %> | ||
| 243 | <%vars.realOptimizeFinalConstraintsVars |> var => startValue(var) %> | ||
| 244 | },<%\n%> | ||
| 245 | >> | ||
| 246 | %><% match varInfo.numIntAlgVars | ||
| 247 | case 0 then "" | ||
| 248 | else | ||
| 249 | << | ||
| 250 | .fmi2IntegerVars = { | ||
| 251 | <%vars.intAlgVars |> var => startValue(var) %> | ||
| 252 | },<%\n%> | ||
| 253 | >> | ||
| 254 | %><% match varInfo.numBoolAlgVars | ||
| 255 | case 0 then "" | ||
| 256 | else | ||
| 257 | << | ||
| 258 | .fmi2BooleanVars = { | ||
| 259 | <%vars.boolAlgVars |> var => startValue(var) %> | ||
| 260 | },<%\n%> | ||
| 261 | >> | ||
| 262 | %><% match varInfo.numParams | ||
| 263 | case 0 then "" | ||
| 264 | else | ||
| 265 | << | ||
| 266 | .fmi2RealParameter = { | ||
| 267 | <%vars.paramVars |> var => startValue(var) %> | ||
| 268 | },<%\n%> | ||
| 269 | >> | ||
| 270 | %><% match varInfo.numIntParams | ||
| 271 | case 0 then "" | ||
| 272 | else | ||
| 273 | << | ||
| 274 | .fmi2IntegerParameter = { | ||
| 275 | <%vars.intParamVars |> var => startValue(var) %> | ||
| 276 | },<%\n%> | ||
| 277 | >> | ||
| 278 | %><% match varInfo.numBoolParams | ||
| 279 | case 0 then "" | ||
| 280 | else | ||
| 281 | << | ||
| 282 | .fmi2BooleanParameter = { | ||
| 283 | <%vars.boolParamVars |> var => startValue(var) %> | ||
| 284 | },<%\n%> | ||
| 285 | >> | ||
| 286 | %> | ||
| 287 | }; | ||
| 288 | |||
| 289 | const char * const <%symbolName(modelNamePrefixStr,"realNames")%>[OM_NUM_REAL] = { | ||
| 290 | <%vars.stateVars |> var => realVarName(var) %> | ||
| 291 | <%vars.derivativeVars |> var => realVarName(var) %> | ||
| 292 | <%vars.algVars |> var => realVarName(var) %> | ||
| 293 | <%vars.discreteAlgVars |> var => realVarName(var) %> | ||
| 294 | <%vars.realOptimizeConstraintsVars |> var => realVarName(var) %> | ||
| 295 | <%vars.realOptimizeFinalConstraintsVars |> var => realVarName(var) %> | ||
| 296 | }; | ||
| 297 | |||
| 298 | /* TODO: Generate used builtin functions before SimCode */ | ||
| 299 | static inline double om_mod(double x, double y) | ||
| 300 | { | ||
| 301 | return x-floor(x/y)*y; | ||
| 302 | } | ||
| 303 | |||
| 304 | <%functionsFile(functions, literals, externalFunctionIncludes)%> | ||
| 305 | |||
| 306 | /* Evaluates every equation of the model for the current time and the current | ||
| 307 | state, which leaves both the state derivatives and the algebraic variables | ||
| 308 | up to date. */ | ||
| 309 | static fmi2Status <%symbolName(modelNamePrefixStr,"functionEquations")%>(fmi2Component comp) | ||
| 310 | { | ||
| 311 | <% match allEquations | ||
| 312 | case {} then "" | ||
| 313 | case {eqs} then (eqs |> eq => equation_(eq); separator="\n") | ||
| 314 | else (allEquations |> eqs => (eqs |> eq => equation_(eq); separator="\n"); separator="\n") | ||
| 315 | %> | ||
| 316 | return fmi2OK; | ||
| 317 | } | ||
| 318 | |||
| 319 | fmi2Component <%symbolName(modelNamePrefixStr,"instantiate")%>(void) | ||
| 320 | { | ||
| 321 | return &<%symbolName(modelNamePrefixStr,"component")%>; | ||
| 322 | } | ||
| 323 | |||
| 324 | fmi2Status <%symbolName(modelNamePrefixStr,"initialize")%>(fmi2Component comp, fmi2Real startTime) | ||
| 325 | { | ||
| 326 | comp->currentTime = startTime; | ||
| 327 | <%callExternalObjectConstructors(extObjInfo)%> | ||
| 328 | return <%symbolName(modelNamePrefixStr,"functionEquations")%>(comp); | ||
| 329 | } | ||
| 330 | |||
| 331 | fmi2Status <%symbolName(modelNamePrefixStr,"step")%>(fmi2Component comp, fmi2Real currentTime, fmi2Real stepSize) | ||
| 332 | { | ||
| 333 | fmi2Status status; | ||
| 334 | comp->currentTime = currentTime; | ||
| 335 | /* The derivatives are evaluated before the states are advanced, which makes | ||
| 336 | the step below a plain explicit Euler step. */ | ||
| 337 | status = <%symbolName(modelNamePrefixStr,"functionEquations")%>(comp); | ||
| 338 | if (status != fmi2OK) { | ||
| 339 | return status; | ||
| 340 | } | ||
| 341 | <%match varInfo.numStateVars | ||
| 342 | case 0 then "" | ||
| 343 | else | ||
| 344 | << | ||
| 345 | { | ||
| 346 | int i; | ||
| 347 | for (i=0; i<<%varInfo.numStateVars%>; i++) { | ||
| 348 | comp->fmi2RealVars[i] += comp->fmi2RealVars[i+<%varInfo.numStateVars%>]*stepSize; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | >> | ||
| 352 | %> | ||
| 353 | comp->currentTime = currentTime + stepSize; | ||
| 354 | /* Report the algebraic variables at the time the states were advanced to. */ | ||
| 355 | return <%symbolName(modelNamePrefixStr,"functionEquations")%>(comp); | ||
| 356 | } | ||
| 357 | |||
| 358 | fmi2Real <%symbolName(modelNamePrefixStr,"getTime")%>(fmi2Component comp) | ||
| 359 | { | ||
| 360 | return comp->currentTime; | ||
| 361 | } | ||
| 362 | |||
| 363 | fmi2Real <%symbolName(modelNamePrefixStr,"getReal")%>(fmi2Component comp, int index) | ||
| 364 | { | ||
| 365 | <% match nVariablesReal(varInfo) | ||
| 366 | case 0 then | ||
| 367 | << | ||
| 368 | (void)comp; | ||
| 369 | (void)index; | ||
| 370 | return 0.0; | ||
| 371 | >> | ||
| 372 | else | ||
| 373 | << | ||
| 374 | if (index < 0 || index >= OM_NUM_REAL) { | ||
| 375 | return 0.0; | ||
| 376 | } | ||
| 377 | return comp->fmi2RealVars[index]; | ||
| 378 | >> | ||
| 379 | %> | ||
| 380 | } | ||
| 381 | >> | ||
| 382 | end modelSourceFile; | ||
| 383 | |||
| 384 | ✗ | template realVarName(SimVar var) | |
| 385 | "One entry of the name table of the real variables." | ||
| 386 | ::= | ||
| 387 | match var | ||
| 388 | case SIMVAR(__) then '"<%Util.escapeModelicaStringToCString(CodegenUtil.crefStr(name))%>",<%\n%>' | ||
| 389 | end realVarName; | ||
| 390 | |||
| 391 | ✗ | template appMainFile(SimCode simCode) | |
| 392 | "The ESP-IDF application: runs the model in a task and prints the results." | ||
| 393 | ::= | ||
| 394 | let modelNamePrefixStr = modelNamePrefix(simCode) | ||
| 395 | match simCode | ||
| 396 | case simCode as SIMCODE(modelInfo=MODELINFO(__)) then | ||
| 397 | << | ||
| 398 | /* Generated by OpenModelica from <%dotPath(modelInfo.name)%> */ | ||
| 399 | #include <stdio.h> | ||
| 400 | #include <stdint.h> | ||
| 401 | #include <unistd.h> | ||
| 402 | |||
| 403 | #include "freertos/FreeRTOS.h" | ||
| 404 | #include "freertos/task.h" | ||
| 405 | #include "esp_timer.h" | ||
| 406 | |||
| 407 | #include "<%modelNamePrefixStr%>_model.h" | ||
| 408 | |||
| 409 | /* Print one line every OM_PRINT_EVERY steps. Printing every step of a model | ||
| 410 | solved with a small step size floods the console and slows the solver down | ||
| 411 | more than the solver itself costs. */ | ||
| 412 | #ifndef OM_PRINT_EVERY | ||
| 413 | #define OM_PRINT_EVERY 1 | ||
| 414 | #endif | ||
| 415 | |||
| 416 | /* Run the simulation in real time, that is, spend OM_STEP_SIZE seconds of | ||
| 417 | wall clock time on every step. Set it to 0 to solve as fast as the chip | ||
| 418 | can, which is what you want when the results are only printed. */ | ||
| 419 | #ifndef OM_REALTIME | ||
| 420 | #define OM_REALTIME 0 | ||
| 421 | #endif | ||
| 422 | |||
| 423 | /* Solve forever instead of stopping at OM_STOP_TIME. */ | ||
| 424 | #ifndef OM_RUN_FOREVER | ||
| 425 | #define OM_RUN_FOREVER 0 | ||
| 426 | #endif | ||
| 427 | |||
| 428 | /* The stack of the task the model is solved in. Every variable of the model | ||
| 429 | is statically allocated, so this only has to hold the function calls. */ | ||
| 430 | #ifndef OM_TASK_STACK_SIZE | ||
| 431 | #define OM_TASK_STACK_SIZE 8192 | ||
| 432 | #endif | ||
| 433 | |||
| 434 | /* Hand the CPU back at least this often, in microseconds, so that the idle | ||
| 435 | task gets to run and feed the task watchdog. How many steps that is depends | ||
| 436 | on the model and on how fast the results are printed, which is why it is a | ||
| 437 | time and not a number of steps. */ | ||
| 438 | #ifndef OM_YIELD_INTERVAL_US | ||
| 439 | #define OM_YIELD_INTERVAL_US 200000 | ||
| 440 | #endif | ||
| 441 | |||
| 442 | static void om_print_header(void) | ||
| 443 | { | ||
| 444 | int i; | ||
| 445 | printf("time"); | ||
| 446 | for (i=0; i<OM_NUM_REAL; i++) { | ||
| 447 | printf(",%s", <%symbolName(modelNamePrefixStr,"realNames")%>[i]); | ||
| 448 | } | ||
| 449 | printf("\n"); | ||
| 450 | } | ||
| 451 | |||
| 452 | static void om_print_values(fmi2Component comp) | ||
| 453 | { | ||
| 454 | int i; | ||
| 455 | printf("%g", (double)<%symbolName(modelNamePrefixStr,"getTime")%>(comp)); | ||
| 456 | for (i=0; i<OM_NUM_REAL; i++) { | ||
| 457 | printf(",%g", (double)<%symbolName(modelNamePrefixStr,"getReal")%>(comp, i)); | ||
| 458 | } | ||
| 459 | printf("\n"); | ||
| 460 | } | ||
| 461 | |||
| 462 | static void om_simulation_task(void *arg) | ||
| 463 | { | ||
| 464 | fmi2Component comp; | ||
| 465 | fmi2Status status = fmi2OK; | ||
| 466 | double time = OM_START_TIME; | ||
| 467 | const double h = OM_STEP_SIZE; | ||
| 468 | uint32_t step = 0; | ||
| 469 | int64_t wallClockStart; | ||
| 470 | int64_t lastYield; | ||
| 471 | |||
| 472 | (void)arg; | ||
| 473 | |||
| 474 | comp = <%symbolName(modelNamePrefixStr,"instantiate")%>(); | ||
| 475 | if (comp == NULL) { | ||
| 476 | printf("Failed to instantiate " OM_MODEL_NAME "\n"); | ||
| 477 | vTaskDelete(NULL); | ||
| 478 | return; | ||
| 479 | } | ||
| 480 | |||
| 481 | printf("# OpenModelica " OM_MODEL_NAME " on %s\n", CONFIG_IDF_TARGET); | ||
| 482 | <%symbolName(modelNamePrefixStr,"initialize")%>(comp, OM_START_TIME); | ||
| 483 | om_print_header(); | ||
| 484 | om_print_values(comp); | ||
| 485 | |||
| 486 | wallClockStart = esp_timer_get_time(); | ||
| 487 | lastYield = wallClockStart; | ||
| 488 | |||
| 489 | while (OM_RUN_FOREVER || time < OM_STOP_TIME) { | ||
| 490 | status = <%symbolName(modelNamePrefixStr,"step")%>(comp, time, h); | ||
| 491 | if (status != fmi2OK) { | ||
| 492 | printf("# step failed at time %g\n", time); | ||
| 493 | break; | ||
| 494 | } | ||
| 495 | step++; | ||
| 496 | time = OM_START_TIME + h*(double)step; | ||
| 497 | |||
| 498 | if ((step % OM_PRINT_EVERY) == 0) { | ||
| 499 | om_print_values(comp); | ||
| 500 | } | ||
| 501 | |||
| 502 | if (OM_REALTIME) { | ||
| 503 | /* Sleep until the wall clock has caught up with the simulated time. */ | ||
| 504 | int64_t due = wallClockStart + (int64_t)((time - OM_START_TIME)*1e6); | ||
| 505 | int64_t now = esp_timer_get_time(); | ||
| 506 | if (due > now) { | ||
| 507 | usleep((useconds_t)(due - now)); | ||
| 508 | lastYield = esp_timer_get_time(); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | /* A step shorter than a tick does not sleep above, and solving as fast as | ||
| 512 | possible never does, so give the idle task a tick of its own whenever it | ||
| 513 | has been waiting for too long. */ | ||
| 514 | if (esp_timer_get_time() - lastYield >= OM_YIELD_INTERVAL_US) { | ||
| 515 | vTaskDelay(1); | ||
| 516 | lastYield = esp_timer_get_time(); | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | printf("# done at time %g after %u steps\n", time, (unsigned)step); | ||
| 521 | vTaskDelete(NULL); | ||
| 522 | } | ||
| 523 | |||
| 524 | void app_main(void) | ||
| 525 | { | ||
| 526 | xTaskCreate(om_simulation_task, "om_sim", OM_TASK_STACK_SIZE, NULL, 5, NULL); | ||
| 527 | } | ||
| 528 | >> | ||
| 529 | end appMainFile; | ||
| 530 | |||
| 531 | ✗ | template readmeFile(SimCode simCode) | |
| 532 | "How to build the generated project and flash it onto the board." | ||
| 533 | ::= | ||
| 534 | let modelNamePrefixStr = modelNamePrefix(simCode) | ||
| 535 | match simCode | ||
| 536 | case simCode as SIMCODE(modelInfo=MODELINFO(__)) then | ||
| 537 | << | ||
| 538 | # <%dotPath(modelInfo.name)%> on an ESP32 | ||
| 539 | |||
| 540 | This is an [ESP-IDF](https://docs.espressif.com/projects/esp-idf) project | ||
| 541 | generated by OpenModelica with `--simCodeTarget=ESP32`. It solves the model | ||
| 542 | with a fixed step explicit Euler solver and prints the results as CSV on the | ||
| 543 | console of the board. It needs neither the OpenModelica simulation runtime nor | ||
| 544 | the FMI headers, only ESP-IDF itself. | ||
| 545 | |||
| 546 | ## Build and flash | ||
| 547 | |||
| 548 | The default chip is the ESP32-C6, with the results printed on UART0, which is | ||
| 549 | the port the USB-serial bridge of the devkit is wired to. It is the same port | ||
| 550 | the board is flashed over: a `COMx` on Windows, a `/dev/ttyUSBx` on Linux. | ||
| 551 | Boards with a built in USB-Serial-JTAG port enumerate a second port; set | ||
| 552 | `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y` in `sdkconfig.defaults` to print the | ||
| 553 | results there instead. | ||
| 554 | |||
| 555 | ``` | ||
| 556 | . $IDF_PATH/export.sh | ||
| 557 | idf.py set-target esp32c6 | ||
| 558 | idf.py build | ||
| 559 | idf.py -p COM6 flash monitor | ||
| 560 | ``` | ||
| 561 | |||
| 562 | Use `idf.py set-target esp32` (or `esp32s3`, `esp32c3`, ...) for another chip. | ||
| 563 | |||
| 564 | ## What is generated | ||
| 565 | |||
| 566 | | File | Contents | | ||
| 567 | | ---- | -------- | | ||
| 568 | | `main/<%modelNamePrefixStr%>_model.h` | Model interface: the number of variables, the simulation settings and the functions below | | ||
| 569 | | `main/<%modelNamePrefixStr%>_model.c` | The statically allocated model state, the equations, the model functions and the solver step | | ||
| 570 | | `main/main.c` | `app_main`, which solves the model in a FreeRTOS task and prints the results | | ||
| 571 | |||
| 572 | ## Tuning the application | ||
| 573 | |||
| 574 | `main/main.c` starts with a handful of defines that you can change, or | ||
| 575 | override from `main/CMakeLists.txt` with `target_compile_definitions`: | ||
| 576 | |||
| 577 | | Define | Default | Meaning | | ||
| 578 | | ------ | ------- | ------- | | ||
| 579 | | `OM_PRINT_EVERY` | 1 | Print one line of results every N steps | | ||
| 580 | | `OM_REALTIME` | 0 | Spend `OM_STEP_SIZE` seconds of wall clock time on every step | | ||
| 581 | | `OM_RUN_FOREVER` | 0 | Keep solving instead of stopping at `OM_STOP_TIME` | | ||
| 582 | | `OM_TASK_STACK_SIZE` | 8192 | Stack of the task the model is solved in | | ||
| 583 | | `OM_YIELD_INTERVAL_US` | 200000 | Hand the CPU back at least this often, so the task watchdog stays fed | | ||
| 584 | |||
| 585 | To drive the hardware from the model, read the variables with | ||
| 586 | `<%symbolName(modelNamePrefixStr,"getReal")%>` and write them to a peripheral | ||
| 587 | instead of printing them. `<%symbolName(modelNamePrefixStr,"realNames")%>` | ||
| 588 | holds the name of every variable, in the same order. | ||
| 589 | |||
| 590 | ## Limitations | ||
| 591 | |||
| 592 | The target inherits the limitations of the experimental embedded C target it | ||
| 593 | shares its equation generation with: no algebraic loops, no events, no arrays, | ||
| 594 | no records and no strings. Models that use them fail to generate. | ||
| 595 | >> | ||
| 596 | end readmeFile; | ||
| 597 | |||
| 598 | annotation(__OpenModelica_Interface="codegen"); | ||
| 599 | end CodegenESP32; | ||
| 600 | |||
| 601 | // vim: filetype=susan sw=2 sts=2 | ||
| 602 |