Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek and today I'll continue with event-driven programming, but this time as it applies to real-time embedded systems. In the last lesson 33, you learned the main concepts related to event-driven programming, but they were all in the context of desktop Windows. Also, the whole explanation as to why events should drive the application was entirely based on Graphical User Interface (GUI), and it might not be clear to you, how any of this applies to real- time embedded systems. So, I'd like to begin today's lesson by going back to the sequential programming with a "superloop" and the Real-Time Operating System (RTOS) that you learned in the segment of eight lessons 21 through 28, because it is the most familiar and still dominating style of programming real-time embedded systems. From that starting point, you will see how to gradually apply event- driven concepts and what kind of problems they will help you to solve. For today, I've prepared an example project very similar to what you used back in lesson 25 about the RTOS, except that today's code is based on Micro-C/OS-2 so that you get exposed to this popular RTOS as well. Silicon Labs has recently released Micro-C/OS-2 on GitHub under the permissive Apache-2 open source license. The project for today's lesson is available for download as a ZIP file from the companion web-page to this video course at state- machine.com/quickstart. For today, you will also need to download qpc-dot-ZIP. After downloading you need to unzip both archives into the same directory, like this. The slightly modified Micro-C/OS-2 code is included in the qpc\ 3rd_party folder. When you build and run this example, it just blinks the green LED on the TivaC LaunchPad board, but this time you can speed up the blinking by pressing the SW1 button. Additionally, to visualize what's going on with the button, the blue LED is turned on when the button is depressed and is turned off when the button is released. The 'main_blinky' thread in today's code is almost the same as in lesson 25, except is uses the Micro-C/OS OSTimeDly() API to block and wait in-line for the specific time delay. The time delay used is coming from the 'shared_blink_time' variable, which as the name suggests is shared between the blinky and button threads. To avoid any concurrency hazards around that shared variable, it is protected by a mutual-exclusion mechanism, which happens to be the priority-ceiling mutex of MicroC/OS-II. The 'main_button' thread also has the usual structure of the endless, while(1) "superloop". Here, the thread waits on the semaphore object that is signaled when the button is pressed. When this happens, the button thread turns the Blue LED on and updates the shared_blink_time variable to speed up the blinking of the green LED in the blinky thread. Again, you see here the use of the Micro-C/OS mutex to protect the access to the shared variable. After this, the button thread waits again on the semaphore object signaled when the button is released. Immediately after that, the Blue LED is turned off. Small as it is, this program demonstrates the general principles by which RTOS applications are designed. The main characteristic of the design is that threads synchronize their execution with various events by means of *blocking* and waiting in-line for a chosen event to occur. But a blocked thread is unresponsive to any other events not explicitly waited for. Consequently the main workaround for adding new events is to create more threads, because multiple threads can wait for multiple events in parallel. But this ever growing number of threads quickly becomes very expensive and unmanageable. But even more importantly, the new threads likely need to access the same data, peripherals, or other resources, as the already existing threads. And this leads to *sharing of resources* among threads, such as the shared_blink_time variable. Now, you recall that context switching in an RTOS is closely related to interrupt processing, and in fact, an RTOS just hijacks the interrupt processing hardware already available in the CPU to do its magic. But that means that threads inherit all the problems of interrupts, including race conditions around any shared resources. Overlooked or unhindered race conditions can lead directly to system failure. So, now, you need to identify and prevent all race conditions. The RTOS actually supports it by providing an assortment of mutual exclusion mechanisms, such as mutexes. But ironically, these mechanisms often lead to even more blocking, which brings a whole slew of secondary implications. All this immensely complicates any timing analysis and can lead to missed real-time deadlines, which can ultimately lead to the system failure. For these and other reasons, the experienced software developers learned to be very wary of blocking and instead they apply a set of best practices that drastically reduce the blocking in their software. One of the most succinct formulations of these best practices I found, comes from the C++ and concurrency expert Herb Sutter. Specifically, his article "Prefer Using Active Objects Instead of Naked Threads" contains the following three best practices: One: Keep data isolated, private to a thread where possible: This really means that threads should avoid sharing of data or resources with other threads, which eliminates the "shared-state concurrency" node in the "perils of blocking" diagram. Without sharing, there is no need for mutual exclusion and the big part of the vicious circle is eliminated. Of course, "avoid sharing" is easy to say, but how are then the threads supposed to communicate and synchronize? Well, this is addressed in the best practice number two: Communicate among threads via asynchronous messages. This best practice packs two important concepts: "messages" or "events" and "asynchronous communication". These are exactly the event-driven concepts you've learned in the past lesson 33. "Event objects", also called "messages", are packaged data specially designed for communication. They carry the information about what happened: for example a "timeout-occurred", or "button-was-pressed". Additionally, event objects can carry event parameters that pertain to the event. For example, an "ethernet-packet-arrived" event can carry the whole Ethernet data packet as its event parameter. You will see examples of events in a minute. "Asynchronous communication" means that senders of events only post the events to the recipients, but they don't wait in-line, meaning don't block, until the event is processed. So, the best practice number two is designed to eliminate the "synchronization by blocking" node from the "perils of blocking diagram". And finally, the best practice number three: Organize your thread's work around a message pump references directly the event-driven concept of "event loop" also called "message pump", which you learned in the last lesson 33. This best practice goes directly after the blocking node in the diagram. Please note that the "blocking" node cannot be eliminated completely, at least not with a traditional RTOS, because as you remember, every RTOS thread must necessarily block somewhere in its "superloop". But the best practice number three prescribes the only allowed structure of the "superloop" as the "message pump", which restricts the blocking to only one specific place in the loop. Of course, all these best practices combined establish a design pattern, which is known as the "Active Object" pattern. The pattern is valuable, because it provides a layer on top of naked threads, which otherwise let you do anything including troublesome things. In this pattern, Active Objects, like any other objects, have private data. But each active object also has a private thread and a private event queue. The only way to interact with an active object is by posting events to its event queue. The posting is *asynchronous*, meaning that the event is only placed in the queue, but there is no waiting until the event is processed. The event processing happens in the event-loop running in the private thread of the active object. The processing might involve sending secondary events to other active objects or even to self. In a sense active objects are the most stringent form of object- oriented programming, because the asynchronous communication enables active objects to be truly encapsulated. In contrast, the traditional OOP encapsulation, as provided by C++, C# or Java, does not really encapsulate anything in terms of concurrency. Any operation on an object runs in the caller's thread and the object's private data are subject to the same race conditions as global data, not encapsulated at all. To become thread-safe, operations need to be explicitly protected by a mutual exclusion mechanism, such as a mutex or a monitor, but this leads to additional blocking and requires special attention from the programmer. In contrast, all private data of an active object are truly encapsulated for concurrency without any mutual exclusion mechanism, because they can be only accessed from the active object's own thread. Note that this encapsulation for concurrency is not a programming language feature, so it is no more difficult to achieve in C++ as in C, but it requires a programming discipline to avoid sharing resources (known as the "shared-nothing principle"). However, the event-based communication helps immensely, because instead of sharing a resource, a dedicated active object can become the manager or broker of that resource and the rest of the system can access the resource only via events posted to this broker active object. From the historical perspective, the Active Object design pattern combines the RTOS, object-oriented programming, and event-driven programming. I will call these trends "Modern", as in the title of this whole "Modern Embedded Systems Programming" video course. This is in contrast to the still dominating "Traditional" approach, based on a traditional RTOS and shared-state-concurrency that dates back to the early 1980's when RTOSes went mainstream. Alright, this was a lot of dry theory. So, now let's actually see how active objects could be implemented, on top of a traditional RTOS. Staring from the previous sequentially coded example with MicroC/OS- II, let's now add the Active Object services. Because the Active Object layer will be based on MicroC/OS, let's call it "MicroC/AO". The minimal implementation of "MicroC/AO" will consist of the UC- underscore-AO-dot-H header file and the UC-underscore-AO-dot-C source file, which I prepared in advance and will quickly review with you today. The header file includes the MicroC/OS-II RTOS, and adds to it the event facilities. The event signal will be just a small integer, which will hold enumerated signals representing various interesting occurrences in your application. There will be a couple of reserved signals, such as the INIT signal that will be dispatched to each active object right before entering the event loop. The USER signal, will be the first signal available to the users. Next, you need to define the event structure. It needs to contain the signal, but it also needs to be extensible to fit arbitrary event parameters. This would be accomplished by the object-oriented concept of inheritance, as you learned in lesson 30. But just to give you an example use case, a hypothetical EthernetEvent could inherit the Event base structure and could add the Ethernet packet as the parameter. Next, you need to define the facilities for Active Objects. You start with the forward declaration of the active object struct, followed by the definition of the "DispatchHandler" pointer to function. This will be a "virtual" operation in the Active object class, as you learned in lesson 32 about polymorphism in C. Next, you can define the Active object struct, which, as you recall, needs to have a private thread, and a private event queue. For MicroC/OS-II, the thread can be represented by its unique priority. The event queue will be represented with the MicroC/OS-II "OS-Event" pointer. The name "OS-Event" here is a bit of a misnomer, because it means here operating-system object to *deliver* the event instances you just defined above. In case of MicroC/OS-II, this operating system object will be a message queue, which is somewhat of an overkill, because it can be potentially both written-to and read-from by multiple threads. In contrast, an event-queue of an active object can be much simpler, because it needs to be read from a single thread only. But, a message queue is the closest approximation of what you need, even though it's a bit more expensive than strictly necessary. Finally, the Active struct contains the pointer to its "dispatch" virtual operation that will be provided in the subclasses. This is the implementation option of polymorphism you saw in lesson 32, where you embed the whole virtual table directly in the class. With just one virtual function "dispatch", this implementation makes the most sense. The specific subclasses of the Active base class will also add their private data through inheritance, in the same way as subclasses of Event add event parameters. These will be the strictly encapsulated private active object data accessible only from the private thread of the active object. You'll see examples of it in a minute. The Active class will have a few public operations, starting with the constructor, which in C is just a regular function that you need to call explicitly. Then, the Active_start() operation will start the internal thread of the active object. And finally, the Active_post() operation will asynchronously post events to the active object's event queue. Now, let's quickly review the implementation of these services in the UC-underscore-AO-dot-C source file. Let's begin with the Active_start() operation. First, the function creates the internal Micro-C-OS-2 message queue and asserts that the queue was created correctly. But the most important is how the function creates the internal Micro-C-OS-2 task for the Active object. Interestingly, you can see here that *EVERY* active object thread is based on the *SAME* event-loop function defined earlier in the file. This is a drastic departure from the traditional way if creating RTOS threads, where each thread runs its own, custom thread function defined in the APPLICATION-level code, outside the RTOS. In contrast, here, all Active object threads run exactly the *same* event-loop function defined inside the Active Object module and even made static, to hide it completely in this module. This means that the control over the thread function resides in the Active Object module rather than the application. Now, I hope you remember from lesson 33 that such "Inversion of Control" is one of the main characteristics of event-driven programming. But, while all active objects run the exact same event-loop function, each such function operates on a *different* Active object instance "me", which is passed as the 'pdata' argument to the OSTaskCreateExt MicroC-OS-2 API. So now, inside the Active event-loop implementation, which must conform to the MicroC-OS-II task function signature, the 'pdata' void poiner parameter is immediately cast back to Active and assigned to the "me" pointer to give it access to the Active object instance this function works on. The rest of the Active object event-loop code is quite similar to the Windows GUI code you've seen in the last lesson 33, so let's pull that code up for reference For example, the initialization of the application, which in Windows GUI happens in the call to CretateWindow, sends the WM_CREATE message to the window proc. In your embedded code, this is accomplished by directly calling the dispatch virtual function via a pointer to function, to which you pass the initial event carrying the reserved INIT signal. An interesting twist here is related to the chosen Active Object interface, which posts only pointers to events, not the whole event objects by value like it was in Windows. In general case of events that are modified outside a given active object, you must be very careful to avoid race conditions. But events without parameters can be constant and allocated in ROM. Such *immutable* events can be shared freely, because they don't pose any concurrency risks. You will see a few more examples of posting immutable events, later in this lesson. The following code implements the actual event-loop the same way really as the Windows GUI code. At the top of the loop, you see the OS-Q-Pend() call that obtains the next event from the active object's event queue. This is the only place in the loop where blocking is allowed, when the queue is empty. Next, the received event is dispatched to the active object "me" by means of the virtual call via the "me-dispatch" pointer to function. The final touches of the implementation are the Active object constructor, which simply sets the virtual operation "dispatch". And finally, the asynchronous event posting to an active object boils down to posting the event to its private event queue. So, this is about it at this point. Now, let's try to use the Active object layer instead of the "naked" Micro-C-OS-II RTOS. For starters, let's convert just one of the traditional sequential threads to an Active Object. It turns out that the Button thread is easier with the currently available Active object services. You can keep the original sequential code as a reference for now, but remember that most of it will be deleted after you're done. So first you need to create the Button active object class by inheriting the Active base class as the first member 'super'. The Button active object has no private data, so you don't add any other members to the struct. Next, you implement the Button dispatch virtual function, in which you process one event at a time. Typically, you code the function with the switch statement, which discriminates based on the event signal. When you receive the special, reserved INIT_SIG signal, you establish the initial state of the Blue LED as off. When you receive the application specific BUTTON_PRESSED signal, you perform all the actions that you did when the button-press semaphore was signaled. Finally, when you receive the application specific BUTTON_RELEASED signal, you perform the actions that you did when the button-release semaphore was signaled. So, here you've just seen how to convert the sequential code to event-driven code. The most important thing to remember is that in the event-driven code you never block to wait for events. All waiting is external to the dispatch function and happens at the top of the event-loop. The last function you need to define is the constructor of your new Button active class. Here, you have to call the base class' constructor, where you attach the specific Button_dispatch implementation to the virtual dispatch operation. This completes the definition of your new Button class, so you can delete the sequential code including the blocking semaphores. But you need to keep the stack, because it is still needed for the internal thread of the Button active object. Apart of that, you also need to provide: the memory buffer for the private event queue of the active object, the instance of the Button active object, and the global pointer to the object so that other parts of the code, such as the BSP, can post events to it. The last change in main-dot-c is to call the Button constructor explicitly and to start the active object by calling the Active_start() member function. Regarding the BSP – Board Support Package, you need to replace the blocking semaphores with the definition of the event signals And you need to declare the global pointer to the Button active object. Please note that the global pointer is of the type Active, which is the superclass, rather than the specific Button subclass. This is intentional, to completely encapsulate any private data that the subclass might have inside. The rest of the application simply does not know about any such data and can only interact with the Button instance as a generic Active object. In the BSP implementation bsp-dot-c, you need to replace the signaling on the semaphores with posting events to the Button active object. The BUTTON_PRESSED and BUTTON_RELEASED events have no changing parameters, so you can use the immutable, const events in ROM. And this is finally all as far as the Button active object is concerned. The compiler still points out a few details to fix, but eventually the project builds cleanly. Please note that the Blinky functionality is still coded with the traditional sequential and blocking Micro-C-OS-2 thread. Alright, I know, you must be really curious to see how all this works. So, let's just load it to the board and run it. As you can see the Green LED blinks as before, but this is hardly surprising, because this is still the old sequential code. But the Button is the new, event-driven active object code. And as you can see, it also works, exactly as before. So, congratulations, you just implemented your first active object. Very well, so now I'm sure you want to go with all your guns blazing and convert the sequential Blinky thread to an active object as well. Using the existing Button active object as a template, you can just copy and paste that code, and replace Button with Blinky. But now, comes the most interesting part, which is the actual implementation of the Blinky_dispatch() operation, where, as you sure remember, you're not allowed to use any blocking calls. But herein lies a problem. Button-press and button-release were obvious events, but where is an event in the time delay? Well, *every* blocking call *always* corresponds to an event, even though initially you might not know how to call it. But time delays specifically correspond to Time Events. A Time Event is an event that an active object can request to be posted to its event queue in some time in the future. Such functionality does not yet exist in your Micro-C-Active-Object layer, so you need to add it. First, you go to the Micro-C-A-O header file, where you add a Time Event class. Because Time Events are just specialized events, the TimeEvent class inherits Event. But in addition a Time Event must remember the active object that requested the timeout. And a Time Event must also keep track of the timeout, which will be a down-counter decremented by every system clock tick. When the counter reaches zero, the Time Event will be posted to the active object and the Time Event will be considered disarmed. This also means that the timeout value of zero indicates a disarmed Time Event that is not ticking. Finally, often you might want the Time Event to be delivered periodically. This can be quite easily achieved by adding the interval member. This interval would be loaded to the timeout down- counter when it reaches zero. That way, the Time Event will automatically arm itself and will keep ticking towards the next timeout. The interval value of zero will then naturally correspond to a one-shot Time Event that would be automatically disarmed after expiring only once. The Time Event will have a few public operations, such as the constructor, as well as the arm and disarm operations. Finally, the class-wide operation TimeEvent-tick would be a "static" class operation without the "me" pointer for the specific instance. It will service all instances of the Time Event class every system clock tick. Now, regarding the implementation of the TimeEvent operations in Micro-C-A-O-dot-C, I'll use a very simple approach. To keep track of all Time Event instances in the system I will simply use a static array of pointers sized for some maximum number of Time Events and the actual number of Time Events registered so far. I like to add a prefix l-underscore to indicate that these variables are local to the module. Then, in the TimeEvent constructor, you initialize the data members as usual, but you also store the TimeEvent pointer in your local array and you increment the actual number of TimeEvents "registered" in the system. An important consideration here is to realize that the local variables can be implicitly shared among concurrent RTOS threads and also system clock tick interrupt, through the interface of the TimeEvent operations. This means that there is a potential for race conditions around these variables, so you need to apply some mutual exclusion protection around them. Here, you can use the fast Micro-C-OS-II critical section mechanism, because the access is very short. This is exactly the same mechanism that Micro-C-OS-II itself uses internally in its own source code to protect its internal variables. This leads to an interesting observation that, in a sense your active object layer is an event-driven *extension* of a traditional RTOS. As such an extension, it poses similar challenges as other parts of the RTOS code, for example, avoiding internal race conditions. But going back to the TimeEvent "arm" operation, it simply sets the timeout and interval members inside a critical section. Similarly, the TimeEvent "disarm" operation clears the timeout to zero, also in a critical section. Finally, the most interesting class-wide TimeEvent-tick operation goes over all the registered Time Event instances. This function is intended to be called from the system clock tick interrupt, which can never be preempted by any RTOS thread. Therefore, it does not need a critical section. Each time event is first checked whether it is armed. And if so, its timeout counter is decremented and checked again if it just has reached zero. If so, the time event is posted to the active object and the timeout counter is reloaded from the interval data member. This works for both periodic time events and one-shot time events, when the interval is zero. The last modification you should not forget is to add the TimeEvent_tick call to the system clock tick Micro-C-OS-II callback, so that the registered time events are serviced. Alright, so this was the extension of the Active Object layer. Now let's use it to implement your Blinky AO. The Blinky active object clearly needs a time event, so let's add one TimeEvent instance as its private data. As all private data members, the TimeEvent member needs to be initialized in the constructor, where it becomes associated with the active object "me->super" and gets the signal TIMEOUT, defined in bsp-dot-h. So, now you have the event signal for your timeout and you can write the case statement for it. As usual, you copy a portion of sequential code upto the blocking call. But here comes a problem. Should you turn the LED on or off? I mean, in the sequential code you had *two* blocking calls--one for LED-on time and the other for the LED-off time. But here, in the event- driven code you have only one TIMEOUT event and need to know whether the LED was most recently on or off. This information needs to be stored in such a way that it would survive the calls and *returns* from the dispatch operation. This means that it definitely cannot live on the stack as an automatic variable. Instead, the ideal place for it is the private data in your active object class, where you add the isLedOn boolean flag. Assuming that the LED is initially off, you can set the flag accordingly in the constructor. Now, in the TIMEOUT case, you first check the flag, and if it indicates that the LED is not on yet, you turn it on and remember this change in the flag. Then you arm your time event to expire in bt clock ticks. Otherwise, you turn the LED off and set the flag accordingly. Here, you arm your time event to expire in 3 times bt clock ticks. But this code has still a basic flaw. The TimeEvent is armed only in the TIMEOUT case, but this case executes only when TIMEOUT event occurs. So, you need to somehow "prime the pump" so to speak, so that the TimeEvent gets armed the first time. And this is exactly where you can take advantage of the INIT event, dispatched to every active object right before entering its event loop. Here, you can explicitly turn the LED on, set the isLedOn flag and arm the Time Event. But this is just repeating what is happening in the TIMEOUT case when the flag is cleared. So, your other option is just to intentionally fall-through from INIT to TIMEOUT. Now, you still need to move the shared_blink_time stuff before the new Blinky active object, remove the sequential code, and start the Blinky AO instead of the sequential blinky thread. Of course, now you want to see if the code still works, and... both Blinky and Button active objects work as expected. Now, as the very last exercise in this lesson, I'd like you to take a critical look at the two Active objects you've created. They came to be only because you have translated the sequential design from the conventional RTOS to active objects. But the real and only reason why you ended up with two active objects is that sequential "superloops" were not composable due to unresponsiveness. Making them composable again was the whole reason for existence for an RTOS, at the expense of creating multiple threads. But all this does not apply to event-driven code, which remains responsive to all events. This means that event-driven code IS directly composable. Well, in that case you should be able to merge Blinky and Button together into just *one* active object. So, let's actually do it right now, starting with renaming Blinky to BlinkyButton. Next, you need to move any private data, if present, from the Button class to BlinkyButton. And you need to move all events handled by Button to the BlinkyButton_dispatch operation. Any duplicate cases need to be merged, such as event handling in INIT. But wait a second. Now the shareed-blink-time is no longer really *shared* between threads! Instead, the variable can become internal, fully encapsulated, private data inside the BlinkyButton active object. As all other member variables, blink-time needs to be initialized in the constructor. And since there can be no race conditions around blink-time anymore and you can get rid of the mutex protection. This not only reduces the overhead, but eliminates any potential blocking in the event- driven code. Thus the code has a more predictable timing and is better suited for hard real-time applications. So you replace the shared-blink-time variable with me->blink_time private attribute, cleanup the temporary variables, and get rid of the mutex. Now you can simply delete the Button active object altogether. Note that by doing so, you free up significant RAM for the stack and the queue buffer. Obviously now, you no longer need to create or start the Button active object. You still need to make a few name changes to fix the syntax... And finally, as the compiler reminds you, you need to change the global AO_button pointer to AO_blinkyButton. Of course, the very last thing to do is to verify that the final code still works as before. And indeed, it does… I hope that this exercise demonstrates the following two properties of active objects: First: an active object can hold much more functionality than a traditional, blocking thread, because you no longer need to create expensive threads just to make them responsive to events. And second, the ability to combine functionality inside event-driven active objects makes it much easier to avoid sharing of resources among them than the traditional blocking threads. In summary, I realize that this lesson grew to be a little longer than usual, but really a lot happened today. You experienced nothing short of a paradigm shift from traditional sequential programming with an RTOS – concepts that essentially didn't change since the early 1980's, to modern, event-driven active objects. Not only this, you actually started to build your own active object framework called here Micro-C-AO, which is very different from an RTOS because it is based on inversion of control. In your framework you used a few services from the traditional RTOS kernel, such as threads and message-queues. But a traditional RTOS does not provide much else needed in event-driven programming, which you had to create yourself. At the same time, most mechanisms provided in a traditional RTOS are of no use in programming the event-driven active objects. Not only they are not helpful, they can be outright harmful, because most of them are based on *blocking*, which is not allowed inside active objects. At the end of the day, even though a traditional RTOS can be used to implement an event-driven framework, it is not the best fit. I will hopefully get back to this in the future lessons. If you like this channel, please give this video a like and subscribe to stay tuned. You can also visit state-machine.com/quickstart for the class notes and project file downloads.