docs
docs copied to clipboard
Add example fros cross-building for embedded (bare metal) devices
New page in this section: https://docs.conan.io/2/examples/cross_build.html
Things to take into consideration:
- Using a
gcc
for the genericarm-none-eabi
target - The current
cpu
setting may not be enough to cover the variability of the ARM instruction sets across different devices, this covers:-
M-profile
architecture - different SoCs may have different CPU features, and unlike x86_64 where most distributable binaries are built against a baseline configuration (the "lowest common denominator"), in embedded/low power devices one may want to use specific CPU features that are optional
-
- Other compiler flags are important, e.g. with the aim of reducing code size:
see discussion https://www.reddit.com/r/cpp/comments/165fi6f/comment/jyfhb1l/
@jaskij fyi
Thanks @jcar87 , I'll copy the info from my Reddit comment here, in several comments so it's easier to refer to.
First is the ISA. Instruction Set Architecture.
The way it is usually done in x86-64 is to have on binary made for the lowest common denominator, usually just x86-64, and for software which actually cares about performance do dynamic dispatch at runtime (see glibc, NumPy, OpenBLAS).
Such an approach doesn't work for embedded targets for multiple reasons, primary one being code size. Since code size actually impacts the choice of microcontroller, and the BOM cost of the product, there is a lot of incentive to minimize it. Other times it's about simply fitting into the microcontroller of choice.
Looking at just arm-none-eabi
, from what I have seen in my career this spans three ISA generations, and numerous options for specific cores. Starting with the ARMv6-M Cortex-M0, which doesn't have a hardware integer divider, and it's integer multiplication takes either 1 or 32 cycles depending on the choice of the vendor (this is a tune flag in GCC). Through Cortex-M7 which can have no FPU, a single precision one, or a double precision one. To new cores like M23 and M33.
As an example from my Cortex-M7 project:
-mcpu=cortex-m7 -mthumb -mfpu=fpv5-d16 -mfloat-abi=hard
-
-mcpu=cortex-m7
is the core used -
-mthumb
is Thumb mode (as opposed to ARM mode), not sure it's necessary as Cortex-M7 doesn't support ARM mode -
-mfpu=fpv5-d16
is the FPU selection (and this is somewhat independent of the core - a Cortex-M7 can have no FPU, a single precision one or a double precision one) -
-mfloat-abi=hard
is the ABI used for floating points, and I'd need to dig deep into GCC docs to know if it's necessary or not (docs state "default depends on the specific target configuration" - so I'd need to verify if the default is correct for my case to know if I can skip this, easier to just set it)
If I am downloading a binary package, I'd expect to be able to select the values of three of the four flags above (-mthumb can be set permanently, ARM mode is rarely, if ever, used).
Next are the non-ISA flags, these are much more static.
This is the contents of my common flags variable, and except the last two ones it's stuff I think most any embedded project uses.
-ffunction-sections -fdata-sections -ffreestanding -fno-builtin -fmerge-constants -fomit-frame-pointer -fstack-usage
-
-ffunction-sections -fdata-sections
- these two tell the compiler to put each function and object in it's own section, so that the linker can later do a GC pass and remove unused stuff, it has a huge impact on code size -
-ffreestanding
- this is the baremetal flag -
-fno-builtin
- I don't want GCC to replace standard library functions with builtins, for various reasons -
-fmerge-constants
- further size reduction -
-fomit-frame-pointer
- not like I can get core dumps or stack traces out of the device, so might optimize a little more -
-fstack-usage
- output stack usage data for analysis, I'd expect you to include those with the shipped binaries
Of those above, I think -fno-builtin
and -fomit-frame-pointer
are things that should be changeable, although people might want to change other oness too.
-fstack-usage
will either be a non-issue, or a huge thing, because it introduces a new type of file to ship.
For compilation flags, the last thing is optimization. On x86-64 you may expect to basically just ship either -Og
or -O2
and call it a day. It's much more varied in the embedded world.
-Os
is a flag that's often used in release builds. Debug is probably still -Og
. But there are projects which use -O0
or -O1
. Others use a custom set of optimization passes. Basically, anything goes.
People will also want to be able to select the compiler itself. It does matter, I have had to work around bugs in ARM's official toolchain myself.
There's also the thing with configuring libraries.
Libraries using configuration headers are covered in the discussion in: https://github.com/conan-io/conan/issues/6646
As another example, here's the set of configuration options I'm using with fmt, ones you wouldn't expect building for a regular OS.
add_definitions(
-DFMT_STATIC_THOUSANDS_SEPARATOR='.'
-DFMT_USE_FLOAT=0
-DFMT_USE_DOUBLE=0
-DFMT_USE_LONG_DOUBLE=0
-DFMT_USE_FLOAT128=0
)