ANTIRTOS: No RTOS Needed

Embedded programming is a tricky task that looks straightforward to the uninitiated, but those with a few decades of experience know differently. Getting what you want to work predictably or even fit into the target can be challenging. When you get to a certain level of complexity, breaking code down into multiple tasks can become necessary, and then most of us will reach for a real-time operating system (RTOS), and the real fun begins. [Aleksei Tertychnyi] clearly understands such issues but instead came up with an alternative they call ANTIRTOS.

The idea behind the project is not to use an RTOS at all but to manage tasks deterministically by utilizing multiple queues of function pointers. The work results in an ultra-lightweight task management library targeting embedded platforms, whether Arduino-based or otherwise. It’s pure C++, so it generally doesn’t matter. The emphasis is on rapid interrupt response, which is, we know, critical to a good embedded design. Implemented as a single header file that is less than 350 lines long, it is not hard to understand (provided you know C++ templates!) and easy to extend to add needed features as they arise. A small code base also makes debugging easier. A vital point of the project is the management of delay routines. Instead of a plain delay(), you write a custom version that executes your short execution task queue, so no time is wasted. Of course, you have to plan how the tasks are grouped and scheduled and all the data flow issues, but that’s all the stuff you’d be doing anyway.

The GitHub project page has some clear examples and is the place to grab that header file to try it yourself. When you really need an RTOS, you have a lot of choices, mostly costing money, but here’s our guide to two popular open source projects: FreeRTOS and ChibiOS. Sometimes, an RTOS isn’t enough, so we design our own full OS from scratch — sort of.

13 thoughts on “ANTIRTOS: No RTOS Needed

  1. I did something similar, around an event queue, and added several macros similar to the ones used in C coroutines to allow to write asynchronous functions. I used it for the firmware in my keyboard.

  2. I use some sensors that have their own ASICs and their readout is not constant time depending on the state of the ASIC when polling it. I just bypass this in my delays by logging diffing the millis before and after and subtract the time it took of my delays. that way the delay really is the delay I did intend, sans the senser readout lag.

    Might be a caveman solution in my own case, but I keep this header file in mind. Seems cool! It’s just, I don’t make great enough projects to need that precision yet. Wish I could see it in a video, my own work seems to be very basic.

    1. Why not just FreeRTOS+avoiding shared memory as much as you can? I’ve never had a problem with it and it’s widely used and trusted, and there are a few others that seem to be almost as trusted, and a commercial version that’s even safer…

    1. One of the biggest drawbacks of an RTOS is interrupt overhead. Managing of tasks is usually done from within an ISR and this increases ISR latency.

      On bigger systems, especially when you have multiple ISR priority levels this is not a big problem, but not for the smaller and simpler 8-bit architectures. Especially the Atmega’s don’t work nicely with an RTOS. Both because of it’s limited architecture, and it’s abundance of registers, which takes a lot of pushing and popping for a task switch. A simple system like this can help to keep your code organized. It’s much better then blinking a led with software delays and blocking all programming.

  3. This is fine for very short tasks, but for multistep tasks, you would have to queue each step after the trigger for each step, which means you would have to maintain some form of state machine. For many cases, it’s simpler and more maintainable to use coroutines such as protothreads.

    1. When you have a function pointer, you already have a state machine. The pointer always points to the current state, and you just change the pointer to another function for another state. I have built state machines this way, and it worked very nicely.

  4. ugh so

    the part i like is the idea of just hand-rolling something simple. one of the things that continues to delight me is that ideas that are novel at first might seem complicated but a lot of times you throw them into code and it becomes 10 or 20 lines, maybe 100 once you add in the framework around it.

    i’m gonna credit forth with giving me a real appreciation of this idea…in forth, you can use ‘IMMEDIATE’ words to essentially implement your own domain-specific language. no sophistication, just implementation. and i’ve run with this idea in a very unoriginal way, i’ve made a handful of pidgin forths for different (mostly embedded) projects. and it always amazes me how just a tiny effort on the framework — a “compiler” literally written in 70 lines of C — and suddenly the rest of the project feels like i’m writing in a high-level language. but without enduring any of the vagaries of a full-featured compiler or runtime library. it’s the to-the-metal experience i want but also comfortable at the same time!

    i have a perhaps-unjustified hatred of arduino (and SDKs and IDEs in general). you might call my hack ‘anti-high level language’ in the parlance of this article.

    the thing is, what makes these special, is that they’re so simple! from anywhere i stand, i can see all the way to the bottom and to the top. and i only reuse code in the most haphazard fashion. i don’t generalize. that’s the real forth lesson, that’s what Chuck Moore says when you invite him to speak to your club: don’t generalize. i love generalization. so i appreciate this reminder…just solve the problem in front of you in the simplest and clearest way possible. and then when you find out it’s really a different problem, you benefit from the transparency of your original code. your first draft doesn’t have all of the features or structure, but it also is so small and simple you can understand it and then throw it away in the rewrite. pluses and minuses but it’s a good approach to at least have in your toolbag.

    and that’s where this project goes off the rails imo. an anti-rtos should be a one-off. once it generalizes, once it becomes code you lug around from one project to the next, then it has to grow warts. it has to solve problems you haven’t seen yet. and it can’t. that’s the fundamental “anti-[big thing]” innovation…you can’t solve the problem that isn’t in front of you so don’t try. but this tries.

    on top of that…C++ yecht. no one can really read C++ templates. i believe that if Bjarne Stroustrup sits down to a weekend hack, he writes his templates by trial-and-error, a frustrating dialogue with the compiler’s verbose and opaque template-oriented error messages. and yeah it’s possible to know what C++ is doing, which templates are instantiated, what book-keeping, implicit destructor calls, exceptions, run-time type information, virtual functons, memory allocation, it’s possible to get to the bottom of any of it. but if you do, you defeat the purpose of C++. if you have to see to the bottom, you violate encapsulation.

    haha and it’s hard for me to not nitpick the C++. it seems like Aleksei is afraid of the new C++ features, a mindset i strongly agree with. but it means the work queues are dynamically-allocated with new[] and delete[], and it also accounts for the large size of this extremely simple framework. there’s 4 different implementations of the work queue — no arg, arg, delay no arg, delay arg. and the arg processing introduces copying questions (copying is the hardest question in all of C++). and it only supports a single arg. if it was a one-off, the simplicity would not be obscured by the redundant 4 implementations. and if it wasn’t C++, you’d pass a void* instead of a template and you’d dodge the copying question. so the upside is, the templates are very easy to read but the downside is that’s because they don’t give you a genuine expressive advatage.

    OTOH, cycles are cheap in 2024. why shouldn’t a tiny RTOS incur absolutely unnecessary memory management overhead?

    haha so it’s neat idea but i’d personally respect it a lot more if it was a one-off hack as part of a cat toy or something. in my mind, lightweight frameworks, anti-frameworks, are brilliant precisely because you throw them away. i might cut and paste but i would never genuinely re-use something like this.

    but maybe i’m entirely wrong and the point is to look at it and not to use it, and if that’s the case then really it’s a pretty good demonstration of just how easy this can be!

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.