Das erste Programm: Unterschied zwischen den Versionen
KKeine Bearbeitungszusammenfassung |
Markierung: Manuelle Zurücksetzung |
||
| (5 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 105: | Zeile 105: | ||
In unserem Fall reservieren wir hier den Speicher von 0 - 0x8000 für den Stapel. | In unserem Fall reservieren wir hier den Speicher von 0 - 0x8000 für den Stapel. | ||
Ich beschreibe den Befehl “mov” später nochmal etwas intensiver, zunächst wollen wir erstmal etwas sehen. | Ich beschreibe den Befehl “mov” später nochmal etwas intensiver, zunächst wollen wir erstmal etwas sehen. | ||
==== Zahlenformate ==== | ==== Zahlenformate ==== | ||
| Zeile 207: | Zeile 207: | ||
{| style="width: 100%; | {| style="width: 100%; | ||
| style="width: 33%;" | [[ | | style="width: 33%;" | [[Systemprogrammierung / Bare Metal|< Zurück (Systemprogrammierung / Bare Metal)]] | ||
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | | style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | ||
| style="width: 33%; text-align:right;" | [[General Purpose I/O | | style="width: 33%; text-align:right;" | [[General Purpose I/O|Weiter (General Purpose I/O (GPIO)) >]] | ||
|} | |} | ||
Aktuelle Version vom 21. November 2024, 09:29 Uhr
Unser erstes Programm wird zunächst nichts machen. Es wird einfach eine Dauerschleife durchlaufen. Wir verwenden es als Grundbaustein, um alle weiteren Versuche zu erstellen. Ich beschreibe zunächst, wie so ein Programm erstellt wird, welche Besonderheiten es gibt, wie es kompiliert und dann ausgeführt wird.
Den Sourcecode erstellen
Zunächst öffnen wir unser Textprogramm, mit dem wir unser erstes Assemblerprogramm schreiben.
/*
The first program
13.10.2020 www.satyria.de
*/
.section .init @This shows the linker what to consider
.globl _start @Generates a global label
_start: @The label _start (entry address)
b main @Branch to "main"
.section .text @Instruction to the linker that code is coming
main: @Create the label "main"
mov sp,#0x8000 @Create a stack of 32768 bytes
MainLoop: @Infinite loop
b MainLoop
Das Programm wird nun in das Verzeichnis bei der Standardinstallation bei Windows in folgendes Verzeichnis abgelegt (gespeichert): “C:\msys64\home\xxx”. Mit “xxx” ist in der Regel dein Name gemeint. Unter linux kann es ins “Home” Verzeichnis abgelegt werden. Gebe ihm am besten den Namen “erstes.s”. Der Suffix “.s” deklariert die Datei als Assembler Sourcecode.
Zunächst beschreibe ich hier das Programm und welche Befehle verwendet werden und am Schluß werden wir es Kompilieren.
Kommentare
Hier im Beispiel habe ich die Kommentare in Orange hervorgehoben, damit man diese besser erkennt. In den folgenden Beispiele werden diese in Olive dargestellt.
Es gibt hier zwei Arten von Kommentaren. Kommentare, die über mehrere Zeilen gehen, werden mit “/*” eingeleitet und mit “*/” beendet. Hier im Beispiel ist dies in Zeile 1-4 zu sehen.
In den folgenden Zeilen, wird “@” verwendet, um einen Zeilenkommentar zu erstellen. Der Kommentar beginnt ab dem Zeichen “@” und endet mit dem Zeilenende.
Sektionen
Sektionen werden in Assembler benötigt, um dem Linker zu sagen, wie er mit den folgenden Zeilen umgehen muss.
Unser erster “.section”-Befehl (Zeile 5) erzeugt eine Art Initiierung des Programms. Dieser Code wird als erstes ausgeführt, darf aber hinterher nicht nochmal aufgerufen werden. In dieser Sektion springen wir in unser eigentliches Programm.
In Zeile 11 verwenden wir die Sektion “.text”. Dies Information sagt dem Linker, dass es sich um Ausführbaren Code handelt. Dieser Teil wird auch zum Beschreiben gesperrt.
Hier eine Auswahl von möglichen “.section” Befehlen:
| Section Name | Beschreibung |
|---|---|
| .text | Ausführbarer Programm-Code
Dieser Teil beinhaltet nur-lesbare Daten und hat keinen Schreibzugriff |
| .data | Daten
Innerhalb dieser Sektion können die Daten gelesen und geschrieben werden. Die Daten sind nicht Ausführbar |
| .rdata | Ähnliche wie .data, allerdings können die Daten nicht beschrieben werden. Verwendung findet dies zum Beispiel für Konstanten. |
| .init | Erzeugt einen Code, der nur beim Start des Programms ausführbar ist. |
.globl
In Zeile 6 wird das Label _start als Global definiert. Damit wird dem Linker dieses Label bekannt gemacht. In unserem System, welches wir hier programmieren müssen wir dieses Label als Global definieren, da der Linker damit unseren Code in den Speicher ab der Adresse 0x8000 legen kann. Bei 32-Bit Bare Metal Anwendungen, erwartet der Raspberry Pi das Programm genau dort. Das liegt an der eigenart des Raspberrys. Der Raspberry startet zunächst nicht mit der CPU, sondern mit seinem Grafikchip. Dort wird eine Firmware gestartet, der Raspberry wird initialisiert und erst dann der CPU übergeben. In dieser Zeit ist dieser Speicherplatz reserviert und die CPU bekommt dann die Zuweisung auf die Speicherstelle 0x8000 zu schauen und dort den Code auszuführen. Bei 64-Bit Bare Metal Anwendungen ist es die Adresse 0x80000.
Die ersten Assemblerbefehle
In diesem Beispiel wurden bisher nur zwei Assemblerbefehle verwendet, diese sind “b” und “mov” und blau hervorgehoben.
b (Branch)
b ist die Abkürzung für Branch und bedeutet, dass das Programm verzweigen soll.
b main @Verzweige nach "main" (Branch)
Der erste Branch-Befehl (Zeile 9) bedeutet, dass er zum Label “main” springen soll und damit wird dann unser Programm gestartet.
MainLoop: @Endlosschleife
b MainLoop
Der zweite Branch-Befehl, der hier verwendet wird, bedeutet, dass er zum Label “MainLoop” springen soll. Wenn man hier den Code mal genauer anschaut, sieht man, dass hier ein ständiges Springen in den gleichen Codeabschnitt erfolgt. Damit haben wir hier eine Endlosschleife. Dies müssen wir hier tun, da sonst das Programm irgendwas macht, eventuell auch Dinge, die wir nicht wollen.
Noch ein Wort zu Labels
Alle Labels, die angesprungen werden, müssen im Code mit “:” abgeschlossen sein. erlaubt sind alle Alphanumerischen Zeichen plus ein paar Sonderzeichen. Idealerweise sollte man Aussagekräftige Labels verwenden, damit der Code später noch gut Lesbar bleibt.
mov (Move)
mov sp,#0x8000 @Erzeuge einen Stapel von 32768 Bytes
Der Befehl “mov” bedeutet, dass hier Informationen sich bewegen sollen. In unserem Fall schreiben wir die Adresse 0x8000 in das Stapelregister (sp). Wir benötigen später in unseren Programmen den Stabel. Diesen Verwenden wir, um kurzfristige Daten abzulegen.
Ein Stapel, ist wie dieser auch zu sehen. Die letzte Zahl, die wir dort hineinlegen, wird oben drauf gelegt, erst wenn wir diese wieder abfragen, kommt die zuvor abgelegte Zahl wieder zum vorschein. Der Stapel (sp) negiert allerdings seine Position. Das heißt, dass die erste Zahl hier im Speicher 0x8000 abgelegt wird und die nächste dann an Position 0x7FFF.
In unserem Fall reservieren wir hier den Speicher von 0 - 0x8000 für den Stapel.
Ich beschreibe den Befehl “mov” später nochmal etwas intensiver, zunächst wollen wir erstmal etwas sehen.
Zahlenformate
Im Beispiel “mov” habe ich die “Zahl” 0x8000 verwendet, wenn man nun im Kommentar schaut, steht da die Zahl 32768. Diese zwei Zahlen sind identisch. Die Zeichen 0x verwendet man, um dem System zu sagen, dass es sich um eine Hexadezimalzahl handel. Ohne Angabe eines Formates, wertet der Compiler diese Zahl als Dezimalzahl aus. Ausser diesen zwei Formaten gibt es noch ein paar andere. Hier eine Aufzählung:
| Zahlentyp | Basis | Vorzeichen | Erlaubte Zeichen |
|---|---|---|---|
| Dezimal | 10 | 0-9 | |
| Hexadezimal | 16 | 0x oder 0X | 0-9, A-F |
| Oktalzahl | 8 | O | 0-7 |
| Binärzahl | 1 | 0b oder 0B | 0-1 |
| Floating Point | 10 | 0f oder 0F | 0-9 |
Kompilieren
Unseren Sourcecode haben wir unter Windows in das Verzeichnis “C:\msys64\home\xxx” abgelegt. Damit wir es kompilieren können, öffnen wir unter Windows “msys2”. Zu finden ist das Programm über das Windowsmenü.
Unter Linux öffnen wir die Konsole.
In der Regel wird das entsprechende Home-Verzeichnis geöffnen, egal ob wir hier nun Windows verwenden oder Linux.
Sollte es nicht dort sein, kann man einfach mit dem Befehl “cd ~” in diese Verzeichnis wechseln.
Mit “ls” bekommst du den Inhalt angezeigt.
Hier als Beispiel unter Windows:
Um nun das Kompilieren durchzuführen, werden drei Befehle benötigt:
arm-none-eabi-as erstes.s -o erstes.o
arm-none-eabi-ld --no-undefined erstes.o -o erstes.elf
arm-none-eabi-objcopy erstes.elf -O binary kernel7l.img
“arm-none-eabi-as” erzeugt mit der Option “-o” zunächst eine Objektdatei. Da der Assembler eventuell noch nicht alles weiß, erzeugt er zunächst diese Datei.
Erst der Linker “arm-none-eabi-ld” erzeugt den eigentlichen Maschinencode und setzt Informationen zusammen, die eventuell selbst im Code nicht deklariert wurden.
Da der Linker eine “elf” Datei erzeugt, aber unser Raspi im Ursprungszustand diese nicht versteht und er eine andere Art der Datei erwartet, müssen wir diese Datei noch entsprechend verändern. Dies übernimmt der Befehl “arm-none-eabi-objcopy” für uns. Das Ergbnis ist eine “kernel7l.img” Datei.
Programm auf dem Raspberry Pi ausführen
SD-Karte vorbereiten (einfache Art)
Damit wir nun die Datei auf dem Raspberry Pi ausführen können, müssen wir noch ein paar Dinge vorbereiten.
Die einfachste Methode dabei ist, einfach eine bereits lauffähige SD-Karte zu verwenden, auf der zum Beispiel “Raspian” installiert ist. Wenn man nun diese SD-Karte in einem Filemanger anschaut, gibt es dort zwei Partitionen. Uns interessiert hier nur die Boot-Partition. Am besten ändert man nun alle Namen der Dateien, die mit “kernel…” beginnen, um. Diese würden uns bei der Ausführung unseres Programms stören und eventuell auch nicht starten.
Gebe ihnen solche Name, die du später dann wieder entsprechend zurück verändern kannst. Damit kannst du das Raspian weiterhin verwenden, wenn du keine Lust mehr hast, weiter zu machen… Hier ein Beispiel:
SD-Karte vorbereiten (bevorzugte Art)
Eine zweite Methode, die ich selbst bevorzuge, wäre es, sich eigens für die Versuche eine SD-Karte zu erstellen. Hier beschreibe ich, wie dies geht.
Formatiere zunächst eine SD-Karte im “FAT32” Format und gebe ihr den Namen “BOOT”. Damit diese Karte startfähig wird, werden noch ein paar Dateien benötigt:
bcm2711-rpi-4-b.dtb
bootcode.bin
start.elf
start4.elf
Diese können entweder von der Seite https://github.com/raspberrypi/firmware/tree/master/boot heruntergeladen oder von einer Lauffähigen SD-Karte kopiert werden und anschließend auf die SD-Karte kopiert werden.
Programm ausführen
Egal, für welche Version du dich entschieden hast, kannst du nun deine erzeugte Datei “kernel7i.img” auf die Bootpartion der SD-Karte kopieren. Lege nun die Karte in den Raspberry Pi ein und starte den Raspberry Pi. Bei diesem Beispiel wird aber nichts passieren, da unser Code bisher nichts macht, ausser eine Endlosschleife durchzuführen. Und dies macht er auch.
Solltest du das HDMI-Kabel angeschlossen haben, kannst du den Bootvorgang auf dem Bildschirm verfolgen und am Ende den schönen Farbverlauf sehen.
Im nächsten Kapitel werden wir den Raspberry Pi die erste sichtbare Aktion (LED leuchten lassen) ausführen lassen und dass ohne eine weitere Hardware nutzen zu müssen.
| < Zurück (Systemprogrammierung / Bare Metal) | < Hauptseite > | Weiter (General Purpose I/O (GPIO)) > |