Freddie Chopin

[C++11][Cortex-M] - distortos - object-oriented C++ RTOS for microcontrollers

Discussion created by Freddie Chopin on Mar 30, 2015
Latest reply on Sep 19, 2017 by Freddie Chopin
Hello!

I finally decided to share some info about a project I've been doing for the past 8 months. Currently it can be considered "alpha" or maybe "early beta" stage, but - despite literal meaning of these terms - the things that are already done (and there's quite a lot of them) work pretty well and are thoroughly tested. The only architecture supported at the moment is ARMv7-M, so ARM Cortex-M3 and ARM Cortex-M4(F). The only "officially" supported chip is STM32F407VG (the one found on the first STM32F4DISCOVERY board).

The whole project is written in C++11 and there is no C API. Such C API is of course planned and it will be pthread, maybe one more, a bit more "native", which would allow better/easier access to unique features of this project. Anyway - the C++11 API that is currently available aims for 100% compatibility with POSIX (in features, not in names). It is worth noting that the code DOES NOT use dynamically allocated memory, so EVERYTHING (especially threads and their stacks) can be constructed as local variables (on stack), global variables or however you like it.

Another important thing worth noting is that this project doesn't aim to be "the smallest" and/or "the fastest" RTOS. As the time passed (and as powerful chips with with lots of RAM got more popular and cheaper (; ) I came to a conclusion that all extreme measures taken in other projects just to save a few clock cycles or 10 bytes of memory are not justified at all. What I'm saying here doesn't mean that this code runs pathetically slow and uses whole available memory (; I just don't devote all my attention to such optimizations - I focus on these matters in the "right" proportion (;

Here's a link to project's official website:
http://distortos.org/

Here is a link to github repository:
https://github.com/DISTORTEC/distortos

And here's an article I wrote on my page about the current state of this project:
http://www.freddiechopin.info/en/articles/34-news/94-distortos-7-miesiecy-i-0x3ff-commitow (it is also available in Polish).

I'm pasting the article below, but anyone interested is encouraged to visit the link above, because the original text has a lot of clickable links to relevant source files in the repository.

--- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 ---

distortos - 7 months & 0x3FF commits!

(Sunday, 22 February 2015 20:52)

distortos project - mentioned in previous news article - is still evolving. Today it is exactly 7 months since the first commit in the repository, and the total number of these commits reached a nice value of 1023. Many readers - at least those who are a bit into RTOSes for microcontrollers - may wonder "who needs another RTOS project?". Answer to this question is very complex and I could write a separate article about that, so for now a link to README file - in which I tried to explain the main reasons for this project's existence - must suffice. I think that at least the huge emphasis placed on C++ and C++11 support makes this project worthwhile, although this feature should not diminish other advantages (some of which are of course "future" (; ), important for people who don't intend to use C++ (this feature is one of the "future" ones for sure, as for now only C++11 API is available).

There are still many features missing, but these 7 months were well spent - quite a lot was done, and these things work remarkably well (;

The most important features are obviously threads (Thread, StaticThread and ThreadBase classes), which can accept any number of arguments of any type - thanks to new features of C++11 standard - not only the void* known from other RTOSes. Moreover - regular functions, member functions, functors or lambdas can be used as thread's function.

All of these features are also present in software timers (SoftwareTimer class).

Scheduler supports full preemption based on thread's priority (there are 256 priority levels), and when several threads with the same priority are present, they are scheduled according to FIFO or round-robin algorithm (selected for each thread individually).

Semaphore (Semaphore class) is probably the most basic synchronization object. It is possible to configure maximal value the semaphore can "have", so this object can also be used as binary semaphore. Semaphores can be "posted" from interrupts.

Mutex (Mutex class) is one of more advanced synchronization mechanisms. Just as in POSIX standard, it can be configured to use any of the three "modes" - normal, error-checking or recursive - and any of the three "protocols" - normal, priority inheritance or priority protocol (also known as priority ceiling). Unlike many other RTOSes, priority inheritance protocol works with no limitations: through any number of inheritance "levels" and with any number of mutexes locked by threads in any order (diagram and description of test case of this particular feature).

Classic condition variable (ConditionVariable class) is a mechanism which extends functionality of mutexes.

Queues can be used for standard communication between threads and interrupts (in any combination). There are four variants of queues:
- without priorities, without support for objects (RawFifoQueue and StaticRawFifoQueue classes),
- without priorities, with support for objects (FifoQueue and StaticFifoQueue classes),
- with priorities, without support for objects (RawMessageQueue and StaticRawMessageQueue classes),
- with priorities, with support for objects (MessageQueue and StaticMessageQueue classes).

Messages added to queues which don't support priorities (...FifoQueue) are ordered in FIFO order, while queues which do support priorities (...MessageQueue) allow adding messages with one of 256 priority levels. Queues without support for objects (...Raw...) copy the messages with memcpy() function, while queues with support for objects use all operations required in such scenario (construction, destruction, assignment operator, swap, emplace, ...).

All blocking operations are also available in non-blocking variants and in blocking versions with timeout (absolute or relative), which - thanks to C++11 std::chrono - can be easily expressed in any unit (like seconds, minutes, hours, ... - link), not only in system "ticks".

All errors are signaled with error codes - functions use neither errno variable, nor C++ exceptions.

The only supported architecture - for now - is ARMv7-M, so ARM Cortex-M3 and ARM Cortex-M4(F), and the only "officially" supported chip is STM32F407VG (known from STM32F4Discovery board), although using any different chip with proper core would be trivial.

I saved the best part (I hope) for the end - use of all functionalities mentioned above is possible without dynamic memory allocation. All previously described objects can be constructed as global variables or on stack.

I'm sure I forgot to mention some more interesting features...

Feel encouraged to test the code, ask questions, post comments, send suggestions and cooperate (; Stay tuned!

--- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 --- >8 ---

And finally - a small news article with a teaser - http://www.freddiechopin.info/en/articles/34-news/93-work-in-progress

The simplest thread doesn't require ANY configuration, because main() function is already a thread! So here's the immortal LED blinker in the shortest form:

01.#include "distortos/ThisThread.hpp"
02.  
03.int main()
04.{
05.    // --- configure your GPIO pin here ---
06.  
07.    while (1)
08.    {
09.        // --- toggle the state of GPIO here ---
10.  
11.        distortos::ThisThread::sleepFor(std::chrono::milliseconds(500));
12.    }
13.}

Compilation result

text      data       bss       dec       hex   filename
4924       100      4840      9864      2688   ./output/distortos.elf

2kB of RAM is the stack for main() and another 2kB is the stack for interrupts. These two numbers are obviously too much for such a simple program, so they can be reduced with no problems.

If you'd like to have a separate thread it's still pretty simple:

01.#include "distortos/StaticThread.hpp"
02.#include "distortos/ThisThread.hpp"
03.  
04.void blinkFunction()
05.{
06.    // --- configure your GPIO pin here ---
07.  
08.    while (1)
09.    {
10.        // --- toggle the state of GPIO here ---
11.  
12.        distortos::ThisThread::sleepFor(std::chrono::milliseconds(500));
13.    }
14.}
15.  
16.int main()
17.{
18.    auto blinkThread = distortos::makeStaticThread<512>(1, blinkFunction);
19.    blinkThread.start();
20.    return blinkThread.join();
21.}

Size of stack for the thread (512 bytes) and its priority (1) are not based on any deep reasoning - just some two values that will make the code work in an expected way (unless the GPIO configuration or toggling would use functions that use a lot of stack). The priority can be changed in 0-255 range, but 0 is not a very good option, as it's the priority of idle thread.

text      data       bss       dec       hex   filename
6084      1144      4880     12108      2f4c   ./output/distortos.elf

Most of the used RAM (almost everything above the 2kB + 2kB for stacks mentioned above) is used by newlib's module with maloc() and free(), which is pulled into the link because of free(). This code doesn't allocate a single byte of memory from the heap.

Regards,
FCh

Outcomes