
Guten Tag! Heute möchte ich Ihnen sagen, wie Sie ein Minimalprogramm schreiben, das auf einem ARM Cortex-M3 ausgeführt wird und "Hello, World!" Druckt. Wir werden versuchen, schrittweise das notwendige Minimum herauszufinden, das wir dafür benötigen. Wir werden auf dem QEMU-Emulator laufen. Daher kann sich jeder fortpflanzen, auch wenn er kein Stück Eisen zur Hand hat.
Also lass uns gehen!
Der QEMU-Emulator unterstützt den Cortex-M3-Kern und emuliert die darauf basierende Stellaris LM3S811-Plattform von Texas Instruments. Wir werden auf dieser Plattform laufen. Wir benötigen die arm-none-eabi- Toolchain (Sie können sie
hier herunterladen). Als nächstes müssen wir die Hauptlogik unseres Programms, den Startcode, der die Kontrolle auf das Programm überträgt, und das Linkerskript schreiben.
Auf einem habr schon ganz
gute Artikel darüber, wie man eine Diode auf ein Stück Eisen von Grund auf blinkt. Daher werde ich hier nicht näher darauf eingehen, was und wie es funktioniert, sondern nur das erforderliche Mindestmaß an Wissen geben, um zu beginnen.
Unsere Hallo Welt in der Datei test.c:
static volatile unsigned int * const UART_DR = (unsigned int *)0x4000c000; static void uart_print(const char *s) { while (*s != '\0') { *UART_DR = *s; s++; } } void c_entry(void) { uart_print("Hello, World!\n"); while (1) ; }
Genau diese Adresse 0x4000c000 stammt aus der Dokumentation, dort liegt das DR-Register Null Null.
Wir werden nicht an der Einrichtung des UART beteiligt sein (dies muss auf der Hardware erfolgen), aber wir werden versuchen, die Symbole sofort direkt darin zu platzieren.
Jetzt müssen wir die Kontrolle irgendwie auf unsere Funktion with_entry in der Datei test.c. übertragen. Erstellen Sie dazu den folgenden Code (Startup.S-Datei) und fügen Sie ihn am Anfang in das endgültige ELF-Image ein.
.type start, %function .word stack_top .word start .global start start: ldr r1, =c_entry bx r1
Das erste Wort bei 0x0 sollte ein Zeiger auf die Oberseite des Stapels (SP) sein. Bei 0x4 gibt es einen PC, der wie SP in Register geladen wird. Beachten Sie, dass start genau als Funktion und nicht als Bezeichnung deklariert wird, da Cortex-M-Code im Thumb-Modus ausgeführt wird (dies ist ein so vereinfachter Satz von ARM-Befehlen) und die Adressen der Funktionen im Interrupt-Vektor in der Form (Adresse |) vorliegen müssen 0x1) - d.h. Das letzte Bit der Adresse muss 1 sein.
Als nächstes lädt die Startfunktion einfach die Adresse unserer Funktion c_entry () aus der Datei test.c und übergibt dort die Steuerung über "bx r1".
Es bleibt nur, unser Programm erfolgreich zu verknüpfen. Dazu müssen Sie die Speicherkarte unseres Mikrocontrollers einstellen. In der Dokumentation finden Sie die Adressen und Größen von Flash-Speicher (ROM) und RAM (RAM). Hier ist das Linker-Skript test.ld:
SECTIONS { . = 0x0; .text : { startup.o(.text) test.o(.text) } . = 0x20000000; .data : { *(.data) } .bss : { *(.bss) } . = ALIGN(8); . = . + 0x1000; stack_top = .; }
Es ist wichtig, auf die Adressen zu achten. "." im Linker-Skript gibt die aktuelle Position an. Wir setzen den Textabschnitt am Anfang des ROM (Adresse 0x0) in der folgenden Reihenfolge - startup.o (.text) geht zuerst. Gehen Sie als nächstes in den RAM (. = 0x20000000;) und geben Sie dort Daten (initialisierte globale Daten) und bss (nicht initialisierte globale Daten) ein. Unten sehen wir ALIGN (8) - ARM erfordert die Ausrichtung des SP (Stack Pointer) um 8. Da der Stapel kleiner wird, ist die Zuweisung von Speicherplatz unter dem Stapel nur eine Ergänzung. “ =. + 0x1000 ”. Wir kennen unser Programm gut, daher reicht ein 4-KB-Stack mit einem großen Spielraum aus.
Das ist alles, es bleibt alles zusammen zu setzen. Ich bringe build.sh:
Alles hier sollte mehr oder weniger verstanden werden, mit Ausnahme der Flagge. In diesem Fall müssen Sie es nicht hinzufügen (Sie können es überprüfen), aber da wir ein Baremetal-Image von Grund auf neu vorbereiten, ist es besser, dem Compiler mitzuteilen, dass Funktionen wie main () nicht beachtet werden.
Als Ergebnis haben wir die ELF-Datei test.elf erhalten. Führen Sie es auf QEMU aus:
$ qemu-system-arm -M lm3s811evb -kernel test.elf -nographic Hello, World!
Es funktioniert.
Dies ist natürlich eine Fallstudie, um zu verstehen, was passiert. Wenn Sie aussagekräftigere Funktionen benötigen, sollten Sie vorgefertigte Dinge verwenden. Wir haben Unterstützung für diese Plattform in
Embox hinzugefügt . Diese Vorlage heißt platform / stellaris / lm3s811evb. Wenn jemand versuchen möchte, eine etwas ernstere Sache (Konsole, Timer, Interrupts) auszuführen, können Sie sie sammeln und versuchen. In diesem Fall, ich wiederhole, brauchen Sie keine Hardware-Karte.
Und wer noch nicht genug Emulatoren hat oder uns Fragen stellen und mit den
Eisenstücken spielen will
, wird diesen Samstag und Sonntag beim IT-Festival
techtrain.ru in St. Petersburg warten. Wir werden verschiedene Eisenstücke auf dem Stand haben und in der Demo-Zone werden wir versuchen zu erklären, wie man sie programmiert.