From cfec70b9b6ad6aa10079add10640add3dcf4dc55 Mon Sep 17 00:00:00 2001 From: cumin <13809292481@163.com> Date: Mon, 17 Aug 2026 17:41:09 +0800 Subject: [PATCH 1/2] feat: add cross-platform UF2 uploader --- .gitattributes | 3 +- LICENCE.md | 1 + README.md | 17 ++ linux/x86_64/uf2upload | Bin 0 -> 38224 bytes src/build_mac.sh | 11 + src/linux-aarch64/Dockerfile | 14 +- src/linux-aarch64/build.sh | 4 + src/uf2upload/Makefile | 25 ++ src/uf2upload/uf2upload.cpp | 480 +++++++++++++++++++++++++++++++++++ win/uf2upload.exe | Bin 0 -> 2410313 bytes 10 files changed, 551 insertions(+), 4 deletions(-) create mode 100644 linux/x86_64/uf2upload create mode 100644 src/uf2upload/Makefile create mode 100644 src/uf2upload/uf2upload.cpp create mode 100644 win/uf2upload.exe diff --git a/.gitattributes b/.gitattributes index 4e560bb50..ab32f7524 100644 --- a/.gitattributes +++ b/.gitattributes @@ -47,6 +47,8 @@ dfu-suffix binary dfu-util binary upload-reset binary hid-flash binary +uf2upload binary +uf2upload.exe binary *.class binary *.dll binary *.exe binary @@ -54,4 +56,3 @@ hid-flash binary *.jpg binary *.pdf binary *.png binary - diff --git a/LICENCE.md b/LICENCE.md index 6e92b637d..a61fc5827 100644 --- a/LICENCE.md +++ b/LICENCE.md @@ -4,6 +4,7 @@ This files includes licensing information for parts of this repository. * [busybox-w32](https://frippery.org/busybox/) * [dfu-util](http://dfu-util.sourceforge.net/) (also available [here](src/dfu-util/COPYING)) * upload-reset +* uf2upload [GPLv3](#gplv3) is used for: * [HID-flash](https://github.com/Serasidis/STM32_HID_Bootloader) diff --git a/README.md b/README.md index 391dbc37b..914d9eba8 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,20 @@ [![GitHub commits](https://img.shields.io/github/commits-since/stm32duino/Arduino_Tools/2.5.0.svg)](https://github.com/stm32duino/Arduino_Tools/compare/2.4.0...main) Contains upload tools for STM32 based boards and some other usefull scripts. + +## UF2 uploader + +`uf2upload` is a self-contained host tool for a board with a UF2 mass-storage +bootloader. It first checks whether the target UF2 volume is already mounted; +when it is, it copies the converted image directly. Otherwise it performs a +USB CDC 1200-bps touch, waits for the volume label and copies the result. It +has no Python or shell dependency at upload time: + +``` +uf2upload --input firmware.bin --port COM7 --volume XIAOC5BOOT \ + --address 0x08008000 --family 0x00C5C5C5 --max-size 1015808 +``` + +Use `--skip-touch` to disable the CDC touch when no bootloader volume is +currently mounted. The source is built for the host package with +`make -C src/uf2upload`. diff --git a/linux/x86_64/uf2upload b/linux/x86_64/uf2upload new file mode 100644 index 0000000000000000000000000000000000000000..d4c1ebcfac2e2e52d73f87cce89baa45f41945b7 GIT binary patch literal 38224 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 | head -5 || echo "(upload_reset executed successfully)") && \ echo "" && \ + echo "=== Testing uf2upload ===" && \ + file /usr/local/bin/uf2upload && \ + uf2upload --help && \ + echo "" && \ echo "========================================" && \ echo "✓ All ARM64 binaries are working!" && \ echo "========================================" diff --git a/src/linux-aarch64/build.sh b/src/linux-aarch64/build.sh index 41c302aa0..6d198f3ef 100755 --- a/src/linux-aarch64/build.sh +++ b/src/linux-aarch64/build.sh @@ -58,6 +58,7 @@ docker cp "${CONTAINER_ID}:/dfu-prefix" "${OUTPUT_DIR}/" docker cp "${CONTAINER_ID}:/dfu-suffix" "${OUTPUT_DIR}/" docker cp "${CONTAINER_ID}:/hid-flash" "${OUTPUT_DIR}/" docker cp "${CONTAINER_ID}:/upload_reset" "${OUTPUT_DIR}/" +docker cp "${CONTAINER_ID}:/uf2upload" "${OUTPUT_DIR}/" echo "" echo "Step 5: Cleaning up container..." @@ -70,6 +71,7 @@ chmod +x "${OUTPUT_DIR}/dfu-prefix" chmod +x "${OUTPUT_DIR}/dfu-suffix" chmod +x "${OUTPUT_DIR}/hid-flash" chmod +x "${OUTPUT_DIR}/upload_reset" +chmod +x "${OUTPUT_DIR}/uf2upload" echo "" echo "Step 7: Verifying built binaries..." @@ -79,6 +81,7 @@ file "${OUTPUT_DIR}/dfu-prefix" file "${OUTPUT_DIR}/dfu-suffix" file "${OUTPUT_DIR}/hid-flash" file "${OUTPUT_DIR}/upload_reset" +file "${OUTPUT_DIR}/uf2upload" echo "----------------------------------------" echo "" @@ -92,6 +95,7 @@ echo "All required binaries have been built:" echo " - dfu-util, dfu-prefix, dfu-suffix (DFU programming)" echo " - hid-flash (HID bootloader)" echo " - upload_reset (Reset utility)" +echo " - uf2upload (CDC touch and UF2 mass-storage upload)" echo "" echo "Files in ${OUTPUT_DIR}:" ls -lh "${OUTPUT_DIR}" diff --git a/src/uf2upload/Makefile b/src/uf2upload/Makefile new file mode 100644 index 000000000..120111387 --- /dev/null +++ b/src/uf2upload/Makefile @@ -0,0 +1,25 @@ +CXX ?= g++ +CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -Werror + +ifeq ($(OS),Windows_NT) + DEST = ../../win/uf2upload.exe + LDFLAGS ?= -static -static-libgcc -static-libstdc++ +else + LDFLAGS ?= + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + DEST = ../../macosx/uf2upload + else + DEST = ../../linux/x86_64/uf2upload + endif +endif + +.PHONY: all clean + +all: $(DEST) + +$(DEST): uf2upload.cpp + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $< + +clean: + rm -f ../../win/uf2upload.exe ../../macosx/uf2upload ../../linux/x86_64/uf2upload diff --git a/src/uf2upload/uf2upload.cpp b/src/uf2upload/uf2upload.cpp new file mode 100644 index 000000000..9e61c25fe --- /dev/null +++ b/src/uf2upload/uf2upload.cpp @@ -0,0 +1,480 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Cross-platform UF2 uploader for Arduino boards using a USB CDC touch and + * a UF2 mass-storage bootloader. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +namespace { + +constexpr uint32_t UF2_MAGIC_START0 = 0x0A324655UL; +constexpr uint32_t UF2_MAGIC_START1 = 0x9E5D5157UL; +constexpr uint32_t UF2_MAGIC_END = 0x0AB16F30UL; +constexpr uint32_t UF2_FLAG_FAMILY_ID_PRESENT = 0x00002000UL; +constexpr size_t UF2_BLOCK_SIZE = 512U; +constexpr size_t UF2_PAYLOAD_SIZE = 256U; + +struct Options { + std::string input; + std::string port; + std::string volume; + uint32_t address = 0U; + uint32_t family = 0U; + size_t max_size = 0U; + unsigned int timeout_ms = 10000U; + bool skip_touch = false; +}; + +void usage(const char *program) +{ + std::cout << "Usage: " << program << " --input [--port ]" + << " --volume