AN: Capstone Dive Computer Example - Quantum Leaps

state.machine.com

AN: Capstone Dive Computer Example - Quantum Leaps

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


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

Copyright © Quantum Leaps, LLC. All Rights Reserved.

i


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 eventdriven

applications. QP enables building well-structured embedded applications as a set of concurrently

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 Edition”

[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 instrumentation

(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 completely

replace a traditional RTOS. QP includes a simple non-preemptive scheduler and a fully preemptive

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

1 of 29


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 application

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 burdening

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 footprints.

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 written

in strict adherence to the coding standard documented at www.state-machine.com/doc/-

AN_QL_Coding_Standard.pdf.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

2 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 \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 critical

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 confidence

in event-driven programming, you need to understand how a real-time framework is ultimately

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 replacing

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 standalone

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 device

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, including

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 hierarchical

event processor. The two components complement each other in that QEP provides the UMLcompliant

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 latter

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-infirst-out

(FIFO) and last-in-first-out (LIFO) policies. QF supports also the more advanced publishsubscribe

event deliver mechanism, as described in Chapter 6 of [PSiCC2]. Both mechanisms can

coexist in a single application.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

3 of 29


1.6 Zero-Copy Event Memory Management

Application Note:

Capstone Dive Computer Example

www.state-machine.com

Perhaps that most valuable feature provided by the QF real-time framework is the efficient “zerocopy”

event memory management, as described in Chapter 6 of [PSiCC2]. QF supports event multicasting

based on the reference-counting algorithm, automatic garbage collection for events, efficient

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 periodic

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 cooperative

“vanilla” kernel, or the QK preemptive kernel (Chapter 10 in [PSiCC2]). The second native

queue version is a simple “thread-safe” queue not capable of blocking and designed for sending

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 pointers

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 Section

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 kernel.

As described in Section 6.3.8 in Chapter 6, run-to-completion kernels, like QK, provide preemptive

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].

Copyright © Quantum Leaps, LLC. All Rights Reserved.

4 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 sleepmodes

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 features,

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 carefully

designed to place the MCU into a low-power sleep mode safely and without creating race conditions

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 visibility

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 instrumentation.

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

5 of 29


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).

Idle task

LED16

ADC conv

LED15

Alarm

LED14

Diving

LED11

Surfaced

LED10

Surfaced

LED9

STR912

ARM9 MCU

Speaker

for alarms

Ascent rate

bar-graph

Depth

in meters/feet

2x16

character LCD

Amount of gas

bar-graph

Time to Surface

/ Dive Time

Button B1

add gas

Figure 3 The Dive Computer User Interface.

Button B2

depth units

Ascent rate

potentiometer

Inputs

• Button B1

• Button B2

• Potentiometer via A/D Converter

Outputs

• LEDs

• LCD Display

• Speaker

The hardware inputs are used to provide stimuli to a simulated Dive Computer. The hardware outputs

provide a user interface and feedback to the operator of the system, as he or she performs

simulated dives.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

6 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 increments;

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 increase/decrease

depth

o gas_rate_in_cl(depth_in_mm) returns the volume of air, in centiliters, consumed

each half second at a given depth

o gas_to_surface_in_cl(depth_in_mm) performs the numerical integration necessary

to determine the amount of air, in centiliters, necessary to surface safely

Copyright © Quantum Leaps, LLC. All Rights Reserved.

7 of 29


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 coupling

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 components

of the application must make requests (via events) to the respective “manager” of that resource.

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

8 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

(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 produced

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 derived

from the QEvent base structure. Listing 1 shows signals and events used in the Capstone application.

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 actually

identical in both versions.

#ifndef capstone_h

#define capstone_h

(1) enum CapstoneSignals {

Copyright © Quantum Leaps, LLC. All Rights Reserved.

9 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

(2) B1_DOWN_SIG = Q_USER_SIG, /**< button B1 depressed */

B1_UP_SIG,

B2_DOWN_SIG,

/**< button B1 released */

/**< 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_SILENCE_SIG,

/**< alarm request to AlarmMgr */

/**< 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 enumeration

(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 reserved

QEP signals.

(3) The globally published signals are grouped at top of the enumeration. The MAX_PUB_SIG enumeration

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 sidebar

below).

Copyright © Quantum Leaps, LLC. All Rights Reserved.

10 of 29


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 embedding

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 always

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 unnamed

padding within a structure object, but not at its beginning” [ISO/IEC 9899:TC2]. In particular,

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 structures

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 Capstone

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().

Copyright © Quantum Leaps, LLC. All Rights Reserved.

11 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

(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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

12 of 29


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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

13 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 “always”

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 actions

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 object

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 necessarily

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 refer

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 itself

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 internal

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 toggles

between Dive-Time and Time to Surface (TTS).

Copyright © Quantum Leaps, LLC. All Rights Reserved.

14 of 29


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 superstate

“always”. The substate can override the inherited behavior and it can also define new behavior.

2.4.2 AlarmMgr State Machine

The main responsibility of the Alarm Manager (AlarmMgr) state machine is to handle all alarm conditions

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 handle

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 discovering

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

15 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 techniques

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).

Copyright © Quantum Leaps, LLC. All Rights Reserved.

16 of 29


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

Application Note:

Capstone Dive Computer Example

www.state-machine.com

(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 beginning

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 includes

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 (timers).

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;

Copyright © Quantum Leaps, LLC. All Rights Reserved.

17 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

(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;

Copyright © Quantum Leaps, LLC. All Rights Reserved.

18 of 29


eturn Q_HANDLED();

}

}

(11) return Q_SUPER(&QHsm_top);

}

Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 unresponsive

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() function

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 actually

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 conjunction

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,

Copyright © Quantum Leaps, LLC. All Rights Reserved.

19 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

20 of 29


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

Application Note:

Capstone Dive Computer Example

www.state-machine.com

(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 particular

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 object

ready to process the next event. The “vanilla” kernel is cooperative, which means that all active

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 active

object can be delayed this way is called the task-level response. With the non-preemptive “vanilla”

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

Copyright © Quantum Leaps, LLC. All Rights Reserved.

21 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 number

crunching on a fixed-point CPU to calculate the GPS position. At the same time, the GPS receiver

must track the GPS satellite signals, which involves closing control loops in sub-millisecond

intervals. It turns out that it’s not easy to break up the position-fix computation into short enough

RTC steps to allow reliable signal tracking.

But the RTC semantics of state machine execution does not mean that a state machine has to monopolize

the CPU for the duration of the RTC step. A preemptive kernel can perform a context

switch in the middle of the long RTC step to allow a higher-priority active object to run. As long as

the active objects don’t share resources they can run concurrently and complete their RTC steps

independently.

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 objects,

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

22 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 selectively

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 timestamping,

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 implementation

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 {

PHILO_STAT = QS_USER

/* application-specific trace records */

};

#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_InitStruct.UART_Parity

= UART_StopBits_1;

= UART_Parity_No;

UART_InitStruct.UART_BaudRate = BSP_QS_BAUD_RATE;

Copyright © Quantum Leaps, LLC. All Rights Reserved.

23 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

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 */

(14) QS_FILTER_ON(QS_ALL_RECORDS);

QS_FILTER_OFF(...);

...

/* setup the QS filters... */

(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)

(20)

l_currTime32 += (currTime16 - l_prevTime16) & 0xFFFF;

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 enabled.

GPIO3 controls the UART0 transmit and receive pins.

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

Copyright © Quantum Leaps, LLC. All Rights Reserved.

24 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

(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 discussed

in the next section.

(22) The QS_onFlush() callback flushes the QS trace buffer to the host. Typically, the function busywaits

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

25 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

count

32-bit time stamp

returned from

QS_onGetTime()

System

clock tick

period

TIM3->CNTR

Register

0xFFFF

0x0000

System

clock-tick

time

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 workstation

(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 Manual”):

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.

Copyright © Quantum Leaps, LLC. All Rights Reserved.

26 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

Figure 8 QSPY software trace output from the Capstone example

Copyright © Quantum Leaps, LLC. All Rights Reserved.

27 of 29


Application Note:

Capstone Dive Computer Example

www.state-machine.com

4 References

Document

[PSiCC2] “Practical UML Statecharts in

C/C++, Second Edition”, Miro Samek, Newnes,

2008, ISBN 0750687061

[Netrino 08] Embedded Software Boot Camp

training program, Netrino 08

[QP/C 08] “QP/C Reference Manual”, Quantum

Leaps, LLC, 2008

[QP/C++ 08] “QP/C++ Reference Manual”,

Quantum Leaps, LLC, 2008

[QP-nano 08] “QP-nano Reference Manual”,

Quantum Leaps, LLC, 2008

[QL AN-Directory 07] “Application Note: QP

Directory Structure”, Quantum Leaps, LLC,

2007

Location

Available from most online book retailers, such as

amazon.com. See also: http://www.statemachine.com/psicc2.htm

http://www.netrino.com/Embedded-

Systems/Training-Courses/Boot-Camp

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

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

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

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

AN_QP_Directory_Structure.pdf

Copyright © Quantum Leaps, LLC. All Rights Reserved.

28 of 29


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: info@quantum-leaps.com

WEB : http://www.quantum-leaps.com

http://www.state-machine.com

“Practical UML Statecharts

in C/C++,

Second Edition”

(PSiCC2),

by Miro Samek,

Newnes, 2008,

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.

29 of 29

More magazines by this user
Similar magazines