ARM GCC

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

ARM-GCC bezeichnet die für ARM-Mikrocontroller konfigurierte Variante des C- und C++- Crosscompilers GCC.

In diesem Artikel geht es primär um den arm-none-eabi-gcc, der ein für embedded Systems optimiertes ABI (Application Binary Interface) hat. Für ARM-System, die ein eigenständiges OS (wie Linux) haben, gibt es den arm-elf-gcc.

Es gibt diverse fertige Binärdistributionen die für verschiedene Controller mit ARM-Kern verwendet werden können.

Bestandteile

Die Toolchain besteht aus mehreren Kommandozeilen-Programmen, die sich auf einfache Weise in einen Editor oder eine Entwicklungsumgebung einbinden lassen. Weit verbreitet ist die Verwendung von make zur Steuerung.

Die Bestandteile im einzelnen:

  • Binutils: Assembler, Linker und weitere Hilfsprogramme.
  • GCC: Der eigentliche C(++)-Compiler.
  • eine C-Standardbibliothek

Die vorherrschende C-Bibliothek ist die newlib (oder auch newlib-nano), für andere Optionen siehe hier.

Bei der Auswahl der Toolchain sollte beachtet werden, dass es größere Unterschiede bei den bereitgestellten C-Bibliotheken gibt. Die Sourcery Codebench Lite-Edition stellt z.B. keine Bibliotheken mit FPU-Unterstützung bereit, so dass trotz vorhandener FPU beim Cortex-M4 nur suboptimaler Code erzegt werden kann. Siehe [1] für ein kleines Beispiel und eine Erklärung.

Fertige GCC Binärdistributionen

Beim Einsatz des gcc in Verbindung mit in C geschriebenem startup-Code bei den Optimierungsleveln "-O2" und "-O3" muss zusätzlich "-fno-gcse" gesetzt werden, da ansonsten die von der CPU benötigte NVIC-Tabelle(n) und zugehörige Funktionen unter Umständen nicht so aussehen wie sie sollten.

Siehe auch ARM-GCC development resources im Forum.

Nutzung mit eigener Umgebung/Kommandozeile

Hier einige Hinweise wie man den GCC direkt verwenden kann (zB. mit selbstgebautem makefile), falls man das nicht von einer Entwicklungsumgebung machen lässt.

Compiler & Linker Flags

Die Flags, die festlegen, welcher Core verwendet wird, sind in der folgenden Tabelle beschrieben:

Option Cortex-M0 Cortex-M0+ Cortex-M3 Cortex-M4 Cortex-M7 Cortex-M33 Cortex-M23
CPU -mcpu=cortex-m0[1] -mcpu=cortex-m0plus[1] -mcpu=cortex-m3 -mcpu=cortex-m4 -mcpu=cortex-m7 -mcpu=cortex-m33 -mcpu=cortex-m23
Float ABI -mfloat-abi=soft -mfloat-abi=soft

-mfloat-abi=softfp

-mfloat-abi=hard

-mfloat-abi=soft
FPU -mfpu=fpv4-sp-d16 -mfpu=fpv5-d16 -mfpu=fpv5-sp-d16 / -mfpu=fpv5-d16
Instruction Set -mthumb

[1]Anmerkung: Es gibt für diese Cores 2 Optionen (für den Hersteller): diese Betreffen die Multiplikationsbefehle.

  • Die übliche Implementierung enthält einen 1-Takt-Multiplizierer
  • Für reduzierte Chipfläche kann auche ein 32-Takt-Multiplizierer ausgewählt werden

Um nun bei Multiplikationen mit Konstanten nicht die langsame 32-Takt-Multiplikation zu nutzen kann der Compiler angewiesen werden, dort Shifts oder Ähnliches zu generieren. Um dem Compiler das mitzuteilen wird der Parameter, der die CPU angibt, abgeändert: -mcpu=cortex-m0[plus].small-multiply

Zusätzlich zu diesen Flags gibt es noch die Maschinenunabhängigen Parameter, wovon hier nur einige wichtigen erläutert werden:

Option Erklärung
-W[all, extra] Lässt den Compiler Warnungen ausgeben.

-Wall gibt nur Standard-Warnungen aus, nicht alle! -Wextra erzeugt noch einmal mehr Warnungen.

-O[0,1,2,3,s,g] Wählt die Art der Optimierung.

-O0: keine Optimierung -O[1,2,3]: Optimierung auf Ausführungsgeschwindigkeit. Je höher die Stufe, desto aggressiver ist die Optimierung. Das führt unter Umständen zu beträchtlich größerem Code -Os: Optimierung auf Codegröße -Og: Optimierung, aber so, dass das Debugging nicht erschwert wird

-fdata-sections

-ffunction-sections

Teilt jeder Funktion/jeder Variable eine eigene Section zu.Damit kann der Linker mit der Option --gc-sections ungenutzte Funktionen entfernen. Siehe GCC:_unbenutzte_Funktionen_entfernen
-Wl,--gc-sections Der Linker verwirft unreferenzierte Sections und packt diese damit nicht ins Binary. Siehe GCC:_unbenutzte_Funktionen_entfernen
-g Erzeugt Debugging-Informationen.

Siehe auch GCC Debugging Options

-fno-rtti

-fno-exceptions

C++-Optionen

Teilt dem Compiler mit, dass - keine Runtime Type Information zu generieren sind - keine Exceptions benutzt werden und damit kein Overhead generiert werden muss.

-flto Link-Time-Optimizations:

Der Linker optimiert den kompletten Code. Da er nicht nur ein Source-File, sondern alle kennt, kann er optimieren, wo dem Compiler Informationen fehlen. Achtung: bei älteren Compiler-Versionen muss nach diesem Flag noch einmal die Optimierungsstufe angegeben werden.

Die GCC Dokumentation listet alle Parameter auf, auch Parameter speziell für ARM.

  • Siehe auch das Readme vom GCC-ARM-Embedded
  • Um das -flto -Flag verwenden zu können, muss der GCC LTO unterstützen. Beim GCC-ARM-Embedded ist dies ab Version 4.7-2013-q2-update der Fall.
  • Die LTO erkennt die ISR's und den Interrupt Vector möglicherweise als "unbenutzt" und optimiert sie daher weg. Dies kann durch Markierung der Funktionen & Variablen mit "__attribute__ ((used))" verhindert werden.
  • Alle Compileroptionen müssen auch beim Linken mit angegeben werden (ist in obiger Tabelle berücksichtigt), da auch dann u.U. Code generiert werden kann.

Startupcode & Linkerscript

  • Damit der compilierte Code an den richtigen Stellen im Controller landet (d.h. dem Flash) muss man dem Linker ein Linkerscript mitgeben. Dies geht per "-T pfad_zum_linkerscript.ld" an den Linker-Befehl. Das Script ist praktisch Controller-spezifisch, es gibt Beispiel-Scripte der Controller-Hersteller.
  • Damit beim Starten die richtigen Initialisierungen vorgenommen werden (wie globale Variablen und bei C++ Konstruktoren globaler Objekt-Instanzen) muss als erstes ein Startupcode laufen, der dann die main()-Funktion aufruft. Startupcode im allgemeinen ist meistens in Assembler geschrieben, aber die ARM-Architektur macht aber auch einfacheren C/C++-Code möglich. Auch für den Startupcode gibt es Beispiele der Controller-Hersteller.

Zusammen bieten die beiden Dateien der Anwendung ein Standard-C-Interface, d.h. man kann wie gewohnt globale Variablen verwenden und seinen Code in die main()-Funktion schreiben.

FPU der Cortex-M4F nutzen

Um die FPU zu nutzen, muss der Compiler per Flag dazu gebracht werden, FPU-Instruktionen zu generieren.

Außerdem muss vor Benutzung der FPU-Befehle die FPU aktiviert werden, dies geschieht typischerweise im Startupcode, bevor die main() -Funktion aufgerufen wird. Hier die entsprechenden Befehle, falls sie im verwendeten Startupcode nicht onehin schon enthalten sind:

/*FPU settings*/
 ldr     r0, =0xE000ED88           /* Enable CP10,CP11 */
 ldr     r1,[r0]
 orr     r1,r1,#(0xF << 20)
 str     r1,[r0]

In C/C++ unter Verwendung der CMSIS geht es so:

SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */

Weiterhin sollte die GCC-Distribution auch Laufzeitbibliotheken mit FPU-Unterstützung mitbringen (CodeBench lite und Yagarto werden ohne ausgeliefert, GCC-ARM-Embedded mit).

Am Beispiel der STM32F4 mehr dazu in diesem Thread: Floating Pointing Unit STM32F4

Links

  • Hier finden sich noch ein paar Tipps, für den 1. kann aber mittlerweile der GCC-ARM-Embedded direkt verwendet werden, da er jetzt LTO unterstützt (s.o.).
  • Thread: Unterschied arm-none-eabi-gcc und arm-elf-gcc