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

Aus C und Assembler mit Raspberry
 
(4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 9: Zeile 9:


== Erstellung des Sourcecodes ==
== 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.
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. Erstelle zunächst das Verzeichnis "src", in der wir unseren Sourcecode ablegen werden.


=== Assembler-Code (boot.S) ===
=== Assembler-Code (boot.S) ===
Zeile 15: Zeile 15:
Öffnen Sie ein Textprogramm und schreiben Sie folgendes erstes Assemblerprogramm:
Öffnen Sie ein Textprogramm und schreiben Sie folgendes erstes Assemblerprogramm:


<syntaxhighlight lang="GAS">
<syntaxhighlight lang="asm">
//
//
// The first program for RPI4
// The first program for RPI4
Zeile 57: Zeile 57:
[[Programmierumgebung erstellen (64-Bit)]]
[[Programmierumgebung erstellen (64-Bit)]]


Erstellen Sie dann ein Makefile, angepasst für den Raspberry Pi 4:
Erstellen Sie dann ein Makefile, welches im Root-Verzeichnis des Projektes abgelegt wird:


<syntaxhighlight lang="makefile">
<syntaxhighlight lang="makefile">
CSRCS := $(wildcard *.c)
CSRCS := $(wildcard src/*.c)
CPPSRCS := $(wildcard *.cpp)
CPPSRCS := $(wildcard src/*.cpp)
ASRCS := $(wildcard *.S)
ASRCS := $(wildcard src/*.S)
COBJS := $(CSRCS:.c=.o)
COBJS := $(CSRCS:.c=.o)
CPPOBJS := $(CPPSRCS:.cpp=.o)
CPPOBJS := $(CPPSRCS:.cpp=.o)
Zeile 78: 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
ifeq ($(MSYSTEM),)
# --- Native Windows Umgebung (CMD / PowerShell) ---
# @cls
@if exist kernel8.elf del /q /f kernel8.elf
@if exist kernel8.img del /q /f kernel8.img
@if exist kernel8.map del /q /f kernel8.map
@if exist src\*.o del /q /f src\*.o
else
# --- MSYS2 / Unix-ähnliche Umgebung ---
/bin/rm -f kernel8.elf kernel8.map src/*.o *.img > /dev/null 2> /dev/null || true
endif


new:
new:
    /bin/clear
ifeq ($(MSYSTEM),)
@cls
else
/bin/clear
endif
</syntaxhighlight>
</syntaxhighlight>


Zeile 114: Zeile 128:


== Linker-Script (linker.ld) ==
== Linker-Script (linker.ld) ==
Erstellen Sie das folgende Linker-Script, welches wir unter [[Arbeiten mit Make und Linker-Script]] beschrieben haben:
Erstellen Sie das folgende Linker-Script, welches wir unter [[Arbeiten mit Make und Linker-Script]] beschrieben haben. Dieses wird, wie das Makefile in das Root-Verzeichnis abgelegt:


<syntaxhighlight lang="shell">
<syntaxhighlight lang="shell">
Zeile 159: Zeile 173:


== Kompilieren und Ausführen ==
== Kompilieren und Ausführen ==
Bevor wir kompilieren, prüfe deine Verzeichnisstruktur:
<syntaxhighlight lang="shell">
C:.
|  linker.ld
|  makefile
|     
\---src
        boot.S
        main.c
</syntaxhighlight>
Nun können wir unser erstes Programm kompilieren:
Nun können wir unser erstes Programm kompilieren:



Aktuelle Version vom 5. Juni 2026, 11:25 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. Erstelle zunächst das Verzeichnis "src", in der wir unseren Sourcecode ablegen werden.

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, welches im Root-Verzeichnis des Projektes abgelegt wird:

CSRCS := $(wildcard src/*.c)
CPPSRCS := $(wildcard src/*.cpp)
ASRCS := $(wildcard src/*.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:
ifeq ($(MSYSTEM),)
# --- Native Windows Umgebung (CMD / PowerShell) ---
# @cls
	@if exist kernel8.elf del /q /f kernel8.elf
	@if exist kernel8.img del /q /f kernel8.img
	@if exist kernel8.map del /q /f kernel8.map
	@if exist src\*.o del /q /f src\*.o
else
# --- MSYS2 / Unix-ähnliche Umgebung ---
	/bin/rm -f kernel8.elf kernel8.map src/*.o *.img > /dev/null 2> /dev/null || true
endif

new:
ifeq ($(MSYSTEM),)
	@cls
else
	/bin/clear
endif

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. Dieses wird, wie das Makefile in das Root-Verzeichnis abgelegt:

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

Bevor wir kompilieren, prüfe deine Verzeichnisstruktur:

C:.
|   linker.ld
|   makefile
|       
\---src
        boot.S
        main.c

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)) >