2
\$\begingroup\$

I would like to know one thing about keeping the main() in embedded coding practice. I have a stand-alone system that have n number of states and it would be shifting to the respective states.

At first I did a while(1) condition in main() and checked for any of the states to occur:

main()
{
  initialize;

  while(1)
  {
    check for state n.

    if state n found

    GoTo state n
  }
}

I don't know whether I made a mistake by defining the state n inside the main itself. Or should I separate the state n definition by separating it from main() and calling the state n function from main()?

I want to practice the best coding standard. What is the best practised method in embedded systems to keep the main()?

I would like to have inputs from your part to have the best structure for defining embedded coding methods to keep main() and corresponding source files.

What will be the case when the states become elaborate and complex? How should I keep the main()? I trust coding needs to be kept tidy. I would appreciate your valuable inputs and suggestions.

| improve this question | | | | |
\$\endgroup\$
  • 2
    \$\begingroup\$ Unless the action for a state is very simple, I would put the state actions in separate functions that are called from main() as required. \$\endgroup\$ – Peter Bennett Jan 6 '14 at 5:25
  • \$\begingroup\$ If the main becomes complex, you might benefit from using an RTOS to manage separate tasks. \$\endgroup\$ – Gustavo Litovsky Jan 8 '14 at 21:09
  • 3
    \$\begingroup\$ @GustavoLitovsky You really don't need a RTOS unless the program is quite complex. I wouldn't consider a RTOS unless the program has the potential to end up >20k LOC with multiple things that need to execute simultaneously. For smaller bare bone MCU programs, you will definitely not benefit from a RTOS. \$\endgroup\$ – Lundin Jan 14 '14 at 9:40
  • 2
    \$\begingroup\$ There is great book related to schedulers and RTOS, fortunately it's being distributed for free now Patterns for Time-Triggered Embedded Systems by Michael J. Pont. There is a very nice co-operative scheduler code in that ebook, it can be ported to any MCU. \$\endgroup\$ – alexan_e Jan 14 '14 at 9:54
  • 2
    \$\begingroup\$ @Lundin: There's no hard and fast rule about when an RTOS is better. If you could show me a study showing 20k LOC is the trigger point, I'd be happy to look at it. In reality, it all depends on the resources, timing tolerance, reliability, and other issues. Adding more and more to the main loop becomes unwieldy fast and can result in timing issues you didn't expect, well below 20k LOC. For a small bare bones MCU you definitively will not use an RTOS, since bare bones already implies no RTOS :D If you mean small MCU, then yes, it depends. \$\endgroup\$ – Gustavo Litovsky Jan 14 '14 at 20:39
9
\$\begingroup\$

If you're concerned that code for a simple switch() based state machine (SM) will become untidy, you can split it into smaller subroutines.

void do_state_s1() {
    // stuff
    if ( /* certain  contidition */ ) {
        g_iState = STATE_S2;  // transition to another state
    }
}

void do_state_s2()  {
    // other stuff
    if ( /* some other condition */ ) {
        g_iState = STATE_S1;
    }
}

void do_state_machine() {
    switch (g_iState) {
    case STATE_S1:
        do_state_s1();
        break;

    case STATE_S2:
        do_state_s2();
        break;
    };
}

void main() {
    g_iState = STATE_S1;        // initialize the SM

    while (1) {
        do_state_machine();
    }
}
| improve this answer | | | | |
\$\endgroup\$
  • \$\begingroup\$ +1 I'd add a recommendation to make the state values an enumeration, and be sure to put the state variable in a global scope. \$\endgroup\$ – Joe Hass Jan 6 '14 at 12:11
  • \$\begingroup\$ I took the liberty to write an alternative version of this as another answer. @Joe Hass There is actually no need to have the state variable at file scope if you return the state from each function. (But you might of course have to do that if you want to use the returned value for something else, like an error code.) \$\endgroup\$ – Lundin Jan 14 '14 at 9:36
8
\$\begingroup\$

Based on the answer by @Nick Alexeev, I'm posting a version that is using a function pointer jump table.

In my opinion, this is the ideal way to implement a state machine for an embedded system, since it leaves main perfectly clean.

And you don't really need the switch, it will actually get optimized away by the compiler into machine code with a similar function pointer jump table.

typedef enum
{
  STATE_S1,
  STATE_S2,
  ...
  STATE_N // the number of states in this state machine
} state_t;

typedef state_t (*state_func_t)(void);


state_t do_state_s1 (void);
state_t do_state_s2 (void);

static const state_func_t STATE_MACHINE [STATE_N] =
{
  &do_state_s1,
  &do_state_s2,
  ...
};


void main()
{
  state_t state = STATE_S1;

  while (1)
  {
    state = STATE_MACHINE[state]();
  }
}

state_t do_state_s1 (void)
{
  state_t result = STATE_S1;
  // stuff
  if (...) 
    result = STATE_S2;
  return result;
}

state_t do_state_s2 (void)
{
  state_t result = STATE_S2;
  // other stuff
  if (...) 
    result = STATE_S1;
  return result;
}
| improve this answer | | | | |
\$\endgroup\$
  • 6
    \$\begingroup\$ @David That sounds like a myth or you are used at working with some obscure non-standard compiler (indeed there are quite a few of those). I've used code like this on at least 10 completely different MCU platforms and I've never had any mysterious function pointer problems. In fact, many MCUs implement the interrupt vector table as a chunk of flash at a given address, to where you download an array of function pointers. Simply use good, standard-conforming C compilers and you will have no problems. \$\endgroup\$ – Lundin Jan 14 '14 at 14:28
  • 2
    \$\begingroup\$ @David: The biggest problems I've observed with embedded-system function pointers are (1) the compiler/linker will assume that any call to a function pointer may call any function whose address is taken and whose signature matches, and (2) parameter passing when calling such functions can be astonishingly inefficient. If one avoids passing parameters when making indirect calls (on some processors, a single 8-bit parameter would be okay--otherwise use global variables instead), and makes sure that every function pointer which calls a different group of functions has a different signature... \$\endgroup\$ – supercat Jan 14 '14 at 16:55
  • 2
    \$\begingroup\$ @supercat But in a switch-statement, the compiler cannot likely assume that a certain case is never executed either, so you get no extra program size from using the function pointer version. You may however get a faster program, since there is less branching around and just one function call, instead of several as may be the case when you us a switch. Simply take pretty much any code that uses a switch statement, build with optimizations on, then look at the generated disassembly and behold: the switch-cases have likely been replaced with function calls. \$\endgroup\$ – Lundin Jan 15 '14 at 7:15
  • 2
    \$\begingroup\$ @David: Many microcontrollers can do direct memory access much faster than indirect, so compilers use a call graph to statically allocate automatic variables, sharing the space used by routines which cannot be in scope simultaneously; this will use the same total space as would a stack if every call sequence in the call graph were exercised, but it does not allow direct or indirect recursion. If a routine with a given signature which directly or indirectly calls a function pointer with that same signature, the compiler/linker will have no way of knowing that... \$\endgroup\$ – supercat Jan 15 '14 at 15:09
  • 2
    \$\begingroup\$ ...there is no actual execution path which would cause that routine to be invoked recursively, and so it will disallow the call. BTW, if the linker supported it, the PIC 18F could work with single-byte function pointers more efficiently than two-byte or three-byte ones. Simply build a jump table for each signature (I doubt any programs would have more than 42 routines for any particular signature). The jump table would consist of "ADDWF PC" followed by a sequence "MOVF temp,W / GOTO xxxx" for each routine [omit the MOVF for routines that don't take a parameter in W]. \$\endgroup\$ – supercat Jan 15 '14 at 15:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.