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.
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
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
- 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.
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.
> 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
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.
>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!
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.
> 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
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ß
> 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
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.