Hallo Leute,
OK, der Betreff wirkt etwas provokativ. Ich habe folgendes Problem:
ich würde mich gerne ein wenig "from ground up" mit der STM32-Serie
beschäftigen. Als Compiler möchte ich den gcc verwenden.
Das "from ground up" bedeutet, dass ich auch die Hintergründe verstehen
möchte. Das Problem: ich scheitere gerade an den Basics.
Ich habe mir jetzt den arm-elf-gcc aus dem Quellen übersetzt. Schon das
war nicht einfach, da man diverse andere Pakete benötigt, die man irgend
wie verheiraten muss. Weitere Probleme gibt es mit den
configure-Optionen. Nun steht die "toolschain", auch die STM32
peripheral lib habe ich heruntergeladen. Ein einfaches test.c mit leeren
main() lässt sich als Assemblerausgabe übersetzen. Der erzeugte
Assemblerquelltext passt soweit.
Nun habe ich jedoch kaum Erfahrung mit dem Einbinden von Startup codes.
In der Peripheral lib gibt's ja eine Menge Dateien, wo ich schon gar
nicht weiß, welche ich verwenden will.
Schleuse ich das Testprogramm einfach durch den gcc und lasse dabei auch
linken, wird gegen die newlib gelinkt, als Ergebnis erhalte ich ein
a.out. Leider ist das Minimalprogramm fast 100KB groß, also schon zu
groß für die meisten STM32-Typen.
Das Problem: ich habe circa zwanzig crt0.o im arm-elf-System, zig
"Linkerscripte", von deren Syntax ich keine Ahnung habe, etc. pp.
Rufe ich gcc mit -nostdlib auf und lasse eine crt0 dazulinken, meckert
er über fehlende Symbole. Lasse ich crt0 weg, meckert er wegen fehlendem
_start.
Also zusammenfassend:
1.) Schon bei der Toolchain-Erstellung fehlen mir die notwendigen
Details zu den möglichen Optionen und deren Auswirkungen. Wo finde ich
Details?
2.) Mit welchem Startcode komme ich zu einem Minimalprogramm mit
vernünftiger Größe?
3.) Warum wird schon bei einem leeren main() ein Haufen Zeug aus der
newlib dazugelinkt?
4.) Wo gibt Details über die Linkerscripte?
5.) Ich habe mir einige Doku von arm und stm angeschaut. Irgend wie ist
nirgendwo im Detial erklärt, wie der Startprozess bei den cortex-Typen
aussieht. Ich weiß nur, dass bei boot0 = 0 der Startcode ab 0800 0000
ausgeführt wird. Abhängig vom Bootmode wird der Flashbereich angeblich
in den Bereich ab Adresse 0000 0000 gemapped. Irgendwo steht, dass nach
dem Reset des Prozessors die Stackendadresse aus Adresse 0000 0000
gelesen und dann mit der Programmausführung an Adresse 0000 0004
begonnen wird. Da stellt sich mir z.B. gleich die Frage, wo die
Interruptvektoren liegen.
Ich weiß auch, dass es auf der Gnu homepage eine Menge Doku gibt. Ich
weiß, dass configure --help geht, ich habe die Doku der STM32
Prozessoren und die Original ARM-Doku vorliegen. Jetzt muss ich nur noch
alles verstehen. Ich habe durchaus einige Erfahrung, z.B. mit
PC-Programmierung (C++, Java, Asembler), verschiedenen einfacheren
Controllern, Hardwareentwicklung etc.
Aber das "from ground up" bei den ARM-Typen scheint nicht ganz trivial
zu sein. Und das Thema JTAG ist noch völlig offen. Irgend wie muss ich
den Code ja noch in den Prozessor kriegen.
Bei den AVR-Teilen war das alles relativ unkompliziert, da habe ich
sogar kurz ein tool geschrieben, um den Maschinencode in den Prozessor
zu bekommen (LPT missbraucht, timing ausführlich in der AVR-Doku
beschrieben). Alles sehr einfach und überschaubar. Auch die toolchain
war schnell gebaut.
Wäre nett, wenn mir jemand Tipps geben könnte, auch wenn meine Fragen
vielleicht ein wenig diffus wirken. OK, zu Not beschaffe ich ein
Entwicklungsboard, dann habe ich wenigstens kein Problem, das Programm
in den Prozessor zu bekommen. Aber auch dann denke ich, bleibt noch
einiges zu tun, wenn ich nicht eine fertige Toolchain verwenden möchte.
Also wie gesagt: ich bin für jeden Tipp dankbar.
@Albert:
vielen Dank für Deine schnelle und nette Antwort! Wie gesagt, ich habe
mir ja bereits die Library von STM heruntergeladen. Da ist jede Menge
Startupcode und linker-Beiwerk dabei. Das ganze ist halt ein wenig
unübersichtlich, wenn man in die Thematik einsteigt. ;-)
Es wäre deshalb sehr nett, wenn Du mir die von Dir erwähnte Doku
zukommen lassen könntest. Vielen Dank schonmal.
Parallel werde ich mir mal die Doku zu den STM-libs anschauen.
Vielleicht komme ich ja inzwischen ein wenig weiter.
@Albert:
Nachtrag: Das erwähnte Buch liegt schon seit geraumer Zeit in meinem
Amazon-Wünschzettel. Aber da liegen noch ca. 250 andere ;-)
Werde mir mal die Auszüge auf google anschauen. Ich dachte halt, mit der
Originaldoku wäre ich eigentlich bestens versorgt. OK, wenn das Buch
einen guten Eindruck macht, werde ich es mal bestellen.
Das Buch musst du nciht unbedingt bestellen. Ich finde nicht das es
nötig ist wenn mal ein projekt zum laufen gebracht hat.
Im Ordner der STM Peripheral Lib gibt es im ordner
libraries/CMSIS/Documentation ein htm File was schon relativ viel
erklärt was startup Code und ähnliches angeht. Unter Punkt 3 steht das
interessante davon.
Hier mal die Art wie ich vorgehen würde:
Als erstes brauchst du mal den startup-code. Diesen findest du im Ordner
libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/arm.
Eines dieser files musst du in dein Projekt einbinden (welche von den
vielen weiss ich net, müsste man genauer lesen) und dann im Linkerscript
bekanntmachen. Dieses File ruft dann auch deine int main(void) Funktion
auf.
Siehe dazu die Codezeilen:
1
Reset_Handler PROC
2
EXPORT Reset_Handler [WEAK]
3
IMPORT __main
4
IMPORT SystemInit
5
LDR R0, =SystemInit
6
BLX R0
7
LDR R0, =__main
8
BX R0
9
ENDP
Dann brauchst du vl. noch die core_cm3.c und core_cm3.h. Diese findest
du im Ordner Libraries\CMSIS\CM3\CoreSupport.
Und dann eben die ganzen librarie files.
Ein Makefile für den STM32 findest du hier:
http://svn.savannah.gnu.org/svn/paparazzi/paparazzi3/trunk/conf/Makefile.stm32
Das musst du natürlich noch anpassen. Also die Pfade zum gcc und deinem
Linkerscript.
>Vergiss den c-Code den ich angehängt habe.
:-)
Habe schonmal die Hilfedatei angeschaut. Die von Dir beschriebene Info
habe ich allerdings nicht gefunden. Werde gleich nochmal suchen. Andere
Frage: Reicht prinzipiell der Startupcode, oder muss wirklich ein Teil
der newlib dazugelinkt werden? So lange ich keinerlei
Standardfunktionen verwende, sollte doch eigentlich der Startupcode
reichen, oder nicht?
- Startupcode für GNU Assembler ist in der STM-Library
lib/cmsis/core/cm3/startup/gcc suffix betr. die "Baureihe" md entspr.
medium density (habe ich selbst für STM noch nicht verwendet, wird aber
sicher funktionieren)
- ein Linker-Script für GNU Linker ist in proj/templ/RIDE, muss evtl.
ein wenig angepasst werden, ist aber eine brauchbare Basis (war sie
zumindest für mich)
- man kann ohne newlib auskommen, auf jeden Fall ohne die recht
speicherfressenden stdio Funktionen (also erstmal kein printf oder
ähnliches verwenden - und wenn dann wenigstens iprintf)
- target arm-elf wird nicht mehr häufig genutzt, toolchain mit target
arm-eabi konfigurieren wenn selbst bauen unvermeidbar. Besser fertig
gebaute toolchain verwenden, bleiben noch genug andere "Baustellen"
- crt0 und linker-scripte in der toolchain habe ich nie genutzt, sind
etwas zu generell und scheinbar für Systeme, die mehr Speicher und einen
ununterbrochenen Adressraum haben. Weiterhin werden durch diese Dateien
auch Abhängigkeiten erzeugt (mglw. Fehlermeldungen bei exit() o.ä. die
dann stdio/vfprintf nachziehen). Ja, das ist leider nicht so schön wie
bei avr-libc wo alles gut vorgekaut und auf kleine Controller abgestimmt
ist.
@ High Performer
Gib uns Bescheid, wenn Du im startup-code für STM32
über das hier stolperst:
0xF1E0F85F
Dann wird es für mich interessant. ;-)
Ich konnte dafür noch keine schlüssige Erklärung
finden, außer dass notwendig. Nur wieso, schreibt
niemand. Habe es zumindest noch nicht gefunden.
hallo High Performer,
auch auf die gefahr hin von einigen gcc freaks "gesteinigt" zu werden,
hier meine empfehlung wenn es dir um das verstehen des stm32 bzw. des
cortex m3 geht und nicht um die inbetriebnahme einer tool-kette:
lade dir die kickstart version der iar entwicklungsumgebung von der iar
homepage (kostet nichts und du kannst projekte bis 32kbyte code
erzeugen), installiere sie, lade eines der beispielprogramme und
versuche diese zu verstehen.
zeitaufwand: ca. 30 Minuten
wenn du das ganze debuggen möchtest:
kauf dir den J-Link EDU von segger (kostest ca. €70,- inkl. versand).
gruss
gerhard
Auch interessant:
www.state-machine.com/arm/Building_bare-metal_ARM_with_GNU.pdf
(weiss allerdings nicht sicher, ob das den Cortex abdeckt).
GNU Tools gibt's fertig bei CodeSourcery.
heinz
@gerhard:
>hallo High Performer,>auch auf die gefahr hin von einigen gcc freaks "gesteinigt" zu werden,>hier meine empfehlung wenn es dir um das verstehen des stm32 bzw. des>cortex m3 geht und nicht um die inbetriebnahme einer tool-kette:
Vielen Dank für den Tipp. Warum sollten Dich dafür gcc-freaks steinigen?
;-)
Ich gebe zu, dass ich auch gerne mit dem gcc arbeite. Die
Linux-Entwicklung läuft bei mir mit dem gcc, auch das avr-Zeugs
übersetze ich mit gcc. Deshalb wäre es natürlich schon günstig, wenn ich
auch die ARM-Schiene mit dem gcc erschlagen könnte.
Ich denke mal, das Hauptproblem beim Einstieg in eine neuer Architektur
ist wohl, die toolchain zum Laufen zu bekommen und das erste
Blinkprogramm auf dem Prozessor laufen zu lassen. Das ist das eigentlich
spannende ;-) Der Rest ist dann Datenblattstudium (OK OK, es gibt bei
jeder Architektur Pferdefüße) und testen der Peripherie. Aber damit habe
ich eigentlich nie Probleme.
Trotzdem nochmal vielen Dank für Deinen Tipp.
Aktueller Stand:
habe alle mir bekannten relevanten Dateien (.c und .s) mit gcc und as
übersetzt. Beim Versuch, das ganze mit ld mit dem in der STM-library
vorhandenen Linkerscript zu linken, stürzt anscheinend ld ab
(Speicherzugriffsfehler). Ohne die crt0.o aus der Toolchain findet ld
auch die Marke _start nicht. In der STM-library habe ich keine
crt*-Dateien finden können. OK, ich schaue mal nach in welcher Datei aus
der STM-library _start definiert ist. Bleibt nur das Problem mit dem
ld-Absturz...
High Performer schrieb:> Aktueller Stand:>> habe alle mir bekannten relevanten Dateien (.c und .s) mit gcc und as> übersetzt.> Beim Versuch, das ganze mit ld mit dem in der STM-library> vorhandenen Linkerscript zu linken, stürzt anscheinend ld ab> (Speicherzugriffsfehler).
- Immer nur mit dem Compiler-Frontend arbeiten, direkte Aufrufe von as
und ld vermeiden. Geht alles auch über arm-*-gcc und spart Stress.
- Absturz Speicherzugriffsfehler darf nicht sein. Das kann man getrost
Codesourcery mitteilen. Linker sollte sich zumindest mit einer
ordentlichen Fehlermeldung verabschieden.
> Ohne die crt0.o aus der Toolchain findet ld> auch die Marke _start nicht.
Fehler oder Warnung? Im Zweifel im verwendeten Linker-Script (ist mit -T
dem Linker anzugeben) zu Beginn ein ENTRY(Reset_Handler); sollte Warnung
verschwinden lassen.
>In der STM-library habe ich keine> crt*-Dateien finden können.
Die im o.g. Pfad zu findenden Startup-Codes heissen nur nicht crt*,
enthalten aber den erf. Startcode (.data copy, bss clear, main rufen)
und auch den Vector-Table
> OK, ich schaue mal nach in welcher Datei aus> der STM-library _start definiert ist.
Womöglich nirgends. Evtl. kann man im Assembler-Startup hinter
Reset_Handler: noch eine Zeile _start: schreiben.
>Bleibt nur das Problem mit dem ld-Absturz...
Das ist nicht gut. Ist mir aber noch nicht begegnet, nutze CS G++ lite
schon seit vielen Versionen (unter MS Win).
Zwar "Eigenwerbung" aber hier ist ein Projekt für STM32 und GNU Tools
(CS G++ lite verwendet):
http://www.siwawi.arubi.uni-kl.de/avr_projects/arm_projects/arm_memcards/index.html#stm32_memcard.
Geht weniger um den Anwendungscode, vielmehr einfach die Ausgabe von
"make all" betrachten. Das hilft vielleicht etwas, die Schritte und erf.
Dateien zu verstehen. Gibt Variationsmöglichkeiten aber die im Projekt
gezeigte Vorgehensweise funktioniert hier ganz gut.
>Absturz Speicherzugriffsfehler darf nicht sein. Das kann man getrost>Codesourcery mitteilen.
Würde wohl nicht viel bringen. Ich habe die toolchain direkt aus den
gnu-Quellen erstellt.
Es gibt ein git repository mit code, aber wohl (noch) keine ZIP/TGZ
Dateien zum downloaden.
Code browsen:
http://libopenstm32.git.sourceforge.net/git/gitweb.cgi?p=libopenstm32/libopenstm32;a=tree
Dort kannst du auf "snapshot" klicken und bekommst hoffentlich den
aktuellen Stand als tar.gz Datei (sollte winzip z.B. auch öffnen können
falls du nicht unter Linux arbeitest).
Hallo Leute,
hier mal der aktuelle Stand:
Habe nun, nachdem ld immer abgestürzt ist, mal eine andere Version der
binutils übersetzt und verwendet. Nach einigen Versuchen kam dann auch
eine ausführbare Datei mit ca. 2,5KB Code dabei raus. Habe mir ein
Hexfile draus gebaut und mal angeschaut. Zumindest das Heapende steht,
wie in der ARM-Doku beschrieben, an Adresse 0. Der Adressraum beginnt
bei 0x08000000, also landet der Code im Flash. Für mich sehen die Daten
ab der Adresse vier allerdings eher nach Interruptvektoren aus als nach
ausführbarem Code. Auch objdump zeigt mir an, dass Vektoren am
Flashanfang stehen, was also darauf hindeutet, dass irgend etwas schief
läuft. Laut ARM-Doku soll ja an Adresse 4 der ausführbare Code beginnen.
Könnte sein, dass ein ungeeignetes Linker Script die Interruptvektoren
an die falsche Stelle klatscht. Werde mich mal um die Interruptvektoren
kümmern (weiß jemand, wo die liegen sollen?) und wieder melden. Aber wie
gesagt: immerhin lief ld fehlerfrei durch. Aber bei int main (void) {
return 0; } ist das auch keine Kunst. Mal sehen, wenn ich tatsächlich
ein Programm schreibe, dann vor allem mit Funktionen aus der
C-Standardlib oder gar Fließkommaarithmetik.
Ich bin weiterhin für alle Hinweise dankbar. Momentan vor allem für das
Thema Interruptvektoren.
Vielen Dank an dieser Stelle für alle bisherigen und folgenden
Antworten.
Schönen Abend noch.
OK, ich sehe gerade, im ausführbaren Code steht an Adresse 4 die
Adresse des Resethandlers. Ich denke nicht, dass das so korrekt ist,
oder?
Vom Resethandler geht's dann wie folgt weiter:
LoopCopyDataInit bzw. CopyDataInit, dann
LoopFillZeroBss bzw. FillZeroBss, dann SystemInit und am Schluss main.
Hallo, das ist so schon richtig. Am Anfang des flashes steht die Vector
Table für den NVIC. Und an Adresse 4 steht der Reset_Handler. Du hast
die Doku insoweit missverstanden das der Controller diesen Handler als
erstes aufrufen wird (wenn er startet). In diesem Reset Handler wird
manchmal eine lowlevel_init aufgerufen die die CPU Clock einstellt oder
gleich die main aufgerufen. Hinter der Adresse 0x00000004 stehen ab
Adresse 0x00000008 die anderen Interrupt Vektoren.
Hier mal noch eien Seite mit dem Startzup Code und dem dazugehörigen
linkerscript:
http://embeddedfreak.wordpress.com/2009/08/07/cortex-m3-interrupt-vector-table/
@Albert:
OK, nach der Doku im Link passt das so, wie das Hexfile bei mir
aussieht.
Nächstes Problem: wollte mal etwas aus der Standardlib testen. Habe
div() verwendet. Nun findet ld diese Funktion nicht, egal, welche lib
ich angebe (libc müsste doch OK sein, oder?)
Na ja, alles ein wenig zäh, aber der Lerneffekt ist enorm. ;-)
Ich denke, das wird der letzte Prozessor sein, bei dem ich so nackt
starte. Aber vielleicht packt mich ja der Ehrgeiz wieder mal...
Ach ja, zum Thema Doku wegen des Bootvorgangs. Ich zitiere aus dem
STM32 reference manual:
"... After this startup delay has elapsed, the CPU fetches the
top-of-stack value from address 0x0000 0000, then starts code execution
from the boot memory starting from 0x0000 0004."
Die Frage ist nun: steht an Adresse vier einfach eine Adresse, oder
ein Sprungbefehl? Habe mir den Opcode nicht näher angeschaut. Sollte
so langsam schlafen gehen, morgen wieder arbeitsreicher Tag. Melde mich
morgen Abend nochmal.
Viele Grüße!
High Performer schrieb:> Ach ja, zum Thema Doku wegen des Bootvorgangs. Ich zitiere aus dem> STM32 reference manual:>> "... After this startup delay has elapsed, the CPU fetches the> top-of-stack value from address 0x0000 0000, then starts code execution> from the boot memory starting from 0x0000 0004.">> Die Frage ist nun: steht an Adresse vier einfach eine Adresse, oder> ein Sprungbefehl? Habe mir den Opcode nicht näher angeschaut. Sollte> so langsam schlafen gehen, morgen wieder arbeitsreicher Tag. Melde mich> morgen Abend nochmal.
An Adresse 0x0000 0004 steht NUR die Adresse des Reset Handlers. Aber
die erste Aufgabe des Controllers ist es an die dort angegebene Adresse
zu springen. Er fängt also mit einem Sprung an diese Adresse an. Danach
(ab Adresse 0x0000 0008) folgen andere Interrupt Handler (die ersten 15
sind Core spezifisch, danach folgen die Herstellerspezifischen für die
Peripherie). Nachzulesen im Cortex-M3 Insight Guide von Hitex im Kapitel
2.4.5.3.1
http://www.st.com/mcu/files/mcu/1221142709.pdf>Nächstes Problem: wollte mal etwas aus der Standardlib testen. Habe>div() verwendet. Nun findet ld diese Funktion nicht, egal, welche lib>ich angebe (libc müsste doch OK sein, oder?)
Hast du die include Pfade korrekt angegeben? div() findet sich ja in der
stdlib.h. Ich gehe mal davon aus das in deinem Linkerscript der Pfad
nicht korrekt angegeben ist.
Kannst du mal dein Linkerscript anhängen?
P.S. Wenn das Projekt dann mal läuft, besteht die möglichkeit eine kurze
Anleitung zu bekommen welche Dateien benötigt werden, welche tools du
verwendest und wie man vl. in 5 Schritten zu nem eifnachen Startprogramm
kommt?
>Hast du die include Pfade korrekt angegeben? div() findet sich ja in der>stdlib.h. Ich gehe mal davon aus das in deinem Linkerscript der Pfad>nicht korrekt angegeben ist.
Also die stdlib.h muss ja bereits der compiler finden. Das funktioniert
ja auch. lediglich ld findet dann beim Zusammenbinden der einzelnen
Dateien das Symbol nicht.
Also ich habe nun die toolchain nochmal komplett neu erstellt. Altes
Problem: linker meldet:
"test2.c:(.text+0x12): undefined reference to `div'"
libc.a, libgcc.a und libg.a werden gefunden (--verbose output von ld).
libc.a enthält laut Ausgabe von nm das Symbol div. Warum wird dies nicht
vom Linker gefunden?
Werde nun mal andere Funktionen aus stdlib testen...
OK, memcpy() findet ld ebenfalls nicht.
Habe schon ein komisches Gefühl, weil ich gcc mit --disable-headers
übersetzt habe. Allerdings, wie gesagt, gibt's ja erst mit dem linker
Probleme. Warum findet der shice ld das Symbol nicht. Werde jetzt mal
die relevanten object files aus den libs rauskopieren und direkt in der
Kommandozeile von ld angeben...
Scheitert leider ebenfalls. Zuerst hat er __divsi3 und __modsi3 nicht
gefunden. Habe die entsprechenden Dateien angegeben. Jetzt findet er
__div0 nicht. __div0 ist jedoch in der Datei _divsi3.o enthalten, in der
auch __divsi3 definiert ist. Was nun?
Nachdem er ja die beiden bislang fehlenden Symbole gefunden hat, dafür
aber ein weiteres nicht findet, das aber definiert ist, vermute ich
irgend ein Problem im Linker. Es sieht so aus, als würde er
Abhängigkeiten nicht rekursiv auflösen können. Hat jemand von Euch eine
Idee?
OK, habe die Lösung nach exzessiver Verwendung von google:
Die Reihenfolge der Objektdateien spielt eine entscheidende Rolle beim
ld! Und zwar, logischerweise, in der umgekehrten Reihenfolge, wie ich
zuerst dachte. Beispiel:
datei1.o benötigt Symbol A
datei2.o definiert Symbol A.
--> ld datei2.o datei1.o.
ld bindet die Dateien in der angegebenen Reihenfolge. Für datei1.o muss
Symbol A bereits definiert sein, also in der Symboltabelle des Linkers
enthalten sein. das Symbol ist nun in datei2.o definiert. Deshalb muss
datei2.o zuerst angegeben werden.
OK, werde mal noch kurz a.out und das hexfile checken. Dann bis morgen.