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 (4) MAX_SIG // the last signal (keep always last) }; (5) struct ObjectPosEvt (6) : public QEvent // derive from the QEvent class { (7) uint8_t x; // the x-position of the object (8) uint8_t y; // new y-position of the object }; struct ObjectImageEvt : public QEvent { // derive from 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 }; struct MineEvt : public QEvent { // derive from the QEvent class uint8_t id; // the ID of the Mine MineEvt(QSignal sig, uint8_t id) { this->sig = sig; this->dynamic_ = 0; this->id = id; } }; struct ScoreEvt : public QEvent { // derive from the QEvent class uint16_t score; // the current score ScoreEvt(QSignal sig, uint16_t score) { this->sig = sig; this->dynamic_ = 0; this->score = score; } }; // 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;
_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 <qpcpp>\examples\arm-cortex\vanilla\iar\game-ev-lm3s811\bsp.cpp). 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.cpp for the ARM-Cortex board.
(1) extern "C" 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) extern "C" void ISR_ADC(void) { static uint32_t adcLPS = 0; // Low-Pass-Filtered ADC reading static uint32_t wheel = 0; // the last wheel position static uint32_t btn_debounced = 0; static uint8_t debounce_state = 0; 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)), // eg, 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) AO_Ship->postFIFO(ope); // post to the Ship 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