|
|
@@ -0,0 +1,2270 @@
|
|
|
+/**
|
|
|
+\page tutorial_page QP/C Tutorial
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+This Tutorial presents an example project implemented entirely with the QP/C
|
|
|
+event-driven platform using UML state machines and the event-driven paradigm.
|
|
|
+The example application is an interactive "Fly 'n' Shoot"-type game. My aim in
|
|
|
+this section is to show the essential elements of the method in a real,
|
|
|
+nontrivial program, but without getting bogged down in details, rules, and
|
|
|
+exceptions. At this point, I am not trying to be complete or even precise,
|
|
|
+although this example is meant to show a good design and the recommended
|
|
|
+coding style. I don't assume that you know much about UML state machines, the
|
|
|
+UML notation, or the event-driven programming. I will either briefly introduce
|
|
|
+the concepts, as needed, or refer you to the the \ref PSiCC2 book for more
|
|
|
+details. The example "Fly 'n' Shoot" game is based on the "Quickstart"
|
|
|
+application provided in source code with the ARM Cortex-M3 LM3S811 evaluation kit
|
|
|
+(see \ref F2s2 "Figure 2-2") from Luminary Micro
|
|
|
+(http://www.luminarymicro.com). I was trying to make the "Fly 'n' Shoot"
|
|
|
+example behave quite similarly to the original Luminary Micro "Quickstart"
|
|
|
+application, so that you can directly compare the event-driven approach with
|
|
|
+the traditional solution to essentially the same problem specification.
|
|
|
+
|
|
|
+- \subpage installing
|
|
|
+- \subpage lets_play
|
|
|
+- \subpage main_function
|
|
|
+- \subpage design
|
|
|
+- \subpage active_objects
|
|
|
+- \subpage events
|
|
|
+- \subpage coding_hsm
|
|
|
+- \subpage execution
|
|
|
+- \subpage tracing
|
|
|
+- \subpage comparison
|
|
|
+- \subpage summary
|
|
|
+
|
|
|
+\note The <A HREF="http://state-machine.com/downloads"><B>standard QP/C
|
|
|
+distribution</B></A> contains two versions of the game. A <B>DOS version</B>
|
|
|
+is provided for the standard Windows-based PC so that you don't need any
|
|
|
+special embedded board to play the game and experiment with the code.
|
|
|
+Also provided is an <B>embedded version</B> for the inexpensive ARM
|
|
|
+Corterx-M3-based LM3S811 evaluation kit from Luminary Micro. Both the PC and
|
|
|
+the ARM-Cortex versions use the exact <B>same</B> source code for all application
|
|
|
+components and differ only in the Board Support Package (BSP).
|
|
|
+
|
|
|
+
|
|
|
+Next: \ref installing
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page installing 1. Installing QP/C and Building QP Libraries and Applications
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref tutorial_page \n
|
|
|
+Next: \ref lets_play
|
|
|
+
|
|
|
+QP/C is distributed in a simple platform-independent ZIP file, or in a
|
|
|
+self-extracting Windows executable. Either way, installing QP requires simply
|
|
|
+decompressing the provided archive into a directory of your choice (e.g., \c
|
|
|
+<qpc> for QP/C). The Section \ref files_page describes the directories
|
|
|
+and files included in the standard QP/C distribution.
|
|
|
+
|
|
|
+Specifically to the "Fly 'n' Shoot" example, the companion code contains two
|
|
|
+versions of the game. I provide a DOS version for the standard Windows-based
|
|
|
+PC (see \ref F2s1 "Figure 2-1") so that you don't need any special embedded
|
|
|
+board to play the game and experiment with the code.
|
|
|
+
|
|
|
+\note I've chosen the legacy 16-bit DOS platform because it allows programming
|
|
|
+a standard PC at the bare-metal level. Without leaving your desktop, you can
|
|
|
+work with interrupts, directly manipulate CPU registers, and directly access
|
|
|
+the I/O space. No other modern 32-bit development environment for the standard
|
|
|
+PC allows this much so easily. The ubiquitous PC running under DOS (or a DOS
|
|
|
+console within any variant of Windows) is as close as it gets to emulate
|
|
|
+embedded software development on the commodity 80x86 hardware. Additionally,
|
|
|
+you can use free, mature tools, such as the Open Watcom C/C++ compiler.
|
|
|
+
|
|
|
+I also provide an embedded version for the inexpensive ARM Corterx-M3-based
|
|
|
+ARM Cortex-M3 LM3S811 evaluation kit from Luminary Micro (see \ref F2s2 "Figure 2-2").
|
|
|
+Both the PC and ARM-Cortex versions use the exact same source code for
|
|
|
+all application components and differ only in the Board Support Package (BSP).
|
|
|
+
|
|
|
+
|
|
|
+\section building_lib 1.1 Building QP Libraries
|
|
|
+
|
|
|
+\note The pre-compiled QP libraries are provided in the standard QP
|
|
|
+distribution (see \ref files_page), so you can start experimenting with all
|
|
|
+examples without building the QP libraries. However, if you want to re-build
|
|
|
+the QP libraries, this section provides the details.
|
|
|
+
|
|
|
+\ref F1s1 "Figure 1-1" illustrates the steps required to build the QF library.
|
|
|
+The process of building other QP components, such as QEP or QK, is essentially
|
|
|
+identical. The key point of the design is that all platform-independent QF
|
|
|
+source files include the same \c qf_port.h header file as the application
|
|
|
+source files (see \ref F1s1 "Figure 1-1"). At this point you can clearly see
|
|
|
+that the Platfrom Abstraction Layer (PAL) provided in QP plays the dual role
|
|
|
+of facilitating the porting of QP as well as using it in the applications.
|
|
|
+\ref F1s1 "Figure 1-1" shows also that every QP component, such as QF, can
|
|
|
+contain a platform-specific source file (\c qf_port.c in this case). The
|
|
|
+platform-specific source file is optional and many ports don't require it.
|
|
|
+
|
|
|
+\anchor F1s1
|
|
|
+\image html Fig8.02.jpg "Figure 1-1 Building the QF library."
|
|
|
+
|
|
|
+The standard QP ports often contain a simple \c make.bat script or a
|
|
|
+\c Makefile for building all the QP libraries for the port. You typically can
|
|
|
+choose the build configuration by providing a target to the \c make.bat script
|
|
|
+or to the \c Makefile. The default target is "dbg". Other possible targets are
|
|
|
+"rel", and "spy". The following table summarizes the targets accepted by the
|
|
|
+\c make.bat scripts or the \c Makefiles.
|
|
|
+
|
|
|
+<TABLE SUMMARY="Build Targets" cellSpacing=4 cellPadding=1 border=0
|
|
|
+ALIGN="center" VALIGN="middle">
|
|
|
+ <TR bgColor="#c8cedc">
|
|
|
+ <TD><B> Build Configuration</B></TD>
|
|
|
+ <TD><B> Build Command</B></TD>
|
|
|
+ </TR>
|
|
|
+
|
|
|
+ <TR bgColor="#ffffcc">
|
|
|
+ <TD> Debug </TD>
|
|
|
+ <TD>make</TD>
|
|
|
+ </TR>
|
|
|
+
|
|
|
+ <TR bgColor="#ffffdd">
|
|
|
+ <TD> Release </TD>
|
|
|
+ <TD>make rel</TD>
|
|
|
+ </TR>
|
|
|
+
|
|
|
+ <TR bgColor="#ffffcc">
|
|
|
+ <TD> Spy </TD>
|
|
|
+ <TD>make spy</TD>
|
|
|
+ </TR>
|
|
|
+</TABLE>
|
|
|
+
|
|
|
+\note All QP components are designed to be deployed in fine-granularity object
|
|
|
+libraries. QP libraries allow the linker to eliminate any unreferenced QP code
|
|
|
+at link time, which results in automatic scaling of every QP component for a
|
|
|
+wide range of applications. This approach eliminates the need to manually
|
|
|
+configure and recompile the QP source code for each application at hand.
|
|
|
+
|
|
|
+\section building_app 1.2 Building QP Applications
|
|
|
+
|
|
|
+\note The standard QP distribution contains pre-compiled examples (see \ref
|
|
|
+files_page), so you can start experimenting with all examples without building
|
|
|
+them. However, if you want to re-build the QP examples, this section provides
|
|
|
+the details.
|
|
|
+
|
|
|
+\ref F1s2 "Figure 1-2" shows the process of building a QP application. Each QP
|
|
|
+component requires inclusion of only one platform-specific header file and
|
|
|
+linking one platform-specific library. For example, to use the QF real-time
|
|
|
+framework, you need to include the \c qf_port.h header file and you need to
|
|
|
+link the \c qf.lib library file from the specific QP port directory. It really
|
|
|
+doesn't get any simpler than that.
|
|
|
+
|
|
|
+\anchor F1s2
|
|
|
+\image html Fig8.01.jpg "Figure 1-2 Building a QP-based Application."
|
|
|
+
|
|
|
+The QP port you are using is determined by the directory branch in which the
|
|
|
+\c qf_port.h header file and the QF library file are located. Section
|
|
|
+\ref files_page shows some examples of such port directories. Typically you
|
|
|
+need to instruct the C/C++ compiler to include header files from the specific
|
|
|
+QP port directory and also from the platform-independent include directory
|
|
|
+\c <qpc>\\include\\. I strongly discourage hard-coding full path-names
|
|
|
+of the include files in your source code. You should simply include the QP
|
|
|
+port header file (<TT>#include "qf_port.h"</TT>) without any path. Then you
|
|
|
+specify to the compiler to search the QP port directory for include files,
|
|
|
+typically through the <TT>-I</TT> option.
|
|
|
+
|
|
|
+Prev: \ref tutorial_page \n
|
|
|
+Next: \ref lets_play
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page lets_play 2. Let's Play
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref installing \n
|
|
|
+Next: \ref main_function
|
|
|
+
|
|
|
+The following description of the "Fly 'n' Shoot" game serves the dual purpose
|
|
|
+of explaining how to play the game and as the problem specification for the
|
|
|
+purpose of designing and implementing the software later in this Tutorial. To
|
|
|
+accomplish these two goals I need to be quite detailed, so please bear with
|
|
|
+me.
|
|
|
+
|
|
|
+Your objective in the game is to navigate a space ship through an endless
|
|
|
+horizontal tunnel with mines. Any collision with the tunnel or the mine
|
|
|
+destroys the ship. You can move the ship up and down with UP-arrow and
|
|
|
+DOWN-arrow keys on the PC (see \ref F2s1 "Figure 2-1") or the potentiometer
|
|
|
+wheel on the LM3S811 board (see \ref F2s2 "Figure 2-2"). You can also fire a
|
|
|
+missile to destroy the mines in the tunnel by pressing the SPACE-bar on the PC
|
|
|
+or the User button on the LM3S811 board. Score accumulates for survival (at
|
|
|
+the rate of 30 points per second) and destroying the mines.
|
|
|
+
|
|
|
+The game lasts for only one ship. The game starts in a demo mode, where the
|
|
|
+tunnel walls scroll at the normal pace from right to left and the "Press
|
|
|
+Button" text flashes in the middle of the screen. You need to generate the
|
|
|
+"fire missile" event for the game to begin (press SPACE-bar on the PC and the
|
|
|
+User Button on the LM3S811 board). You can have only one missile in flight at
|
|
|
+a time, so trying to fire a missile while it is already flying has no effect.
|
|
|
+Hitting the tunnel wall with the missile brings you no points, but you earn
|
|
|
+extra points for destroying the mines.
|
|
|
+
|
|
|
+The game has two types of mines with different behavior. In the original
|
|
|
+Luminary "Quickstart" application both types of mines behave the same, but I
|
|
|
+wanted to demonstrate how state machines can elegantly handle differently
|
|
|
+behaving mines.
|
|
|
+
|
|
|
+Mine type-1 is small, but can be destroyed by hitting any of its pixels with
|
|
|
+the missile. You earn 25 points for destroying a mine type- Mine type-2 is
|
|
|
+bigger, but is nastier in that the missile can destroy it only by hitting its
|
|
|
+center, not any of the "tentacles". Of course, the ship is vulnerable to the
|
|
|
+whole mine. You earn 45 points for destroying a mine type 2.
|
|
|
+
|
|
|
+When your crash the ship, either by hitting a wall or a mine, the game ends
|
|
|
+and displays the flashing "Game Over" text as well as your final score. After
|
|
|
+5 seconds of flashing, the "Game Over" screen changes back to the demo screen,
|
|
|
+where the game waits to be started again.
|
|
|
+
|
|
|
+Additionally the application contains a screen saver because the OLED display
|
|
|
+of the original LM3S811 board has burn-in characteristics similar to a CRT.
|
|
|
+The screen saver only becomes active if 20 seconds elapse in the demo mode
|
|
|
+without starting the game (i.e., the screen saver never appears during game
|
|
|
+play). The screen saver is a simple random-pixel-type, rather than the "Game
|
|
|
+of Life" algorithm used in the original Luminary "Quickstart" application.
|
|
|
+I've decided to simplify this aspect of the implementation, because the more
|
|
|
+elaborate pixel-mixing algorithm does not contribute any new or interesting
|
|
|
+behavior. After a minute of running the screen saver, the display turns blank
|
|
|
+and only a single random pixel shows on the screen. Again, this is a little
|
|
|
+difference from the original "Quickstart" application, which instead blanks
|
|
|
+the screen and starts flashing the User LED. I've changed this behavior
|
|
|
+because I have a better purpose for the User LED (to visualize the activity of
|
|
|
+the idle loop).
|
|
|
+
|
|
|
+\section DOS Running the DOS Version
|
|
|
+
|
|
|
+The "Fly 'n' Shoot" sample code for the DOS version (in C++) is located in
|
|
|
+\c <qpc>\\examples\\80x86\\dos\\watcom\\l\\game\\, directory, where
|
|
|
+\c <qpc> stands for the installation directory you chose to install the
|
|
|
+QP/C software.
|
|
|
+
|
|
|
+The compiled executable is provided, so you can run the game on any
|
|
|
+Windows-based PC by simply double-clicking on the executable game.exe located
|
|
|
+in the directory \c
|
|
|
+<qpc>\\examples\\80x86\\dos\\watcom\\l\\game\\dbg\\.
|
|
|
+
|
|
|
+\anchor F2s1
|
|
|
+\image html Fig1.01.jpg "Figure 2-1 The Fly 'n' Shoot game running in a DOS window under Windows XP."
|
|
|
+
|
|
|
+The first screen you see is the game running in the demo mode with the text
|
|
|
+"Push Button" flashing in the middle of the display. At the top of the display
|
|
|
+you see a legend of keystrokes recognized by the application. You need to hit
|
|
|
+the SPACE key to start playing the game. Please press the ESC key to cleanly
|
|
|
+exit the application.
|
|
|
+
|
|
|
+If you run "Fly 'n' Shoot" in a window under Microsoft Windows, the animation
|
|
|
+effects in the game might appear a little jumpy, especially when compared to
|
|
|
+the ARM-Cortex version of the same game. You can make the application execute
|
|
|
+significantly smoother if you switch to the full-screen mode by pressing and
|
|
|
+holding the Alt key and then pressing the Enter key. You go back to the window
|
|
|
+mode by the same Alt-Enter key combination.
|
|
|
+
|
|
|
+As you can see in \ref F2s1 "Figure 2-1", the DOS version uses simply the
|
|
|
+standard VGA text mode to emulate the OLED display of the LM3S811 board. The
|
|
|
+lower part of the DOS screen is used as a matrix of 80x16 character-wide
|
|
|
+"pixels", which is a little less than the 96x16 pixels of the OLED display,
|
|
|
+but is still good enough to play the game. I specifically avoid employing any
|
|
|
+fancier graphics in this early example because I have a bigger fish to fry for
|
|
|
+you than to worry about the irrelevant complexities of programming graphics.
|
|
|
+My main goal is to make it easy for you to understand the event-driven code
|
|
|
+and experiment with it.
|
|
|
+
|
|
|
+To this end, I chose the <STRONG>Open Watcom</STRONG> toolset to build this
|
|
|
+example as well as several other examples in this book. Open Watcom it is
|
|
|
+available under a OSI-certified <STRONG>open source</STRONG> license that
|
|
|
+permits free commercial and non-commercial use. You can download Open Watcom
|
|
|
+C/C++ toolset for DOS from
|
|
|
+<a href="ftp://ftp.openwatcom.org/">ftp://ftp.openwatcom.org/</a>. Please
|
|
|
+select the \c open-watcom-c-dos-1.8.exe installer. Ready to print documentation
|
|
|
+in PDF format is also available from
|
|
|
+<a href="http://www.openwatcom.org/index.php/Manuals">
|
|
|
+http://www.openwatcom.org/index.php/Manuals</a>.
|
|
|
+
|
|
|
+The Open Watcom C/C++ toolset for DOS is distributed as a Windows installer.
|
|
|
+After you download the \c open-watcom-c-dos-1.8.exe file, please launch the
|
|
|
+installer and follow the instructions it provides.
|
|
|
+
|
|
|
+\note I strongly recommend that you install the Open Watcom toolset into the
|
|
|
+directory \c C:\\tools\\watcom\\. That way, you will be able to use directly
|
|
|
+the provided make scripts. If you choose to install Open Watcom into a different
|
|
|
+location, you can still use the make scripts supplied with the QP distribution,
|
|
|
+but you need to define the \c WATCOM environment variable. You should
|
|
|
+<STRONG>not</STRONG> install Open Watcom in the standard "Prgram Files"
|
|
|
+directory or any directory name with a space.
|
|
|
+
|
|
|
+To experinment with the "Fly 'n' Shoot" game code you can use any code editor
|
|
|
+to modify the source code. Then you re-build the application by means of the
|
|
|
+supplied \c make.bat script, which is located in the directory \c
|
|
|
+<qpc>\\examples\\80x86\\dos\\watcom\\l\\game\\.
|
|
|
+
|
|
|
+In the next section, I describe briefly how to run the embedded version of the
|
|
|
+game. If you are not interested in the ARM-Cortex version, please feel free to
|
|
|
+skip to the following Section \ref main_function, where I start explaining the
|
|
|
+application code.
|
|
|
+
|
|
|
+
|
|
|
+\section Cortex Running the ARM-Cortex Version
|
|
|
+
|
|
|
+In contrast to the "Fly 'n' Shoot" version for DOS running in the ancient real
|
|
|
+mode of the 80x86 processor, the exact same source code runs on one of the
|
|
|
+most modern processors in the industry: the ARM-Cortex.
|
|
|
+
|
|
|
+\anchor F2s2
|
|
|
+\image html Fig1.02.jpg "Figure 2-2 The Fly 'n' Shoot game running on the ARM Cortex-M3 LM3S811 evaluation board."
|
|
|
+
|
|
|
+The sample code for the ARM Cortex-M3 LM3S811 board is located in \c
|
|
|
+<qpc>\\examples\\arm-cortex\\vanilla\\iar\\game-ev-lm3s811\\ directory,
|
|
|
+where \c <qpc> stands for the root directory you chose to install the
|
|
|
+accompanying software. The code for the ARM-Cortex kit has been compiled with
|
|
|
+the 32KB-limited Kickstart edition of the <STRONG>IAR Embedded Workbench for
|
|
|
+ARM</STRONG> (IAR EWARM) v 5.11, which is provided with the ARM Cortex-M3
|
|
|
+EKI-LM3S811 kit. You can also download this software <STRONG>free</STRONG> of
|
|
|
+charge directly from IAR Systems (http://www.iar.com), after filling out an
|
|
|
+online registration.
|
|
|
+
|
|
|
+The installation of IAR EWARM is quite straightforward, as the software comes
|
|
|
+with the installation utility. You also need to install the USB drivers for
|
|
|
+the hardware debugger built into the LM3S811 board, as described in the
|
|
|
+documentation of the ARM Cortex-M3 LM3S811 kit.
|
|
|
+
|
|
|
+\note I strongly recommend that you install the IAR EWARM toolset into the
|
|
|
+directory \c C:\\tools\\iar\\arm_ks_5.11 That way, you will be able to use
|
|
|
+directly the provided EWARM workspace files and make scripts.
|
|
|
+
|
|
|
+Before you program the "Fly 'n' Shoot" game to the LM3S811 board, you might
|
|
|
+want to play a little with the original "Quickstart" application that comes
|
|
|
+pre-programmed with the LM3S811 kit.
|
|
|
+
|
|
|
+To program the "Fly 'n' Shoot" game to the flash memory of the LM3S811 board,
|
|
|
+you first connect the LM3S811 board to your PC with the USB cable provided in
|
|
|
+the kit and make sure that the Power LED is on (see \ref F2s2 "Figure 2-2").
|
|
|
+Next, you need to launch the IAR Embedded Workbench and open the workspace
|
|
|
+game-ev-lm3s81eww located in \c
|
|
|
+<qpc>\\examples\\arm-cortex\\vanilla\\iar\\game-ev-lm3s811\\ directory.
|
|
|
+At this point your screen should look similar to the screenshot shown in
|
|
|
+\ref F2s3 "Figure 2-3". The game-ev-lm3s811 project is set up to use the LMI FTDI
|
|
|
+debugger, which is the piece of hardware integrated on the LM3S811 board (see
|
|
|
+\ref F2s2 "Figure 2-2"). You can verify this setup by opening the "Options"
|
|
|
+dialog box via the Project->Options menu. Within the "Options" dialog box, you
|
|
|
+need to select the Debugger category in the panel on the left. While you are
|
|
|
+at it, you could also verify that the flash loading is enabled by selecting
|
|
|
+the "Download" tab. The checked "Use flash loader(s)" checkbox means that the
|
|
|
+flash loader application provided by IAR will be first loaded to the RAM of
|
|
|
+the MCU, and this application will program the flash with the image of your
|
|
|
+application. To start the flash programming process, select the Project->Debug
|
|
|
+menu, or simply click on the Debug button (see \ref F2s3 "Figure 2-3") in the
|
|
|
+toolbar. The IAR Workbench should respond by showing the flash programming
|
|
|
+progress bar for several seconds, as shown in \ref F2s3 "Figure 2-3". Once the
|
|
|
+flash programming completes, the IAR EWARM switches to the IAR C-Spy debugger
|
|
|
+and the program should stop at the entry to main(). You can start playing the
|
|
|
+game either by hitting the "Go" button in the debugger, or you can close the
|
|
|
+debugger and reset the board by pressing the Reset button. Either way, "Fly
|
|
|
+'n' Shoot" game is now permanently programmed into the LM3S811 board and will
|
|
|
+start automatically upon every power up.
|
|
|
+
|
|
|
+\anchor F2s3
|
|
|
+\image html Fig1.03.jpg "Figure 2-3 Loading the Fly 'n' Shoot game into the flash of LM3S811 MCU with IAR EWARM IDE"
|
|
|
+
|
|
|
+The IAR Embedded Workbench environment allows you to experiment with the "Fly
|
|
|
+'n' Shoot" code very easily. You can edit the files and recompile the
|
|
|
+application at a click of a button (F7). The only caveat is that the first
|
|
|
+time after the installation of the IAR toolset you need to build the Luminary
|
|
|
+Micro driver library for the LM3S811 MCU from the sources. You accomplish this
|
|
|
+by loading the workspace ek-lm3s81eww located in the directory
|
|
|
+\c <IAR-EWARM>\\ARM\\examples\\Luminary\\Cortex-M3\\boards\\ek-lm3s811,
|
|
|
+where <IAR-EWARM> stands for the directory name where you've installed
|
|
|
+the IAR toolset. In the ev-lm3s81eww workspace, you select the "driverlib -
|
|
|
+Debug" project from the drop-down list at the top of the Workspace panel, and
|
|
|
+then press F7 to build the library.
|
|
|
+
|
|
|
+Prev: \ref installing \n
|
|
|
+Next: \ref main_function
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page main_function 3. The main() Function
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref lets_play \n
|
|
|
+Next: \ref design
|
|
|
+
|
|
|
+Perhaps the best place to start the explanation of the "Fly 'n' Shoot"
|
|
|
+application code is the main() function, located in the file \c main.c. Unless
|
|
|
+indicated otherwise in this Tutorial, you can browse the code either in the
|
|
|
+DOS version, or the ARM-Cortex version, because the application source code is
|
|
|
+identical in both. The complete \c main.c file is shown in
|
|
|
+\ref L3s1 "Listing 3-1"
|
|
|
+
|
|
|
+\note To explain code listings, I place numbers in parentheses at the
|
|
|
+interesting lines in the left margin of the listing. I then use these labels
|
|
|
+in the left margin of the explanation section that immediately follows the
|
|
|
+listing. Occasionally, to unambiguously refer to a line of a particular
|
|
|
+listing from sections of text other than the explanation section, I use the
|
|
|
+full reference consisting of the listing number followed by the label. For
|
|
|
+example, \ref L3s1 "Listing 3-1"(21) refers to the label (21) in
|
|
|
+\ref L3s1 "Listing 3-1"
|
|
|
+
|
|
|
+\anchor L3s1
|
|
|
+<STRONG>Listing 3-1 The file main.c of the "Fly 'n' Shoot" game application.
|
|
|
+</STRONG>
|
|
|
+\code
|
|
|
+ (1) #include "qp_port.h" /* the QP port */
|
|
|
+ (2) #include "bsp.h" /* Board Support Package */
|
|
|
+ (3) #include "game.h" /* this application */
|
|
|
+
|
|
|
+ /* Local-scope objects -----------------------------------------------------*/
|
|
|
+ (4) static QEvent const * l_missileQueueSto[2]; /* event queue */
|
|
|
+ (5) static QEvent const * l_shipQueueSto[3]; /* event queue */
|
|
|
+ (6) static QEvent const * l_tunnelQueueSto[GAME_MINES_MAX + 5]; /* event queue */
|
|
|
+ (7) static ObjectPosEvt l_smlPoolSto[GAME_MINES_MAX + 8]; /* small-size pool */
|
|
|
+ (8) static ObjectImageEvt l_medPoolSto[GAME_MINES_MAX + 8]; /* medium-size pool */
|
|
|
+ (9) static QSubscrList l_subscrSto[MAX_PUB_SIG]; /* publish-subscribe */
|
|
|
+
|
|
|
+ /*..........................................................................*/
|
|
|
+ void main(int argc, char *argv[]) {
|
|
|
+ /* explicitly invoke the active objects' ctors... */
|
|
|
+(10) Missile_ctor();
|
|
|
+(11) Ship_ctor();
|
|
|
+(12) Tunnel_ctor();
|
|
|
+
|
|
|
+(13) BSP_init(argc, argv); /* initialize the Board Support Package */
|
|
|
+(14) QF_init(); /* initialize the framework and the underlying RT kernel */
|
|
|
+
|
|
|
+ /* initialize the event pools... */
|
|
|
+(15) QF_poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), sizeof(l_smlPoolSto[0]));
|
|
|
+(16) QF_poolInit(l_medPoolSto, sizeof(l_medPoolSto), sizeof(l_medPoolSto[0]));
|
|
|
+
|
|
|
+(17) QF_psInit(l_subscrSto, Q_DIM(l_subscrSto)); /* init publish-subscribe */
|
|
|
+
|
|
|
+ /* start the active objects... */
|
|
|
+(18) QActive_start(AO_Missile,/* global pointer to the Missile active object */
|
|
|
+ 1, /* priority (lowest) */
|
|
|
+ l_missileQueueSto, Q_DIM(l_missileQueueSto), /* evt queue */
|
|
|
+ (void *)0, 0, /* no per-thread stack */
|
|
|
+ (QEvent *)0); /* no initialization event */
|
|
|
+(19) QActive_start(AO_Ship, /* global pointer to the Ship active object */
|
|
|
+ 2, /* priority */
|
|
|
+ l_shipQueueSto, Q_DIM(l_shipQueueSto), /* evt queue */
|
|
|
+ (void *)0, 0, /* no per-thread stack */
|
|
|
+ (QEvent *)0); /* no initialization event */
|
|
|
+(20) QActive_start(AO_Tunnel, /* global pointer to the Tunnel active object */
|
|
|
+ 3, /* priority */
|
|
|
+ l_tunnelQueueSto, Q_DIM(l_tunnelQueueSto), /* evt queue */
|
|
|
+ (void *)0, 0, /* no per-thread stack */
|
|
|
+ (QEvent *)0); /* no initialization event */
|
|
|
+
|
|
|
+(21) QF_run(); /* run the QF application */
|
|
|
+ }
|
|
|
+\endcode
|
|
|
+
|
|
|
+\li (1) The "Fly 'n' Shoot" game is an example of an application implemented with
|
|
|
+the QP event-driven platform. Every application C-file that uses QP must
|
|
|
+include the qp_port.h header file. This header file contains the specific
|
|
|
+adaptation of QP to the given processor, operating system, and compiler, which
|
|
|
+is called a port. Each QP port is located in a separate directory and the C
|
|
|
+compiler finds the right qp_port.h header file through the include search path
|
|
|
+provided to the compiler (typically via the -I compiler option). That way I
|
|
|
+don't need to change the application source code to recompile it for a
|
|
|
+different processor or compiler. I only need to instruct the compiler to look
|
|
|
+in a different QP port directory for the qp_port.h header file. For example,
|
|
|
+the DOS version includes the qp_port.h header file from the directory
|
|
|
+\c <qpc>\\ports\\80x86\\dos\\watcom\\l\\, and the ARM-Cortex version
|
|
|
+from the directory \c <qpc>\\ports\\arm-cortex\\vanilla\\iar\\.
|
|
|
+
|
|
|
+\li (2) The bsp.h header file contains the interface to the Board Support Package
|
|
|
+and is located in the application directory.
|
|
|
+
|
|
|
+\li (3) The game.h header file contains the declarations of events and other
|
|
|
+facilities shared among the components of the application. I will discuss this
|
|
|
+header file in the upcoming Section \ref events. This header file is located
|
|
|
+in the application directory.
|
|
|
+
|
|
|
+The QP event-driven platform is a collection of components, such as the QEP
|
|
|
+event processor that executes state machines according to the UML semantics
|
|
|
+and the QF real-time framework that implements the active object computing
|
|
|
+model. Active objects in QF are encapsulated state machines (each with an
|
|
|
+event queue, a separate task context, and a unique priority) that communicate
|
|
|
+with one another asynchronously by sending and receiving events, while QF
|
|
|
+handles all the details of thread-safe event exchange and queuing. Within an
|
|
|
+active object, the events are processed by the QEP event processor
|
|
|
+sequentially in a run-to-completion (RTC) fashion, meaning that processing of
|
|
|
+one event must necessarily complete before processing the next event.
|
|
|
+
|
|
|
+\li (4-6) The application must provide storage for the event queues of all active
|
|
|
+objects used in the application. Here the storage is provided at compile time
|
|
|
+through the statically allocated arrays of immutable (const) pointers to
|
|
|
+events, because QF event queues hold just pointers to events, not events
|
|
|
+themselves. Events are represented as instances of the QEvent structure
|
|
|
+declared in the qp_port.h header file. Each event queue of an active object
|
|
|
+can have a different size and you need to decide this size based on your
|
|
|
+knowledge of the application. I discuss the event queues in Chapters 6 and 7
|
|
|
+of \ref PSiCC2.
|
|
|
+
|
|
|
+\li (7-8) The application must also provide storage for event pools that the
|
|
|
+framework uses for fast and deterministic dynamic allocation of events. Each
|
|
|
+event pool manages can provide only fixed-size memory blocks. To avoid wasting
|
|
|
+memory by using oversized blocks for small events, the QF framework can manage
|
|
|
+up to three event pools of different block sizes (for small, medium, and large
|
|
|
+events). The "Fly 'n' Shoot" application uses only two out of the three
|
|
|
+possible event pools (the small and medium pools).
|
|
|
+
|
|
|
+The QF real-time framework supports two event delivery mechanisms: the simple
|
|
|
+direct event posting to active objects, and the more advanced mechanism called
|
|
|
+publish-subscribe that decouples event producers from the consumers. In the
|
|
|
+publish-subscribe mechanism, active objects subscribe to events by the
|
|
|
+framework. Event producers publish the events to the framework. Upon each
|
|
|
+publication request, the framework delivers the event to all active objects
|
|
|
+that had subscribed to that event type. One obvious implication of
|
|
|
+publish-subscribe is that the framework must store the subscriber information,
|
|
|
+whereas it must be possible to handle multiple subscribers to any give event
|
|
|
+type. The event delivery mechanisms are described in Chapters 6 and 7 of \ref
|
|
|
+PSiCC2.
|
|
|
+
|
|
|
+\li (9) The "Fly 'n' Shoot" application uses the publish-subscribe event delivery
|
|
|
+mechanism supported by QF, so it needs to provide the storage for the
|
|
|
+subscriber lists. The subscriber lists remembers which active objects have
|
|
|
+subscribed to which events. The size of the subscriber database depends on
|
|
|
+both the number of published events, which is specified in the MAX_PUB_SIG
|
|
|
+constant found in the game.h header file, and the maximum number of active
|
|
|
+objects allowed in the system, which is determined by the QF configuration
|
|
|
+parameter #QF_MAX_ACTIVE.
|
|
|
+
|
|
|
+\li (10-12) These functions perform an early initialization of the active objects
|
|
|
+in the system. They play the role of static "constructors", which in C you
|
|
|
+need to invoke explicitly. (C++ calls such static constructors implicitly
|
|
|
+before entering \c main()).
|
|
|
+
|
|
|
+\li (13) The function \c BSP_init() initializes the board and is defined in the
|
|
|
+bsp.c file.
|
|
|
+
|
|
|
+\li (14) The function QF_init() initializes the QF component and the underlying
|
|
|
+RTOS/kernel, if such software is used. You need to call QF_init() before you
|
|
|
+invoke any of QF services.
|
|
|
+
|
|
|
+\li (15-16) The function QF_poolInit() initializes the event pools. The parameters
|
|
|
+of this function are the pointer to the event pool storage, the size of this
|
|
|
+storage, and the block-size of this pool. You can call this function up to
|
|
|
+three times to initialize up to three event pools. The subsequent calls to
|
|
|
+QF_poolInit() must be made in the increasing order of block sizes. For
|
|
|
+instance, the small block-size pool must be initialized before the medium
|
|
|
+block-size pool.
|
|
|
+
|
|
|
+\li (17) The function QF_poolInit() initializes the publish-subscribe event
|
|
|
+delivery mechanism of QF. The parameters of this function are the pointer to
|
|
|
+the subscriber-list array and the dimension of this array.
|
|
|
+
|
|
|
+The utility macro Q_DIM(a) provides the dimension of a one-dimensional array
|
|
|
+<TT>a[]</TT> computed as <TT>sizeof(a)/sizeof(a[0])</TT>, which is a
|
|
|
+compile-time constant. The use of this macro simplifies the code because it
|
|
|
+allows me to eliminate many #define constants that otherwise I would need to
|
|
|
+provide for the dimensions of various arrays. I can simply hard-code the
|
|
|
+dimension right in the definition of an array, which is the only place that I
|
|
|
+specify it. I then use the macro Q_DIM() whenever I need this dimension in the
|
|
|
+code.
|
|
|
+
|
|
|
+\li (18-20) The function QActive_start() tells the QF framework to start managing
|
|
|
+an active object as part of the application. The function takes the following
|
|
|
+parameters: the pointer to the active object structure, the priority of the
|
|
|
+active object, the pointer to its event queue, the dimension (length) of that
|
|
|
+queue, and three other parameters that I explain in Chapter 7 of \ref
|
|
|
+PSiCC2, since they are not relevant at this point. The active object
|
|
|
+priorities in QF are numbered from 1 to #QF_MAX_ACTIVE, inclusive, where a
|
|
|
+higher priority number denotes higher urgency of the active object. The
|
|
|
+constant #QF_MAX_ACTIVE is defined in the QF port header file qf_port.h and
|
|
|
+currently cannot exceed 63.
|
|
|
+
|
|
|
+I like to keep the code and data of every active object strictly encapsulated
|
|
|
+within its own C-file. For example, all code and data for the active object
|
|
|
+Ship are encapsulated in the file ship.c, with the external interface
|
|
|
+consisting of the function \c Ship_ctor() and the pointer \c AO_Ship.
|
|
|
+
|
|
|
+\li (21) At this point, you have provided to the framework all the storage and
|
|
|
+information it needs to manage your application. The last thing you must do is
|
|
|
+to call the function QF_run() to pass the control to the framework.
|
|
|
+
|
|
|
+After the call to QF_run() the framework is in full control. The framework
|
|
|
+executes the application by calling your code, not the other way around. The
|
|
|
+function QF_run() never returns the control back to main(). In the DOS version
|
|
|
+of the "Fly 'n' Shoot" game, you can terminate the application by pressing the
|
|
|
+ESC key, in which case QF_run() exits to DOS, but not to \c main(). In an
|
|
|
+embedded system, such as the ARM-Cortex board, QF_run() runs forever or till
|
|
|
+the power is removed, whichever comes first.
|
|
|
+
|
|
|
+\note For best cross-platform portability, the source code uses consistently
|
|
|
+the <STRONG>UNIX end-of-line convention</STRONG> (lines are terminated with LF
|
|
|
+only, 0xA character). This convention seems to be working for all C/C++
|
|
|
+compilers and cross-compilers, including legacy DOS-era tools. In contrast,
|
|
|
+the DOS/Windows end-of-line convention (lines terminated with the CR,LF, or
|
|
|
+0xD,0xA pair of characters), is known to cause problems on UNIX-like
|
|
|
+platforms, especially in the multi-line preprocessor macros.
|
|
|
+
|
|
|
+Prev: \ref lets_play \n
|
|
|
+Next: \ref design
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page design 4. Designing an Event-Driven Application
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref main_function \n
|
|
|
+Next: \ref active_objects
|
|
|
+
|
|
|
+To proceed further with the explanation of the "Fly 'n' Shoot" application, I
|
|
|
+need to step up to the design level. At this point I need to explain how the
|
|
|
+application has been decomposed into the active objects, and how these objects
|
|
|
+exchange events to collectively deliver the functionality of the "Fly 'n'
|
|
|
+Shoot" game.
|
|
|
+
|
|
|
+In general, the decomposition of a problem into active objects is
|
|
|
+not trivial. As usual in any decomposition, your goal is to achieve possibly
|
|
|
+loose coupling among the active object components (ideally no sharing of any
|
|
|
+resources), and you also strive for minimizing the communication in terms of
|
|
|
+the frequency and size of exchanged events.
|
|
|
+
|
|
|
+In the case of the "Fly 'n' Shoot" game, I need first to identify all objects
|
|
|
+with reactive behavior (i.e. with a state machine). I applied the simplest
|
|
|
+object-oriented technique of identifying objects, which is to pick the
|
|
|
+frequently used nouns in the problem specification. From Section \ref
|
|
|
+lets_play, I identified Ship, Missile, Mines, and Tunnel. However, not every
|
|
|
+state machine in the system needs to be an active object (with a separate task
|
|
|
+context, an event queue, and a unique priority level), and merging them is a
|
|
|
+valid option when performance or space is needed. As an example of this idea,
|
|
|
+I ended up merging the Mines into the Tunnel active object, whereas I
|
|
|
+preserved the Mines as independent state machine components of the Tunnel
|
|
|
+active object. By doing so I applied the "Orthogonal Component" design pattern
|
|
|
+described in Chapter 5 of \ref PSiCC2.
|
|
|
+
|
|
|
+The next step in the event-driven application design is assigning
|
|
|
+responsibilities and resources to the identified active objects. The general
|
|
|
+design strategy for avoiding sharing of resources is to encapsulate each
|
|
|
+resource inside a dedicated active object and to let that object manage the
|
|
|
+resource for the rest of the application. That way, instead of sharing the
|
|
|
+resource directly, the rest of the application shares the dedicated active
|
|
|
+object via events.
|
|
|
+
|
|
|
+So, for example, I decided to put the Tunnel active object in charge of the
|
|
|
+display. Other active objects and state machine components, such as Ship,
|
|
|
+Missile and Mines, don't draw on the display directly, but rather send events
|
|
|
+to the Tunnel object with the request to render the Ship, Missile, or Mine
|
|
|
+bitmaps at the provided (x, y) coordinates of the display.
|
|
|
+
|
|
|
+With some understanding of the responsibilities and resource allocations to
|
|
|
+active object I can move on to devising the various scenarios of event
|
|
|
+exchanges among the objects. Perhaps the best instrument to aid the thinking
|
|
|
+process at this stage is the UML sequence diagram, such as the diagram
|
|
|
+depicted in \ref F4s1 "Figure 4-1". This particular sequence diagram shows the
|
|
|
+most common event exchange scenarios in the "Fly 'n' Shoot" game (the primary
|
|
|
+use cases, if you will). The explanation section immediately following the
|
|
|
+diagram illuminates the interesting points.
|
|
|
+
|
|
|
+\note A UML sequence diagram like \ref F4s1 "Figure 4-1" has two dimensions.
|
|
|
+Horizontally arranged boxes represent the various objects participating in the
|
|
|
+scenario whereas heavy boarders indicate active objects. As usual in the UML,
|
|
|
+the object name in underlined. Time flows down the page along the vertical
|
|
|
+dashed lines descending from the objects. Events are represented as horizontal
|
|
|
+arrows originating from the sending object and terminating at the receiving
|
|
|
+object. Optionally, thin rectangles around instance lines indicate focus of
|
|
|
+control.
|
|
|
+
|
|
|
+\anchor F4s1
|
|
|
+\image html Fig1.04.jpg "Figure 4-1 The sequence diagram of the Fly 'n' Shoot game."
|
|
|
+
|
|
|
+\li (1) The \c TIME_TICK is the most important event in the game. This event is
|
|
|
+generated by the QF framework from the system time tick interrupt at a rate of
|
|
|
+30 times per second, which is needed to drive a smooth animation of the
|
|
|
+display. Because the \c TIME_TICK event is of interest to virtually all
|
|
|
+objects in the application, it is published by the framework to all active
|
|
|
+objects. (The publish-subscribe event delivery in QF is described in Chapter 6
|
|
|
+of \ref PSiCC2.)
|
|
|
+
|
|
|
+\li (2) Upon reception of the \c TIME_TICK event, the Ship object advances its
|
|
|
+position by one step and posts the event <TT>SHIP_IMG(x, y, bmp)</TT> to the
|
|
|
+Tunnel object. The SHIP_IMG event has parameters x and y, which are the
|
|
|
+coordinates of the Ship on the display, as well as the bitmap number bmp to
|
|
|
+draw at these coordinates.
|
|
|
+
|
|
|
+\li (3) The Missile object is not in flight yet, so it simply ignores the
|
|
|
+\c TIME_TICK event this time.
|
|
|
+
|
|
|
+\li (4) The Tunnel object performs the heaviest lifting for the \c TIME_TICK
|
|
|
+event. First, Tunnel redraws the entire display from the current frame buffer.
|
|
|
+This action performed 30 times per second provides the illusion of animation
|
|
|
+of the display. Next, the Tunnel clears the frame buffer and starts filling it
|
|
|
+up again for the next time frame. The Tunnel advances the tunnel walls by one
|
|
|
+step and copies the walls to the frame buffer. The Tunnel also dispatches the
|
|
|
+\c TIME_TICK event to all its Mine state machine components.
|
|
|
+
|
|
|
+\li (5) Each Mine advances its position by one step and posts the <TT>MINE_IMG(x,
|
|
|
+y, bmp)</TT> event to the Tunnel to render the appropriate Mine bitmap at the
|
|
|
+position <TT>(x, y)</TT> in the current frame buffer. Mines of type 1 send the
|
|
|
+bitmap number MINE1_BMP, while mines of type 2 send \c MINE2_BMP.
|
|
|
+
|
|
|
+\li (6) Upon reception of the <TT>SHIP_IMG(x, y, bmp)</TT> event from the Ship,
|
|
|
+the Tunnel object renders the specified bitmap in the frame buffer and checks
|
|
|
+for any collision between the ship bitmap and the tunnel walls. Tunnel also
|
|
|
+dispatches the original <TT>SHIP_IMG(x, y, bmp)</TT> event to all active
|
|
|
+Mines.
|
|
|
+
|
|
|
+\li (7) Each Mine determines if the Ship is in collision with that Mine.
|
|
|
+
|
|
|
+\li (8) The \c PLAYER_TRIGGER event is generated when the Player reliably presses
|
|
|
+the button (button press is debounced). This event is published by the QF
|
|
|
+framework and is delivered to the Ship and Tunnel objects, which both
|
|
|
+subscribe to the \c PLAYER_TRIGGER event.
|
|
|
+
|
|
|
+\li (9) Ship generates the <TT>MISSILE_FIRE(x, y)</TT> event to the Missile
|
|
|
+object. The parameters of this event are the current <TT>(x, y)</TT>
|
|
|
+coordinates of the Ship, which are the starting point for the Missile.
|
|
|
+
|
|
|
+\li (10) Tunnel receives the published \c PLAYER_TRIGGER event as well because
|
|
|
+Tunnel occasionally needs to start the game or terminate the screen saver mode
|
|
|
+based upon this stimulus.
|
|
|
+
|
|
|
+\li (11) Missile reacts to the <TT>MISSILE_FIRE(x, y)</TT> event by starting to
|
|
|
+fly, whereas it sets its initial position from the <TT>(x, y)</TT> event
|
|
|
+parameters delivered from the Ship.
|
|
|
+
|
|
|
+\li (12) This time around, the \c TIME_TICK event arrives while Missile is in
|
|
|
+flight. Missile posts the <TT>MISSILE_IMG(x, y, bmp)</TT> event to the Table.
|
|
|
+
|
|
|
+\li (13) Table renders the Missile bitmap in the current frame buffer and
|
|
|
+dispatches the <TT>MISSILE_IMG(x, y, bmp)</TT> event to all the Mines to let
|
|
|
+the Mines test for the collision with the Missile. This determination depends
|
|
|
+on the type of the Mine. In this scenario a particular Mine[n] object detects
|
|
|
+a hit and posts the <TT>HIT_MINE(score)</TT> event to the Missile. The Mine
|
|
|
+provides the score earned for destroying this particular mine as the parameter
|
|
|
+of this event.
|
|
|
+
|
|
|
+\li (14) Missile handles the <TT>HIT_MINE(score)</TT> event by becoming
|
|
|
+immediately ready to launch again and lets the Mine do the exploding. As I
|
|
|
+decided to make the Ship responsible for the scorekeeping, the Missile also
|
|
|
+generates the <TT>DESTROYED_MINE(score)</TT> event to the Ship, to report the
|
|
|
+score for destroying the Mine.
|
|
|
+
|
|
|
+\li (15) Upon reception of the <TT>DESTROYED_MINE(score)</TT> event, the Ship
|
|
|
+updates the score reported by the Missile.
|
|
|
+
|
|
|
+\li (16) The Ship object handles the <TT>PLAYER_SHIP_MOVE(x, y)</TT> event by
|
|
|
+updating its position from the event parameters.
|
|
|
+
|
|
|
+\li (17) When the Tunnel object handles the <TT>SHIP_IMG(x, y, bmp_id)</TT> event
|
|
|
+next time around, it detects a collision between the Ship and the tunnel wall.
|
|
|
+In that case it posts the event \c HIT_WALL to the Ship.
|
|
|
+
|
|
|
+\li (18) The Ship responds to the \c HIT_WALL event by transitioning to the
|
|
|
+"exploding" state.
|
|
|
+
|
|
|
+Even though the sequence diagram in \ref F4s1 "Figure 4-1" shows merely some
|
|
|
+selected scenarios of the "Fly 'n' Shoot" game, I hope that the explanations
|
|
|
+give you a big picture of how the application works. More importantly, you
|
|
|
+should start getting the general idea about the thinking process that goes
|
|
|
+into designing an event-driven system with active objects and events.
|
|
|
+
|
|
|
+Prev: \ref main_function \n
|
|
|
+Next: \ref active_objects
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page active_objects 5. Elaborating State Machines of Active Objects
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref design \n
|
|
|
+Next: \ref events
|
|
|
+
|
|
|
+I hope that the analysis of the sequence diagram in \ref F4s1 "Figure 4-1" makes
|
|
|
+it clear that actions performed by an active object depend as much on the
|
|
|
+events it receives, as on the internal mode of the object. For example, the
|
|
|
+Missile active object handles the \c TIME_TICK event very differently when the
|
|
|
+Missile is in flight (\ref F4s1 "Figure 4-1"(12)) compared to the time when it is
|
|
|
+not (\ref F4s1 "Figure 4-1"(3)). The best known mechanism of handling such modal
|
|
|
+behavior is through state machines because a state machine makes the behavior
|
|
|
+explicitly dependent on both the event and the state of an object. In Chapter
|
|
|
+2 of \ref PSiCC2 I introduce UML state machine concepts more thoroughly. In
|
|
|
+this section, I give a cursory explanation of the state machines associated
|
|
|
+with each object in the "Fly 'n' Shoot" game.
|
|
|
+
|
|
|
+\section missile 5.1 The Missile Active Object
|
|
|
+
|
|
|
+I start with the Missile state machine shown in \ref F5s1 "Figure 5-1", because it
|
|
|
+turns out to be the simplest one. The explanation section immediately
|
|
|
+following the diagram illuminates the interesting points.
|
|
|
+
|
|
|
+\note A UML state diagram like \ref F5s1 "Figure 5-1" preserves the general form
|
|
|
+of the traditional state transition diagrams, where states are represented as
|
|
|
+nodes and transitions as arcs connecting the nodes. In the UML notation the
|
|
|
+state nodes are represented as rectangles with rounded corners. The name of
|
|
|
+the state appears in bold type in the name compartment at the top of the
|
|
|
+state. Optionally, right below the name, a state can have an internal
|
|
|
+transition compartment separated from the name by a horizontal line. The
|
|
|
+internal transition compartment can contain entry actions (actions following
|
|
|
+the reserved symbol "entry"), exit actions (actions following the reserved
|
|
|
+symbol "exit"), and other internal transitions (e.g., those triggered by
|
|
|
+\c TIME_TICK in \ref F5s1 "Figure 5-1"(3)). State transitions are represented as
|
|
|
+arrows originating at the boundary of the source state and pointing to the
|
|
|
+boundary of the target state. At a minimum, a transition must be labeled with
|
|
|
+the triggering event. Optionally, the trigger can be followed by event
|
|
|
+parameters, a guard, and a list of actions.
|
|
|
+
|
|
|
+\anchor F5s1
|
|
|
+\image html Fig1.05.jpg "Figure 5-1 Missile state machine diagram."
|
|
|
+
|
|
|
+\li (1) The state transition originating at the black ball is called the initial
|
|
|
+transition. Such transition designates the first active state after the state
|
|
|
+machine object is created. An initial transition can have associated actions,
|
|
|
+which in the UML notation are enlisted after the forward slash "/". In this
|
|
|
+particular case, the Missile state machine starts in the "armed" state and the
|
|
|
+actions executed upon the initialization consist of subscribing to the event
|
|
|
+\c TIME_TICK. Subscribing to an event means that the framework will deliver
|
|
|
+the specified event to the Missile active object every time the event is
|
|
|
+published to the framework. Chapter 7 of \ref PSiCC2 describes the
|
|
|
+implementation of the publish-subscribe event delivery in QF.
|
|
|
+
|
|
|
+\li (2) The arrow labeled with the <TT>MISSILE_FIRE(x, y)</TT> event denotes a
|
|
|
+state transition, that is, change of state from "armed" to "flying". The
|
|
|
+<TT>MISSILE_FIRE(x, y)</TT> event is generated by the Ship object when the
|
|
|
+Player triggers the Missile (see the sequence diagram in \ref F4s1 "Figure 4-1").
|
|
|
+In the \c MISSILE_FIRE event, Ship provides Missile with the initial
|
|
|
+coordinates in the event parameters <TT>(x, y)</TT>.
|
|
|
+
|
|
|
+\note The UML intentionally does not specify the notation for actions. In
|
|
|
+practice, the actions are often written in the programming language used for
|
|
|
+coding the particular state machine. In all state diagrams in this book, I
|
|
|
+assume the C programming language. Furthermore, in the C expressions I refer
|
|
|
+to the data members associated with the state machine object through the
|
|
|
+<TT>me-></TT> prefix and to the event parameters through the <TT>e-></TT>
|
|
|
+prefix. For example, the action <TT>me->x = e->x;</TT> means that the internal
|
|
|
+data member \c x of the Missile active object is assigned the value of the
|
|
|
+event parameter \c x.
|
|
|
+
|
|
|
+\li (3) The event name \c TIME_TICK enlisted in the compartment below the state
|
|
|
+name denotes an internal transition. Internal transitions are simple reactions
|
|
|
+to events performed without a change of state. An internal transition, as well
|
|
|
+as a regular transition, can have a guard condition, enclosed in square
|
|
|
+brackets. Guard condition is a Boolean expression evaluated at runtime. If the
|
|
|
+guard evaluates to TRUE, the transition is taken. Otherwise, the transition is
|
|
|
+not taken and no actions enlisted after the forward slash "/" are executed. In
|
|
|
+this particular case, the guard condition checks whether the x-coordinate
|
|
|
+propagated by the Missile speed is still visible on the screen. If so, the
|
|
|
+actions are executed. These actions include propagation of the Missile
|
|
|
+position by one step and posting the \c MISSILE_IMG event with the current
|
|
|
+Missile position and the \c MISSILE_BMP bitmap number to the Tunnel active
|
|
|
+object. Direct event posting to an active object is accomplished by the QF
|
|
|
+function QActive_postFIFO(), which I discuss in Chapter 7 of \ref PSiCC2.
|
|
|
+
|
|
|
+\li (4) The same event \c TIME_TICK with the <TT>[else]</TT> guard denotes a
|
|
|
+regular state transition with the guard condition complementary to the other
|
|
|
+occurrence of the \c TIME_TICK event in the same state. In this case, the
|
|
|
+\c TIME_TICK transition to "armed" is taken if the Missile object flies out of
|
|
|
+the screen.
|
|
|
+
|
|
|
+\li (5) The event <TT>HIT_MINE(score)</TT> triggers another transition to the
|
|
|
+"armed" state. The action associated with this transition posts the
|
|
|
+\c DESTROYED_MINE event with the parameter e->score to the Ship object, to
|
|
|
+report destroying the mine.
|
|
|
+
|
|
|
+\li (6) The event \c HIT_WALL triggers a transition to the "exploding" state, with
|
|
|
+the purpose of animating the explosion bitmaps on the display.
|
|
|
+
|
|
|
+\li (7) The label "entry" denotes the entry action to be executed unconditionally
|
|
|
+upon the entry to the "exploding" state. This action consists of clearing
|
|
|
+explosion counter (<TT>me->exp_ctr</TT>) member of the Missile object.
|
|
|
+
|
|
|
+\li (8) The \c TIME_TICK internal transition is guarded by the condition that the
|
|
|
+explosion does not scroll off the screen, and that the explosion counter is
|
|
|
+lower than 16. The actions executed include propagation of the explosion
|
|
|
+position and posting the \c EXPLOSION_IMG event to the Tunnel active object.
|
|
|
+Please note that the bitmap of the explosion changes as the explosion counter
|
|
|
+gets bigger.
|
|
|
+
|
|
|
+\li (6) The \c TIME_TICK regular transition with the complementary guard changes
|
|
|
+the state back to the "armed" state. This transition is taken after the
|
|
|
+animation of the explosion completes.
|
|
|
+
|
|
|
+\section ship 5.2 The Ship Active Object
|
|
|
+
|
|
|
+The state machine of the Ship active object is shown in \ref F5s2 "Figure 5-2".
|
|
|
+This state machine introduces the profound concept of hierarchical state
|
|
|
+nesting. The power of state nesting derives from the fact that it is designed
|
|
|
+to eliminate repetitions that otherwise would have to occur.
|
|
|
+
|
|
|
+One of the main responsibilities of the Ship active object is to maintain the
|
|
|
+current position of the Ship. On the original LM3S811 board, this position is
|
|
|
+determined by the potentiometer wheel (see \ref F2s2 "Figure 2-2"). The
|
|
|
+<TT>PLAYER_SHIP_MOVE(x, y)</TT> event is generated whenever the wheel position
|
|
|
+changes, as shown in the sequence diagram (\ref F4s1 "Figure 4-1"). The Ship
|
|
|
+object must always keep track of the wheel position, which means that all
|
|
|
+states of the Ship state machine must handle the <TT>PLAYER_SHIP_MOVE(x,
|
|
|
+y)</TT> event.
|
|
|
+
|
|
|
+In the traditional finite state machine (FSM) formalism, you would need to
|
|
|
+repeat the Ship position update from the <TT>PLAYER_SHIP_MOVE(x, y)</TT> event
|
|
|
+in every state. But such repetitions would bloat the state machine and, more
|
|
|
+importantly, would represent multiple points of maintenance both in the
|
|
|
+diagram and the code. Such repetitions go against the DRY principle (Don't
|
|
|
+Repeat Yourself), which is vital for flexible and maintainable code.
|
|
|
+
|
|
|
+Hierarchical state nesting remedies the problem. Consider the state "active"
|
|
|
+that surrounds all other states in \ref F5s2 "Figure 5-2". The high-level "active"
|
|
|
+state is called the superstate and is abstract in that the state machine
|
|
|
+cannot be in this state directly, but only in one of the states nested within,
|
|
|
+which are called the substates of "active". The UML semantics associated with
|
|
|
+state nesting prescribes that any event is first handled in the context of the
|
|
|
+currently active substate. If the substate cannot handle the event, the state
|
|
|
+machine attempts to handle the event in the context of the next-level
|
|
|
+superstate. Of course, state nesting in UML is not limited to just one level
|
|
|
+and the simple rule of processing events applies recursively to any level of
|
|
|
+nesting.
|
|
|
+
|
|
|
+Specifically to the Ship state machine diagram shown in \ref F5s2 "Figure 5-2",
|
|
|
+suppose that the event <TT>PLAYER_SHIP_MOVE(x, y)</TT> arrives when the state
|
|
|
+machine is in the "parked" state. The "parked" state does not handle the
|
|
|
+<TT>PLAYER_SHIP_MOVE(x, y)</TT> event. In the traditional finite state machine
|
|
|
+this would be the end of story—the <TT>PLAYER_SHIP_MOVE(x, y)</TT> event would
|
|
|
+be silently discarded. However, the state machine in \ref F5s2 "Figure 5-2" has
|
|
|
+another layer of the "active" superstate. Per the semantics of state nesting,
|
|
|
+this higher-level superstate handles the PLAYER_SHIP_MOVE(x, y) event, which
|
|
|
+is exactly what's needed. The same exact argumentation applies for any other
|
|
|
+substate of the "active" superstate, such as "flying" or "exploding", because
|
|
|
+none of these substates handle the <TT>PLAYER_SHIP_MOVE(x, y)</TT> event.
|
|
|
+Instead, the "active" superstate handles the event in one single place,
|
|
|
+without repetitions.
|
|
|
+
|
|
|
+\anchor F5s2
|
|
|
+\image html Fig1.06.jpg "Figure 5-2 Ship state machine diagram."
|
|
|
+
|
|
|
+\li (1) Upon the initial transition, the Ship state machine enters the "active"
|
|
|
+superstate and subscribes to events \c TIME_TICK and \c PLAYER_TRIGGER.
|
|
|
+
|
|
|
+\li (2) At each level of nesting a superstate can have a private initial
|
|
|
+transition that designates the active substate after the superstate is entered
|
|
|
+directly. Here the initial transition of state "active" designates the
|
|
|
+substate "parked" as the initial active substate.
|
|
|
+
|
|
|
+\li (3) The "active" superstate handles the <TT>PLAYER_SHIP_MOVE(x, y)</TT> event
|
|
|
+as an internal transition in which it updates the internal data members
|
|
|
+<TT>me->x</TT> and <TT>me->y</TT> from the event parameters <TT>e->x</TT> and
|
|
|
+<TT>e->y</TT>, respectively.
|
|
|
+
|
|
|
+\li (4) The TAKE_OFF event triggers transition to "flying". This event is
|
|
|
+generated by the Tunnel object when the Player starts the game (see the
|
|
|
+description of the game in Section \ref lets_play).
|
|
|
+
|
|
|
+\li (5) The entry actions to "flying" include clearing the me->score data member
|
|
|
+and posting the event \c SCORE with the event parameter me->score to the
|
|
|
+Tunnel active object.
|
|
|
+
|
|
|
+\li (6) The \c TIME_TICK internal transition causes posting the event \c SHIP_IMG
|
|
|
+with current Ship position and the \c SHIP_BMP bitmap number to the Tunnel
|
|
|
+active object. Additionally, the score is incremented for surviving another
|
|
|
+time tick. Finally, when the score is "round" (divisible by 10) it is also
|
|
|
+posted to the Tunnel active object. This decimation of the \c SCORE event is
|
|
|
+performed just to reduce the bandwidth of the communication, because the
|
|
|
+Tunnel active object only needs to give an approximation of the running score
|
|
|
+tally to the user.
|
|
|
+
|
|
|
+\li (7) The \c PLAYER_TRIIGGER internal transition causes posting the event
|
|
|
+\c MISSILE_FIRE with current Ship position to the Missile active object. The
|
|
|
+parameters <TT>(me->x, me->y)</TT> provide the Missile with the initial
|
|
|
+position from the Ship.
|
|
|
+
|
|
|
+\li (8) The <TT>DESTROYED_MINE(score)</TT> internal transition causes update of
|
|
|
+the score kept by the Ship. The score is not posted to the Table at this
|
|
|
+point, because the next \c TIME_TICK will send the "rounded" score, which is
|
|
|
+good enough for giving the Player the score approximation.
|
|
|
+
|
|
|
+\li (9) The \c HIT_WALL event triggers transition to "exploding"
|
|
|
+
|
|
|
+\li (10) The <TT>HIT_MINE(type)</TT> event also triggers transition to "exploding"
|
|
|
+
|
|
|
+\li (11) The "exploding" state of the Ship state machine is very similar to the
|
|
|
+"exploding" state of Missile (see \ref F5s1 "Figure 5-1"(7-9)).
|
|
|
+
|
|
|
+\li (12) The <TT>TIME_TICK[else]</TT> transition is taken when the Ship finishes
|
|
|
+exploding. Upon this transition, the Ship object posts the event
|
|
|
+<TT>GAME_OVER(me->score)</TT> to the Tunnel active object to terminate the
|
|
|
+game and display the final score to the Player.
|
|
|
+
|
|
|
+
|
|
|
+\section tunnel 5.3 The Tunnel Active Object
|
|
|
+
|
|
|
+The Tunnel active object has the most complex state machine, which is shown in
|
|
|
+\ref F5s3 "Figure 5-3". Unlike the previous state diagrams, the diagram in
|
|
|
+\ref F5s3 "Figure 5-3" shows only the high-level of abstraction and omits a lot of
|
|
|
+details such as most entry/exit actions, internal transitions, guard
|
|
|
+conditions, or actions on transitions. Such a "zoomed out" view is always
|
|
|
+legal in the UML, because UML allows you to choose the level of detail that
|
|
|
+you want to include in your diagram.
|
|
|
+
|
|
|
+The Tunnel state machine uses state hierarchy more extensively than
|
|
|
+the Ship state machine in \ref F5s2 "Figure 5-2". The explanation section
|
|
|
+immediately following \ref F5s3 "Figure 5-3" illuminates the new uses of state
|
|
|
+nesting as well as the new elements not explained yet in the other state
|
|
|
+diagrams.
|
|
|
+
|
|
|
+\anchor F5s3
|
|
|
+\image html Fig1.07.jpg "Figure 5-3 Tunnel state machine diagram."
|
|
|
+
|
|
|
+\li (1) An initial transition can target a substate at any level of state
|
|
|
+hierarchy, not necessarily just the next-lower level. Here the top-most
|
|
|
+initial transition goes down two levels to the substate "demo".
|
|
|
+
|
|
|
+\li (2) The superstate "active" handles the \c PLAYER_QUIT event as a transition
|
|
|
+to the final state (see explanation of element (3)). Please note that the
|
|
|
+PLAYER_QUIT transition applies to all substates directly or transitively
|
|
|
+nested in the "active" superstate. Because a state transition always involves
|
|
|
+execution of all exit actions from the states, the high-level PLAYER_QUIT
|
|
|
+transition guarantees the proper cleanup that is specific to the current state
|
|
|
+context, whichever substate happens to be active at the time when the
|
|
|
+\c PLAYER_QUIT event arrives.
|
|
|
+
|
|
|
+\li (3) The final state is indicated in the UML notation as the bull's-eye symbol
|
|
|
+and typically indicates destruction of the state machine object. In this case,
|
|
|
+the \c PLAYER_QUIT event indicates termination of the game.
|
|
|
+
|
|
|
+\li (4) The <TT>MINE_DISABLED(mine_id)</TT> event is handled at the high level of
|
|
|
+the "active" state, which means that this internal transition applies to the
|
|
|
+whole sub-machine nested inside the "active" superstate. (See also the
|
|
|
+discussion of Mine object in the next section.)
|
|
|
+
|
|
|
+\li (5) The entry action to the "demo" state starts the screen time event (timer)
|
|
|
+<TT>me->screenTimeEvt</TT> to expire in 20 seconds. Time events are allocated
|
|
|
+by the application, but they are managed by the QF framework. QF provides
|
|
|
+functions to arm a time event, such as QTimeEvt_postIn() for one-shot timeout,
|
|
|
+and QTimeEvt_postEvery() for periodic time events. Arming a time event is in
|
|
|
+effect telling the QF framework, for instance, "Give me a nudge in 20
|
|
|
+seconds". QF then posts the time event (the event me->screenTimeEvt in this
|
|
|
+case) to the active object after the requested number of clock ticks. Chapters
|
|
|
+6 and 7 of \ref PSiCC2 talk about time events in detail.
|
|
|
+
|
|
|
+\li (6) The exit action from the "demo" state disarms the me->screenTimeEvt time
|
|
|
+event. This cleanup is necessary when the state can be exited by a different
|
|
|
+event than the time event, such as the \c PLAYER_TRIGGER transition.
|
|
|
+
|
|
|
+\li (7) The \c SCREEN_TIMEOUT transition to "screen_saver" is triggered by the
|
|
|
+expiration of the me->screenTimeEvt time event. The signal \c SCREEN_TIMEOUT
|
|
|
+is assigned to this time event upon initialization and cannot be changed
|
|
|
+later.
|
|
|
+
|
|
|
+\li (8) The transition triggered by \c PLAYER_TRIGGER applies equally to the two
|
|
|
+substates of the "screen_saver" superstate.
|
|
|
+
|
|
|
+
|
|
|
+\section mines 5.4 The Mine Components
|
|
|
+
|
|
|
+Mines are also modeled as hierarchical state machines, but are not active
|
|
|
+objects. Instead, Mines are components of the Tunnel active object and share
|
|
|
+its event queue and priority level. The Tunnel active object communicates with
|
|
|
+the Mine components synchronously by directly dispatching events to them via
|
|
|
+the function QHsm_dispatch(). Mines communicate with Tunnel and all other
|
|
|
+active objects asynchronously by posting events to their event queues via the
|
|
|
+function QActive_postFIFO().
|
|
|
+
|
|
|
+\note Active objects exchange events asynchronously, meaning that the sender
|
|
|
+of the event merely posts the event to the event queue of the recipient active
|
|
|
+object without waiting for the completion of the event processing. In
|
|
|
+contrast, synchronous event processing corresponds to a function call (e.g.,
|
|
|
+QHsm_dispatch()), which processes the event in the caller's thread of
|
|
|
+execution.
|
|
|
+
|
|
|
+\anchor F5s4
|
|
|
+\image html Fig1.08.jpg "Figure 5-4 The Table active object manages two types of Mines."
|
|
|
+
|
|
|
+As shown in \ref F5s4 "Figure 5-4", Tunnel maintains the data member mines[],
|
|
|
+which is an array of pointers to hierarchical state machines (QHsm *). Each of
|
|
|
+these pointers can point either to a Mine1 object, a Mine2 object, or NULL, if
|
|
|
+the entry is unused. Please note that Tunnel "knows" the Mines only as generic
|
|
|
+state machines (pointers to the QHsm structure defined in QP). Tunnel
|
|
|
+dispatches events to Mines uniformly, without differentiating between
|
|
|
+different types of Mines. Still, each Mine state machine handles the events it
|
|
|
+its specific way. For example, Mine type 2 checks for collision with the
|
|
|
+Missile differently than with the Ship while Mine type 1 handles both
|
|
|
+identically.
|
|
|
+
|
|
|
+\note The last point is actually very interesting. Dispatching the same event
|
|
|
+to different Mine objects results in different behavior, specific to the type
|
|
|
+of the Mine, which in OOP is known as polymorphism. I'll have more to say
|
|
|
+about this in Chapter 3 of \ref PSiCC2.
|
|
|
+
|
|
|
+Each Mine object is fairly autonomous. The Mine maintains its own position and
|
|
|
+is responsible for informing the Tunnel object whenever the Mine gets
|
|
|
+destroyed or scrolls out of the display. This information is vital for the
|
|
|
+Tunnel object so that it can keep track of the unused Mines.
|
|
|
+
|
|
|
+\ref F5s5 "Figure 5-5" shows a hierarchical state machine of Mine2 state machine.
|
|
|
+Mine1 is very similar, except that it uses the same bitmap for testing
|
|
|
+collisions with the Missile and the Ship.
|
|
|
+
|
|
|
+\anchor F5s5
|
|
|
+\image html Fig1.09.jpg "Figure 5-5 Mine2 state machine diagram."
|
|
|
+
|
|
|
+\li (1) The Mine starts in the "unused" state.
|
|
|
+
|
|
|
+\li (2) The Tunnel object plants a Mine by dispatching the <TT>MINE_PLANT(x,
|
|
|
+y)</TT> event to the Mine. The Tunnel provides the <TT>(x, y)</TT> coordinates
|
|
|
+as the original position of the Mine.
|
|
|
+
|
|
|
+\li (3) When the Mine scrolls off the display the state
|
|
|
+machine transitions to "unused".
|
|
|
+
|
|
|
+\li (4) When the Mine hits the Ship the state machine transitions to "unused".
|
|
|
+
|
|
|
+\li (5) When the Mine scrolls finishes exploding the state machine transitions to
|
|
|
+"unused".
|
|
|
+
|
|
|
+\li (6) When the Mine is recycled by the Tunnel object the state machine
|
|
|
+transitions to "unused".
|
|
|
+
|
|
|
+\li (7) The exit action in the "unused" state posts the MINE_DISABLDED(mine_id)
|
|
|
+event to the Tunnel active object. Through this event, the Mine informs the
|
|
|
+Tunnel that it's becoming disabled, so that Tunnel can update its
|
|
|
+<TT>mines[]</TT> array (see also \ref F5s4 "Figure 5-4"(4)). The mine_id parameter
|
|
|
+of the event becomes the index into the <TT>mines[]</TT> array. Please note
|
|
|
+that generating the <TT>MINE_DISABLDED(mine_id)</TT> event in the exit action
|
|
|
+from "used" is much safer and more maintainable than repeating this action in
|
|
|
+each individual transition (3), (4), (5), and (6).
|
|
|
+
|
|
|
+Prev: \ref design \n
|
|
|
+Next: \ref events
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/** \page events 6. Defining Event Signals and Event Parameters
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref active_objects \n
|
|
|
+Next: \ref coding_hsm
|
|
|
+
|
|
|
+The key events in the "Fly 'n' Shoot" game have been identified in the
|
|
|
+sequence diagram in \ref F4s1 "Figure 4-1". Other events have been invented during
|
|
|
+the state machine design stage. In any case, you must have noticed that events
|
|
|
+consist really of two parts. The part of the event called the signal conveys
|
|
|
+the type of the occurrence (what happened). For example, the \c TIME_TICK
|
|
|
+signal conveys the arrival of a time tick, while \c PLAYER_SHIP_MOVE signal
|
|
|
+conveys that the player wants to move the Ship. An event can also contain
|
|
|
+additional quantitative information about the occurrence in form of event
|
|
|
+parameters. For example, the \c PLAYER_SHIP_MOVE signal is accompanied by the
|
|
|
+parameters <TT>(x, y)</TT> that contain the quantitative information as to
|
|
|
+where exactly to move the Ship. In QP, events are represented as instances of
|
|
|
+the QEvent structure provided by the framework. Specifically, the QEvent
|
|
|
+structure contains the member sig, to represent the signal of that event.
|
|
|
+Event parameters are added in the process of inheritance, as described in the
|
|
|
+sidebar \ref derivation.
|
|
|
+
|
|
|
+\section enumerating 6.1 Enumerating Event Signals and Defining Event Parameters
|
|
|
+
|
|
|
+Because events are explicitly shared among most of the application components,
|
|
|
+it is convenient to declare them in the separate header file game.h shown
|
|
|
+\ref L6s1 "Listing 6-1". The explanation section immediately following the
|
|
|
+listing illuminates the interesting points.
|
|
|
+
|
|
|
+\anchor L6s1
|
|
|
+<STRONG>Listing 6-1 Signals, event structures, and active object interfaces
|
|
|
+defined in file game.h.</STRONG>
|
|
|
+\code
|
|
|
+ (1) enum GameSignals { /* signals used in the game */
|
|
|
+ (2) TIME_TICK_SIG = Q_USER_SIG, /* published from tick ISR */
|
|
|
+ PLAYER_TRIGGER_SIG, /* published by Player (ISR) to trigger the Missile */
|
|
|
+ PLAYER_QUIT_SIG, /* published by Player (ISR) to quit the game */
|
|
|
+ GAME_OVER_SIG, /* published by Ship when it finishes exploding */
|
|
|
+ /* insert other published signals here ... */
|
|
|
+ (3) MAX_PUB_SIG, /* the last published signal */
|
|
|
+
|
|
|
+ PLAYER_SHIP_MOVE_SIG, /* posted by Player (ISR) to the Ship to move it */
|
|
|
+ BLINK_TIMEOUT_SIG, /* signal for Tunnel's blink timeout event */
|
|
|
+ SCREEN_TIMEOUT_SIG, /* signal for Tunnel's screen timeout event */
|
|
|
+ TAKE_OFF_SIG, /* from Tunnel to Ship to grant permission to take off */
|
|
|
+ HIT_WALL_SIG, /* from Tunnel to Ship when Ship hits the wall */
|
|
|
+ HIT_MINE_SIG, /* from Mine to Ship or Missile when it hits the mine */
|
|
|
+ SHIP_IMG_SIG, /* from Ship to the Tunnel to draw and check for hits */
|
|
|
+ MISSILE_IMG_SIG, /* from Missile the Tunnel to draw and check for hits */
|
|
|
+ MINE_IMG_SIG, /* sent by Mine to the Tunnel to draw the mine */
|
|
|
+ MISSILE_FIRE_SIG, /* sent by Ship to the Missile to fire */
|
|
|
+ DESTROYED_MINE_SIG, /* from Missile to Ship when Missile destroyed Mine */
|
|
|
+ EXPLOSION_SIG, /* from any exploding object to render the explosion */
|
|
|
+ MINE_PLANT_SIG, /* from Tunnel to the Mine to plant it */
|
|
|
+ MINE_DISABLED_SIG, /* from Mine to Tunnel when it becomes disabled */
|
|
|
+ MINE_RECYCLE_SIG, /* sent by Tunnel to Mine to recycle the mine */
|
|
|
+ SCORE_SIG, /* from Ship to Tunnel to adjust game level based on score */
|
|
|
+ /* insert other signals here ... */
|
|
|
+ (4) MAX_SIG /* the last signal (keep always last) */
|
|
|
+ };
|
|
|
+
|
|
|
+ (5) typedef struct ObjectPosEvtTag {
|
|
|
+ (6) QEvent super; /* extend the QEvent class */
|
|
|
+ (7) uint8_t x; /* the x-position of the object */
|
|
|
+ (8) uint8_t y; /* new y-position of the object */
|
|
|
+ } ObjectPosEvt;
|
|
|
+
|
|
|
+ typedef struct ObjectImageEvtTag {
|
|
|
+ QEvent super; /* extend the QEvent class */
|
|
|
+ uint8_t x; /* the x-position of the object */
|
|
|
+ int8_t y; /* the y-position of the object */
|
|
|
+ uint8_t bmp; /* the bitmap ID representing the object */
|
|
|
+ } ObjectImageEvt;
|
|
|
+
|
|
|
+ typedef struct MineEvtTag {
|
|
|
+ QEvent super; /* extend the QEvent class */
|
|
|
+ uint8_t id; /* the ID of the Mine */
|
|
|
+ } MineEvt;
|
|
|
+
|
|
|
+ typedef struct ScoreEvtTag {
|
|
|
+ QEvent super; /* extend the QEvent class */
|
|
|
+ uint16_t score; /* the current score */
|
|
|
+ } ScoreEvt;
|
|
|
+
|
|
|
+ /* opaque pointers to active objects in the application */
|
|
|
+ (9) extern QActive * const AO_Tunnel;
|
|
|
+(10) extern QActive * const AO_Ship;
|
|
|
+(11) extern QActive * const AO_Missile;
|
|
|
+
|
|
|
+ /* active objects' "constructors" */
|
|
|
+(12) void Tunnel_ctor(void);
|
|
|
+(13) void Ship_ctor(void);
|
|
|
+(14) void Missile_ctor(void);
|
|
|
+\endcode
|
|
|
+
|
|
|
+\li (1) In QP, signals of events are simply enumerated constants. Placing all
|
|
|
+signals in a single enumeration is particularly convenient to avoid
|
|
|
+inadvertent overlap in the numerical values of different signals.
|
|
|
+
|
|
|
+\li (2) The application-level signals do not start from zero but rather are offset
|
|
|
+by the constant #Q_USER_SIG. This is because QP reserves the lowest few
|
|
|
+signals for the internal use and provides the constant #Q_USER_SIG as an
|
|
|
+offset from which user-level signals can start. Please also note that by
|
|
|
+convention, I attach the suffix \c _SIG to all signals so that I can easily
|
|
|
+distinguish signals from other constants. I drop the suffix \c _SIG in the
|
|
|
+state diagrams to reduce the clutter.
|
|
|
+
|
|
|
+\li (3) The constant \c MAX_PUB_SIG delimits the published signals from the rest.
|
|
|
+The publish-subscribe event delivery mechanism consumes some RAM, which is
|
|
|
+proportional to the number of published signals. I save some RAM by providing
|
|
|
+the lower limit of published signals to QP (\c MAX_PUB_SIG) rather than
|
|
|
+maximum of all signals used in the application. (See also
|
|
|
+\ref L3s1 "Listing 3-1"(9)).
|
|
|
+
|
|
|
+\li (4) The last enumeration \c MAX_SIG indicates the maximum of all signals used
|
|
|
+in the application.
|
|
|
+
|
|
|
+\li (5) The event structure \c ObjectPosEvt defines a "class" of events that
|
|
|
+convey the object's position on the display in the event parameters.
|
|
|
+
|
|
|
+\li (6) The structure \c ObjectPosEvt derives from the base structure QEvent, as
|
|
|
+explained in the sidebar \ref derivation.
|
|
|
+
|
|
|
+\li (7-8) The structure \c ObjectPosEvt adds parameters \c x and \c y, which are
|
|
|
+coordinates of the object on the display.
|
|
|
+
|
|
|
+\li (9-11) These global pointers represent active objects in the application and
|
|
|
+are used for posting events directly to active objects. Because the pointers
|
|
|
+can be initialized at compile time, I like to declare them const, sot that
|
|
|
+they can be placed in ROM. The active object pointers are "opaque", because
|
|
|
+they cannot access the whole active object, but only the part inherited from
|
|
|
+the QActive structure. I'll have more to say about this in the next section.
|
|
|
+
|
|
|
+\li (12-14) These functions perform an early initialization of the active objects
|
|
|
+in the system. They play the role of static "constructors", which in C you
|
|
|
+need to call explicitly, typically at the beginning of main() (see also
|
|
|
+\ref L3s1 "Listing 3-1"(10-12)).
|
|
|
+
|
|
|
+\section generating 6.2 Generating, Posting, and Publishing Events
|
|
|
+
|
|
|
+The QF framework supports two types of asynchronous event exchange:
|
|
|
+
|
|
|
+-# The simple mechanism of direct event posting supported through the
|
|
|
+functions QActive_postFIFO() and QActive_postLIFO(), where the producer of an
|
|
|
+event directly posts the event to the event queue of the consumer active
|
|
|
+object.
|
|
|
+-# A more sophisticated publish-subscribe event delivery mechanism supported
|
|
|
+through the functions QF_publish() and QActive_subscribe(), where the
|
|
|
+producers of the events "publish" them to the framework, and the framework
|
|
|
+then delivers the events to all active objects that had "subscribed" to these
|
|
|
+events.
|
|
|
+
|
|
|
+In QF, any part of the system can produce events, not necessarily only the
|
|
|
+active objects. For example, interrupt service routines (ISRs) or device
|
|
|
+drivers can also produce events. On the other hand, only active objects can
|
|
|
+consume events, because only active objects have event queues.
|
|
|
+
|
|
|
+\note QF also provides "raw" thread-safe event queues (struct QEQueue), which
|
|
|
+can consume events as well. These "raw" thread-safe queues cannot block and
|
|
|
+are intended to deliver events to ISRs or device drivers. Please refer to
|
|
|
+Chapter 7 of \ref PSiCC2 for more details.
|
|
|
+
|
|
|
+The most important characteristic of event management in QF is that the
|
|
|
+framework passes around only pointers to events, not the events themselves. QF
|
|
|
+never copies the events by value ("zero-copy" policy); even in case of
|
|
|
+publishing events that often involves multicasting the same event to multiple
|
|
|
+subscribers. The actual event instances are either constant events statically
|
|
|
+allocated at compile time, or dynamic events allocated at runtime from one of
|
|
|
+the event pools that the framework manages. \ref L6s2 "Listing 6-2" provides
|
|
|
+examples of publishing static events and posting dynamic events from the
|
|
|
+interrupt service routines (ISRs) of the "Fly 'n' Shoot" version for the
|
|
|
+ARM-Cortex board (file \c
|
|
|
+<qpc>\\examples\\arm-cortex\\vanilla\\iar\\game-ev-lm3s811\\bsp.c). In
|
|
|
+the upcoming Section \ref coding_hsm you will see other examples of event
|
|
|
+posting from active objects in the state machine code.
|
|
|
+
|
|
|
+\anchor L6s2
|
|
|
+<STRONG>Listing 6-2 Generating, posting , and publishing events from the ISRs
|
|
|
+in bsp.c for the ARM-Cortex board.</STRONG>
|
|
|
+\code
|
|
|
+ (1) void ISR_SysTick(void) {
|
|
|
+ (2) static QEvent const tickEvt = { TIME_TICK_SIG, 0 };
|
|
|
+ (3) QF_publish(&tickEvt); /* publish the tick event to all subscribers */
|
|
|
+ (4) QF_tick(); /* process all armed time events */
|
|
|
+ }
|
|
|
+ /*..........................................................................*/
|
|
|
+ (5) void ISR_ADC(void) {
|
|
|
+ static uint32_t adcLPS = 0; /* Low-Pass-Filtered ADC reading */
|
|
|
+ static uint32_t wheel = 0; /* the last wheel position */
|
|
|
+ unsigned long tmp;
|
|
|
+
|
|
|
+ ADCIntClear(ADC_BASE, 3); /* clear the ADC interrupt */
|
|
|
+ (6) ADCSequenceDataGet(ADC_BASE, 3, &tmp); /* read the data from the ADC */
|
|
|
+
|
|
|
+ /* 1st order low-pass filter: time constant ~= 2^n samples
|
|
|
+ * TF = (1/2^n)/(z-((2^n - 1)/2^n)),
|
|
|
+ * e.g., n=3, y(k+1) = y(k) - y(k)/8 + x(k)/8 => y += (x - y)/8
|
|
|
+ */
|
|
|
+ (7) adcLPS += (((int)tmp - (int)adcLPS + 4) >> 3); /* Low-Pass-Filter */
|
|
|
+
|
|
|
+ /* compute the next position of the wheel */
|
|
|
+ (8) tmp = (((1 << 10) - adcLPS)*(BSP_SCREEN_HEIGHT - 2)) >> 10;
|
|
|
+
|
|
|
+ if (tmp != wheel) { /* did the wheel position change? */
|
|
|
+ (9) ObjectPosEvt *ope = Q_NEW(ObjectPosEvt, PLAYER_SHIP_MOVE_SIG);
|
|
|
+(10) ope->x = (uint8_t)GAME_SHIP_X; /* x-position is fixed */
|
|
|
+(11) ope->y = (uint8_t)tmp;
|
|
|
+(12) QActive_postFIFO(AO_ship, (QEvent *)ope); /* post to the Ship AO */
|
|
|
+ wheel = tmp; /* save the last position of the wheel */
|
|
|
+ }
|
|
|
+ . . .
|
|
|
+ }
|
|
|
+\endcode
|
|
|
+
|
|
|
+\li (1) In the case of the ARM-Cortex board, the function ISR_SysTick() services
|
|
|
+the system clock tick ISR generated by the ARM-Cortex system tick timer.
|
|
|
+
|
|
|
+\li (2) The \c TIME_TICK event never changes, so it can be statically allocated
|
|
|
+just once. This event is declared as const, which means that it can be placed
|
|
|
+in ROM. The initializer list for this event consists of the signal
|
|
|
+\c TIME_TICK_SIG followed by zero. This zero informs the QF framework that
|
|
|
+this event is static and should never be recycled to an event pool.
|
|
|
+
|
|
|
+\li (3) The ISR calls the framework function QF_publish(), which takes the pointer
|
|
|
+to the tickEvt event to deliver to all subscribers.
|
|
|
+
|
|
|
+\li (4) The ISR calls the function QF_tick(), in which it the framework manages
|
|
|
+the armed time events.
|
|
|
+
|
|
|
+\li (5) The function \c ISR_ADC() services the ADC conversions, which ultimately
|
|
|
+deliver the position of the Ship.
|
|
|
+
|
|
|
+\li (6) The ISR reads the data from the ADC.
|
|
|
+
|
|
|
+\li (7-8) A low-pass filter is applied to the raw ADC reading and the
|
|
|
+potentiometer wheel position is computed.
|
|
|
+
|
|
|
+\li (9) The QF macro <TT>Q_NEW(ObjectPosEvt, PLAYER_SHIP_MOVE_SIG)</TT>
|
|
|
+dynamically allocates an instance of the ObjectPosEvt event from an event pool
|
|
|
+managed by QF. The macro also performs the association between the signal
|
|
|
+\c PLAYER_SHIP_MOVE_SIG and the allocated event. The Q_NEW() macro returns the
|
|
|
+pointer to the allocated event.
|
|
|
+
|
|
|
+\note The <TT>PLAYER_SHIP_MOVE(x, y)</TT> event is an example of an event with
|
|
|
+changing parameters. In general, such an event cannot be allocated statically
|
|
|
+(like the \c TIME_TICK event at label (2)) because it can change
|
|
|
+asynchronously next time the ISR executes. Some active objects in the system
|
|
|
+might still be referring to the event via a pointer, so the event should not
|
|
|
+be changing. Dynamic event allocation of QF solves all such concurrency
|
|
|
+issues, because every time a new event is allocated. QF then recycles the
|
|
|
+dynamic events, after it determines that all active objects are done with
|
|
|
+accessing the events.
|
|
|
+
|
|
|
+\li (10-11) The \c x and \c y parameters of the event are assigned.
|
|
|
+
|
|
|
+\li (12) The dynamic event is posted directly to the Ship active object.
|
|
|
+
|
|
|
+Prev: \ref active_objects \n
|
|
|
+Next: \ref coding_hsm
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page coding_hsm 7. Coding Hierarchical State Machines
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref events \n
|
|
|
+Next: \ref execution
|
|
|
+
|
|
|
+Contrary to widespread misconceptions, you don't need big design automation
|
|
|
+tools to translate hierarchical state machines (UML statecharts) into
|
|
|
+efficient and highly maintainable C or C++. This section explains how to
|
|
|
+hand-code the Ship state machine from \ref F5s2 "Figure 5-2" with the help of the
|
|
|
+QF real-time framework and the QEP hierarchical processor, which is also part
|
|
|
+of the QP event-driven platform. Once you know how to code this state machine,
|
|
|
+you know how to code them all.
|
|
|
+
|
|
|
+The source code for the Ship state machine is found in the file \c ship.c
|
|
|
+located either in the DOS version or the ARM-Cortex version of the "Fly 'n'
|
|
|
+Shoot" game. I break the explanation of this file into <STRONG>three</STRONG>
|
|
|
+steps.
|
|
|
+
|
|
|
+\section step1 7.1 Step 1: Defining the Ship Structure
|
|
|
+
|
|
|
+In the first step you define the Ship data structure. Just like in case of
|
|
|
+events, you use inheritance to derive the Ship structure from the framework
|
|
|
+structure QActive (see the sidebar \ref derivation). Creating this inheritance
|
|
|
+relationship ties the Ship structure to the QF framework. The main
|
|
|
+responsibility of the QActive base structure is to store the information about
|
|
|
+the current active state of the state machine, as well as the event queue and
|
|
|
+priority level of the Ship active object. In fact, QActive itself derives from
|
|
|
+a simpler QEP structure QHsm that represents just the current active state of
|
|
|
+a hierarchical state machine. On top of that information, almost every state
|
|
|
+machine must also store other "extended-state" information. For example, the
|
|
|
+Ship object is responsible for maintaining the Ship position as well as the
|
|
|
+score accumulated in the game. You supply this additional information by means
|
|
|
+of data members enlisted after the base structure member super, as shown in
|
|
|
+\ref L7s1 "Listing 7-1".
|
|
|
+
|
|
|
+\anchor L7s1
|
|
|
+<STRONG>Listing 7-1 Deriving the Ship structure in file ship.c.</STRONG>
|
|
|
+\code
|
|
|
+ (1) #include "qp_port.h" /* the QP port */
|
|
|
+ (2) #include "bsp.h" /* Board Support Package */
|
|
|
+ (3) #include "game.h" /* this application */
|
|
|
+
|
|
|
+ /* local objects -----------------------------------------------------------*/
|
|
|
+ (4) typedef struct ShipTag {
|
|
|
+ (5) QActive super; /* derive from the QActive struct */
|
|
|
+ (6) uint8_t x; /* x-coordinate of the Ship position on the display */
|
|
|
+ (7) uint8_t y; /* y-coordinate of the Ship position on the display */
|
|
|
+ (8) uint8_t exp_ctr; /* explosion counter, used to animate explosions */
|
|
|
+ (9) uint16_t score; /* running score of the game */
|
|
|
+(10) } Ship; /* the typedef-ed name for the Ship struct */
|
|
|
+
|
|
|
+ /* state handler functions... */
|
|
|
+(11) static QState Ship_active (Ship *me, QEvent const *e);
|
|
|
+(12) static QState Ship_parked (Ship *me, QEvent const *e);
|
|
|
+(13) static QState Ship_flying (Ship *me, QEvent const *e);
|
|
|
+(14) static QState Ship_exploding(Ship *me, QEvent const *e);
|
|
|
+
|
|
|
+(15) static QState Ship_initial (Ship *me, QEvent const *e);
|
|
|
+
|
|
|
+(16) static Ship l_ship; /* the sole instance of the Ship active object */
|
|
|
+
|
|
|
+ /* global objects ----------------------------------------------------------*/
|
|
|
+(17) QActive * const AO_ship = (QActive *)&l_ship; /* opaque pointer to Ship AO */
|
|
|
+\endcode
|
|
|
+
|
|
|
+\li (1) Every application-level C-file that uses the QP platform must include the
|
|
|
+\c qp_port.h header file.
|
|
|
+
|
|
|
+\li (2) The \c bsp.h header file contains the interface to the
|
|
|
+Board Support Package.
|
|
|
+
|
|
|
+\li (3) The \c game.h header file contains the declarations of
|
|
|
+events and other facilities shared among the components of the application
|
|
|
+\li (see \ref L6s1 "Listing 6-1").
|
|
|
+
|
|
|
+\li (4) This structure defines the Ship active object.
|
|
|
+
|
|
|
+\note I like to keep active objects, and indeed all state machine objects
|
|
|
+(such as Mines), strictly encapsulated. Therefore, I don't put the state
|
|
|
+machine structure definitions in header files but rather define them right in
|
|
|
+the implementation file, such as ship.c. That way, I can be sure that the
|
|
|
+internal data members of the Ship structure are not known to any other parts
|
|
|
+of the application.
|
|
|
+
|
|
|
+\li (5) The Ship active object structure derives from the framework structure
|
|
|
+QActive, as described in the sidebar \ref derivation.
|
|
|
+
|
|
|
+\li (6-7) The x and y data members represent the position of the Ship on the
|
|
|
+display.
|
|
|
+
|
|
|
+\li (8) The exp_ctr member is used for pacing the explosion animation (see
|
|
|
+also the "exploding" state in the Ship state diagram in \ref F5s2 "Figure 5-2").
|
|
|
+
|
|
|
+\li (9) The score member stores the accumulated score in the game.
|
|
|
+
|
|
|
+\li (10) I use the typedef to define the shorter name Ship equivalent to struct
|
|
|
+ShipTag.
|
|
|
+
|
|
|
+\li (11-14) These four functions are called state-handler functions because they
|
|
|
+correspond one-to-one to the states of the Ship state machine shown in
|
|
|
+\ref F5s2 "Figure 5-2". For example, the \c Ship_active() function represents the
|
|
|
+"active" state. The QEP event processor calls the state handler functions to
|
|
|
+realize the UML semantics of state machine execution. \c
|
|
|
+<qpc>\\include\\qep.h. All state handler functions have the same
|
|
|
+signature. A state handler function takes the state machine pointer and the
|
|
|
+event pointer as arguments, and returns the status of the operation back to
|
|
|
+the QEP event processor, for example whether the event was handled or not. The
|
|
|
+return type QState is typedef-ed to \c uint8_t in the header file
|
|
|
+\c <qpc>\\include\\qep.h.
|
|
|
+
|
|
|
+\note I use a simple naming convention to strengthen the association between
|
|
|
+the structures and the functions designed to operate on these structures.
|
|
|
+First, I name the functions by combining the typedef'ed structure name with
|
|
|
+the name of the operation (e.g., \c Ship_active). Second, I always place the
|
|
|
+pointer to the structure as the first argument of the associated function and
|
|
|
+I always name this argument <TT>me</TT> (e.g., <TT>Ship_active(Ship *me,
|
|
|
+...)</TT>).
|
|
|
+
|
|
|
+\li (16) In addition to state handler functions, every state machine must declare
|
|
|
+the initial pseudostate, which QEP invokes to execute the top-most initial
|
|
|
+transition (see \ref F5s2 "Figure 5-2"(1)). The initial pseudostate handler
|
|
|
+has signature identical to the regular state handler function.
|
|
|
+
|
|
|
+\li (17) In this line I statically allocate the storage for the Ship active
|
|
|
+object. Please note that the object l_ship is defined static, so that it is
|
|
|
+accessible only locally at the file scope of the ship.c file.
|
|
|
+
|
|
|
+\li (18) In this line I define and initialize the global pointer AO_Ship to the
|
|
|
+Ship active object (see also \ref L6s1 "Listing 6-1"(10)). This pointer is
|
|
|
+"opaque", because it treats the Ship object as the generic QActive base
|
|
|
+structure, rather than the specific Ship structure. The power of an "opaque"
|
|
|
+pointer is that it allows me to completely hide the definition of the Ship
|
|
|
+structure and make it inaccessible to the rest of the application. Still, the
|
|
|
+other application components can access the Ship object to post events
|
|
|
+directly to it via the QActive_postFIFO(QActive *me, QEvent const *e)
|
|
|
+function.
|
|
|
+
|
|
|
+\section step2 7.2 Step 2: Initializing the State Machine
|
|
|
+
|
|
|
+The state machine initialization is divided into the following two steps for
|
|
|
+increased flexibility and better control of the initialization timeline:
|
|
|
+
|
|
|
+-# The state machine "constructor"; and
|
|
|
+-# The top-most initial transition.
|
|
|
+
|
|
|
+The state machine "constructor", such as \c Ship_ctor(), intentionally does
|
|
|
+not execute the top-most initial transition defined in the initial pseudostate
|
|
|
+because at that time some vital objects can be missing and critical hardware
|
|
|
+might not be properly initialized yet3. Instead, the state machine
|
|
|
+"constructor" merely puts the state machine in the initial pseudostate. Later,
|
|
|
+the user code must trigger the top-most initial transition explicitly, which
|
|
|
+happens actually inside the function QActive_start() (see
|
|
|
+\ref L3s1 "Listing 3-11"(18-20)). \ref L7s2 "Listing 7-2" shows the instantiation
|
|
|
+(the "constructor" function) and initialization (the initial pseudostate) of
|
|
|
+the Ship active object.
|
|
|
+
|
|
|
+\anchor L7s2
|
|
|
+<STRONG>Listing 7-2 Instantiation and Initialization of the Ship active object
|
|
|
+in ship.c.</STRONG>
|
|
|
+\code
|
|
|
+ (1) void Ship_ctor(void) { /* instantiation */
|
|
|
+ (2) Ship *me = &l_ship;
|
|
|
+ (3) QActive_ctor(&me->super, (QStateHandler)&Ship_initial);
|
|
|
+ (4) me->x = GAME_SHIP_X;
|
|
|
+ (5) me->y = GAME_SHIP_Y;
|
|
|
+ }
|
|
|
+ /*..........................................................................*/
|
|
|
+ (6) QState Ship_initial(Ship *me, QEvent const *e) { /* initialization */
|
|
|
+ (7) QActive_subscribe((QActive *)me, TIME_TICK_SIG);
|
|
|
+ (8) QActive_subscribe((QActive *)me, PLAYER_TRIGGER_SIG);
|
|
|
+
|
|
|
+ (9) return Q_TRAN(&Ship_active); /* top-most initial transition */
|
|
|
+ }
|
|
|
+\endcode
|
|
|
+
|
|
|
+\li (1) The global function Ship_ctor() is prototyped in game.h and called at the
|
|
|
+beginning of main().
|
|
|
+
|
|
|
+\li (2) The "me" pointer points to the statically allocated Ship object (see
|
|
|
+\ref L7s1 "Listing 7-1"(16)).
|
|
|
+
|
|
|
+\li (3) Every derived structure is responsible for initializing the part inherited
|
|
|
+from the base structure. The "constructor" QActive_ctor() puts the state
|
|
|
+machine in the initial pseudostate &Ship_initial. (see
|
|
|
+\ref L6s1 "Listing 6-1"(15)).
|
|
|
+
|
|
|
+\li (4-5) The Ship position is initialized.
|
|
|
+
|
|
|
+\li (6) The Ship_initial() function defines the top-most initial transition in the
|
|
|
+Ship state machine (see \ref F5s2 "Figure 5-2"(1)).
|
|
|
+
|
|
|
+\li (7-8) The Ship active object subscribes to signals \c TIME_TICK_SIG and
|
|
|
+\c PLAYER_TRIGGER_SIG, as specified in the state diagram in
|
|
|
+\ref F5s2 "Figure 5-2"(1).
|
|
|
+
|
|
|
+\li (9) The initial state "active" is specified by invoking the QP macro Q_TRAN().
|
|
|
+
|
|
|
+\note The macro #Q_TRAN() must always follow the return statement.
|
|
|
+
|
|
|
+
|
|
|
+\section step3 7.3 Step 3: Defining State Handler Functions
|
|
|
+
|
|
|
+In the last step, you actually code the Ship state machine by implementing one
|
|
|
+state at a time as a state handler function. To determine what elements belong
|
|
|
+the any given state handler function, you follow around the state's boundary
|
|
|
+in the diagram (\ref F5s2 "Figure 5-2"). You need to implement all transitions
|
|
|
+originating at the boundary, any entry and exit actions defined in the state,
|
|
|
+as well as all internal transitions enlisted directly in the state.
|
|
|
+Additionally, if there is an initial transition embedded directly in the
|
|
|
+state, you need to implement it as well.
|
|
|
+
|
|
|
+Take for example the state "flying" shown in \ref F5s2 "Figure 5-2". This state
|
|
|
+has an entry action and two transitions originating at its boundary:
|
|
|
+\c HIT_WALL and <TT>HIT_MINE(type)</TT>, as well as three internal transitions
|
|
|
+\c TIME_TICK, \c PLAYER_TRIGGER, and <TT>DESTROYED_MINE(score)</TT>. The
|
|
|
+"flying" state nests inside the "active" superstate. \ref L7s3 "Listing 7-3"
|
|
|
+shows two state handler functions of the Ship state machine from
|
|
|
+\ref F5s2 "Figure 5-2". The state handler functions correspond to the states
|
|
|
+"active" and "flying", respectively. The explanation section immediately
|
|
|
+following the listing highlights the important implementation techniques.
|
|
|
+
|
|
|
+\anchor L7s3
|
|
|
+<STRONG>Listing 7-3 State handler functions for states "active" and "flying"
|
|
|
+in ship.c.</STRONG>
|
|
|
+\code
|
|
|
+ (1) QState Ship_active(Ship *me, QEvent const *e) {
|
|
|
+ (2) switch (e->sig) {
|
|
|
+ (3) case Q_INIT_SIG: { /* nested initial transition */
|
|
|
+ (4) /* any actions associated with the initial transition */
|
|
|
+ (5) return Q_TRAN(&Ship_parked);
|
|
|
+ }
|
|
|
+ (6) case PLAYER_SHIP_MOVE_SIG: {
|
|
|
+ (7) me->x = ((ObjectPosEvt const *)e)->x;
|
|
|
+ (8) me->y = ((ObjectPosEvt const *)e)->y;
|
|
|
+ (9) return Q_HANDLED();
|
|
|
+ }
|
|
|
+ }
|
|
|
+(10) return Q_SUPER(&QHsm_top); /* return the superstate */
|
|
|
+ }
|
|
|
+ /*..........................................................................*/
|
|
|
+ QState Ship_flying(Ship *me, QEvent const *e) {
|
|
|
+ switch (e->sig) {
|
|
|
+(11) case Q_ENTRY_SIG: {
|
|
|
+(12) ScoreEvt *sev;
|
|
|
+
|
|
|
+ me->score = 0; /* reset the score */
|
|
|
+(13) sev = Q_NEW(ScoreEvt, SCORE_SIG);
|
|
|
+(14) sev->score = me->score;
|
|
|
+(15) QActive_postFIFO(AO_Tunnel, (QEvent *)sev);
|
|
|
+(16) return Q_HANDLED();
|
|
|
+ }
|
|
|
+ case TIME_TICK_SIG: {
|
|
|
+ /* tell the Tunnel to draw the Ship and test for hits */
|
|
|
+ ObjectImageEvt *oie = Q_NEW(ObjectImageEvt, SHIP_IMG_SIG);
|
|
|
+ oie->x = me->x;
|
|
|
+ oie->y = me->y;
|
|
|
+ oie->bmp = SHIP_BMP;
|
|
|
+ QActive_postFIFO(AO_Tunnel, (QEvent *)oie);
|
|
|
+
|
|
|
+ ++me->score; /* increment the score for surviving another tick */
|
|
|
+
|
|
|
+ if ((me->score % 10) == 0) { /* is the score "round"? */
|
|
|
+ ScoreEvt *sev = Q_NEW(ScoreEvt, SCORE_SIG);
|
|
|
+ sev->score = me->score;
|
|
|
+ QActive_postFIFO(AO_Tunnel, (QEvent *)sev);
|
|
|
+ }
|
|
|
+ return Q_HANDLED();
|
|
|
+ }
|
|
|
+ case PLAYER_TRIGGER_SIG: { /* trigger the Missile */
|
|
|
+ ObjectPosEvt *ope = Q_NEW(ObjectPosEvt, MISSILE_FIRE_SIG);
|
|
|
+ ope->x = me->x;
|
|
|
+ ope->y = me->y + SHIP_HEIGHT - 1;
|
|
|
+ QActive_postFIFO(AO_Missile, (QEvent *)ope);
|
|
|
+ return Q_HANDLED();
|
|
|
+ }
|
|
|
+ case DESTROYED_MINE_SIG: {
|
|
|
+ me->score += ((ScoreEvt const *)e)->score;
|
|
|
+ /* the score will be sent to the Tunnel by the next TIME_TICK */
|
|
|
+ return Q_HANDLED();
|
|
|
+ }
|
|
|
+(17) case HIT_WALL_SIG:
|
|
|
+(18) case HIT_MINE_SIG: {
|
|
|
+(19) /* any actions associated with the transition */
|
|
|
+(20) return Q_TRAN(&Ship_exploding);
|
|
|
+ }
|
|
|
+ }
|
|
|
+(21) return Q_SUPER(&Ship_active); /* return the superstate */
|
|
|
+ }
|
|
|
+\endcode
|
|
|
+
|
|
|
+\li (1) Each state handler must have the same signature, that is, it must take
|
|
|
+two parameters: the state machine pointer "me" and the pointer to QEvent. The
|
|
|
+keyword const before the '*' in the event pointer declaration means that the
|
|
|
+event pointed to by that pointer cannot be changed inside the state handler
|
|
|
+function (i.e., the event is read-only). A state handler function must return
|
|
|
+QState, which conveys the status of the event handling to the QEP event
|
|
|
+processor.
|
|
|
+
|
|
|
+\li (2) Typically, every state handler is structured as a switch statement
|
|
|
+that discriminates based on the signal of the event signal e->sig.
|
|
|
+
|
|
|
+\li (3) This line of code pertains to the nested initial transition
|
|
|
+\ref F5s2 "Figure 5-2"(2). QEP provides a reserved signal #Q_INIT_SIG that the
|
|
|
+framework passes to the state handler function when it wants to execute the
|
|
|
+initial transition.
|
|
|
+
|
|
|
+\li (4) You can enlist any actions associated with this initial transition
|
|
|
+(none in this particular case).
|
|
|
+
|
|
|
+\li (5) You designate the target substate with the Q_TRAN() macro. This macro
|
|
|
+must always follow the return statement, through which the state handler
|
|
|
+function informs the QEP event processor that the transition has been taken.
|
|
|
+
|
|
|
+\note The initial transition must necessarily target a direct or transitive
|
|
|
+substate of a given state. An initial transition cannot target a peer state or
|
|
|
+go up in state hierarchy to higher-level states, which in the UML would
|
|
|
+represent a "malformed" state machine.
|
|
|
+
|
|
|
+\li (6) This line of code pertains to the internal transition
|
|
|
+<TT>PLAYER_SHIP_MOVE_SIG(x, y)</TT> in \ref F5s2 "Figure 5-2"(3).
|
|
|
+
|
|
|
+\li (7-8) You access the data members of the Ship state machine via the "me"
|
|
|
+argument of the state handler function. You access the event parameters via
|
|
|
+the "e" argument. You need to cast the event pointer from the generic QEvent
|
|
|
+base class to the specific event structure expected for the
|
|
|
+PLAYER_SHIP_MOVE_SIG, which is ObjectPosEvt in this case.
|
|
|
+
|
|
|
+\note The association between the event signal and event structure (event
|
|
|
+parameters) is established at the time the event is generated. All recipients
|
|
|
+of that event must know about this association to perform the cast to the
|
|
|
+correct event structure.
|
|
|
+
|
|
|
+\li (9) You terminate the case statement with "<TT>return Q_HANDLED()</TT>",
|
|
|
+which informs the QEP event processor that the event has been handled (but no
|
|
|
+transition has been taken).
|
|
|
+
|
|
|
+\li (10) The final return from a state handler function designates the
|
|
|
+superstate of that state by means of the QEP macro #Q_SUPER(). The final
|
|
|
+return statement from a state handler function represents the single point of
|
|
|
+maintenance for changing the nesting level of a given state. The state
|
|
|
+"active" in \ref F5s2 "Figure 5-2" has no explicit superstate, which means
|
|
|
+that it is implicitly nested in the "top" state. The "top" state is a UML
|
|
|
+concept that denotes the ultimate root of the state hierarchy in a
|
|
|
+hierarchical state machine. QEP provides the "top" state as a state handler
|
|
|
+function QHsm_top(), and therefore the Ship_active() state handler returns the
|
|
|
+pointer &QHsm_top.
|
|
|
+
|
|
|
+\note In C and C++, a pointer-to-function QHsm_top() can be written either as
|
|
|
+QHsm_top, or &QHsm_top. Even though the notation QHsm_top is more succinct, I
|
|
|
+prefer adding the ampersand explicitly, to leave absolutely no doubt that I
|
|
|
+mean a pointer-to-function &QHsm_top.
|
|
|
+
|
|
|
+\li (11) This line of code pertains to the entry action into state "flying"
|
|
|
+(\ref F5s2 "Figure 5-2"(5)). QEP provides a reserved signal ::Q_ENTRY_SIG that
|
|
|
+the framework passes to the state handler function when it wants to execute
|
|
|
+the entry actions.
|
|
|
+
|
|
|
+\li (12) The entry action to "flying" posts the SCORE event to the Tunnel
|
|
|
+active object (\ref F5s2 "Figure 5-2"(5)). This line defines a temporary
|
|
|
+pointer to the event structure ScoreEvt.
|
|
|
+
|
|
|
+\li (13) The QF macro <TT>Q_NEW(ScoreEvt, SCORE_SIG)</TT> dynamically
|
|
|
+allocates an instance of the ScoreEvt from an event pool managed by QF. The
|
|
|
+macro also performs the association between the signal SCORE_SIG and the
|
|
|
+allocated event. The #Q_NEW() macro returns the pointer to the allocated
|
|
|
+event.
|
|
|
+
|
|
|
+\li (14) The score parameter of the ScoreEvt is set from the state machine
|
|
|
+member me->score.
|
|
|
+
|
|
|
+\li (15) The SCORE(me->score) event is posted directly to the Tunnel active
|
|
|
+object by means of the QP function QActive_postFIFO(). The arguments of this
|
|
|
+function are the recipient active object (AO_Tunnel in this case) and the
|
|
|
+pointer to the event (the temporary pointer sev in this case).
|
|
|
+
|
|
|
+\li (16) You terminate the case statement with "<TT>return Q_HANDLED()</TT>",
|
|
|
+which informs QEP that the entry actions have been handled.
|
|
|
+
|
|
|
+\li (17-18) These two lines of code pertain to the state transitions from
|
|
|
+"flying" to "exploding" (\ref F5s2 "Figure 5-2"(9, 10)).
|
|
|
+
|
|
|
+\li (19) You can enlist any actions associated with the transition (none in
|
|
|
+this particular case).
|
|
|
+
|
|
|
+\li (20) You designate the target of the transition with the #Q_TRAN() macro.
|
|
|
+
|
|
|
+\li (21) The final return from a state handler function designates the
|
|
|
+superstate of that state. The state "flying" in \ref F5s2 "Figure 5-2" nests
|
|
|
+in the state "active", so the state handler Ship_flying() returns the pointer
|
|
|
+&Ship_active wrapped with the macro Q_SUPER().
|
|
|
+
|
|
|
+When implementing state handler functions you need to keep in mind that the
|
|
|
+QEP event processor is in charge here rather than your code. QEP will invoke a
|
|
|
+state handler function for various reasons: for hierarchical event processing,
|
|
|
+for execution of entry and exit actions, for triggering initial transitions,
|
|
|
+or even just to elicit the superstate of a given state handler. Therefore, you
|
|
|
+should not assume that a state handler would be invoked only for processing
|
|
|
+signals enlisted in the case statements. You should avoid any code outside the
|
|
|
+switch statement, especially code that would have side effects.
|
|
|
+
|
|
|
+Prev: \ref events \n
|
|
|
+Next: \ref execution
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page execution 8. Using the Built-in Real-Time Kernels and Third-Party RTOSes
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref coding_hsm \n
|
|
|
+Next: \ref tracing
|
|
|
+
|
|
|
+As you saw in \ref L3s1 "Listing 3-1"(21), the \c main() function eventually
|
|
|
+gives control to the event-driven framework by calling QF_run() to execute the
|
|
|
+application. In this section, I briefly explain how QF allocates the CPU
|
|
|
+cycles to various tasks within the system and what options you have in
|
|
|
+choosing the execution model.
|
|
|
+
|
|
|
+\section using_vanilla 8.1 Simple Non-Preemptive "Vanilla" Kernel
|
|
|
+
|
|
|
+In the simplest configuration, the "Fly 'n' Shoot" game executes under the
|
|
|
+simple cooperative "vanilla" kernel, which is provided in the QP. The
|
|
|
+"vanilla" kernel operates by constantly polling all event queues of active
|
|
|
+objects in an endless loop. The kernel always selects the highest-priority
|
|
|
+active object ready to run, which is the highest-priority active object with a
|
|
|
+non-empty event queue.
|
|
|
+
|
|
|
+\note The "vanilla" kernel is so simple that many commercial real-time
|
|
|
+frameworks don't even call it a kernel . Instead, this configuration is simply
|
|
|
+referred to as "without an RTOS". However, if you want to understand what it
|
|
|
+means to execute active objects "without an RTOS" and what execution profile
|
|
|
+you can expect in this case, you need to realize that a simple cooperative
|
|
|
+vanilla kernel is indeed involved.
|
|
|
+
|
|
|
+The interrupt service routines (ISRs) can preempt the execution of active
|
|
|
+objects at any time, but due to the simplistic nature of the "vanilla" kernel,
|
|
|
+every ISR returns to exactly the preemption point. If the ISR posts or
|
|
|
+publishes an event to any active object, the processing of this event won't
|
|
|
+start until the current RTC step completes. The maximum time an event for the
|
|
|
+highest-priority active object can be delayed this way is called the
|
|
|
+task-level response. With the non-preemptive "vanilla" kernel, the task-level
|
|
|
+response is equal to the longest RTC step of all active objects in the system.
|
|
|
+Please note that the task-level response of the "vanilla" kernel is still a
|
|
|
+lot better than the traditional "superloop" (a.k.a., main+ISRs) architecture.
|
|
|
+I'll have more to say about this in the upcoming Section \ref comparison,
|
|
|
+where I compare the event-driven "Fly 'n' Shoot" example to the traditionally
|
|
|
+structured "Quickstart" application.
|
|
|
+
|
|
|
+The task-level response of the simple "vanilla" kernel turns out to be
|
|
|
+adequate for surprisingly many applications, because state machines by nature
|
|
|
+handle events quickly without a need to busy-wait for events. (A state machine
|
|
|
+simply runs-to-completion and becomes dormant until another event arrives).
|
|
|
+Please also note that often you can make the task-level response as fast as
|
|
|
+you need by breaking up longer RTC steps into shorter ones (e.g., by using the
|
|
|
+"Reminder" state pattern described in Chapter 5 of \ref PSiCC2.
|
|
|
+
|
|
|
+\section using_QK 8.2 The QK Preemptive Kernel
|
|
|
+
|
|
|
+In some cases, breaking up long RTC steps into short enough pieces might be
|
|
|
+very difficult, and consequently the task-level response of the non-preemptive
|
|
|
+"vanilla" kernel might be too long. An example system could be a GPS receiver.
|
|
|
+Such a receiver performs a lot of floating point number crunching on a
|
|
|
+fixed-point CPU to calculate the GPS position. At the same time, the GPS
|
|
|
+receiver must track the GPS satellite signals, which involves closing control
|
|
|
+loops in sub-millisecond intervals. It turns out that it's not easy to break
|
|
|
+up the position-fix computation into short enough RTC steps to allow reliable
|
|
|
+signal tracking. But the RTC semantics of state machine execution does not
|
|
|
+mean that a state machine has to monopolize the CPU for the duration of the
|
|
|
+RTC step. A preemptive kernel can perform a context switch in the middle of
|
|
|
+the long RTC step to allow a higher-priority active object to run. As long as
|
|
|
+the active objects don't share resources they can run concurrently and
|
|
|
+complete their RTC steps independently.
|
|
|
+
|
|
|
+The QP event-driven platform includes a tiny, fully preemptive, priority-based
|
|
|
+real-time kernel component called QK, which is specifically designed for
|
|
|
+processing events in the RTC fashion. Configuring QP to use the preemptive QK
|
|
|
+kernel is very easy, but as with any fully preemptive kernel you must be very
|
|
|
+careful with any resources shared among active objects5. The "Fly 'n' Shoot"
|
|
|
+example has been purposely designed to avoid any resource sharing among active
|
|
|
+objects, so the application code does not need to change at all to run on top
|
|
|
+of the QK preemptive kerel, or any other preemptive kernel or RTOS for that
|
|
|
+matter. The accompanying code contains the "Fly 'n' Shoot" example with QK in
|
|
|
+the following directory: <qpc>\\examples\\80x86\\qk\\watcom\\l\\game\\.
|
|
|
+You can execute this example in a DOS-console on any standard Windows-based
|
|
|
+PC.
|
|
|
+
|
|
|
+\section using_RTOS 8.3 Traditional OS/RTOS
|
|
|
+
|
|
|
+QP can also work with a traditional operating system (OS), such as Windows or
|
|
|
+Linux, MicroC/OS-II or virtually any real-time operating system (RTOS) to take
|
|
|
+advantage of the existing device drivers, communication stacks, and other
|
|
|
+middleware. The accompanying code contains the "Fly 'n' Shoot" example with
|
|
|
+MicroC/OS-II in the following directory:
|
|
|
+<qpc>\\examples\\80x86\\ucos2\\watcom\\l\\game\\. You can execute this
|
|
|
+example in a DOS-console on any standard Windows-based PC. QP contains a
|
|
|
+platform abstraction layer (PAL), which makes adapting QP to virtually any
|
|
|
+operating system easy. The carefully designed PAL allows tight integration
|
|
|
+with the underlying OS/RTOS by reusing any provided facilities for interrupt
|
|
|
+management, message queues, and memory partitions. I cover porting QP in
|
|
|
+Chapter 8 of \ref PSiCC2.
|
|
|
+
|
|
|
+
|
|
|
+Prev: \ref coding_hsm \n
|
|
|
+Next: \ref tracing
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page tracing 9. Using Software Tracing for Testing and Debugging
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref execution \n
|
|
|
+Next: \ref comparison
|
|
|
+
|
|
|
+A running application built of active objects is a highly structured affair
|
|
|
+where all important system interactions funnel through the real-time framework
|
|
|
+(QF) and the state-machine engine (QEP). This offers a unique opportunity to
|
|
|
+use <STRONG>software tracing</STRONG> techniques to gain unprecedented insight
|
|
|
+into the entire system.
|
|
|
+
|
|
|
+Software tracing is a method for obtaining diagnostic information in a live
|
|
|
+environment without the need to stop the application to get the system
|
|
|
+feedback. In a nutshell, software tracing is similar to peppering the code
|
|
|
+with \c printf() statements for logging and debugging, except that
|
|
|
+mature software tracing techniques are much less intrusive and more selective
|
|
|
+than the primitive \n printf().
|
|
|
+
|
|
|
+Due to the inversion of a control, software tracing is particularly effective
|
|
|
+and powerful in combination with the event-driven frameworks. The QP
|
|
|
+event-driven platorm contains the sophisticated software tracing system called
|
|
|
+Quantum Spy (QS). The QS trace data can be thorough enough to produce complete
|
|
|
+sequence diagrams and detailed state machine activity for all state machines
|
|
|
+in the system. You can selectively monitor all event exchanges, event queues,
|
|
|
+event pools, and time events because all these elements are controlled by the
|
|
|
+framework. Additionally, if you use one of the kernels built into QP (the
|
|
|
+vanilla kernel or the preemptive QK kernel), you can obtain all the data
|
|
|
+available to a traditional RTOS as well, such as context switches and mutex
|
|
|
+activity
|
|
|
+
|
|
|
+To show you how software tracing works in practice I present an example of a
|
|
|
+software tracing session. I use the application. All versions of the "Fly 'n'
|
|
|
+Shoot" game contain the QS instrumentation. The tracing instrumentation
|
|
|
+becomes active when you build the "Spy" configuration.
|
|
|
+
|
|
|
+\note To build the "Spy" configuration in DOS version, please execute the \c
|
|
|
+make.bat script with the \c spy argument (\c make \c spy). To buld the "Spy"
|
|
|
+configuration in the ARM-Cortex version, please select the "Spy" configuration
|
|
|
+in the IAR EWARM IDE (see \ref F2s3 "Figure 2-3").
|
|
|
+
|
|
|
+\ref F9s1 "Figure 9-1" shows how to collect the software trace data from the
|
|
|
+DOS version of the "Fly 'n' Shoot" application, which is located in the
|
|
|
+directory
|
|
|
+\<qpc\>\\examples\\80x86\\dos\\watcom\\l\\dpp\\spy\\dpp.exe. You can
|
|
|
+re-build the "Spy" configuration by executing \c make \c spy from the
|
|
|
+command line. You need to run the \c GAME.EXE executable on a target
|
|
|
+PC with a serial port. You connect the serial port of the target machine to
|
|
|
+the serial port of a Windows or a Linux host workstation via a NULL-modem
|
|
|
+cable.
|
|
|
+
|
|
|
+\anchor F9s1
|
|
|
+\image html Fig11.02.jpg "Figure 9-1 Collecting software trace data from a 80x86 target."
|
|
|
+
|
|
|
+Actually, all versions of "Fly 'n' Shoot" applications included in the
|
|
|
+standard QP distribution are instrumented for software tracing and I encourage
|
|
|
+you to try them all. For example, you can collect trace data from the LM3S811
|
|
|
+board (see \ref F9s2 "Figure 9-2"). The LM3S811 board sends the QS trace data
|
|
|
+through the UART0 connected to the Virtual COM Port (VCP) provided by the USB
|
|
|
+debugger, so the QSPY host application can conveniently receive the trace data
|
|
|
+on the host PC. No additional serial cable is needed.
|
|
|
+
|
|
|
+\anchor F9s2
|
|
|
+\image html Fig11.01.jpg "Figure 9-2 Collecting software trace data from the LM3S811 board."
|
|
|
+
|
|
|
+On the host workstation, you need to start the QSPY host application that
|
|
|
+decompresses and visualizes the QS trace data. The Windows executable of the
|
|
|
+QSPY host application is located in the directory
|
|
|
+<qpc>\\tools\\qspy\\win32\\vc2005\\Release\\qspy.exe. Assuming that this
|
|
|
+directory is your current directory, or is in your path, you invoke this
|
|
|
+console application by typing the following command at the command prompt:
|
|
|
+
|
|
|
+\code
|
|
|
+qspy -c COM1 -b 115200
|
|
|
+\endcode
|
|
|
+
|
|
|
+The first command-line parameter <TT>-c COM1</TT> tells the QSPY host
|
|
|
+application to receive the trace data from COM1. If your target is connected
|
|
|
+to a different COM port, you need to adjust the COM number. The second
|
|
|
+parameter configures the baud rate of the serial port to 115200. Section \ref
|
|
|
+qspy_command explains the comman-line paratmeters of the QSPY host
|
|
|
+application.
|
|
|
+
|
|
|
+\note In the particular case of a Windows PC, you can use the same machine as
|
|
|
+the target and the host at the same time. You need to use a machine with two
|
|
|
+serial ports, which you connect with a NULL modem cable. You can use one
|
|
|
+serial port for the DPP target application running in a DOS-window and the
|
|
|
+other for the QSPY host application.
|
|
|
+
|
|
|
+You might also use a Linux host machine. In case of Linux, you must first
|
|
|
+build the executable by running the \c Makefile located in the directory
|
|
|
+<qpc>/tools/qspy/linux/gnu/. You invoke the Linux executable by typing
|
|
|
+the following command at the command prompt:
|
|
|
+
|
|
|
+\code
|
|
|
+qspy -c /dev/ttyS0 -b 115200
|
|
|
+\endcode
|
|
|
+
|
|
|
+The first parameter <TT>-c /dev/ttyS0</TT> tells the QSPY application to
|
|
|
+receive the trace data from the ttyS0 serial device. If you connected a
|
|
|
+different serial port to the target, you need to adjust the ttyS number.
|
|
|
+
|
|
|
+If everything is connected correctly, the QSPY host application should produce
|
|
|
+the human-readable output of the trace data to the screen. Please refer
|
|
|
+to \ref qspy_page explains the human-readable format as well as importing the
|
|
|
+trace data to MATLAB®.
|
|
|
+
|
|
|
+
|
|
|
+Prev: \ref execution \n
|
|
|
+Next: \ref comparison
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg Copyright © 2002-2011 Quantum Leaps, LLC. All
|
|
|
+Rights Reserved.\n http://www.state-machine.com */
|
|
|
+
|
|
|
+/** \page comparison 10. Comparison to the Traditional Approach
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref tracing \n
|
|
|
+Next: \ref summary
|
|
|
+
|
|
|
+The "Fly 'n' Shoot" game behaves intentionally almost identically to the
|
|
|
+"Quickstart" application provided in source code with the Luminary Micro
|
|
|
+ARM Cortex-M3 LM3S811 evaluation kit (http://www.luminarymicro.com). In this
|
|
|
+section I'd like to compare the traditional approach represented by the
|
|
|
+"Quickstart" application with the state machine-based solution exemplified in
|
|
|
+the "Fly 'n' Shoot" game.
|
|
|
+
|
|
|
+\ref F10s1 "Figure 10-1"(a) shows schematically the flowchart of the
|
|
|
+"Quickstart" application, while \ref F10s1 "Figure 10-1"(b) shows the
|
|
|
+flowchart of the "Fly 'n' Shoot" game running on top of the cooperative
|
|
|
+"vanilla" kernel. At the highest level, the flowcharts are similar in that
|
|
|
+they both consist of an endless loop surrounding the entire processing. But
|
|
|
+the internal structure of the main loop is very different in the two cases. As
|
|
|
+indicated by the heavy lines in the flowcharts, the "Quickstart" application
|
|
|
+spends most of its time in the tight "event loops" designed to busy-wait for
|
|
|
+certain events, such as the screen update event. In contrast, the "Fly 'n'
|
|
|
+Shoot" application spends most of its time right in the main loop. The QP
|
|
|
+framework dispatches any available event to the appropriate state machine that
|
|
|
+handles the event and returns quickly to the main loop without ever waiting
|
|
|
+for events internally.
|
|
|
+
|
|
|
+\anchor F10s1
|
|
|
+\image html Fig1.11.jpg "Figure 10-1 The control flow in the "Quickstart" application (a), and the Fly 'n' Shoot example (b). The heavy lines represent the most frequently exercised paths through the code."
|
|
|
+
|
|
|
+The "Quickstart" application has much more convoluted flow of control than the
|
|
|
+"Fly 'n' Shoot" example, because the traditional solution is very specific to
|
|
|
+the problem at hand while the state-machine approach is generic. The
|
|
|
+"Quickstart" application is structured very much like a traditional sequential
|
|
|
+program that tries to stay in control from the beginning to the end. From time
|
|
|
+to time, the application pauses to busy-wait for a certain event, whereas the
|
|
|
+code is generally not ready to handle any other events than the one it chooses
|
|
|
+to wait for. All this contributes to the inflexibility of the design. Adding
|
|
|
+new events is hard because the whole structure of the intervening code is
|
|
|
+designed to accept only very specific events and would need to change
|
|
|
+dramatically to accommodate new events. Also, while busy-waiting for the
|
|
|
+screen update event (equivalent to the \c TIME_TICK event in "Fly 'n' Shoot"
|
|
|
+example) the application is really not responsive to any other events. The
|
|
|
+task-level response is hard to characterize and generally depends on the event
|
|
|
+type. The timing established by the hard-coded waiting for the existing events
|
|
|
+might not work well for new events.
|
|
|
+
|
|
|
+In contrast, the "Fly 'n' Shoot" application has a much simpler control flow
|
|
|
+that is purely event-driven and completely generic (see
|
|
|
+\ref F10s1 "Figure 10-1"(b)). The context of each active object component is
|
|
|
+represented as the current state of a state machine, rather than as a certain
|
|
|
+place in the code. That way, hanging in tight "event loops" around certain
|
|
|
+locations in the code corresponding to the current context is unnecessary.
|
|
|
+Instead, a state machine remembers the context very efficiently as a small
|
|
|
+data item (the state-variable, see Chapter 3 of \ref PSiCC2). After processing
|
|
|
+of each event the state machine can return to the common event loop that is
|
|
|
+designed generically to handle all kinds of events. For every event, the state
|
|
|
+machine naturally picks up where it left off and moves on to the next state,
|
|
|
+if necessary. Adding new events is easy in this design, because a state
|
|
|
+machine is responsive to any event at any time. An event-driven,
|
|
|
+state-machine-based application is incomparably more flexible and resilient to
|
|
|
+change than the traditional one.
|
|
|
+
|
|
|
+\note The generic event loop can also very easily detect the situation when no
|
|
|
+events are available, in which case the QP framework calls the QF_onIdle()
|
|
|
+function (see \ref F10s1 "Figure 10-1"(b)). This callback function is designed
|
|
|
+to be customized by the application and is the ideal place to put the CPU in a
|
|
|
+low-power sleep mode to conserve the power. In contrast, the traditional
|
|
|
+approach does not offer any single place to transition to the low-power sleep
|
|
|
+mode, and consequently is much less friendly for implementing truly low-power
|
|
|
+designs.
|
|
|
+
|
|
|
+Prev: \ref tracing \n
|
|
|
+Next: \ref summary
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/** \page summary 11. Summary
|
|
|
+
|
|
|
+<I>This QP/C Tutorial is adapted from Chapter 1 of \ref PSiCC2\n
|
|
|
+by Miro Samek, the founder and president of Quantum Leaps, LLC.</I>
|
|
|
+
|
|
|
+\image html qp_tutorial.jpg
|
|
|
+
|
|
|
+Prev: \ref comparison
|
|
|
+
|
|
|
+If you've never done event-driven programming before, the internal structure
|
|
|
+of the "Fly 'n' Shoot" game must certainly represent a big paradigm shift for
|
|
|
+you. In fact, I hope that it actually blows your mind, because otherwise I'm
|
|
|
+not sure that you really appreciate the complete reversal of control of an
|
|
|
+event-driven program compared to the traditional sequential code. This
|
|
|
+reversal of control, known as the "Hollywood Principle" (don't call us, we'll
|
|
|
+call you), baffles many newcomers, who often find it "mind-boggling",
|
|
|
+"backwards", or "weird".
|
|
|
+
|
|
|
+My main goal in this Tutorial was just to introduce you to the event-driven
|
|
|
+paradigm and the modern state machines to convince you that these powerful
|
|
|
+concepts aren't particularly hard to implement directly in C or C++. Indeed, I
|
|
|
+hope you noticed that the actual coding of the nontrivial "Fly 'n' Shoot" game
|
|
|
+wasn't a big deal at all. All you needed to know was just a few cookie-cutter
|
|
|
+rules for coding state machines and familiarity with a few framework services
|
|
|
+for implementing the actions.
|
|
|
+
|
|
|
+Wile the coding turned out to be essentially a non-issue; the bulk of the
|
|
|
+programming effort was spent on the design of the application. At this point,
|
|
|
+I hope that the "Fly 'n' Shoot" example helps you to get the big picture of
|
|
|
+how the method works. Under the event driven model, the program structure is
|
|
|
+divided into two rough groups: events and state machine components (active
|
|
|
+objects). An event represents the occurrence of something interesting. A state
|
|
|
+machine codifies the reactions to the events, which generally depend both on
|
|
|
+the nature of the event and on the state of the component. While events often
|
|
|
+originate from the outside of your program, such as time ticks or button
|
|
|
+presses in the "Fly 'n' Shoot" game, events can also be generated internally
|
|
|
+by the program itself. For example the Mine components generate notification
|
|
|
+events when they detect a collision with the Missile or the Ship.
|
|
|
+
|
|
|
+An event-driven program executes by constantly checking for possible events
|
|
|
+and, when an event is detected, dispatching the event to the appropriate state
|
|
|
+machine component (see \ref F10s1 "Figure 10-1"(b)). In order for this
|
|
|
+approach to work, the events must be checked continuously and frequently. This
|
|
|
+implies that the state machines must execute quickly, so that the program can
|
|
|
+get back to checking for events. In order to meet this requirement, a state
|
|
|
+machine cannot go into a condition where it is busy-waiting for some long or
|
|
|
+indeterminate time. The most common example of this would be a while loop
|
|
|
+inside a state-handler function, where the condition for termination was not
|
|
|
+under program control, for instance the button press. This kind of program
|
|
|
+structure, an indefinite loop, is referred to as "blocking" code6, and you saw
|
|
|
+examples of it in the "Quickstart" application (see
|
|
|
+\ref F10s1 "Figure 10-1"(a)). In order for the event driven programming model
|
|
|
+to work, you must only write "non-blocking" code.
|
|
|
+
|
|
|
+Finally, the "Fly 'n' Shoot" example demonstrates the use of the event-driven
|
|
|
+platform called QP, which is a collection of components for building
|
|
|
+event-driven application. The QF real-time framework component framework
|
|
|
+embodies the "Hollywood principle", by calling the application code, not the
|
|
|
+other way around. Such arrangement is very typical for event-driven systems
|
|
|
+and application frameworks similar to QF are at the heart of virtually every
|
|
|
+design automation tool on the market today.
|
|
|
+
|
|
|
+The QF framework operates in the "Fly 'n' Shoot" game in its simplest
|
|
|
+configuration, in which QF runs on a bare-metal target processor without any
|
|
|
+operating system. QF can also be configured to work with the build-in
|
|
|
+preemptive real-time kernel called QK (see Chapter 10 of \ref PSiCC2), or can
|
|
|
+be easily ported to almost any traditional OS or RTOS (see Chapter 8 of \ref
|
|
|
+PSiCC2). In fact, you can view the QF framework itself as a high-level,
|
|
|
+event-driven, real-time operating system.
|
|
|
+
|
|
|
+Prev: \ref comparison
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|
|
|
+
|
|
|
+/**
|
|
|
+\page derivation Encapsulation and Single Inheritance in C
|
|
|
+
|
|
|
+Inheritance is the ability to derive new structures based on existing
|
|
|
+structures in order to reuse and organize code. You can implement single
|
|
|
+inheritance in C very simply by literally embedding the base structure as the
|
|
|
+first member of the derived structure. For example, \ref FA1 "Figure 1"(a)
|
|
|
+shows the structure ScoreEvt derived from the base structure QEvent by
|
|
|
+embedding the QEvent instance as the first member of ScoreEvt. To make this
|
|
|
+idiom better stand out, I always name the base structure member super.
|
|
|
+
|
|
|
+\anchor FA1 \image html FA1.jpg "Figure 1 (a) Derivation of structures in C, (b) memory alignment, and (c) the UML class diagram."
|
|
|
+
|
|
|
+As shown in \ref FA1 "Figure 1"(b), such nesting of structures always aligns
|
|
|
+the data member super at the beginning of every instance of the derived
|
|
|
+structure. In particular, this alignment lets you treat a pointer to the
|
|
|
+derived ScoreEvt structure as a pointer to the QEvent base structure.
|
|
|
+Consequently, you can always safely pass a pointer to ScoreEvt to any C
|
|
|
+function that expects a pointer to QEvent. (To be strictly correct in C, you
|
|
|
+should explicitly cast this pointer. In OOP such casting is called upcasting
|
|
|
+and is always safe.) Therefore, all functions designed for the QEvent
|
|
|
+structure are automatically available to the ScoreEvt structure as well as
|
|
|
+other structures derived from QEvent. \ref FA1 "Figure 1"(c) shows the UML
|
|
|
+class diagram depicting the inheritance relationship between ScoreEvt and
|
|
|
+QEvent structures.
|
|
|
+
|
|
|
+QP uses single inheritance quite extensively not just for derivation of events
|
|
|
+with parameters, but also for derivation of state machines and active objects.
|
|
|
+Of course, the C++ version of QP uses the native C++ support for class
|
|
|
+inheritance rather than "derivation of structures".
|
|
|
+
|
|
|
+\image html logo_ql_TM.jpg
|
|
|
+Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.\n
|
|
|
+http://www.state-machine.com */
|