Design Con 2015

Building Bare-Metal ARM Systems with GNU: Part 4

Miro Samek, Quantum Leaps

July 17, 2007

Miro Samek, Quantum LeapsJuly 17, 2007

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
The compiler options for C are defined in the Makefile located in the c_blinky subdirectory. The Makefile specifies different options for building debug and release configurations and allows compiling to ARM or Thumb on the module-by-module basis.

Listing 1. Compiler options used for C project, debug configuration (a) and release configuration (b).

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 compiler options for C++ are defined in the Makefile located in the cpp_blinky subdirectory. The Makefile specifies different options for building the Debug and Release configurations and allows compiling to ARM or Thumb on the module-by-module basis.

Listing 2. Compiler options used for C++ project.

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++
The compiler options controlling the C++ dialect are closely related to reducing the overhead of C++. However, disabling RTTI and exception handling at the compiler level is still not enough to prevent the GNU linker from pulling in some 50KB of library code.

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 The mini_cpp.cpp module with non-throwing new and delete as well as dummy version of __aeabi_atexit().

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 read Part 2, go to  Startup code and the low level initialization
To read Part 3, go to The Linker Script.

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@quantum-leaps.com.

References
[1] GNU Assembler (as) HTML documentation included in the CodeSourcery Toolchain for ARM.
[2] IAR Systems, "ARM IAR C/C++ Compiler Reference Guide for Advanced RISC Machines Ltd's ARM Cores", Part number: CARM-13, Thirteenth edition: June 2006. Included in the free EWARM KickStart edition
[3] Lewin A.R.W. Edwards, "Embedded System Design on a Shoestring", Elsevier 2003.
[4] ARM Projects.

[5] GNU ARM toolchain.

Loading comments...

Most Commented

  • Currently no items

Parts Search Datasheets.com

KNOWLEDGE CENTER