This QP/C Tutorial is adapted from Chapter 1 of Practical UML Statecharts in C/C++, Second Edition
by Miro Samek, the founder and president of Quantum Leaps, LLC.
Prev: 5. Elaborating State Machines of Active Objects
Next: 7. Coding Hierarchical State Machines
The key events in the "Fly 'n' Shoot" game have been identified in the sequence diagram in 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 TIME_TICK signal conveys the arrival of a time tick, while 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 PLAYER_SHIP_MOVE signal is accompanied by the parameters (x, y) 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 Encapsulation and Single Inheritance in C.
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 Listing 6-1. The explanation section immediately following the listing illuminates the interesting points.
Listing 6-1 Signals, event structures, and active object interfaces defined in file game.h.
(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);
_SIG to all signals so that I can easily distinguish signals from other constants. I drop the suffix _SIG in the state diagrams to reduce the clutter.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 (MAX_PUB_SIG) rather than maximum of all signals used in the application. (See also Listing 3-1(9)).MAX_SIG indicates the maximum of all signals used in the application.ObjectPosEvt defines a "class" of events that convey the object's position on the display in the event parameters.ObjectPosEvt derives from the base structure QEvent, as explained in the sidebar Encapsulation and Single Inheritance in C.ObjectPosEvt adds parameters x and y, which are coordinates of the object on the display.The QF framework supports two types of asynchronous event exchange:
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.
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. 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 <qpc>\examples\arm-cortex\vanilla\iar\game-ev-lm3s811\bsp.c). In the upcoming Section 7. Coding Hierarchical State Machines you will see other examples of event posting from active objects in the state machine code.
Listing 6-2 Generating, posting , and publishing events from the ISRs in bsp.c for the ARM-Cortex board.
(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 */ } . . . }
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 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.ISR_ADC() services the ADC conversions, which ultimately deliver the position of the Ship.Q_NEW(ObjectPosEvt, PLAYER_SHIP_MOVE_SIG) dynamically allocates an instance of the ObjectPosEvt event from an event pool managed by QF. The macro also performs the association between the signal PLAYER_SHIP_MOVE_SIG and the allocated event. The Q_NEW() macro returns the pointer to the allocated event.PLAYER_SHIP_MOVE(x, y) event is an example of an event with changing parameters. In general, such an event cannot be allocated statically (like the 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.x and y parameters of the event are assigned.Prev: 5. Elaborating State Machines of Active Objects
Next: 7. Coding Hierarchical State Machines
Copyright © 2002-2010 Quantum Leaps, LLC. All Rights Reserved.
http://www.state-machine.com
1.6.3