Epsilon is a high-performance graphing calculator operating system. It includes eight apps that cover the high school mathematics curriculum.
First of all, you should learn how to build and run your very own version of Epsilon. Note that you don’t need an actual NumWorks calculator to do this. Indeed, Epsilon can be compiled as a standalone application that will run on your computer.
Epsilon’s code is comprehensive, as it goes from a keyboard driver up to a math engine. Epsilon is made out of five main bricks: Ion, Kandinsky, Poincaré, Escher, and Apps.
Ion is the underlying library that abstracts all the hardware operations. It performs tasks such as setting the backlight intensity, configuring the LED or setting pixel colors on the screen. It also answers to questions such as “tell me which keys are pressed” and “what is the battery voltage?”.
That library is in charge of doing all the drawing. It performs functions such as “draw that text at this location” or “fill that rectangle in blue”.
Escher is our GUI toolkit. It provides functionalities such as “draw a button” or “place three tabs named Foo, Bar and Baz”. It asks Ion for events and uses Kandinsky to do draw the actual user interface.
Poincare is in charge of parsing, laying out and evaluating mathematical expressions. You feed it some text such as sin(root(2/3,3))
and it will draw the expression as in a text book and tell you that this expression is approximatively equal to 0.01524.
Last but not least, each app makes heavy use of both Escher and Poincare to display a nice user interface and to perform mathematical computation.
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.
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++.
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!
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.
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:
We decided to avoid malloc
altogether and to use a mix of static allocation and a pool of relocatable garbage-collected nodes for manipulating mathematical expressions.
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-M 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/userland/flash/userland_shared.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/shared/boot/rt0.cpp file, which is worth reading too.