Hello and welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and in this lesson I continue the subject of Active Objects for Real-Time. Specifically, today you'll see how the use of asynchronous, mutable events makes Active Objects even more suitable for hard real-time than traditional with a Real-Time Operating System (RTOS). As usual, let's get started by copying the previous lesson-43 directory to lesson 44. Get inside the new lesson-44 directory and double-click on the project lesson to open it in the micro-Vision IDE. To remind you quickly what happened in the last lesson-43, you saw that Active Objects do NOT have to monopolize the CPU for the whole duration of run-to-completion event-processing. Indeed, under a preemptive, priority-based scheduler, Active Objects with their state machines CAN freely preempt each other, and so they meet all the requirements of the Rate-Monotonic Scheduling (RMS) or Rate-Monotonic Analysis (RMA) method, which you learned back in lesson #26. But the example in last lesson was a bit simplistic in that it didn't include any interactions: neither among the original RTOS threads nor among the Active Objects, after you've translated the threads to active objects. So, today you will make the example much more realistic by adding interactions among Active Objects to see how that impacts their real-time behavior. Specifically, suppose that now after each button press you want your Blinky2 thread to change the blinking pattern of your Blinky1 thread. The first implementation I'll show you will be based on a naive sharing of global variables between the Blinky1 and Blinky2 active objects. Such sharing is precisely something that I've been trying to convince you to avoid, at any cost. But perhaps the best way to learn a rule like that is to see the consequences of breaking the rule. So, let's exactly break the "no-sharing" rule as follows: Let's replace the current, hard-coded blinking pattern of the Blinky1 thread with global variables, so that the pattern can be later changed. The global variable "g_ticks" will hold the number of clock ticks for the time event. It will be initialized with the current value of clock ticks. The global variable "g_iter" will hold the number of iterations inside the Blinky1 event handler. It will also be initialized with the current number of iterations. But actually, the time event cannot be armed for periodic timeouts only once at the beginning, because it won't change the timeout as the global variable g_ticks changes. So, instead, let's arm the time event for one-shot timeout and then arm it again after each timeout. Now, comes the sharing of the global variables in Blinky2. Here, after each button press event you want to change the global variables "g_ticks" and "g_iter" to some new values, which will in turn change the blinking pattern in Blinky1. Such change can be accomplished in many ways, but one simple solution is to store the values in two static and constant arrays for ticks and iterations, respectively. And then to set the global variables from the arrays indexed by a private sequence counter. To make sure that a different set of values is used each time, the sequence counter needs to be incremented and wrapped around at the end of the sequence. Finally, of course you need to add the sequence counter to the Blinky2 class and you need to initialize the counter in the top-most initial transition. Alright, let's run this code in your TivaC LaunchPad board... At the first button press, you can see that the blinking pattern of Blinky1 changes from fast to slow. At the next press, the pattern changes back from slow to fast. So the code appears to be working just fine. But I hope that by now you can see that there is a race condition in this code. If you don't see it, please re-watch lesson #20 about race conditions. But yes, if the button press would come just after changing g_ticks but before changing g_iter, the Blinky1 active object would see an inconsistent pair of values: the new g_ticks and the old g_iter. Now, this race condition exists in a very narrow time window, so most likely it would escape casual testing in the lab. But rest assured that if you ship your code in thousands of devices to thousands of customers, some of them will inadvertently trigger the race condition. One way or another, it's much better to face the problem right now, during the development, than to wait for the customers to find it. So, let's increase the chance of the race condition by adding some workload between setting g_ticks and g_iter. For example, you can borrow one-third of the workload from the loop below, like that. Such delay between setting variables might seem completely unnatural at first, but it actually can easily happen in practice. For example, if the values were delivered by a serial interface. Serial transmission of the 4 bytes of g_iter would take about as much as your loop. I want to stress it once more that the loop is NOT creating the problem, because the race condition exists even without any loop, it merely makes it easier to find. But anyway, I'm sure you're curious to see how that works on your TivaC board. Well, now the timing of Blinky1 is completely different. Just after some activity in Blinky2, Blinky1 takes over the whole CPU and nothing else is running. Alright, I reset the board and try again: Essentially the same thing. Now, I try without resetting the board... And this time, Blinky1 was still taking all the CPU to begin with, so the button press didn't make any difference. So, what's going on? Well, when you zoom in, you can see that Blinky1 occasionally pauses, which happens with two overlapping periods: 1000 microseconds and about 1285 microseconds. The 1000 microsecond period is caused by the SysTick interrupt preempting the Blinky1 active object. But the 1285 microsecond period is caused by the original Blinky1 workload of 1500 iterations, corresponding to the previous value of g_iter. But this time when Blinky1 finishes its run-to-completion step, the TIMEOUT event is already waiting in its event queue, because it was armed for just one clock tick, corresponding to the new value of g_ticks. This situation causes total lockup of the embedded target, which becomes completely unresponsive. This is a classic example of "denial of service". But all this shouldn't sound new to you, because the issues related to resource sharing among RTOS threads have been discussed back in lesson #28. The only new aspect here is that all this applies equally to Active Objects as well. Well, if this is the case, then the traditional solutions based on mutual exclusion should apply also. So, let's just apply one such mechanism. In traditional threads, a mutual mechanism of choice is the mutex introduced in lesson #28. But mutex is a blocking mechanism, so it cannot be used in the non-blocking active objects. But in the same lesson #28 you also learned about selective scheduler locking, which is a non-blocking mutual exclusion mechanism and therefore you can apply it inside active objects. So here, around the code that modifies the global variables, you can just copy the QXK scheduler lock from lesson #28. Typically, you need to adjust the priority ceiling. But here, it just so happens that the ceiling of 5 is right, because the highest-priority Blinky1 active object accessing the shared global variables has also priority 5. Alright, let's test this version. As you can see, the software now works much better in that Blinky1 no longer overloads the CPU. And also, the Blinky1 pattern changes at each button press, so definitely the mutual exclusion works. But now something strange is going on with the timing of Blinky1 during the transient from one blinking pattern to another. Specifically, the period at the transient sometimes takes over 3 milliseconds, which exceeds the Blinky1 deadline of 2 milliseconds. A hiccup like that might be unacceptable in a hard real-time system. But this is a consequence of the bounded priority inversion always present with mutual exclusion. The whole time that the low-priority Blinky2 spends holding the priority-ceiling lock prevents the high-priority Blinky1 from running and should really be counted towards Blinky1 CPU utilization, whereas CPU utilization is the critical concept in the Rate Monotonic Scheduling method. During the bounded priority inversion, the CPU utilization is increased, so the system is temporarily above the RMS schedulability bound and therefore the deadline is missed. So in short summary: You just saw some consequences of sharing resources among active objects and why you should avoid it. Sharing without mutual exclusion is simply wrong. Sharing with mutual exclusion can screw up real-time performance. So even though this traditional approach known as "shared state concurrency" is still dominating embedded programming, using *events* for interactions allows you to avoid the sharing and eliminates the whole need for mutual exclusion. So now, let's take a look at that event-driven alternative with the focus on today's subject of real-time performance. The first thing you will need is a special event that will carry the information about blinking pattern in its event parameters. In the introduction to event-driven programming, I mentioned that events can have such parameters, but today is the first time you will actually see how to do this. So, you need to create a new event class, let's call it BlinkPatternEvt that will inherit QEvt and add to it the two parameters with the same meaning as your global variables. Next, in Blinky2, upon BUTTON_PRESS, you want to create such an event, set the parameters to the desired values and post it to Blinky1. It turns out that you've already done something similar with *immutable* events, such as BUTTON_PRESS, in your bsp.c. Well then, let's try, as the first attempt, to do essentially the same here as well. So you allocate a static event of the type BlinkPatternEvt, but now it cannot be const, because you will want to change its parameters. This also means that you cannot use a one-time initializer, because you need to modify the parameters every time. But still, you should not to forget that every event needs a signal. Let's call it BLINK_PATTERN_SIG and add it to the enumeration of all signals in bsp.h. Next, you can get rid of the mutual exclusion protection and replace setting the global variable with setting the ticks event parameter. You can leave the for-loop with the emulated workload, and then you again replace the global with the "iter" parameter. Finally, you post the event to the Blinky1 active object. Here, I'd like to emphasize that this is NOT the final, recommended way of generating *mutable* events, and you'll see that it can be actually wrong. However, I see similar attempts made by developers new to event-driven programming, so I show this step for instructional reasons to later explain why it's not good in general. But let's leave it for now and continue to Blinky1, where you still need to receive the generated event. For that, you add a case for the BLINK_PATTERN signal. Inside, you can access the event parameters by explicitly down-casting the received generic event e of type QEvt to the subclass BlinkPatternEvt, like this. You can use the ticks parameter immediately to arm the time event to time out: *after* the received number of ticks and subsequently *every* number of received ticks. You can revert to this periodic time out, because now you have this explicit change event. Previously, without this event, you had to use a one-shot timeout, because you had to be ready for a change in the global variables at every timeout. You should also not forget to arm the time event for periodic operation in the initial transition. Regarding the number of iterations, you need to have it available in all subsequent TIMEOUT events, so you need to store it in the class attribute. You change the value of this attribute in the BLINK_PATTERN event, whereas you access the event parameter the same way as before. Of course, now you need to add this attribute to the class and you need to initialize it in the initial transition. Well, this would be it for now, so let's try to compile the code. OK, the C compiler doesn't perform upcasting automatically, so you need to do it explicitly. And, as you are at it, you also need to add disarming of the time event before arming it again. Well, time for testing: upload the code and reset the board As you can see, the first button press causes Blinky1 to change from slow to fast. And the second press causes it to change from fast to slow. There are also no more hiccups in Blinky1 timing. So, everything seems to be working perfectly without mutual exclusion and without sharing. Or is it really... Well, unfortunately, there is still sharing going on around the static BlinkPattern event. I mean, the static event object bpevt might not be completely global, because it is hidden inside the Blinky2 state-handler function, but there is still only one such object in the system. The static event is obviously constantly available to the Blinky2 thread, which changes it from time to time, and it is made available to the concurrently executing Blinky1 active object via a pointer sent through the event queue. This is in contrast to the buttonPress event, which is also shared between concurrently executing SysTick interrupt and Blinky2, but that event is *immutable*, can be declared constant, and it lives in ROM. So, the mutability of the ButtonPress event makes all the difference and requires completely different, much more complex approach. The good news is, that the active object framework, like QP in this case, handles most of this complexity for you in a generic, thread safe way. So, to do this correctly, you need to let the framework allocate the mutable event dynamically via the macro Q_NEW. This macro allocates an event of the given type and also sets the provided signal in the event, so you don't need to worry about that later. The macro returns a pointer to the event, which you need to keep, but it does not need to be static. Now, you don't need to set the signal. And you fill the event parameters as before, remembering that now you have a pointer. Finally you post the event, as before And that's all for creating and posting a mutable event. The reception of the event in Blinky1 does not need to change at all. In particular, you don't need to worry about deleting the dynamically allocated event, because the framework will handle this automatically for you. The only thing that you still need to do is to initialize the part of the framework that deals with the dynamic events. Specifically, you need to allocate the storage for the dynamic events, which are kept in deterministic, fixed-size event pools. The QP framework can handle many such pools with different sizes, but here you need just one pool for holding events of type ButtonPressEvt. Once you statically allocate the pool storage, you hand it over to the framework by calling QF_poolInit(), like this. So, this is finally the correct version of event-driven code, free of sharing and mutual exclusion. Let's quickly test it to see how it performs, especially in real-time Well, as you can see, it looks really good... Alright! So, here is the summary of today's lesson: Every real-life application eventually needs mutable events, but today you saw that such mutable events must be handled with great care. Real-time frameworks, like QP, provide support for deterministic and thread-safe allocation and exchange of mutable events. In fact, this is one of the most valuable services that the framework provides. The framework crates an abstraction that is often called "Zero-Copy Event Delivery", because from the application perspective it looks like copying events, whereas under the hood the framework performs a clever management of reference-counted events and deterministic event pools. But as all non-trivial abstractions, this one is also subject to the "Law of Leaky Abstractions", which states that "All non-trivial abstractions, to some degree, are leaky.". In case of the "Zero-Copy Event Delivery" abstraction, this means that for it to work correctly, the application needs to obey certain ownership rules, summarized in this lifecycle diagram of a dynamic event. At first, all dynamic events are owned by the framework. An event producer, like Blinky2 here, can gain ownership ow anew event by calling Q_NEW(). At this point, the producer gains the ownership rights with the permission to write to the event. The event producer might keep changing event as long as it needs, which might be over multiple run-to-completion steps, but eventually the producer must transfer the ownership back to the framework. Typically the producer posts or publishes the event. As a special case, the producer might decide that the event is not good, in which case the producer must call the garbage collector explicitly. After any of these three operations, the producer loses ownership of the event and can no longer access it. For example, changing the event after posting it is NOT allowed. Also, posting or publishing the event again would be wrong, because at this point, the application doesn't have ownership rights to the event anymore. The consumer active object gains ownership of the current event "e" when it receives the event. This time, however, the active object gains merely a *read-only* ownership of the event. The consumer active object is also allowed to post or publish the event, and this happens without losing the ownership of the event. So, for example, this event posting would be OK. And that publishing in addition would be fine as well. The ownership ends, however, at the end of the run-to-completion step. So, if the current event has any information that is needed in the future RTC steps, this needs to be saved, typically in the internal attributes of the active object. So, these are the rules that you need to obey. In exchange, the framework will safely and deterministically deliver your mutable events with hard real-time performance, which is superior to the traditional "shared-state concurrency and mutual exclusion" approach. This concludes this lesson about active objects, mutable events, and real-time. In the next lesson I plan to talk about software tracing, also known as logging. 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!