Writing such big automatas for simple tasks like led on/off is overkill. I've been struggling with that forever. That's why i decided to use coroutines based on computed goto for sequential tasks. They reduce amount of code for like a twice, yet they are also automatas under hood. I still use classic automatas, but in embedded systems there are really a lot of sequential tasks that makes your code a huge mess. If i can write simple piece of code in blocking maner, then it also means that i can write the same piece of code without explicitly defining states, just by using coroutines.
Of course, this is overkill for a "blinky". That's precisely why I also provide the Visual Basic calculator example. Try to use your coroutines and computed gotos for that... We really don't need to debate that a sequential solution for a sequential problem works best. I actually made sure to mention this in the video. But the problem is that most real-life projects are *not sequential*, even though they often seem to start that way. In any real-life problem, you inevitably discover that your code needs to handle many sequences of events, so you keep proliferating tasks (or coroutines). You also keep adding ifs, elses, flags, and variables to your hard-coded sequences of blocking calls (e.g., in your coroutines) until you create the usual "spaghetti".
@@StateMachineCOM well, in my case i don't use pure approach for complex automatas. It's more like a synthesis of both. Since computed gotos do not mess with switch-case construction - i am not restricted to use them both. Sorry, i am not trying to debate your points. I am just trying to say that i was "huge fan of pure FSM design". I've been using automatas everywhere until i regret it. Not because they were working bad, but because they took too much time to design.
Yes, of course. This video lesson starts the whole segment about state machines of all kinds, including the traditional Finite State Machines (FSMs), UML state machines, and other state machines. The focus will be on the *software* applications of state machines, so the hardware origins of FSMs (like the Mealy and Moore machines) will be covered only briefly. ---MMS
Great video. As always. While I completely agree state machine is one of the keys to organize programs, I'm still not convinced by the non-blocking event-driven RTOS approach. In order to better understand your design decision, allow me to play devil's advocate: ~~~~~~~ To me, the beauty of an RTOS is about having the cleanness of sequential blocking-code, without wasting CPU time. The BlinkyButton program is complex because we are combining two tasks that should be kept separated. TASK 1: Button and Blue LED management TASK 2: Timer and Green LED management Here's a naive example of how I would create those two small tasks: Task 1 uses an RTOS primitive to block until a button event is raised and update the blue LED. ``` while(1) { OS_block_until(button_event) if (button_event == PRESSED) { BSP_ledBlueOn(); blinky =/ 2; } else { BSP_ledBlueOff(); } } ``` Task 2 uses an RTOS primitive to introduce a delay (yielding to other tasks) ``` while(1) { switch(me->state) { case OFF_STATE: BSP_ledGreenOn(); me->state = ON_STATE; break; case ON_STATE: BSP_ledGreenOff(); me->state = OFF_STATE; break; } OS_delay(1000); } ``` Note the heavy use of wait-related RTOS primitives: They will guaranty that the tasks will yield to more important tasks, as soon as possible. I would argue that this code is - Easier to read - More decoupled - Will require a little bit more memory (the additional task stack overhead) - Even if we have more task, and therefore more switching overhead, the tasks yield immediately after completing their purpose and therefore have a better CPU efficiency. How would you justify the non-blocking event-driven approach instead of my two tasks approach? ~~~~ It's more than likely that my comment is misguided: I'm sure the real purpose of the BlinkyButton is to be as simple as possible to introduce the subject. That said, I can't help but wonder about the tradeoffs of using this approach. In any case, thanks for your great content. It forces me to re-evaluate my approach and I'm thankful for that.
Absolutely, your sequential RTOS threads are small and sweet, but they are a *ticking bomb*. Why? Because (1) the hard-coded event sequences are not extensible and (2) the threads have no other way of communicating except sharing variables (*)-see below. Extensibility is crucial, because you cannot add anything else to your blinky thread (try it!). So, to add any new functionality you have to create another thread. Even if you don't care about the cost of RAM for the stack and TCB (task-control block), the problem is now coupling. The new thread most likely needs to somehow "talk" to the blinky thread, for example to change the blinking rate of the LED. So now you need to *share* a variable (blinking rate) between concurrent threads. To avoid *race conditions*, you must not forget to use a mutual-exclusion mechanism (perhaps a mutex). But now, the mutex can create additional delays and can screw your timing. (*) You might try to use a message queue instead of shared variables, but then your design will be increasingly "message driven". You will then notice that you can only block on the queue, but you should *not block* in the code that handles the messages, because it will delay processing of other messages. In other words, by trading shared variables for messages, you will start to build your homegrown event-driven framework. So, my whole point is, to face the realities from the beginning and use the Active Object design pattern properly from the get go. I hope that my comments make more sense now... --MMS
@@StateMachineCOM Thanks for your answer. I agree, my example falls short when we need intertask communication. I'm thinking of trying this out with an IoT project. This seems like the perfect kind of challenge the active object would help to solve.
Fantastic, I just read the chapter about the calculator on the PSiCC2 book today. One problem is that the book reference some code that I could not find in any download version of the qp site like "\qpc\examples\80x86\dos\tcpp101\l\game\" on pg 25
The project download for this lesson (www.state-machine.com/quickstart/lesson35.zip ) contains the VisualBasic calculator demo with code in the sub-directory `demo_vbcalc`, so you can examine the code and play with the calculator. Actually, as you do, I challenge you to find out more "corner cases". This example has been used in other books about state machines before (e.g., Ian Horrocks' book "Constructing the User Interface with Statecharts"). Horrocks found 10 problems with this little calculator and other people have found more... --MMS
Hello Miro, thank you for this series is the best for embedded. I'm having trouble running qpc on STM32CubeIDE. I've include qpc/include and qpc\ports\arm-cm\qxk\gnu but I still receive errors like "undefined reference to `QF_init'" although it is recognizing Q_DEFINE_THIS_FILE and if I ctr+click on QF_init function from main the IDE is directing me to de definition of QF_init in the qf.h file. Any thought about this? Does any one use qpc with STM32CubeIDE? Should I move to Keil or IAR?
If you are interested in qpc examples for STM32, please take a look in the directory qpc\examples\dpp_nucleo... and qpc\examples\dpp_stm32.. These examples use parts of STM32Cube software library. --MMS
As always a nice, clean and quality video on the subject of embedded sw. A quick note here: it would be great if you could use a slightly bigger font in the code editor. Thank you for the video :)
If you treat embedded software just like other software then there are lots of design patterns. But a design pattern which is quite useful for designing state machines is the State Pattern. Miro has got an article that he wrote about this pattern many years ago which discusses this in detail. But it essentially allows you to write an event handler function for each 'state'. Then you have a high level dispatcher which simply sends messages to the state handler. Within the state handler, you can then write state specific event handling code (which is just switch-case) as shown in this video. The only problem with the state pattern is the large amounts of states you end up with. You can get around this by using flags and all sorts of conditionals but it kinda ruins the ideology behind the design pattern.
This great stuff and I used it while working on appliance firmware at GE. I am sorry Miro felt pressured to drop the quantum metaphor in his book because of some bunny slipper wearing, titty baby, sniveling, basement dwelling mamma's boys who have nothing better to complain about. I find it hard to believe that rational and mature people would bother complaining. But thinking about some of my software coworkers over the past 40 years, perhaps they are not rational and certainly a great number of them were immature. I thought was a great analogy, especially between states and sub-states.
Merging blinky and button together is not a good idea, cause it is violating single responsibility principle. Assume you have 10 active objects. Merging them together will result in a spagetthi code, where you cannot separate one component from another or replace it.
I wouldn't blindly follow the SRP when concurrency is involved. In concurrent programming, the shared-nothing (SN) principle (as advocated in the Active Object pattern) is stronger and more valuable. The negative implications of resource sharing are far worse than combining responsibilities. Also, multiple threads are expensive and inefficient (private stacks, thread-control blocks, context switch overhead). The main justification for introducing new threads should be real-time considerations, where some activities must gain access to the CPU before others. --MMS
@@StateMachineCOM Instead of changing sharedBlinkTime, ButtonAO should send event "change blink time" with some parameter to BlinkyAO, thus blinkTime will be a private variable for BlinkyAO not exposed to anyone. The code will be modular still, SRP won't be viaoltaed and there will be no shared state
@@Xeenych Yes, you could send an event. Such a design would be much better than sharing resources and could be even acceptable. But still, there is the memory expense and CPU inefficiency associated with having many active objects (threads). This expense can be avoided by merging the two trivial active objects because they are *event-driven*. This is in stark contrast to traditional, *blocking* threads that cannot be easily merged. Maybe the chosen example of Blinky and Button was not that compelling, but one of the main goals of the video is to demonstrate the extensibility of event-driven active objects and contrast it with the inextensibility of blocking threads. I hope you get that point. --MMS
Only channel that i have the notifications enabled. Thank you for this brilliant series Miro.
Me too :)
The longest kurs in History
7 years
Thanks for your Efforts
One of the best video I've seen about the State Machine.
This is the best embedded software training 🤩 thanks miro samek
Top level lessons for low level stuffs
Brilliant explanation and example , keep up the great work.
You have the best tutorials. Thank you!
I was amazed by your video lectures. I'm looking forward to the next State Machine lessons.
We look forward to continuing the lessons
Excellent channel!! It's a pity new videos are so rare.
Always excellent and precious tutorial! Thank you so much!
Now I feel that the most challenging task is to properly model a state machine.
Thank you for your work
Writing such big automatas for simple tasks like led on/off is overkill. I've been struggling with that forever. That's why i decided to use coroutines based on computed goto for sequential tasks. They reduce amount of code for like a twice, yet they are also automatas under hood. I still use classic automatas, but in embedded systems there are really a lot of sequential tasks that makes your code a huge mess. If i can write simple piece of code in blocking maner, then it also means that i can write the same piece of code without explicitly defining states, just by using coroutines.
Of course, this is overkill for a "blinky". That's precisely why I also provide the Visual Basic calculator example. Try to use your coroutines and computed gotos for that... We really don't need to debate that a sequential solution for a sequential problem works best. I actually made sure to mention this in the video. But the problem is that most real-life projects are *not sequential*, even though they often seem to start that way. In any real-life problem, you inevitably discover that your code needs to handle many sequences of events, so you keep proliferating tasks (or coroutines). You also keep adding ifs, elses, flags, and variables to your hard-coded sequences of blocking calls (e.g., in your coroutines) until you create the usual "spaghetti".
@@StateMachineCOM well, in my case i don't use pure approach for complex automatas. It's more like a synthesis of both. Since computed gotos do not mess with switch-case construction - i am not restricted to use them both.
Sorry, i am not trying to debate your points. I am just trying to say that i was "huge fan of pure FSM design". I've been using automatas everywhere until i regret it. Not because they were working bad, but because they took too much time to design.
Are you planning to upload more videos for FSM?? also thanks a lot for your existing videos they are brilliant!
Yes, of course. This video lesson starts the whole segment about state machines of all kinds, including the traditional Finite State Machines (FSMs), UML state machines, and other state machines. The focus will be on the *software* applications of state machines, so the hardware origins of FSMs (like the Mealy and Moore machines) will be covered only briefly. ---MMS
Great video. As always.
While I completely agree state machine is one of the keys to organize programs, I'm still not convinced by the non-blocking event-driven RTOS approach.
In order to better understand your design decision, allow me to play devil's advocate:
~~~~~~~
To me, the beauty of an RTOS is about having the cleanness of sequential blocking-code, without wasting CPU time.
The BlinkyButton program is complex because we are combining two tasks that should be kept separated.
TASK 1: Button and Blue LED management
TASK 2: Timer and Green LED management
Here's a naive example of how I would create those two small tasks:
Task 1 uses an RTOS primitive to block until a button event is raised and update the blue LED.
```
while(1)
{
OS_block_until(button_event)
if (button_event == PRESSED)
{
BSP_ledBlueOn();
blinky =/ 2;
}
else
{
BSP_ledBlueOff();
}
}
```
Task 2 uses an RTOS primitive to introduce a delay (yielding to other tasks)
```
while(1)
{
switch(me->state)
{
case OFF_STATE:
BSP_ledGreenOn();
me->state = ON_STATE;
break;
case ON_STATE:
BSP_ledGreenOff();
me->state = OFF_STATE;
break;
}
OS_delay(1000);
}
```
Note the heavy use of wait-related RTOS primitives: They will guaranty that the tasks will yield to more important tasks, as soon as possible.
I would argue that this code is
- Easier to read
- More decoupled
- Will require a little bit more memory (the additional task stack overhead)
- Even if we have more task, and therefore more switching overhead, the tasks yield immediately after completing their purpose and therefore have a better CPU efficiency.
How would you justify the non-blocking event-driven approach instead of my two tasks approach?
~~~~
It's more than likely that my comment is misguided: I'm sure the real purpose of the BlinkyButton is to be as simple as possible to introduce the subject.
That said, I can't help but wonder about the tradeoffs of using this approach.
In any case, thanks for your great content.
It forces me to re-evaluate my approach and I'm thankful for that.
Absolutely, your sequential RTOS threads are small and sweet, but they are a *ticking bomb*. Why? Because (1) the hard-coded event sequences are not extensible and (2) the threads have no other way of communicating except sharing variables (*)-see below.
Extensibility is crucial, because you cannot add anything else to your blinky thread (try it!). So, to add any new functionality you have to create another thread. Even if you don't care about the cost of RAM for the stack and TCB (task-control block), the problem is now coupling. The new thread most likely needs to somehow "talk" to the blinky thread, for example to change the blinking rate of the LED. So now you need to *share* a variable (blinking rate) between concurrent threads. To avoid *race conditions*, you must not forget to use a mutual-exclusion mechanism (perhaps a mutex). But now, the mutex can create additional delays and can screw your timing.
(*) You might try to use a message queue instead of shared variables, but then your design will be increasingly "message driven". You will then notice that you can only block on the queue, but you should *not block* in the code that handles the messages, because it will delay processing of other messages. In other words, by trading shared variables for messages, you will start to build your homegrown event-driven framework.
So, my whole point is, to face the realities from the beginning and use the Active Object design pattern properly from the get go.
I hope that my comments make more sense now...
--MMS
@@StateMachineCOM
Thanks for your answer.
I agree, my example falls short when we need intertask communication.
I'm thinking of trying this out with an IoT project. This seems like the perfect kind of challenge the active object would help to solve.
Fantastic, I just read the chapter about the calculator on the PSiCC2 book today. One problem is that the book reference some code that I could not find in any download version of the qp site like "\qpc\examples\80x86\dos\tcpp101\l\game\" on pg 25
The project download for this lesson (www.state-machine.com/quickstart/lesson35.zip ) contains the VisualBasic calculator demo with code in the sub-directory `demo_vbcalc`, so you can examine the code and play with the calculator. Actually, as you do, I challenge you to find out more "corner cases". This example has been used in other books about state machines before (e.g., Ian Horrocks' book "Constructing the User Interface with Statecharts"). Horrocks found 10 problems with this little calculator and other people have found more... --MMS
Hello Miro, thank you for this series is the best for embedded. I'm having trouble running qpc on STM32CubeIDE. I've include qpc/include and qpc\ports\arm-cm\qxk\gnu but I still receive errors like "undefined reference to `QF_init'" although it is recognizing Q_DEFINE_THIS_FILE and if I ctr+click on QF_init function from main the IDE is directing me to de definition of QF_init in the qf.h file. Any thought about this? Does any one use qpc with STM32CubeIDE? Should I move to Keil or IAR?
If you are interested in qpc examples for STM32, please take a look in the directory qpc\examples\dpp_nucleo... and qpc\examples\dpp_stm32.. These examples use parts of STM32Cube software library. --MMS
@@StateMachineCOM Thx Miro, I forgot to reply, I manage to integrate qpc vanilla with stm32 CubeMxIde and works great.
Excellent video 👏, thank you!
When should we expect part 2?
As always a nice, clean and quality video on the subject of embedded sw.
A quick note here: it would be great if you could use a slightly bigger font in the code editor.
Thank you for the video :)
Confirm the ultimate goal, then focus on the simplest and cheapest way to fulfill it. It may fail, but not so ugly.
Embedded HERO !
Thank you sir
What are the other design patterns in embedded c ?
If you treat embedded software just like other software then there are lots of design patterns. But a design pattern which is quite useful for designing state machines is the State Pattern. Miro has got an article that he wrote about this pattern many years ago which discusses this in detail. But it essentially allows you to write an event handler function for each 'state'. Then you have a high level dispatcher which simply sends messages to the state handler. Within the state handler, you can then write state specific event handling code (which is just switch-case) as shown in this video. The only problem with the state pattern is the large amounts of states you end up with. You can get around this by using flags and all sorts of conditionals but it kinda ruins the ideology behind the design pattern.
@@supershaye Do we have access to that article? that would be useful
This great stuff and I used it while working on appliance firmware at GE.
I am sorry Miro felt pressured to drop the quantum metaphor in his book because of some bunny slipper wearing, titty baby, sniveling, basement dwelling mamma's boys who have nothing better to complain about. I find it hard to believe that rational and mature people would bother complaining. But thinking about some of my software coworkers over the past 40 years, perhaps they are not rational and certainly a great number of them were immature. I thought was a great analogy, especially between states and sub-states.
Thanks a lot for your appreciation of the "quantum analogy" that I used in the first "Practical Statecharts in C/C++" book! --MMS
Merging blinky and button together is not a good idea, cause it is violating single responsibility principle. Assume you have 10 active objects. Merging them together will result in a spagetthi code, where you cannot separate one component from another or replace it.
I wouldn't blindly follow the SRP when concurrency is involved. In concurrent programming, the shared-nothing (SN) principle (as advocated in the Active Object pattern) is stronger and more valuable. The negative implications of resource sharing are far worse than combining responsibilities. Also, multiple threads are expensive and inefficient (private stacks, thread-control blocks, context switch overhead). The main justification for introducing new threads should be real-time considerations, where some activities must gain access to the CPU before others. --MMS
@@StateMachineCOM Instead of changing sharedBlinkTime, ButtonAO should send event "change blink time" with some parameter to BlinkyAO, thus blinkTime will be a private variable for BlinkyAO not exposed to anyone. The code will be modular still, SRP won't be viaoltaed and there will be no shared state
@@Xeenych Yes, you could send an event. Such a design would be much better than sharing resources and could be even acceptable. But still, there is the memory expense and CPU inefficiency associated with having many active objects (threads). This expense can be avoided by merging the two trivial active objects because they are *event-driven*. This is in stark contrast to traditional, *blocking* threads that cannot be easily merged. Maybe the chosen example of Blinky and Button was not that compelling, but one of the main goals of the video is to demonstrate the extensibility of event-driven active objects and contrast it with the inextensibility of blocking threads. I hope you get that point. --MMS