Dieser Post ist Teil der Serie über mein privates BeagleBone-Black-Projekt. Der C-Teil des HAL wird auf einem x86-Entwicklungsrechner kompiliert — das Binary muss aber auf dem ARM Cortex-A8 des BeagleBone Black laufen. Das ist Cross-Kompilierung, und CMake macht das handhabbar.
Voraussetzungen
Toolchain installieren
Auf Ubuntu/Debian:
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihfDas installiert die wichtigsten Tools:
arm-linux-gnueabihf-gcc— C-Compiler für ARMv7 mit Hard-Float ABI`arm-linux-gnueabihf-g` — C-Compiler
arm-linux-gnueabihf-ld— Linkerarm-linux-gnueabihf-strip— Symbol-Stripper
Version prüfen:
arm-linux-gnueabihf-gcc --version
# arm-linux-gnueabihf-gcc (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0Sysroot (optional aber empfohlen)
Ein Sysroot ist eine Kopie des Ziel-Dateisystems — mit den korrekten Libraries und Headern für die Zielplattform. Ohne Sysroot linkt der Compiler gegen die Host-Libraries, was zu ABI-Mismatches führt.
Sysroot vom BeagleBone Black kopieren:
rsync -avz \
--exclude="/proc" --exclude="/sys" --exclude="/dev" \
pi@beaglebone:/ \
~/bbb-sysroot/Für einfache Projekte ohne systemspezifische Libraries reicht die Toolchain alleine.
Sobald man gegen |
CMake Toolchain-File
Das Herzstück ist das Toolchain-File — eine CMake-Datei die dem Build-System mitteilt, welchen Compiler, welchen Linker und welches Zielsystem es nutzen soll.
Datei cmake/armv7-toolchain.cmake:
# Zielsystem
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR armv7)
# Toolchain-Prefix
set(TOOLCHAIN_PREFIX arm-linux-gnueabihf)
# Compiler
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
# Sysroot (auskommentieren wenn kein Sysroot vorhanden)
# set(CMAKE_SYSROOT /home/user/bbb-sysroot)
# Nur im Sysroot nach Libraries suchen, nicht auf dem Host
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# ARMv7 Cortex-A8 spezifische Flags
set(CMAKE_C_FLAGS "-march=armv7-a -mfpu=neon -mfloat-abi=hard" CACHE STRING "")
set(CMAKE_CXX_FLAGS "-march=armv7-a -mfpu=neon -mfloat-abi=hard" CACHE STRING "")Die wichtigsten Variablen erklärt:
CMAKE_SYSTEM_NAMETeilt CMake mit, dass für ein anderes Betriebssystem gebaut wird. Deaktiviert viele Host-Checks.
CMAKE_SYSTEM_PROCESSORProzessor-Architektur des Zielsystems — beeinflusst Compiler-Defaults.
CMAKE_C_COMPILERAbsoluter Pfad oder Name des Cross-Compilers.
CMAKE_SYSROOTWo CMake nach Headers und Libraries sucht.
CMAKE_FIND_ROOT_PATH_MODE_*Verhindert dass CMake versehentlich Host-Libraries findet.
CMakeLists.txt-Besonderheiten
Die CMakeLists.txt selbst unterscheidet sich kaum von einer normalen Konfiguration.
Ein paar Punkte sind beim Cross-Compile zu beachten:
cmake_minimum_required(VERSION 3.20)
project(bbb_hal C)
# Compiler-Test deaktivieren wenn kein Emulator vorhanden
# (CMake versucht sonst das kompilierte Binary auszuführen)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Ziel-Bibliothek
add_library(bbb_drivers STATIC
src/gpio_driver.c
src/i2c_driver.c
src/uart_driver.c
)
target_include_directories(bbb_drivers PUBLIC include)
# Installationspfad für Deploy-Script
install(TARGETS bbb_drivers DESTINATION lib)
install(DIRECTORY include/ DESTINATION include)
|
Build ausführen
# Build-Verzeichnis erstellen und konfigurieren
cmake \
-DCMAKE_TOOLCHAIN_FILE=cmake/armv7-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release \
-B build-arm
# Kompilieren
cmake --build build-arm
# Prüfen: ist es wirklich ein ARM-Binary?
file build-arm/libbbb_drivers.a
# build-arm/libbbb_drivers.a: current ar archive
arm-linux-gnueabihf-readelf -h build-arm/libbbb_drivers.a | grep Machine
# Machine: ARMIntegration in Drone CI
Im CI-Container muss die Toolchain installiert sein.
Ich verwende in der Pipeline direktes apt-get um keine eigenen Images pflegen zu müssen:
steps:
- name: build-c
image: ubuntu:22.04
commands:
- apt-get update -q
- apt-get install -y cmake gcc-arm-linux-gnueabihf
- cmake
-DCMAKE_TOOLCHAIN_FILE=cmake/armv7-toolchain.cmake
-DCMAKE_BUILD_TYPE=Release
-B build-arm
- cmake --build build-arm --parallel $(nproc)Deployment auf das Board
Nach dem Build muss das Binary auf den BeagleBone Black:
# Library und Header übertragen
rsync -avz build-arm/libbbb_drivers.a pi@beaglebone:/opt/bbb-hal/lib/
rsync -avz include/ pi@beaglebone:/opt/bbb-hal/include/
# Shared Library (wenn gebaut)
rsync -avz build-arm/libbbb_drivers.so pi@beaglebone:/usr/local/lib/
ssh pi@beaglebone "ldconfig"Im CI-Deploy-Step:
- name: deploy
image: alpine
environment:
SSH_KEY:
from_secret: bbb_ssh_key
commands:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$SSH_KEY" | ssh-add -
- rsync -avz build-arm/ pi@beaglebone:/opt/bbb-hal/
- ssh pi@beaglebone "systemctl restart bbb-hal"
when:
branch: [main]Häufige Fehler
Falscher ABI: hard vs softfp
/usr/bin/ld: skipping incompatible /usr/lib/libm.so when searching for -lmDer Linker findet eine Library mit falschem Float-ABI.
Lösung: Sysroot korrekt setzen oder Libraries explizit mit -mfloat-abi=hard kompilieren.
Fehlende Sysroot-Header
fatal error: linux/i2c-dev.h: No such file or directoryDer Header existiert nicht in der Toolchain selbst, nur im Board-Sysroot. Lösung: Sysroot korrekt konfigurieren oder Header manuell einbinden.
CMake findet Host-Libraries
-- Found OpenSSL: /usr/lib/x86_64-linux-gnu/libssl.so (found version "3.0.2")CMake hat die Host-Library gefunden statt der ARM-Version.
Lösung: CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY im Toolchain-File prüfen.
Binary läuft nicht auf dem Board
pi@beaglebone:~$ ./myapp
bash: ./myapp: cannot execute binary file: Exec format errorDas Binary wurde für x86 kompiliert, nicht für ARM.
Ursache: Toolchain-File wurde nicht übergeben.
Prüfen mit file myapp — sollte ARM zeigen.
Rust Cross-Compilation
Als Bonus: so sieht Cross-Compilation für den Rust-Teil des Projekts aus.
# Target installieren
rustup target add armv7-unknown-linux-gnueabihfDatei .cargo/config.toml:
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"Build:
cargo build \
--release \
--target armv7-unknown-linux-gnueabihfRust und CMake nutzen dieselbe Toolchain ( |
Nächster Post in der Serie: Rust in der HAL — warum und wie