The Cortex-M4 processor addresses application areas requiring a blend of traditional microcontroller functionality and digital signal processing - this is often called a Digital Signal Controller.
One of the optional features which a licensee of the Cortex-M4 can include in their design is a powerful Floating Point Unit (FPU).
This document gives 10 tips on how best to use the FPU on a Cortex-M4 processor.
FPU is an optional feature on the Cortex-M4 processor. Some microcontrollers with Cortex-M4 processor do not have an FPU, so please check the datasheets carefully. If the FPU is not present, then most toolchains include an option to emulate floating point using integer operations in their C run-time library.
Floating point numbers can be single precision (“float” in C) or double precision (“double” in C). The FPU in the Cortex-M4 processor supports single precision operations, but not double. If double precision calculation is present, the C compiler will use runtime library functions to handle the calculation in software. For best performance, it is best to handle the calculation in single precision if possible. Note that most compilers will emulate double precision using integer operations (not single precision FP).
There can be cases where you accidentally used a double precision calculation and you didn’t know it. This can be due to implicit widening of types required by the C language standard. Therefore it is useful to check the output from compilation process to see if it is calling double precision runtime functions. The exact way to do this is toolchain-dependent, so you need to check with the tool’s documentation. One common way is to generate a disassembled listing of the compiled image and see what is inside. Some compilers might have an option to force every floating point operation to single precision only, or generate notification messages when double precision calculation is used.
Floating point calculations are performed on a separate register bank inside the floating point unit. If both the main thread (e.g. main program) and interrupt service routines (ISR) use the FPU, extra context saving and restoring are required to ensure that the ISR does not corrupt the data used by the main thread. The extra context saving and restoring requires extra clock cycles, and therefore if you want to have fast ISR response time, one way is to avoid floating point calculations inside an ISR. In this way, the stacking and unstacking of FPU registers is avoided using a feature called Lazy Stacking (see Arm application note AN298).
When floating point operations are carried out in thread mode and an interrupt occurs, the Lazy Stacking (see #4) feature reserves space for the FPU registers on the stack so that they can be pushed onto the stack later if necessary. So you need to check the stack size allocation to make sure there is enough space to accommodate the larger stack frame (26 words instead of 8 words).
Many toolchains provide multiple choices of C runtime libraries for different processing requirements. For example, in the Arm C compiler/Keil MDK, you can select between standard C library (for higher performance) or MicroLib (for smaller size). In gcc, you can also have the option of NewLIB or NewLib-Nano (small memory footprint library). In most cases, these size-optimized libraries provide all the features required in most embedded applications. However, be aware that the runtime library optimized for size might not have full IEEE754 support (e.g. corner cases for NaN etc).
The CMSIS header files provide a C-level abstraction of the underlying Cortex-M4 core.
Please note that the CMSIS-Core header files use two C macros:
The FPU must be enabled before any FPU instruction is executed, otherwise a hardware exception will be raised.
If your application does not need to handle any floating point calculations, then you can leave the FPU switched off all the time by not defining the __FPU_USED macro. This can reduce power consumption. In some applications, you might only need to use the FPU for a short time and then can switch it off again when the FPU operations are completed. However, in this case you should create a HardFault or UsageFault handler to check fault status and re-enable the FPU in case any floating point code is executed accidentally when the FPU is disabled.
Even if you have the FPU in your microcontroller, some floating point calculations (e.g. sine, cosine) still need to be handled by C runtime library functions. In those cases parameters and results must be passed between the program code and C runtime library functions. Be aware that there are two options in the ABI for the Arm architecture, namely hard ABI and soft ABI. In the hard ABI values are passed via the FPU registers, and in the soft ABI values are passed via integer registers. If you are creating a code library that need to run on multiple targets that may or may not have FPU, you should use the soft ABI. More details on this topic is covered in chapter 13 of “The Definitive Guide to Arm Cortex-M3 and Cortex-M4 Processors, 3rd edition”.
The IEEE-754 standard defines several rounding modes for floating point calculations. The FPU in the Cortex-M4 supports:
You can use the “fesetround” function to select which rounding mode to use.
This information is very valuable to me. Thank you so much Joseph!
Often it won't pay to use the FPU for copying data; if you have just a single M4 register available, it would be quicker to just repeat LDR/STR than loading multiple FPU registers. But if you've run completely out of integer registers, it might pay to use a list of floats.
I'd like to give other readers a hint:
If you don't use the FPU anyway, you could initalize a structure and read it into a list of floating point registers ("ink the stamp").
Then you could use these registers as a 'data-stamp'. For most people, this would be initializing arrays.
For people like me, I'll keep at least one or 2 'data-stamps' ready, I'll reserve space for copying data and also a few registers for normal float calculations if I would ever need it.
Hopefully someone will benefit from this post.
It is fine to use floating point register to store values that is not a valid floating point number. The check for validity of floating point format takes place when you try to carry out a floating point calculation, but not for data transfers or memory accesses. So the data will not be modified provided that you limit the operations to data transfers.
In fact, sometime C compilers make use of floating point registers for non-floating point processing (but this could affect interrupt latency as you might need to save/restore more registers.)
Very good article.
What happens if I load a value, which is not a valid floating point value, into a floating point register ?
What I'm getting at, is that in some cases it might pay to use the FPU for copying or zeroing blocks of memory.
Excellent points Carlos.
The "-Wdouble-promotion" was added in gcc 4.6 (GCC 4.6 Release Series — Changes, New Features, and Fixes- GNU Project - Free Software Foundation (FSF)), and the descriptions can be found here: Warning Options - Using the GNU Compiler Collection (GCC)
The description on option "-fsingle-precision-constant" can be found here:Optimize Options - Using the GNU Compiler Collection (GCC)
Regarding "# 3 Check the compiler’s output": for GCC, one may use the options "-Wdouble-promotion" and "-fsingle-precision-constant" to help deal with the widening you speak of.