Bare-Metal Debugging (JTAG, RPI 4)
Einleitung
Bei der Entwicklung von Software wird oft ein Debugger benötigt, um Fehler zu finden und zu beheben. Ein gängiger Debugger ist GDB. Solange das Programm aus einer Umgebung wie Linux oder Windows startbar ist, kann dieser Debugger direkt verwendet werden. Bei der Bare-Metal-Entwicklung sind die Dinge jedoch etwas schwieriger, da hier die gewohnte Betriebssystemumgebung fehlt. In solchen Fällen sind Emulatoren oder spezielle Hardwarelösungen nötig.
Da Emulatoren meist nicht zu 100 % die Hardware widerspiegeln können, verwenden wir das Hostsystem, welches direkt mit dem Raspberry Pi 4 (RPI4) kommuniziert. Das Programm wird auf dem Raspberry Pi direkt ausgeführt. Über das Hostsystem können wir jedoch den Code direkt manipulieren und sehen das Ergebnis unmittelbar auf der Hardware.
Der Raspberry Pi unterstützt für solche Kommunikation das JTAG-Protokoll. Leider können Hostsysteme dieses Protokoll selten direkt bereitstellen, weshalb wir auf zusätzliche Hardware angewiesen sind. Eine günstige Option ist das "CJMCU FT232H Modul" (https://amzn.eu/d/hb8tKuA), welches ich für diese Anleitung verwendet habe.
Hardware vorbereiten
Das FT232H Modul wurde zwar mit Stiftleisten geliefert, aber diese mussten zunächst auf das Modul aufgelötet werden. Anschließend konnte mit Kabelbrücken die Verbindung mit dem Raspberry Pi 4 hergestellt werden.
Verdrahtung
Die Verdrahtung zwischen dem FT232H Modul und dem Raspberry Pi 4 erfolgt wie folgt:

| FT232H | Raspi 4 | |
|---|---|---|
| Name | GPIO | PIN |
| AD0 | GPIO25 | 22 |
| AD1 | GPIO26 | 37 |
| AD2 | GPIO24 | 18 |
| AD3 | GPIO27 | 13 |
| AD4 | GPIO22 | 15 |
| AD7 | GPIO23 | 16 |
| GND | GND | 6 (9,14,20,25,30,34,39) |
Verwendung von Windows
Unter Windows verwenden wir die MSYS2-Umgebung. Diese bietet eine Unix-ähnliche Umgebung unter Windows und ist nützlich für viele Entwicklungsaufgaben.
Installation und Einrichtung
Falls noch nicht geschehen, installieren wir MSYS2 nach folgender Anleitung Programmierumgebung erstellen (Konsole) und richten die Programmierumgebung ein, indem wir die folgenden Befehle ausführen:
- Starte MSYS2 MSYS.
- Gib die folgenden Befehle ein, um OpenOCD zu installieren und die Umgebungsvariablen zu setzen:
pacman -S mingw64/mingw-w64-x86_64-openocd
echo 'export PATH="/mingw64/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Konfigurationsdatei erstellen
Leider fehlt uns eine Konfigurationsdatei, die JTAG und FT232H beschreibt. Daher erstellen wir eine Datei namens ft232h-jtag.cfg mit folgendem Inhalt:
adapter speed 3000
adapter driver ftdi
ftdi vid_pid 0x0403 0x6014
ftdi layout_init 0x0078 0x017b
ftdi_tdo_sample_edge falling
ftdi layout_signal nTRST -ndata 0x0010 -noe 0x0040
ftdi layout_signal nSRST -ndata 0x0020 -noe 0x0040
transport select jtag
Diese Datei legen wir ins Verzeichnis "C:\msys64\mingw64\share\openocd\scripts\interface\ftdi" ab, wenn MSYS2 nach den Vorgaben installiert wurde.
Treiberinstallation
OpenOCD möchte auf das Gerät über libusb zugreifen. In der Regel bietet Windows diesen Treiber nicht, sodass wir hier noch einen Treiber installieren müssen. Dazu verwenden wir "Zadig", welches von https://zadig.akeo.ie/ heruntergeladen werden kann.
- Installiere Zadig.
- Starte Zadig und aktiviere im Menü "Options" die Option "List All Devices".
- Wähle "Single RS232-HS" aus der Geräteliste.
- Als Treiber wähle "libusbK" aus und klicke auf "Replace Driver".
Damit ist Windows bereit, mit dem Modul über OpenOCD zu kommunizieren.
Verwendung von Linux
Installation und Einrichtung
Zunächst benötigen wir OpenOCD:
sudo apt update
sudo apt install openocd
Konfigurationsdatei erstellen
Leider fehlt uns eine Konfigurationsdatei, die JTAG und FT232H beschreibt. Daher erstellen wir eine Datei namens ft232h-jtag.cfg mit folgendem Inhalt:
adapter speed 3000
adapter driver ftdi
ftdi vid_pid 0x0403 0x6014
ftdi layout_init 0x0078 0x017b
ftdi_tdo_sample_edge falling
ftdi layout_signal nTRST -ndata 0x0010 -noe 0x0040
ftdi layout_signal nSRST -ndata 0x0020 -noe 0x0040
transport select jtag
Diese Datei legen wir ins Verzeichnis "/usr/share/openocd/scripts/interface/ftdi" ab.
Raspberry Pi vorbereiten
Nachdem unser Betriebssystem auf dem Raspberry Pi eingerichtet ist, müssen wir den Raspberry Pi 4 für Bare-Metal-Programmierung vorbereiten. Dazu schreiben wir ein kleines Programm, das nur eine Endlosschleife erzeugt, aber den Raspberry Pi startet:
//
// The boot program for RPI4
// 07.03.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)
b _start // endless loop
Wir verwenden ein Makefile, um das Programm zu kompilieren und zu linken (siehe Unser erstes Programm in C (PI4)#Kompilieren des Programms mit Make). Den erzeugten "kernel8.img" kopieren wir auf die SD-Karte. Beachte, dass auch folgende Dateien im Root-Verzeichnis der SD-Karte liegen müssen:
- bcm2711-rpi-4-b.dtb
- bootcode.bin
- fixup4.dat
- start4.elf
Nun müssen wir dem Raspberry Pi auch mitteilen, dass er auf JTAG reagiert. Dazu erstellen wir eine config.txt mit folgendem Inhalt:
gpio=22-27=np
enable_jtag_gpio=1Diese Datei legen wir ebenfalls in das Root-Verzeichnis der SD-Karte.
Starten von OpenOCD und Verwendung von GDB
Verbindung herstellen
Verbinde zunächst das FT232H-Modul per USB mit dem Hostsystem. Auf dem Modul sollte nun eine LED leuchten, welche anzeigt, dass das Gerät betriebsbereit ist. Starte den Raspberry Pi. Wenn du ihn an einem Monitor angeschlossen hast, wird auf dem Bildschirm das Regenbogenbild angezeigt, was bedeutet, dass das System gestartet wurde.
Starte nun "MSYS2 MSYS" oder unter Linux ein Terminal:

openocd -f interface/ftdi/ft232h-jtag.cfg -f target/bcm2711.cfg
Wenn alles korrekt eingerichtet ist, sollte OpenOCD alle vier CPUs des Raspberry Pi anzeigen. Es zeigt außerdem, dass die Ports 3333, 3334, 3335 und 3336 für die GDB-Kommunikation bereitgestellt werden.
Kernel-Upload und Debugging
Um sicherzugehen, öffnen wir ein weiteres "MSYS2 MSYS"-Fenster oder zweites Terminal unter Linux. Hier wechseln wir in das Verzeichnis, welches den zu testenden Code enthält. Falls der Code noch nicht kompiliert ist, muss dies jetzt erledigt werden.
Verwende für diese Versuche den kernel8.elf:
aarch64-none-elf-gdb
target extended-remote :3333Dies stellt eine Verbindung zur CPU0 her.
Lade nun den Kernel auf den Raspberry Pi hoch:
load kernel8.elfDamit der Kernel nun startet, verwenden wir die folgenden Befehle:
set $pc = 0x80000
continueOhne Kernel-Upload
Wenn bereits auf dem Raspberry PI 4 der Kernel läuft, verwende folgendes:
aarch64-none-elf-gdb kernel8.elfIm Debugger dann:
target extended-remote :3333