Using small C libraries in Mbed OS bare metal
We recommend using small C libraries with the bare metal profile. These are versions of the C standard library that do not include thread safety features; they are suitable for a non-RTOS profile like bare metal, and their size is much better suited for an ultraconstrained hardware.
Both the ARM
and GCC_ARM
toolchains support code-optimized versions of their C standard libraries, microlib
and newlib-nano
.
Building with the small C libraries
You can build with the smaller C libraries by creating an mbed_app.json
with the following contents:
{
"requires": ["bare-metal"],
"target_overrides": {
"*": {
"target.c_lib": "small"
}
}
}
This links your application with microlib
for the ARM
toolchain and newlib-nano
for the GCC_ARM
toolchain.
Note: If your application uses the Arm microlib, it should not return from main()
. Please see non-returning main() below for advice.
Newlib-nano
Newlib-nano is an open source C library targeting embedded microcontrollers. It is based on newlib but is much smaller. One restriction is that newlib-nano is not thread-safe, so an application that uses the RTOS should not use it.
Arm microlib
Microlib is an alternative library to the default C library. It is intended for deeply embedded applications that must fit into extremely small memory footprints.
These applications do not run under an operating system. You can find more information at the Arm developer documentation.
Differences between Arm C standard library and microlib
To see a complete list of the differences between microlib and the default C library, please see the Arm developer documentation.
In particular:
- Microlib has no re-entrant variant. Microlib does not provide mutex locks to guard against code that is not thread-safe.
- Microlib does not support selectable one- or two-region memory models as the standard library does. Microlib provides only the two-region memory model with separate stack and heap regions.
Mbed OS supports a two-region memory model for heap and stack. This means you can use the same scatter file with both the Arm C standard library and microlib.
Scatter file for Arm toolchain
By default, only a few targets have been tested with microlib. If your target has not been tested, the build system will throw an error. In that case, you need to check if the Arm scatter file for your target supports the two-region memory model. In other words, are the ARM_LIB_HEAP
and ARM_LIB_STACK
regions defined? This file is located in targets/.../device/TOOLCHAIN_ARM_STD/your_target_scatter_file.sct
):
- If yes, you can use the scatter file unmodified for microlib.
- If no, check if your target was ported to uARM:
- If yes, replace the
TOOLCHAIN_ARM_STD
scatter file with../TOOLCHAIN_ARM_MICRO/microlib_scatter_file.sct
. - If no, you need to update the scatter file to use the two-region memory model. You can find more information on the two-region memory model in the design document. For more details, see this example of a scatter file updated for the two-region memory model.
- If yes, replace the
After you have completed the steps above, add small
to the supported_c_libs
parameter for your target in targets.json
:
"supported_c_libs": {
"arm": ["std", "small"],
"gcc_arm": ["std", "small"],
"iar": ["std"]
}
Non-returning main()
Arm microlib doesn't support returning from main()
; attempting to return from main()
causes a bare metal application to crash. Here we show two ways to prevent this.
Sleeping in a loop
One recommended technique is to sleep in a loop at the end of main()
:
while (true) {
sleep();
}
This is energy efficient compared to an empty while (true) {}
loop, which keeps the processor running. A loop is still needed, because sleep()
returns after the system is woken up by an interrupt.
Dispatching an EventQueue
If your application is based on an EventQueue
, dispatching it at the end of main()
works as well:
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed_events.h"
#include <stdio.h>
int main()
{
// creates a queue with the default size
EventQueue queue;
// events are simple callbacks
queue.call(printf, "called immediately\n");
queue.call_in(2000, printf, "called in 2 seconds\n");
queue.call_every(1000, printf, "called every 1 seconds\n");
// events are executed by the dispatch_forever method
queue.dispatch_forever();
}
The call to queue.dispatch_forever()
never returns, as long as you don't break the dispatch anywhere. The EventQueue
class puts the system to sleep to save energy between events.
Note on uARM toolchain
The uARM toolchain is the ARMC6 toolchain with the Arm microlib, the C micro-library. This toolchain will be deprecated after 5.15.