User Tools

Site Tools


coding_strategy_standard_for_state_machines

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

coding_strategy_standard_for_state_machines [2019/04/08 20:11] (current)
Line 1: Line 1:
 +===== How to Write Good Code for State Machines ======
  
 +The Zyante text does a reasonable job explaining how to implement control as software-based state machines. However, it provides little guidance regarding a good implementation strategy. I tried several different strategies over the summer and have found one that works well. We will adopt it as part of the coding standard for state machines for this course. ​
 +
 +==== In the Tick Function: State Update then State Action ====
 +
 +In the text, the authors implement the ''​tick()''​ function with two switch statements. We will still follow this approach. ​ The first switch statement will perform the state-update,​ and the second switch statement will perform the state-action.
 +
 +==== State Machine Initialization Function ====
 +
 +All state machines should have an initialization function that will initialize the current state as well as initialize any state machine variables. ​ This initialization function should be called by ''​main''​ before entering the loop that is responsible for calling the ''​tick''​ function.
 +
 +Why do this? 
 +  * It ensures all variables in the state machine are properly initialized (including the starting state).
 +  * It provides a single function that can be called to reset the state machine.
 +  * You cannot perform initialization in the first state of your state machine because the tick function first performs the state transition, and then the state action, so assuming you immediately transition out of your first state, **any actions you place in your initial state will be skipped and never executed**.
 +
 +
 +
 +==== File Organization ====
 +Your state-machine will be completely contained in a single file. The name of this file will be given in the description for each of the labs. This single file will contain everything that the state-machine needs to implement control. Other non-control functions such as updating the display and so forth will be located in a different file. You will also have a single "​.h"​ file to advertise the init and tick functions. The name that you must use with this "​.h"​ will be given to you in the lab description.
 +
 +==== All Variables Used By the tick() Function Have File or function Scope ====
 +Any variable defined outside of a function needs to be defined using the ''​static''​ keyword. This ensures that the variable is local to a single file and avoids multiple-definition errors from the compiler/​linker. If the variables are defined inside the tick function, they have local scope but you probably still need to use the ''​static''​ keyword because you will often want the value preserved between "​tick"​ invocations,​ (e.g., a variable that you increment to implement a timer).
 +
 +
 +==== Example ====
 +I will provide a snippet from my implementation of the clock lab so that you can see what I mean.
 +
 +<​code>​
 +// States for the controller state machine.
 +enum clockControl_st_t {
 + init_st, ​                // Start here, transition out of this state on the first tick.
 + never_touched_st, ​       // Wait here until the first touch - clock is disabled until set.
 + waiting_for_touch_st, ​   // waiting for touch, clock is enabled and running.
 + ad_timer_running_st, ​    // waiting for the touch-controller ADC to settle.
 + auto_timer_running_st, ​  // waiting for the auto-update delay to expire ​
 +                                 // (user is holding down button for auto-inc/​dec)
 + rate_timer_running_st, ​  // waiting for the rate-timer to expire to know when to perform the auto inc/dec.
 + rate_timer_expired_st, ​  // when the rate-timer expires, perform the inc/dec function.
 + add_second_to_clock_st ​  // add a second to the clock time and reset the ms counter.
 +} currentState;​
 +
 +void clockControl_init() {
 +  currentState = init_st;
 +}
 +
 +void clockControl_tick() {  ​
 +  // Perform state update first.
 +  switch(currentState) {
 +    case init_st:
 +      break;
 +    case never_touched_st:​
 +      break;
 +    case waiting_for_touch_st:​
 +      break;
 +    case ad_timer_running_st:​
 +      break;
 +    case auto_timer_running_st:​
 +      break;
 +    case rate_timer_running_st:​
 +      break;
 +    case rate_timer_expired_st:​
 +      break;
 +    case add_second_to_clock_st:​
 +      break;
 +    default:
 +      printf("​clockControl_tick state update: hit default\n\r"​);​
 +      break;
 +  }
 +  ​
 +  // Perform state action next.
 +  switch(currentState) {
 +    case init_st:
 +      break;
 +    case never_touched_st:​
 +      break;
 +    case waiting_for_touch_st:​
 +      break;
 +    case ad_timer_running_st:​
 +      break;
 +    case auto_timer_running_st:​
 +      break;
 +    case rate_timer_running_st:​
 +      break;
 +    case rate_timer_expired_st:​
 +      break;
 +     ​default:​
 +      printf("​clockControl_tick state action: hit default\n\r"​);​
 +      break;
 +  }  ​
 +}
 +</​code>​
 +
 +==== Requirements ====
 +Please follow the template shown above.
 +  - State-update before state-action.
 +  - You must declare a ''​currentState''​ variable as part of the enum state declaration. Use ''​currentState''​ as shown above.
 +  - Have an init function that initializes ''​currentState''​ and any other state variables.
 +  - The state names used in the ''​enum''​ must be the same names as those used in your state diagrams.
 +  - Each state machine will be placed in its own file.
 +  - Follow the ''​enum''​ approach shown above. ​
 +===== Debugging Software State Machines =====
 +Debugging software-based state machines, especially those that are time based (referred to as synchronous in the text) can be difficult using a conventional debugger because stopping the program interferes with execution and can make it difficult to find the bug. Often it is helpful to have your state machines print out messages as they change state so you can watch state transitions occur in real-time.
 +
 +The snippet of code shown below would be placed in the same file as the clock state-machine shown above. Call this function at the top of the tick function. The function shown below will print out state changes as they occur. What makes this code particularly helpful is that it only prints out a message when currentState changes value. This eliminates a lot of extraneous printing that often hides what you are trying to see. It keeps track of the previous value of currentState by using a static variable. Try using this function, I suspect that you will find it to be quite useful.
 +
 +Place this function before the tick() function in the file to keep the compiler happy. Also make sure to ''​include <​stdio.h>''​.
 +<​code>​
 +// This is a debug state print routine. It will print the names of the states each
 +// time tick() is called. It only prints states if they are different than the
 +// previous state.
 +void debugStatePrint() {
 +  static clockControl_st_t previousState;​
 +  static bool firstPass = true;
 +  // Only print the message if:
 +  // 1. This the first pass and the value for previousState is unknown.
 +  // 2. previousState != currentState - this prevents reprinting the same state name over and over.
 +  if (previousState != currentState || firstPass) {
 +    firstPass = false; ​               // previousState will be defined, firstPass is false.
 +    previousState = currentState; ​    // keep track of the last state that you were in.
 +    printf("​msCounter:​%d\n\r",​ msCounter);
 +    switch(currentState) {            // This prints messages based upon the state that you were in.
 +      case init_st:
 +        printf("​init_st\n\r"​);​
 +        break;
 +      case never_touched_st:​
 +        printf("​never_touched_st\n\r"​);​
 +        break;
 +      case waiting_for_touch_st:​
 +        printf("​waiting_for_touch_st\n\r"​);​
 +        break;
 +      case ad_timer_running_st:​
 +        printf("​ad_timer_running_st\n\r"​);​
 +        break;
 +      case auto_timer_running_st:​
 +        printf("​auto_timer_running_st\n\r"​);​
 +        break;
 +      case rate_timer_running_st:​
 +        printf("​rate_timer_running_st\n\r"​);​
 +        break;
 +      case rate_timer_expired_st:​
 +        printf("​rate_timer_expired_st\n\r"​);​
 +        break;
 +     }
 +  }
 +}
 +</​code>​
coding_strategy_standard_for_state_machines.txt ยท Last modified: 2019/04/08 20:11 (external edit)