In part 2 of this MSP430 state machine project, I will overview the state machine logic, as well as covering the C programming code.
State Machine
The state machine diagram above illustrates the logic behind the state machines operation. The machine can only be in one state at a time, this is known as the current state. Changes from one state to another are brought about by an event, this brings about a transition into a new state or alters a parameter in the current state.
As can be seen from the diagram there are 5 states. There are 8 events listed in top right of the image, 2 of these events (E_INC and E_DEC) do not trigger a state transition but alter a parameter in the states they are associated with.
The lines and arrows between each of the states shows the direction that a state can transition to i.e. in the diagram above we can see that the OFFSTATE cannot make a transition to the AUTOMATIC STATE. Therefore the state diagram gives us a structure to follow, that can be hard coded into the C programming code ensuring the logical flow of the state machine is followed.
States
OFFSTATE – This is simply a state to indicate the system is off
ONSTATE – This is simply a state to indicate the system is on
BRIGHTNESS ADJ STATE – Allows the brightness of an LED to be increased and decreased
PRESETS MODE STATE – Allows the user to scroll through 3 preset LED brightness values
AUTOMATIC STATE – The brightness of the LED will be dependant on the brightness of the ambient light falling on the LDR
Events
E_OFF – This brings about a transition into the OFFSTATE
E_ON – This brings about a transition into the ONSTATE
E_BRIADJ – This brings about a transition into the BRIGHTNESS ADJ STATE
E_PRESET – This brings about a transition into the PRESETS MODE STATE
E_AUTO – This brings about a transition into the AUTOMATIC STATE
E_INC – Allows the user to increase the brightness or change the preset levels, depending on which state the system is currently in
E_DEC – Allows the user to decrease the brightness or change the preset levels depending on which state the system is currently in
E_TIMEOUT – This brings about a transition into the ONSTATE and is a 15 second timeout
MSP430 State Machine C Code
In this section of the tutorial I will give a brief overview of the main parts of the code. All of the code was written in either Eclipse or Texas Instruments Code Composer Studio v5.3 or above.
Variables
There are 2 types of enum value enum states and enum events, the enum states are prefixed with an ‘S’ and the enum events are prefixed with an ‘E’, this ensures that they are easily readable when coding.
There are also 10 global variables which is not ideal and global variables should be avoided when ever possible, at the time of writing this code I was not aware of a better solution and this method sufficed.
// State definitions, states are the routine the program jumps to, these are prefixed with an 'S' enum states {S_OFF, S_ON, S_BRIADJ, S_PRESET, S_AUTO, S_MAX}; // Event definitions, events trigger a transition and are prefixed with an 'E' enum events {E_OFF, E_ON, E_BRIADJ, E_PRESET, E_AUTO, E_INC, E_DEC, E_TIMEOUT, E_MAX}; // Global variables int Current_State = S_OFF; // Default state the system starts in volatile int sysTick = 0; // Volatile to prevent main code accessing a register copy unsigned int LongDelay = 0; // Used to create a 15 Sec delay unsigned int ScrRefresh = 0; // Used to slow the screen refresh rate down unsigned int brightness = 5; // variable to store brightness external to StateM unsigned int preset_mode = 1; // variable to store preset mode external to StateM unsigned int adc; // Used to store the ADC value unsigned int briadjSys = 0; // Used as a flag when S_BRIADJ = Current_State unsigned int presetSys = 0; // Used as a flag when S_PRESET = Current_State unsigned int autoSys = 0; // Used as a flag when S_AUTO = Current_State
Most of these are self explanatory so I will cover the ones I believe are most relevant:
sysTick – This uses the system timer to generate a timed pulse which is used like a system heart beat to bring control and order to the state machine and the user button polling.
ScrRefresh – As the LCD was interfaced with minimum pins, any feedback from the LCD to indicated it is busy was not possibly. The LCD operates at a much slower frequency to the microcontroller.
briadjSys, presetSys and autoSys – The last 3 global variables are used as flags to determine the current state, they are toggled every time a transition is made from or to states S_BRIADJ, S_PRESET and S_AUTO. Specific functions are run depending on the toggled values outside the StateM function, but within the infinite while loop. These variables are necessary as the functions will then run even when the state changes to S_ON, which can be transitioned to after 15 seconds if the TimeOut function is called.
Functions
CheckButtons – This function contains if else statements, which are executed and return an event depending on which user button was pressed. There is also some addtional checks using C operators to determine what state the state machine is in. The CheckButtons function returns a value to the StateM function. *Update* This code is still valid but I have improved on a function to check the GPIO pins on the MSP430, by using switch case statements. This is all covered in a new two part article on switch debouncing, located here.
int CheckButtons() // Buttons trigger an event which is then parsed to the StateM function { if (LongDelay >= 30) { return E_TIMEOUT; } if(~P1IN & OnOff_B) // ON/OFF button pressed **S2 LOW to make { if (Current_State != S_OFF) // If the Current_State is NOT S_OFF execute IF { return E_OFF; // Pass E_OFF to the StateM Function } else { return E_ON; // Pass E_ON to the StateM Function } } if(~P2IN & Select_B) // SELECT button pressed **LOW to make { if (Current_State == S_ON) // If the Current_State is equal to S_ON execute IF { return E_BRIADJ; // Pass E_BRIADJ to the StateM Function } if (Current_State == S_BRIADJ) // If the Current_State is equal to S_BRIADJ execute IF { return E_PRESET; // Pass E_PRESET to the StateM Function } if (Current_State == S_PRESET) // If the Current_State is equal to S_BRIADJ execute IF { return E_AUTO; // Pass E_AUTO to the StateM Function } else // If the Current_State is equal to execute ELSE { return E_BRIADJ; // Pass E_BRIADJ to the StateM Function } } if(~P2IN & Inc_B) // INCREASE button pressed **LOW to make { return E_INC; // Pass E_INC to the StateM Function } if(~P2IN & Dec_B) // DECREASE button pressed **LOW to make { return E_DEC; // Pass E_DEC to the StateM Function } else { return S_MAX; } }
StateM – The StateM function contains the main frame work for the state machine. It uses switch case statements to determine which state the current state can make a transition to, when an event is passed to it.
The logic in this function defines how the state machine will operate. Looking back to the state machine diagram it can be seen that the OFFSTATE can only make a transition into the ONSTATE. Now looking at the first case in the code below, it can be seen that the only state S_OFF can move to is S_ON, which is brought about by the E_ON event. The rest of the switch case code follows the same logical process and can be directly compared with the state diagram.
Right at the end of the StateM function are 2 if statements, these are used to run external functions called OnEnter, OnExit and Do. These functions can be used to action any code when a transitions occurs between 2 states, or if the same state is selected.
/* ********** Start of state machine code ********** */ void StateM(int event) { int NextState = Current_State; // NextState is equal to the Current_State value switch ( Current_State ) // Switch the Current_State value using the event { //value parsed from the CheckButtons function case S_OFF: // Current state S_OFF, accepts 1 event and switch (event) //can therefore only move to 1 other state S_ON { case E_ON: NextState = S_ON; break; } break; case S_ON: // Current state S_ON, accepts 2 events and can switch (event) //therefore only accept 2 transitions to other states { case E_BRIADJ: NextState = S_BRIADJ; break; case E_OFF: NextState = S_OFF; break; } break; case S_BRIADJ: // Current state S_BRIADJ, accepts 5 events but can switch (event) //only accept 3 transitions to other states { case E_PRESET: NextState = S_PRESET; break; case E_OFF: NextState = S_OFF; break; case E_TIMEOUT: NextState = S_ON; break; case E_INC: NextState = Current_State; // No state change, but changes a parameter break; //inside the current state case E_DEC: NextState = Current_State; // No state change, but changes a parameter break; //inside the current state } break; case S_PRESET: // Current state S_PRESET, accepts 5 events but can switch (event) //only accept 3 transitions to other states { case E_AUTO: NextState = S_AUTO; break; case E_OFF: NextState = S_OFF; break; case E_TIMEOUT: NextState = S_ON; break; case E_INC: NextState = Current_State; // No state change, but changes a parameter break; //inside the current state case E_DEC: NextState = Current_State; // No state change, but changes a parameter break; //inside the current state } break; case S_AUTO: // Current state S_PRESET, accepts 5 events but can switch (event) //only accept 3 transitions to other states { case E_BRIADJ: NextState = S_BRIADJ; break; case E_OFF: NextState = S_OFF; break; case E_TIMEOUT: NextState = S_ON; break; } break; // The program should never get to the default case default: break; } // The code below executes every time the state machine function //is called, and runs after the transition into the next state if (NextState != Current_State) // NextState (NOT =) to Current_State { //which should always be the case //OnExit(Current_State); // Not used in this project OnEnter(NextState); Current_State = NextState; } if ( event != E_MAX) {Do( Current_State );} }
counter – This function sets up and initialises the Timer 0 on the MSP430, the code is well commented so does not need a great deal of explanation. When the timer has counted to the predefined value, an interrupt is generated, this is then used to set the sysTick variable to 1, the sysTick variable is then zeroed every time the StateM function is executed, which allows the user buttons to be polled. There is also some additional counter timing functions carried out here for the TimeOut function.
/* ********** Start of sysTick timer and interrupt code ********** */ int counter() { TA0CCR0 = 65500 -1; // Count limit 137.5kHz / 65500 = 2.1, therefore interrupt triggers roughly every 476mS TA0CCTL0 = CCIE; // Enable counter interrupts TA0CTL = TASSEL_2 + MC_1 + ID_3; // Timer A0 with SMCLK at 1.1MHZ / 8 = 137.5kHz, count UP _BIS_SR( GIE ); // Enable interrupts } #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0_A0 (void) // Timer0 A0 interrupt service routine { sysTick = 1; LongDelay++; if(LongDelay >= 32) {LongDelay = 0;} ScrRefresh = 1; }
TimeOut – The TimeOut function simply runs if a counter reaches a certain value, which is roughly 15 seconds. The counter for this function is reset every time the user presses a button. looking back at the CheckButtons function right at the top can be seen the logic for the E_TIMEOUT event.
void TimeOut() // Return menu to S_ON state after 15 seconds approximately { if (LongDelay >= 30 && Current_State != S_ON && Current_State != S_OFF) { StateM( CheckButtons() ); // Execute state machine function LongDelay = 0; // Resets LongDelay counter } }
setup_HW – This function contains the code used to stop the watchdog timer, set up the system clock, set up the ADC and finally some GPIO port settings.
int setup_HW() // Setup HW clocks, ports etc { WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // Set DCO step and modulation ADC10CTL0 = ADC10SHT_3 + ADC10ON; // Setup ADC ADC10CTL1 = INCH_2; // Setup ADC P2DIR |= LED; // P2.3 output P2OUT &= ~LED; // P2.3 selection P2DIR |= LED2; // P2.5 output P2OUT &= ~LED2; // P2.5 selection }
automatic – This function is used to take the ADC value read from the LDR input. It divides the value by 9, so it can be broken down into 10 values the user can then adjust the brightness on a scale of 1 to 10.
void automatic() { adc = ADC10MEM; // Copies data in ADC10MEM to unsigned int adc ADC10CTL0 |= ENC + ADC10SC; // Enable Conversion + Start Conversion int divby = 9; brightness = adc / divby / divby; // Divides the ADC value by 9 then by 9 again if (brightness >= 11) {brightness = 10;} if (brightness <= 1) {brightness = 1;} }
automatic_lcd – This function is used to update the LCD with the raw value being read directly from the ADC input, an external itoa function is used to convert the integer into a char string so it can be displayed correctly on the LCD. The ScrRefresh variable is used to ensure the LCD is not updated too quickly, which would otherwise cause corruption of the LCD information.
void automatic_lcd() { if(ScrRefresh == 1 && Current_State != S_ON) { ClearLcmScreen(); LcmSetCursorPosition(0,4); PrintStr("AUTOMATIC"); LcmSetCursorPosition(1,0); PrintStr("Level = "); LcmSetCursorPosition(1,12); int base = 10; // Sets the base of the conversion to 10 i.e. Decimal unsigned char buffer[8]; // Buffer stores the string int counter = 4; // Couter for the string size itoa(adc, buffer, base); // Itoa function call with 3 parameters while(buffer[counter]) { PrintStr(buffer); // This will print one character, the loop is used to print the whole string counter++; } } ScrRefresh = 0; // Reset screen refresh counter to zero }
main – The main function obviously contains the programming code and is essential to any C program, this is the last function I will over with a code excerpt as it ties together the functions previously discussed.
There are some hardware set up functions run at the start of main. Then there is an infinite while loop, a characteristic of any embedded system. The first if statement ensures the sysTick variable is equal to 1, and then if this is true the next if statement ensures a user button has been pressed.
The StateM function is, which calls the CheckButtons function, which then returns an event to the StateM function. Once the StateM function is complete, the sysTick and LongDelay variables are both reset to 0.
The last 3 if statements ensure that the LED’s are controlled continuously even when the state machine is not running, the three variables being used as flags are all reset upon entering the S_OFF state.
Finally the TimeOut function is called and counter checked.
/* ********** Start Main ********** */ int main() { setup_HW(); // Function to setup hardware InitializeLcm(); // Initialise the LCM into 4bit mode ClearLcmScreen(); // Clear the LCM buffer counter(); // Timer_A determines the time for the sysTick while ( Current_State != S_MAX )// Continue while Current_State is (NOT =) to S_MAX { if (sysTick == 1) { if (~P1IN & OnOff_B || ~P2IN & Select_B || ~P2IN & Inc_B || ~P2IN & Dec_B)# // If sysTick coincides with OnOff or Select button being pressed, run state machine { StateM( CheckButtons() ); // Execute state machine function sysTick = 0; // Resets sysTick to 0 LongDelay = 0; // Resets LongDelay if key pressed } } if (briadjSys == 1 && Current_State != S_OFF) {soft_PWM();} if (presetSys == 1 && Current_State != S_OFF) {preset_level();} if (autoSys == 1 && Current_State != S_OFF) {automatic(); soft_PWM(); automatic_lcd();} TimeOut(); } return 0; } /* ********** End Main ********** */
OnEnter – This function is called at the end of the StateM function, and is passed the next state value as an integer. Using a switch case statement the OnEnter function then runs any code specified for that new state upon entering.
Do – This function is also called at the end of the StateM function, and is passed the current state (which now has the same value as the next state) value as an integer. Using a switch case statement the Do function then runs any code specified for the current state to run.
bri_adj – Function is called by the Do function when in S_BRIADJ state, it displays information on the LCD depending on the brightness value, which is determined by the increase and decrease user buttons.
preset_modes – This is very similar to bri_adj function except it is called when in the S_PRESET state.
soft_PWM – Function loads a software PWM based on switch case statements, which determine the brightness of the output LED’s.
preset_level – Almost the same as the soft_PWM function, but fewer cases.
Complete State Machine C Code
The link below contains the zip file with the full state machine C code covered in this second tutorial, there is a small advert page first via Adfly, which can be skipped and just takes a few seconds, but helps me to pay towards the hosting of the website.