Als erfahrener Entwickler im Bereich eingebetteter Systeme möchte ich Ihnen einen detaillierten Leitfaden zum RK3588-Chipsatz von Rockchip vorstellen. Dieser leistungsstarke SoC (System-on-Chip) hat sich in verschiedenen Anwendungsbereichen wie Edge Computing, KI-Beschleunigung und High-End-Embedded-Systemen etabliert. Dieser Leitfaden folgt den EEAT-Prinzipien (Expertise, Erfahrung, Autorität und Vertrauenswürdigkeit) und bietet fundierte Informationen für Entwickler aller Erfahrungsstufen.

Inhaltsverzeichnis

  1. Technische Spezifikationen des RK3588
  2. Entwicklungsumgebung einrichten
  3. Bootloader und Firmware
  4. Betriebssystem-Konfiguration
  5. Hardwareschnittstellen programmieren
  6. GPU- und NPU-Programmierung
  7. Multimedia-Verarbeitung
  8. Energiemanagement
  9. Debugging und Leistungsoptimierung
  10. Anwendungsfälle und Projekte

1. Technische Spezifikationen des RK3588

Der RK3588 ist ein hochintegrierter Octa-Core-Prozessor mit ARM-Architektur, entwickelt von Rockchip Electronics. Er kombiniert leistungsstarke CPU-Kerne mit spezialisierten Beschleunigern für KI, Grafik und Multimedia.

CPU-Architektur

  • 4x ARM Cortex-A76 Kerne (bis zu 2,4 GHz)
  • 4x ARM Cortex-A55 Kerne (bis zu 1,8 GHz)
  • big.LITTLE-Architektur mit dynamischem Lastausgleich

GPU und NPU

  • ARM Mali-G610 MP4 GPU (OpenGL ES 3.2, Vulkan 1.2)
  • 6 TOPS NPU (Neural Processing Unit) für KI-Beschleunigung
  • Unterstützung für TensorFlow, PyTorch, ONNX und andere KI-Frameworks

RAM und Speicher

  • LPDDR4/LPDDR4X/LPDDR5-Unterstützung (bis zu 32 GB)
  • eMMC 5.1, UFS 3.0, SD/MMC
  • PCIe 3.0 für schnelle Speichererweiterungen

Multimedia-Fähigkeiten

  • 8K-Videodekodierung (H.265/HEVC, H.264, VP9, AV1)
  • 8K-Videoenkodierung (H.265/HEVC, H.264)
  • HDR10, HDR10+, HLG-Unterstützung
  • HDMI 2.1, DisplayPort 1.4, eDP, MIPI-DSI

Konnektivität

  • 2,5-Gigabit-Ethernet
  • USB 3.1, USB 2.0, USB-C
  • MIPI-CSI für Kameraschnittstellen
  • PCIe für Erweiterungskarten

2. Entwicklungsumgebung einrichten

Für die Entwicklung mit dem RK3588 benötigen Sie eine spezialisierte Umgebung. Hier führe ich Sie durch die notwendigen Schritte.

Benötigte Hardware

  • RK3588-Entwicklungsboard (z.B. Rock 5 Model B, Radxa CM5, Orange Pi 5)
  • USB-zu-TTL-Serielladapter für Konsolenzugriff
  • Stromversorgung (12V/3A empfohlen)
  • microSD-Karte (A2-Klasse, mindestens 32 GB)
  • Netzwerkkabel für Ethernet-Verbindung
  • HDMI-Kabel für Display-Ausgabe

Software-Voraussetzungen

  • Linux-Entwicklungsumgebung (Ubuntu 20.04 LTS oder höher empfohlen)
  • Cross-Compiler-Toolchain für ARM64
  • Git für Quellcode-Management
  • Docker (optional, für containerisierte Entwicklung)

Installation der Entwicklungswerkzeuge

bash
# Grundlegende Pakete installieren
sudo apt update
sudo apt install -y git make gcc g++ build-essential libncurses-dev unzip python3 python3-pip

# ARM64-Toolchain installieren
sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

# Rockchip-spezifische Tools installieren
git clone https://github.com/rockchip-linux/rkbin.git
git clone https://github.com/rockchip-linux/rkdeveloptool.git
cd rkdeveloptool
autoreconf -i
./configure
make
sudo make install

Repository-Struktur für RK3588-Entwicklung

rk3588-dev/
├── kernel/            # Linux-Kernel-Quellcode
├── u-boot/            # U-Boot-Bootloader
├── buildroot/         # Rootfs-Generierung
├── rkbin/             # Rockchip-Binärdateien
├── device/            # Gerätespezifische Konfigurationen
├── toolchain/         # Cross-Compiler und Werkzeuge
└── output/            # Kompilierte Binärdateien

3. Bootloader und Firmware

Der Bootprozess des RK3588 umfasst mehrere Stufen, beginnend mit der ROM-Firmware bis hin zum Betriebssystem.

Bootsequenz

  1. BootROM (maskierter ROM im SoC)
  2. Miniloader/SPL (First-Stage-Bootloader)
  3. U-Boot (Second-Stage-Bootloader)
  4. Linux-Kernel
  5. Rootfs (Root-Dateisystem)

U-Boot für RK3588 kompilieren

bash
# U-Boot-Repository klonen
git clone https://github.com/u-boot/u-boot.git
cd u-boot

# Auf einen stabilen Branch wechseln
git checkout v2023.01

# RK3588-spezifische Patches anwenden
wget https://gitlab.com/rockchip-linux/u-boot/-/archive/next-dev/u-boot-next-dev.tar.gz
tar xf u-boot-next-dev.tar.gz
cp -r u-boot-next-dev/arch/arm/mach-rockchip/rk3588 arch/arm/mach-rockchip/

# Konfiguration für Ihr spezifisches Board auswählen
make CROSS_COMPILE=aarch64-linux-gnu- rock-5b-rk3588_defconfig

# U-Boot kompilieren
make CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

Tabelle: Rockchip-Bootmodi und Ihre Verwendung

Bootmodus Beschreibung Aktivierung Anwendungsfall
Maskierter ROM Integrierter ROM im SoC Automatisch beim Einschalten Initialer Bootprozess
Recovery Recovery-Modus USB-OTG + Netzwerktaste Firmware-Wiederherstellung
Loader Firmware-Update-Modus USB-OTG im Maskierten ROM Flashing neuer Firmware
Normal Standardmäßiger Boot Ohne spezielle Tasten Regulärer Systembetrieb
Fastboot Android-Fastboot USB-OTG + Bootmodus-Taste Android-System-Updates

Firmware-Update mit rkdeveloptool

bash
# Gerät in Maskierter-ROM-Modus versetzen
# (Drücken Sie die Recovery-Taste während des Einschaltens)

# Verbindung mit dem Gerät überprüfen
sudo rkdeveloptool ld

# Loader hochladen
sudo rkdeveloptool db rkbin/bin/rk35/rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.08.bin

# Partition-Table hochladen
sudo rkdeveloptool gpt parameter.txt

# Bootloader-Image flashen
sudo rkdeveloptool wl 0x4000 u-boot/u-boot.img

# System-Image flashen (für schnelle Tests)
sudo rkdeveloptool wl 0x40000 system.img

# Gerät neu starten
sudo rkdeveloptool rd

4. Betriebssystem-Konfiguration

Der RK3588 unterstützt verschiedene Betriebssysteme, darunter Linux-Distributionen wie Debian, Ubuntu und spezialisierte Systeme wie Android.

Linux-Kernel für RK3588 kompilieren

bash
# Kernel-Repository klonen
git clone https://github.com/torvalds/linux.git
cd linux

# Rockchip-Patches anwenden
git remote add rockchip https://github.com/rockchip-linux/kernel.git
git fetch rockchip
git checkout -b rk3588-dev rockchip/develop-5.10

# Kernel konfigurieren
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

# Wichtige Konfigurationsoptionen:
# - CONFIG_ARCH_ROCKCHIP=y
# - CONFIG_CPU_RK3588=y
# - CONFIG_DRM_ROCKCHIP=y
# - CONFIG_VIDEO_ROCKCHIP_ISP=y
# - CONFIG_ROCKCHIP_MPP_SERVICE=y

# Kernel kompilieren
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image dtbs modules

# Module installieren
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=../rootfs modules_install

Root-Dateisystem erstellen

Sie können ein Root-Dateisystem mit Buildroot oder durch manuelle Anpassung einer bestehenden Distribution erstellen.

Mit Buildroot:

bash
# Buildroot klonen
git clone https://git.buildroot.net/buildroot
cd buildroot

# Konfigurieren
make rockchip_rk3588_defconfig
make menuconfig

# Kompilieren
make -j$(nproc)

Mit Debian/Ubuntu:

bash
# Erstellen eines leeren Image
dd if=/dev/zero of=rootfs.img bs=1M count=4096

# Partitionieren und Formatieren
sudo parted rootfs.img --script -- mklabel gpt
sudo parted rootfs.img --script -- mkpart primary ext4 1MiB 100%
sudo losetup -fP rootfs.img
LOOPDEV=$(sudo losetup -a | grep rootfs.img | awk -F: '{print $1}')
sudo mkfs.ext4 ${LOOPDEV}p1

# Root-Dateisystem mounten
mkdir -p rootfs
sudo mount ${LOOPDEV}p1 rootfs

# Debian-Basissystem installieren
sudo debootstrap --arch=arm64 bullseye rootfs http://deb.debian.org/debian

# Konfiguration anpassen
sudo chroot rootfs
echo "rk3588-dev" > /etc/hostname
cat > /etc/fstab << EOF
/dev/mmcblk0p1 / ext4 defaults 0 1
EOF
passwd root
exit

# Unmounten
sudo umount rootfs
sudo losetup -d $LOOPDEV

Tabelle: Unterstützte Betriebssysteme für RK3588

Betriebssystem Unterstützungsgrad Besonderheiten Empfohlene Anwendungsfälle
Debian/Ubuntu Vollständig Breite Paketunterstützung, Desktop-Umgebungen Allgemeine Entwicklung, Desktop-Anwendungen
Android Gut Hardware-beschleunigte Multimedia Touchscreen-Geräte, Media-Center
Buildroot Sehr gut Minimales System, hochgradig anpassbar Eingebettete Systeme, IoT-Geräte
Yocto Gut Anpassbare Distribution-Builder Industrielle Anwendungen
ChromeOS Eingeschränkt Experimentelle Unterstützung Web-zentrierte Anwendungen

5. Hardwareschnittstellen programmieren

Der RK3588 bietet zahlreiche Hardwareschnittstellen, die für verschiedene Anwendungen genutzt werden können.

GPIO-Programmierung

Die allgemeinen Ein-/Ausgabe-Pins (GPIO) können über sysfs oder die neuere GPIO-Character-Device-API programmiert werden.

c
// GPIO über Character Device API steuern
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>

#define GPIO_CHIP_PATH "/dev/gpiochip0"
#define GPIO_PIN 17  // Beispiel-GPIO-Pin

int main() {
    struct gpiohandle_request req;
    struct gpiohandle_data data;
    int fd, ret;

    // GPIO-Chip öffnen
    fd = open(GPIO_CHIP_PATH, O_RDONLY);
    if (fd < 0) {
        perror("GPIO-Chip öffnen fehlgeschlagen");
        return EXIT_FAILURE;
    }

    // GPIO als Ausgang konfigurieren
    memset(&req, 0, sizeof(req));
    req.lineoffsets[0] = GPIO_PIN;
    req.flags = GPIOHANDLE_REQUEST_OUTPUT;
    strcpy(req.consumer_label, "gpio_test");
    req.lines = 1;

    ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
    if (ret < 0) {
        perror("GPIO-Handle-Request fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    // GPIO-Zustand umschalten (10 Mal)
    for (int i = 0; i < 10; i++) {
        data.values[0] = i % 2;  // Wechsel zwischen 0 und 1
        ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
        if (ret < 0) {
            perror("GPIO-Wert setzen fehlgeschlagen");
            break;
        }
        usleep(500000);  // 500ms warten
    }

    // Aufräumen
    close(req.fd);
    close(fd);
    return EXIT_SUCCESS;
}

I2C-Kommunikation

I2C wird häufig für die Kommunikation mit Sensoren, Displays und anderen Peripheriegeräten verwendet.

c
// I2C-Kommunikation mit einem Sensor
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>

#define I2C_BUS "/dev/i2c-3"  // Abhängig vom Board-Layout
#define SENSOR_ADDR 0x68      // Beispieladresse eines Sensors

int main() {
    int fd, ret;
    unsigned char reg = 0x0F;  // Beispiel-Register (ID-Register)
    unsigned char value;

    // I2C-Bus öffnen
    fd = open(I2C_BUS, O_RDWR);
    if (fd < 0) {
        perror("I2C-Bus öffnen fehlgeschlagen");
        return EXIT_FAILURE;
    }

    // I2C-Gerät auswählen
    if (ioctl(fd, I2C_SLAVE, SENSOR_ADDR) < 0) {
        perror("I2C-Gerät auswählen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    // Register lesen
    ret = i2c_smbus_read_byte_data(fd, reg);
    if (ret < 0) {
        perror("I2C-Lesen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    value = (unsigned char)ret;
    printf("Sensorwert: 0x%02X\n", value);

    // Aufräumen
    close(fd);
    return EXIT_SUCCESS;
}

SPI-Kommunikation

Serial Peripheral Interface (SPI) wird für schnelle Kommunikation mit Displays, Flash-Speicher und anderen Geräten verwendet.

c
// SPI-Kommunikation mit einem Gerät
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

#define SPI_DEVICE "/dev/spidev0.0"
#define SPI_SPEED 1000000  // 1 MHz

int main() {
    int fd, ret;
    uint8_t tx[] = {0x01, 0x02, 0x03};  // Zu sendende Daten
    uint8_t rx[sizeof(tx)] = {0};       // Empfangspuffer
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = sizeof(tx),
        .speed_hz = SPI_SPEED,
        .bits_per_word = 8,
    };

    // SPI-Gerät öffnen
    fd = open(SPI_DEVICE, O_RDWR);
    if (fd < 0) {
        perror("SPI-Gerät öffnen fehlgeschlagen");
        return EXIT_FAILURE;
    }

    // SPI-Modus einstellen (Mode 0)
    uint8_t mode = SPI_MODE_0;
    if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {
        perror("SPI-Modus einstellen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    // Bits pro Wort einstellen
    uint8_t bits = 8;
    if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
        perror("Bits pro Wort einstellen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    // Max. Geschwindigkeit einstellen
    if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &SPI_SPEED) < 0) {
        perror("SPI-Geschwindigkeit einstellen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    // Daten übertragen
    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret < 0) {
        perror("SPI-Übertragung fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }

    // Empfangene Daten anzeigen
    printf("Empfangene Daten: ");
    for (size_t i = 0; i < sizeof(rx); i++) {
        printf("0x%02X ", rx[i]);
    }
    printf("\n");

    // Aufräumen
    close(fd);
    return EXIT_SUCCESS;
}

6. GPU- und NPU-Programmierung

Der RK3588 verfügt über leistungsstarke Grafikprozessoren (GPU) und neuronale Verarbeitungseinheiten (NPU), die für rechenintensive Anwendungen genutzt werden können.

GPU-Programmierung mit OpenGL ES

Die Mali-G610 MP4 GPU unterstützt OpenGL ES 3.2 und Vulkan 1.2.

c
// Einfaches OpenGL ES 3.0 Beispiel
#include <GLES3/gl3.h>
#include <EGL/egl.h>
#include <stdio.h>
#include <stdlib.h>

// EGL-Variablen
EGLDisplay display;
EGLConfig config;
EGLContext context;
EGLSurface surface;

// OpenGL-Variablen
GLuint program;
GLuint vertexShader;
GLuint fragmentShader;

// Shader-Quellcode
const char *vertexShaderSource =
    "#version 300 es\n"
    "layout(location = 0) in vec3 position;\n"
    "void main() {\n"
    "   gl_Position = vec4(position, 1.0);\n"
    "}\n";

const char *fragmentShaderSource =
    "#version 300 es\n"
    "precision mediump float;\n"
    "out vec4 fragColor;\n"
    "void main() {\n"
    "   fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n";

// EGL initialisieren
int initEGL() {
    // Display öffnen
    display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        printf("EGL-Display nicht gefunden\n");
        return -1;
    }

    // EGL initialisieren
    EGLint major, minor;
    if (!eglInitialize(display, &major, &minor)) {
        printf("EGL-Initialisierung fehlgeschlagen\n");
        return -1;
    }
    printf("EGL-Version: %d.%d\n", major, minor);

    // Konfiguration wählen
    EGLint configAttribs[] = {
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 24,
        EGL_NONE
    };
    
    EGLint numConfigs;
    if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfigs)) {
        printf("EGL-Konfiguration wählen fehlgeschlagen\n");
        return -1;
    }

    // Kontext erstellen
    EGLint contextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 3,
        EGL_NONE
    };
    context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
    if (context == EGL_NO_CONTEXT) {
        printf("EGL-Kontext erstellen fehlgeschlagen\n");
        return -1;
    }

    // Pixel-Buffer-Surface erstellen
    EGLint pbufferAttribs[] = {
        EGL_WIDTH, 256,
        EGL_HEIGHT, 256,
        EGL_NONE
    };
    surface = eglCreatePbufferSurface(display, config, pbufferAttribs);
    if (surface == EGL_NO_SURFACE) {
        printf("EGL-Surface erstellen fehlgeschlagen\n");
        return -1;
    }

    // Kontext aktivieren
    if (!eglMakeCurrent(display, surface, surface, context)) {
        printf("EGL-Kontext aktivieren fehlgeschlagen\n");
        return -1;
    }

    return 0;
}

// Shader kompilieren
GLuint compileShader(GLenum type, const char *source) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &source, NULL);
    glCompileShader(shader);
    
    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        GLchar infoLog[512];
        glGetShaderInfoLog(shader, sizeof(infoLog), NULL, infoLog);
        printf("Shader-Kompilierung fehlgeschlagen: %s\n", infoLog);
        return 0;
    }
    
    return shader;
}

// OpenGL initialisieren
int initGL() {
    // Shader kompilieren
    vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
    fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
    
    // Shader-Programm erstellen
    program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        GLchar infoLog[512];
        glGetProgramInfoLog(program, sizeof(infoLog), NULL, infoLog);
        printf("Shader-Programm-Verknüpfung fehlgeschlagen: %s\n", infoLog);
        return -1;
    }
    
    // Shader nach Verknüpfung löschen
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    
    return 0;
}

// Hauptfunktion
int main() {
    // EGL und OpenGL initialisieren
    if (initEGL() < 0 || initGL() < 0) {
        return EXIT_FAILURE;
    }
    
    // Viewport einstellen
    glViewport(0, 0, 256, 256);
    
    // Bildschirm löschen
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    // Shader-Programm aktivieren
    glUseProgram(program);
    
    // Dreieck-Vertices definieren
    GLfloat vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
    };
    
    // Vertex-Buffer-Objekt erstellen
    GLuint VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    // Vertex-Attribut einstellen
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
    glEnableVertexAttribArray(0);
    
    // Dreieck zeichnen
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    // Buffer tauschen
    eglSwapBuffers(display, surface);
    
    // Kurz warten, um das Ergebnis zu sehen
    printf("Dreieck gezeichnet\n");
    sleep(1);
    
    // Aufräumen
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(program);
    eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(display, surface);
    eglDestroyContext(display, context);
    eglTerminate(display);
    
    return EXIT_SUCCESS;
}

NPU-Programmierung mit RKNN

Die NPU des RK3588 kann mit dem RKNN-SDK (Rockchip Neural Network SDK) programmiert werden.

c
// Beispiel für RKNN-Bildklassifizierung
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rknn_api.h"

#define MODEL_PATH "mobilenet_v1.rknn"
#define INPUT_SIZE 224

// Bild laden (vereinfachte Version)
unsigned char* load_image(const char* filename, int* width, int* height, int* channels) {
    // In einer realen Anwendung würde hier eine Bibliothek wie stb_image verwendet
    // Für dieses Beispiel generieren wir ein Testbild
    *width = INPUT_SIZE;
    *height = INPUT_SIZE;
    *channels = 3;
    
    unsigned char* data = malloc(*width * *height * *channels);
    if (!data) {
        return NULL;
    }
    
    // Testbilddaten generieren (grauer Verlauf)
    for (int y = 0; y < *height; y++) {
        for (int x = 0; x < *width; x++) {
            int idx = (y * *width + x) * *channels;
            data[idx] = x % 256;     // R
            data[idx + 1] = y % 256; // G
            data[idx + 2] = 128;     // B
        }
    }
    
    return data;
}

// Hauptfunktion
int main(int argc, char** argv) {
    rknn_context ctx;
    int ret;
    
    // RKNN-Modell laden
    FILE* fp = fopen(MODEL_PATH, "rb");
    if (!fp) {
        printf("Modell öffnen fehlgeschlagen: %s\n", MODEL_PATH);
        return -1;
    }
    
    // Modellgröße bestimmen
    fseek(fp, 0, SEEK_END);
    long model_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    
    // Modell in Speicher laden
    void* model = malloc(model_size);
    if (fread(model, 1, model_size, fp) != model_size) {
        printf("Modell lesen fehlgeschlagen\n");
        free(model);
        fclose(fp);
        return -1;
    }
    fclose(fp);
    
    // RKNN initialisieren
ret = rknn_init(&ctx, model, model_size, 0, NULL);
    free(model);
    if (ret < 0) {
        printf("RKNN-Initialisierung fehlgeschlagen: %d\n", ret);
        return -1;
    }
    
    // RKNN-Eingabe-Attribute abrufen
    rknn_input_output_num io_num;
    ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
    if (ret < 0) {
        printf("RKNN-Abfrage fehlgeschlagen: %d\n", ret);
        rknn_destroy(ctx);
        return -1;
    }
    printf("Modell hat %d Eingaben und %d Ausgaben\n", io_num.n_input, io_num.n_output);
    
    // Eingabe-Tensoren konfigurieren
    rknn_tensor_attr input_attrs[io_num.n_input];
    memset(input_attrs, 0, sizeof(input_attrs));
    for (int i = 0; i < io_num.n_input; i++) {
        input_attrs[i].index = i;
        ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &input_attrs[i], sizeof(rknn_tensor_attr));
        if (ret < 0) {
            printf("RKNN-Eingabe-Attribut-Abfrage fehlgeschlagen: %d\n", ret);
            rknn_destroy(ctx);
            return -1;
        }
    }
    
    // Ausgabe-Tensoren konfigurieren
    rknn_tensor_attr output_attrs[io_num.n_output];
    memset(output_attrs, 0, sizeof(output_attrs));
    for (int i = 0; i < io_num.n_output; i++) {
        output_attrs[i].index = i;
        ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &output_attrs[i], sizeof(rknn_tensor_attr));
        if (ret < 0) {
            printf("RKNN-Ausgabe-Attribut-Abfrage fehlgeschlagen: %d\n", ret);
            rknn_destroy(ctx);
            return -1;
        }
    }
    
    // Eingabe-Tensor-Dimensionen anzeigen
    printf("Eingabe-Tensoren:\n");
    for (int i = 0; i < io_num.n_input; i++) {
        printf("  Eingabe %d: Größe=%d, Typ=%d, Dimensionen=%d %d %d %d\n", 
               i, input_attrs[i].size, input_attrs[i].type, 
               input_attrs[i].dim[0], input_attrs[i].dim[1], 
               input_attrs[i].dim[2], input_attrs[i].dim[3]);
    }
    
    // Ausgabe-Tensor-Dimensionen anzeigen
    printf("Ausgabe-Tensoren:\n");
    for (int i = 0; i < io_num.n_output; i++) {
        printf("  Ausgabe %d: Größe=%d, Typ=%d, Dimensionen=%d %d %d %d\n", 
               i, output_attrs[i].size, output_attrs[i].type, 
               output_attrs[i].dim[0], output_attrs[i].dim[1], 
               output_attrs[i].dim[2], output_attrs[i].dim[3]);
    }
    
    // Bild laden
    int img_width, img_height, img_channels;
    unsigned char* img_data = load_image("test.jpg", &img_width, &img_height, &img_channels);
    if (!img_data) {
        printf("Bild laden fehlgeschlagen\n");
        rknn_destroy(ctx);
        return -1;
    }
    
    // Eingaben vorbereiten
    rknn_input inputs[1];
    memset(inputs, 0, sizeof(inputs));
    inputs[0].index = 0;
    inputs[0].type = RKNN_TENSOR_UINT8;
    inputs[0].size = img_width * img_height * img_channels;
    inputs[0].fmt = RKNN_TENSOR_NHWC;
    inputs[0].buf = img_data;
    
    // Eingaben setzen
    ret = rknn_inputs_set(ctx, io_num.n_input, inputs);
    if (ret < 0) {
        printf("RKNN-Eingaben setzen fehlgeschlagen: %d\n", ret);
        free(img_data);
        rknn_destroy(ctx);
        return -1;
    }
    
    // Inferenz durchführen
    printf("Inferenz läuft...\n");
    ret = rknn_run(ctx, NULL);
    if (ret < 0) {
        printf("RKNN-Ausführung fehlgeschlagen: %d\n", ret);
        free(img_data);
        rknn_destroy(ctx);
        return -1;
    }
    
    // Ausgaben abrufen
    rknn_output outputs[io_num.n_output];
    memset(outputs, 0, sizeof(outputs));
    for (int i = 0; i < io_num.n_output; i++) {
        outputs[i].want_float = 1;
    }
    
    ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL);
    if (ret < 0) {
        printf("RKNN-Ausgaben abrufen fehlgeschlagen: %d\n", ret);
        free(img_data);
        rknn_destroy(ctx);
        return -1;
    }
    
    // Top 5 Ergebnisse (für Klassifikationsmodell) finden
    for (int i = 0; i < io_num.n_output; i++) {
        float* output_buffer = (float*)outputs[i].buf;
        uint32_t output_size = outputs[i].size / sizeof(float);
        
        printf("Top 5 Klassifikationsergebnisse:\n");
        for (int j = 0; j < 5 && j < output_size; j++) {
            float max_value = -1000.0f;
            int max_index = -1;
            
            // Maximum finden
            for (uint32_t k = 0; k < output_size; k++) {
                if (output_buffer[k] > max_value) {
                    max_value = output_buffer[k];
                    max_index = k;
                }
            }
            
            // Ergebnis anzeigen und Maximum entfernen für nächste Iteration
            printf("  %d: Index=%d, Wert=%f\n", j, max_index, max_value);
            output_buffer[max_index] = -1000.0f;
        }
    }
    
    // Ausgaben freigeben
    rknn_outputs_release(ctx, io_num.n_output, outputs);
    
    // Aufräumen
    free(img_data);
    rknn_destroy(ctx);
    
    return 0;
}

TensorFlow Lite auf RK3588 NPU

Der RK3588 unterstützt auch TensorFlow Lite mit NPU-Delegaten für beschleunigte KI-Verarbeitung. Hier ein Beispiel für die Integration:

c
// TensorFlow Lite mit RKNN-Delegaten
#include <iostream>
#include <cstdlib>
#include <tensorflow/lite/interpreter.h>
#include <tensorflow/lite/kernels/register.h>
#include <tensorflow/lite/model.h>
#include <tensorflow/lite/delegates/rknn/rknn_delegate.h>

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Verwendung: " << argv[0] << " <tflite_modell_pfad>" << std::endl;
        return EXIT_FAILURE;
    }
    
    const char* model_path = argv[1];
    
    // Modell laden
    std::unique_ptr<tflite::FlatBufferModel> model =
        tflite::FlatBufferModel::BuildFromFile(model_path);
    if (!model) {
        std::cerr << "Fehler beim Laden des TFLite-Modells" << std::endl;
        return EXIT_FAILURE;
    }
    
    // Resolver erstellen
    tflite::ops::builtin::BuiltinOpResolver resolver;
    
    // Interpreter erstellen
    std::unique_ptr<tflite::Interpreter> interpreter;
    tflite::InterpreterBuilder(*model, resolver)(&interpreter);
    if (!interpreter) {
        std::cerr << "Fehler beim Erstellen des Interpreters" << std::endl;
        return EXIT_FAILURE;
    }
    
    // RKNN-Delegaten erstellen
    TfLiteRKNNDelegateOptions options = TfLiteRKNNDelegateOptionsDefault();
    options.execution_priority = RKNN_PRIORITY_HIGH;
    options.cache_path = "/data/rknn/cache";
    
    TfLiteDelegate* rknn_delegate = TfLiteRKNNDelegateCreate(&options);
    if (interpreter->ModifyGraphWithDelegate(rknn_delegate) != kTfLiteOk) {
        std::cerr << "Fehler beim Anwenden des RKNN-Delegaten" << std::endl;
        TfLiteRKNNDelegateDelete(rknn_delegate);
        return EXIT_FAILURE;
    }
    
    // Tensor-Größen zuweisen
    if (interpreter->AllocateTensors() != kTfLiteOk) {
        std::cerr << "Fehler beim Zuweisen der Tensoren" << std::endl;
        TfLiteRKNNDelegateDelete(rknn_delegate);
        return EXIT_FAILURE;
    }
    
    // Eingabetensor abrufen
    TfLiteTensor* input_tensor = interpreter->input_tensor(0);
    
    // Beispiel-Eingabedaten erstellen (alle auf 0.5 setzen)
    int input_size = 1;
    for (int i = 0; i < input_tensor->dims->size; i++) {
        input_size *= input_tensor->dims->data[i];
    }
    
    if (input_tensor->type == kTfLiteFloat32) {
        float* input_data = interpreter->typed_input_tensor<float>(0);
        for (int i = 0; i < input_size; i++) {
            input_data[i] = 0.5f;
        }
    } else if (input_tensor->type == kTfLiteUInt8) {
        uint8_t* input_data = interpreter->typed_input_tensor<uint8_t>(0);
        for (int i = 0; i < input_size; i++) {
            input_data[i] = 128;
        }
    } else {
        std::cerr << "Nicht unterstützter Eingabetyp" << std::endl;
        TfLiteRKNNDelegateDelete(rknn_delegate);
        return EXIT_FAILURE;
    }
    
    // Inferenz durchführen
    if (interpreter->Invoke() != kTfLiteOk) {
        std::cerr << "Fehler bei der Inferenz" << std::endl;
        TfLiteRKNNDelegateDelete(rknn_delegate);
        return EXIT_FAILURE;
    }
    
    // Ausgabetensor abrufen
    TfLiteTensor* output_tensor = interpreter->output_tensor(0);
    
    // Ergebnisse ausgeben
    if (output_tensor->type == kTfLiteFloat32) {
        float* output_data = interpreter->typed_output_tensor<float>(0);
        int output_size = 1;
        for (int i = 0; i < output_tensor->dims->size; i++) {
            output_size *= output_tensor->dims->data[i];
        }
        
        std::cout << "Ausgabe (erste 10 Werte):" << std::endl;
        for (int i = 0; i < 10 && i < output_size; i++) {
            std::cout << "  [" << i << "]: " << output_data[i] << std::endl;
        }
    }
    
    // Delegaten freigeben
    TfLiteRKNNDelegateDelete(rknn_delegate);
    
    return EXIT_SUCCESS;
}

7. Multimedia-Verarbeitung

Der RK3588 ist besonders leistungsfähig in der Multimedia-Verarbeitung mit Hardware-Codecs für 8K-Video, mehrere Kameraanschlüsse und hochwertige Audioverarbeitung.

Video-Dekodierung mit MPP (Media Process Platform)

Rockchip’s MPP-Framework ermöglicht die effiziente Nutzung der Hardware-Video-Codecs:

c
// Video-Dekodierungsbeispiel mit MPP
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <rockchip/rk_mpi.h>

#define MAX_FRAME_SIZE (8 * 1024 * 1024)  // 8MB für einen Frame

int main(int argc, char** argv) {
    if (argc < 3) {
        printf("Verwendung: %s <eingang.h264> <ausgang.yuv>\n", argv[0]);
        return -1;
    }
    
    // MPP-Kontext
    MppCtx ctx;
    MppApi *mpi;
    
    // Input/Output Dateien
    FILE *input_file = fopen(argv[1], "rb");
    FILE *output_file = fopen(argv[2], "wb");
    if (!input_file || !output_file) {
        printf("Fehler beim Öffnen der Dateien\n");
        return -1;
    }
    
    // MPP initialisieren
    MPP_RET ret = mpp_create(&ctx, &mpi);
    if (ret != MPP_OK) {
        printf("MPP erstellen fehlgeschlagen: %d\n", ret);
        return -1;
    }
    
    // Dekoder initialisieren
    ret = mpi->control(ctx, MPP_SET_OUTPUT_TIMEOUT, (MppParam)&timeout);
    if (ret != MPP_OK) {
        printf("Timeout setzen fehlgeschlagen: %d\n", ret);
        goto CLEANUP;
    }
    
    // Decoder konfigurieren
    MppCodingType codec_type = MPP_VIDEO_CodingAVC; // H.264
    ret = mpi->control(ctx, MPP_DEC_SET_CODING_TYPE, &codec_type);
    if (ret != MPP_OK) {
        printf("Codec-Typ setzen fehlgeschlagen: %d\n", ret);
        goto CLEANUP;
    }
    
    // Decoder initialisieren
    ret = mpi->init(ctx, MPP_CTX_DEC, codec_type);
    if (ret != MPP_OK) {
        printf("MPP initialisieren fehlgeschlagen: %d\n", ret);
        goto CLEANUP;
    }
    
    // Eingabe-Puffer für Rohdaten
    void *input_buffer = malloc(MAX_FRAME_SIZE);
    if (!input_buffer) {
        printf("Speicherallokation fehlgeschlagen\n");
        goto CLEANUP;
    }
    
    // Paket für Dekodierung
    MppPacket packet;
    
    // Frame für dekodierte Ausgabe
    MppFrame frame = NULL;
    
    // Video-Stream dekodieren
    size_t read_size;
    size_t packet_size = 0;
    while ((read_size = fread(input_buffer + packet_size, 1, 4096, input_file)) > 0) {
        packet_size += read_size;
        
        // Paket erstellen
        ret = mpp_packet_init(&packet, input_buffer, packet_size);
        if (ret != MPP_OK) {
            printf("Paket initialisieren fehlgeschlagen: %d\n", ret);
            break;
        }
        
        // Paket an Decoder senden
        ret = mpi->decode_put_packet(ctx, packet);
        if (ret != MPP_OK) {
            printf("Paket senden fehlgeschlagen: %d\n", ret);
            mpp_packet_deinit(&packet);
            break;
        }
        
        mpp_packet_deinit(&packet);
        
        // Dekodierte Frames abrufen
        do {
            ret = mpi->decode_get_frame(ctx, &frame);
            if (ret != MPP_OK) {
                printf("Frame abrufen fehlgeschlagen: %d\n", ret);
                break;
            }
            
            if (frame) {
                // Prüfen, ob Frame gültig ist
                if (mpp_frame_get_errinfo(frame)) {
                    printf("Frame hat Fehler\n");
                    mpp_frame_deinit(&frame);
                    continue;
                }
                
                // Frame-Informationen abrufen
                MppBuffer buffer = mpp_frame_get_buffer(frame);
                if (buffer) {
                    void *ptr = mpp_buffer_get_ptr(buffer);
                    size_t size = mpp_buffer_get_size(buffer);
                    int width = mpp_frame_get_width(frame);
                    int height = mpp_frame_get_height(frame);
                    MppFrameFormat fmt = mpp_frame_get_fmt(frame);
                    
                    printf("Dekodierter Frame: %dx%d, Format=%d\n", width, height, fmt);
                    
                    // YUV-Daten in Ausgabedatei schreiben
                    fwrite(ptr, 1, size, output_file);
                }
                
                // Frame freigeben
                mpp_frame_deinit(&frame);
                frame = NULL;
            }
        } while (frame);
        
        // Puffer zurücksetzen für nächstes Paket
        packet_size = 0;
    }
    
    // Aufräumen
    free(input_buffer);
    
CLEANUP:
    if (ctx) {
        mpi->reset(ctx);
        mpp_destroy(ctx);
        ctx = NULL;
    }
    
    if (input_file) fclose(input_file);
    if (output_file) fclose(output_file);
    
    return 0;
}

Kamera-Zugriff mit V4L2

Video4Linux2 (V4L2) ist die Standard-API für Kamera-Zugriff unter Linux:

c
// V4L2-Kamera-Beispiel
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

#define CAMERA_DEVICE "/dev/video0"
#define BUFFER_COUNT 4
#define FRAME_WIDTH 1920
#define FRAME_HEIGHT 1080
#define PIXEL_FORMAT V4L2_PIX_FMT_YUYV

struct buffer {
    void *start;
    size_t length;
};

int main() {
    int fd;
    struct buffer *buffers;
    
    // Kamera-Gerät öffnen
    fd = open(CAMERA_DEVICE, O_RDWR);
    if (fd < 0) {
        perror("Kamera öffnen fehlgeschlagen");
        return EXIT_FAILURE;
    }
    
    // Kamera-Fähigkeiten abfragen
    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
        perror("Kamera-Fähigkeiten abfragen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }
    
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        fprintf(stderr, "Gerät unterstützt keine Videoaufnahme\n");
        close(fd);
        return EXIT_FAILURE;
    }
    
    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        fprintf(stderr, "Gerät unterstützt kein Streaming\n");
        close(fd);
        return EXIT_FAILURE;
    }
    
    // Videoformat einstellen
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = FRAME_WIDTH;
    fmt.fmt.pix.height = FRAME_HEIGHT;
    fmt.fmt.pix.pixelformat = PIXEL_FORMAT;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("Videoformat einstellen fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }
    
    // Buffer anfordern
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));
    req.count = BUFFER_COUNT;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    
    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("Buffer anfordern fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }
    
    if (req.count < 2) {
        fprintf(stderr, "Nicht genug Pufferspeicher\n");
        close(fd);
        return EXIT_FAILURE;
    }
    
    // Buffer allozieren
    buffers = calloc(req.count, sizeof(*buffers));
    if (!buffers) {
        perror("Speicherallokation fehlgeschlagen");
        close(fd);
        return EXIT_FAILURE;
    }
    
    // Buffer Memory-Map durchführen
    for (unsigned int i = 0; i < req.count; i++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("Buffer-Info abfragen fehlgeschlagen");
            close(fd);
            free(buffers);
            return EXIT_FAILURE;
        }
        
        buffers[i].length = buf.length;
        buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
                              MAP_SHARED, fd, buf.m.offset);
        
        if (buffers[i].start == MAP_FAILED) {
            perror("Memory-Map fehlgeschlagen");
            close(fd);
            free(buffers);
            return EXIT_FAILURE;
        }
    }
    
    // Buffer zur Warteschlange hinzufügen
    for (unsigned int i = 0; i < req.count; i++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("Buffer zur Warteschlange hinzufügen fehlgeschlagen");
            close(fd);
            free(buffers);
            return EXIT_FAILURE;
        }
    }
    
    // Streaming starten
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
        perror("Streaming starten fehlgeschlagen");
        close(fd);
        free(buffers);
        return EXIT_FAILURE;
    }
    
    // 10 Frames erfassen
    for (int i = 0; i < 10; i++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        
        // Buffer aus der Warteschlange holen
        if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
            perror("Buffer aus der Warteschlange holen fehlgeschlagen");
            break;
        }
        
        printf("Frame %d erfasst: %ld Bytes\n", i, buffers[buf.index].length);
        
        // Hier könnten Sie die Bilddaten verarbeiten
        // z.B. in eine Datei schreiben:
        char filename[20];
        sprintf(filename, "frame%d.raw", i);
        FILE *fp = fopen(filename, "wb");
        if (fp) {
            fwrite(buffers[buf.index].start, 1, buffers[buf.index].length, fp);
            fclose(fp);
        }
        
        // Buffer wieder in die Warteschlange stellen
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            perror("Buffer zurück in die Warteschlange stellen fehlgeschlagen");
            break;
        }
    }
    
    // Streaming stoppen
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
        perror("Streaming stoppen fehlgeschlagen");
    }
    
    // Aufräumen
    for (unsigned int i = 0; i < req.count; i++) {
        munmap(buffers[i].start, buffers[i].length);
    }
    
    free(buffers);
    close(fd);
    
    return EXIT_SUCCESS;
}

滚动至顶部