This is an old revision of the document!
Generating C code
Sinelabore creates compact and clearly readable C code from UML state charts. There are a number of different ways in which the generated code can be fine-tuned through the setting of configuration parameters. This allows generation to follow known patterns, which are illustrated with examples below.
- Object pattern: Instance related data is always grouped in a struct. All state machine related functions operate on this data struct. The advantage is that the data flow is easy to follow, the code is re-entrant because there is no global manipulated state, and multiple instances are easy to realise.
- Opaque object pattern: This is similar to the object pattern, but hides the implementation from the outside of the generating state machine code. This further reduces dependencies between the state machine implementation and other code. See also https://en.wikipedia.org/wiki/Opaque_pointer
- Single instance pattern: Typically there is only one instance and no instance pointer is needed. For example, implementing an IRQ handler as a state machine, or using a hardware module in the state machine that exists only once.
In the generated code, state hierarchies are mapped to nested switch/case code. Event handling code is mapped to if/else-if/else structures. The various configuration parameters allow you to generate code that is optimal for your system. See section 2.2 of the PDF manual for a list of all available options. To generate C code call the code generator with the command line flag ’-l cx’.
To generate a configuration file with all parameters related to the C code generation call the code generator as follows once:
java −cp path_to_bin_folder/∗ codegen.Main −l cx −gencfg > codegen.cfg
There are various configuration options to adjust the generated code:
- usage as interrupt handler without any parameters (e.g.
#pragma vector=UART0TX_VECTOR __interrupt void irq_handler(void)
); - having only the event as parameter of the handler
- having an instance pointer and event as parameter (for running multiple instances of the same machine e.g.
void testcase(TESTCASE_INSTANCEDATA_T *instanceVar, TESTCASE_EVENT_T msg )
) - having an own defined parameter containing the event which allow to easily send data with the event (e.g.
void testcase(TESTCASE_INSTANCEDATA_T *instanceVar, USER_EVENT_T *userData)
- Support for externally defined events (i.e. outside the UML diagram) to better support reuse of legacy code
- Support for conditional events (i.e. boolean expressions)
- the C backend has features for high-availability applications where it is desirable to detect serious errors happening outside the state machine code but effecting the correct execution of the state machine. Such errors might come from a runaway pointer overwriting key variables, power brownout corrupting a bit or a bad ram cell losing a bit to name a few. To detect and handle such situations is an overall system design task. But the code generator can help you to detect inconsistencies of the state machine offering different mechanisms described in the manual.
The code generator supports code generation from models with regions. A region is an orthogonal part of a state. Regions allow to express parallelism within a state. A state can have two or more regions. Region contains states and transitions.
An example state diagram with regions is shown below. Imagine how much effort it would be to manually write the code for this machine. And even worse how difficult it would be to maintain it over time when new states/events are added.
All this allows to generate code that is optimal for your system. See the manual for a list of all available options. To generate C code call the code generator with the command line flag -l cx
.
This results in the following files:
- C-file with the state machine implementation
- H-file of the state machine
- H-file with the events that can be sent to the machine
- A file with some code that supports debugging
Example code generated from the oven example in the handbook:
#include "oven_ext.h" #include "oven_hlp.h" #include "oven.h" #include <stdio.h> extern unsigned char msg; extern T_PWR pwr; void oven(OVEN_INSTANCEDATA_T *instanceVar){ OVEN_EV_CONSUMED_FLAG_T evConsumed = 0U; /* Create a copy of the instance data. Changes during the machine execution are done on the copy while tests (e.g. isIn) must use the unmodified instance data */ OVEN_INSTANCEDATA_T instanceVarCopy = *instanceVar; switch (instanceVar->stateVar) { case Active: /* calling region code */ evConsumed |= ovenLight(instanceVar, &instanceVarCopy, msg); evConsumed |= ovenPower(instanceVar, &instanceVarCopy, msg); evConsumed |= ovenRadioator(instanceVar, &instanceVarCopy, msg); /* Check if event was already processed */ if(evConsumed==0U){ if(msg==(OVEN_EVENT_T)evDec){ /* Transition from Active to Active */ /* Exit code for regions in state Active */ /* Action code for transition */ timer_dec(); /* Entry code for regions in state Active */ /* entry chain */ /* entry chain */ /* entry chain */ /* adjust state variables */ (&instanceVarCopy)->stateVar = Active; }else if(msg==(OVEN_EVENT_T)evInc){ /* Transition from Active to Active */ /* Exit code for regions in state Active */ /* Action code for transition */ timer_inc(); /* Entry code for regions in state Active */ /* entry chain */ /* entry chain */ /* entry chain */ /* adjust state variables */ (&instanceVarCopy)->stateVar = Active; }else{ /* Intentionally left blank */ } /*end of event selection */ } break; /* end of case Active */ case Inactive: if(1){ /* Transition from Inactive to Active */ /* Entry code for regions in state Active */ /* entry chain */ /* entry chain */ /* entry chain */ /* adjust state variables */ (&instanceVarCopy)->stateVar = Active; }else{ /* Intentionally left blank */ } /*end of event selection */ break; /* end of case Inactive */ default: /* Intentionally left blank */ break; } /* end switch stateVar_root */ /* Save the modified instance data */ *instanceVar = instanceVarCopy; }
Beside the state machine a UML class can also contain additional attributes and operations. Attributes are mapped as member variables of the state machine instance data. Operations are mapped to C-functions. The following example shows a class with an attribute and operation. Operations can be used beneficially to model entry/exit code or other helper functions. Attributes might represent internal variables of the stateful class.
For more details read section “Generating C Code” of the manual.