Hi,
Ich bin gerade dabei, meinen Einstieg in die ARM Cortex-M uCs zu finden.
Und masochistisch veranlagt wie ich nunmal bin, habe ich mir statt eines
Demoboards einfach einen STM32F103 geholt und mir einen dazu passenden
Adapter auf Buchsenleisten gebastelt, und zudem gänzlich auf eine IDE
zur Programmierung verzichtet, sondern stattdessen die
"gcc-arm-embedded" Toolchain auf der Konsole benutzt. Nach stundenlangem
Durchackern von Manuals zu Linkerskripten und den Startup-Scripts aus
der Standard Peripheral Library von STM hab ich es nun auch hinbekommen,
ein paar LEDs blinken zu lassen.
Aus den paar wenigen Tutorials, die ich zu Cortex-M3 gefunden habe und
die nicht von einer IDE ausgehen, habe ich die GCC-Option -nostartfiles
entnommen, und nachdem ich ein bisschen eigenen Startup-Code geschrieben
habe, scheint das auch irgendwie zu funktionieren. Nun frage ich mich
aber, wie lange das wohl noch so bleibt, denn in meinem Startup-Code
mache ich nichts anderes, als .data aus dem ROM ins RAM zu laden und
.bss mit Nullen zu füllen, danach rufe ich die SystemInit-Funktion der
StdPeriphLib und schließlich main() auf. Was da vollkommen fehlt ist die
Initialisierung der libc.
Deshalb meine Frage: Ist es sinnvoll, überhaupt -nostartfiles zu
benutzen? Wenn ja, was muss ich in meinem Startup-Code dann noch tun, um
eine funktionierende libc zu haben? Und wenn nicht: Sorgt GCC dann für
den kompletten Startup-Code, oder muss ich trotzdem noch Teile selbst
schreiben?
Danke im Voraus!
Es kommt alles ein bissel auf deinen Programmierstil an. Wenn du drauf
pochst, vorinitialisierte statische Variablen haben zu wollen, dann mußt
du selbige eben vom ROM in den RAM laden, ansonsten kannst du dir diesen
Zirkus schenken. Ich benutze sowas grundsätzlich nicht und gehe immer
davon aus, daß eben alle Variablen vor Benutzung irrelevante Daten
enthalten.
Allenfalls wäre da noch das generelle Ablöschen des verwendeten RAM's
mit null, weil einige grandiose Programmierer (z.B. Chan mit seinem
Fat-Filesystem) das einfach voraussetzen. Rechthaberische Schlampen
eben.
Ich schreib mir meinen Startupcode eigentlich immer selber, weil mir
die Startupcodes von diversen Toolherstellern zu blöde sind.
Kleines Beispiel: unaufgelöste Interrupts und Exceptions landen bei den
meisten Leuten schlicht auf einem "B ." und das ist m.E. das
Allerblödeste was man tun kann. Im Fehlerfalle friert der ganze µC
einfach ein ohne noch irgendwie reagieren zu können. Ich mach es anders,
ich lade ne Fehlerzelle mit einem entsprechenden Fehlercode und löse nen
Warmstart aus. Dann kann main immer noch nachschauen, ob es ne Exception
gegeben hat, selbiges auf irgendeinem Kanal melden (UART oder so), alles
wieder initialisieren und dann weitermachen. So bleibt der µC
arbeitsbereit auch dann, wenn man irgendwas ihn aus der Bahn geworfen
hat.
W.S.
David L. schrieb:> Und masochistisch veranlagt wie ich nunmal bin, habe ich mir statt eines> Demoboards einfach einen STM32F103 geholt und mir einen dazu passenden> Adapter auf Buchsenleisten gebastelt, und zudem gänzlich auf eine IDE> zur Programmierung verzichtet, sondern stattdessen die> "gcc-arm-embedded" Toolchain auf der Konsole benutzt.
Wieso "masochistisch veranlagt"? Das ist genau die richtige Methode, so
machen das eche Männer. Schön das es noch so welche gibt, und wir es
hier nicht nur noch mit diesen IDE-benutzenden Waschlappen,
Warnhinweislesern und Sozialdemokraten zu tun haben.
Allerdings brauchen eche Männer auch keine libc.
Oder nen gcc, die tippen nur
Damit es hier in Thread mal wieder sinnvoll weiter geht:
Ja, ich tue mir das auch an und schreibe (oder modifiziere) den Startup
auch selbst. Neben der Vectortabelle und den Irq-Defaulthandlern ist
eigentlich nur nur das Datensegment und bss zu initialisieren.
Irgendwelche Initialisierungen der newlib sind meistens nicht nötig.
Falls du allerdings C++ verwendest müssen auch alle Constructoren von
globalen Classen vor der main-Funktion abgearbeitet werden. Das macht
z.B. die Funktion
1
__libc_init_array();
Die benötigt allerdings ein paar Segmente in der ld mit festen Namen. In
etwa so:
1
/* C++ constructors etc */
2
. = ALIGN(4);
3
KEEP(*(.init))
4
5
. = ALIGN(4);
6
__preinit_array_start = .;
7
KEEP (*(.preinit_array))
8
__preinit_array_end = .;
9
10
. = ALIGN(4);
11
__init_array_start = .;
12
KEEP (*(SORT(.init_array.*)))
13
KEEP (*(.init_array))
14
__init_array_end = .;
15
16
KEEP(*(.fini));
17
18
. = ALIGN(0x4);
19
KEEP (*crtbegin.o(.ctors))
20
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
21
KEEP (*(SORT(.ctors.*)))
22
KEEP (*crtend.o(.ctors))
23
24
. = ALIGN(0x4);
25
KEEP (*crtbegin.o(.dtors))
26
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
27
KEEP (*(SORT(.dtors.*)))
28
KEEP (*crtend.o(.dtors))
29
/* End C++ */
30
} > Flash
In der SystemInit wird häufug nur der Takt vom internen rc-Oszillator
auf Quarz und PLL gestellt und die globale Variable SystemCoreClock
belegt. Wenn man in die CPU einsteigen will, kann man sich diese Parts
auch gleich noch anlernen. Dann guckt man später nicht wies's Schwein
ins Uhrwerk wenn mal ein Takt geändert werden soll...
Ich muss aber dazu sagen, printf pflege ich selbst und auch new und
delete leite ich auf die Funktionen der heap4.c (FreeRTOS) wenn es
benötigt wird. So erspare ich mir das Implementieren der Funktionen die
die newlib sonst erwartet.
Ich verwende CrossWorks zum Debuggen und habe die Projekte in 2 Formen.
Einmal mit den Libs und dem Projektfile von Crossworks zum anderen als
makefile mit den gnu-arm-tools. Die Crossworks XML-Projektfiles sind
leichter zu bearbeiten als ein makefile. Das rumklicken in der IDE hasse
ich auch wie die Pest. Das gibt mir die Sicherheit nicht von einer IDE
abhängig zu sein.
Ich würde es begrüßen, wenn sich hier im Forum ein paar Leute
zusammentun würden, um mal so was wie ein Standard
Makefile+Startup+Linkerscript für die gängigsten CPUs (STM32,LPC)
zusammenträgt. Ich wäre sicher dabei.