Practical UML Statecharts in C/C++, Second Edition: Event-Driven Programming for Embedded Systems (PSiCC2) is the most popular book about UML statecharts and event-driven programming for embedded systems. This ultimate resource describes all the related concepts and provides a very detailed design study of the QP™ frameworks.
companion page to PSiCC2
QP datasheet
(PDF 1.4MB)
What is QP™?
QP™ is a family of very lightweight, open source frameworks for embedded microprocessors, microcontrollers, and DSPs. QP™ enables developing well-structured embedded applications as a set of concurrently executing hierarchical state machines (UML statecharts). With QP™, coding of modern state machines directly in C or C++ is a non-issue. No big design automation tools are needed.
Current versions of QP™ are: 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.
QP™ can work with or without a traditional OS/RTOS. In the standalone configuration, QP™ can completely replace a traditional RTOS. QP™ includes a simple non-preemptive scheduler and a fully preemptive kernel (QK). The QP/C and QP/C++ frameworks can also work with a traditional OS/RTOS to take advantage of existing device drivers, communication stacks, and other middleware.
QP™ has a strong user community and has been applied worldwide by some of the largest companies in their respective industries, such as: consumer electronics, telecommunications equipment, industrial automation, transportation systems, medical devices, national defense, and many more.
Why do you need it?
Most software developers are accustomed to the basic sequential control, in which a program (a "superloop" or a task in an traditional RTOS) waits for events in various places in its execution path by either actively polling for events or passively blocking on a semaphore or other such RTOS mechanism. Though this approach is functional in many situations, it doesn't work very well when the system must timely react to multiple events whose arrival times and order one cannot predict. The fundamental problem is that while a sequential task is waiting on one kind of event, it is not doing any other work and is not responsive to other events.
A long known, better alternative is to structure the software around the event-driven programming model, which requires a distinctly different way of thinking than conventional sequential programs. Event-driven systems are naturally divided into the application, which actually handles the events, and the supervisory event-driven framework, which waits for events and dispatches them to the application. The control resides in the event-driven framework, so from the application standpoint, the control is inverted compared to a traditional sequential program. To remain responsive, the event-driven application cannot block, but must quickly return control after handling each event. Thus, the execution context cannot be preserved in the stack-based variables and the program counter as it is in a sequential task. Instead, the event-driven application becomes a state machine, or actually a set of collaborating state machines that preserve the context from one event to the next in the static variables.
Traditionally, event-driven programming of embedded systems required highly sophisticated design-automation tools, such as Rational Rose-RT or I-Logix Rhapsody (now both acquired by IBM). But the really important part of any such tool is not the flashy GUI for drawing the diagrams. The most valuable part of those systems is the event-driven, state machine-based framework that each of those tools contains and which forms the basis for the automatic code generation.
The open source QP™ state machine frameworks are in many respects similar to the frameworks buried in all commercially successful tools. The only difference is that the QP™ frameworks are not concerned with facilities for animation of state machines and are not biased toward mechanical code generation. Instead, the QP™ frameworks are designed for direct, manual coding of event-driven applications.
When you start using QP™, you will quickly discover that coding even the most involved state machines is really a non issue. Your problems will change. You will no longer struggle with 15 levels of convoluted if-else statements, and you will stop worrying about semaphores or other such low-level RTOS mechanisms. Instead, you will start thinking at a higher level of abstraction about events, state machines, and state transitions. After you experience this quantum leap you will find that programming can be much more fun. You will never want to go back to the "spaghetti" code or the raw RTOS.
QP Feature Summary
QP consists of a universal UML-compliant event processor (QEP™), a portable, event-driven, real-time framework (QF®), a tiny run-to-completion kernel (QK™), and software tracing system (QS™).
QEP™ is a universal UML-compliant event processor that enables direct coding of UML statecharts in highly maintainable C or C++, in which every state machine element is mapped to code precisely, unambiguously, and exactly once (traceability). QEP fully supports hierarchical state nesting, which enables reusing behavior across many states instead of repeating the same actions and transitions over and over again.
QF® is a highly portable, event-driven, real-time framework for concurrent execution of state machines specifically designed for real-time embedded (RTE) systems.
QK™ is a tiny preemptive non-blocking run-tocompletion kernel designed specifically for executing state machines in a run-to-completion (RTC) fashion.
QS™ is a software tracing system that enables live monitoring of event-driven QP applications with minimal target system resources and without stopping or significantly slowing down the code.
| QP/C | QP/C++ | QP-nano | |
|---|---|---|---|
| UML-Compliant Event Processor (QEP™) | |||
| Feature | QEP/C | QEP/C++ | QEP-nano |
| Highly maintainable and traceable boilerplate state machine mapping to C or C++ | ![]() |
![]() |
![]() |
| Full support for hierarchical state nesting | ![]() |
![]() |
![]() |
| Full support for automatic entry/exit action execution on arbitrary state transition topology | ![]() |
![]() |
![]() |
| Full support of nested initial transitions | ![]() |
![]() |
![]() |
| Number of states limited only by code space in ROM | ![]() |
![]() |
![]() |
| Support for events with parameters | arbitrary event parameters |
arbitrary event parameters |
fixed event parameter1 |
| Extremely small data requirements (RAM footprint) | 1 pointer-to- function per state machine |
1 pointer-to- function per state machine plus the VPTR |
1 pointer-to- function per state machine |
| Very small code size (ROM footprint) | 0.6-1.5 KB2 | 0.8-2.0 KB2 | 0.5-1.0 KB2 |
| Support for simpler non-hierarchical Finite State Machines with entry/exit actions | ![]() |
![]() |
![]() |
| Fully reentrant event processor code with minimal stack requirements | ![]() |
![]() |
![]() |
| Source code 98% compatible with MISRA guidelines and passing strict analysis with PC-lint | ![]() |
![]() |
![]() |
| QSPY software tracing instrumentation for testability | ![]() |
![]() |
![]() |
| Real-Time Framework (QF®) | |||
| Feature | QF/C | QF/C++ | QF-nano |
| Support for active object computing with the following number of active objects: | up to 63 | up to 63 | up to 8 |
| Deterministic thread-safe execution of active objects | ![]() |
![]() |
![]() |
| Inherently low-power architecture | ![]() |
![]() |
![]() |
| Direct event delivery with first-in-first-out (FIFO) policy | ![]() |
![]() |
![]() |
| Direct event delivery with last-in-first-out (LIFO) policy | ![]() |
![]() |
![]() |
| Publish-subscribe event delivery with event multicasting capabilities | ![]() |
![]() |
![]() |
| Dynamic events with zero-copy event passing policy for maximum performance | ![]() |
![]() |
3 |
| Automatic recycling of dynamic events (garbage collection for events) | ![]() |
![]() |
4 |
| Efficient zero-copy deferring and recalling events | ![]() |
![]() |
![]() |
| One-shot time events (one-shot timers) | 5 |
5 |
6 |
| Periodic time events (periodic timers) | 5 |
5 |
![]() |
| Thread-safe event queues for task-to-ISR communication | 7 |
7 |
![]() |
| Thread-safe memory partitions (fixed size heaps) for application use | ![]() |
![]() |
![]() |
| Platform Abstraction Layer (PAL) for ease of portability | ![]() |
![]() |
![]() |
| PAL supports integration with a traditional OS/RTOS | ![]() |
![]() |
![]() |
| Cooperative kernel with prioritized execution of active objects | ![]() |
![]() |
![]() |
| Small, scalable code size (ROM footprint) | 2-4 KB2 | 2-4 KB2 | 0.5-2 KB2 |
| Source code 98% compatible with MISRA guidelines and passing strict analysis with PC-lint | ![]() |
![]() |
![]() |
| Assertion-based error handling | ![]() |
![]() |
![]() |
| QSPY™ software tracing instrumentation for testability | ![]() |
![]() |
![]() |
| Preemptive Run-to-Completion Kernel (QK™) | |||
| Feature | QK/C | QK/C++ | QK-nano |
| Preemptive, priority-based, deterministic execution of one-shot tasks (active objects) | up to 63 tasks |
up to 63 tasks |
up to 8 tasks |
| Extremely fast run-to-completion event processing without blocking | ![]() |
![]() |
![]() |
| Single stack for all tasks and interrupts | ![]() |
![]() |
![]() |
| Allows using compiler-generated interrupt service routines | ![]() |
![]() |
![]() |
| Highly portable to most CPUs and compilers | ![]() |
![]() |
![]() |
| Priority-ceiling mutexes | ![]() |
![]() |
![]() |
| Extended context switch for coprocessors | ![]() |
![]() |
![]() |
| Thread-local storage | ![]() |
![]() |
![]() |
| QSPY software tracing instrumentation for testability | ![]() |
![]() |
![]() |
| Software Tracing (QS™) | |||
| Feature | QS/C | QS/C++ | |
| Minimally intrusive, thread-safe software tracing for all QP components | ![]() |
![]() |
|
| Good data compression minimizing the buffering and bandwidth requirements | ![]() |
![]() |
|
| Generic mechanisms for logging of application-level activity | ![]() |
![]() |
|
| Sophisticated runtime filtering of records based on record-type and object type | ![]() |
![]() |
|
| Precise time-stamping of trace records | ![]() |
![]() |
|
| Robust HDLC-like data protocol | ![]() |
![]() |
|
| Decoupled data logging and transmission for flexibility and portability | ![]() |
![]() |
|
| Portable host-based application (QSPY) with full source code | ![]() |
![]() |
|
| QSPY MATLAB® interface | ![]() |
![]() |
|
|
1 QEP-nano supports only fixed-size event parameter(s) of 0 (no parameter), 1, 2, or 4 bytes
2 The actual code size depends on the processor and the compiler, but the given ranges are representative 3 QF-nano copies entire fixed-size events 4 QF-nano does not need automatic event recycling 5 In QF/C and QF/C++ time events are allocated by the application and there is no limit how many could be used 6 QF-nano provides one time event per active object 7 In QF/C and QF/C++ thread-safe event queues are allocated by the application and there is not limit how many could be used |
|||
日本語
中文
한국어
Français
Deutsch
English
QP™ Frameworks
QM™ Modeling Tool
Companion page to PSiCC2
Companion page to PSiCC1
