From aa7b62403bb8fea37b3bcbb07698c1fd3238b790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Metrot?= Date: Sat, 22 Aug 2026 01:26:50 +0200 Subject: [PATCH 1/3] Add an embedded-files build config for MB6HC Derived from Duet3_MB6HC.mk with -DUSE_EMBEDDED_FILES. Kept out of the default all: target like the other special-purpose configs. Its helper targets are renamed rather than inherited: sharing duet3mb6hc-prebuild, duet3mb6hc-libs and clean-Duet3_MB6HC with the normal config made make warn about overridden commands and made clean-Duet3_MB6HC silently clean the embedded build directory. Needs the USE_EMBEDDED_FILES compile fix to build. --- .gitignore | 4 +- Makefile | 5 +- Makefiles/Duet3_MB6HC_embedded.mk | 303 ++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 Makefiles/Duet3_MB6HC_embedded.mk diff --git a/.gitignore b/.gitignore index 776b630011..3f57661954 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,9 @@ /FMDC_V02_Debug/ /FMDC_V03/ /Duet3_MB6HC_no_SD/ +/Duet3_MB6HC_embedded/ /Duet3_MB6HC_no_S_curve/ -/INDX/ +/tests/build/ /.clangd /.clang-format /src/Temp/ -/.settings/ diff --git a/Makefile b/Makefile index c890c34d82..fe9e06a3d8 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ export DEBUG_FLAGS .DEFAULT_GOAL := help # Available build configurations -CONFIGS := Duet3_MB6HC Duet3_MB6XD Duet3_CAN0 Duet3Mini5plus Duet3_MB6HC_no_SD +CONFIGS := Duet3_MB6HC Duet3_MB6XD Duet3_CAN0 Duet3Mini5plus Duet3_MB6HC_no_SD Duet3_MB6HC_embedded # Print available targets .PHONY: help @@ -83,7 +83,7 @@ help: # Build all configurations # Excluded from 'all' (still buildable as explicit targets): Duet3_MB6HC_no_SD (duplicate MB6HC binary name), Duet3_CAN0 .PHONY: all -all: $(filter-out Duet3_MB6HC_no_SD Duet3_CAN0,$(CONFIGS)) +all: $(filter-out Duet3_MB6HC_no_SD Duet3_CAN0 Duet3_MB6HC_embedded,$(CONFIGS)) # Verify toolchain .PHONY: test-toolchain @@ -201,6 +201,7 @@ $(WORKSPACE)/CANlib/SAM4S_RTOS/libCANlib.a: -include Makefiles/Duet3_CAN0.mk -include Makefiles/Duet3Mini5plus.mk -include Makefiles/Duet3_MB6HC_no_SD.mk +-include Makefiles/Duet3_MB6HC_embedded.mk # Generic clean target .PHONY: clean diff --git a/Makefiles/Duet3_MB6HC_embedded.mk b/Makefiles/Duet3_MB6HC_embedded.mk new file mode 100644 index 0000000000..2fcb8274cd --- /dev/null +++ b/Makefiles/Duet3_MB6HC_embedded.mk @@ -0,0 +1,303 @@ +# Duet 3 MB6HC built with USE_EMBEDDED_FILES. +# +# Derived from Duet3_MB6HC.mk. The only functional difference is -DUSE_EMBEDDED_FILES, which in +# Pins_Duet3_MB6HC.h turns off mass storage, high-speed SD and the SBC interface and turns on the +# embedded filesystem that lives after _firmware_end. +# +# This exists for the Renode emulator: modelling HSMCI is a large job, and without an SD card there +# is no config.g, so no M575, so the aux UART never opens and there is no way to send G-code in. +# Embedding the files removes the SD card from that chain entirely. +# +# RepRapFirmware Duet3_MB6HC Configuration Makefile +# Build configuration for Duet 3 Main Board 6HC (SAME70 MCU) + +# Build directory and output +DUET3EMB_BUILD_DIR := Duet3_MB6HC_embedded +DUET3EMB_TARGET_NAME := Duet3Firmware_MB6HC_embedded +DUET3EMB_TARGET_ELF := $(DUET3EMB_BUILD_DIR)/$(DUET3EMB_TARGET_NAME).elf +DUET3EMB_TARGET_BIN := $(DUET3EMB_BUILD_DIR)/$(DUET3EMB_TARGET_NAME).bin +DUET3EMB_TARGET_MAP := $(DUET3EMB_BUILD_DIR)/$(DUET3EMB_TARGET_NAME).map + +# Workspace root +WORKSPACE := .. + +# Library dependencies +DUET3EMB_FREERTOS_LIB := $(WORKSPACE)/FreeRTOS/SAME70/libFreeRTOS.a +DUET3EMB_COREN2G_LIB := $(WORKSPACE)/CoreN2G/SAME70_CAN_SDHC_USB_RTOS/libCoreN2G.a +DUET3EMB_RRFLIBS_LIB := $(WORKSPACE)/RRFLibraries/SAME70_RTOS/libRRFLibraries.a +DUET3EMB_CANLIB_LIB := $(WORKSPACE)/CANlib/SAME70_RTOS/libCANlib.a +DUET3EMB_LIBTINYUSB_LIB := $(WORKSPACE)/LibTinyusb/SAME70/libLibTinyusb.a +DUET3EMB_MBEDTLS_LIB := $(WORKSPACE)/LibMbedTls/SAME70/libLibMbedTls.a + +# Source directories +DUET3EMB_SRC_DIR := src + +# Find libcpp and libc files first (must be linked first for proper malloc resolution) +DUET3EMB_LIBCPP_SRCS := $(shell find $(DUET3EMB_SRC_DIR)/libcpp -name '*.cpp' -o -name '*.cc' 2>/dev/null) +DUET3EMB_LIBC_SRCS := $(shell find $(DUET3EMB_SRC_DIR)/libc -name '*.c' -o -name '*.cpp' 2>/dev/null) + +# Find all source files (excluding specified directories) +DUET3EMB_CPP_SRCS := $(shell find $(DUET3EMB_SRC_DIR) -name '*.cpp' \ + ! -path '*/libcpp/*' \ + ! -path '*/libc/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/smtp/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/snmp/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/httpd/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/test/*' \ + ! -path '*/DuetNG/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/tftp/*' \ + ! -path '*/Networking/W5500Ethernet/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/netif/ppp/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/lwiperf/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/sntp/*' \ + ! -path '*/Display/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/http/*' \ + ! -path '*/Duet3Mini/*' \ + ! -path '*/Hardware/SAM4E/*' \ + ! -path '*/Hardware/SAME5x/*' \ + ! -path '*/Pccb/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/mqtt/*' \ + ! -path '*/Hardware/SAM4S/*' \ + ! -path '*/DuetM/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/doc/*') + +DUET3EMB_C_SRCS := $(shell find $(DUET3EMB_SRC_DIR) -name '*.c' \ + ! -path '*/libc/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/smtp/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/snmp/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/test/*' \ + ! -path '*/DuetNG/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/tftp/*' \ + ! -path '*/Networking/W5500Ethernet/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/netif/ppp/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/lwiperf/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/sntp/*' \ + ! -path '*/Display/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/http/*' \ + ! -path '*/Duet3Mini/*' \ + ! -path '*/Hardware/SAM4E/*' \ + ! -path '*/Hardware/SAME5x/*' \ + ! -path '*/Pccb/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/src/apps/mqtt/*' \ + ! -path '*/Hardware/SAM4S/*' \ + ! -path '*/DuetM/*' \ + ! -path '*/Networking/LwipEthernet/Lwip/doc/*' \ + ! -path '*/MQTT_C/tests.c' \ + ! -path '*/MQTT_C/examples/*' \ + ! -path '*/MQTT_C/src/mqtt_pal.c') + +# Include paths +DUET3EMB_INCLUDES := \ + -I$(WORKSPACE)/LibMbedTls/include \ + -I$(WORKSPACE)/LibMbedTls/library \ + -I$(WORKSPACE)/LibMbedTls/configs \ + -I$(WORKSPACE)/LibTinyusb \ + -I$(WORKSPACE)/CoreN2G \ + -I$(WORKSPACE)/CoreN2G/src \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70 \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf/sam/drivers \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf/sam/utils \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf/common/utils \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf/sam/utils/preprocessor \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf/sam/utils/header_files \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/asf/sam/utils/cmsis/same70/include \ + -I$(WORKSPACE)/CoreN2G/src/arm/CMSIS/5.4.0/CMSIS/Core/Include \ + -I$(WORKSPACE)/CoreN2G/src/SAM4S_4E_E70/SAME70 \ + -I$(WORKSPACE)/FreeRTOS \ + -I$(WORKSPACE)/FreeRTOS/src/include \ + -I$(WORKSPACE)/FreeRTOS/src/portable/GCC/ARM_CM7/r0p1 \ + -I$(WORKSPACE)/RRFLibraries/src \ + -I$(WORKSPACE)/CANlib/src \ + -I$(WORKSPACE)/WiFiSocketServerRTOS/src/include \ + -I$(DUET3EMB_SRC_DIR) \ + -I$(DUET3EMB_SRC_DIR)/Hardware/SAME70 \ + -I$(DUET3EMB_SRC_DIR)/Networking \ + -I$(DUET3EMB_SRC_DIR)/Networking/LwipEthernet/Lwip \ + -I$(DUET3EMB_SRC_DIR)/Networking/LwipEthernet/Lwip/src/include \ + -I$(DUET3EMB_SRC_DIR)/Networking/MQTT/MQTT_C/include + +# Preprocessor defines +MQTTC_PAL_DEFINE := Networking/MQTT/mqtt_pal.h +DUET3EMB_DEFINES := \ + -D__SAME70Q20B__ \ + -DRTOS \ + -DDUET3_MB6HC \ + -DMBEDTLS_CONFIG_FILE='"config-same70.h"' \ + -DUSE_EMBEDDED_FILES + +# Compiler flags - C +DUET3EMB_CFLAGS := -c -std=gnu99 \ + -Wall \ + -mcpu=cortex-m7 \ + -mthumb \ + -fno-math-errno \ + -mfpu=fpv5-d16 \ + -mfloat-abi=hard \ + -mfp16-format=ieee \ + -mno-unaligned-access \ + -ffunction-sections \ + -fdata-sections \ + -nostdlib \ + -Wundef \ + -Wdouble-promotion \ + -Werror=return-type \ + -Werror=implicit \ + -fsingle-precision-constant \ + -O2 \ + -Werror \ + -Wwrite-strings \ + $(DUET3EMB_INCLUDES) \ + $(DUET3EMB_DEFINES) \ + -Dnoexcept= \ + $(DEBUG_FLAGS) + +# Compiler flags - C++ +DUET3EMB_CXXFLAGS := -c -std=c++20 \ + -Wall \ + -mcpu=cortex-m7 \ + -mthumb \ + -fno-math-errno \ + -mfpu=fpv5-d16 \ + -mfloat-abi=hard \ + -mfp16-format=ieee \ + -mno-unaligned-access \ + -ffunction-sections \ + -fdata-sections \ + -fno-threadsafe-statics \ + -fno-rtti \ + -fexceptions \ + -nostdlib \ + -Wundef \ + -Wdouble-promotion \ + -Wfloat-conversion \ + -Werror=return-type \ + -Wsuggest-override \ + -fsingle-precision-constant \ + -fstack-usage \ + -O2 \ + -Werror \ + -Wnoexcept \ + -Wshadow \ + -Wsign-promo \ + $(DUET3EMB_INCLUDES) \ + $(DUET3EMB_DEFINES) \ + -D_XOPEN_SOURCE \ + $(DEBUG_FLAGS) + +# Linker flags - split into LDFLAGS1 (before -o) and LDFLAGS2 (after -o) +DUET3EMB_LDFLAGS1 := --specs=nosys.specs \ + -Os \ + -Wl,--gc-sections \ + -Wl,--fatal-warnings \ + -Wl,--no-warn-rwx-segment \ + -mcpu=cortex-m7 \ + -mfpu=fpv5-d16 \ + -mfloat-abi=hard \ + -T$(DUET3EMB_SRC_DIR)/Hardware/SAME70/same70q20b_flash.ld \ + -Wl,-Map,$(DUET3EMB_TARGET_MAP) \ + -mthumb + +DUET3EMB_LDFLAGS2 := \ + -Wl,--cref \ + -Wl,--check-sections \ + -Wl,--gc-sections \ + -Wl,--entry=Reset_Handler \ + -Wl,--unresolved-symbols=report-all \ + -Wl,--warn-common \ + -Wl,--warn-section-align \ + -Wl,--warn-unresolved-symbols + +# Library search paths +DUET3EMB_LDLIBS := \ + -L$(WORKSPACE)/CoreN2G/SAME70_CAN_SDHC_USB_RTOS \ + -L$(WORKSPACE)/RRFLibraries/SAME70_RTOS \ + -L$(WORKSPACE)/FreeRTOS/SAME70 \ + -L$(WORKSPACE)/CANlib/SAME70_RTOS \ + -L$(WORKSPACE)/LibTinyusb/SAME70 \ + -L$(WORKSPACE)/LibMbedTls/SAME70 \ + -lLibTinyusb \ + -lLibMbedTls \ + -lCoreN2G \ + -lRRFLibraries \ + -lFreeRTOS \ + -lCANlib \ + -lsupc++ + +DUET3EMB_LDLIBS_POST := -Wl,--end-group -lm + +# Object files - libcpp and libc first for proper symbol resolution +DUET3EMB_LIBCPP_OBJS := $(DUET3EMB_LIBCPP_SRCS:%.cpp=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_LIBCPP_OBJS := $(DUET3EMB_LIBCPP_OBJS:%.cc=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_LIBC_OBJS := $(DUET3EMB_LIBC_SRCS:%.c=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_LIBC_OBJS := $(DUET3EMB_LIBC_OBJS:%.cpp=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_CPP_OBJS := $(DUET3EMB_CPP_SRCS:%.cpp=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_CPP_OBJS := $(DUET3EMB_CPP_OBJS:%.cc=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_C_OBJS := $(DUET3EMB_C_SRCS:%.c=$(DUET3EMB_BUILD_DIR)/%.o) +DUET3EMB_OBJS := $(DUET3EMB_LIBCPP_OBJS) $(DUET3EMB_LIBC_OBJS) $(DUET3EMB_CPP_OBJS) $(DUET3EMB_C_OBJS) + +# Dependency files +DUET3EMB_DEPS := $(DUET3EMB_OBJS:.o=.d) + +# Target rule +.PHONY: Duet3_MB6HC_embedded +Duet3_MB6HC_embedded: $(DUET3EMB_TARGET_BIN) + $(Q)echo "========================================" + $(Q)echo "Duet3_MB6HC firmware build complete!" + $(Q)echo "Output: $(DUET3EMB_TARGET_BIN)" + $(Q)echo "========================================" + $(Q)$(SIZE) $(DUET3EMB_TARGET_ELF) + +# Link ELF file +$(DUET3EMB_TARGET_ELF): $(DUET3EMB_OBJS) $(DUET3EMB_CANLIB_LIB) $(DUET3EMB_COREN2G_LIB) $(DUET3EMB_RRFLIBS_LIB) $(DUET3EMB_FREERTOS_LIB) $(DUET3EMB_LIBTINYUSB_LIB) $(DUET3EMB_MBEDTLS_LIB) + $(Q)echo " LD $@" + $(Q)mkdir -p $(@D) + $(Q)$(LD) $(DUET3EMB_LDFLAGS1) -o $@ $(DUET3EMB_LDFLAGS2) -Wl,--start-group $(DUET3EMB_OBJS) $(DUET3EMB_LDLIBS) $(DUET3EMB_LDLIBS_POST) + +# Generate binary file +$(DUET3EMB_TARGET_BIN): $(DUET3EMB_TARGET_ELF) + $(Q)echo " OBJCOPY $@" + $(Q)$(OBJCOPY) -O binary $< $@ + $(Q)if command -v CrcAppender >/dev/null 2>&1; then \ + echo " CRC $@"; \ + CrcAppender $@; \ + fi + +# Compile C++ files +$(DUET3EMB_BUILD_DIR)/%.o: %.cpp + $(Q)echo " CXX $<" + $(Q)mkdir -p $(@D) + $(Q)$(CXX) $(DUET3EMB_CXXFLAGS) -DMQTTC_PAL_FILE="$(MQTTC_PAL_DEFINE)" -MMD -MP -o $@ $< + +# Compile C files +$(DUET3EMB_BUILD_DIR)/%.o: %.c + $(Q)echo " CC $<" + $(Q)mkdir -p $(@D) + $(Q)$(CC) $(DUET3EMB_CFLAGS) -DMQTTC_PAL_FILE="$(MQTTC_PAL_DEFINE)" -MMD -MP -o $@ $< + +# Compile .cc files (same as .cpp) +$(DUET3EMB_BUILD_DIR)/%.o: %.cc + $(Q)echo " CXX $<" + $(Q)mkdir -p $(@D) + $(Q)$(CXX) $(DUET3EMB_CXXFLAGS) -DMQTTC_PAL_FILE="$(MQTTC_PAL_DEFINE)" -MMD -MP -o $@ $< + +# Touch Version.cpp before build (pre-build step) +.PHONY: duet3mb6hc-embedded-prebuild +duet3mb6hc-embedded-prebuild: + $(Q)touch -c $(DUET3EMB_SRC_DIR)/Version.cpp 2>/dev/null || true + +$(DUET3EMB_OBJS): | duet3mb6hc-embedded-prebuild + +# Include dependencies +-include $(DUET3EMB_DEPS) + +# Clean target +.PHONY: clean-Duet3_MB6HC_embedded +clean-Duet3_MB6HC_embedded: + $(Q)echo " RM $(DUET3EMB_BUILD_DIR)" + $(Q)rm -rf $(DUET3EMB_BUILD_DIR) + +# Library dependencies (rules defined in main Makefile to avoid duplicates) +.PHONY: duet3mb6hc-embedded-libs +duet3mb6hc-embedded-libs: $(DUET3EMB_FREERTOS_LIB) $(DUET3EMB_COREN2G_LIB) $(DUET3EMB_RRFLIBS_LIB) $(DUET3EMB_CANLIB_LIB) $(DUET3EMB_LIBTINYUSB_LIB) $(DUET3EMB_MBEDTLS_LIB) + $(Q)echo "All required libraries built successfully" From 14dde9af9eb0b2cb581e440dd155139408104228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Metrot?= Date: Thu, 20 Aug 2026 20:57:49 +0200 Subject: [PATCH 2/3] Add a builder for the embedded filesystem image Companion to CrcAppender.py. Builds the structure EmbeddedFiles.cpp expects at _firmware_end: magic 0x543C2BEF, directoriesOffset, numFiles, then a descriptor per file, with every offset relative to _firmware_end. The part that is easy to get wrong is the CRC. A USE_EMBEDDED_FILES image links with _firmware_crc == _firmware_end, so vector slot 7 initially points at where the filesystem is about to go. Since AppMain CRCs everything below the address in that slot, the slot has to be moved past the filesystem - which this does, so CrcAppender.py must run after this tool and not before. Both refuse to run against an image whose slot 7 is not where they expect, rather than silently CRCing the wrong extent and leaving a board blinking on the bench. Verified end to end under the Renode emulator: the firmware accepts its own CRC, finds /sys/config.g and executes it. --- Scripts/BuildEmbeddedFiles.py | 115 ++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 Scripts/BuildEmbeddedFiles.py diff --git a/Scripts/BuildEmbeddedFiles.py b/Scripts/BuildEmbeddedFiles.py new file mode 100755 index 0000000000..5259fd7b4d --- /dev/null +++ b/Scripts/BuildEmbeddedFiles.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Append an embedded filesystem to a USE_EMBEDDED_FILES firmware image. + +Builds the structure src/Storage/EmbeddedFiles.cpp expects to find at _firmware_end: + + uint32 magic = 0x543C2BEF + uint32 directoriesOffset + uint32 numFiles + { uint32 nameOffset, uint32 contentOffset, uint32 contentLength } * numFiles + ... names, directory list, file contents + +All offsets are relative to _firmware_end, which for a correctly linked image is the current end of +the .bin. Names are full paths with a leading slash and no drive number ("/sys/config.g"), matching +what SkipDriveNumber leaves behind. The directory list is NUL-terminated strings followed by an empty +one. + +The image is linked with _firmware_crc == _firmware_end, so vector table slot 7 initially points at +where the filesystem starts. Since AppMain CRCs everything below the address in that slot, the slot +has to be moved past the filesystem - which this does. Run CrcAppender.py afterwards, not before. + +Usage: BuildEmbeddedFiles.py [--load-address 0x400000] +""" + +import argparse +import os +import struct +import sys + +MAGIC = 0x543C2BEF +VECTOR_SLOT_FIRMWARE_CRC = 0x1C +HEADER_SIZE = 12 +DESCRIPTOR_SIZE = 12 + + +def collect(root): + """Return [(stored_path, content_bytes)] and the set of directories, both sorted for determinism.""" + files = [] + directories = set() + for dirpath, _, filenames in os.walk(root): + rel_dir = os.path.relpath(dirpath, root) + for name in sorted(filenames): + if name.startswith('.'): + continue # skip .DS_Store and friends + parts = [] if rel_dir == '.' else rel_dir.split(os.sep) + stored = '/' + '/'.join(parts + [name]) + with open(os.path.join(dirpath, name), 'rb') as f: + files.append((stored, f.read())) + for i in range(len(parts)): + directories.add('/' + '/'.join(parts[:i + 1])) + files.sort(key=lambda item: item[0]) + return files, sorted(directories) + + +def build(files, directories): + body = bytearray() + body_base = HEADER_SIZE + DESCRIPTOR_SIZE * len(files) + + name_offsets = [] + for stored, _ in files: + name_offsets.append(body_base + len(body)) + body += stored.encode('ascii') + b'\0' + + directories_offset = body_base + len(body) + for directory in directories: + body += directory.encode('ascii') + b'\0' + body += b'\0' # empty string terminates the list + + descriptors = bytearray() + for (stored, content), name_offset in zip(files, name_offsets): + content_offset = body_base + len(body) + body += content + descriptors += struct.pack(' Date: Sat, 22 Aug 2026 01:26:50 +0200 Subject: [PATCH 3/3] Add a CRC appender for embedded images An image with the filesystem appended must have vector slot 7 moved past it before the CRC is computed, or the firmware rejects its own checksum and the board sits in a 3-blink loop. --- Scripts/CrcAppender.py | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 Scripts/CrcAppender.py diff --git a/Scripts/CrcAppender.py b/Scripts/CrcAppender.py new file mode 100755 index 0000000000..418faed11e --- /dev/null +++ b/Scripts/CrcAppender.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Append the firmware CRC that RepRapFirmware checks at startup. + +A drop-in replacement for the Duet3D CrcAppender binary that the board Makefiles call if it is on the +PATH. Without it the .bin is unflashable: AppMain (Platform/Tasks.cpp) recomputes this CRC on every +boot and, if it does not match, sits in a 3-blink diagnostic loop instead of starting. + +Vector table slot 7 (offset 0x1C) holds &_firmware_crc, the absolute address the CRC word lives at. +The image starts at the vector table, so the CRC offset within the file is that address minus the +load address, which for a correctly linked image is exactly the current end of the file. The CRC is +the standard reflected CRC-32 (poly 0xEDB88320, init 0xFFFFFFFF, final complement) that +src/Storage/CRC32.cpp implements, i.e. the one in zlib. + +Usage: CrcAppender.py [--load-address 0x400000] +""" + +import argparse +import struct +import sys +import zlib + +VECTOR_SLOT_FIRMWARE_CRC = 0x1C + + +def append_crc(path, load_address): + with open(path, 'rb') as f: + image = f.read() + + if len(image) < VECTOR_SLOT_FIRMWARE_CRC + 4: + raise SystemExit(f"{path}: too short to contain a vector table") + + crc_address, = struct.unpack_from('