Designing embedded software

We're listing here all the topics you should be familiar with before being able to efficiently contribute to the project. Those are not hard requirements, but we believe it would be more efficient if you got familiar with the following concepts.

C++

The choice of a programming language is a controversial topic. Not all of them can be used to write an operating system, but quite a few can. We settled on C++ for several reasons:

Of course knowing a tool means knowing its limits. C++ isn't exempt of defaults:

If you want to contribute to Epsilon, you'll need to learn some C++.

Working with limited memory

Our device has 256 KB of RAM. That's very little memory by today's standards. That being said, by writing code carefuly, a huge lot can be achieved in that space. After all, that's 64 times more memory than the computer of the Apollo mission!

Stack memory

The stack memory is possibly the most used area of memory. It contains all local variables, and keeps track of the context of code execution. It can be overflowed in case of nested function calls if the reserved space is too small. We booked 32KB for the stack.

Heap memory

Unfortunately, local variables can't answer all use cases, and sometimes one need to allocate memory that lives longer than a function call. This is traditionally done by using a pair of malloc/free functions.

This raises a lot of potential problems that can trigger unpredictable dynamic behaviors:

Memory leaks
If one forgets to free memory that is no longer used, the system will eventually run out of available memory.
Memory fragmentation
Memory allocation has to be contiguous. So the allocation algorithm has to use a smart heuristic to ensure that it will not fragment its allocated space too much.

Some automatic memory management solutions do exist (garbage collection, smart pointers), but they all come with a cost. We decided to manually manage dynamic memory, but to use it as sparingly as possible.

Writing code that runs on the bare metal

Unlike code that runs inside of an operating system (pretty much everything these days), an embedded firmware doesn't make use of virtual memory.

In practice, this means that the firmware will need to know in advance how the memory space is laid out. In other words, it will need to answer those questions:

The firmware will also need to take special care of the system initialization. There is no such thing as a "main" function on a firmware. Instead, on Cortex-M4 devices, after reset the CPU simply jumps to the address contained at address 0x00000000 (which happens to be the first bytes of flash memory). So if your firmware starts by 0x12345678, code execution will start at address 0x12345678.

Enforcing such a careful memory layout would be an impossible job without the proper tool. Fortunately, embedded linkers can be scripted and allow this kind of tailor-made configuration. You'll find Epsilon's linker script in "ion/src/device/boot/flash.ld" - it is heavily commented and should be self-explanatory.

That being said, there are additional things the OS usually takes care of which we need to do ourselves : for example, initialize global variables to zero. This is done in the "ion/src/device/boot/rt0.cpp" file, which is worth reading too.