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.
Mal eben das zu erklären wie man den startup-Code mit den Vector-tables und der initialisierung schreibt wird schwierig, das ist recht aufwendig. Du kannst das ganze aber hier nachlesen: http://books.google.de/books?id=mb5d_xeINZEC&printsec=frontcover&dq=the+definitive+guide+to+arm-cortex-m3&hl=de&ei=bX56TJbfPKWhOJfe9NwG&sa=X&oi=book_result&ct=book-thumbnail&resnum=1&ved=0CDAQ6wEwAA#v=onepage&q&f=false Auf der Arbeit habe ich noch ein pdf wo die erstellung des Startup-Codes udn der Linkerscripte für einen stm32 genau erklärt wird. Das schicke ich dir morgen auch mal. Da ist es ganz genau step-by-step erklärt. Die eifnachste Variante wird aber sein sich zumindest mal den startup zuschicken zu lassen. Dann must du nur noch das linkerscript und das makefile schreiben.
@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.
Hier noch ein paar nützliche Links: Toolchain bauen: http://github.com/esden/summon-arm-toolchain GPL'te firmware lib für STM32: http://sourceforge.net/projects/libopenstm32/develop (dort gibts auch simple code-Beispiele und Linkerskripte)
>GPL'te firmware lib für STM32: >http://sourceforge.net/projects/libopenstm32/develop >(dort gibts auch simple code-Beispiele und Linkerskripte) Gibts dort auch irgendwas zum Download? Wo?
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.