User Tools

Site Tools


state_machine_interlocks_and_global_variables

Differences

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

Link to this comparison view

state_machine_interlocks_and_global_variables [2019/04/08 20:11] (current)
Line 1: Line 1:
 +===== Interlocked State Machines and Shared Variables =====
  
 +This page provides an example of a simple interlocked state machine. It will also discuss best practices for sharing variables between state machines. The tutorial has been completed implemented. You can paste the source-code shown below into actual files in the Xilinx SDK and run the tutorial, if you like. There is a [[https://​www.youtube.com/​watch?​v=R02LG_sSjFk|video]] of this demonstration project on the [[https://​www.youtube.com/​channel/​UC2DAqectVGE6DIT-b8N2dJg|ECEN 330 youtube channel]].
 +
 +The example depicted below consists of two state machines: flashControl and flashLed. The flashControl state machine reads the slide switches and sets a global variable accordingly. Each slide switch (SW0 - SW3) is associated with a delay value. SW0 is associated with a shorter value and the delay values increase with switch number with SW3 being associated with the longest delay. The shortest delay is selected with none of the slide switches is slid up. Next, flashControl enables the flashLed state machine. It then waits until the flashLed state machine indicates that it has completed its task. The state diagram for flashControl is shown directly below.
 +
 +{{::​flashcontrolstatediagram.jpg?​800|}}
 +
 +The flashLed state machine flashes a simple "​shifting"​ pattern on the LEDs. The pattern consists of a single '​1'​ that starts out in the left-most LED (LD3). After the delay selected by flashControl expires, the '​1'​ moves to the right, one LED position to LD2, and so forth. This "​shifting"​ process occurs until the '​1'​ shifts out of the pattern and all of the LEDs are dark. The flashLed indicates the completion of its task and then waits until the flashControl state machine disables it, whereupon flashLed returns to its init state to initialize variables, and then on the next tick, going to the state where it waits to be enabled. The state diagram for flashLed is shown directly below.
 +
 +{{::​flashledstatediagram.jpg?​800|}}
 +
 +==== Handling Shared Variables ====
 +
 +The text allows direct access to shared variables but this is not considered good practice. In general, it is best to use functions to access shared variables. Functional access to global variables allows you to:
 +  - more carefully control how shared variables are accessed.
 +  - breakpoint on functions in the debugger and hence determine exactly who is changing/​reading a shared variable.
 +  - easily modify the underlying data-structure without affecting the code that accesses that data-structure.
 +
 +The general strategy for sharing variables with functions is:
 +  - create globals.c/​globals.h files.
 +  - advertise the set/get functions in the globals.h file.
 +  - define the shared variable as ''​static''​ in the globals.c file.
 +  - implement the set/get functions in the globals.c file.
 +
 +Allowing access to shared variables is accomplished by including globals.h in the associated .c file. The globals.c and globals.h file are shown below. A single global delay-termination variable is handled by two functions. One function "​sets"​ the global variable while another functions "​gets"​ the value of the global variable. ​
 +
 +----
 +
 +globals.h
 +<code C>
 +#ifndef GLOBALS_H_
 +#define GLOBALS_H_
 +
 +#include <​stdint.h>​
 +
 +uint16_t globals_getDelayTerminationValue(); ​           // Used to retrieve the global delay value.
 +
 +void globals_setDelayTerminationValue(uint16_t value); // Used to set the the global delay value to some value.
 +
 +#endif /* GLOBALS_H_ */
 +</​code>​
 +
 +----
 +
 +globals.c
 +<code C>
 +#include <​stdint.h>​
 +
 +// The global delay termination value is stored here.
 +// The static keyword ensures that no other file can access this variable.
 +static uint16_t global_delayValue;​
 +
 +// Used to retrieve the global delay value.
 +uint16_t globals_getDelayTerminationValue() {
 +  return global_delayValue;​
 +}
 +
 +// Used to set the the global delay value to some value.
 +void globals_setDelayTerminationValue(uint16_t value) {
 +  global_delayValue = value;
 +}
 +</​code>​
 +
 +==== Implementing the flashControl State Machine ====
 +
 +The flashControl state machine is implemented in two files: flashControl.h and flashControl.c. Both are shown below and implement the state machine shown in the associated state diagram, shown above. Study the comments to understand the implementation details.
 +
 +----
 +flashControl.h
 +<code C>
 +#ifndef FLASH_CONTROL_H_
 +#define FLASH_CONTROL_H_
 +
 +// Only advertise the tick() function.
 +void flashControl_tick();​
 +
 +#endif /* CONTROL_H_ */
 +</​code>​
 +
 +----
 +flashControl.c
 +<code C>
 +#include "​supportFiles/​switches.h"​
 +#include "​supportFiles/​buttons.h"​
 +#include "​globals.h"​
 +#include "​flashLed.h"​
 +#include <​stdio.h>​
 +
 +/*
 + * This is a tutorial set of state machines. These machines operate as follows.
 + * flashControl starts up and waits until the user pushes any of the buttons on the ZYBO board.
 + * When a button is pushed, it sets a global variable with a delay based upon which one of the
 + * slide switches are slid up (see the code below). After that, flashControl starts up
 + * flashLed. flashLed flashes LD3 - LD0 with a shifting '​1'​ pattern. The delay between
 + * shifts is determined by the slide switches. When done, flashLed sets a completed flag
 + * and waits to be disabled. Once flashLed is disabled, it returns to its init_st and waits
 + * to be enabled again.
 + * flashControl repeats this entire process each time any of the buttons are pushed.
 + * This tutorial example is demonstrating the use of functional interfaces to access any global
 + * variables. It also shows how interlocks are implemented using flashLed_enable() and
 + * flashLed_disable().
 + */
 +
 +enum flashControl_st {
 +  flashControl_init_st, ​                     // Standard init state.
 +  flashControl_waitForPb_st, ​                // Waiting for button press.
 +  flashControl_waitForFlashLedCompletion_st ​ // Waiting until the LED flashing has ended.
 +} flashControl_currentState = flashControl_init_st;​
 +
 +// These are the delay values that control how often the LED shifts across the display.
 +#define DEFAULT_TERMINATION_VALUE_IN_MS ((uint16_t) 100)  // Use this delay value if no switches are up
 +#define SWO_TERMINATION_VALUE_IN_MS ((uint16_t) 200) // Use this delay if SW0 is slid up.
 +#define SW1_TERMINATION_VALUE_IN_MS ((uint16_t) 400) // Use this delay if SW1 is slid up.
 +#define SW2_TERMINATION_VALUE_IN_MS ((uint16_t) 600) // Use this delay if SW2 is slid up.
 +#define SW3_TERMINATION_VALUE_IN_MS ((uint16_t) 800) // Use this delay if SW3 is slid up.
 +
 +// When you call switches_read(),​ these are the bit-position definitions.
 +#define SW0_MASK 0x1 // This bit is true if SW0 is slid up.
 +#define SW1_MASK 0x2 // This bit is true if SW1 is slid up.
 +#define SW2_MASK 0x4 // This bit is true if SW2 is slid up.
 +#define SW3_MASK 0x8 // This bit is true if SW3 is slid up.
 +
 +// Standard debug routine.
 +void flashControl_stateDebugPrint() {
 +  static flashControl_st 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 != flashControl_currentState || firstPass) {
 +    firstPass = false; ​               // previousState will be defined, firstPass is false.
 +    previousState = flashControl_currentState; ​    // keep track of the last state that you were in.
 +    switch(flashControl_currentState) {
 +      case flashControl_init_st:​
 +        printf("​flashControl_init_st\n\r"​);​
 +        break;
 +      case flashControl_waitForPb_st:​
 +        printf("​flashControl_waitForPb_st\n\r"​);​
 +        break;
 +      case flashControl_waitForFlashLedCompletion_st:​
 +        printf("​flashControl_waitForFlashLedCompletion_st\n\r"​);​
 + break;
 +      default:
 + break;
 +    }
 +  }
 +}
 +
 +void flashControl_tick() {
 +  flashControl_stateDebugPrint();​
 +  uint8_t switchValue;​ //​ Keep track of the switch value here.
 +  // State Action. Only Mealy actions in this state machine.
 +  switch(flashControl_currentState) {
 +    case flashControl_init_st:​
 +      break;
 +    case flashControl_waitForPb_st:​
 +      break;
 +    case flashControl_waitForFlashLedCompletion_st:​
 +      break;
 +    default:
 +      break;
 +  }
 +  // State Update. State machine relies on Mealy actions for most things.
 +  switch(flashControl_currentState) {
 +    case flashControl_init_st:​
 +      flashControl_currentState = flashControl_waitForPb_st;​ //​ Stay in the init state for one tick.
 +      break;
 +    case flashControl_waitForPb_st:​
 +      if (buttons_read()) { // True if any button is pushed.
 +        switchValue = switches_read();​ //​ Read the switches to determine the delay.
 + // The delay termination value is stored in a global location.
 + if (switchValue & SW0_MASK)
 +   globals_setDelayTerminationValue(SWO_TERMINATION_VALUE_IN_MS);​
 + else if (switchValue & SW1_MASK)
 +   globals_setDelayTerminationValue(SW1_TERMINATION_VALUE_IN_MS);​
 + else if (switchValue & SW2_MASK)
 +   globals_setDelayTerminationValue(SW2_TERMINATION_VALUE_IN_MS);​
 + else if (switchValue & SW3_MASK)
 +   globals_setDelayTerminationValue(SW3_TERMINATION_VALUE_IN_MS);​
 + else
 +   globals_setDelayTerminationValue(DEFAULT_TERMINATION_VALUE_IN_MS);​
 + flashLed_enable();​ //​ Start up the LED flashing state machine.
 +        // Go and wait for the LEDs to stop flashing.
 + flashControl_currentState = flashControl_waitForFlashLedCompletion_st;  ​
 +      }
 +      break;
 +    case flashControl_waitForFlashLedCompletion_st:​
 +    // If the LED state machine has finished, disable the flashLed SM and go to the init state, otherwise spin here.
 +      if (flashLed_completed()) {
 +        flashLed_disable();​
 + flashControl_currentState = flashControl_init_st;​
 +      }
 +      break;
 +    default:
 +      break;
 +  }
 +}
 +</​code>​
 +
 +==== Implementing the flashLed State Machine ====
 +
 +The flashLed state machine is implemented in the files flashLed.h and flashLed.c, shown below. Note that along with the standard "​tick"​ function, flashLed.h also advertises the availability of:
 +  * ''​flashLed_enable()''​ - when invoked, it enables the flashLed state machine.
 +  * ''​flashLed_disable()''​ - when invoked, it disables the flashLed state machine and allows it to return to its initial state.
 +  * ''​flashLed_completed()''​ - if this function returns true, the flashLed state machine has finished flashing the LEDs and is waiting to be disabled/​enabled to run again. If this function returns false, then the flashLed state machine is still in the process of flashing the LEDs.
 +
 +These three functions are similar to the global function that was discussed earlier. They provide access to variables managed in flashLed.c. In other words, the bool enableFlag variable contained in the flashLed.c file is shared between the flashLed and flashControl state machines. Note that this sharing could have been handled in the globals example discussed earlier, but this alternate implementation is provided for completeness. ​
 +
 +----
 +
 +//Note: As a general rule, if shared variables are strongly associated with a state machine, then it usually makes sense to define the variable and the associated set/get functions in the files used to implement that state machine. This is the case for the ''​flashLed_enable()''​ and ''​flashLed_disable()''​ functions. Otherwise, the variables can be defined in a globals.c file along with their respective set/get functions.//​
 +
 +----
 +
 +The files for flashLed.h and flashLed.c are shown directly below.
 +
 +----
 +flashLed.h
 +<code C>
 +#ifndef FLASHLED_H_
 +#define FLASHLED_H_
 +
 +// Provide these accessors for the enable variable. No direct access allowed. Only allow access via functions.
 +void flashLed_enable();​
 +void flashLed_disable();​
 +
 +// Accessor to determine if the flashLed state machine has completed its task.
 +bool flashLed_completed();​
 +
 +// Standard tick function.
 +void flashLed_tick();​
 +
 +#endif /* FLASHLED_H_ */
 +</​code>​
 +-----
 +
 +flashLed.c
 +<code C>
 +#include <​stdbool.h>​
 +#include <​stdint.h>​
 +#include "​supportFiles/​leds.h"​
 +#include "​supportFiles/​buttons.h"​
 +#include "​supportFiles/​switches.h"​
 +#include "​globals.h"​
 +#include <​stdio.h>​
 +
 +enum flashLed_st {
 +  flashLed_init_st,​ //​ Standard init state.
 +  flashLed_waitForEnable_st,​ //​ Spin here until the enable flag becomes true.
 +  flashLed_delay_st,​ //​ This is the delay state to wait between LEDs flashing.
 +  flashLed_completed_st //​ You are here if you have flashed all of the LEDs.
 +} flashLed_currentState = flashLed_init_st;​
 +
 +// Enables this state machine to leave the init state.
 +// static ensure that this variable is not visible outside of this file.
 +static bool enableFlag = false;
 +
 +// Provide these accessors for the enable variable. No direct access allowed. Only allow access via functions.
 +void flashLed_enable() {enableFlag = true;}
 +void flashLed_disable() {enableFlag = false;}
 +
 +// Used to let external controlling state machines know that have completed operation.
 +bool flashLed_completed() {return flashLed_currentState == flashLed_completed_st;​}
 +
 +// Start out with a '​1'​ in the 5th bit. You will shift this value to the right to
 +// light up the individual LEDs.
 +#define LED_INITIAL_VALUE 0x10
 +
 +void flashLed_stateDebugPrint() {
 +  static flashLed_st 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 != flashLed_currentState || firstPass) {
 +    firstPass = false; ​               // previousState will be defined, firstPass is false.
 +    previousState = flashLed_currentState; ​    // keep track of the last state that you were in.
 +      switch(flashLed_currentState) {
 +      case flashLed_init_st:​
 +        printf("​flashLed_init_st\n\r"​);​
 +        break;
 +      case flashLed_waitForEnable_st:​
 +        printf("​flashLed_waitForEnable_st\n\r"​);​
 +        break;
 +      case flashLed_delay_st:​
 +        printf("​flashLed_delay_st\n\r"​);​
 +        break;
 +      case flashLed_completed_st:​
 +        printf("​flashLed_completed_st\n\r"​);​
 +        break;
 +      default:
 +        break;
 +    }
 +  }
 +}
 +
 +// Standard tick function.
 +void flashLed_tick() {
 +  static uint16_t delay = 0;  // Declared static here as it is only used in this state machine.
 +  static uint8_t ledValue = LED_INITIAL_VALUE;​
 +  flashLed_stateDebugPrint();​
 +  // State Action.
 +  switch(flashLed_currentState) {
 +  case flashLed_init_st:​
 +    delay = 0;  // Reset the delay.
 +    ledValue = LED_INITIAL_VALUE;​ //​ Start out with the initial value for the LEDs.
 +      break;
 +    case flashLed_waitForEnable_st:​
 +      break;
 +    case flashLed_delay_st:​
 +      delay++;​  ​      // Increment the delay.
 +      break;
 +    case flashLed_completed_st:​
 +      break;
 +    default:
 +      break;
 +  }
 +  // State Update.
 +  switch(flashLed_currentState) {
 +  case flashLed_init_st:​
 +    flashLed_currentState = flashLed_waitForEnable_st;​ //​ Stay in init state for only one tick.
 +    break;
 +  case flashLed_waitForEnable_st:​
 +    if (enableFlag) {
 +      leds_write(ledValue >>= 1);                // Shift the led value and write to the LEDs (Mealy)
 +      flashLed_currentState = flashLed_delay_st;​ // Go and wait for the delay to expire.
 +    }
 +    break;
 +  case flashLed_delay_st: ​ // Leave the LED on and wait here for the delay to expire.
 +    if (delay == globals_getDelayTerminationValue()) {
 +      delay = 0;                   // Reset the delay (Mealy)
 +      leds_write(ledValue >>= 1);  // Shift the value and Write to the LEDs (Mealy)
 +    }
 +    if (ledValue == 0) {                         // If this is zero, you are done shifting the LEDs.
 +       ​flashLed_currentState = flashLed_completed_st;​ //​ This round is done, go to the completed state.
 +    }
 +    break;
 +  case flashLed_completed_st:​
 +    if (!enableFlag) ​                            // Wait here until you are disabled.
 +      flashLed_currentState = flashLed_init_st;​ // Go to the init state.
 +    break;
 +    default:
 +      break;
 +  }
 +}
 +</​code>​
 +
 +----
 +
 +Finally, the ''​main()''​ function is contained in flashMain.c as shown below.
 +
 +flashMain.c
 +<code C>
 +#include "​supportFiles/​utils.h"​
 +#include "​flashControl.h"​
 +#include "​flashLed.h"​
 +#include "​supportFiles/​buttons.h"​
 +#include "​supportFiles/​switches.h"​
 +#include "​supportFiles/​leds.h"​
 +
 +int main() {
 +  leds_init(true);​ //​ init the LEDs.
 +  buttons_init();​
 +  switches_init();​
 +  while (1) {
 +    flashControl_tick();​
 +    flashLed_tick();​
 +    utils_msDelay(1);​
 +  }
 +}
 +</​code>​
 +
 +-----
 +
 +When you run the program on the 330 board, you will see the following printed out at the console:
 +
 +{{::​xilinxsdkconsole.jpg?​500|}}
state_machine_interlocks_and_global_variables.txt ยท Last modified: 2019/04/08 20:11 (external edit)