Jump to content

n1ksn

Members
  • Content Count

    38
  • Joined

  • Last visited

  • Days Won

    2
  1. I realized after posting this tutorial that the functions associated with the LED state machine can (and should) be declared as static. This will allow an optimizing complier to do a better job compressing the code. I've revised the zip file accordingly, including static declaration for the pushbutton state machine. I have not changed the code snippets. Sorry for any inconvenience. Cheers, Andy n1ksn
  2. Thank you for the post, Christian. When I opened the TI spreadsheet it hung my system. However, I was able to look at the generated code. Looks interesting. When I've recovered from writing my tutorial I'll give it another look. Andy n1ksn
  3. Here is the code for the main module in the complete state machine demo program. I'm including it here because the comments contain general descriptions of the the program modules and the main function is rather short. //------------------------------------------------------------------------------ // main.c in project StateMachineBlink6 // // The overall structure of this program follows the approach of Tom Baugh // found in "MSP430 State Machine Programming." // // The modules in this program: // // Main: General shell program for initialization and execution of the // system tick loop. Program sleeps in LPM3 when not executing a // tick. This version uses the watchdog timer to generate system // ticks, leaving TimerA free for other modules. // // System: Does general low-level hardware initialization and contains // the WDT ISR. // // MyApp: Demonstration applicaton to control the state of the LEDs // using three pushbuttons. // // Led: Driver that sets up and runs state machines that control three LEDs. // // Button: Driver that sets up and runs state machines that control // pushbutton functions, including debouncing and optional alternate // function or acceleration feature. The functions actually // executed on a push are determined by an event sink registration // function called by the client, which in this case is the // MyApp module. // // See the individual module headers for further descriptions. // // This program is written for the MSP430G2452, but should be easily changed // to other MSP430 devices with 4K or more of flash memory by modifying // the device header file include statements. Note that a clock crystal of // 32768 Hz is used to allow LPM3 sleep between ticks. Also, this program was // compiled and tested using CCS Workbench, with the compiler set to level 4 // optimiazation and minimum size. It should compile under IAR Kickstart with // no modifications. // // Andrew Palm // 2011.10.25 //------------------------------------------------------------------------------ #include #include #include "System.h" #include "Led.h" #include "Button.h" #include "MyApp.h" void InitializeHW(void); void InitializeApps(void); //------------------------------------------------------------------------------ void main(void) { WDTCTL = (WDTPW | WDTHOLD); // Disable WDT InitializeHW(); InitializeApps(); _enable_interrupts(); while(1) { // Event loop _low_power_mode_3(); // Sleep between ticks Led_ProcessTimerEvents(); // Process LED state machine timer events Button_ProcessTimerEvents(); // Process button SM timer events } } //------------------------------------------------------------------------------ void InitializeHW(void) { System_InitializeHW(); Led_InitializeHW(); Button_InitializeHW(); MyApp_InitializeHW(); } //------------------------------------------------------------------------------ void InitializeApps(void) { Led_InitializeApp(); Button_InitializeApp(); MyApp_InitializeApp(); } After stopping the watchdog timer the initialization functions for all the modules are called and the program enters an endless loop. The processor goes to sleep in LPM3 and is awakened by the watchdog timer ISR (the system tick). The loop contains the calls to the state machine timer functions for the LEDs and the buttons. After these are executed the processor goes back to sleep. There is also a port pin ISR in the module Button.c, but after this performs a few operations the processor goes immediately back to sleep. Basically, all the action is driven by three pushbuttons. Each button is managed by a state machine, similar to the state machines for the LEDs. These are contained in the Button module. There are a couple of differences however. First, the button state machine is "flat"--there are no superstates. Second, since for any given state there is only one signal that causes a transition out of that state, signals are not explicitly used in the code (although they appear on the UML statechart). The button state machines handle debouncing and add functionality to the buttons by implementing either an alternate function or an acceleration feature on an extended push and release in addition to the normal push and release function. Another difference in the button state machines is that the client does not control their transitions through client interface functions as is done for the LEDs. Rather, the client module, named MyApp in this demo, "registers event sinks" with the button state machines. What this comes down to is that the client module passes to the button module the addresses of functions that it wants to be executed when a button is pressed. These registrations can be static, occuring only at program startup, or they can be dynamically changed during program execution in response to different operating modes. The demo shows how this can be done. I'm sure everyone is familiar with applications where a set of panel buttons change their function when the gizmo goes into a different mode of operation. The comments at the beginning of the module MyApp explain the button functions and how they change. One button when pressed and released normally (under one second) rotates through the three LEDs. A long (over one second) push and release of this same button changes the mode of the selected LED. One mode is the steady on/off mode where the other two buttons turn the LED on and off. The other two modes are blinking modes. In one of these the other two buttons change the LED blink period up or down. In the other blinking mode the other two buttons increase and decrease the blink duty cycle. I won't go further into the demo, as the main point of these posts was to show how to code the state machines in Led.c. But you should at least look at Button.h and Button.c to understand event sink registration. (By the way, this user interface has several deficiencies, not the least of which is that the currently selected LED is not indicated. But making this better would take away from putting state machines to work in my "real" projectcs.) Attached below is a zip file with all the project code, plus jpg files containing the UML statecharts for the LEDs and the buttons. I've also included the UMLet files for these. If you decide to use this approach and want to draw statecharts, the UMLet program, which is freeware, is a nice way to go. It can be downloaded as a standalone program or an Eclipse plug-in. Unfortunately, the program compiles under CCS to over 2K, so you will need an MSP430 beyond what comes with the Launchpad. But you should be able to write a simpler demo under 2K that blinks the two Launchpad LEDs and cycles through different LED behaviours with successive pushes of the single Launchpad user pushbutton. I hope those of you that have read these posts will have found something useful. Best regards, Andy n1ksn StateMachineDemo.zip
  4. We are headed down the home stretch. We finish this module with two more sets of functions, those which send signals to the state machine. Here are the functions in the client interface (with prototypes in Led.h): //------------------------------------------------------------------------------ // Functions to allow client module to control LEDs // Put LED in steady off state void Led_TurnOff(Led_Index index) { This_Led = Led_Current[index]; This_Signal = LED_OFF_SIG; (This_Led->Current_State)(); } // Put LED in steady on state void Led_TurnOn(Led_Index index) { This_Led = Led_Current[index]; This_Signal = LED_ON_SIG; (This_Led->Current_State)(); } // Puts LED in last steady state void Led_Steady(Led_Index index) { This_Led = Led_Current[index]; This_Signal = LED_STEADY_SIG; (This_Led->Current_State)(); } // Puts LED in blink state void Led_Blink(Led_Index index) { This_Led = Led_Current[index]; This_Signal = LED_BLINK_SIG; (This_Led->Current_State)(); } // Sets LED on and off tick lengths (in system ticks), // restricted to range set by LED_MAX_TICKS and LED_MIN_TICKS. // Does not change current state of LED. void Led_SetBlink(Led_Index index, uint16_t on_ticks, uint16_t off_ticks) { if(on_ticks < LED_MIN_TICKS) { on_ticks = LED_MIN_TICKS; } if(on_ticks > LED_MAX_TICKS) { on_ticks = LED_MAX_TICKS; } if(off_ticks < LED_MIN_TICKS) { off_ticks = LED_MIN_TICKS; } if(off_ticks > LED_MAX_TICKS) { off_ticks = LED_MAX_TICKS; } This_Led = Led_Current[index]; This_Led->on_ticks = on_ticks; This_Led->off_ticks = off_ticks; } // Resets blink period to 2 * LED_DEFAULT_TICKS with 50% duty cycle // without changing LED state. void Led_ResetBlink(Led_Index index) { This_Led = Led_Current[index]; This_Led->on_ticks = LED_DEFAULT_TICKS; This_Led->off_ticks = LED_DEFAULT_TICKS; } // Retrieves current on_tick value of LED uint16_t Led_OnTicks(Led_Index index) { This_Led = Led_Current[index]; return(This_Led->on_ticks); } // Retrieves current off_tick value of LED uint16_t Led_OffTicks(Led_Index index) { This_Led = Led_Current[index]; return(This_Led->off_ticks); } The first four client interface functions all have the same structure. FIrst, This_Led is set to the active LED through the Current_Led array. (I explained the use of this array in an earlier part.) Then This_Signal is set to the appropriate signal to cause the desired transformation. Finally, the current state handler function is called. This last statement is (This_Led->Current_State)(); Strickly speaking there should be an asterisk in front of This_Led, as the address of the function to be called is the contents of the pointer This_Led->Current_State. But C syntax allows the asterisk to be dropped (or kept). This is how any state machine transformation is made. The third of these functions was added after I started this tutorial, so there is an edit of the Led.h code shown previously. I realized that I had no client function exercising the return to the History dot in the steady superstate, so I slipped it in. The last four functions in this set are used to modify the lengths of the on and off intervals in the blink superstate. None of them change the state of the LED state machine. They are operations done in response to button pushes in the overall demo program. The next function controls the state machine software timers: //------------------------------------------------------------------------------ // This function is called every system tick from main. It drives the state // machine software timers if they are active. void Led_ProcessTimerEvents(void) { // Common signal for any timer calls to state machines This_Signal = LED_TIMEOUT_SIG; if(Led_1.timer_active) { Led_1.timer_counter--; if(0 == Led_1.timer_counter) { This_Led = &Led_1; // Set active LED // Call active LED current state with timeout signal Led_1.Current_State(); } } if(Led_2.timer_active) { Led_2.timer_counter--; if(0 == Led_2.timer_counter) { This_Led = &Led_2; Led_2.Current_State(); } } if(Led_3.timer_active) { Led_3.timer_counter--; if(0 == Led_3.timer_counter) { This_Led = &Led_3; Led_3.Current_State(); } } } Since the only signal a timer sends is LED_TIMEOUT_SIG this is loaded into This_Signal at the start of the function. Then for each state machine if the timer is active the variable timer_counter is decremented and if it has reached zero the pointer This_Led is set to the address of the appropriate Led structure and the function pointed to by Current_State is called. This will be one of the blink state handler functions. If the timer is not active, or if the timer has not counted all the way down to zero, nothing is done to change the state machine. This function could have been implemented using a for(; loop with an index going through the three LEDs, and this approach might reduce the program size somewhat. If there were more than three LEDs I would definately consider that approach. However, since this function is called by main on every system tick, we want it to execute as fast as possible so that it doesn't hog too much of the time available in the tick interval. A good exercise would be to try it both ways, look at the disassembly output for each, and count processor cycles for each. Depending on the compiler used, one way could have a distinct time advantage over the other. It is this kind of nitty-gritty inspection of the code that is done in Baugh's book (using the IAR compiler). Fortunately, as a hobbyist none of my projects has required this deep inspection, but I can appreciate why it might be necessary. But I digress. That complete our look at all the code in Led.c. I hope it has been clear enough that you can try using this state machine approach in some of your own projects. In the last installment I'll make some comments on the other modules in the demo program and attach a zip file with all the code in this project. Andy n1ksn
  5. We are now ready to look at the state entry and state handler functions. Here is the set associated with the steady superstate: //------------------------------------------------------------------------------ // Led state machine state entry and state handler functions void Led_Enter_Steady(void) { switch(This_Signal) { // Signal determines substate to enter case LED_STEADY_SIG: { if(This_Led->Last_Steady_State == &Led_State_Steady_On) { Led_Enter_Steady_On(); } else { Led_Enter_Steady_Off(); } break; } case LED_ON_SIG: { Led_Enter_Steady_On(); break; } case LED_OFF_SIG: { Led_Enter_Steady_Off(); break; } } } void Led_Enter_Steady_Off(void) { *(This_Led->port) &= ~(This_Led->bitmask); // Turn off LED This_Led->Current_State = &Led_State_Steady_Off; // Save current (sub)state for reentrance to history This_Led->Last_Steady_State = This_Led->Current_State; } void Led_State_Steady_Off(void) { switch(This_Signal) { // Signal determines next state case LED_ON_SIG: { Led_Enter_Steady_On(); break; } case LED_BLINK_SIG: { Led_Enter_Blink(); break; } } } void Led_Enter_Steady_On(void) { *(This_Led->port) |= (This_Led->bitmask); // Turn on LED This_Led->Current_State = &Led_State_Steady_On; // Save current (sub)state for reentrance to history This_Led->Last_Steady_State = This_Led->Current_State; } void Led_State_Steady_On(void) { switch(This_Signal) { // Signal determines next state case LED_OFF_SIG: { Led_Enter_Steady_Off(); break; } case LED_BLINK_SIG: { Led_Enter_Blink(); break; } } } The first function is Led_Enter_Steady, the entry function for the steady superstate. This is the most complicated of the entry functions because this superstate can be entered (in addition to the initial transformation) on three different signals, LED_OFF_SIG, LED_ON_SIG, and LED_STEADY_SIG. Also, if the signal is the last of these the transformation supposed to go to the last active steady substate. For this reason the switch statement has three case entries, one for each signal, and an if-then-else construct to implement the History dot. So depending on the signal, the appropriate entry function is called. If there were any entry actions for this superstate, they would go in this function before the switch statement. Next we have the Led_Enter_Steady_Off entry function. This is where the steady off state entry actions take place. The first statement uses the bitmask to turn off the LED. Note how the left hand side has the asterisk which makes it denote the contents of the pointer This_Led->port which points to LEDOUT which is defined as P1OUT. Then the Current_State variable is set to the address of Led_State_Steady_Off (see the amperstand?). The next time the current state function is called from outside the state machine, this will be the function that is called. Finally, the current state is saved as part of the implementation of the History dot. The Led_State_Steady_Off state handler function has a single switch statement with two cases, one for each of the signals that result in a transition out of that state. If the state had any exit functions, they would be ahead of this switch statement. The last two functions are the state entry and state handler functions for the steady on state and are very similiar to those for the steady off state. Here are the functions for the blink superstate and its substates: void Led_Enter_Blink(void) { This_Led->timer_active = TRUE; // Timer needed in this superstate Led_Enter_Blink_On(); // Initial substate } void Led_Enter_Blink_On(void) { *(This_Led->port) |= (This_Led->bitmask); // Turn on LED This_Led->timer_counter = This_Led->on_ticks; // Reload delay timer This_Led->Current_State = &Led_State_Blink_On; } void Led_State_Blink_On(void) { switch(This_Signal) { // Signal determines next state case LED_TIMEOUT_SIG: { Led_Enter_Blink_Off(); break; } case LED_OFF_SIG: case LED_ON_SIG: case LED_STEADY_SIG: { // Timer not needed in steady superstate This_Led->timer_active = FALSE; Led_Enter_Steady(); break; } } } void Led_Enter_Blink_Off(void) { *(This_Led->port) &= ~(This_Led->bitmask); // Turn off LED This_Led->timer_counter = This_Led->off_ticks; // Reload delay timer This_Led->Current_State = &Led_State_Blink_Off; } void Led_State_Blink_Off(void) { switch(This_Signal) { // Signal determines next state case LED_TIMEOUT_SIG: { Led_Enter_Blink_On(); break; } case LED_OFF_SIG: case LED_ON_SIG: case LED_STEADY_SIG: { // Timer not needed in steady superstate This_Led->timer_active = FALSE; Led_Enter_Steady(); break; } } } Compared to the steady superstate, the entry function for the blink superstate is very simple. It executes the entry action of activating the timer and then goes right to the entry function for blink on, as per the dot inside this superstate in the UML statechart. The entry function Led_Enter_Blink_On does the entry actions of turning on the LED and loading the timer counter with the variable on_ticks. It then sets the Current_State variable to the corresponding state handler function address. The state handler function Led_State_Blink_On has a single switch statement with two cases to handle four possible signals. If the timeout signal is received the blink off entry function is called. For the other three signals the transition is back to the steady superstate, so in this case the superstate exit action of deactivating the timer is done followed by a call to the steady superstate entry function. The functions for the blink off state are very similar. These functions illustrate one problem with this simple approach, namely that the exit action for the blink superstate has to appear in two places, in each of the substate state handler functions. This is a potential maintenance problem (you could change the exit action in one place but not the other), but more sophisticated approaches that avoid this are more complicated. I think this is a small price to pay because the writing of these state entry and handler functions flowed quite easily with the UML statechart sitting in front of me. That's it for now. In the next part we will look at the client interface and timer functions which actually send the signals to the state machine and drive its transitions. Andy n1ksn
  6. Now let's look at how we will initialize our LED state machines on program startup. Here is our next code segment: //------------------------------------------------------------------------------ // The "constructor" and initializer for LED state machines void Led_Ctor(Led *This, uint16_t on_ticks, uint16_t off_ticks, uint8_t bitmask, volatile uint8_t *port); void Led_Initial(void); // Led state machine state entry and state handler functions void Led_Enter_Steady(void); void Led_Enter_Steady_Off(void); void Led_State_Steady_Off(void); void Led_Enter_Steady_On(void); void Led_State_Steady_On(void); void Led_Enter_Blink(void); void Led_Enter_Blink_Off(void); void Led_State_Blink_Off(void); void Led_Enter_Blink_On(void); void Led_State_Blink_On(void); These are the function prototypes for this module Led.c, except for those given in Led.h. The first two are for functions used to initialize an LED state machine, which we will look at in a bit. The remainder are state entry functions and state functions for the state machine. While we see them together here, let's say a few words about them. Looking back at our UML statechart we see that there is an entry function for both superstates (representing the steady and blink modes of operation) and for each of the four substates. There is a state function for each substate, but no state functions for the superstates. Why is this? Generally, the substate entry functions are used to perform the actions labelled "Entry" in the statechart and then set the current state to the associated state function. For example, Led_Enter_Blink_On turns the LED on and loads the timer counter for the number of on ticks. It then sets the Current_State variable in the state machine structure to the address of the function Led_State_Blink_On. Now a state entry function for a superstate also performs any entry actions for that superstate, but there is no corresponding state function because whenever a superstate is entered, one always ends up in one of the substates. So instead of setting the Current_State variable, a superstate entry function just calls the entry function for the substate that will be active. This will become clearer later on. Here is the segment containing the initialization functions: //------------------------------------------------------------------------------ // LED state machine constructor functon void Led_Ctor(Led *This, uint16_t on_ticks, uint16_t off_ticks, uint8_t bitmask, volatile uint8_t *port) { This->Current_State = &Led_Initial; This->on_ticks = on_ticks; This->off_ticks = off_ticks; This->bitmask = bitmask; This->port = port; } //------------------------------------------------------------------------------ // State machine initial state function void Led_Initial(void) { // Put any special initialization statements here Led_Enter_Steady(); // Initially in steady superstate } The function Led_Ctor takes a pointer (named This) to the Led structure to be initialized, plus values for some of the variables in the structure. The pointer is used in statements like This->on_ticks = on_ticks; where the left hand side is the structure variable and the right side is the function argument containing the desired initial value for that variable. The first function statement sets the Current_State function pointer to the function Led_Initial, which is the second function shown in this segment. This is a special pseudo-state function that represents the highest level dot on the UML statechart. For this example, this function simply calls the entry function for the steady superstate. Any other special initialization actions could be put in it, but aren't needed here. Now we look at the initialization functions called by main on program startup: //------------------------------------------------------------------------------ // Initialiation functions called by main on startup void Led_InitializeHW(void) { // Led port pins initialized in System.c } // Construct LED blink state machines and initialize to defaults void Led_InitializeApp(void) { // Common signal for initial calls to state machines for steady off state This_Signal = LED_OFF_SIG; Led_Ctor(&Led_1, LED_DEFAULT_TICKS, LED_DEFAULT_TICKS, LED1_BITMASK, &LEDOUT); This_Led = &Led_1; // Set active LED // Call active LED initial state with led steady off signal Led_1.Current_State(); Led_Ctor(&Led_2, LED_DEFAULT_TICKS, LED_DEFAULT_TICKS, LED2_BITMASK, &LEDOUT); This_Led = &Led_2; Led_2.Current_State(); Led_Ctor(&Led_3, LED_DEFAULT_TICKS, LED_DEFAULT_TICKS, LED3_BITMASK, &LEDOUT); This_Led = &Led_3; Led_3.Current_State(); } The first function does nothing, as the LED port pins are set up in System.c. I left it here as a placeholder. The second function first sets the variable This_Signal to LED_OFF_SIG. As discussed previously, this variable must be set before any state entry or state function is invoked. Next Led_Ctor is called to initialize the Led_1 state machine. The on and off tick variables are set to a default value (given by a define statement in the header Led.h) that results in a blink with a one second period and a 50% duty cycle (equal on and off times). The output port and bitmask are also set as per the defines in System.h. After this the variable This_Led is set to point to Led_1, again because this variable is used inside entry and state functions. Lastly (for Led_1), there is a call to whatever function has its address stored in the Current_State variable. This was set to the address of Led_Initial, so that function is called. Since that function in turn calls Led_Enter_Steady and This_Signal is set to LED_OFF_SIG, we then go to Led_Enter_Steady_Off and finally (as we will see below) end up with Current_State equal to the address of Led_State_Steady_Off. Then we repeat all this for Led_2 and Led_3. Follow that? It really isn't that bad, but I admit scratching my head a few times at first contact. In the next part we will see just what goes into the state entry and state functions. Andy n1ksn
  7. Now that we have done some of the preliminaries, it's timer to finally get down to some of the C code to implement our LED state machine. //------------------------------------------------------------------------------ // Led.c in project StateMachineBlink6 // // This module contains LED drivers for setting LEDs off, on, or blinking. // It illustrates the implementation of a simple hierarchical state machine. // // Andrew Palm // 2011.10.25 //------------------------------------------------------------------------------ #include #include #include "System.h" #include "Led.h" //------------------------------------------------------------------------------ // Variable type and values of signals to LED state machine typedef enum { NULL_SIG = 0, LED_OFF_SIG, LED_ON_SIG, LED_STEADY_SIG, LED_BLINK_SIG, LED_TIMEOUT_SIG } Led_Signal; First we see the includes which are the two header files covered in Part 2 plus the device file and the header stdint.h which defines types such as uint8_t. After these follows another combined typedef enum statement that defines the state machine signals. The signals are those associated with the transition arrows in the UML statechart from Part 1. They are how the world outside the state machine causes it to change state. Specifically (ignoring the NULL_SIG which isn't used), the first four signals are sent by the client interface functions while LED_TIMEOUT_SIG is sent by the software timer. We'll see how they work further on. //------------------------------------------------------------------------------ // LED finite state machine variables typedef struct LedTag { // The LED FSM PFN Current_State; // Pointer to current state function PFN Last_Steady_State; // Saved (sub) state for Steady history uint16_t timer_counter; // Number of delay ticks remaining uint16_t on_ticks; // Number of delay ticks for on state uint16_t off_ticks; // Number of delay ticks for off state volatile uint8_t *port; // Pointer to LED port uint8_t bitmask; // Bit mask for LED pin uint8_t timer_active; // Flag that timer is active } Led; // Instances of LED state machines static Led Led_1; // Led FSM for LED_1 static Led Led_2; // Led FSM for LED_2 static Led Led_3; // Led FSM for LED_3 //------------------------------------------------------------------------------ // Array to allow specification of LED with an index variable for clients // The elements should match up with the typedef enum for Led_Index in Led.h. static Led *Led_Current[] = { &Led_1, &Led_2, &Led_3 }; //------------------------------------------------------------------------------ // These variables are used to set the currently active LED and signal // for the state entry and handler functions (rather than passing them // as parameters). static Led *This_Led; // Pointer to active state machine static Led_Signal This_Signal; // Active signal for state machine The next statement is a structure typedef that is the heart of the state machine. It sets up the type "Led" and combines the variables that are part of the LED state machine. Why a structure? Because it is an enormously convenient way to collect together a set of variables that are of different types, just as array allows us to collect together several variables of the same type. Instead of an array index, we use either a dot notation or a special structure pointer notation to reference the individual members of the structure. We'll see that later. The variables are as follows: - Current_State - A function pointer that points to the current state function. Every state machine needs this variable. Recall that we defined its type PFN in System.h. - Last_Steady_State - A function pointer that saves the last (sub)state in the steady superstate. This is used to implement the "History" dot in the UML statechart. - timer_counter - The number of system ticks remaining in the software timer for this state machine (when active). - on_ticks - The number of ticks for the blink on (sub)state. Is used to initialize the timer counter at the beginning of the on part of the blink cycle. - off_ticks - As above, but for the off part of the blink cycle. - port - This is a pointer to the output port for the LED. Note that it of type volatile (uint8_t *). This type is necessary because of how C characterizes the I/O ports in the header files. We will leave it at that here. - bitmask - This is the bit mask for the LED pin on its port. - timer_active - This is a boolean flag (TRUE or FALSE) to turn the software timer on and off. Next we have three "instantiations" of the Led type, one for each LED. These are brilliantly named Led_1, Led_2, and Led_3. Whenever these appear in this module they represent the group of variables in the structure for one of the LEDs. We declare them static so that they maintain their values throughout the life of the program. Next we have an array whose three elements are the addresses of the three state machines just created. The address for Led_1 is denoted &Led_1, and so on. Note that its type is (* Led) which means the elements are pointers to variables of type Led. This array is used to allow the client module to reference a specific LED, via its address, using the index for this array. The array index variable is the parameter in the client interface functions whose prototypes we looked at in Led.h. The last two statements create the variables used to manipulate the state machine variables. The variable This_Led is a pointer to an Led structure and is part of the mechanism to specify the variables of a specific Led structure. For example, if a client function is invoked with the Led_Index variable index = LED2, then the statement This_Led = Led_Current_Led[index]; will cause This_Led to have the value &Led_2 and we can reference the structure variables with expressions such as This_Led->timer_counter The variable This_Signal is of type Led_Signal (defined in the first code segment of this part) and it will contain the current signal being sent to a state machine. We will use these last two variables to specify the currently chosen LED state machine and the current signal being sent to the state machine in the various functions used to implement the state machine. These module-scope variables could be eliminated by writing our state machine functions with with two argument parameters. I decided to do it with these variables instead to avoid the overhead that goes with passing parameters to a function. The penalty we pay for this is that we must not forget to set these two variables before we call one of these functions. In the next installment we will see how the state machines are initialized on program startup. Andy n1ksn
  8. In this part we'll look at a couple of header files that are included in our Led.c module. The first is the file System.h which contains some global defines for our program: #ifndef SYSTEM_H #define SYSTEM_H #define FALSE 0 #define TRUE 1 #define NULL 0 // Each tick approximately 15.6 ms with ACLK at 32768 Hz (1 sec = 64 ticks) #define ACLK_FREQ 32768 #define TICK_DIVISOR 512 // In this version, set to match WDT interval #define TICKS_PER_SEC (ACLK_FREQ / TICK_DIVISOR) // Port and I/O pin bitmask definitions #define LEDOUT P1OUT #define LED1_BITMASK BIT0 #define LED2_BITMASK BIT1 #define LED3_BITMASK BIT2 #define BTNIN P1IN #define BTNIE P1IE #define BTNIFG P1IFG #define BTNIES P1IES #define BTNREN P1REN #define BTNOUT P1OUT #define BTN1_BITMASK BIT3 #define BTN2_BITMASK BIT4 #define BTN3_BITMASK BIT5 typedef void( * PFN )(void); // Pointer to function (void) foo(void) void System_InitializeHW(void); #endif After some initial defines for boolean values and NULL we have defines associated with the system tick. Our Led.c state machine module will be using the TICKS_PER_SEC value. Note that the module System.c sets up the watchdog timer to provide a ~15.6 ms system tick by putting the WDT in an interval mode, so these defines must coincide with that interval. (If you use a regular timer for the system tick, these defines help set that up.) The next four defines give aliases for the port(s) to which the LEDs are connected, and the bitmasks for them. This particular demonstration uses a single port, but each LED could be wired to a different port in which case aliases would be need for these. These defines aren't really necessary, as we could use P1OUT, BIT0, etc. in our module, but I like to localize pin assignments in the System.h header and System.c module. Below these are similar defines for the pushbutton module. Near the bottom of the header is a typedef for a pointer to a function which is of the "void foo(void)" variety, with no arguments and no return value. The name of the defined type is PFN (for "pointer to function") and it is the only function pointer type we will use. This state machine implementation uses a function pointer of this type to indicate the current state of the state machine. If you are not comfortable with function pointers in C, you should get to know them. They are our friends. Now let's look at the header Led.h: #ifndef LED_H #define LED_H // Each system tick approximately 15.6 ms with ACLK at 32768 Hz (64 ticks/sec) #define LED_DEFAULT_TICKS (TICKS_PER_SEC / 2) // Default blink half period #define LED_MAX_TICKS (32 * TICKS_PER_SEC) // Max ~ 1 cycle per minute #define LED_MIN_TICKS 2 // Should be at least 2 // Called by main at start-up void Led_InitializeHW(void); void Led_InitializeApp(void); // This function is called every system tick from main to drive // software timers for the LED state machines void Led_ProcessTimerEvents(void); //------------------------------------------------------------------------------ // Variable type and values to allow client module to specify LED typedef enum { LED_1 = 0, LED_2, LED_3 } Led_Index; #define LED_MAX 3 // Must coincide with typedef above // Functions to control LEDs from client module // Put LED in steady off state void Led_TurnOff(Led_Index index); // Put LED in steady on state void Led_TurnOn(Led_Index index); // Puts LED in last steady state void Led_Steady(Led_Index index); // Puts LED in blink state void Led_Blink(Led_Index index); // Sets LED on and off tick lengths (in system ticks), // restricted to range set by LED_MAX_TICKS and LED_MIN_TICKS. // Does not change current state of LED. void Led_SetBlink(Led_Index index, uint16_t on_ticks, uint16_t off_ticks); // Resets blink period to 2 * LED_DEFAULT_TICKS with 50% duty cycle // without changing LED state. void Led_ResetBlink(Led_Index index); // Retrieves current on_tick value of LED uint16_t Led_OnTicks(Led_Index index); // Retrieves current off_tick value of LED uint16_t Led_OffTicks(Led_Index index); #endif Here we first have a few more defines which depend on the TICKS_PER_SEC value. They set the default value for an LED blink period and the minimum and maximum number of system ticks for how long an LED can be off or on when blinking. The minimum value is the most important, as too short an interval could cause trouble with our blink software timer. The other two are arbitrary values. The next three statements are function prototypes which appear in this header so that the main module can call them. (This header is included in the main module.) The first two prototypes are for initialization of module-specific hardware and software on startup, and the third will be called by main on each system tick to operate the LED software timers used when they are blinking. The remainder of the function prototypes are the client interface for this module. Whichever modules that need to operate the LEDs will do so through these function calls. Their names are fairly self-explanatory. In order for a client to identify which LED is to be affected, the functions have the parameter "index" which is of type Led_Index. This is a custom type set up by the typedef enum statement just above these function prototypes. It is a combination of typedef and enum that defines a variable which can only take on the values enumerated in the definition. In this case there are three LED index values, LED_1, etc. Why do this typedef instead of using a regular old integer? It is a way to have the compiler flag some programmer mistakes that would set the index to a value outside the valid range for the LED index. (I don't think it will catch runtime errors, however.) In any case, it is a nice way to remember which index value goes with which LED. A companion define sets LED_MAX to the number of LEDs in our module. That's enough for this part. Next we'll (finally) start to look at the C code in Led.c. If you have any comments or questions about any of these posts, please don't hesitate to write a reply to this thread. I'll eventually include a zip file will all the program files in it. Later, Andy n1ksn Added in edit: I've added another client function prototype, namely for Led_Steady. This function returns the state machine to whatever steady substate was last active.
  9. In this thread I would like to go though the pieces of code that implement a state machine to control one or more LEDs (3 in this case). It is written to be a device driver with an interface to a client module. You could argue that it is overkill for controlling LEDs, but this is intended to be an example on translating a two-level hierarchical UML statechart into C code. The complete program includes other modules, but for now I just want to concentrate on the LED state machine. First, though, why use a state machine? There are different choices for the structure of embedded software, and which one works best depends on factors like program size and complexity and ease of maintenance and modification. From most of the the books I've read on the subject (e.g., Simon's "An Embedded Software Primer"), the structures go from the simplest, a round robin with polling, to a round robin with interrupts (including some sleeping), then a task queue and scheduler, and finally a real-time operating system (RTOS). State machines, which seem to be neglected in many books, offer an alternative to task schedulers and real-time operating systems (or can be used in conjunction with them). They are a good approach when the software activity is primarily responses to stimuli from inputs. State machines allow for multiple thread operations combined with a fairly straight-forward way to translate a program specification into code. They can be implemented with large, nested switch statements, but this approach can get out of hand and be difficult to maintain. (I know this from personal experience because I took this approach a couple of years ago with a program to control a homebrew shortwave transceiver. It depended on a mess of flags and multiple switch statements and was difficult to debug.) Instead we will associate with each state a couple of C functions that perform the needed actions, parse signals to the state machine, and make the appropriate transitions. This work is an outgrowth of my study of two books. "MSP430 State Machine Programming with the ES2274" by Tom Baugh shows how to build very low power applications on the MSP430 using flat state machines. It also taught me a lot about C and MSP430 assembly instructions. "Practical UML Statecharts in C/C++" by Miro Samek shows how to use his open-source system to implement hierarchical state machines and its Chapter 2 is a crash course UML statecharts, which are part of the Unified Modeling Language (UML). The approach I will take uses a two-level hierarchical UML statechart for the specification, which I then translate into a flat state machine implemented as per Baugh. (Alternatively, one can use some of Samek's software to implement these state machines.) Here is the UML statechart specifying the state machine for the LED driver module: Each box with rounded corners in the diagram is a state. There are two higher level states representing two different modes of operation. Each of these "superstates" contains two substates which describe the behavior of the LED state machine for that mode of operation. The superstate ledStateSteady represents the LED being either off or on indefinately. The superstate ledStateBink represents the LED being in a blinking mode. The arrows represent transistions from one state to another caused by a "signal" to the state machine. For example, if the LED is in the steady off state, reception of the signal LED_ON_SIG causes a transition to the steady on state. As another example, if the LED is in either the steady off or steady on states and receives an LED_BLINK_SIG, it will transition to the blinking superstate. Note that some transition arrows start or end on a superstate, while others start or end on a substate. The black dots represent initial starting points. The dot at the very top shows that at start up the state machine goes to the steady superstate. When going into the steady superstate, the dot inside that state shows that initially the transition is into the off steady state. The dot inside the blink superstate shows that upon entering that superstate the state machine transitions to the blink on state. Inside the steady superstate is a dot with an "H" inside. This represents "history" and means that when there is a transition due to the signal LED_STEADY_SIG, the state machine returns to whichever steady substate was last active. (According to strict UML rules, this H dot should replace the solid dot inside the steady superstate, but I think this is confusing.) Finally, each state contains Entry and/or Exit actions which are taken upon entry to or exit from that state. For example, when the blink superstate is entered the state machine software timer (driven by the system tick) is activated to allow blinking. When the blink superstate is left the timer is deactivated. As another example, when the blink on substate is entered the LED is turned on and the timer counter is set for the number of system ticks it will be on. When the timer counts down to zero, a LED_TIMEOUT_SIG signal is sent to the state machine and a transition is made to the blink off state, where upon entry the LED is turned off and the timer counter is loaded with the number of system ticks it is to be off. One rule of UML statecharts is that as you transition out of a substate into another substate in a different superstate, you must execute the applicable entry and exit actions of the superstates (as well as the substates) as you progress up and then down the hierarchy. This simple example does not have any transitions to illustrate this. Well, that's it for this first post. Next time we'll quickly look at the "client interface" (the associated header file) and then begin building the C code to implement the LED state machine. Cheers, Andy n1ksn
  10. Well, I thought I was done with this thread for a while, but after posting the last demo program some improvements came to mind. First, I added an alternate function capability to the pushbuttons. This means that any pushbutton can be set up to be: NORMAL: One function, activated on push and release. ALT_FN: Two functions, one activated on a short push and release, the second activated on a long (1 sec) push and release. ACCEL: One function, activated once on a short push and release, activated repeatedly during a long push. OFF: Does nothing. I also cleaned up the state handler functions, especially how the pushbutton interval timers are turned on and off. So let's have a little wrap-up now. Why did I do all this? I've been looking for a general approach to micro software organization that could have multiple threads of execution but not require a real time operating system (RTOS). The only RTOS I've ever been able (or willing) to get working was 4AVROS for the Atmel AVRs. (Nice program, by the way.) But for the small chips I usually work with an RTOS is overkill, and there can be a stiff learning curve and overhead when using them. On the other hand, for moderately complicated projects a round-robin or nested switch statement approach gets messy and difficult to debug or modify. At this time it appears to me that Baugh's approach is the way to go. One can have multiple threads without much, if any, overhead. I pulled in the flat state machine routines of Samek in these demos to make it easier to set up and follow the state machine operations, but by doing this one looses the minimal current draw found in Baugh's programs. So my strategy for future projects will be to use the structure found in these demos unless the micro's current draw is an issue (for example if the project is to operate for long periods on a coin cell). A side benefit to this work has been the deepening of my C knowledge from reading Baugh and Samek. Although Baugh's book is for C, he looks at the assembly code generated by the C code to determine how best to write things. In other words, know your compiler and what it actually does. Baugh and Samek also make liberal use of structures, and before reading them I didn't appreciate just how powerful a tool they can be. Samek shows how to organize and use them as a substitute for C++ classes. For example, each pushbutton state machine is represented by an instance of a structure derived from a base state machine structure, like class inheritance. By doing this in C you get the benefits of some object orientation without the overhead of C++. Cheers, Andy (Revised 10/13/2011) StateMachineBlink3.zip Here are two UML (unified modeling language) statechart diagrams of the state machines in the code. The rounded boxes are the states. They show the entry and exit actions, plus any additional "Do" actions. The diamonds represent pseudostates for decisions. The "guard" conditions controlling these these are shown in brackets. The arrows leaving states are labelled with the signal that causes the transition. I drew these diagrams with this cool little free program called "UMLet" that I found on the web. It is easy to use and produces decent diagrams for statecharts and other UML tools. You might want to check it out.
  11. Attached is a revision of my recently posted project. I added an alternate function capability to the pushbutton code and cleaned up the state handler functions and pushbutton timer handling. Sorry for any inconvenience. I thought I was done with this, but I felt compelled to clean up the code. Andy cStateMachineBlink3.zip
  12. *** Note: An updated version of this port is posted below on this thread. *** I've been posting a series of demo programs on state machines in the state machine thread started by zeke in the General forum. Yesterday I ported the latest version StateMachineBlink2 to an Atmel ATmega168 on a small custom demo board (or the STK500). I posted it as a project in AVR Freaks, but the zip file of the code is below for your convenience. The port was fairly straightforward from the MSP430G2452. There were the usual modifications going from CCS to WinAVR GCC, plus I had to complement the LED outputs since my board uses the AVR pins as sinks for the LEDs. Also, the hardware has external pushbutton pullups rather than using the internal ones. I continued to use the watchdog timer as the system tick generator, and it took a bit of digging in the device datasheet and the libc user's manual to get that set up. Also, this was the first time I used a sleep mode with an AVR, so that took some time to figure out. Finally, the biggest change was understanding how the port pin interrupt registers are organized on an AVR. In my opinion, the MSP430 does it better, or at least more naturally. The code should be OK for other AVRs with small changes to register, interrupt vector, and pin names. With -Os optimization the code was just about 2K in size, with under 100 bytes of RAM used. Later, Andy cStateMachineBlink2.zip
  13. The MOSFET connections on the new layout in the post now look correct to me. To quote Mr. Burns--"Excellent." As of this moment, the zip file is still incorrect, but get some sleep! Andy
  14. Ooooh, gordon. Very nice. Very nice. I got a private message drawing my attention to this. It's a great motivation to learn how to do the PCB thing. I took a quick look at the schematic vs the pcb layout, and I think there is a problem with the connections to the 2N7000 keying MOSFET. As drawn, the source goes to the TX jack, the gate is grounded, and the drain goes to the MSP. At least that is what it looks like. It should have the gate (center pin) go to the MSP, the source to ground, and the drain to the TX jack. Let me know what you think. The drawing is the only file I can access. What software is need to work with these files? Thanks again, Andy
  15. *** Note: There is an upgraded version of this code in a post below in this thread. *** This is the final installment in this series of demo programs for using a state machine approach. This version continues to blink three LEDs, but I've added three pushbuttons to the setup. Each LED and each pushbutton is managed by a separate state machine. Both types of state machine use the same base functions and structures found in StateMachine.h and StateMachine.c that were used in the last version. I've set things up so that each LED blinks with a period of one second, but the duty cycle can be modified either up or down using the pushbuttons as follows: PB1 increases the LED duty cycle by a factor of 2 for each push, with a maximum of 50%. PB2 decreases the LED duty cycle by a factor of 1/2 for each push, with a minimum of 3.125%. PB3 selects which LED is active for modification by PB1 and PB2 by rotating through them, 1, 2, 3, 1, 2, 3, .... PB1 and PB2 have an acceleration function. If pushed for 1 second they slew though their associated function 4 times a second. To keep things relatively simple in this demo, there is no user indication of which LED is active. The pushbutton module is not hard-wired to the LED blink module. The LED blink module must "register" an "event sink" function with each pushbutton it wishes to use. It also specifies if the acceleration feature is wanted. This allows the pushbutton module to be used by more than one client module. Pushbutton functionality can be changed on the fly. This approach to pushbutton management is found in Tom Baugh's book on state machine programming. The overall organization of the program also comes from his book, although I have made no efforts to minimize current use other than put the program into LPM3 between ticks. (Baugh is a master of low current design and I highly recommend his book.) However, the state machine implementation is more formalized than Baugh's approach. The code in StateMachine.c and StateMachine.h is modified from Miro Samek's book "Practical UML Statecharts in C/C++" so I've included GNU Public License Version 2 notices in those files. Any software derived from this code must retain these notices, or at least that is my understanding of the legal mumbo-jumbo. In this version the system ticks are now provided by the watchdog timer instead of Channel 0 on TimerA, and the system tick interval is set to about 16 ms (corresponding to a tick divisor of 512 when using a 32768 Hz clock crystal). The state machine timers have been moved out of the state handler functions and now reside in the timer event processor functions. Thus the state handler functions are no longer called at each system tick, but only upon timeout events. The timers for the LEDs run continuously, but the timers for the pushbuttons do not, so a timer_active flag was added for them. Also, the pushbutton state machine uses the timer for both debouncing and timing event sink execution in acceration mode. Another difference between the LED and PB state machines is that a port pin interrupt for a pushbutton generates an event from the port pin ISR. Thus, events for the state machine are generated in more than one place. The full code plus selected comments are shown below. With the changes made to this version, one should be able to run it on the MSP430G2211 or similar chips by changing only the device header include statements. I have not tested compilation under IAR Kickstart, but I see no reason for this to be a problem, as only standard intrinsic functions are used. Wrapup: Using state machines is an alternative to using a real time operating system to manage separate execution threads in a program. These demo programs illustrate the basic use of state machines to independently blink three LEDs and manage pushbutton functions. (If code size or current draw are big issues the "naked" state machine approach of Baugh is preferable, perhaps at the cost of some code readability.) Learning some of this material has also increased my knowledge of the C language, especially when it comes to structures and function pointers. Note that these state machines are non-hierarchical. Greater flexibility would be obtained using hierarchical state machines, but these remain a future study topic for me. I hope some of you will find these programs as interesting as I did. Andy StateMachineBlink2.zip //------------------------------------------------------------------------------ // main.c in project StateMachineBlink2 // // The overall structure of this program follows the approach of Tom Baugh // found in "MSP430 State Machine Programming." // // This program was written to explore the use of non-hierarchical state // machines as a way of organizing a program. The state machine structures // and functions used here are based on the QEP FSM processor found in Chapter // 3 of "Practical UML Statecharts in C/C++" by Miro Samek. // // The modules in this program: // // Main: General shell program for initialization and execution of the // system tick loop. Program sleeps in LPM3 when not executing a // tick. This version uses the watchdog timer to generate system // ticks, leaving TimerA free for other modules. // // System: Does general low-level hardware initialization and contains // the WDT ISR. // // StateMachine: Contains base state machine and event structures and // performs base state machine functions. This code is // modified from code copyrighted by Miro Samek under the // GNU Public License Version 2. This code is free for any // use or distribution provided that the copywrite notice // at the beginning of the module files is retained. // // Blink: Sets up and runs state machines that blink three LEDs. // // PushButton: Sets up and runs state machines that control pushbutton // functions, including debouncing and an optional acceleration // feature. The functions actually executed on a push are // determined by "event sink registration functions" called by // the client, which in this case is the Blink module. // // See the individual module headers for further descriptions. // // This program is written for the MSP430G2452, but should be easily changed // to the MSP430G2211 or similar with 2K or more of flash memory by modifying // the device header file include statements. Note that a clock crystal of // 32768 Hz is used to allow LPM3 sleep between ticks. Also, this program was // compiled and tested using CCS Workbench, with the compiler set to level 4 // optimiazation and minimum size. It should compile under IAR Kickstart with // no modifications. // // Andrew Palm // 2011.10.04 //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // Blink.c in project StateMachineBlink2 // // This module blinks three LEDs at different duty cycles using state machines. // // This version adds three pushbuttons with the following functions: // PB1: Increases duty cycle of the active LED // PB2: Decreases duty cycle of the active LED // PB3: Selects the active LED. Rotates through the three LEDs in the // order 1, 2, 3, 1, 2, 3,.... // // To simplify this demo, all three LEDs have the same period of one // second--only the duty cycles can be changed by the pushbuttons. And there // is no indication of which LED is currently active based on PB3 pushes. // // PB1 and PB2 are set up to have an acceleration feature. Holding the button // down for one second causes the assocated action to occur four times a // second. This feature is selectable using the event sink registration // function for each pushbutton. // // There is one "state handler function" (SHF) for each state. Each SHF // contains a switch statement with cases based on the event signals received. // There are special cases for state entry and state exit functions, which are // automatically invoked by the state machine dispatch function when a state // transition occurs. // // There are two states in this LED blinking state machine, LED_IsOn and // LED_IsOff. // // There is only one type of user event in this state machine, a delay timer // timeout event. Blink_ProcessEvents is called by main upon wake-up // from a timer interrupt, decrements the timer counters, and issues timeout // events at the end of the delays. // // The state handler functions in this state machine set up these timers // which determine how long a state is held. On entry the time counter is // set for the delay interval. When the counter times out there is a // transition to the new state. // // Andrew Palm // 2011.10.04 //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // PushButton.c in project StateMachineBlink2 // // This module uses state machines to debounce three pushbuttons. The state // machines use the same machinery as the module Blink.c. Events are sent to // the state machine dispatch function by either the timer tick generator or // the port ISR. The port ISR is used to detect the initial push. The timer // is used for both debouncing and the accelerated mode interval. // // The function of each pushbutton is controlled by the client application // Blink.c, which upon initialization "registers" with each pushbutton the // address of a function to execute when a push is confirmed. This function // is called an "event sink." This method is based on the approach found in // "MSP430 State Machine Programming" by Tom Baugh. // // The pushbutton state machine has four states: // - IsIdle: Wait for a push detected by port pin ISR // - PushDetected: Wait for the debounce delay, check pin status, execute // registered event sink if button still pushed // - PushConfirmed: Wait for button release or extended push. If released, // return to idle. If extended push, enter accelerated // mode. // - Accelerated: Execute event sink at specified interval until release. // // The client module sets whether acceleration is used by setting the // accel_mode variable to FALSE in the registration call for that button. // In this demonstration program, the pushbuttons are set up with PB1 and PB2 // accelerated and PB3 not accelerated. // // See Blink.c for the PB functions in this demo. // // Andrew Palm // 2011.10.04 //------------------------------------------------------------------------------
×
×
  • Create New...