Dockerize build fixes: GCC 14 toolchain, clean build, flash helper

Bring the headless Docker build closer to the official OpenGD77 build and
add a flashing helper.

docker/Dockerfile
  - Replace Ubuntu's stock gcc-arm-none-eabi (GCC 10.3.1) with the Arm GNU
    Toolchain 14.2.rel1 (GCC 14). The official firmware is built with GCC 14
    (gnu-tools-for-stm32 14.3); GCC 10 produced a firmware that hard-faults on
    the DMR path. Guard the image build to fail if the compiler is not GCC 14.
  - git config --system safe.directory '*' so the build stamps a real GitID
    instead of #UNKNOWN.

docker/build.sh
  - rm -rf build before compiling. obj/ lives on the host mount and was being
    reused across toolchain changes, silently relinking stale .o files (an
    earlier rebuild produced a byte-identical GCC-10 image because of this).

docker/firmware.mk
  - Add -DNDEBUG to exactly mirror the STM32CubeIDE "MDUV380_FW" release config
    (which defines both NDEBUG and DEBUG).

flash.sh
  - Helper to flash via opengd77_stm32_firmware_loader.py with model, secondary
    language (auto-builds the .gla files) and the DMR codec donor.

.gitignore
  - Ignore generated *.gla files and the host-side languages_builder binary.

Note: the GCC 14 rebuild did NOT resolve the DMR-receive hang; that fault is in
the firmware source revision (this tree is a snapshot; the known-good build is
commit ebd7100, not present here) and is out of scope of these build changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcus Kida 2026-07-06 10:29:48 +02:00
parent 6714da6e00
commit 0d8a293199
5 changed files with 144 additions and 5 deletions

4
.gitignore vendored
View file

@ -1,3 +1,7 @@
.DS_Store .DS_Store
build/ build/
codec_bin_section_1.bin codec_bin_section_1.bin
# Generated language plugin files + host-side builder binary
*.gla
languages_builder

View file

@ -8,14 +8,39 @@ FROM --platform=linux/amd64 ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
# IMPORTANT: use the same GCC major version as the official OpenGD77 builds.
# Ubuntu 22.04's stock gcc-arm-none-eabi is GCC 10.3.1, which miscompiles the
# timing-critical DMR / HR-C6000 path: the radio hard-faults (FreeRTOS
# configASSERT -> disable IRQs -> spin forever, needs a battery pull) the moment
# the AMBE codec runs on DMR TX. FM is unaffected. The official firmware is built
# with "gnu-tools-for-stm32 14.3.rel1" (GCC 14.3); we pin the equivalent vanilla
# Arm GNU Toolchain (GCC 14.2) so our build matches.
ARG ARM_TOOLCHAIN_VERSION=14.2.rel1
ARG ARM_TOOLCHAIN_TARBALL=arm-gnu-toolchain-${ARM_TOOLCHAIN_VERSION}-x86_64-arm-none-eabi.tar.xz
ARG ARM_TOOLCHAIN_URL=https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_TOOLCHAIN_VERSION}/binrel/${ARM_TOOLCHAIN_TARBALL}
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc-arm-none-eabi \
binutils-arm-none-eabi \
libnewlib-arm-none-eabi \
libstdc++-arm-none-eabi-newlib \
make \ make \
git \ git \
wget \
xz-utils \
ca-certificates \ ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Download and install the Arm GNU Toolchain (bare-metal, arm-none-eabi).
RUN wget -q -O /tmp/arm-toolchain.tar.xz "${ARM_TOOLCHAIN_URL}" \
&& mkdir -p /opt/arm-toolchain \
&& tar -xJf /tmp/arm-toolchain.tar.xz -C /opt/arm-toolchain --strip-components=1 \
&& rm /tmp/arm-toolchain.tar.xz
ENV PATH="/opt/arm-toolchain/bin:${PATH}"
# Fail the image build early if the toolchain is not the expected GCC major.
RUN arm-none-eabi-gcc --version | grep -q ' 14\.' \
&& echo "Toolchain OK: $(arm-none-eabi-gcc --version | head -1)"
# git refuses to report a version for a repo owned by another user (the mounted
# /work). Trust it so the build stamps a real GitID instead of #UNKNOWN.
RUN git config --system --add safe.directory '*'
WORKDIR /work WORKDIR /work

View file

@ -12,6 +12,9 @@ chmod +x tools/codec_cleaner.Linux
( cd application/source/linkerdata && ../../../tools/codec_cleaner.Linux -C ) ( cd application/source/linkerdata && ../../../tools/codec_cleaner.Linux -C )
echo "==> Building firmware" echo "==> Building firmware"
# Clean build: obj/ lives on the host mount and would otherwise be reused across
# toolchain changes, silently relinking stale .o files (e.g. an old GCC build).
rm -rf build
mkdir -p build mkdir -p build
cd build cd build
make -f /work/docker/firmware.mk -j"$(nproc)" make -f /work/docker/firmware.mk -j"$(nproc)"

View file

@ -21,7 +21,8 @@ TARGET := OpenMDUV380
CPU := -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb CPU := -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb
# ---- Preprocessor defines ------------------------------------------------- # ---- Preprocessor defines -------------------------------------------------
DEFS := -DUSE_HAL_DRIVER -DSTM32F405xx -DPLATFORM_MDUV380 -DDEBUG # NDEBUG + DEBUG mirrors the STM32CubeIDE "MDUV380_FW" release config exactly.
DEFS := -DUSE_HAL_DRIVER -DSTM32F405xx -DPLATFORM_MDUV380 -DNDEBUG -DDEBUG
# Short git hash if available, otherwise UNKNOWN (matches the .cproject rule). # Short git hash if available, otherwise UNKNOWN (matches the .cproject rule).
GITVERSION := $(shell git -C .. rev-parse --short HEAD 2>/dev/null || echo UNKNOWN) GITVERSION := $(shell git -C .. rev-parse --short HEAD 2>/dev/null || echo UNKNOWN)

106
flash.sh Normal file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env bash
#
# flash.sh — flash the OpenGD77 firmware to an MD-UV380 / DM-1701 in DFU mode,
# optionally merging a secondary language and the DMR (AMBE codec) donor firmware.
#
# The radio's language slot #1 is always English. This script injects ONE
# secondary language (slot #2) via the loader's -L option. Re-flash with a
# different language to change it.
#
# Usage:
# ./flash.sh # defaults: model MD-UV380, language German
# ./flash.sh -L French # flash with French as the secondary language
# ./flash.sh -m DM-1701 -L Italian
# ./flash.sh -L none # English only, no secondary language
# ./flash.sh -s /path/donor.bin # (re)register the DMR codec donor firmware
# ./flash.sh --list-languages # show available languages and exit
# ./flash.sh -l # list attached DFU devices and exit
#
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FW_DIR="$REPO_DIR/MDUV380_firmware"
LOADER="$FW_DIR/tools/opengd77_stm32_firmware_loader.py"
FIRMWARE="$FW_DIR/build/OpenMDUV380.bin"
LANG_SRC="$FW_DIR/application/include/user_interface/languages/src"
# ---- defaults -------------------------------------------------------------
MODEL="MD-UV380" # or: DM-1701, MD-9600, MD-2017, MD-380
LANGUAGE="German" # a language name (see --list-languages), or "none"
DONOR="" # official V26.45 firmware; empty = use the saved one
usage() { sed -n '3,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }
lc() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; } # lowercase (bash 3.2 safe)
# ---- parse args -----------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--model) MODEL="$2"; shift 2 ;;
-L|--lang|--language) LANGUAGE="$2"; shift 2 ;;
-s|--source|--donor) DONOR="$2"; shift 2 ;;
-f|--firmware) FIRMWARE="$2"; shift 2 ;;
-l|--list) python3 "$LOADER" -l; exit $? ;;
--list-languages)
echo "Available languages (pass to -L):"
( cd "$LANG_SRC" && ls *.gla 2>/dev/null | sed 's/\.gla$//' ) \
|| ls "$LANG_SRC"/../*.h 2>/dev/null | xargs -n1 basename | sed 's/\.h$//'
echo " none (English only)"
exit 0 ;;
-h|--help) usage 0 ;;
*) echo "Unknown option: $1" >&2; usage 1 ;;
esac
done
# ---- sanity checks --------------------------------------------------------
[[ -f "$LOADER" ]] || { echo "!! Loader not found: $LOADER" >&2; exit 1; }
[[ -f "$FIRMWARE" ]] || { echo "!! Firmware not found: $FIRMWARE (run ./build-firmware.sh first)" >&2; exit 1; }
# ---- resolve / build the language file ------------------------------------
LANG_ARGS=()
if [[ -n "$LANGUAGE" && "$(lc "$LANGUAGE")" != "none" ]]; then
# Build the .gla files once if they're missing.
if ! ls "$LANG_SRC"/*.gla >/dev/null 2>&1; then
echo "==> Building language (.gla) files..."
( cd "$LANG_SRC" && gcc -w -I../ -o languages_builder languages_builder.c && ./languages_builder >/dev/null )
fi
# Case-insensitive match of the requested name to an actual <Name>.gla file.
GLA=""
for f in "$LANG_SRC"/*.gla; do
base="$(basename "$f" .gla)"
if [[ "$(lc "$base")" == "$(lc "$LANGUAGE")" ]]; then GLA="$f"; break; fi
done
if [[ -z "$GLA" ]]; then
echo "!! Unknown language '$LANGUAGE'. Available:" >&2
( cd "$LANG_SRC" && ls *.gla | sed 's/\.gla$//' | sed 's/^/ /' ) >&2
exit 1
fi
echo "==> Secondary language: $(basename "$GLA" .gla)"
LANG_ARGS=(-L "$GLA")
else
echo "==> Secondary language: none (English only)"
fi
# ---- donor firmware (DMR / AMBE codec) ------------------------------------
DONOR_ARGS=()
if [[ -n "$DONOR" ]]; then
[[ -f "$DONOR" ]] || { echo "!! Donor firmware not found: $DONOR" >&2; exit 1; }
echo "==> DMR donor (this run): $DONOR"
DONOR_ARGS=(-s "$DONOR")
elif grep -qi 'sourcestm32firmware *= *..*' "$HOME/.gd77firmwareloader.ini" 2>/dev/null; then
saved="$(grep -i 'sourcestm32firmware' "$HOME/.gd77firmwareloader.ini" | cut -d= -f2- | xargs)"
echo "==> DMR donor: using saved donor -> $saved"
else
echo "==> DMR donor: NONE registered — firmware will flash as FM-only."
echo " (pass -s /path/to/official_V26.45.bin once to enable DMR)"
fi
# ---- flash ----------------------------------------------------------------
echo "==> Model: $MODEL"
echo "==> Firmware: $FIRMWARE"
echo "==> Make sure the radio is in DFU mode (power on holding PTT + SK1)."
echo
set -x
exec python3 "$LOADER" -m "$MODEL" \
"${DONOR_ARGS[@]+"${DONOR_ARGS[@]}"}" \
"${LANG_ARGS[@]+"${LANG_ARGS[@]}"}" \
-f "$FIRMWARE"