Hello and welcome to the Modern Embedded Systems Programming course.
My name is Miro Samek and in this lesson I'd like to go back to event-driven programming and
Active Objects in particular. You've been using event-driven Active Objects since lesson #34,
but perhaps it's not quite clear to you what advantages they provide in real-time programming.
So, this lesson ties together the concepts of event-driven programming,
active objects, state machines and real-time operating system (RTOS).
I talked about all these concepts in this video course before.
In fact, let's go back today to the project for lesson # 27 about the RTOS.
Copy the lesson-27 directory and rename it to lesson-43.
Get inside the new lesson-43 directory and click
on the project "lesson" to open it in the Micro-Vision IDE.
To remind you quickly what happened back in lesson-27, you experimented with a preemptive,
priority-based RTOS kernel called QXK, which is a prat of the QP/C framework.
This RTOS kernel managed two traditional threads: blinky-1 and blinky-2. Thread
blinky-3 was not started and not used, so let's just clean it up.
To understand what the threads were doing, let's just build and load the code to the
TivaC LaunchPad board and then watch how it works using a logic analyzer.
The logic analyzer trace labeled Blinky-1 corresponds to the blinky-1 thread,
while the trace labeled Idle corresponds to the idle thread of the QXK kernel.
When you zoom in, you can see that both threads rapidly toggle the LED lines:
Green-LED in case of Blinky-1 and Red-LED in case of the Idle thread.
Blinky-1 runs for about
1200 microseconds, after which it delays its execution till the next clock tick.
The result is that Blinky-1 runs periodically every 2 milliseconds.
The idle thread of the QXK kernel runs when no other threads are running.
Now, there is also the blinky-2 thread, started with lower priority than blinky-1.
However, blinky-2 waits on a semaphore, which is signaled when the switch SW1 is pressed.
So let's set the trigger in your logic analyzer on the falling edge of the SW1 trace,
because the switch is active low.
Now, when you start the trace, you don't see anything until the SW1 switch is pressed.
By the way, I received quite a few inquiries about inexpensive logic analyzers.
So, today, I'm using a very cheap logic analyzer, that you can get for
about $10 dollars on Amazon.com and perhaps other online retailers.
This logic analyzer works with the open-source PulseView software that I'm using in this video.
I've put some resources about this tool in the video description below.
But going back to your real-time project, it has been specifically design to showcase the
principles of Rate Monotonic Scheduling (RMS), also known as Rate Monotonic Analysis (RMA),
that I've introduced back in lesson #26. You can see here RMS in action. The Blinky-1
thread with the highest rate runs at the highest priority. And because of this,
it always meets its hard real-time deadline of 2 milliseconds, even if other threads are ready
to run, like Blinky-2 or the idle thread. Now, these were traditional blocking threads
managed by a traditional preemptive, priority-based RTOS kernel.
The main question for today is: can Active Objects also do this? In other words, can Active
Objects be compatible with the RMA/RMS method to deliver provably hard real-time behavior.
Well, the purpose of this lesson is to find it out as well as to explore other
properties of active objects related to concurrency and real-time deadlines.
The first step is to convert the blinky-1 and blinky-2 threads to active objects. You've seen
such conversion before, in the second half of lesson 34, so I'll go over this quickly.
I will do the coding still manually today, although I will use the QM modeling tool for
illustration. The QM model will be included in the project download for this lesson,
so you will be able to try automatic code generation if you like.
So, starting with blinky-1, you need to to create the Blinky1 active object class
that inherits the QActive base class from the QP/C active object framework. In C,
you do this by declaring a Blinky1 struct with the first member "super" of type QActive.
Blinky1 active object will need a time event,
to replace the blocking time delay from the corresponding blinky-1 thread.
The Blinky1 class needs a constructor function
...as well as a very simple, state machine consisting of the top-most initial pesudostate
and just one state "active".
The constructor must initialize the superclass Qactive
The top-most initial pseudostate arms the time event to fire in
2 clock ticks and then periodically every 2 clock ticks. With system clock tick occurring
every millisecond, this will make Blinky1 periodic with the period of 2 milliseconds.
The top-most initial transition goes to the state "active".
The state "active" has only one internal transition triggered by
TIMEOUT_SIG, so here is the skeleton code according to the "optimal" state
machine implementation that I've introduced back in lesson #39.
The action executed in the TIMEOUT internal transition
is the exact body of the original blinky-1 thread, minus the blocking delay, of course.
At this point, you can delete the original
code for the blinky-1 thread and move on to blinky-2.
The Blinky-2 active object has quite similar structure to Blinky-1,
so I copy, paste, and replace Blinky1 with Blinky2 in the selected code.
Blinky2 does not need the time event.
And also, it handles the BUTTON_PRESS event instead of TIMEOUT.
The action executed in the BUTTON_PRESS internal transition is the exact body
of the original blinky-2 thread, minus the blocking semaphore, of course.
Now, you can delete the blinky-2 thread code. But before deleting the blinky-2 thread instance
and the blinky-2 private stack, let's think for a minute what you need for the Blinky2
active object as the replacement. Well, you need the Blinky2 instance
and you need an event queue buffer. The length of the queue must be adequate for
the worst-case scenario, but 10 events is typically a safe initial guess.
On the other hand, and most interestingly, you no longer need the private stack for
the Blinky2 active object. This is because the underlying
QXK RTOS kernel takes advantage of the non-blocking nature of active objects,
and executes them as so called "basic threads". Basic threads are run-to-completion activations
as opposed to endless loops of traditional, blocking threads that are then called
"extended threads". This allows all "basic threads" to use the same stack. In a minute you
will see in the logic analyzer trace that these basic threads can still preempt each other.
Of course, you also need to create the event queue buffer and the instance for
the Blinky1 active object, but again, you don't need the private stack for it.
So, the net result is that you save a lot of precious RAM,
because stacks are typically much bigger than event queues.
Moving on, you can delete the blocking semaphore.
And instead of the extended blinky-1 thread,
you need to call the constructor of your Blinky1 active object.
Now you need to tell the framework that you're starting an active object,
as opposed to an extended thread. For this you use the QACTIVE_START() API.
The signature of the parameters remains the same, so you leave the same priority,
but you need to provide the event queue butter and its length, and you don't provide the stack.
You apply analogous changes to start the Blinky2 active object.
So, this would be all for main-dot-c,
but you still need to adjust a few things in the Board Support Package BSP.
For example, you still need to define the newly introduced event signals like BUTTON_PRESS_SIG
and TIMEOUT_SIG. For this, open BSP-dot-H,
and declare an enumeration for the signals.
Remember that in QP the signals cannot start with
zero but must be offset by the Q_USER_SIG constant.
Also, instead of the semaphore, you will be posting events to your active objects,
and for that you'll need global pointers to them.
You still need to add the definitions and initializations of the global
active object pointers in main-dot-C.
And also you replace the signaling on the semaphore with posting an immutable
BUTTON_PRESS event to the Blinky2 active object, just as you did for the TimeBomb
project in the previous lessons. This should finally be all,
so let's try to build the code. Ah, OK, the problem is the missing
definition of the loop index, that was dropped during copy-and-paste.
Alright, the code builds cleanly, so let's
load it to your TivaC LaunchPad board and see how it works!
Well, it works exactly as before with the traditional threads.
Specifically, pressing the button causes Blinky2 to run as before, but Blinky1 keeps
running completely undisturbed and meets its hard real-time deadlines every time.
This is because Blinky1, having higher priority, freely preempts Blinky2.
So, even though Blinky2 does all its processing in run-to-completion steps,
still, it is preempted by Blinky1 -- multiple times.
But this is entirely fine, because Blinky2 eventually completes its run-to-completion step
before handling another BUTTON_PRESS event in the future.
This illustrates, the most important point of this lesson: The run-to-completion execution
semantics of active objects and state machines does NOT mean that a state machine
needs to monopolize the CPU for the whole duration of the run-to-completion step.
Under a preemptive kernel, such as QXK, run-to-completion steps in active objects can
and will be interleaved in time. This is handled entirely by the underlying real-time kernel.
So, if this kernel is preemptive, active objects can preempt each other and can meet hard real-time
deadlines, just as the traditional threads did. What that means is that active objects
with state machines, when combined with a preemptive, priority-based real-time kernel,
are as suitable for the Rate-Monotonic Scheduling (RMS) as the traditional extended threads.
Actually, active objects are even more suitable for hard real-time and for the
RMS method in particular, because they don't share resources and instead use asynchronous events.
But this is such a critically important aspect of active objects
that it deserves its own lesson, and this is exactly what I will explain next time.
If you like this channel, please give this video a like and subscribe to stay tuned.
You can also visit state-machine.com/video-course for the class notes and project file downloads.
Finally, all the projects are also available on GitHub
in the Quantum Leaps repository "modern embedded programming course". Tanks for watching!