Building Bare-Metal ARM Systems with GNU: Part 4 - Compiler options for C and C++
(07/17/07, 12:05:00 H EDT)
Editor's note: In this series of ten articles Miro Samek of Quantum Leaps details developing apps on the ARM processor using QNU, complete with source code in C and C++.
Earlier in this series he provided the basics of the design (Part 1), a simple LED blinker application; the generic startup code and low level initialization (Part 2) and the linker script for the C/C++ ARM-9 based Blinky application that causes four user LEDs to light up on the Atmel AT91SAM7S-EK (Part 3). In this part I describe the C and C++ compiler options that allow freely mixing ARM and Thumb code, as well as supporting fine-granularity code sections for functions. The code accompanying this article is available online at Embedded.com's Downloadable Code page
Compiler Options for C
Listing 1 above shows the most important compiler options for C, which are: (1) "mcpu option specifies the name of the target ARM processor. GCC uses this name to determine what kind of instructions it can emit when generating assembly code. Currently, the ARM_CPU symbol is set to arm7tdmi. (2) "mthumb-interwork allows freely mixing ARM and Thumb code (3) "mlong-calls tells the compiler to perform function calls by first loading the address of the function into a register and then performing a subroutine call on this register (BX instruction). This allows the called function to be located anywhere in the 32-bit address space, which is sometimes necessary for control transfer between ROM- and RAM-based code. Note: The need for long calls really depends on the memory map of a given ARM-based MCU. For example, the Atmel AT91SAM7 family actually does not require long calls between ROM and RAM, because the memories are less than 25-bits apart. On the other hand, the NXP LPC2xxx family requires long calls because the ROM and RAM are mapped to addresses 0x0 and 0x40000000, respectively. The long-calls option is safe for any memory map. (4) "ffunction-sections instructs the compiler to place each function into its own section in the output file. The name of the function determines the section's name in the output file. For example, the function Blinky_shift() is placed in the section .text.Blinky_shift. You can then choose to locate just this section in the most appropriate memory, such as RAM. (5) "O chooses the optimization level. The release configuration has a higher optimization level (5b). (6) the release configuration defines the macro NDEBUG. Compiler Options for C++
The C++ Makefile located in the directory cpp_blinky uses the same options as C discussed in the previous section plus two options that control the C++ dialect: (1) "fno-rtti disables generation of information about every class with virtual functions for use by the C++ runtime type identification features (dynamic_cast and typeid). Disabling RTTI eliminates several KB of support code from the C++ runtime library (assuming that you don't link with code that uses RTTI). Note that the dynamic_cast operator can still be used for casts that do not require runtime type information, i.e. casts to void * or to unambiguous base classes. (1) "fno-exceptions stops generating extra code needed to propagate exceptions, which can produce significant data size overhead. Disabling exception handling eliminates several KB of support code from the C++ runtime library (assuming that you don't link external code that uses exception handling). Reducing the Overhead of C++ This is because the standard new and delete operators throw exceptions and therefore require the library support for exception handling. (The new and delete operators are used in the static constructor/destructor invocation code, so are linked in even if you don't use the heap anywhere in your application.) Most low-end ARM-based MCUs cannot tolerate 50KB code overhead. To eliminate that code you need to define your own, non-throwing versions of global new and delete, which is done in the module mini_cpp.cpp located in the directory cpp_blinky1.
Listing 3 above shows the minimal C++ support that eliminates entirely the exception handling code. The highlights are as follows: (1) The standard version of the operator new throws std::bad_alloc exception. This version explicitly throws no exceptions. This minimal implementation uses the standard malloc(). (2) This minimal implementation uses the standard free(). (3) The function __aeabi_atexit() handles the static destructors. In a bare-metal system this function can be empty because application has no operating system to return to, and consequently the static destructors are never called. Finally, if you don't use the heap, which you shouldn't in robust, deterministic applications, you can reduce the C++ overhead even further. The module no_heap.cpp provides dummy empty definitions of malloc() and free(): Next, in Part 5, I'll describe the options for fine-tuning the application by selective ARM/Thumb compilation and by placing hot-spot parts of the code in RAM. Stay tuned. To read Part 1, go to What's need to get started. To download the C and C++ source code associated with this article series, go to Embedded.com's Downloadable Code page, or go to Blinky for C and Blinky for C++ to download the Zip files. Miro Samek, Ph.D., is president of Quantum Leaps, LLC. He can be contacted at miro@. References [5] GNU ARM toolchain. |
|
来自: lao_o > 《Embedded》