Application-provided data structures

Event Definition Table

This is defined as follows:

struct execEventDef
    byte DefaultPriority;
    byte LocalEventNumber;
    word or byte StateMachineNumber;

struct execEventDef execEvent[TOTAL_EVENTS];

Each event is given a unique number in the system which is either a byte or a word, depending on the number of events in the system (the event queues can be defined to be bytes or words, depending on build options). This number is an index into this table.

The default priority refers to the queue which the event is normally posted to, unless specified otherwise.

The state machine number is an index into the state machine definition table. Each event belongs to (only) one state machine and is effectively an input to that state machine.

The local event number is an index into the state transition table for that state machine, and defines the event number within the state machine to which it belongs.

Event number 0 is a dummy event, which is not included in the table. It is never used as such, but is used to indicate an event which has been cancelled (and hence will not be processed). Therefore, the lowest event in the table is 1 (not 0).

Event Queue Definition Table

This is defined as follows:

struct execQueueDef
    word or byte NumberOfElements;
    word or byte *QueueStart;

struct execQueueDef execQueue[TOTAL_QUEUES];

The array execQueue is user-supplied, as are the queues themselves.

The event queues are made up of a single byte or word array for all the queues (i.e. the queues are contiguous). The size of this array, execEventQueue, is the total of all the elements in all the queues.

It is important that the queue start pointers are set up correctly so that one queue is adjacent to the next, with the highest priority queue at the lowest address (i.e. effectively in reverse order).

The number of queues in the system is given by the constant TOTAL_QUEUES.

Input Event Definition Table

An input event definition table is defined as follows:

struct execInputEvents
    word or byte HighEvent[8];
    word or byte LowEvent [8];
    byte BitMask;

One of these exists for each byte of input that is required to be processed using the input event posting facility, and the names of these are user-defined. There need be none at all if it is not required to generate events from inputs in this way.

If there are bits in an input byte that need to be processed in a special manner, e.g. at different scan rates, then more than one of these tables can exist for a given input byte. The bit mask determines which bits are relevant.

The high event defines the event number to be posted on a low to high transition, and the low event defines the event number to be posted on a high to low transition.

The first elements of the High/LowEvent array (i.e. HighEvent[0], LowEvent[0]) correspond to the least significant bit of the BitMask.

An event number of 0 indicates that no event is to be posted. This is useful if it is only required to post an event for the transition in one direction.

State Machine Definition Table

This is defined as follows:

struct execStateMachineDef
    byte NumberOfStates;
    byte NumberOfEvents;
    struct execStateTransitionDef *TransitionTable;

struct execStateMachineDef execStateMachine[STATE_MACHINES];

The array execStateMachine is user-supplied, as are the state transition tables themselves (see section 2.5.5 for the definition of TransitionTable, which also defines the number of states and events).

The number of state machines in the system is given by the constant STATE_MACHINES.

State Transition Table

Each element in a state transition table is defined as follows:

struct execStateTransitionDef
    byte NextState;
    byte Tag;
    void (*EventFunction)(word Tag);

A state transition table is a two-dimensional array of state transition elements as follows:

execStateTransitionElement MyStateTable[STATES][EVENTS];

Therefore, in processing an event, the executive first looks up in the event definition table to find out which state machine that event belongs to, and converts the global event number into a local event. It takes the current state of the appropriate state machine and does a lookup in the state transition table, based on event and the current state, to find out the next state and event function. It calls the event function and, when it returns, it sets the current state of that state machine to the new state as looked up, or if the state transition has been overridden by processing in the event function, to the overridden state.

The NextState data item is a number based on 1 rather than 0 (i.e. the first state in the table is state 1 not state 0). A zero state value is used to represent a disabled state machine. When a state machine is enabled, it is set to a known starting state, and a state machine may be disabled at any time.

The event function pointer may be null, in which case the state transition is made, but no function is called.

The purpose of Tag is to identify the state transition if the same function is called for different transitions. This is placed in the global variable execTransitionTag prior to calling the event function.

The state transition tables are user-provided and may be given any names that may be desired. MyStateTable is an example in this case.

A valid entry must exist for each event in each state. It is recommended that for ‘invalid’ state-event combinations, that an exception notification function is called, and a transition is defined to a state that would be most appropriate to cause recovery from the situation.

Structure Definition Array

Each element in a structure definition array is defined as follows:

struct execStructDef
    word Size;
    word Elements;
    word Offset;

This structure is used to define the structure of a structure, or simple array or data item, which is to be passed (in a portable way) over a communications link. The pack/unpack facility uses this information (contained in ROM) to convert the given structure into and back out of byte-packed big-endian format.

A particular definition either consists of a single instance of this structure if it describes a simple array or data item, or an array of these structures if a structure or an array of structures is to be described.

A series of macros are provided in order to simplify the construction of descriptor arrays. These are:

  • PACK_ELEMENT (structure, type, member, number)
  • PACK_STRUCT (structure, type, member, number)
  • PACK_SINGLE (type, number)
  • PACK_STRUCT_START (structure, number)

The use of these macros is best described by example. To describe a simple array, e.g. word myArray[5];, use the following: struct execStructDef myArrayDescriptor = PACK_SINGLE (word, 5);

When describing structures, any level of nested structure arrays may be used. For the following:

struct myStruct1
    byte m_S1E1[2];
    word m_S1E2;
struct myStruct2
    float m_S2E1;
    struct myStruct1 m_S2E2[3];
    word m_S2E3;
struct myStruct3
    byte m_S3E1[10];
    struct myStruct1 m_S3E2;
    slword m_S3E3;
    struct myStruct2 m_S3E4[4];

… the third compound structure is described as follows:

struct execStructDef myStruct3Descriptor[16] =
  PACK_STRUCT_START (struct myStruct3, 1),
    PACK_ELEMENT (struct myStruct3, byte, m_S3E1, 10),
    PACK_STRUCT (struct myStruct3, struct myStruct1, m_S3E2, 1),
      PACK_ELEMENT (struct myStruct1, byte, m_S1E1, 2),
      PACK_ELEMENT (struct myStruct1, word, m_S1E2, 1),
    PACK_ELEMENT (struct myStruct3, slword, m_S3E3, 1),
    PACK_STRUCT (struct myStruct3, struct myStruct2, m_S3E4, 4),
      PACK_ELEMENT (struct myStruct2, float, m_S2E1, 1),
      PACK_STRUCT (struct myStruct2, struct myStruct1, m_S3E2, 3),
        PACK_ELEMENT (struct myStruct1, byte, m_S1E1, 2),
        PACK_ELEMENT (struct myStruct1, word, m_S1E2, 1),
      PACK_ELEMENT (struct myStruct2, word, m_S2E3, 1),

Note that the pack and unpack routines use recursion to handle nested structure definitions. On microcontrollers where stack space is very short, then the use of complex nested structures should be avoided, unless the microcontroller is an 8-bit big-endian device (such as the 8051), in which case packing/unpacking is not necessary on the target processor.

Incoming Asynchronous Data Stream State

This block of data is defined as follows:

struct execAsyncStateDef
    word Checksum;
    enum {OK, Repeat, Overflow, Error, Hunting,
          InitialEscape, Node, Sequence, Receiving,
          EscapeSequence, Checksum1, Checksum2} DecodeState;
    byte ThisNode;
    byte MessageNode;
    byte DataSize;
    byte ThisSequence;
    byte LastSequence;

One of these blocks is required for each incoming asynchronous data stream. This data is maintained by the function execAsyncByteIn(), with the exception of ThisNode which is set up by the application. This function alters the data in this structure so that, next time it is called, it is operating on the same data as previous.

The Checksum is a CRC-16 checksum and represents the checksum of the accumulated received data.

The DecodeState is used to track the progress of the incoming data. When reception of a message is completed or aborted, the function execAsyncByteIn() returns true. When this happens and DecodeState is OK, then the message should be processed and replied to (on a slave). If DecodeState is Repeat then the previous reply should be transmitted, unless the message is received while transmitting the previous reply, in which case it is advisable not to reply, so as to allow the master to resynchronise.

DecodeState of Overflow or Error indicates a corrupted incoming message which should not be replied to (since it may be the node number which is corrupted – in which case a reply would be expected from another node). Under these circumstances, the master will almost certainly be expecting a reply and will not get one. However, this information may be useful in gathering statistics about line quality. Note that an overflow occurs if more than 255 bytes of message data are received (not including repeated DLE’s), and this may indicate a software problem at the other end of the link.

The DecodeState on the master is used similarly to the slave, except that the state is used to determine whether to proceed with a new exchange or to do a retry on the old.

ThisNode is used to filter messages so that only messages for a particular node are received. If the node number does not match then the whole message will be ignored and the application will not be informed. ThisNode should be set to the node number of the controller in question, or 255 if all messages are to be received. An incoming node number of 255 is treated as broadcast, and the incoming message will be registered regardless of the value of ThisNode. A broadcast should not be replied to.

The MessageNode is the node number embedded in the incoming message. Normally this will be the same as ThisNode when a message has been received. This may not be the case if either the incoming message node or ThisNode is set to 255.

The DataSize is the size of the data (in bytes) as it is placed in the application-provided buffer (i.e. with leading and trailing data and double DLE’s removed). It increments as the data is being received.

ThisSequence and LastSequence are used in determining whether the message is a repeat or not. Note that after a correctly received message, these will always be the same.

Other Values

The following values, defined in the application header file “ExecApp.h”, are required to be provided for the executive to operate correctly:


The file “ExecApp.h” is included by “Exec.h” and the values defined are used by the executive when it is compiled. This means that the executive must be freshly compiled with each change to this file, and cannot be distributed as a pre-compiled library.

By convention, “ExecApp.h” contains all the definitions required globally by the application as well as the executive (see the example in section 4.2).

Alternatively, the application header file may be given a different name, such as “MyApp.h”, in which case the constant APP_HEADER must be defined as “MyApp.h” in the compile options, typically appearing as APP_HEADER=”\”MyApp.h\””.

Generating Definitions Automatically

The various data structures and definitions contain numerous cross-dependencies which, if broken, will cause an application or, worse, part of it, to malfunction. There is therefore a script-based facility to automatically insert the relevant code into the appropriate source code files.

This facility uses the ‘Python’ scripting language. Python is free software and can be downloaded from the Internet at It comes complete with Python interpreter (for Windows, most flavours of Unix and Macintosh), library and comprehensive documentation. It is very easy for ‘C’ programmers to learn, and the facility supplied with the State-Event Executive can easily be expanded to generate other parts of application code (e.g. to create string tables to give event trace data meaning).

The automatically-generated parts of the code are contained in special comment-delimited sections. The pattern of the delimiting is as follows: the start of a section begins with //{{XXX(Y), where XXX(Y) is a pseudo-macro distinguishing one section from another. The section ends with //}}. The script generates these sections internally and then searches all the specified source files, and matches these sections in the existing files against the sections it has generated, substituting in any changes. Thus, these sections must already pre-exist in the source files before the tool is run (though they can be empty, ready for the tool to insert the real code). Note that the tool only modifies the files with changes, and warns of any sections that have not been found or appear in more than one place.

The facility is driven from a main script which is defined by the application. This scripts defines all the necessary table data and imports/calls functions in ‘’ which actually manipulates the data, stuffing the generated code into the source files. Running the application-defined script with Python from the command line results in the source files being updated, and a log being output to the screen indicating what has been done. The easiest way to understand how the application defined script is constructed is to examine and then modify the example script (see section 4.1). Note that any errors will manifest themselves as Python exceptions, which will abort the script.

The following is a complete list of the pseudo-macros used by

  • Used by the function genStateTables():
    • EXEC_DEFINE() – this contains all the manifest constants and declarations in the main application header file.
    • EXEC_DEFINITION() – this contains the overall definition of the set of state machines
    • EXEC_DECLARE() – this contains the declarations of all the state machines (for the appropriate header file).
    • EXEC_SM_DECLARE(sm), where sm is the manifest constant for a particular state machine – this contains the state declarations for a given state machine.
    • EXEC_SM_DEFINE(sm), where sm is the manifest constant for a particular state machine – this contains the state definition table for a given state machine.
  • Used by the function genTimers():
    • EXEC_TIMERS() – this contains the manifest constants of the timers.
  • Used by the function genCommsStructures():
    • EXEC_COMMS_STRUCT(st), where st is the structure tag of the structure being defined – this contains the structure declaration for a given structure.
    • EXEC_COMMS_DESCRIPTOR(st), where st is the structure tag of the structure being defined – this contains the communications packing descriptor for the given structure.

The function genFiles() within takes a list o fall these sections and does the pattern matching and file stuffing.

The function genStabReport() within generates an HTML file with the state tables in a more readable tabular form.