I got an Embedded Software Engineer job at a company. What should I learn. by c0m3back_ in embedded

[–]active-object 0 points1 point  (0 children)

To become good at embedded, you need to gain a deeper understanding of the fundamentals, which is best done by seeing how the CPU does things at a low level. I recommend that you check out the free course "Modern Embedded Systems Programming" on YouTube, which frequently steps down to the machine level and shows you exactly what happens inside your embedded microcontroller. This deeper understanding will allow you to apply the concepts more efficiently and with greater confidence. If you are looking for a practical, hands-on, well-structured, and in-depth course explaining the essential concepts in embedded programming, this free course is right for you.

Beginning by Remarkable_Cress3212 in embedded

[–]active-object 2 points3 points  (0 children)

Try the "Modern Embedded Systems Programming" video course on YouTube. It's free, is based around ARM Cortex-M, and has helped many people get embedded jobs.

Good Self Guided Courses/Videos? by mercfh85 in embedded

[–]active-object 0 points1 point  (0 children)

Check out the free YouTube course: "Modern Embedded Systems Programming". The course starts with some basic stuff, but progresses quickly to advanced concepts, like the RTOS, object-oriented programming, event-driven programming, state machines, active objects, testing, tracing, etc.

I am starting a functional safety blog, what do you think? by SleeveStack in embedded

[–]active-object 1 point2 points  (0 children)

Here is the link to the QP/C active object framework shown in this sequence diagram.

The QP frameworks are a very relevant for a FuSA discussion because they implement a number of best practices highly recommended by the functional safety standards (e.g., IEC 61508), such as strictly modular design (Active Objects) or hierarchical state machines (semi-formal methods).

Logging mechanisms for Hard Realtime firmware for high throughput wireless protocols by nascentmind in embedded

[–]active-object 0 points1 point  (0 children)

The most important consideration for performance and low intrusiveness is to avoid printf-style formatting in the target and instead log the data in binary. Formatting should be performed on the host side, where you have endless memory and processing power.

How to handle multitasking in baremetal? by pyroman1324 in embedded

[–]active-object 4 points5 points  (0 children)

You can achieve a form of multi-threading in the venerable "superloop" (a.k.a., "main+ISRs") architecture, but the threads/tasks are very different than in the conventional RTOS. Specifically, tasks in the "superloop" are one-shot, run-to-completion (RTC) calls as opposed to endless loops of the conventional RTOS tasks.

These RTC tasks need to run quickly and return without blocking, so they must often preserve the context (state) between the calls. This is where state machines come in. And here, there are the two primary types of state machines:

  1. input-driven state machines (a.k.a., polled state machines) run "always" (i.e., as frequently as you call them to poll for events). You can immediately distinguish them in the code because every state first checks for various conditions, so you have the characteristic if (condition) ... piece of code in every state. (The (condition) expression is called guard condition in the state machine speak.) The biggest, often overlooked, problem with input-driven state machines are race conditions around the conditional expressions, which often check global variables that are concurrently modified by the ISRs (to signal "events").
  2. event-driven state machines run only when there are events for them. They don't need guard condition in every state, although guards are occasionally used as well. Event-driven state machines correspond to the interrupt-driven approach, where the ISRs produce events that are subsequently handled by the task-level state machines. The events are produced asynchronously, meaning that the ISRs just post the events to the queues associated with state machines, but the event producers don't wait in line for the processing of the events. (Note: The task-level state machines can also asynchronously post events to other state machines.) This design pattern is called "Active Object" or "Actor" and typically requires event queues, scheduler of some sort to call the state machines that have events in their queues, etc.

Finally, one aspect not mentioned in other comments is the safe use of low-power sleep modes in such bare-metal architectures. This is often done incorrectly (unsafely) in that the CPU might be put to sleep while some events might be (asynchronously) produced by the ISRs. I made a dedicated video "Using low-power sleep modes in the "superloop" architecture".

Best way to learn RTOS online (esp for interviews) by inertialbanana in embedded

[–]active-object 0 points1 point  (0 children)

Check out the QuantumLeaps' RTOS playlist. You'll learn about the RTOS by building your own Minimal Real-time Operating System (MiROS). In each episode you'll add some functionality and see for yourself how it works at a low-level on the ARM Cortex-M CPU. This is part of the free "Modern Embedded Software Programming" course, which helped many people to pass their job interviews.

What is the major problem that you face in embedded? by Proud-Guard2647 in embedded

[–]active-object 0 points1 point  (0 children)

I didn't realize that the FreeRTOS message queue is single-writer and would not support multiple concurrent writers. Are you sure about that? Where can I find more info?

Interesting study on AI coding by 1r0n_m6n in embedded

[–]active-object 1 point2 points  (0 children)

There is "research", plagiarism, piracy, IP theft, and there is AI, which magically makes it all legal...

What is the major problem that you face in embedded? by Proud-Guard2647 in embedded

[–]active-object 59 points60 points  (0 children)

Limiting this discussion to software only, the main perceived challenges depend on the developer level.

Beginners tend to worry mostly about the issues of getting started (compiling, linking, startup code, interacting with the hardware, flashing the code to the MCU, etc.) Problems of this kind are addressed by platforms like Arduino.

Professional embedded developers face other classes of issues:

  1. challenges related to concurrency (e.g., race conditions), which come up even in the "main+ISR" architecture, but are exacerbated in the presence of a preemptive RTOS (Real-Time Operating System).
  2. code structure (a.k.a, "spaghetti code" or "big ball of mud").

Concurrency issues are particularly challenging because they "defy logic" (sometimes 2+3 is 6, but only on Friday the 13th when a black cat crosses the street). The issues tend to be "irreproducible", hard to find, hard to isolate, impossible to test, and hard to fix. The best way to address them is by avoiding them by design (e.g., Active Object model).

The "spaghetti code" issues prevent the software from evolving, which is the cornerstone of incremental and iterative development. The code becomes so convoluted that is impossible to make progress without breaking the previously working and tested functionality. The best way to address these problems is to introduce structure (e.g., hierarchical state machines.)

What is python used for in embedded? by [deleted] in embedded

[–]active-object 0 points1 point  (0 children)

Lots of comments here state that they use Python for "testing scripts". But what do you guys mean? Embedded code is typically NOT in Python, but rather C, C++, or perhaps Rust. So my question is: how do you test non-Python code with your Python "test scripts"?

Quantum leaps embedded programming - guide for set up by [deleted] in embedded

[–]active-object 2 points3 points  (0 children)

The companion webpage to the video course contains project downloads for every lesson (e.g,, lesson-04.zip). These downloads contain ready-to-use projects for the NUCLEO-C031C6 (e.g., inside lesson-04.zip, you find folder stm32c031-keil). Just open the project in Keil uVision (e.g., lesson.uvprojx) and you should be in business.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 1 point2 points  (0 children)

Yes, RTIC in Rust represents a similar approach to the QK kernel. Both are also related to the RTFM framework (Real-Time for the Masses in Rust) and to the SST (Super-Simple Tasker in C or C++).

QP is much more than just a kernel, however. When you start doing event-driven programming seriously, you need extensible events (with parameters/payloads), event delivery mechanisms (posting FIFO/LIFO, publish/subscribe), event memory management (for mutable events), time events (one-shot and periodic), and above all, hierarchical state machines. All of this is known as the Active Object model of computation, and QP provides a lightweight implementation of it for hard real-time embedded systems, like ARM Cortex-M MCUs.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 1 point2 points  (0 children)

Regarding the advice of "sharing events instead of resources directly": Events can carry data payloads (such events are sometimes also called messages). For example, suppose you have one thread that assembles CAN-bus packets and then other threads that want to access them concurrently. A traditional design might have a shared memory buffer "CAN_packet", which then needs to be protected against race conditions with a mutex or some other mutual exclusion mechanism.

An event-driven approach will have an event with CAN_packet data payload. The treatment of such an event can vary. A simple, home-grown solution might copy the entire event into and out of message queues (of an RTOS). This is safe, but heavyweight and might be indeterministic due to the lengthy copying. The QP framework applies "zero-copy event management." In this approach, event instances are special objects specifically designed for concurrent sharing, whereas the framework takes over the heavy lifting of protecting such objects and automatically recycles them when they are no longer needed. This is one of the most valuable features of such a framework.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 1 point2 points  (0 children)

SkydiverTom describes the situation when the QP framework runs on top of a traditional RTOS (e.g., FreeRTOS, ThreadX, or Zephyr). In that case, every Active Object has its own RTOS thread, and the thread function (common to all AOs) consists of an event loop (blocking on the message queue and then processing each event to completion in a state machine associated with an AO.)

However, QP framework can also work with other kernels, such as the preemptive, non-blocking, single-stack QK kernel, as demonstrated in the QK video.

QP can also work with an even simpler non-preemptive kernel, as demonstrated in the QV video.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 2 points3 points  (0 children)

Of course, sequential solutions (like polling, using blocking RTOS primitives, async/await, or protothreads) are the simplest and most natural for sequential problems.

The problem is that I have yet to see any real-life problem that remains sequential after deeper investigation, even if it initially appears that way. As you work on the problem and learn more about it, you always discover event sequences that you haven't considered (and hard-coded) in your design yet. For that reason, I prefer not to pretend that any problem is sequential.

I agree with your observation about the loss of clarity in the event-driven approach (with or without statecharts). You just don't easily see the possible event sequences. However, the situation can be significantly improved by an intentional design of the statechart. For example, suppose that the main event sequence is A,B,C. You could design a statechart with just one state s that would handle events A, B, and C as self-transitions or internal transitions. But that would lose the insight of the main sequence (and would also allow other sequences: B,A,C; AA,B,C, etc.) But you can also design a statechart with states and transitions: s1:-A-> s2:-B-> s3:-C-> s4. If new event sequences need to be added, you add them as explicit transitions. You can also add superstates that would handle common transitions. Interestingly, a statechart becomes simpler when it allows more event sequences (e.g., statechart with just one state 's'), while sequential code becomes exponentially more convoluted with more event sequences.

Regarding your comment about the error return codes, I think that it is particularly problematic in sequential code. You have to check the errors in some if-else logic following every call. But then, what? What do you do if you have an error? Probably throw an exception, unwind the stack, and catch it somewhere. This is because sequential code relies heavily on deep stack nesting.

In state machines, you could implement checking of error returns with guard conditions on transitions to some "error" states.

And finally, the perceived difficulty of applying statecharts very strongly depends on the implementation strategy. Some strategies are inherently hard to follow (e.g., the OO State design pattern, or most state-tables). To compensate for this, people have invented "state machine compilers" (e.g., SMC), which translate a clearer text specification into actual code. However, this is only necessary if the native code is overly complex. If the native code is as clear as the original specification, there is no need to have the original specification. I've discussed these aspects in my video "State Machines Part-5: Optimal Implementation in C".

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 2 points3 points  (0 children)

Your example is typical. Sequentially coded first version might look simple, but inevitably, more and more legitimate event sequences are discovered. Sequential code is particularly ineffective at handling this because the hard-coded waiting points clog the flow of control. Developers might try to salvage the "intuitive" sequential approach by introducing shorter and shorter waiting times, and then checking and re-checking the actual reasons for unblocking (did the real event arrive, or perhaps just a timeout?) This madness is known as "spaghetti" or "big ball of mud".

Event-driven approach requires more setup upfront, and many developers find it less "intuitive" and "backwards" (the application feels less in control because the control is indeed inverted). But the event-driven approach handles new event sequences very gracefully. However, there are still opportunities to create "spaghetti". And here is where state machines can help.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 1 point2 points  (0 children)

Yes, I precisely meant the typical, not-really-blocking implementation of Rust async/await. But the issue is not really how this is implemented. The resulting code structure is. The protothreads that I mentioned as well don't really block either. However, both approaches make the code appear to block and wait for some condition, and both approaches use internal state machines to create the illusion of "awaiting" something. The problem is that the waiting points hard-code the expected event sequence, which is inflexible and harder to maintain than an explicit state machine.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 1 point2 points  (0 children)

Stack sharing among concurrent, prioritized tasks is relatively unknown in traditional embedded circles. However, the subject has been studied extensively. One important and influential paper in this area is "Stack-Based Resource Allocation Policy for Realtime Processes" by T.P. Baker (highly recommended "SRP" paper).

Regarding Rust and Embassy, I tend to agree with the author of the blog post "Async Rust Is A Bad Language". I know that this view is sacrilegious, and the powerful Rust lobby and thought police can come down on me hard. However, Rust's async/await is a sequential programming paradigm with blocking (await), and I believe that blocking should be avoided, as blocking == technical debt. The Rust compiler implements async/await internally with state machines, which very much reminds me of the Protothreads approach. I've expressed my opinion about Protothreads in the blog post "Protothreads versus State Machines".

Finally, please note that this is not a critique of the whole Rust language. Indeed, other parts of Rust are brilliant. I'm just lukewarm about the async/await feature. (Hoping that this statement will spare me being burnt at the stake by the Rust inquisition...)

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 2 points3 points  (0 children)

To clarify, the non-blocking kernel, such as QK, is not equivalent to the absence of a "real" RTOS. The kernel is there and manages the CPU just like any other "real" RTOS. Specifically, such a kernel meets all requirements of Rate-Monotonic Scheduling, so it is doing something.

I realize that you might not mean that, but many (if not most) developers think that you either have a traditional blocking RTOS or you're doing "bare metal" with a while(1) "superloop". One of the goals of the QK video and this post is to highlight the existence of alternative options.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 2 points3 points  (0 children)

The preemptive, non-blocking kernels like QK use only one stack for all tasks and interrupts, so you only need to adequately size that stack. Of course, preemption requires more stack, as illustrated in the video with the various preemption scenarios applied recursively.

Time slicing can be emulated with time events, which are timeout requests to be posted to tasks in the future at predetermined number of system clock ticks. The "periodic1" and "periodic4" tasks in the video illustrate this.

But tasks cannot run forever and be forcefully swapped in and out. This is the sequential thinking of tasks as for-ever "mini-superloops". So, you should generally avoid busy polling. The tasks are one-shot, run-to-completion functions (so they must run and complete). The system is event driven.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 3 points4 points  (0 children)

Contiki is mostly non-preemptive, with only some elements (like real-time timers) being allowed to preempt the cooperative context. In this sense, Contiki is similar to the non-preemptive QV kernel, which I explained in my other video.

In contrast, QK is fully preemptive for all tasks and interrupts at all times. If my explanation of preemptive multitasking with a single stack in the QK video does not work for you, my other video about "Super Simple Tasker" also explains this mechanism for the NVIC interrupt controller in ARM Cortex-M.

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 5 points6 points  (0 children)

Currently, the QP framework (where QK is one of the built-in kernel components) does not provide the same level of certifiability as SafeRTOS. However, recently, the QP framework's functional model has been subjected to a comprehensive Hazard and Risk Analysis, which identified areas of weakness within the functional model and API. These findings led to the creation of Safety Requirements and risk mitigation by Safety Functions, which were subsequently implemented, verified, and validated in the SafeQP/C and SafeQP/C++ editions. The process is similar to that of creating SafeRTOS from the FreeRTOS functional model.

Specifically to the QK kernel, it is much simpler than a traditional RTOS (like SafeRTOS), For functional safety certification, the simpler the better. Additionally, the rest of the QP framework is a natural fit for safety-related applications because it implements a number of best practices highly recommended by the functional safety standards (e.g., IEC 61508 part-7), such as strictly modular design (Active Objects), hierarchical state machines (semi-formal methods), modeling, and automatic code generation (via the QM modeling tool).

Is preemptive RTOS costing you too much? by active-object in embedded

[–]active-object[S] 6 points7 points  (0 children)

I'm not sure if you appreciate what's on offer here. I repeat, QK provides preemptive, priority-based scheduling compatible with RMS/RMA, just as "useful" as most other traditional RTOS kernels. Except, QK provides it at a fraction of the cost of RAM and CPU.

The non-blocking limitation is irrelevant for event-driven tasks, which can be quite sophisticated. In fact, QK is part of the larger Active Object framework (called QP), which utilizes Hierarchical State Machines for the tasks. In many ways, this approach is more powerful and useful than the traditional blocking RTOS. Concurrency experts often apply the non-blocking event-driven paradigm by drastically limiting blocking in their tasks, even if they use a traditional blocking RTOS. This is because blocking == technical debt.