Sometimes you want to run the same state machine multiple times. E.g. processing three serial interfaces with the same state machine. In object orient languages this is easy as the concept of objects is the basis of these languages. You would simply instantiate the class three times.
Luckily in C there is also a well known concept to do this. Instead of defining objects you define a (instance) structure that contains all local data a function needs. When calling that function a pointer to the instance data structure is handed over. In our case the function is the state machine handler. The code generator automatically creates the instance data structure automatically for you.
In this example we will show you how to “create multiple instances” of the state machine. And then we show you how to create an own structure where additional private data for the state handler can be defined.
Let's start …
Follow these simple steps to prepare the generated code for multiple instances. First set some parameters in the configuration file:
HsmFunctionWithInstanceParameters
to YES
HsmFunctionWithEventParameter
to YES
As a result the generated state machine handler signature looks like this:
void testcase(TESTCASE_INSTANCEDATA_T* userInstanceVar, TESTCASE_EVENT_T msg)
You can now simply declare multiple variables of type TESTCASE_INSTANCEDATA_T which contains the data per instance. When calling the state machine just point to one of these variables.
Sometimes it is necessary that a state machine knows on which object it currently works on. Therefore a predefined member called inst_id
is generated by default. You can set it to any value that makes sense in your context (e.g. provide the COM port number the machine currently processes).
TESTCASE_INSTANCEDATA_T instDataA = TESTCASE_INSTANCEDATA_INIT; TESTCASE_INSTANCEDATA_T instDataB = TESTCASE_INSTANCEDATA_INIT; TESTCASE_INSTANCEDATA_T instDataC = TESTCASE_INSTANCEDATA_INIT; // Set object ID if the machine needs to know which object it is // E.g. which serial port to open ... instDataA.inst_id=0; instDataB.inst_id=1; instDataC.inst_id=2; // call "object A" testcase(&instDataA, msg); // call "object B" testcase(&instDataB, msg); // call "object C" testcase(&instDataC, msg);
I think you agree that there is no magic in running multiple instances of the state handler.
Sometimes it makes sense to provide an own instance structure. In our example we want to store the baud rate, parity and other values relevant for each serial port.
So first define an own struct in a file called own_inst_type.h
(as an example). The example below shows how to define local variables like the noRxBytes
(number of already defined bytes) as well as parameters like the parity etc.
#ifndef __OWN_INST_TYPE__ #define __OWN_INST_TYPE__ typedef struct InstanceData MY_TESTCASE_INSTANCEDATA_T; #include <stdint.h> #include <testcase_ext.h> #include "testcase.h" struct InstanceData{ uint16_t baudrate; uint8_t noBits; uint8_t parity; uint8_t stopBit; TESTCASE_INSTANCEDATA_T instanceVar; }; #endif // __OWN_INST_TYPE__
Then tell the code generator to use this struct. Add the third line to the configuration file which then looks like this:
HsmFunctionWithInstanceParameters=YES HsmFunctionWithEventParameter=YES HsmFunctionUserDefinedParameter=MY_TESTCASE_INSTANCEDATA_T
The generated state machine signature now looks as follows. The difference is that the own instance structure is now used instead of the generic one.
void testcase(MY_TESTCASE_INSTANCEDATA_T* userInstanceVar, TESTCASE_EVENT_T msg)
To use the newly defined instance type declare the instance variables now as follows:
// init three different serial ports MY_TESTCASE_INSTANCEDATA_T instDataA = {0,9600,8,'N',1,TESTCASE_INSTANCEDATA_INIT}; MY_TESTCASE_INSTANCEDATA_T instDataB = {0,19200,8,'E',1,TESTCASE_INSTANCEDATA_INIT}; MY_TESTCASE_INSTANCEDATA_T instDataC = {0,9600,8,'N',1,TESTCASE_INSTANCEDATA_INIT}; // Set object ID if the machine needs to know which object it is // E.g. which serial port to open ... instDataA.instanceVar.inst_id=0; instDataB.instanceVar.inst_id=1; instDataC.instanceVar.inst_id=2; // call "object A" testcase(&instDataA, msg);
Now the state machine is ready to use the new private instance data inside the machine code.
Coming back now to the example of three serial interfaces. The following figure shows a simplified state machine for handling a serial interface. The state machine is generic. Whether COM0 or COM1 or COM2 is processed is determined only from the content of the instance variable. And also other parameters are stored there and of course the local machine variables.
I hope that the concepts how to run multiple state machines of the same type are clearer now. And also how to define an own instance structure that is necessary for more complex machines.
In case of any questions don't hesitate to contact me!