an: capstone dive computer exampleold.state-machine.com/resources/an_capstone.pdf · alone qf...

31
QP state machine framework example Application Note Capstone Dive Computer Example Document Revision B September 2008 Copyright © Quantum Leaps, LLC www.quantum-leaps.com www.state-machine.com

Upload: others

Post on 16-May-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

QP state machine framework example

Application Note Capstone Dive Computer

Example

Document Revision B September 2008

Copyright © Quantum Leaps, LLC www.quantum-leaps.com www.state-machine.com

Page 2: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

i Copyright © Quantum Leaps, LLC. All Rights Reserved.

Table of Contents

1 Introduction.................................................................................................... 1 1.1 Scalability........................................................................................................ 2 1.2 Source Code .................................................................................................... 2 1.3 Portability ........................................................................................................ 3 1.4 Support for Modern State Machines ..................................................................... 3 1.5 Direct Event Posting and Publish-Subscribe Event Delivery...................................... 3 1.6 Zero-Copy Event Memory Management................................................................ 4 1.7 Open-Ended Number of Time Events ................................................................... 4 1.8 Native Event Queues ......................................................................................... 4 1.9 Native Memory Pool .......................................................................................... 4 1.10 Built-in “Vanilla” Scheduler................................................................................. 4 1.11 Tight Integration with the QK preemptive Kernel ................................................... 4 1.12 Low-Power Architecture ..................................................................................... 5 1.13 Assertion-Based Error Handling........................................................................... 5 1.14 Built-in Software Tracing Instrumentation ............................................................ 5

2 The Dive Computer Example ........................................................................... 6 2.1 Step 1: Requirements........................................................................................ 7 2.2 Step 2: Sequence Diagrams ............................................................................... 8 2.3 Step 3: Signals, Events, and Active Objects.......................................................... 9 2.4 Step 4: Designing the State Machines................................................................ 13

2.4.1 Capstone State Machine............................................................................. 13 2.4.2 AlarmMgr State Machine ............................................................................ 15

2.5 Step 5: Coding Active Objects .......................................................................... 16 2.6 Step 6: Coding State Machines of Active Objects ................................................. 17 2.7 Step 7: Initializing and Starting the Application ................................................... 19 2.8 Step 8: Choosing the Real-Time Execution Model ................................................ 21

2.8.1 Simple Non-Preemptive “Vanilla” Kernel ....................................................... 21 2.8.2 The QK Preemptive Kernel ......................................................................... 22 2.8.3 Traditional OS/RTOS ................................................................................. 22

3 The Quantum Spy (QS) Instrumentation....................................................... 23 3.1 QS Time Stamp Callback QS_onGetTime().......................................................... 25 3.2 Invoking the QSpy Host Application ................................................................... 26

4 References .................................................................................................... 28 5 Contact Information...................................................................................... 29

Page 3: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

1 Introduction This Application Note describes an example application for the QP™ event-driven platform. QP™ is a family of very lightweight, open source, state machine-based frameworks for developing event-driven applications. QP enables building well-structured embedded applications as a set of concur-rently executing hierarchical state machines (UML statecharts) directly in C or C++ without big tools. QP is described in great detail in the book “Practical UML Statecharts in C/C++, Second Edi-tion” [PSiCC2] (Newnes, 2008).

As shown in Figure 1, QP consists of a universal UML-compliant event processor (QEP), a portable real-time framework (QF), a tiny run-to-completion kernel (QK), and software tracing instrumenta-tion (QS). Current versions of QP include: QP/C™ and QP/C++™, which require about 4KB of code and a few hundred bytes of RAM, and the ultra-lightweight QP-nano, which requires only 1-2KB of code and just several bytes of RAM.

Figure 1 QP Components (in grey) and their relationship with the target hardware, board support package (BSP), and the application.

QP can work with or without a traditional RTOS or OS. In the simplest configuration, QP can com-pletely replace a traditional RTOS. QP includes a simple non-preemptive scheduler and a fully pre-emptive kernel (QK). QK is smaller and faster than most traditional preemptive kernels or RTOS, yet offers fully deterministic, preemptive execution of embedded applications. QP can manage up to 63 concurrently executing tasks structured as state machines (active objects).

QP/C and QP/C++ can also work with a traditional OS/RTOS to take advantage of existing device drivers, communication stacks, and other middleware. QP has been ported to Linux/BSD, Windows, VxWorks, uC/OS-II, and other popular OS/RTOS.

1 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 4: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

1.1 Scalability All components of the QP event-driven platform are designed for scalability, so that your final ap-plication image contains only the services that you actually use. QP is designed for deployment as a fine-granularity object library, which you statically link with your applications. This strategy puts the onus on the linker to eliminate any unused code automatically at link-time, instead of burden-ing the application programmer with configuring the QF code for each application at compile time.

As shown in Figure 2, a minimal QP/C or QP/C++ system requires some 8KB of code space (ROM) and abut 1KB of data space (RAM), to leave enough room for a meaningful application code and data. This code size corresponds to the footprint of a typical, small, bare-bones RTOS application, except that the RTOS approach requires typically more RAM for the stacks.

Figure 2 RAM/ROM footprints of QP/C, QP/C++, QP-nano, and other RTOS/OS. The chart shows approximate total system size, as opposed to just the RTOS/OS foot-prints. Please note logarithmic axes.

NOTE: A typical, standalone QP configuration with QEP, QF, and the “vanilla” scheduler or the QK preemptive kernel, with all major features enabled, requires around 4KB of code. Obviously, you need to budget additional ROM and RAM for your own application code and data. Figure 7.2 shows the application footprint.

1.2 Source Code The Quantum Leaps website at www.state-machine.com/downloads/ contains the complete source code for all QP components. The source code is very clean and consistent. The code has been writ-ten in strict adherence to the coding standard documented at www.state-machine.com/doc/-AN_QL_Coding_Standard.pdf.

2 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 5: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

3 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

All QP source code is “lint-free”. The compliance was checked with PC-lint/FlexLint static analysis tool from Gimpel Software (www.gimpel.com). The QP distribution includes the <qp>\ports\lint\ subdirectory, which contains the batch script make.bat for compiling all the QP components with PC-lint.

The QP source code is also 98% compliant with the Motor Industry Software Reliability Association (MISRA) Guidelines for the Use of the C Language in Vehicle Based Software [MISRA 98]. These standards were created by MISRA to improve the reliability and predictability of C programs in criti-cal automotive systems. Full details of this standard can be obtained directly from the MISRA web site at www.misra.org.uk. The PC-lint configuration used to analyze QP code includes the MISRA rule checker.

Finally and most importantly, simply giving you the source code is not enough. To gain real confi-dence in event-driven programming, you need to understand how a real-time framework is ulti-mately implemented and how the different pieces fit together. The [PSiCC2] book, and numerous Application Notes and QDKs, provide this kind of information.

1.3 Portability All QP source code is written in portable ANSI-C, or in the Embedded C++ subset in case of QP/C++, with all processor-specific, compiler-specific, or operating system-specific code abstracted into a clearly defined platform abstraction layer (PAL).

In the simplest standalone configurations, QP runs on “bare-metal” target CPU completely replac-ing the traditional RTOS. As shown in Figure 1, the QP event-driven platform includes the simple non-preemptive “vanilla” scheduler as well as the fully preemptive kernel QK. To date, the stand-alone QF configurations have been ported to over 10 different CPU architectures, ranging from 8-bit (e.g., 8051, PIC, AVR, 68H(S)08), through 16-bit (e.g., MSP430, M16C, x86-real mode), to 32-bit architectures (e.g., traditional ARM, ARM Cortex-M3, ColdFire, Altera Nios II, x86).

The QF framework can also work with a traditional OS/RTOS to take advantage of the existing de-vice drivers, communication stacks, middleware, or any legacy code that requires a conventional “blocking” kernel. To date, QF has been ported to six major operating systems and RTOSs, includ-ing Linux (POSIX) and Win32.

1.4 Support for Modern State Machines As shown in Figure 1, the QF real-time framework is designed to work closely with the QEP hierar-chical event processor. The two components complement each other in that QEP provides the UML-compliant state machine implementation, while QF provides the infrastructure of executing such state machines concurrently.

The design of QF leaves a lot of flexibility, however. You can configure the base class for derivation of active objects to be either the hierarchical state machine (HSM), the simpler non-hierarchical state machine (FSM), or even your own base class not defined in the QEP event processor. The lat-ter option allows you to use QF with your own event processor.

1.5 Direct Event Posting and Publish-Subscribe Event Delivery The QF real-time framework supports direct event posting to specific active objects with first-in-first-out (FIFO) and last-in-first-out (LIFO) policies. QF supports also the more advanced publish-subscribe event deliver mechanism, as described in Chapter 6 of [PSiCC2]. Both mechanisms can coexist in a single application.

Page 6: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

4 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

1.6 Zero-Copy Event Memory Management Perhaps that most valuable feature provided by the QF real-time framework is the efficient “zero-copy” event memory management, as described in Chapter 6 of [PSiCC2]. QF supports event mul-ticasting based on the reference-counting algorithm, automatic garbage collection for events, effi-cient static events, “zero-copy” event deferral, and up to three event pools with different block sizes for optimal memory utilization.

1.7 Open-Ended Number of Time Events QP can manage open ended number of time events (timers). QF time events are extensible via structure derivation (inheritance in C++). Each time event can be armed as a one-shot or a peri-odic timeout generator. Only armed (active) time events consume CPU cycles.

1.8 Native Event Queues QP provides two versions of native event queues. The first version is optimized for active objects and contains a portability layer to adapt it for either the traditional blocking kernels, the simple co-operative “vanilla” kernel, or the QK preemptive kernel (Chapter 10 in [PSiCC2]). The second na-tive queue version is a simple “thread-safe” queue not capable of blocking and designed for send-ing events to interrupts as well as storing deferred events. Both native QF event queue types are lightweight, efficient, deterministic, and thread-safe. They are optimized for passing just the point-ers to events and are probably smaller and faster than full-blown message queues available in a typical RTOS.

1.9 Native Memory Pool QF provides a fast, deterministic, and thread-safe memory pool. Internally, QF uses memory pools as event pools for managing dynamic events, but you can also use memory pools for allocating any other objects in you application.

1.10 Built-in “Vanilla” Scheduler The QF real-time framework contains a portable, cooperative “vanilla” kernel, as described in Sec-tion 6.3.7 of Chapter 6. The QF port to the “vanilla” kernel is described in Chapter 7 of [PSiCC2].

1.11 Tight Integration with the QK preemptive Kernel The QF real-time framework can also work with a deterministic, preemptive, non-blocking QK ker-nel. As described in Section 6.3.8 in Chapter 6, run-to-completion kernels, like QK, provide pre-emptive multitasking to event-driven systems at a fraction of the cost in CPU and stack usage compared to traditional blocking kernels/RTOSs. The QK implementation is presented in Chapter 10 of [PSiCC2].

Page 7: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

5 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

1.12 Low-Power Architecture Most modern embedded microcontrollers (MCUs) provide an assortment of low-power sleep modes designed to conserve power by gating the clock to the CPU and various peripherals. The sleep-modes are entered under the software control and are exited upon an external interrupt.

The event-driven paradigm is particularly suitable for taking advantage of these power-savings fea-tures, because every event-driven system can easily detect situation when the system has no more events to process, which is called the idle condition (Chapter 6 in [PSiCC2]). In both standalone QF configurations, either with the cooperative “vanilla” kernel, or with the QK preemptive kernel, the QF framework provides callback functions for handling the idle condition. These callbacks are care-fully designed to place the MCU into a low-power sleep mode safely and without creating race con-ditions with active interrupts.

1.13 Assertion-Based Error Handling The QF real-time framework consistently uses the Design by Contract (DbC) philosophy described in Chapter 6 of [PSiCC2]. QF constantly monitors the application by means of assertions built into the framework. Among others, QF uses assertions to enforce the event delivery guarantee, which immensely simplifies event-driven application design.

1.14 Built-in Software Tracing Instrumentation The QP code contains the software tracing instrumentation, so it can provide unprecedented visibil-ity into the system. Nominally, the instrumentation is inactive, meaning that it does not add any code size or runtime overhead. But by defining the macro Q_SPY, you can activate the instrumenta-tion. Chapter 9 in [PSiCC2] is devoted to software tracing.

NOTE: The QF code is instrumented with QS (Q-spy) macros to generate software trace output from active object execution. However, the instrumentation is disabled by default and will not be shown in the listings discussed in this chapter for better clarity. Please refer to Chapter 9 for more information about the QS software tracing implementation.

Page 8: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

2 The Dive Computer Example The Dive Computer example demonstrates programming with modern UML state machines and the QP event-driven framework. The example is based on the capstone exercise used at the Embedded Software Boot Camp training program run by Netrino Institute (www.netrino.com) [Netrino 08]. It is intended to be educational and to reflect real-world behavior associated with safety critical SCUBA diving equipment. The project makes use of the following hardware resources on the ARM9-based STR912-SK development kit (see Figure 3).

Amount of gas bar-graph

Button B1 add gas

Ascent rate bar-graph

Button B2 depth units

Ascent rate potentiometer

Time to Surface / Dive Time

Depth in meters/feet

Speaker for alarms

STR912 ARM9 MCU

Idle task LED16

ADC conv LED15

Alarm LED14

Diving LED11

Surfaced LED10

Surfaced LED9

2x16 character LCD

Figure 3 The Dive Computer User Interface.

Inputs Outputs

• Button B1 • Button B2 • Potentiometer via A/D Converter

• LEDs • LCD Display • Speaker

The hardware inputs are used to provide stimuli to a simulated Dive Computer. The hardware out-puts provide a user interface and feedback to the operator of the system, as he or she performs simulated dives.

6 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 9: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

7 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

2.1 Step 1: Requirements 1. Upon reset:

a. The diver is at the surface (0 m) and prefers to see depth in metric units b. The diver has not been under water yet (hence elapsed dive time is 0:00 seconds) c. The air tank is “empty,” at standard temperature and pressure (STP)

2. At all times: a. LCD displays

i. Top Left (line 0, characters 0-8): Current ascent rate, in m/min ii. Top Right (line 0, characters 9-15): Current depth, in user selectable units of meters (‘m’) or feet (‘f’) iii. Bottom Left (line 1, characters 0-8): The amount of compressed air in liters iv. Bottom Right (line 1, characters 9-15): Alternates (every 2 s) between elapsed dive time and safe minimum time-to-surface (“TTS”), in minutes:seconds

b. Each “press” of button B2 toggles the units of the displayed depth c. LED9 blinks a continuous 1 Hz heartbeat d. LED10 is on at the surface and is off while diving e. LED11 is on while diving and is off at the surface f. LED14 is on while any alarm is active g. LED15 is on while the ADC is converting the ascent rate h. LED16 is glowing at the intensity proportional to the CPU’s idle time

3. At the surface: a. Attempts to ascend are ignored (including no display of ascent rate) b. Pressing or holding button B1 causes air to be forced into the tank in 50 l incre-ments; the maximum capacity is 2,000 liters c. Elapsed dive time does not increment (but is not reset until next dive)

4. While diving: a. The rate of descent/ascent (-40 to +40 m/min) is measured

i. Potentiometer readings (ADC) from 0 to 510 indicate descent ii. A potentiometer reading of 511 indicate diver neutrality (i.e., 0 m/min) iii. Potentiometer readings 512 to 1,023 indicate ascent

b. Depth, in millimeters of precision, should increase or decrease as appropriate c. The rate of air consumption varies with depth, and this reduces the amount of air in the tank by some number of centiliters every half second d. Elapsed dive time increases e. The minimum safe TTS is calculated based on an ascent rate of 15 m/min f. The following alarm conditions are raised by audible tone patterns:

i. Most Dangerous: gas_to_surface_in_cl() exceeds available air supply! ii. Dangerous: ascent_rate_in_mm_per_min() is faster than 15000 mm/min! iii. Least Dangerous: depth_in_mm exceeds() 40000 mm (40 m)!

5. To reduce the complexity of the exercise as well as ensure consistent functionality across implementations the following code is provided in the baseline project:

• The ST Microelectronics library provides functions to display strings (and bar graphs) on the HD44780-type text-based LCD • Module scuba.c handles several diving-specific algorithms, all of which expect to be called every 500 ms as they operate on a base unit of ½ second of elapsed time:

o depth_change_in_mm(ascent_rate_in_mm_per_min) can be used to in-crease/decrease depth o gas_rate_in_cl(depth_in_mm) returns the volume of air, in centiliters, con-sumed each half second at a given depth o gas_to_surface_in_cl(depth_in_mm) performs the numerical integration neces-sary to determine the amount of air, in centiliters, necessary to surface safely

Page 10: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

2.2 Step 2: Sequence Diagrams A good starting point in designing any event-driven system is to draw sequence diagrams for the main scenarios (main use cases) identified from the problem specification. To draw such diagrams, you need to break up your problem into active objects with the main goal of minimizing the cou-pling among active objects. You seek a partitioning of the problem that avoids resource sharing and requires minimal communication in terms of number and size of exchanged events.

Another important consideration at this stage is deciding the ownership of resources, such as the LCD screen, the Speaker, or the LEDs. Generally, you should avoid any sharing of resources. Instead, every resource should be encapsulated inside a dedicated active object which becomes the “manager” of that resource and has exclusive access to the managed resource. All other compo-nents of the application must make requests (via events) to the respective “manager” of that re-source. In case of the Capstone Dive Computer the LCD and ADC resources are assigned to the Capstone active object and the Speaker resource to the Alarm active object, respectively.

The sequence diagram in Figure 4 shows the most representative event exchanges in the Capstone Dive Computer.

Figure 4 The sequence diagram of the Capstone application.

8 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 11: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

9 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(1) The Capstone active object starts in the “surfaced” state.

(2) The Alarm active object starts in the “silent” state.

(3) The system clock tick ISR posts the HEARTBEAT event to the Capstone state machine every ½ second.

(4) Upon receiving the HEARTBEAT event, Capstone triggers ADC conversion to find out the ascent rate.

(5) When the ADC finishes the conversion, the ADC ISR posts the ASCENT_RATE_ADC(raw) event to Capstone. The event parameter raw contains the raw, 10-bit ADC conversion value.

(6) The system clock tick ISR also performs debouncing of the buttons B1 and B2 (see Figure 3). This ISR publishes event B1_DOWN when button B1 is depressed and B2_DOWN when button B2 is depressed.

(7) Similarly, the system clock publishes event B1_UP when button B1 is released and B2_UP when button B2 is released.

(8) Capstone ignores positive ascent rates in the “surfaced” state. However, a negative ascent rate (diving) triggers the transition to the “diving” state.

(9) In the “diving” state Capstone tests for alarm conditions. If any of the alarm condition arises, the Capstone active object posts an ALARM_REQUEST(alarm_type) event to the Alarm state machine. The event parameter alarm_type conatins the enumerated type of the alarm.

(10) The ALARM_REQUEST(alarm_type) event triggers transition to “playing” in the Alarm active object.

(11) The Alarm active object changes the pitch of the speaker based on the TIMEOUT event pro-duced by the system clock tick ISR.

(12) When the Capstone active object determines that the alarm should be silenced, it sends the ALARM_SILENCE(alarm_type) to the Alarm active object. It is up to the Alarm active object to handle the priorities of the requested and silence alarms, as the alarms can overlap.

2.3 Step 3: Signals, Events, and Active Objects Sequence diagrams, like Figure 4, help you discover events exchanged among active objects. The choice of signals and event parameters is perhaps the most important design decision in any event-driven system. The events affect the other main application components: events and state machines of the active objects.

In QP, signals are typically enumerated constants and events with parameters are structures de-rived from the QEvent base structure. Listing 1 shows signals and events used in the Capstone ap-plication. The sample code for the non-preemptive kernel is located in the Solutions\DiveComputer directory.

NOTE: This section describes the platform-independent code of the application. This code is ac-tually identical in both versions.

#ifndef capstone_h #define capstone_h (1) enum CapstoneSignals {

Page 12: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

10 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(2) B1_DOWN_SIG = Q_USER_SIG, /**< button B1 depressed */ B1_UP_SIG, /**< button B1 released */ B2_DOWN_SIG, /**< button B2 depressed */ B2_UP_SIG, /**< button B2 released */ (3) MAX_PUB_SIG, /**< the last published signal */ ASCENT_RATE_ADC_SIG, /**< raw ascent rate ADC reading */ HEARTBEAT_SIG, /**< heartbeat of the Capstone scuba diving computer */ DT_TTS_SIG, /**< signal to alternate dive time/time to surface display */ ALARM_REQUEST_SIG, /**< alarm request to AlarmMgr */ ALARM_SILENCE_SIG, /**< alarm silence to AlarmMgr */ TIMEOUT_SIG, /**< timeout for playing a note in AlarmMgr */ (4) MAX_SIG /**< the last signal */ }; (5) typedef struct ADCEvtTag { (6) QEvent super; /* derives from QEvent */ (7) uint16_t raw; /* raw ADC reading */ } ADCEvt; (8) typedef struct AlarmEvtTag { QEvent super; /* derives from QEvent */ (9) uint8_t alarm_type; /* alarm type */ } AlarmEvt; (10) enum AlarmTypes { /* arranged in ascending order of alarm priority */ ALL_ALARMS, DEPTH_ALARM, ASCENT_RATE_ALARM, OUT_OF_AIR_ALARM, MAX_ALARM /* keep always last */ }; /*..........................................................................*/ (11) void Capstone_ctor(void); (12) void AlarmMgr_ctor(void); (13) extern QActive * const AO_Capstone; /* "opaque" pointer to Capstone AO */ (14) extern QActive * const AO_AlarmMgr; /* "opaque" pointer to AlarmMgr AO */ #endif /* capstone_h */

Listing 1 Signals and events used in the Dive Computer (file capstone.h)

(1) For smaller applications, such as the Dive Computer, all signals can be defined in one enumera-

tion (rather than in separate enumerations or, worse, as preprocessor #define macros). An enumeration automatically guarantees the uniqueness of signals.

(2) Note that the user signals must start with the offset Q_USER_SIG to avoid overlapping the re-served QEP signals.

(3) The globally published signals are grouped at top of the enumeration. The MAX_PUB_SIG enu-meration automatically keeps track of the maximum published signals in the application.

(4) The MAX_SIG enumeration automatically keeps track of the total number of signals used in the application.

(5-7) Every event with parameters, such as the ADCEvt derives from the QEvent base structure. Any number of event parameters can be added after the first member super (see also the side-bar below).

Page 13: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

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 embed-ding the base structure as the first member of the derived structure. For example, the following diagram (a) shows the structure ADCEvt derived from the base structure QEvent by embedding the QEvent instance as the first member of ADCEvt. To make this idiom better stand out, QP al-ways names the base structure member super.

Derivation of structures in C (a), memory alignment (b), the UML class diagram (c).

As shown in panel (b) of the figure above, such nesting of structures always aligns the first data member super at the beginning of every instance of the derived structure. This alignment is guaranteed by the C standard, such as WG14/N1124, Section 6.7.2.1.13, which says: “… A pointer to a structure object, suitably converted, points to its initial member. There may be un-named padding within a structure object, but not at its beginning” [ISO/IEC 9899:TC2]. In par-ticular, this alignment lets you treat a pointer to the derived ADCEvt structure as a pointer to the QEvent base structure.

Consequently, you can always safely pass a pointer to ADCEvt 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 struc-tures derived from QEvent. Panel (c) in the figure above shows the UML class diagram depicting the inheritance relationship between ADCEvt 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”.

(8-9) The AlarmEvt structure describes the ALARM(alarm_type) event.

(10) The enumeration AlarmTypes enumerates all alarm types in the order of priority.

The capstone.h header file shows how to keep the code and data structure of every active object strictly encapsulated within its own C-file. For example, all code and data for the active object Cap-stone are encapsulated in the file capstone.c, with the external interface consisting of the function Capstone_ctor() and the pointer AO_Capstone.

(11-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 call explicitly, typically at the beginning of main().

11 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 14: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

12 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(13-14) 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, they are declared 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.

Page 15: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

2.4 Step 4: Designing the State Machines At the application level, you can mostly ignore such aspects of active objects as the separate task contexts, or event queues, and view them predominantly as state machines. In fact, your main job in developing QP application consists of elaborating the state machines of your active objects.

2.4.1 Capstone State Machine Figure 5 shows the state machine associated with Capstone active object. The explanation section immediately following the state diagram explains the main interesting points.

Figure 5 Capstone state machine.

13 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 16: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

14 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

The state machine in Figure 5 reflects the structure of the requirements, which divide the behavior of the Dive Computer into “always”, “at the surface”, and “while diving” categories. The state “al-ways” contains the actions of the Dive Computer in the “always” category. The substate “surfaced” performs all actions from the “at the surface” category. Finally, the substate “diving” performs all the actions in the “while diving” category.

NOTE: This design makes use of the state nesting feature of the modern UML state machines. State nesting allows the “always” state to have nested substates “surfaced” and “diving”, which inherit the behavior of their superstate.

(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 Capstone state machine starts in the “surfaced” state and the ac-tions executed upon the initialization consist of subscribing to button events. Subscribing to an event means that the QP framework will deliver the specified event to the Capstone active ob-ject every time the event is published to the framework.

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, which is C in this case. However, to avoid clutter in the diagram, the action code must neces-sarily be abbreviated, so it conveys the meaning of the action, but might not necessarily be directly executable.

The convention used in the code presented in this Application Note is that the C expressions re-fer to the data members associated with the state machine object through the “me->” prefix and to the event parameters through the “e->” prefix. For example, the action “me->ascent_rate = e->raw;” means that the internal data member ascent_rate of the active object is assigned the value of the event parameter raw.

(2) The label entry below the state name denotes entry actions executed unconditionally whenever

this state is entered. These actions consist in this case of arming the time events (timers) to post themselves periodically to the Capstone active objects. The me->heartbeat timer posts it-self every ½ seconds while the me->dt_tts timer posts itself every 2 seconds.

(3) The event name HEARTBEAT enlisted in the compartment below the state name denotes an in-ternal transition. Internal transitions are simple reactions to events performed without a change of state. The HEARTBEAT event is generated by the me->heartbeat timer every ½ seconds, as described at step (2). This event triggers ADC conversion and toggles the heartbeat LED.

(4) 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 the particular case of the B2_DOWN event, the guard condition checks whether the depth is displayed in meters (‘m’). If so, units of depth are changed to feet ‘f’ and the depth display is updated.

(5) The guard [else] denotes a complementary guard to the guard already present in the state for the same event. Here, the B2_DOWN event triggers change of units to meters ‘m’ and the depth display is updated.

(6-7) The event DT_TTS is generated from the timer me->dt_tts every 2 seconds. This event tog-gles between Dive-Time and Time to Surface (TTS).

Page 17: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

Per the semantics of state nesting, the substate “surfaced” inherits all the behavior from its super-state “always”. The substate can override the inherited behavior and it can also define new behav-ior.

2.4.2 AlarmMgr State Machine The main responsibility of the Alarm Manager (AlarmMgr) state machine is to handle all alarm con-ditions autonomously. As shown in the sequence diagram in Figure 4, the Capstone active object sends only alarm requests and silences alarms based on the diving conditions. However, Capstone might end up requesting several alarms simultaneously, and it is up to the AlarmMgr to always play the highest-priority alarm at any one time. This includes the situation when an alarm is silenced, but others still remain active, in which case the AlarmMgr must keep playing the highest-priority alarm at that time.

The AlarmMgr is designed generally, to handle any number of alarms (it is not hard-coded to han-dle only three alarms specified in the requirements). The design of the AlarmMgr state machine hinges on the data representation for alarms.

The central data structure is the priority-set, which is provided in the QP framework. The priority set allows inserting elements numbered 1..n and removing elements. The set also allows discover-ing the highest-number event currently present in the priority set quickly and very efficiently.

Figure 6 shows the state machine associated with Alarm active object.

Figure 6 Alarm state machine.

15 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 18: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

16 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

2.5 Step 5: Coding Active Objects As mentioned before, each active object is strictly encapsulated inside a dedicated source file (.C file). Listing 3 shows the declaration (active object structure) of the Capstone active object found in the file capstone.c. The explanation section immediately following this listing describes the tech-niques of encapsulating active objects and using QP services.

#include "qp_port.h" #include "capstone.h" #include "bsp.h" #include "scuba.h" /* generic scuba diving algorithms */ #include "drv_hd44780.h" /* LCD display driver */ /* Active object class -----------------------------------------------------*/ (1) typedef struct CapstoneTag { (2) QActive super; /* derive from QActive */ (3) QTimeEvt heartbeat; QTimeEvt dt_tts;/* timer to alternate dive time/time to surface display */ int32_t depth_in_mm; int8_t depth_units[2]; uint8_t heartbeat_led_sel; uint8_t dt_tts_sel; int32_t ascent_rate_in_mm_per_sec; uint32_t start_dive_time_in_ticks; uint32_t dive_time_in_ticks; uint32_t tts_in_ticks; /* time to surface */ uint32_t gas_in_cylinder_in_cl; /* amount of gas in centliters at STP */ uint32_t consumed_gas_in_cl; /* consumed gas in centiliters */ } Capstone; (4) static QState Capstone_initial (Capstone *me, QEvent const *e); (5) static QState Capstone_always (Capstone *me, QEvent const *e); static QState Capstone_surfaced(Capstone *me, QEvent const *e); static QState Capstone_diving (Capstone *me, QEvent const *e); (6) static void Capstone_display_assent (Capstone *me); static void Capstone_display_depth (Capstone *me); static void Capstone_display_pressure(Capstone *me); /* Local objects -----------------------------------------------------------*/ (7) static Capstone l_capstone; /* the single instance of the Capstone AO */ /* Global-scope objects ----------------------------------------------------*/ (8) QActive * const AO_Capstone = (QActive *)&l_capstone;/* "opaque" AO pointer */ /* ctor ....................................................................*/ (9) void Capstone_ctor(void) { Capstone *me = &l_capstone; (10) QActive_ctor(&me->super, (QStateHandler)&Capstone_initial); (11) QTimeEvt_ctor(&me->heartbeat, HEARTBEAT_SIG); QTimeEvt_ctor(&me->dt_tts, DT_TTS_SIG); }

Listing 2 Defining Capstone active object (file capstone.c)

(1) To achieve true encapsulation, the declaration of the active object structure is placed in the

source file (.C file).

Page 19: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

17 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(2) Each active object in the application derives from the QActive base structure.

(3) The Capstone active object uses two independent time events (timers) for generation of the HEARTBEAT event and the TD_TTS event, respectively.

(4) The Capstone_initial() function defines the actions executed by the top-most initial transition.

(5) The all other functions with the signature QState Capstone_xxx(Capstone *me, QEvent const *e) are state-handler functions. Each state in the diagram (Figure 5) is represented by exactly one such state-handler function.

(6) The other Capstone_xxx() functions are helper functions executed in the actions by the state machine.

(7) The actual instance of the Capstone active object is defined only locally within the capstone.c module (static declaration). This means that the l_capsone object is strictly encapsulated.

(8) Externally, the Capstone active object is known only through the “opaque” pointer AO_Capstone. The pointer is declared ‘const’ (with the const after the ‘*’), which means that the pointer itself cannot change. This ensures that the active object pointer cannot change accidentally and also allows the compiler to allocate the active object pointer in ROM.

(9) The function Capstone_ctor() performs the instantiation of the Capstone active object. It plays the role of the static “constructor”, which in C you need to call explicitly, typically at the begin-ning of main().

NOTE: In C++, static constructors are invoked automatically before main(). This means that in C++, you provide a regular constructor for the class and don’t bother with calling it explicitly. However, you must make sure that the startup code for your particular embedded target in-cludes the additional steps required by the C++ initialization.

(10) The constructor must first instantiate the QActive superclass.

(11) The constructor must also instantiate all objects it contains, such as the time events (tim-ers).

2.6 Step 6: Coding State Machines of Active Objects Listing 3 shows the definition of the hierarchical state of the Capstone active object found in the file capstone.c. The explanation section immediately following this listing describes the techniques of encapsulating active objects and using QP services. The recipes for coding state machine elements are not repeated here, because they are already described in the “QP Tutorials” available from the [PSiCC2] book and online from www.state-machine.com.

/* HSM =====================================================================*/ (1) QState Capstone_initial(Capstone *me, QEvent const *e) { (void)e; /* suppress the compiler warning about unused parameter */ (2) HD44780_PowerUpInit(); /* LCD initialization */ HD44780_DispHorBarInit(); HD44780_StrShow(LCD_DEPTH_X, LCD_DEPTH_Y, "Dpt"); (3) me->depth_units[0] = 'm'; /* meters */ me->depth_units[1] = '\0'; /* zero terminate */ me->gas_in_cylinder_in_cl = 0; me->heartbeat_led_sel = 0; me->dt_tts_sel = 0;

Page 20: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

18 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(4) QActive_subscribe((QActive *)me, B1_DOWN_SIG); QActive_subscribe((QActive *)me, B1_UP_SIG); QActive_subscribe((QActive *)me, B2_DOWN_SIG); QActive_subscribe((QActive *)me, B2_UP_SIG); . . . (5) return Q_TRAN(&Capstone_always); } /*..........................................................................*/ (6) QState Capstone_always(Capstone *me, QEvent const *e) { (7) switch (e->sig) { (8) case Q_ENTRY_SIG: { QTimeEvt_postEvery(&me->heartbeat, (QActive *)me, BSP_TICKS_PER_SEC/2); /* every 1/2 second */ QTimeEvt_postEvery(&me->dt_tts, (QActive *)me, BSP_TICKS_PER_SEC*2); /* every 2 seconds */ (9) return Q_HANDLED(); } case Q_EXIT_SIG: { QTimeEvt_disarm(&me->heartbeat); QTimeEvt_disarm(&me->dt_tts); return Q_HANDLED(); } case Q_INIT_SIG: { (10) return Q_TRAN(&Capstone_surfaced); } case B2_DOWN_SIG: { /* depth unit change request */ if (me->depth_units[0] == 'm') { me->depth_units[0] = 'f'; } else { me->depth_units[0] = 'm'; } Capstone_display_depth(me); return Q_HANDLED(); } case HEARTBEAT_SIG: { /* heartbeat arrives every 1/2 sec */ ADC_StandbyModeCmd(DISABLE); /* exit ADC standby mode */ ADC_ConversionCmd(ADC_Conversion_Start);/* start the conversion */ BSP_LED_on(15); /* trun on the ADC conversion LED */ if (me->heartbeat_led_sel) { BSP_LED_on(9); } else { BSP_LED_off(9); } me->heartbeat_led_sel = !me->heartbeat_led_sel; return Q_HANDLED(); } case DT_TTS_SIG: { /* alternate between dive-time/tts */ if (me->dt_tts_sel) { HD44780_StrShow(LCD_TTS_X, LCD_TTS_Y, "TTS"); HD44780_StrShow(LCD_TTS_X + 3, LCD_TTS_Y, ticks2min_sec(me->tts_in_ticks)); } else { HD44780_StrShow(LCD_TTS_X, LCD_TTS_Y, "Div"); HD44780_StrShow(LCD_TTS_X + 3, LCD_TTS_Y, ticks2min_sec(me->dive_time_in_ticks)); } me->dt_tts_sel = !me->dt_tts_sel;

Page 21: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

19 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

return Q_HANDLED(); } } (11) return Q_SUPER(&QHsm_top); }

Listing 3 Capstone state machine (file capstone.c). Boldface indicates the QP services

(1) The Capstone_initial() function defines the actions executed by the top-most initial transition.

(2) The top-most initial transition is the right place to initialize the hardware owned by the active object. Capstone object owns the LCD, so here it initializes the HD44780 device driver.

(3) The top-most initial transition initializes the extended-state variables of the active object.

(4) The active object subscribes to all interesting to it signals in the top-most initial transition.

NOTE: New QP users often forget to subscribe to events and then the application appears unre-sponsive to certain events when you run it.

(5) The initial state-handler must always return the Q_TRAN() macro, in which it must provide the

pointer to the default initial state (&Capstone_surfaced in this case).

(6) The state handler function with the signature QState Capstone_always(Capstone *me, QEvent const *e) corresponds to the state “always” in the Capstone state diagram (Figure 5).

(7) Generally, every state-handler function is structured as a switch statement discriminating based on the event signal e->sig .

(8) Case Q_ENTRY_SIG specifies the entry actions.

(9) The Q_ENTRY_SIG case must return through the Q_HANLDED() macro provided by the QEP event processor.

(10) The Q_INIT_SIG case represents the local initial transition in a composite state. The Q_INIT_SIG case must return through the Q_TRAN() macro, in which is specifies the default substate.

(11) The final return Q_SUPER() statement specifies the nesting of the given state. If a state does not explicitly nest in any state, the QEP event processor provides the state handler QHsm_top, which is the ultimate root of state hierarchy.

2.7 Step 7: Initializing and Starting the Application Most of the system initialization and application startup can be written in a platform-independent way. In other words, you can use essentially the same main() function for the application with many QP ports.

Typically, you start all your active objects from main(). The signature of the QActive_start() func-tion forces you to make several important decisions about each active object upon startup. First, you need to decide the relative priorities of the active objects. Second, you need to decide the size of the event queues you pre-allocate for each active object. The correct size of the queue is actu-ally related to the priority, as described in Chapter 9 of PSiCC2. Third, in some QF ports, you need to give each active object a separate stack, which also needs to be pre-allocated adequately. And finally, you need to decide the order in which you start your active objects.

The order of starting active objects becomes important when you use an OS or RTOS in conjunc-tion with the QP framework, and only when a spawned thread starts to run immediately, possibly preempting the main() thread from which you launch your application. This could cause problems,

Page 22: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

20 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

if for example the newly created active object attempts to post an event directly to another active object that has not been yet created. Such situation does not occur in the example, but if it is an issue for you, you can try to lock the scheduler until all active objects are started. You can then unlock the scheduler in the QF_onStartup() callback, which is invoked right before QF takes over control. Some RTOSs (e.g., µC/OS-II) allow you to defer starting multitasking until after you start active objects. Another alternative is to start active objects from within other active objects, but this design increases coupling because the active object that serves as the launch pad must know the priorities, queue sizes, and stack sizes for all active objects to be started.

#include "qp_port.h" #include "capstone.h" #include "bsp.h" /* Local-scope objects -----------------------------------------------------*/ (1) static QEvent const *l_capstoneQueueSto[5]; (2) static QEvent const *l_alarmmgrQueueSto[5]; (3) static QSubscrList l_subscrSto[MAX_PUB_SIG]; (4) static union SmallEvent { (5) void *min_size; ADCEvt adce; (6) /* other event types to go into this pool */ (7) } l_smlPoolSto[10]; /* storage for the small event pool */ /*..........................................................................*/ int main(void) { (8) Capstone_ctor(); /* instantiate all Capstone active objects */ (9) AlarmMgr_ctor(); /* instantiate all AlarmMgr active objects */ (10) BSP_init(); /* initialize the Board Support Package */ (11) QF_init(); /* initialize the framework and the underlying RT kernel */ (12) QF_psInit(l_subscrSto, Q_DIM(l_subscrSto)); /* init publish-subscribe */ /* initialize event pools... */ (13) QF_poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), sizeof(l_smlPoolSto[0])); (14) QActive_start(AO_Capstone, 1, l_capstoneQueueSto, Q_DIM(l_capstoneQueueSto), (void *)0, 0, (QEvent *)0); (15) QActive_start(AO_AlarmMgr, 2, l_alarmmgrQueueSto, Q_DIM(l_alarmmgrQueueSto), (void *)0, 0, (QEvent *)0); (16) QF_run(); /* run the QF application */ return 0; }

Listing 4 Initializing and Starting the Application (file main.c).

(1-2) The memory buffers for all event queues are statically allocated.

(3) The memory space for subscriber lists is also statically allocated. The MAX_PUB_SIG enumeration comes in handy here.

(4) The union SmallEvent contains all events that are served by the “small” event pool.

(5) The union contains a pointer-size member to make sure that the union size will be at least that big.

(6) You add all events that you want to be served from this event pool.

Page 23: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

21 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(7) The memory buffer for the “small” event pool is statically allocated.

(8-9) main() starts with calling all static “constructors”. This step is not necessary in C++.

(10) The target board is initialized.

(11) QF is initialized together with the underlying OS/RTOS.

(12) The publish-subscribe mechanism is initialized. You don’t need to call QF_psInit() if your application does not use publish-subscribe.

(13) Up to three event pools can be initialized by calling QF_poolInit() up to three times. The subsequent calls must be made in the order of increasing block-sizes of the event pools. You don’t need to call QF_poolInit() if your application does not use dynamic events.

(14-15) All active objects are started using the “opaque” active object pointers. In this par-ticular example, the active objects are started without private stacks. However, some RTOSs, such as µC/OS-II, require pre-allocating stacks for all active objects.

(16) The control is transferred to QF to run the application. QF_run() might never return.

2.8 Step 8: Choosing the Real-Time Execution Model Listing 4(16) shows that the main() function eventually gives control to the event-driven framework by calling QF_run() to execute the application. This function can have different implementations, depending on which real-time execution model you choose. QP allows you to choose between a simple non-preemptive “Vanilla” kernel, the fully preemptive QK kernel, or a third-party RTOS, if you want to use one. This section explains the options.

2.8.1 Simple Non-Preemptive “Vanilla” Kernel The Example1a accompanying this Application Note uses the simplest QP configuration, in which QP runs on bare-metal target processor without any underlying operating system or kernel . Such a QP configuration is called “plain vanilla” or just “vanilla”.

QP includes a simple non-preemptive “vanilla” kernel which executes one active object at a time in the infinite loop (a.k.a., the background loop). The “vanilla” scheduler is engaged after each event is processed in the run-to-completion (RTC) fashion to choose the next highest-priority active ob-ject ready to process the next event. The “vanilla” kernel is cooperative, which means that all ac-tive objects cooperate to share a single CPU and implicitly yield to each other after every RTC step. The scheduler is non-preemptive, meaning that every active object must completely process an event before any other active object can start processing another event.

The interrupt service routines (ISRs) can preempt the execution of active objects at any time, but due to the simplistic nature of the “vanilla” scheduler, 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 ac-tive object can be delayed this way is called the task-level response. With the non-preemptive “va-nilla” scheduler, 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” scheduler is still a lot better than the traditional “superloop” (a.k.a., main+ISRs) architecture. In fact, the task-level response of the simple “vanilla” scheduler 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

Page 24: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

22 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

RTC steps into shorter ones (e.g., by using the “Reminder” state pattern described in Chapter 5 of PSiCC2).

2.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” scheduler might be too long. An example system could be a GPS receiver. Such a receiver performs a lot of floating point num-ber crunching on a fixed-point CPU to calculate the GPS position. At the same time, the GPS re-ceiver 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 mo-nopolize 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.

QP includes a tiny, fully preemptive, priority-based real-time kernel 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 you must be very careful with any resources shared among active objects. The Capstone example has been purposely designed to avoid any resource sharing among active ob-jects, so the application code does not need to change at all to run on top of the QK, or any other preemptive kernel or RTOS for that matter. The Example1b accompanying this Application Note demonstrates the use of the preemptive QK kernel.

2.8.3 Traditional OS/RTOS QP can also work with a traditional operating system (OS), such as Windows or Linux, or virtually any real-time operating system (RTOS) such as µC/OS-II to take advantage of the existing device drivers, communication stacks, and other middleware.

QP contains a portability layer, which makes adapting QP to virtually any operating system easy. The carefully designed QP portability layer allows tight integration with the underlying OS/RTOS by reusing any provided facilities for interrupt management, message queues, and memory partitions as QP critical section, event queues, or event pools, respectively. Porting QP is described in Chapter 8 of PSiCC2.

Page 25: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

23 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

3 The Quantum Spy (QS) Instrumentation The Capstone examples (Example1a and Example2b) demonstrate how to use the Quantum Spy (QS) to generate real-time trace of a running QP application. Normally, the QS instrumentation is inactive and does not add any overhead to your application, but you can turn the instrumentation on by defining the Q_SPY macro and recompiling the code.

Quantum Spy (QS) is a software tracing facility built into all QP components and also available to the Application code. QS allows you to gain unprecedented visibility into your application by selec-tively logging almost all interesting events occurring within state machines, the framework, the kernel, and your application code. QS software tracing is minimally intrusive, offers precise time-stamping, sophisticated runtime filtering of events, and good data compression (see Chapter 11 in PSiCC2 [PSiCC2]).

QS can be configured to send the real-time data out of the serial port of the target device. On the STR912F MCU, QS uses the built-in USART0 to send the trace data out (see Figure 3), so the QSPY host application can conveniently receive the trace data on the host PC. The complete implementa-tion of the QS software-tracing output consists of several steps, which are all summarized in Listing 5 (file bsp.c).

(1) #ifdef Q_SPY (2) static uint32_t l_currTime32; (3) static uint16_t l_prevTime16; (4) enum AppRecords { /* application-specific trace records */ PHILO_STAT = QS_USER }; #endif /*--------------------------------------------------------------------------*/ #ifdef Q_SPY (5) uint8_t QS_onStartup(void const *arg) { (6) static uint8_t qsBuf[BSP_QS_BUF_SIZE]; /* buffer for Quantum Spy */ GPIO_InitTypeDef GPIO_InitStruct; UART_InitTypeDef UART_InitStruct; (7) QS_initBuf(qsBuf, sizeof(qsBuf)); /* configure the UART0 for QSPY output ... */ (8) SCU_APBPeriphClockConfig(__UART0, ENABLE); /* enable clock for UART0 */ (9) SCU_APBPeriphClockConfig(__GPIO3, ENABLE); /* enable clock for GPIO3 */ (10) SCU_APBPeriphReset(__UART0, DISABLE); /* remove UART0 from reset */ (11) SCU_APBPeriphReset(__GPIO3, DISABLE); /* remove GPIO3 from reset */ /* configure UART0_TX pin GPIO3.4 ... */ (12) GPIO_DeInit(GPIO3); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; GPIO_InitStruct.GPIO_Direction = GPIO_PinOutput; GPIO_InitStruct.GPIO_Type = GPIO_Type_PushPull; GPIO_InitStruct.GPIO_IPConnected = GPIO_IPConnected_Disable; GPIO_InitStruct.GPIO_Alternate = GPIO_OutputAlt3; GPIO_Init(GPIO3, &GPIO_InitStruct); /* configure UART0... */ (13) UART_DeInit(UART0); /* force UART0 registers to reset values */ UART_InitStruct.UART_WordLength = UART_WordLength_8D; UART_InitStruct.UART_StopBits = UART_StopBits_1; UART_InitStruct.UART_Parity = UART_Parity_No; UART_InitStruct.UART_BaudRate = BSP_QS_BAUD_RATE;

Page 26: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

24 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

UART_InitStruct.UART_HardwareFlowControl = UART_HardwareFlowControl_None; UART_InitStruct.UART_Mode = UART_Mode_Tx; UART_InitStruct.UART_FIFO = UART_FIFO_Enable; UART_InitStruct.UART_TxFIFOLevel = UART_FIFOLevel_1_8; UART_InitStruct.UART_RxFIFOLevel = UART_FIFOLevel_1_8; UART_Init(UART0, &UART_InitStruct); /* initialize UART0 */ UART_Cmd(UART0, ENABLE); /* enable UART0 */ /* setup the QS filters... */ (14) QS_FILTER_ON(QS_ALL_RECORDS); QS_FILTER_OFF(...); ... (15) return (uint8_t)1; /* indicate successfull QS initialization */ } /*..........................................................................*/ (16) void QS_onCleanup(void) { } /*..........................................................................*/ /* NOTE: getTime is invoked within a critical section (inetrrupts disabled) */ (17) uint32_t QS_onGetTime(void) { (18) uint16_t currTime16 = (uint16_t)TIM3->CNTR; (19) l_currTime32 += (currTime16 - l_prevTime16) & 0xFFFF; (20) l_prevTime16 = currTime16; (21) return l_currTime32; } /*..........................................................................*/ (22) void QS_onFlush(void) { uint16_t nBytes = BSP_UART_TX_FIFO; /* the capacity of the UART TX FIFO */ uint8_t const *block; (23) while ((block = QS_getBlock(&nBytes)) != (uint8_t *)0) { (24) while ((UART0->FR & 0x80) == 0) { /* TX FIFO not empty? */ } /* keep waiting... */ (25) while (nBytes-- != 0) { UART0->DR = *block++; /* stick the byte to the TX FIFO */ } nBytes = BSP_UART_TX_FIFO; /* for the next time around */ } } #endif /* Q_SPY */ /*--------------------------------------------------------------------------*/

Listing 5 QS implementation to send data out of the UART0 of the STR912 device.

(1) The QS instrumentation is enabled only when the macro Q_SPY is defined

(2) The static l_currTime32 variable is used to hold the 32-bit-wide timestamp.

(3) The static l_prevTime16 variable is used to hold the last 16-bit-wide reading of the free-running 16-bit Timer 3 (the same used to generate the system clock tick interrupt).

(4) This enumeration defines application-specific QS trace record(s), to demonstrate how to use them.

(5) You need to define the QS_onStartup() callback to initialize the QS software tracing.

(6) You should adjust the QS buffer size (in bytes) to your particular application

(7) You always need to call QS_initBuf() from QS_onStartup() to initialize the trace buffer.

(8-9) The clock to the UART0 peripheral is enabled. Also, the clock to the GPIO3 peripheral is en-abled. GPIO3 controls the UART0 transmit and receive pins.

(10-11) The UART0 and GPIO3 peripherals are removed from reset.

Page 27: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

25 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

(12) The transmit pin GPIO3.4 is configured as output, alternative function 3 (UART0 Tx) using the ST driver library interface.

(13) The UART0 is not properly configured using the ST driver library interface.

(14) The QS filters are setup (see Chapter 11 in [PSiCC2] as well as “QP Reference Manual” online).

(15) The QS_onStartup() callback returns 1, meaning that the QS initialization was successful.

(16) The QS_onCleanup() callback is empty for MSP430 (the application never exits).

(17-21) The QS_onGetTime() callback provides a fine-granularity timestamp. The timestamp is dis-cussed in the next section.

(22) The QS_onFlush() callback flushes the QS trace buffer to the host. Typically, the function busy-waits for the transfer to complete. It is only used in the initialization phase for sending the QS dictionary records to the host.

(23) The implementation of QS for STR912 uses the block-oriented QS-buffer interface QS_getBlock(), which provides up to 16 bytes to fill the FIFO of the UART (see Chapter 11 in [PSiCC2]).

(24) The QS_onFlush() callback busy-waits in-line until the transmit buffer is empty.

(25) The data byte is inserted into the UART0 data register.

3.1 QS Time Stamp Callback QS_onGetTime() The platform-specific QS port must provide function QS_onGetTime() (Listing 5(17-21)) that returns the current time stamp in 32-bit resolution. To provide such a fine-granularity time stamp, the BSP uses the free running Timer 3, which is the same timer already used for generation of the system clock-tick interrupt.

NOTE: The QS_onGetTime() callback is always called with interrupts locked.

Figure 7 shows how the Timer 3 Count Register (TIM3->CNTR) reading is extended to 32 bits.

The drawing below shows a free running Timer 3 Count Register (TIM3->CNTR) that counts up from 0 to 0xFFFF and rolls over to 0. If the system tick rate is faster than the rollover rate then you could ‘oversample’ this free-running timer by reading it in the clock tick ISR.

The 32-bit variable l_currTime32 contains the sum of the 16-bit deltas from each readout of the free running Timer 3 Count Register. Because of unsigned 16-bit arithmetic used in Listing 5(19), even a ‘small’ current value minus a ‘large’ previous value still results in the proper delta. Note that QS_onGetTime() can actually be called at just about any time and thus, also needs to update l_currTime32 before it returns the current value.

Page 28: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

time

count

0xFFFF

TIM3->CNTR Register

System clock tick period

32-bit time stamp returned from QS_onGetTime()

System clock-tick

0x0000

Figure 7 Using the Timer 3 Count Register to provide 32-bit QS time stamp.

3.2 Invoking the QSpy Host Application The QSPY host application receives the QS trace data, parses it and displays on the host work-station (currently Windows or Linux). For the configuration options chosen in this port, you invoke the QSPY host application as follows (please refer to the QSPY section in the “QP Reference Man-ual”):

qspy –c COM1 –b 115200

The following Figure 8 shows the human-readable output from the QSPY host application connected to the SR912-SK board running the Capstone code.

26 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 29: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

Figure 8 QSPY software trace output from the Capstone example

27 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

Page 30: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

28 of 29 Copyright © Quantum Leaps, LLC. All Rights Reserved.

4 References Document Location

[PSiCC2] “Practical UML Statecharts in C/C++, Second Edition”, Miro Samek, New-nes, 2008, ISBN 0750687061

Available from most online book retailers, such as amazon.com. See also: http://www.state-machine.com/psicc2.htm

[Netrino 08] Embedded Software Boot Camp training program, Netrino 08

http://www.netrino.com/Embedded-Systems/Training-Courses/Boot-Camp

[QP/C 08] “QP/C Reference Manual”, Quan-tum Leaps, LLC, 2008

http://www.state-machine.com/doxygen/qpc/

[QP/C++ 08] “QP/C++ Reference Manual”, Quantum Leaps, LLC, 2008

http://www.state-machine.com/doxygen/qpcpp/

[QP-nano 08] “QP-nano Reference Manual”, Quantum Leaps, LLC, 2008

http://www.state-machine.com/doxygen/qpn/

[QL AN-Directory 07] “Application Note: QP Directory Structure”, Quantum Leaps, LLC, 2007

http://www.state-machine.com/doc/-AN_QP_Directory_Structure.pdf

Page 31: AN: Capstone Dive Computer Exampleold.state-machine.com/resources/AN_Capstone.pdf · alone QF configurations have been ported to over 10 different CPU architectures, ranging from

Application Note: Capstone Dive Computer Example

www.state-machine.com

5 Contact Information Quantum Leaps, LLC 103 Cobble Ridge Drive Chapel Hill, NC 27516 USA +1 866 450 LEAP (toll free, USA only) +1 919 869-2998 (FAX) e-mail: [email protected] WEB : http://www.quantum-leaps.com http://www.state-machine.com

“Practical UML State-charts in C/C++, Second Editio(PSiCC2), by Miro Samek, Newnes, 2008,

29 of 29

n”

ISBN 0750687061

Netrino, LLC 9250 Bendix Road, Suite 505 Columbia, Maryland 21045 Toll-free: (866) 78-EMBED Main: +1 (410) 997-3401 Fax: +1 (410) 997-3402 WEB: http://www.netrino.com

Copyright © Quantum Leaps, LLC. All Rights Reserved.