Skip to content

Commit eff478e

Browse files
authored
Merge pull request #423 from gibix/tools/bin2uf2
tools: add bin2uf2 tool
2 parents 850a2f6 + c113fd7 commit eff478e

3 files changed

Lines changed: 193 additions & 0 deletions

File tree

tools/bin2uf2/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bin2uf2

tools/bin2uf2/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/arduino/bin2uf2
2+
3+
go 1.26

tools/bin2uf2/main.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright (c) Arduino s.r.l. and/or its affiliated companies
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package main
6+
7+
import (
8+
"encoding/binary"
9+
"flag"
10+
"fmt"
11+
"io"
12+
"os"
13+
"strconv"
14+
"strings"
15+
"unsafe"
16+
)
17+
18+
// UF2 block constants with fixed-size types.
19+
const (
20+
magic1 uint32 = 0x0A324655
21+
magic2 uint32 = 0x9E5D5157
22+
magic3 uint32 = 0x0AB16F30
23+
flags uint32 = 0x00002000 // familyID present
24+
payloadSize uint32 = 256
25+
blockSize uint32 = 512
26+
dataSectionSize uint32 = 476
27+
)
28+
29+
// UF2Block defines the structure of a UF2 block, used as a data container.
30+
// The Payload array is sized to hold the entire data section, so the unused
31+
// portion of the array acts as our padding.
32+
type UF2Block struct {
33+
Magic1 uint32
34+
Magic2 uint32
35+
Flags uint32
36+
TargetAddr uint32
37+
PayloadSize uint32
38+
BlockNo uint32
39+
NumBlocks uint32
40+
FamilyID uint32
41+
Payload [dataSectionSize]byte
42+
Magic3 uint32
43+
}
44+
45+
// Calculate the offset of the NumBlocks field within the block struct.
46+
const numBlocksOffset = unsafe.Offsetof(UF2Block{}.NumBlocks)
47+
48+
func main() {
49+
// Customize the default usage message to be more explicit.
50+
flag.Usage = func() {
51+
fmt.Fprintf(os.Stderr, "Usage: %s <addr> <familyID> <source file> <destination file>\n", os.Args[0])
52+
fmt.Fprintln(os.Stderr, "Converts a binary file to the UF2 format.")
53+
fmt.Fprintln(os.Stderr, "\nArguments:")
54+
fmt.Fprintln(os.Stderr, " addr Starting memory address in hexadecimal format (e.g. 0x100E0000)")
55+
fmt.Fprintln(os.Stderr, " familyID Family ID of the target device in hexadecimal format (e.g. 0xe48bff56)")
56+
fmt.Fprintln(os.Stderr, " source file Input binary file")
57+
fmt.Fprintln(os.Stderr, " dest file Output UF2 file")
58+
}
59+
60+
flag.Parse()
61+
62+
// Check for the correct number of positional arguments.
63+
if len(flag.Args()) != 4 {
64+
flag.Usage()
65+
os.Exit(1)
66+
}
67+
68+
// Parse the address from the first positional argument.
69+
parsedAddr, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(0), "0x"), 16, 32)
70+
if err != nil {
71+
fmt.Fprintf(os.Stderr, "Error: Invalid address format: %v\n", err)
72+
os.Exit(1)
73+
}
74+
address := uint32(parsedAddr)
75+
76+
// Parse the familyID from the second positional argument.
77+
parsedFamilyID, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(1), "0x"), 16, 32)
78+
if err != nil {
79+
fmt.Fprintf(os.Stderr, "Error: Invalid familyID format: %v\n", err)
80+
os.Exit(1)
81+
}
82+
familyID := uint32(parsedFamilyID)
83+
84+
srcPath := flag.Arg(2)
85+
dstPath := flag.Arg(3)
86+
87+
// Open source file
88+
src, err := os.Open(srcPath)
89+
if err != nil {
90+
fmt.Fprintf(os.Stderr, "Error: Could not open source file %s: %v\n", srcPath, err)
91+
os.Exit(1)
92+
}
93+
defer src.Close()
94+
95+
// Create destination file
96+
dst, err := os.Create(dstPath)
97+
if err != nil {
98+
fmt.Fprintf(os.Stderr, "Error: Could not create destination file %s: %v\n", dstPath, err)
99+
os.Exit(1)
100+
}
101+
defer dst.Close()
102+
103+
var blockNum uint32
104+
var totalBlocks uint32
105+
// This slice is a temporary buffer for reading one payload-worth of data.
106+
readBuffer := make([]byte, payloadSize)
107+
108+
// Main loop to read source and write UF2 blocks
109+
for {
110+
bytesRead, err := io.ReadFull(src, readBuffer)
111+
if err == io.EOF {
112+
break
113+
}
114+
if err != nil && err != io.ErrUnexpectedEOF {
115+
fmt.Fprintf(os.Stderr, "Error: Failed reading from source file %s: %v\n", srcPath, err)
116+
os.Exit(1)
117+
}
118+
119+
// Zero out the unused portion of the buffer for partial reads
120+
for i := bytesRead; i < int(payloadSize); i++ {
121+
readBuffer[i] = 0
122+
}
123+
124+
// Create the block struct and populate its fields.
125+
block := UF2Block{
126+
Magic1: magic1,
127+
Magic2: magic2,
128+
Flags: flags,
129+
TargetAddr: address,
130+
PayloadSize: payloadSize,
131+
BlockNo: blockNum,
132+
NumBlocks: 0, // Placeholder, will be updated later.
133+
FamilyID: familyID,
134+
Magic3: magic3,
135+
}
136+
// Copy the data from our read buffer into the beginning of the
137+
// larger Payload array. The rest of the array remains zero, acting as padding.
138+
copy(block.Payload[:], readBuffer)
139+
140+
// --- Write the block to disk piece-by-piece ---
141+
// 1. Write the header fields
142+
binary.Write(dst, binary.LittleEndian, block.Magic1)
143+
binary.Write(dst, binary.LittleEndian, block.Magic2)
144+
binary.Write(dst, binary.LittleEndian, block.Flags)
145+
binary.Write(dst, binary.LittleEndian, block.TargetAddr)
146+
binary.Write(dst, binary.LittleEndian, block.PayloadSize)
147+
binary.Write(dst, binary.LittleEndian, block.BlockNo)
148+
binary.Write(dst, binary.LittleEndian, block.NumBlocks)
149+
binary.Write(dst, binary.LittleEndian, block.FamilyID)
150+
151+
// 2. Write the entire 476-byte data section (payload + padding) in one go.
152+
if _, err := dst.Write(block.Payload[:]); err != nil {
153+
fmt.Fprintf(os.Stderr, "Error: Failed writing data section to %s: %v\n", dstPath, err)
154+
os.Exit(1)
155+
}
156+
157+
// 3. Write the final magic number
158+
if err := binary.Write(dst, binary.LittleEndian, block.Magic3); err != nil {
159+
fmt.Fprintf(os.Stderr, "Error: Failed writing final magic to %s: %v\n", dstPath, err)
160+
os.Exit(1)
161+
}
162+
163+
address += payloadSize
164+
blockNum++
165+
166+
if err == io.EOF || bytesRead < int(payloadSize) {
167+
break
168+
}
169+
}
170+
171+
totalBlocks = blockNum
172+
173+
// After writing all blocks, seek back and update the totalBlocks field in each header
174+
for i := uint32(0); i < totalBlocks; i++ {
175+
// Calculate the offset using our safe constant instead of a magic number.
176+
offset := int64(i)*int64(blockSize) + int64(numBlocksOffset)
177+
_, err := dst.Seek(offset, io.SeekStart)
178+
if err != nil {
179+
fmt.Fprintf(os.Stderr, "Error: Failed seeking in destination file %s: %v\n", dstPath, err)
180+
os.Exit(1)
181+
}
182+
if err := binary.Write(dst, binary.LittleEndian, totalBlocks); err != nil {
183+
fmt.Fprintf(os.Stderr, "Error: Failed updating total blocks in %s: %v\n", dstPath, err)
184+
os.Exit(1)
185+
}
186+
}
187+
188+
fmt.Printf("Successfully converted %s to %s (%d blocks written).\n", srcPath, dstPath, totalBlocks)
189+
}

0 commit comments

Comments
 (0)