Unser erstes Programm in C (PI4): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
 
(4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
Die Programmierung des Raspberry Pi 4 wird ähnlich durchgeführt, wie die Programmierung des Raspberry's Pi 5. Ich habe hierzu den den Kurs dort herauskopiert und es entsprechend angepasst. Ich habe es aus den Gründen gemacht, da ich USB unterstützen möchte und es inzwischen mehr Informationen zum RPI4 gibt, als für den RPI5.
== Einführung ==
In diesem Kurs werden wir die Grundlagen der Programmierung des Raspberry Pi 4 erlernen. Ähnlich wie bei der Programmierung des Raspberry Pi 5, gibt es einige Unterschiede, hauptsächlich bedingt durch die Unterstützung von USB und die umfangreichere Dokumentation für den Raspberry Pi 4. Für diesen Kurs habe ich Material vom Raspberry Pi 5 angepasst, um die Programmierung des Raspberry Pi 4 zu erläutern.


Idealerweise hatte ich dort ein Terminal programmiert, welches ich sehr gut zum Debuggen verwenden kann. In diesem Sinne, kann ich den Kurs entsprechend hier auch einführen.  
Ein Terminal wurde programmiert, das sich sehr gut zum Debuggen eignet. Dies werden wir in unserem Kurs ebenfalls verwenden.


-----
-----
== Ziel unseres ersten Programms ==
Unser erstes Programm wird eine einfache Endlosschleife enthalten. Dies dient als Basis für weitere Experimente und Versuche. Ich werde erklären, wie ein solches Programm erstellt, kompiliert und ausgeführt wird.


Unser erstes Programm wird zunächst nichts tun. Es wird einfach eine Dauerschleife durchlaufen. Dies dient als Grundbaustein für alle weiteren Versuche. Ich werde erklären, wie ein solches Programm erstellt, kompiliert und ausgeführt wird. Zunächst schauen wir, ob alles funktioniert.
== Erstellung des Sourcecodes ==
=== Sourcecode erstellen ===
Leider kommen wir bei Bare-Metal-Programmierung in C nicht um Assembler herum. Auch wenn das Assemblerprogramm zunächst nicht viel tut, wird es später beispielsweise für die Interrupt-Programmierung benötigt.
Leider kommen wir bei BareMetal mit C nicht an Assembler vorbei. Auch wenn das Assemblerprogramm zur Zeit nicht wirklich viel tut, wird es später zum Beispiel für die Interrupt-Progrogramierung benötigt.  
 
=== Assembler-Code (boot.S) ===
 
Öffnen Sie ein Textprogramm und schreiben Sie folgendes erstes Assemblerprogramm:


Öffnen Sie ein Textprogramm, um unser erstes Assemblerprogramm zu schreiben:
<syntaxhighlight lang="GAS">
<syntaxhighlight lang="GAS">
//
//
Zeile 23: Zeile 28:
   b main            // Branch to "main"
   b main            // Branch to "main"
</syntaxhighlight>
</syntaxhighlight>
Speichern Sie die Datei im Standardverzeichnis unter Windows: C:\msys64\home\xxx (wobei xxx in der Regel Ihr Benutzername ist). Unter Linux können Sie die Datei im Home-Verzeichnis speichern. Geben Sie der Datei den Namen boot.S. Die Endung .S kennzeichnet die Datei als Assembler-Sourcecode.
Speichern Sie die Datei im Standardverzeichnis unter Windows: C:\msys64\home\xxx (wobei xxx in der Regel Ihr Benutzername ist). Unter Linux können Sie die Datei im Home-Verzeichnis speichern. Geben Sie der Datei den Namen boot.S. Die Endung .S kennzeichnet die Datei als Assembler-Sourcecode.


Da wir ja in C programmieren wollen schreiben wir noch ein kleines main:
=== C-Code (main.c) ===
 
Nun erstellen wir eine main.c Datei für unser erstes Programm:
 
<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
//
//
Zeile 38: Zeile 47:
}
}
</syntaxhighlight>
</syntaxhighlight>
Hier wird einfach eine Endlosschleife erzeugt.


=== Kompilieren des Programms mit make ===
Hier wird eine einfache Endlosschleife erzeugt.


Um nun unser Programm zu Kompilieren, muss zunächst die Programmierumgebung eingerichtet werden. Verwende dazu folgende Anleitungen. Diese funktionieren auch für den RPI4:
== Kompilieren des Programms mit Make ==
Um unser Programm zu kompilieren, muss zunächst die Programmierumgebung eingerichtet werden. Verwenden Sie hierzu die entsprechenden Anleitungen. Diese funktionieren auch für den Raspberry Pi 4:


[[Programmierumgebung erstellen (Konsole)]]
[[Programmierumgebung erstellen (Konsole)]]
[[Programmierumgebung erstellen (64-Bit)]]
[[Programmierumgebung erstellen (64-Bit)]]


Wenn wir dies nun erstellt haben, können wir ein Makefile erstellen, welches wir für den RPI4 angepasst haben:
Erstellen Sie dann ein Makefile, angepasst für den Raspberry Pi 4:
 
<syntaxhighlight lang="makefile">
<syntaxhighlight lang="makefile">
CSRCS := $(wildcard *.c)
CSRCS := $(wildcard *.c)
Zeile 67: Zeile 78:


CPPFLAGS = -fno-exceptions -fno-rtti -nostdinc++ -DAARCH=64 -mcpu=cortex-a72 -mlittle-endian -Wall -fsigned-char \
CPPFLAGS = -fno-exceptions -fno-rtti -nostdinc++ -DAARCH=64 -mcpu=cortex-a72 -mlittle-endian -Wall -fsigned-char \
  -ffreestanding -g -I ./include -O0 -mstrict-align -std=c++14 -Wno-aligned-new
              -ffreestanding -g -I ./include -O0 -mstrict-align -std=c++14 -Wno-aligned-new


all: clean new kernel8.img
all: clean new kernel8.img


%.o: %.S
%.o: %.S
@echo "as $@"
    @echo "as $@"
@aarch64-none-elf-gcc $(AFLAGS) -c $< -o $@
    @aarch64-none-elf-gcc $(AFLAGS) -c $< -o $@


%.o: %.c
%.o: %.c
@echo "gcc $@"
    @echo "gcc $@"
@aarch64-none-elf-gcc $(CFLAGS) -c $< -o $@
    @aarch64-none-elf-gcc $(CFLAGS) -c $< -o $@


%.o: %.cpp
%.o: %.cpp
@echo "g++ $@"
    @echo "g++ $@"
@aarch64-none-elf-g++ $(CPPFLAGS) -c $< -o $@
    @aarch64-none-elf-g++ $(CPPFLAGS) -c $< -o $@


kernel8.img: $(AllOBJS)
kernel8.img: $(AllOBJS)
@echo "============================================================================="
    @echo "============================================================================="
@echo "Linking..."
    @echo "Linking..."
@aarch64-none-elf-ld -o kernel8.elf -Map kernel8.map -nostdlib \
    @aarch64-none-elf-ld -o kernel8.elf -Map kernel8.map -nostdlib \
--section-start=.init=$(LOADADDR) --no-warn-rwx-segments \
        --section-start=.init=$(LOADADDR) --no-warn-rwx-segments \
-g -T linker.ld $(AllOBJS)
        -g -T linker.ld $(AllOBJS)
aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img
    aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img


clean:
clean:
/bin/rm -f kernel8.elf kernel8.map *.o *.img > /dev/null 2> /dev/null || true
    /bin/rm -f kernel8.elf kernel8.map *.o *.img > /dev/null 2> /dev/null || true


new:
new:
/bin/clear
    /bin/clear
</syntaxhighlight>
</syntaxhighlight>
Speicher diese Datei als "makefile" ab. Wenn du mehr über den Inhalt erfahren möchtest, so schaue unter [[Arbeiten mit Make und Linker-Script]] nach.


Was nun noch fehlt, ist ein Linker-Script, welches wir bereits auch unter [[Arbeiten mit Make und Linker-Script]] beschrieben haben:
Speichern Sie diese Datei unter dem Namen Makefile.
 
Wenn Sie mehr über den Inhalt erfahren möchten, schauen Sie unter "[[Arbeiten mit Make und Linker-Script]]" nach.
 
== Linker-Script (linker.ld) ==
Erstellen Sie das folgende Linker-Script, welches wir unter [[Arbeiten mit Make und Linker-Script]] beschrieben haben:
 
<syntaxhighlight lang="shell">
<syntaxhighlight lang="shell">
ENTRY(_start)
ENTRY(_start)
Zeile 105: Zeile 121:
SECTIONS
SECTIONS
{
{
.init : {
    .init : {
*(.init)
        *(.init)
}
    }
.text : {
    .text : {
*(.text*)
        *(.text*)
_etext = .;
        _etext = .;
}
    }
.rodata : {
    .rodata : {
*(.rodata*)
        *(.rodata*)
}
    }
.init_array : {
    .init_array : {
__init_start = .;
        __init_start = .;
KEEP(*(.init_array*))
        KEEP(*(.init_array*))
__init_end = .;
        __init_end = .;
}
    }
.ARM.exidx : {
    .ARM.exidx : {
__exidx_start = .;
        __exidx_start = .;
*(.ARM.exidx*)
        *(.ARM.exidx*)
__exidx_end = .;
        __exidx_end = .;
}
    }
.eh_frame : {
    .eh_frame : {
*(.eh_frame*)
        *(.eh_frame*)
}
    }
.data : {
    .data : {
*(.data*)
        *(.data*)
}
    }
.bss : {
    .bss : {
__bss_start = .;
        __bss_start = .;
*(.bss*)
        *(.bss*)
*(COMMON)
        *(COMMON)
__bss_end = .;
        __bss_end = .;
}
    }
}
}
__bss_size = (__bss_end - __bss_start) >> 3;
__bss_size = (__bss_end - __bss_start) >> 3;
</syntaxhighlight>
</syntaxhighlight>
Speichere diese Datei als "linker.ld" ab.
Speichern Sie diese Datei unter dem Namen linker.ld.


== Kompilieren und Ausführen ==
Nun können wir unser erstes Programm kompilieren:
Nun können wir unser erstes Programm kompilieren:
<syntaxhighlight lang="shell">
<syntaxhighlight lang="shell">
make
make
</syntaxhighlight>
</syntaxhighlight>


Damit unser Kernel nun funktionsfähig ist, muss der Kernel auf die SD-Karte, die in FAT32 formatiert ist, kopiert werden. Zusätzlich benötigt der Raspberry Pi 4 folgende Dateien auf der SD-Karte:
Damit unser Kernel funktionsfähig ist, muss der Kernel auf eine SD-Karte, die in FAT32 formatiert ist, kopiert werden. Zusätzlich benötigt der Raspberry Pi 4 folgende Dateien auf der SD-Karte:


LISTE!!!
* bootcode.bin
* start.elf
* config.txt
* kernel8.img (Ihr kompiliertes Programm)


Du kannst den Source-Code als ZIP-Datei mit folgenden Link downloaden: https://www.satyria.de/arm/sources/RPI4/C/1.zip
Den Source-Code können Sie als ZIP-Datei [https://www.satyria.de/arm/sources/RPI4/C/1.zip hier] herunterladen.


-----
-----

Aktuelle Version vom 19. April 2025, 08:18 Uhr

Einführung

In diesem Kurs werden wir die Grundlagen der Programmierung des Raspberry Pi 4 erlernen. Ähnlich wie bei der Programmierung des Raspberry Pi 5, gibt es einige Unterschiede, hauptsächlich bedingt durch die Unterstützung von USB und die umfangreichere Dokumentation für den Raspberry Pi 4. Für diesen Kurs habe ich Material vom Raspberry Pi 5 angepasst, um die Programmierung des Raspberry Pi 4 zu erläutern.

Ein Terminal wurde programmiert, das sich sehr gut zum Debuggen eignet. Dies werden wir in unserem Kurs ebenfalls verwenden.


Ziel unseres ersten Programms

Unser erstes Programm wird eine einfache Endlosschleife enthalten. Dies dient als Basis für weitere Experimente und Versuche. Ich werde erklären, wie ein solches Programm erstellt, kompiliert und ausgeführt wird.

Erstellung des Sourcecodes

Leider kommen wir bei Bare-Metal-Programmierung in C nicht um Assembler herum. Auch wenn das Assemblerprogramm zunächst nicht viel tut, wird es später beispielsweise für die Interrupt-Programmierung benötigt.

Assembler-Code (boot.S)

Öffnen Sie ein Textprogramm und schreiben Sie folgendes erstes Assemblerprogramm:

//
// The first program for RPI4
// 20.02.2025 www.satyria.de
//

.section .init      // Ensure the linker places this at the beginning of the kernel image
.globl _start       // Generates a global label
_start:             // The label _start (entry address)

  mov sp, #0x80000  // Create a stack of 512KB (524288 bytes)
  b main            // Branch to "main"

Speichern Sie die Datei im Standardverzeichnis unter Windows: C:\msys64\home\xxx (wobei xxx in der Regel Ihr Benutzername ist). Unter Linux können Sie die Datei im Home-Verzeichnis speichern. Geben Sie der Datei den Namen boot.S. Die Endung .S kennzeichnet die Datei als Assembler-Sourcecode.

C-Code (main.c)

Nun erstellen wir eine main.c Datei für unser erstes Programm:

//
// main.c
// The first program for RPI4
// 20.02.2025 www.satyria.de
//

int main (void)
{
  while (1){}
}

Hier wird eine einfache Endlosschleife erzeugt.

Kompilieren des Programms mit Make

Um unser Programm zu kompilieren, muss zunächst die Programmierumgebung eingerichtet werden. Verwenden Sie hierzu die entsprechenden Anleitungen. Diese funktionieren auch für den Raspberry Pi 4:

Programmierumgebung erstellen (Konsole)

Programmierumgebung erstellen (64-Bit)

Erstellen Sie dann ein Makefile, angepasst für den Raspberry Pi 4:

CSRCS := $(wildcard *.c)
CPPSRCS := $(wildcard *.cpp)
ASRCS := $(wildcard *.S)
COBJS := $(CSRCS:.c=.o)
CPPOBJS := $(CPPSRCS:.cpp=.o)
AOBJS := $(ASRCS:.S=.o)
AllOBJS := $(COBJS) $(CPPOBJS) $(AOBJS)
LOADADDR = 0x80000

GCCFLAGS = -DAARCH=64 -mcpu=cortex-a72 -mlittle-endian -Wall -O0 -ffreestanding \
           -nostartfiles -nostdlib -nostdinc -g -I ./include

AFLAGS = -DAARCH=64 -mcpu=cortex-a72 -mlittle-endian  -I ./include -O0 -g

CFLAGS = -DAARCH=64 -mcpu=cortex-a72 -mlittle-endian -Wall -fsigned-char -ffreestanding -g \
         -I ./include -O0 -fno-exceptions 

CPPFLAGS = -fno-exceptions -fno-rtti -nostdinc++ -DAARCH=64 -mcpu=cortex-a72 -mlittle-endian -Wall -fsigned-char \
              -ffreestanding -g -I ./include -O0 -mstrict-align -std=c++14 -Wno-aligned-new

all: clean new kernel8.img

%.o: %.S
    @echo "as $@"
    @aarch64-none-elf-gcc $(AFLAGS) -c $< -o $@

%.o: %.c
    @echo "gcc $@"
    @aarch64-none-elf-gcc $(CFLAGS) -c $< -o $@

%.o: %.cpp
    @echo "g++ $@"
    @aarch64-none-elf-g++ $(CPPFLAGS) -c $< -o $@

kernel8.img: $(AllOBJS)
    @echo "============================================================================="
    @echo "Linking..."
    @aarch64-none-elf-ld -o kernel8.elf -Map kernel8.map -nostdlib \
        --section-start=.init=$(LOADADDR) --no-warn-rwx-segments \
        -g -T linker.ld $(AllOBJS)
    aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img

clean:
    /bin/rm -f kernel8.elf kernel8.map *.o *.img > /dev/null 2> /dev/null || true

new:
    /bin/clear

Speichern Sie diese Datei unter dem Namen Makefile.

Wenn Sie mehr über den Inhalt erfahren möchten, schauen Sie unter "Arbeiten mit Make und Linker-Script" nach.

Linker-Script (linker.ld)

Erstellen Sie das folgende Linker-Script, welches wir unter Arbeiten mit Make und Linker-Script beschrieben haben:

ENTRY(_start)

SECTIONS
{
    .init : {
        *(.init)
    }
    .text : {
        *(.text*)
        _etext = .;
    }
    .rodata : {
        *(.rodata*)
    }
    .init_array : {
        __init_start = .;
        KEEP(*(.init_array*))
        __init_end = .;
    }
    .ARM.exidx : {
        __exidx_start = .;
        *(.ARM.exidx*)
        __exidx_end = .;
    }
    .eh_frame : {
        *(.eh_frame*)
    }
    .data : {
        *(.data*)
    }
    .bss : {
        __bss_start = .;
        *(.bss*)
        *(COMMON)
        __bss_end = .;
    }
}
__bss_size = (__bss_end - __bss_start) >> 3;

Speichern Sie diese Datei unter dem Namen linker.ld.

Kompilieren und Ausführen

Nun können wir unser erstes Programm kompilieren:

make

Damit unser Kernel funktionsfähig ist, muss der Kernel auf eine SD-Karte, die in FAT32 formatiert ist, kopiert werden. Zusätzlich benötigt der Raspberry Pi 4 folgende Dateien auf der SD-Karte:

  • bootcode.bin
  • start.elf
  • config.txt
  • kernel8.img (Ihr kompiliertes Programm)

Den Source-Code können Sie als ZIP-Datei hier herunterladen.


< Hauptseite > Weiter (Lass die LED leuchten in C (PI4)) >