MSP430 State Machine Project Pt/2

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.

MSP430 State Machine C Code v1.4

Leave a Reply