-
Notifications
You must be signed in to change notification settings - Fork 38
Home
Motate is a bare-metal framework for multiple processor architectures. Motate is designed to be both easy-to-use (and thus easy to maintain) while at the same time being as high-performance as possible across all supported architectures.
For a quick tutorial, see the Quick Start Guide page.
For more complete documentation, see the Motate API page.
Motate is a C++-based framework. However, we've been very careful to select just the good parts of C++, and leave out all that other stuff.
By good parts we mean the parts that allow more expressive code with less "wizardry," and the parts that make sense on a bare-metal embedded system. Every addition to the framework is scrutinized carefully to ensure that, when compiled and optimized, it will make the smallest and/or fastest code it possibly can.
We do this optimization in two phases:
We use the expressiveness of C++ (and increasingly C++11-specific extensions) to help the compiler to understand our intent and use of variables. For example, we want anything that is a hardware peripheral and is known at compile time to be a compile-time object, but since there are often many that are very similar (such as pins), we use template specialization to create an object that is the same interface to all objects of that template, but the registers and masks used for that specific one will be known and decided at compile time.
This allows for a extreme amount of compiler optimization, while still having readable and maintainable code. It also makes it much easier to port the code from architecture to architecture.
For example, to setup an input pin with a pullup and an output pin for an LED, then make the led on when a button is held down:
Motate::InputPin<1> button {Motate::kPullup};
Motate::OutputPin<2> led;
void loop() {
// Assuming the pin is sinking the LED
// and the button is active low:
led = button;
// Same thing:
// if (button)
// led = true;
// else
// led = false;
}
This also allows what we call a Mirage Data Type. A Mirage Data Type has a documented common interface across supported architectures, which serves as the formal interface, but the implementation is designed as described above to "boil away" in the optimizer, leaving the bare minimum of code needed to accomplish the specific task requested -- many times a single operation to manipulate a processor register.
In the above example code, the creation of the button and led objects will generate code that runs at boot (before main()
) to do whatever is necessary for the button and led objects to function on the platform being compiled for. This includes setting up IO registers, enabling clocks, setting up masks, etc. In the decompiled code you will likely find that the setup of those two pins will have been merged into just a few register manipulations. The read of the button and the set of the led will likely be only two or three machine instructions total.
We compile then decompile the code and read the assembly. Many, many times we've been surprised at how the compiler finds optimizations and shortcuts that, had it been C or hand-written assembly would have made terrible and unmanageable code. When we find code that could have been better hand-written, we alter the way that we're "hinting" at the compiler to better allow it to optimize the output.