Software Professional

A weblog by Jack Goossen, a dutch software architect, about embedded software

A different approach to state machines - part 3

Last time we applied some code size optimization to our state machine implementation. In this part we improve the maintainability (and arguably readability) of the code. The code size won in the previous post came at the expense of some extra lines of source code in the form of the guard and effect handlers. Luckily, there exists a little-known pre-processor trick that can help us here: 'X Macros'. In essence an X Macro is a function-like macro with another function-like macro as argument. The replacement token list is a list of 'calls' to the function-like macro with one or more 'regular' parameters. Are you still with me? Ok, let's see an example:

1#define EFFECTS( FUN ) \ 2 FUN( SetDefaultOnLevel ) \ 3 FUN( TurnOff ) \ 4 FUN( DimUp ) \ 5 FUN( DimDown ) \ 6 FUN( StopDimming ) 7 8// add 'e' prefix to the effect name 9#define ENUM_ITEM( item ) e##item, 10 11// create a function prototype from the effect name with prefix 'handle' 12#define PROTOTYPE_ITEM( item ) static void handle##item(void); 13 14// create a case statement for effect in which the corresponding function 15// is called 16#define CASE_ITEM( item ) case e##item : handle##item(); break; 17 18// effects enumeration 19enum { 20 EFFECTS( ENUM_ITEM ) 21}; 22 23// effects function prototypes 24EFFECTS( PROTOTYPE_ITEM ) 25 26// effect handler implementation 27static void effectHandler(uint8_t effect) { 28 switch( effect ) { 29 EFFECTS( CASE_ITEM ) 30 default : 31 break; 32 } 33}

Which expands to (layout adjusted for clarity):

1// effects enumeration 2enum { 3 eSetDefaultOnLevel, 4 eTurnOff , 5 eDimUp , 6 eDimDown , 7 eStopDimming , // I love how C99 allows this! 8}; 9 10// effects function prototypes 11static void handleSetDefaultOnLevel(void); 12static void handleTurnOff(void); 13static void handleDimUp(void); 14static void handleDimDown(void); 15static void handleStopDimming(void); 16 17// effect handler implementation 18static void effectHandler(uint8_t effect) { 19 switch( effect ) { 20 case eSetDefaultOnLevel : handleSetDefaultOnLevel(); break; 21 case eTurnOff : handleTurnOff() ; break; 22 case eDimUp : handleDimUp() ; break; 23 case eDimDown : handleDimDown() ; break; 24 case eStopDimming : handleStopDimming() ; break; 25 default : 26 break; 27 } 28}

The ENUM_ITEM, PROTOTYPE_ITEM and CASE_ITEM macros can be factored out in a separate file, which can be reused for multiple state machines. Note how we only need to add one line to the effects macro to add an effect, versus adding an enumeration, a function prototype and a case statement! The lines of code could be further reduced by factoring out the standard implementation of the effectHandler function, but I prefer to leave some clues about what is going on with all these macros. I feel their usage in this specific case pays off. Just make sure you do not go overboard with nasty pre-processor tricks; they can be notoriously hard to read and debug. The complete sources can be downloaded from statemachine_part3.zip