Forum: Compiler & IDEs gcc, CMSIS, newlib, cortex M3


von High Performer (Gast)


Lesenswert?

Hallo Leute,

ich bewege mich aktuell noch auf der toolchain-Ebene. Dabei kommen mehr 
und mehr Fragen auf.

gcc übersetzen geht, Assemblerausgabe des Compilers sieht brauchbar aus.

Nun fehlt ja noch was: die Laufzeitumgebung bzw. C-Standard lib.

CMSIS von STM bringt einen Satz an .h und .c mit, auch Startupfiles. So 
wie ich es verstehe, sollte das Startup + CMSIS-Dateien + eigene Quellen 
ein lauffähiges Programm ergeben, halt ohne jegliche 
C-Standardfunktionen.
Dass gcc bzw. ld über fehlendes _start meckert, liegt an meinem 
Linkerscript?

Sooo, nun möchte ich jedoch auch mal ein wenig Komfort und grundlegende 
C-Standardfunktionen verwenden. Habe dazu die newlib übersetzt, soweit 
so gut. Allerdings erzeugt gcc dann durchaus üppige Programm (Mindestens 
6K, und ordentlich RAM-Verbrauch). Irgend wie passt dann meiner Meinung 
nach auch das CMSIS-Startupfile nicht mehr dazu. Ist es korrekt, dass 
das CMSIS-Startupfile nicht mehr benötigt wird, da ja die 
newlib-Startupfiles das ganze Handling übernehmen (und noch viel mehr)?
Letzte Frage: Lässt sich der newlib-Code noch optimieren, oder sollte 
ich bei kleineren Programmen einfach nur CMSIS + zugehörige startup 
verwenden?

Gibt es noch andere Libs außer newlib, die einen kleineren Footprint 
haben. In meiner Erinnerung erzeugt die avrlibc zum Beispiel deutlich 
kleinere Minimalprogramme (nützt mir halt leider nichts).

So, ich hoffe, ich konnte einigermaßen meine Probleme verdeutlichen und 
freue mich über jede Antwort.

von Olaf (Gast)


Lesenswert?

Zunaechst mal, ich hab keinen blassen Schimmer von dem Cortex Zeugs. Ich 
benutze Renesas. Aber das ist kein Grund ahnungslos zu sein. :-)

> Nun fehlt ja noch was: die Laufzeitumgebung bzw. C-Standard lib.

Was dir fehlt ist ein Linkerscript und Startupcode. Eventuell auch 
Libaries wenn du sie verwenden willst.

> Dass gcc bzw. ld über fehlendes _start meckert, liegt an meinem
> Linkerscript?

Dein Linker erwartet eine funktion _start welche als erstes nach einem 
Reset des Controllers ausgefuehrt wird. Bei PCs wird sowas normalerweise 
von der Laufzeitumgebung bereitgestellt. Bei Microcontrollern ist es 
eher deine Aufgabe sowas zu machen. Du koenntest z.b selber soetwas in 
Assembler (oder C?) programmieren.
In dieser Funktion musst du folgendes bereitstellen:

1. Notwendigste Basisintialisierung.
   Stackpointer init
   Eventuell /CS fuer externes Ram bereitstellen.
   Waitstates wenn dein Proz ein lahmer Hobel ist

2. initialisierte Variablen aus Flash ins Ram kopieren

3. normale Variable auf Null initialisieren

4. Vielleicht den Stackbereich mit einer Magicnumber
   fuellen. (dann kann man spaeter den Stackverbrauch
   pruefen)

5. main aufrufen.


> Sooo, nun möchte ich jedoch auch mal ein wenig Komfort und
> grundlegende C-Standardfunktionen verwenden. Habe dazu die
> newlib übersetzt, soweit so gut. Allerdings erzeugt gcc
> dann durchaus üppige Programm (Mindestens 6K, und ordentlich
> RAM-Verbrauch).

Da kann ich jetzt nichts zu sagen, allerdings entspricht das nicht
meiner Erfahrung. Aber natuerlich wenn du erstmal printf aufrufst
dann darfst du dich nicht wundern wenn dein Code fett wird.
Und natuerlich geht Ram fuer Stack oder gar Heap verloren.

Ausserdem solltest du Linkerscript und StartUp Code wirklich selber 
erzeugen oder fuer deine Hardware anpassen. Defaultmaessig kann es sein 
das der Compiler/Linker Standardfiles in seinem Dateibaum verwendet. Es 
ist aber viel besser man hat lokale Kopien in seinem Project weil man 
die dann genau an sein Projekt anpassen kann. (z.B Stackgroesse)

Meine LDFLAGS sehen z.B so aus:

LDFLAGS         =  -nostartfiles -o start.elf start.o 
-Wl,-Map=mapfile.txt -T m30624fgafp.ld

Dann enthaelt mein start.o die Startfunktion und ich uebergebe mein 
lokales Linkerscript. Und es schadet nicht das mapfile gruendlich zu 
lesen. .-)

Olaf

von Oliver (Gast)


Lesenswert?

High Performer schrieb:
> Dass gcc bzw. ld über fehlendes _start meckert, liegt an meinem
> Linkerscript?

Jein. Linkerscript und startupcode müssen zusammenpassen. Wenn das 
script ein label _start erwartet, muß das im startupcode auch vorhanden 
sein. Wenn das dort anders heisst, musst du das im linkerscript 
anpassen.

High Performer schrieb:
> Ist es korrekt, dass
> das CMSIS-Startupfile nicht mehr benötigt wird, da ja die
> newlib-Startupfiles das ganze Handling übernehmen (und noch viel mehr)?

Die newlib ist völlig prozesorunabhängig, die kann gar keine passenden 
startupfiles für deinen oder irgend einen anderen Prozessor mitbringen. 
Und das tut die, soweit ich mir erinnere, auch nicht.

Oliver

von Martin T. (mthomas) (Moderator) Benutzerseite


Lesenswert?

- Newlib ist de-facto-Standard für kostenlose ARM-Toolchains auf Basis 
von GNU gcc, binutils. Soweit ich weiss, liegt das an der Lizenz, die 
wenig restriktiv ist. Es gibt u.a. uc-libc aber die ist meines Wissens 
mit restriktiverer Lizenz (die ehemals von Keil vorkompiliert angebotene 
GNU Toolchain kam mit dieser libc, war vor Jahren mein erster und 
einziger Kontakt mit uc-libc). Evtl. gibt es im BSD-Umfeld noch etwas. 
Alles in Allem ist die newlib aber schon brauchbar. stdio mit FP-support 
allerdings sehr "fett". Nur mit Ganzzahlunterstützung nicht mehr ganz so 
resourcenhungrig (iprintf statt printf)

- Falls "fehlendes _start" im Kontext mit missing entry steht, dann 
liegt das am Linker-script und/oder am Startup-code. Entweder ist im 
Linkerscript ein entry-Eintrag mit _start als Parameter oder _start ist 
ein default. Entry-Point Angabe im Linkerscript soll mit dem Symbol 
übereinstimmen, bei der die Anhängigkeitskette beginnt (Oft "reset 
handler"). Wichtig ist die Angabe des Entry-Points vor allem bei "unused 
code removal" (-ffunction-sections), falls im Linkerscript nicht 
explizit per KEEP sections davon ausgenommen werden. Im ungünstigsten 
Fall findet der Linker sonst keinen Startpunkt und sieht keinen "used 
code". Dem Genzen geht man aus dem Weg, in dem die Vektortabelle einer 
eigenen Section zugewiesen wird und diese per KEEP im Linkerscript als 
"nicht-aufräumbar" markiert wird. Muss man ohnehin tun, damit zumindest 
einem Minmaltabelle an der Speicherstelle steht, an der die Hardware 
nach Reset sucht). Da in dieser Tabelle bei den mir bekannten 
ARM-Controllern das Symbol für den Resethandler enthalten ist, kann 
nichts mehr schief gehen.

- Wenn nicht anders angegeben, wird ein default Linker-script der Newlib 
verwendet, sodenn diese installiert ist (das Skript kann man sich mit 
arm-*-ld --verbose anschauen). Habe es bisher nur als Inspirationsquelle 
genutzt aber nie direkt verwendet. Soweit erinnert, ist das nicht für 
getrennte Speicherbereiche für read-only und read-write vorgesehen. 
Dieses Linkerscript ist für die Verwendung mit der crt0 aus der newlib 
vorbereitet. Zu beachten ist noch, dass wenn die toolchain mit 
multilib-support compiliert ist (üblich), einige Linkerscripte und 
Startupdateien existieren können.

- Funktionen aus der newlib (oder anderer libc) kann man auch verwenden, 
wenn man eigene oder von einem Hersteller bereitgestellte Linkerscript 
und startup-code nutzt. Wurde ja bereit von Olaf(Gast) erläutert (mit 
-nostartfiles verhindern, das die Standard-crt0 genutzt wird, mit -T das 
eigene Linkerscript vorgeben, dann per -lc dem Linker mitteilen, dass 
auch in der libc nach Abhängigkeiten gesucht werden soll. (Und immer den 
Linker über das Frontend rufen, also per arm-*-gcc ... , nie den Linker 
arm-*-ld direkt, sonst gibt es nur unnötigen Stress.)

- Einige Funktionen der newlib, u.a. printf/iprintf benötigen 
Hilfsfunktionen, mit denen die Verbindung zur Hardware hergestellt wird: 
sogen. syscalls. Beim letzten Mal nachsehen - für Target ARM7TDMI -, 
waren diese in den newlib-Quellen im Bereich ARM so implementiert, dass 
software-interrupt aufgerufen werden (SWI). Hat man einen passenden 
SWI-Handler (oder eine Debug-Umgebung, welche die SWIs "umbiegt", kann 
man auch mit der syscall Implementierung aus der newlib arbeiten - ist 
aber nicht grade die resourcenschonenst Implementierung. Am Besten 
eigene syscalls bereitstellen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Martin Thomas schrieb:
> Evtl. gibt es im BSD-Umfeld noch etwas.

Die newlib ist zu großen Teilen eine BSD-Bibliothek.  Allerdings ist
BSD halt auf größeren Maschinen groß geworden als heutigen Controllern,
folglich konnte man sich mehr Featuritis leisten.  Möglicherweise
könnte man stattdessen auch ganz alte UNIX-Bibliotheken stattdessen
anpassen, nachdem die alten UNIX-Quellen ja mittlerweile frei zugänglich
sind; bis V7 UNIX, wobei es sinnvoller sein dürfte, das auf V6/V7
aufsetzende 2BSD stattdessen zu verfolgen.  Dessen letzte Inkarnation
namens 2.11BSD hat vom "Feeling" schon einiges von 4.4BSD geerbt (beide
stammen von Anfang der 1990er Jahre), ist aber halt trotzdem noch ein
System für PDP-11-Maschinen, die wohl mittlerweile performancemäßig
selbst ein AVR mit externem Speicherausbau übertreffen dürfte.

Alles in allem war das aber der Grund, warum wir bei der AVR-Toolchain
stattdessen die avr-libc separat gezimmert haben.  Die hat an einigen
wenigen Stellen auch von BSD geerbt, aber eben vieles auch neu
implementiert mit besonderem Augenmerk auf sparsame Ressourcennutzung.
Leider ist das natürlich dann komplett AVR-zentrisch geschrieben, da
der Scope des Projekts nie eine Multiplattformfähigkeit beinhaltet
hat.

von Olaf (Gast)


Lesenswert?

> Alles in allem war das aber der Grund, warum wir bei der
> AVR-Toolchain stattdessen die avr-libc separat gezimmert haben.

Ist das Problem bei soetwas nicht das man bei der Entwicklung immer von 
den Moeglichkeiten der neueren Prozessoren gejagt wird? Mir ist 
jedenfalls noch nie der Flash ausgegangen.

Olaf

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Olaf schrieb:
> Mir ist
> jedenfalls noch nie der Flash ausgegangen.

Dann benutzt du zu große Controller. :-)

Rein vom Code hat sich bei AVR nicht so viel in den letzten 10 Jahren
geändert, dass man deshalb unbedingt den alten Bibliothekscode groß
überarbeiten müsste.  Selbst der Xmega brachte da keine großen
Einschnitte.  Die reine Hardware jenseits des CPU-Kerns ist natürlich
eine ganz andere als vor 10 Jahren, aber das berührt die Bibliothek
nur wenig.

von High Performer (Gast)


Lesenswert?

>Was dir fehlt ist ein Linkerscript und Startupcode. Eventuell auch
>Libaries wenn du sie verwenden willst.

die CMSIS (ST-Version) liefert einen startupcode passend zum Prozessor. 
Dieser Startupcode ist relativ einfach in Assembler gestrickt.

>Dein Linker erwartet eine funktion _start

Weil das so im Linkerscript steht? Und ist das ein Standard des Linkers, 
so dass in jedem Linkerscript _start definiert sein muss?

>welche als erstes nach einem
>Reset des Controllers ausgefuehrt wird. Bei PCs wird sowas normalerweise
>von der Laufzeitumgebung bereitgestellt. Bei Microcontrollern ist es
>eher deine Aufgabe sowas zu machen.

Das macht z.B. der Startupcode der CMSIS, der einfach die Adresse von 
main in den Resetvektor einträgt (beim Überssetzen natürlich).

>1. Notwendigste Basisintialisierung.
>   Stackpointer init
>   Eventuell /CS fuer externes Ram bereitstellen.
>   Waitstates wenn dein Proz ein lahmer Hobel ist
>
>2. initialisierte Variablen aus Flash ins Ram kopieren
>
>3. normale Variable auf Null initialisieren

>5. main aufrufen.


Genau das macht der Startupcode der CMSIS.

>Da kann ich jetzt nichts zu sagen, allerdings entspricht das nicht
>meiner Erfahrung. Aber natuerlich wenn du erstmal printf aufrufst

neee, ein nacktes main()

[...]

>Dann enthaelt mein start.o die Startfunktion und ich uebergebe mein
>lokales Linkerscript. Und es schadet nicht das mapfile gruendlich zu
>lesen. .-)

Danke für diesen Tipp speziell und Deine Antwort im Allgemeinen!

von High Performer (Gast)


Lesenswert?

Hallo Oliver,

>Jein. Linkerscript und startupcode müssen zusammenpassen.

Das ist kein Problem, wenn ich den starupcode der CMSIS verwende, der 
nur wenige Zeilen Assembler umfasst.

>Wenn das
>script ein label _start erwartet, muß das im startupcode auch vorhanden
>sein. Wenn das dort anders heisst, musst du das im linkerscript
>anpassen.

Das würde heißen (da ich beim gcc-Aufruf dem ld kein eigenes Script 
mitgegeben habe), dass das default-script von ld ein definiertes Label 
_start erwartet?

> Ist es korrekt, dass
> das CMSIS-Startupfile nicht mehr benötigt wird, da ja die
> newlib-Startupfiles das ganze Handling übernehmen (und noch viel mehr)?

Die newlib ist völlig prozesorunabhängig, die kann gar keine passenden
startupfiles für deinen oder irgend einen anderen Prozessor mitbringen.
Und das tut die, soweit ich mir erinnere, auch nicht.

Na ja, es gibt die crt0.o etc. Da ist jede Menge Zeug drin mit ctors, 
dtors, exitprocs etc. Genau dieses Zeug bläht mein Minimalprogramm 
derart auf. Lässt sich das irgend wie einschränken. Ich brauche keine 
exitprocs und so'n Zeug auf einem klitzekleinen Cortex-M3.

Und genau hier liegt mein Verständnisproblem: Wie hängen der startupcode 
der CMSIS (der nichts anderes tut als Speicherbereiche zu initialisieren 
etc. und dann main aufzurufen) und die crt0 der newlib zusammen, 
voneinander ab oder schließen sich gar aus? Mir ist einfach nicht klar, 
wie der code in der crt0 überhaupt zum Zuge kommen bzw. mit dem 
Startupcode der CMSIS zusammenarbeiten soll. Oder ist der Code in crt0 
womöglich gar kein Startupcode sondern es handelt sich einfach um 
Helperfunktionen für die restliche libc?

Da ich die Programme aktuell noch nicht praktisch testen kann, weiß ich 
natürlich auch nicht, ob insgesamt was sinnvolles rauskommt.

Also ich sehe schon, ich muss das alles nochmal genauer durchforsten. 
Bin über jede weitere Info dankbar und werde mich bei weiteren 
Erkenntnissen wieder hier melden.

von Olaf (Gast)


Lesenswert?

> die CMSIS (ST-Version) liefert einen startupcode passend zum Prozessor.
> Dieser Startupcode ist relativ einfach in Assembler gestrickt.

Das ist schoen weil du dann schonmal einen Anfang hast. Ich empfehle dir 
aber trotzdem mit einer lokalen Kopie zu arbeiten weil dieses File nicht 
nur zum Prozessor passen muss sondern auch zu deiner Anwendung. 
Vielleicht willst du ja mal die Stackgroesse aendern, oder die Position.

> Weil das so im Linkerscript steht? Und ist das ein Standard des
> Linkers, so dass in jedem Linkerscript _start definiert sein muss?

Hm..hast du schonmal ein Linkerscript gelesen?
Bei mir steht da zum Beispiel das hier drin:

[..]
OUTPUT_FORMAT("elf32-m32c", "elf32-m32c",
              "elf32-m32c")
OUTPUT_ARCH(m32c)
ENTRY(_start)
[..]

Auch da liefert dir eines der Pakete (binutils oder newlib) 
normalerweise Beispiele mit. Die sind aber nicht nur Prozessorabhaengig 
sondern haengen auch von deiner Zielhardware ab. Auch da wuerde ich 
empfehlen mit einer lokalen Kopie zu arbeiten damit du Sonderwuensche 
einbauen kannst. (z.B eine eigene Section fuer einen Bootloader oder 
Dataflash)

> Das macht z.B. der Startupcode der CMSIS, der einfach die Adresse von
> main in den Resetvektor einträgt (beim Überssetzen natürlich).

Das geht natuerlich auch. Man muesste auch komplett auf den 
Assemblerteil verzichten koennen und alles in C machen koennen. Man 
sollte nur daran denken das man keine Variablen nutzen kann und alles 
inline deklarieren muss bevor man seinen Stack eingerichtet hat.

> Na ja, es gibt die crt0.o etc. Da ist jede Menge Zeug drin mit ctors,
> dtors, exitprocs etc. Genau dieses Zeug bläht mein Minimalprogramm
> derart auf. Lässt sich das irgend wie einschränken.

Man muss das ja nicht verwenden. Die crt0 scheint bei dir zu viel Luxus 
zu bieten. (C++?) Deshalb mache ich meine Initialisierungen lieber 
selber.
Bei mir ist dann ein Programm das aus einem leeren main() besteht nur 
56Byte gross. Es besteht dann halt nur aus den wenigen Zeilen Assembler 
um den STack zu initialisieren und Variablen zu kopieren.

Aber es gibt natuerlich ein paar Dinge die so ein einfaches Programm 
nicht macht. Dein Prozessor hat irgendwo eine dicke Liste mit den 
Interruptvektoren? Wo steht die? Wie gross ist die? Vielleicht bindet 
dein crt0 da bereits einen default ein?
In deinem mapfile sollte eigentlich drin stehen was genau wieviel Platz 
braucht und dann kannst du dir das ja getrennt anschauen und entscheiden 
ob es dir wichtig ist.
Oh..und ich koennte mir auch vorstellen das du verschiedene crt0 zur 
Auswahl hast. Es koennte einen Unterschied sein ob man ein fettes ARM 
system hat auf dem man gleich Linux installiert oder einen kleinen 
Minicontroller.

Olaf

von High Performer (Gast)


Lesenswert?

Hallo Olaf,

zuerst einmal Dir und den anderen vielen Dank für die Antworten.

habe mir mal die xref und map angeschaut. Also ich denke, ich kann das 
ganze nicht so einfach kleiner machen. Die Funktionen, die in crt0, crti 
etc. vorhanden sind, werden von den newlib-Funktionen genutzt. Neben den 
syscalls ist ein Haufen anderes Zeug drin. Ich denke mal, wenn ich das 
alles rauslasse bzw. durch stubs ersetze, wird die newlib wohl nicht 
funktionieren.
Schon alleine für malloc braucht es eine Menge overhead, und ich denke, 
viele der Standardfunktionen verwenden malloc etc. Werde aber nochmal 
recherchieren

Ich weiß noch immer nicht so richtig, wie ich da ran soll. Bei Interesse 
sende ich mal die interessanten Auszüge aus der map bzw. xref.

Darf ich Dich mal nach Deiner Laufzeitumgebung fragen?

Gruß

von Olaf (Gast)


Lesenswert?

> Schon alleine für malloc braucht es eine Menge overhead, und ich denke,
> viele der Standardfunktionen verwenden malloc etc. Werde aber nochmal
> recherchieren

Ja, nun wenn du den vollen Luxus nutzen willst dann wird dein Binary 
eben ein paar Kilobyte groesser. Wo ist das Problem? Wer in einem 
controller malloc nutzen will der muss sowieso etwas richtig grosses 
vorhaben oder nicht?

> Darf ich Dich mal nach Deiner Laufzeitumgebung fragen?

Ich verstehe die Frage nicht.

Olaf

von High Performer (Gast)


Lesenswert?

Hallo Olaf,

>Ja, nun wenn du den vollen Luxus nutzen willst dann wird dein Binary
>eben ein paar Kilobyte groesser. Wo ist das Problem? Wer in einem
>controller malloc nutzen will der muss sowieso etwas richtig grosses
>vorhaben oder nicht?

Ja, das ist mir schon klar. Bei mir war es allerdings so, dass auch 
ein Programm mit leeren main() schon 6 kByte groß war. Es wurden nämlich 
eine Menge Funktionen, u.a. malloc etc. eingebunden.

Nun, ist bin einen riesigen Schritt weiter: gcc mit -nostartfiles 
aufgerufen bewirkte Wunder! Ich habe jetzt zusätzlich das (kleine) 
Startupfile der STM-CMSIS eingebunden und ein Linkerscript gebastelt. 
Nun sieht vom erzeugten Code her alles prima aus, und ein einfaches 
Programm mit ein paar einfachen Standardfunktionsaufrufen (z.B. 
strlen()) ist nur noch wenige hundert Bytes groß. Das meiste davon 
braucht die Initialisierungsfunktion der CMSIS, die man sich auch noch 
sparen könnte. Also soweit alles im grünen Bereich!

>> Darf ich Dich mal nach Deiner Laufzeitumgebung fragen?
>
> Ich verstehe die Frage nicht.

Ganz einfach: ich wollte wissen, welche Konfiguration bei dir so kleine 
Programme erzeugt, damit ich einen Ansatz habe, wie es funktionieren 
könnte.
Aber das hat sich ja jetzt erledigt.

Vielen Dank nochmal für Deine/Eure Unterstützung. Mal sehen, wie's 
weitergeht. Werde mich sicher nochmal hier melden ;-)

Viele Grüße

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Olaf schrieb:
> Wer in einem
> controller malloc nutzen will der muss sowieso etwas richtig grosses
> vorhaben oder nicht?

Die stdio-Implementierung dürfte standardmäßig ein malloc()
referenzieren, denn irgendwoher muss sie ja ihren Pufferspeicher
beziehen.  Diese Referenz ist vermutlich selbst dann noch da, wenn
man mit setbuf/setvbuf einen expliziten statischen Puffer dann
zuweist, denn dass man das vorhat, kann das stdio-Subsystem ja zur
Compilezeit nicht ahnen.

Allerdings könnte man dafür ein dummy-malloc() mitliefern, das weiter
nichts macht, als in eine Endlosschleife zu rennen, sodass man das
ggf. mit dem Debugger schnell finden kann, ob malloc() doch noch aus
Versehen gerufen wird.  In der nicht-Debug-Variante gibt die Funktion
dann immer 0 zurück (malloc failed).

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.