Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
opt/
opt/**/
hex2bin

# If you prefer the allow list template instead of the deny list, see community template:
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ The tool supports two conversion modes:
- `input_file`: Path to the input file (**required**)
- `output_file`: Path to the output file (**optional**)
- `mode`: Conversion mode (`hex2bin` or `bin2hex`, **optional**)
- `--all`: (optional, for `bin2hex` only) Write all data, including 0xFF fill bytes, to the HEX file. By default, the tool writes only non-0xFF data (sparse HEX output).
- `--record-bytes=N`: (optional, for `bin2hex` only) Set the number of bytes per HEX record (default: 16, e.g., 16 or 32).

If `output_file` is not provided, it will be automatically determined from the input file name and mode.
If `mode` is not provided, it will be inferred from the input file extension:
Expand All @@ -60,6 +62,30 @@ If `mode` is not provided, it will be inferred from the input file extension:

If both `output_file` and `mode` are omitted, both will be inferred from the input file extension.

### HEX Output Modes

By default, when converting from binary to Intel HEX (`bin2hex`), the tool writes only non-0xFF data to the HEX file (sparse output). This matches the structure of reference HEX files and avoids unnecessary fill records.

To force the tool to write all data (including 0xFF fill bytes) in the HEX file, use the `--all` flag:

```bash
hex2bin firmware.bin --all
```

To control the number of bytes per HEX record, use the `--record-bytes=N` flag (e.g., 16 or 32):

```bash
hex2bin firmware.bin --record-bytes=16
```

You can combine both flags:

```bash
hex2bin firmware.bin --all --record-bytes=16
```

This will produce a HEX file with records of the specified size (default is 16 bytes per record).

### Examples

#### Minimal (auto mode and output):
Expand Down Expand Up @@ -107,3 +133,7 @@ hex2bin firmware.bin out.hex bin2hex
## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgement

This project was generated using only AI via the Cursor app.
115 changes: 71 additions & 44 deletions converter/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,50 +46,6 @@ func writeHexRecord(w io.Writer, recordType byte, address uint16, data []byte) e
return err
}

func BinToIntelHex(inputFile string, outputFile string) error {
// Read the binary input file
binData, err := os.ReadFile(inputFile)
if err != nil {
return fmt.Errorf("error reading input file: %v", err)
}

// Create output file
ofw, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer ofw.Close()

w := bufio.NewWriter(ofw)

// Write extended linear address record for the first segment
err = writeHexRecord(w, ExtLinearAddrRecord, 0x0000, []byte{0x00, 0x00})
if err != nil {
return fmt.Errorf("error writing extended address: %v", err)
}

// Write data records in 32-byte chunks
for i := 0; i < len(binData); i += 32 {
end := i + 32
if end > len(binData) {
end = len(binData)
}
chunk := binData[i:end]
err = writeHexRecord(w, DataRecord, uint16(i), chunk)
if err != nil {
return fmt.Errorf("error writing data chunk: %v", err)
}
}

// Write end of file record
err = writeHexRecord(w, EndOfFileRecord, 0x0000, nil)
if err != nil {
return fmt.Errorf("error writing EOF: %v", err)
}

return w.Flush()
}

func IntelHexToBin(inputFile string, outputFile string) error {
file, err := os.Open(inputFile)
if err != nil {
Expand Down Expand Up @@ -154,3 +110,74 @@ func IntelHexToBin(inputFile string, outputFile string) error {

return nil
}

// BinToIntelHexWithMode converts binary to Intel HEX, with a mode to write all data or only non-0xFF data
// recordSize controls the number of bytes per HEX record (e.g., 16, 32)
func BinToIntelHexWithMode(inputFile string, outputFile string, writeAll bool, recordSize int) error {
if recordSize <= 0 {
recordSize = 32
}
binData, err := os.ReadFile(inputFile)
if err != nil {
return fmt.Errorf("error reading input file: %v", err)
}

of, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer of.Close()

w := bufio.NewWriter(of)

// Write extended linear address record for the first segment
err = writeHexRecord(w, ExtLinearAddrRecord, 0x0000, []byte{0x00, 0x00})
if err != nil {
return fmt.Errorf("error writing extended address: %v", err)
}

if writeAll {
// Write all data in recordSize-byte chunks
for i := 0; i < len(binData); i += recordSize {
end := i + recordSize
if end > len(binData) {
end = len(binData)
}
chunk := binData[i:end]
err = writeHexRecord(w, DataRecord, uint16(i), chunk)
if err != nil {
return fmt.Errorf("error writing data chunk: %v", err)
}
}
} else {
// Improved sparse mode: write any chunk with at least one non-0xFF byte
for i := 0; i < len(binData); i += recordSize {
end := i + recordSize
if end > len(binData) {
end = len(binData)
}
chunk := binData[i:end]
write := false
for _, b := range chunk {
if b != 0xFF {
write = true
break
}
}
if write {
err = writeHexRecord(w, DataRecord, uint16(i), chunk)
if err != nil {
return fmt.Errorf("error writing data chunk: %v", err)
}
}
}
}

// Write end of file record
err = writeHexRecord(w, EndOfFileRecord, 0x0000, nil)
if err != nil {
return fmt.Errorf("error writing EOF: %v", err)
}

return w.Flush()
}
44 changes: 33 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,47 @@ func getModeFromInputExt(inputFile string) (string, error) {
}

func main() {
if len(os.Args) < 2 || len(os.Args) > 4 {
writeAll := false
recordSize := 16
args := os.Args[1:]
// Check for --all and --record-bytes flags
for i := 0; i < len(args); {
if args[i] == "--all" {
writeAll = true
args = append(args[:i], args[i+1:]...)
continue
}
if strings.HasPrefix(args[i], "--record-bytes=") {
var n int
_, err := fmt.Sscanf(args[i], "--record-bytes=%d", &n)
if err == nil && n > 0 {
recordSize = n
}
args = append(args[:i], args[i+1:]...)
continue
}
i++
}

if len(args) < 1 || len(args) > 3 {
fmt.Printf("hex2bin v%s (built %s)\n", version, buildTime)
fmt.Println("Usage: hex2bin <input_file> [output_file] [mode]")
fmt.Println("Usage: hex2bin <input_file> [output_file] [mode] [--all] [--record-bytes=N]")
fmt.Println("Mode: 'bin2hex' for binary to Intel HEX conversion")
fmt.Println(" 'hex2bin' for Intel HEX to binary conversion")
fmt.Println(" '--all' to write all data (no sparse HEX, for bin2hex mode)")
fmt.Println(" '--record-bytes=N' to set bytes per HEX record (default 16, e.g., 16 or 32)")
os.Exit(1)
}

inputFile := os.Args[1]
inputFile := args[0]
outputFile := ""
mode := ""

if len(os.Args) == 4 {
outputFile = os.Args[2]
mode = os.Args[3]
} else if len(os.Args) == 3 {
// Could be output file or mode
arg := os.Args[2]
if len(args) == 3 {
outputFile = args[1]
mode = args[2]
} else if len(args) == 2 {
arg := args[1]
if arg == "bin2hex" || arg == "hex2bin" {
mode = arg
var err error
Expand All @@ -94,7 +117,6 @@ func main() {
}
}
} else {
// Only input file provided
var err error
mode, err = getModeFromInputExt(inputFile)
if err != nil {
Expand All @@ -111,7 +133,7 @@ func main() {
var err error
switch mode {
case "bin2hex":
err = converter.BinToIntelHex(inputFile, outputFile)
err = converter.BinToIntelHexWithMode(inputFile, outputFile, writeAll, recordSize)
case "hex2bin":
err = converter.IntelHexToBin(inputFile, outputFile)
default:
Expand Down