Yes, that's the main message of this lesson. But this is not the mainstream approach yet. Most concurrent systems still use the "free threading" with multiple blocking mechanisms. Unfortunately, most developers vastly underestimate the true risks, costs, and skills required to use these blocking mechanisms correctly and safely. The active object model of computation restricts the "free threading" approach and avoids a lot of the problems (not all, but the most tricky ones). --MMS
Wow wow wow!!!!!!!! After several ups and downs in ur tutorials: This tutorial made it clear to me that all the threads should be developed around the EVENT LOOP!! Great tnx!!!!
Yes, this is the right conclusion. *All* threads should have the event loop structure. Here again, I'd like to recommend the article "Managing Concurrency in Complex Embedded Systems" by David Cummings ( www.state-machine.com/doc/Cummings2006.pdf ). By now, this is perhaps the most frequently recommended article in all my presentations and videos. It uses simple terminology that should be familiar to users of traditional RTOSes. The "Active Object" pattern is not even mentioned (but this is what is being advocated, actually). Please read. --MMS
anybody new to embedded system can't justify the value of this technique but in my 1.5 years of fulltime embedded carrier I understand. Great work sir ,Thank You.
Yes, this lesson covers similar material as the Embedded Online Conference presentation. The following lessons will also state machines and other related things, but in more detail. Stay tuned!
This works well if worst case time of your event loop processing is less than hard realtime deadline. Otherwise it will be a mix of RTOS and event driven. I have used event driven approach to implement usbpd/typec fw stack. But you presented a more generic and secure implementation of event driven approach. Awesome work.
Yes, indeed, the active-object (AO) model can execute on top of many types of real-time kernels. In the presentation I had only time to outline the execution model based on a traditional RTOS. But I've also mentioned that a traditional RTOS is overkill to execute non-blocking AOs. There are simpler non-blocking, but fully *preemptive* hard real-time kernels that can be used instead (e.g., in OSEK/VDX RTOS specification such kernels execute only so called basic threads). But in any case, the run-to-completion (RTC) event processing within each AO should *not* be confused with preemption. RTC does *not* mean that a running AO must monopolize the CPU. To the contrary, different AOs running at different priorities *can* preempt each other, but as long as they don't share resources there are no concurrency hazards. --MMS
Before the message has been handled by the target AO, it May still use the obsolete data, in case we have two or more AOs that need to notify each other with the updated data. There is a time gap between message sent and message processed.
Any computation takes time, so we fundamentally cannot process data "instantaneously". That means that we always deal with some "obsolete data", as you say. This is independent if you use event-driven programming, traditional blocking RTOS, "superloop" or little gnomes to process your data. But we can deal with this by introducing the concept of real-time deadlines, which characterize how long specific data is still good and when it becomes "bad". (Please watch the video "What is real-time?" ruclips.net/video/kLxxXNCrY60/видео.html ). The issue is really different here. The real problem would be if the data was somehow corrupted or inconsistent. This can happen quite easily when you *share* data between ISRs and the background loop, or between tasks in an RTOS. In contrast, event-driven programming solves this problem rather elegantly. The data carried inside events could be a little "obsolete", but is always *consistent* . This is huge! I hope you see the point. --MMS
@@StateMachineCOM Thank you Dr Miros! Yes, I agree with you that event driven programming is the best architecture for concurrence processes Just wondering if quantum computing can use entanglement phenomenon to realize absolute real time concurrence, as now they can use entanglement to flip qbits to get the computing result immediately. You got a PhD in nuclei physics, maybe can share some thoughts on it. Sorry for asking so many questions but you are the best.
@@wondererasl Quantum computing and quantum entanglement have really nothing to do with the real-time embedded programming discussed here. There is no way to compare the two. --MMS
Hi - Thanks for the very insightful video. If I understand correctly, active object = its own thread + private data + private queue. My question is, why is active object has tied to own RTOS based thread? It could be non-RTOS based task handler, which touches only its own private data and has its won queue. Can we call such pattern as Active Object?? thanks
This is an excellent insight. Active Objects are not limited to using threads of traditional RTOS kernel. I started with a traditional RTOS only because it is so widely known. But Active Objects can also use a different kernel, including much simpler kernels. I made a video about one example of such a simpler and more efficient kernel ( ruclips.net/video/PTcauYl994A/видео.htmlsi=V-AdnSug-hmRDql9 ). But even this can be simplified further and Active Objects can be also executed cooperatively without any preemptive kernel. I plan to make some lessons about the various options for executing Active Objects. --MMS
Great tutorials! I follow them since the beginning. I do mainly hardware, with a little bit firmware. Watching them makes me want to start writing more firmware. Can you share what future lessons are coming to this channel? Are you planning more GUI tutorials, also with real display? Thank you!
Hello Miro, Im curious if you have any videos in your repository that would help someone whose using SPI to communicate with a sensor of somekind? Im specifically using a fingerprint scanner, but anything with detail about sending data from one device to another would sure be helpful!
Hi Miro, Combining the "Blinky" AO and the "Button" makes the AO thread priority at the same level. (I guess that the scheduler sets the AO thread priority) So how can you ensure and calculate the real rime requirements? when there were separated threads the real time requirements could be calculated. Which leads me to the question, should i always consider to design according to the new methodology (AO and object oriented wise)? or elsewhere? Really enjoying your course, thanks a lot
Yes, absolutely, the combined AO runs both activities at the same priority. This might be undesirable if these activities have different real-time requirements. But this is not the case here. In fact, most of the time activities are put into separate threads because of blocking in the traditional approach. -MMS
Sure. For example, the event-driven QP frameworks implement the Active Object design pattern and the best practices of concurrent programming. You can also build and run QP on the host computers (Windows, Linux, macOS). To learn and practice the presented concepts, you can download and install QP-bundle ( www.state-machine.com/#Downloads ). On the download web-page, you can also find a "Getting Started" video that shows how to build and run QP on Windows. --MMS
Hey Miro, I wanted to ask you what could I do if I have a blackout reset situation. Is there any register that reserves any kind of data about why this reset occurs...
Hi - I would like to thank you again for amazing video. I do have three more comments or questions. 1. In the original implementation(at the beginning of this video) of the code where bink_time is shared between threads. Since the resource is shared between threads, it is protected by mutex. My question is, do we really need to protect the resource as one thread updates and other thread only reads? There is never going to be data corruption. 2. What is the reason for declaring the dispatch routine as virtual function? Is it really virtual as in case of desktop version? 3. Why timeEvent object is created which is attached to one particular active object and that too, mapped to one particular event in that AO? Why can;t timeEvent be generic/global object and when any object wants to arm a time_tick, it will provide queue and sig value. Once the event triggers(after the tick expires), the TimeEvent_tick will post an event to the particular queue. That also means, the timeEvent has to maintain a table like array of armed timer event data structure, which mainly contains the queue object, event, timeout, interval, state etc. and running_bitmap of active timeEvents. Let me know, if I am missing anything.
Regarding your question #1 about sharing the "shared_blink_time" variable. In this case of just one 32-bit variable, a 32-bit CPU, and only reading on one end, you might get away without the mutual exclusion protection. But this is a *slippery slope* . The code will not work reliably on 16- or 8-bit CPUs, and will be fragile for any future modifications. For example, if you also added the possibility of changing the color of the LED, you will have two variables (blink_time and led_color). Due to preemption, the thread reading the variables might end up using the new "blink_time", but the old "led_color", so it will display an inconsistent state. Regarding your question #2 about the virtual dispatch() function. Such a design allows you to provide different state machine implementation strategies in different subclasses. For example, one implementation could be based on the "state table", another on the "nested switch" and yet another on the "event processor". Finally, regarding your question #3 about the time-events. The presented design is obviously just one of many possibilities. But your suggestion of global and shared time events is potentially hazardous. For example, one hazard is that a time event triggers and gets posted to an event queue, but before it is consumed, another thread arms it again and changes the signal. I hope you can see the problem. And there are many more corner cases like that. Ultimately, it all goes back to the fundamental "sin" of sharing resources. So, one needs to be very careful because it's awfully easy to create something unsafe. (Unfortunately, I see a *lot* of such ad hoc solutions when people improvise with traditional RTOS kernels.) The design presented here avoids such hazards by making time events private to active objects (not shared) with fixed signals. This may be a bit limiting, but it's *safe* . --MMS
@@StateMachineCOM - #1: Yes, if someone wants to overlap two variables in same word, then of course there will collision. If the variable is word size and word aligned then read by one thread and write by other thread then the expensive blocking can be avoided. I guess, you wanted to show the issue of blocking in a thread and that is the reason for use of mutex. #3: I did not get this. The functionality of OS-Delay in RTOS video series was also global; except that scope of delay function starts from the point it is triggered to till it expires. In timeEvent object, the scope extends till the event is processed in dispatch routine. For example, array of tick_events and every entry of tick_event will have state, which can be enumerated to IDLE, ARMED, and POSTED. Yes, one can protect the operation that is combines the update of state and l_tevtNum. Why can't tick_event be generic and used as service by any object? Thanks
@@jankeshchakravarthy9389 Regarding issue #3, as I said there are many ways to implement time events and event passing in general. You probably are used to passing messages by copy, which does not involve sharing and therefore is safe, but also slow, memory-hungry, and potentially non-deterministic. Therefore, the option presented here passes only *pointers* to events into the queues, so that event passing is fast and deterministic. However, the consequences are that the event objects (including time events) remain *shared*. So far in this course, all events were immutable (const), so sharing them wasn't a problem. (And I still need to discuss mutable events in future lessons!) However, time events are mutable, so this issue of sharing such events came up here. So, the time event objects are only protected momentarily during posting, but are NOT protected thereafter and definitely not till dispatch. I hope this clarifies the issue, and my earlier comment makes more sense now. --MMS
Watching your tutorial video makes me excited to learn embedded more, even though it is still difficult. And I have a concern that: Event-driven programming will include Active Object pattern, is that correct ?
Active objects seems like a powerful paradigm. Is most of the sequential code can be converted this way? I expect there would be some exceptions, so what are they? I asked this because I have tried to convert some of my naked thread into active objects. I soon found that making dispatch function was either hard to be efficient or spaghetti..
As you found out by yourself, converting sequential code to event-driven code is NOT trivial at all. For example, consider a piece of sequential code that consists of a while loop that calls a function, that calls another function, that... blocks (or polls). How do you unravel something like that? I mean you need to eliminate the blocking (or polling) and instead return to the event loop, right? I remember spending a whole year in a company doing exactly such a conversion from the existing sequential code to event-driven code. That's why it is important to understand the difference in paradigms and to create event-driven code to begin with. Here, I would like to recommend an article strongly advocating to avoid blocking: "Managing Concurrency in Complex Embedded Systems", see www.state-machine.com/doc/Cummings2006.pdf
What about two-way communication with Active Object pattern? Is this covered in this course? I'm not using strictly AO pattern yet, but I have something very similar based on (unfortunately) single Event Bus in my case and I wonder what I can improve (after I split my event bus into AO queues). Lets say I have ModbusActiveObject, SchedulerActiveObject and WebInterfaceActiveObject. ModbusAO has a queue with ModbusReadRequests. SchedulerAO and WebInterfaceAO can send messages to ModbusAO. When ModbusAO reads data from bus (or there is an error) ModbusAO posts response to the caller AO as another message. I have few other objects (like datalogger, display etc.).
I'm not sure what you mean by "two-way" communication and what's special about it. In the active object model, AO1 can post events to AO2 and also AO2 can post events back to AO1. Does that count as "two-way" communication? Regarding you partitioning of the system, please remember that one AO can typically have more functionality than a traditional thread, because an AO remains *responsive* to events (while a thread blocks and is thus unresponsive). This means that a "ModbusAO" can have also some functionality, besides also encapsulating the Modbus. I'm not sure what the SchedulerAO does. --MMS
@@StateMachineCOM Regarding SchedulerAO - this is just one of two event producers for ModbusAO. It is part of data logger feature. Regarding "what's special about two-way" - I have problem because my "data request" events must be related with "data response" events. AO1 (tcp interface that processed 10 tcp requests) wants data from AO2 (modbus) and it produces multiple events with "ModbusReadRequest". AO2 consumes these events, does it's job and then it must "respond" to AO1. So it should produce multiple events with "ModbusReadResult". But these ModbusReadRequest events must be linked with ModbusReadResult events (to know which response belongs to which request, or at least to which TCP client data should go). But ModbusReadRequest does not exist (in queue), because it was consumed by AO2. So I have to keep "ModbusReadRequest" copies in AO1. At this moment I have implemented weird kind of queue with two heads and tails to handle this, now I'm thinking about something else.
@@technics6215 The request-response pattern is very common, and typically people like to send a request and *block* until the response is received. This, of course, sweeps the whole slew of problems under the rug, because the question is what do you do with all the events that *might* be produced during the several milliseconds between the request and the response(?) Here, I would highly recommend that you read the article "Managing Concurrency in Complex Embedded Systems": www.state-machine.com/doc/Cummings2006.pdf But with active objects, you don't block, but instead, use the *state machine* to "remember" your specific situation. Specifically, you can send the request and then transition to the special state "wait4response". In this state, you can process the RESPONSE event, obviously, but you can also process all other events. Some of them you might ignore (as is the case with blocking), some of them you might simply handle (because they have nothing to do with waiting for the response), and some others you might need to defer, for which you can use the "Deferred Event" state pattern (just google for it). The point is that you face the problems head-on and *you* remain in control of what exactly happens.
@@StateMachineCOM Thank you for your comprehensive answer and interesting article. I already tried "waiting4response" state, and as you noticed - there is little problem with multiple events that could be produced while waiting for first modbus response. It is quite simple approach and actually waiting for an answer is inevitable anyway. I'm wondering if going beyond this kind of implementation (keeping many deferred/pending requests in memory) can be useful with interface like slow Modbus. I want to try to experimentally solve this whole problem without keeping all/multiple sent request states and their contexts inside SchedulerAO (by contexts I mean information like tcp session that should receive result etc.). Maybe I can move around whole modbus transaction state including context in AO1->AO2 event, and then send that information back with result AO2->AO1 event. Maybe such "wandering state and context" that changes owner will simplify my Active Objects and reduce "fragmentation" of information related with whole system-wide operation. That would be also "stateless functional programming manner" implementation inside Active Object instead of "state machine manner" implementation. Functional programming approach is also "run to completion" friendly...
@@technics6215 Sure, you can improve the design of your events and they can carry parameters (like the TCP session, etc.) From my experience events have the biggest impact on the complexity of the state machine(s) that need to process them. In other words, to simplify your state machines you should start with your events. (As they say: "garbage in, garbage out"). I'm not sure if you can go all the way to completely "stateless programming" with this, though. I suppose what will happen is that you will see increasing number of IF-ELSE statements in your code based on testing the parameters of your events. This is a slippery slope to "spaghetti", which you tried to avoid in the first place. --MMS
i have one question you always emphasize that we shouldn't use blocking with event driven programming but you have to use mutex (blocking) to protect the shared resource (blinky time) is it correct ?
Thank you for the question because it pertains to the most important point of this video. And that point is spelled out in the very first best practice of concurrent programming (at 6:30), which is NO SHARING of anything among concurrent threads. With no sharing there is no need for mutual exclusion, so no need for mutual exclusion such as mutexes. Instead of sharing, event-driven programs use EVENTS. This lesson showed how to eliminate sharing by merging the separate active objects (Blinky and Button) into one (BlinkyButton). But you could also eliminate sharing by putting the "blink_time" data into an event and sending it over whenever it changes, instead of sharing a variable. Either way, you can ELIMINATE the sharing and the mutex. --MMS
So in Event Driven Design, you have only one Task (the Active Object)? Is it possible to have more active objects for different purposes? You are saying, that in the Dispatch-Handler we should never block. What if I use for instance an I2C Dispatch Handler, and for some actions I have to wait through a while loop if an action is performed like "Wait until startbit is sent". Edit: I have another question. Can you speak/read german, because you studied in germany?
I'm terribly failing as a teacher if you conclude that event-driven design means only one Active Object. This is absolutely *not* the case. You *can* have multiple Active Objects, as demonstrated at 20:40, where the second Active Object "Button" is created. (Later on, Blinky and Button AOs are merged, but this is independent of the possibility of having multiple AOs.) I hope this makes sense. --MMS
@@StateMachineCOM No you are not failing as a teacher. I was wondering why you merged blinky and button. It seems to me that I need a "god" function and implement all my application there. Thank you for your help. Regardless, can you maybe answer my question about the I2C topic?
Regarding the I/O (such as I2C, SPI, UART), sometimes you need to wait for microsecond-level periods, for example when you "bit-bang" the data through a low-level hardware interface. Such delays are too short to use time events, so your only option is to wait in a busy polling loop. This would then make long-lasting RTC steps, which might be tolerable if the AO has low priority. However, this should be clearly distinguished from long waiting periods like in the request-reply scenario. Specifically, after sending a request (through I2C, SPI, or UART) you should generally *not* wait in-line for the response. The response should arrive as a separate asynchronous event. Please read the recommended article "Managing Concurrency in Complex Embedded Systems" ( www.state-machine.com/doc/Cummings2006.pdf ). --MMS
The code for all lessons should be still available from the advertised link at www.state-machine.com/quickstart (there is now a redirection to www.state-machine.com/video-course , but this should be transparent.) Additionally, all the companion code for the course is available on GitHub at: github.com/QuantumLeaps/modern-embedded-programming-course . --MMS
The project download for this lesson (www.state-machine.com/course/lesson34.zip ) has been augmented with the original code. The starting versions, before any changes were made in the course of the lesson, are provided as bsp0.h, bsp0.c, and main0.c. --MMS
@@StateMachineCOM Great transformation example from OS tasks to AOs! The starting files however still do not seem to be included in the zip file. Can you please check?
"Naked" threads, as referenced by Herb Sutter in his article "Prefer Using Active Objects Instead of Naked Threads", are threads that you, as the application designer, write yourself. In that case, the thread routine can be structured any which way and can include any number of *blocking* primitives. In contrast, thread routines in the Active Object pattern are all structured as the event-loop, which contains exactly one blocking point on the event-queue. The tread inside each active object is no longer "naked", because it is pre-defined in the active object and is no longer exposed to the application. In other words, you don't write the body of the thread. You only write the event-handling code that is *not allowed* to block anywhere. --MMS
./bsp.h(26): error: use of undeclared identifier 'USER_SIG' BUTTON_PRESSED_SIG = USER_SIG ./bsp.h(33): error: unknown type name 'Active' extern Active *AO_BlinkyButton; I got 2 Error, how can I solve this problems ?
The missing elements in your build are defined in the "uc_ao.h" header file (part of the uC/AO "framework" that is being built in this lesson.) You should have both "uc_ao.h" and "uc_ao.c" files in your directory and in your uVision project. If you don't please download the project for this lesson #34 from the usual place: www.state-machine.com/video-course or from GitHub: github.com/QuantumLeaps/modern-embedded-programming-course . Then you can compare *your* files with the originals. For such comparisons, I recommend the free code-differencing tool called WinMerge (you can google for it). --MMS
@@StateMachineCOM I downloaded lesson 34 from githup and project have "uc_ao.h" and "uc_ao.c" . sometimes i can run it on my board in first time, but after that it have error (i have extract from RAR again). the main.c run fine. but when i change main0.c to main.c. it get errors. ./bsp.h(26): error: use of undeclared identifier 'USER_SIG' ./bsp.h(33): error: unknown type name 'Active' extern Active *AO_BlinkyButton; main.c(45): error: use of undeclared identifier 'BSP_semaPress' OSSemPend(BSP_semaPress, 0, &err); /* BLOCKING! */ main.c(59): error: use of undeclared identifier 'BSP_semaRelease' OSSemPend(BSP_semaRelease, 0, &err); /* BLOCKING! */ I just tested again, first run oke, second times get errors.
@@ducnguyentuan8778 If you change main0.c -> main.c, you must also change bsp0.c -> bsp.c. That's because the main0.c and bsp0.c correspond to the earlier example, where only uC/OS-II RTOS was used, but without the active-object framework ("uC/AO"). --MMS
This collection of lectures is the best one I've found so far. Thanks for sharing this. Active objects are the first pillar of concurrency.
Yes, that's the main message of this lesson. But this is not the mainstream approach yet. Most concurrent systems still use the "free threading" with multiple blocking mechanisms. Unfortunately, most developers vastly underestimate the true risks, costs, and skills required to use these blocking mechanisms correctly and safely. The active object model of computation restricts the "free threading" approach and avoids a lot of the problems (not all, but the most tricky ones). --MMS
Wow wow wow!!!!!!!! After several ups and downs in ur tutorials:
This tutorial made it clear to me that all the threads should be developed around the EVENT LOOP!!
Great tnx!!!!
Yes, this is the right conclusion. *All* threads should have the event loop structure. Here again, I'd like to recommend the article "Managing Concurrency in Complex Embedded Systems" by David Cummings ( www.state-machine.com/doc/Cummings2006.pdf ). By now, this is perhaps the most frequently recommended article in all my presentations and videos. It uses simple terminology that should be familiar to users of traditional RTOSes. The "Active Object" pattern is not even mentioned (but this is what is being advocated, actually). Please read. --MMS
Wow thanks so much Miro. The greatest ARM lessons ever
Thank you for making these videos through all those years. The first video in this course was made in 2013!
Never clicked a video so fast like this one *_*
Thank you for sharing.
Whew took me 9 days but I watched everything for a brief overview of embedded programming! Now to google everything later lol
anybody new to embedded system can't justify the value of this technique but in my 1.5 years of fulltime embedded carrier I understand. Great work sir ,Thank You.
Thank you very much Miro for your hard work. I also really enjoyed your presentation on the embedded online conference very informative.
Yes, this lesson covers similar material as the Embedded Online Conference presentation. The following lessons will also state machines and other related things, but in more detail. Stay tuned!
Thanks a lot Miro! Very pedagogical.
Excellent explanation! Clear and succinct.
This works well if worst case time of your event loop processing is less than hard realtime deadline. Otherwise it will be a mix of RTOS and event driven. I have used event driven approach to implement usbpd/typec fw stack. But you presented a more generic and secure implementation of event driven approach. Awesome work.
Yes, indeed, the active-object (AO) model can execute on top of many types of real-time kernels. In the presentation I had only time to outline the execution model based on a traditional RTOS. But I've also mentioned that a traditional RTOS is overkill to execute non-blocking AOs. There are simpler non-blocking, but fully *preemptive* hard real-time kernels that can be used instead (e.g., in OSEK/VDX RTOS specification such kernels execute only so called basic threads).
But in any case, the run-to-completion (RTC) event processing within each AO should *not* be confused with preemption. RTC does *not* mean that a running AO must monopolize the CPU. To the contrary, different AOs running at different priorities *can* preempt each other, but as long as they don't share resources there are no concurrency hazards. --MMS
thanks a lot for this video. This really helps to make better design decisions. Very well explained.
Before the message has been handled by the target AO, it May still use the obsolete data, in case we have two or more AOs that need to notify each other with the updated data. There is a time gap between message sent and message processed.
Any computation takes time, so we fundamentally cannot process data "instantaneously". That means that we always deal with some "obsolete data", as you say. This is independent if you use event-driven programming, traditional blocking RTOS, "superloop" or little gnomes to process your data. But we can deal with this by introducing the concept of real-time deadlines, which characterize how long specific data is still good and when it becomes "bad". (Please watch the video "What is real-time?" ruclips.net/video/kLxxXNCrY60/видео.html ).
The issue is really different here. The real problem would be if the data was somehow corrupted or inconsistent. This can happen quite easily when you *share* data between ISRs and the background loop, or between tasks in an RTOS. In contrast, event-driven programming solves this problem rather elegantly. The data carried inside events could be a little "obsolete", but is always *consistent* . This is huge! I hope you see the point. --MMS
@@StateMachineCOM Thank you Dr Miros! Yes, I agree with you that event driven programming is the best architecture for concurrence processes Just wondering if quantum computing can use entanglement phenomenon to realize absolute real time concurrence, as now they can use entanglement to flip qbits to get the computing result immediately. You got a PhD in nuclei physics, maybe can share some thoughts on it. Sorry for asking so many questions but you are the best.
@@wondererasl Quantum computing and quantum entanglement have really nothing to do with the real-time embedded programming discussed here. There is no way to compare the two. --MMS
Thank you Miro once again.
Hi - Thanks for the very insightful video. If I understand correctly, active object = its own thread + private data + private queue. My question is, why is active object has tied to own RTOS based thread? It could be non-RTOS based task handler, which touches only its own private data and has its won queue. Can we call such pattern as Active Object?? thanks
This is an excellent insight. Active Objects are not limited to using threads of traditional RTOS kernel. I started with a traditional RTOS only because it is so widely known. But Active Objects can also use a different kernel, including much simpler kernels. I made a video about one example of such a simpler and more efficient kernel ( ruclips.net/video/PTcauYl994A/видео.htmlsi=V-AdnSug-hmRDql9 ). But even this can be simplified further and Active Objects can be also executed cooperatively without any preemptive kernel. I plan to make some lessons about the various options for executing Active Objects. --MMS
Great tutorials! I follow them since the beginning. I do mainly hardware, with a little bit firmware. Watching them makes me want to start writing more firmware.
Can you share what future lessons are coming to this channel? Are you planning more GUI tutorials, also with real display?
Thank you!
What a great tutorial !!!
extremely good!!
Amazing comeback!!!!
Awesome! Thank you so much for the new video!
Thank you so much. please can you tell me why you didn't use the virtual table to declare the virtual function dispatch in the Active class?
Hello Miro, Im curious if you have any videos in your repository that would help someone whose using SPI to communicate with a sensor of somekind? Im specifically using a fingerprint scanner, but anything with detail about sending data from one device to another would sure be helpful!
Amazing Tutorial! Keep it up Sir. Thank you
Hi Miro,
Combining the "Blinky" AO and the "Button" makes the AO thread priority at the same level.
(I guess that the scheduler sets the AO thread priority)
So how can you ensure and calculate the real rime requirements?
when there were separated threads the real time requirements could be calculated.
Which leads me to the question, should i always consider to design according to the new methodology
(AO and object oriented wise)? or elsewhere?
Really enjoying your course, thanks a lot
Yes, absolutely, the combined AO runs both activities at the same priority. This might be undesirable if these activities have different real-time requirements. But this is not the case here. In fact, most of the time activities are put into separate threads because of blocking in the traditional approach. -MMS
Excellent content. Please keep it up.
Best On Internet
Great, Awesome, I love you man
Is there some emulator which would allow me to learn and practice these concepts on a Windows or Linux computer?
Sure. For example, the event-driven QP frameworks implement the Active Object design pattern and the best practices of concurrent programming. You can also build and run QP on the host computers (Windows, Linux, macOS). To learn and practice the presented concepts, you can download and install QP-bundle ( www.state-machine.com/#Downloads ). On the download web-page, you can also find a "Getting Started" video that shows how to build and run QP on Windows. --MMS
@@StateMachineCOM Thank you. I will have a look.
Hey Miro, I wanted to ask you what could I do if I have a blackout reset situation. Is there any register that reserves any kind of data about why this reset occurs...
Hi - I would like to thank you again for amazing video. I do have three more comments or questions.
1. In the original implementation(at the beginning of this video) of the code where bink_time is shared between threads. Since the resource is shared between threads, it is protected by mutex. My question is, do we really need to protect the resource as one thread updates and other thread only reads? There is never going to be data corruption.
2. What is the reason for declaring the dispatch routine as virtual function? Is it really virtual as in case of desktop version?
3. Why timeEvent object is created which is attached to one particular active object and that too, mapped to one particular event in that AO? Why can;t timeEvent be generic/global object and when any object wants to arm a time_tick, it will provide queue and sig value. Once the event triggers(after the tick expires), the TimeEvent_tick will post an event to the particular queue. That also means, the timeEvent has to maintain a table like array of armed timer event data structure, which mainly contains the queue object, event, timeout, interval, state etc. and running_bitmap of active timeEvents. Let me know, if I am missing anything.
Regarding your question #1 about sharing the "shared_blink_time" variable. In this case of just one 32-bit variable, a 32-bit CPU, and only reading on one end, you might get away without the mutual exclusion protection. But this is a *slippery slope* . The code will not work reliably on 16- or 8-bit CPUs, and will be fragile for any future modifications. For example, if you also added the possibility of changing the color of the LED, you will have two variables (blink_time and led_color). Due to preemption, the thread reading the variables might end up using the new "blink_time", but the old "led_color", so it will display an inconsistent state.
Regarding your question #2 about the virtual dispatch() function. Such a design allows you to provide different state machine implementation strategies in different subclasses. For example, one implementation could be based on the "state table", another on the "nested switch" and yet another on the "event processor".
Finally, regarding your question #3 about the time-events. The presented design is obviously just one of many possibilities. But your suggestion of global and shared time events is potentially hazardous. For example, one hazard is that a time event triggers and gets posted to an event queue, but before it is consumed, another thread arms it again and changes the signal. I hope you can see the problem. And there are many more corner cases like that. Ultimately, it all goes back to the fundamental "sin" of sharing resources. So, one needs to be very careful because it's awfully easy to create something unsafe. (Unfortunately, I see a *lot* of such ad hoc solutions when people improvise with traditional RTOS kernels.) The design presented here avoids such hazards by making time events private to active objects (not shared) with fixed signals. This may be a bit limiting, but it's *safe* . --MMS
@@StateMachineCOM - #1: Yes, if someone wants to overlap two variables in same word, then of course there will collision. If the variable is word size and word aligned then read by one thread and write by other thread then the expensive blocking can be avoided. I guess, you wanted to show the issue of blocking in a thread and that is the reason for use of mutex.
#3: I did not get this. The functionality of OS-Delay in RTOS video series was also global; except that scope of delay function starts from the point it is triggered to till it expires. In timeEvent object, the scope extends till the event is processed in dispatch routine. For example, array of tick_events and every entry of tick_event will have state, which can be enumerated to IDLE, ARMED, and POSTED. Yes, one can protect the operation that is combines the update of state and l_tevtNum. Why can't tick_event be generic and used as service by any object?
Thanks
@@jankeshchakravarthy9389 Regarding issue #3, as I said there are many ways to implement time events and event passing in general. You probably are used to passing messages by copy, which does not involve sharing and therefore is safe, but also slow, memory-hungry, and potentially non-deterministic.
Therefore, the option presented here passes only *pointers* to events into the queues, so that event passing is fast and deterministic. However, the consequences are that the event objects (including time events) remain *shared*. So far in this course, all events were immutable (const), so sharing them wasn't a problem. (And I still need to discuss mutable events in future lessons!)
However, time events are mutable, so this issue of sharing such events came up here. So, the time event objects are only protected momentarily during posting, but are NOT protected thereafter and definitely not till dispatch. I hope this clarifies the issue, and my earlier comment makes more sense now. --MMS
Welcome back :)
Watching your tutorial video makes me excited to learn embedded more, even though it is still difficult. And I have a concern that: Event-driven programming will include Active Object pattern, is that correct ?
This is great, thank you.
Active objects seems like a powerful paradigm. Is most of the sequential code can be converted this way? I expect there would be some exceptions, so what are they?
I asked this because I have tried to convert some of my naked thread into active objects. I soon found that making dispatch function was either hard to be efficient or spaghetti..
As you found out by yourself, converting sequential code to event-driven code is NOT trivial at all. For example, consider a piece of sequential code that consists of a while loop that calls a function, that calls another function, that... blocks (or polls). How do you unravel something like that? I mean you need to eliminate the blocking (or polling) and instead return to the event loop, right? I remember spending a whole year in a company doing exactly such a conversion from the existing sequential code to event-driven code. That's why it is important to understand the difference in paradigms and to create event-driven code to begin with. Here, I would like to recommend an article strongly advocating to avoid blocking: "Managing Concurrency in Complex Embedded Systems", see www.state-machine.com/doc/Cummings2006.pdf
What about two-way communication with Active Object pattern? Is this covered in this course? I'm not using strictly AO pattern yet, but I have something very similar based on (unfortunately) single Event Bus in my case and I wonder what I can improve (after I split my event bus into AO queues). Lets say I have ModbusActiveObject, SchedulerActiveObject and WebInterfaceActiveObject. ModbusAO has a queue with ModbusReadRequests. SchedulerAO and WebInterfaceAO can send messages to ModbusAO. When ModbusAO reads data from bus (or there is an error) ModbusAO posts response to the caller AO as another message. I have few other objects (like datalogger, display etc.).
I'm not sure what you mean by "two-way" communication and what's special about it. In the active object model, AO1 can post events to AO2 and also AO2 can post events back to AO1. Does that count as "two-way" communication? Regarding you partitioning of the system, please remember that one AO can typically have more functionality than a traditional thread, because an AO remains *responsive* to events (while a thread blocks and is thus unresponsive). This means that a "ModbusAO" can have also some functionality, besides also encapsulating the Modbus. I'm not sure what the SchedulerAO does. --MMS
@@StateMachineCOM Regarding SchedulerAO - this is just one of two event producers for ModbusAO. It is part of data logger feature. Regarding "what's special about two-way" - I have problem because my "data request" events must be related with "data response" events. AO1 (tcp interface that processed 10 tcp requests) wants data from AO2 (modbus) and it produces multiple events with "ModbusReadRequest". AO2 consumes these events, does it's job and then it must "respond" to AO1. So it should produce multiple events with "ModbusReadResult". But these ModbusReadRequest events must be linked with ModbusReadResult events (to know which response belongs to which request, or at least to which TCP client data should go). But ModbusReadRequest does not exist (in queue), because it was consumed by AO2. So I have to keep "ModbusReadRequest" copies in AO1. At this moment I have implemented weird kind of queue with two heads and tails to handle this, now I'm thinking about something else.
@@technics6215 The request-response pattern is very common, and typically people like to send a request and *block* until the response is received. This, of course, sweeps the whole slew of problems under the rug, because the question is what do you do with all the events that *might* be produced during the several milliseconds between the request and the response(?) Here, I would highly recommend that you read the article "Managing Concurrency in Complex Embedded Systems":
www.state-machine.com/doc/Cummings2006.pdf
But with active objects, you don't block, but instead, use the *state machine* to "remember" your specific situation. Specifically, you can send the request and then transition to the special state "wait4response". In this state, you can process the RESPONSE event, obviously, but you can also process all other events. Some of them you might ignore (as is the case with blocking), some of them you might simply handle (because they have nothing to do with waiting for the response), and some others you might need to defer, for which you can use the "Deferred Event" state pattern (just google for it). The point is that you face the problems head-on and *you* remain in control of what exactly happens.
@@StateMachineCOM Thank you for your comprehensive answer and interesting article.
I already tried "waiting4response" state, and as you noticed - there is little problem with multiple events that could be produced while waiting for first modbus response. It is quite simple approach and actually waiting for an answer is inevitable anyway. I'm wondering if going beyond this kind of implementation (keeping many deferred/pending requests in memory) can be useful with interface like slow Modbus.
I want to try to experimentally solve this whole problem without keeping all/multiple sent request states and their contexts inside SchedulerAO (by contexts I mean information like tcp session that should receive result etc.). Maybe I can move around whole modbus transaction state including context in AO1->AO2 event, and then send that information back with result AO2->AO1 event. Maybe such "wandering state and context" that changes owner will simplify my Active Objects and reduce "fragmentation" of information related with whole system-wide operation. That would be also "stateless functional programming manner" implementation inside Active Object instead of "state machine manner" implementation. Functional programming approach is also "run to completion" friendly...
@@technics6215 Sure, you can improve the design of your events and they can carry parameters (like the TCP session, etc.) From my experience events have the biggest impact on the complexity of the state machine(s) that need to process them. In other words, to simplify your state machines you should start with your events. (As they say: "garbage in, garbage out"). I'm not sure if you can go all the way to completely "stateless programming" with this, though. I suppose what will happen is that you will see increasing number of IF-ELSE statements in your code based on testing the parameters of your events. This is a slippery slope to "spaghetti", which you tried to avoid in the first place. --MMS
sir please update videos regularly or weakly
i have one question you always emphasize that we shouldn't use blocking with event driven programming but you have to use mutex (blocking) to protect the shared resource (blinky time) is it correct ?
Thank you for the question because it pertains to the most important point of this video. And that point is spelled out in the very first best practice of concurrent programming (at 6:30), which is NO SHARING of anything among concurrent threads. With no sharing there is no need for mutual exclusion, so no need for mutual exclusion such as mutexes. Instead of sharing, event-driven programs use EVENTS. This lesson showed how to eliminate sharing by merging the separate active objects (Blinky and Button) into one (BlinkyButton). But you could also eliminate sharing by putting the "blink_time" data into an event and sending it over whenever it changes, instead of sharing a variable. Either way, you can ELIMINATE the sharing and the mutex. --MMS
@@StateMachineCOM excellent as always thanks a lot.
So in Event Driven Design, you have only one Task (the Active Object)?
Is it possible to have more active objects for different purposes?
You are saying, that in the Dispatch-Handler we should never block. What if I use for instance an I2C Dispatch Handler, and for some actions I have to wait through a while loop if an action is performed like "Wait until startbit is sent".
Edit:
I have another question. Can you speak/read german, because you studied in germany?
I'm terribly failing as a teacher if you conclude that event-driven design means only one Active Object. This is absolutely *not* the case. You *can* have multiple Active Objects, as demonstrated at 20:40, where the second Active Object "Button" is created. (Later on, Blinky and Button AOs are merged, but this is independent of the possibility of having multiple AOs.) I hope this makes sense. --MMS
@@StateMachineCOM No you are not failing as a teacher. I was wondering why you merged blinky and button. It seems to me that I need a "god" function and implement all my application there.
Thank you for your help.
Regardless, can you maybe answer my question about the I2C topic?
Regarding the I/O (such as I2C, SPI, UART), sometimes you need to wait for microsecond-level periods, for example when you "bit-bang" the data through a low-level hardware interface. Such delays are too short to use time events, so your only option is to wait in a busy polling loop. This would then make long-lasting RTC steps, which might be tolerable if the AO has low priority. However, this should be clearly distinguished from long waiting periods like in the request-reply scenario. Specifically, after sending a request (through I2C, SPI, or UART) you should generally *not* wait in-line for the response. The response should arrive as a separate asynchronous event. Please read the recommended article "Managing Concurrency in Complex Embedded Systems" ( www.state-machine.com/doc/Cummings2006.pdf ). --MMS
Yes, I used to speak German and can still read it, but I prefer English. --MMS
the link to get source code of this lesson have changes . can you give me the original ?
The code for all lessons should be still available from the advertised link at www.state-machine.com/quickstart (there is now a redirection to www.state-machine.com/video-course , but this should be transparent.) Additionally, all the companion code for the course is available on GitHub at: github.com/QuantumLeaps/modern-embedded-programming-course . --MMS
@@StateMachineCOM Thank you.
Great course! Is there a way to get the source code you started the lesson with? lesson34.zip is from the end of the lecture. Thank you!
The project download for this lesson (www.state-machine.com/course/lesson34.zip ) has been augmented with the original code. The starting versions, before any changes were made in the course of the lesson, are provided as bsp0.h, bsp0.c, and main0.c. --MMS
@@StateMachineCOM Awesome, thank you very much. Benjamin
@@StateMachineCOM Great transformation example from OS tasks to AOs! The starting files however still do not seem to be included in the zip file. Can you please check?
@@panagiotismarkopoulos6649 Files are in the git repository, but perhaps not yet on the website.
Good Video. Please add a video on comparison of RTOS like Zephyr, uCOSII, FreeRTOS, Azure
Thank you very much.
What are "naked" threads?
"Naked" threads, as referenced by Herb Sutter in his article "Prefer Using Active Objects Instead of Naked Threads", are threads that you, as the application designer, write yourself. In that case, the thread routine can be structured any which way and can include any number of *blocking* primitives. In contrast, thread routines in the Active Object pattern are all structured as the event-loop, which contains exactly one blocking point on the event-queue. The tread inside each active object is no longer "naked", because it is pre-defined in the active object and is no longer exposed to the application. In other words, you don't write the body of the thread. You only write the event-handling code that is *not allowed* to block anywhere. --MMS
./bsp.h(26): error: use of undeclared identifier 'USER_SIG' BUTTON_PRESSED_SIG = USER_SIG
./bsp.h(33): error: unknown type name 'Active' extern Active *AO_BlinkyButton;
I got 2 Error, how can I solve this problems ?
The missing elements in your build are defined in the "uc_ao.h" header file (part of the uC/AO "framework" that is being built in this lesson.) You should have both "uc_ao.h" and "uc_ao.c" files in your directory and in your uVision project. If you don't please download the project for this lesson #34 from the usual place: www.state-machine.com/video-course or from GitHub: github.com/QuantumLeaps/modern-embedded-programming-course . Then you can compare *your* files with the originals. For such comparisons, I recommend the free code-differencing tool called WinMerge (you can google for it). --MMS
@@StateMachineCOM I downloaded lesson 34 from githup and project have "uc_ao.h" and "uc_ao.c" . sometimes i can run it on my board in first time, but after that it have error (i have extract from RAR again).
the main.c run fine.
but when i change main0.c to main.c. it get errors.
./bsp.h(26): error: use of undeclared identifier 'USER_SIG'
./bsp.h(33): error: unknown type name 'Active' extern Active *AO_BlinkyButton;
main.c(45): error: use of undeclared identifier 'BSP_semaPress' OSSemPend(BSP_semaPress, 0, &err); /* BLOCKING! */
main.c(59): error: use of undeclared identifier 'BSP_semaRelease' OSSemPend(BSP_semaRelease, 0, &err); /* BLOCKING! */
I just tested again, first run oke, second times get errors.
@@ducnguyentuan8778 If you change main0.c -> main.c, you must also change bsp0.c -> bsp.c. That's because the main0.c and bsp0.c correspond to the earlier example, where only uC/OS-II RTOS was used, but without the active-object framework ("uC/AO"). --MMS
@@StateMachineCOM cannot believe, now it is working.
really hat off
👍👍👍