Skip to content

Commit 55dd218

Browse files
committed
feat: add Intel HEX and S-record output format support for read command
Extends the read command with output format selection via the -F/--output-format option. Format detection uses file extension by default (auto mode), supporting binary (.bin), Intel HEX (.hex), and Motorola S-record (.srec, .s19, .s37) outputs. The Intel HEX encoder uses extended linear address records (type 04) for addresses above 64KB. The S-record encoder generates S3 data records with 32-bit addresses and S7 end records, compatible with standard toolchains. Includes roundtrip unit tests that verify encoder output can be correctly parsed back by the corresponding input parsers.
1 parent 1426661 commit 55dd218

File tree

7 files changed

+437
-42
lines changed

7 files changed

+437
-42
lines changed

radfu.h2m

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ version, and memory area layout. This command does not modify the device.
6666

6767
.TP
6868
.B read <file>
69-
Read flash memory contents to a binary file. Use \fB-a\fR to specify the
69+
Read flash memory contents to a file. Use \fB-a\fR to specify the
7070
start address and \fB-s\fR for the size to read. If size is omitted, reads
71-
the entire flash area.
71+
the entire flash area. Use \fB-F\fR to specify the output format (auto-detected
72+
by default from extension). Supported formats: binary (.bin), Intel HEX (.hex),
73+
and Motorola S-record (.srec, .s19, .s37).
7274

7375
.TP
7476
.B write <file>
@@ -173,6 +175,34 @@ radfu verify firmware.hex # Verify flash against Intel HEX file
173175
radfu verify -a 0x08000000 app.s19 # Verify at explicit address
174176
.fi
175177

178+
[output formats]
179+
The \fBread\fR command supports multiple output file formats.
180+
Use \fB-F\fR to explicitly specify the format, or let radfu auto-detect from the
181+
file extension.
182+
183+
.SS Supported Formats
184+
.TP
185+
.B binary (bin)
186+
Raw binary data. Extensions: .bin, .dat, or any unknown extension.
187+
No address information included; only raw flash data.
188+
189+
.TP
190+
.B ihex (Intel HEX)
191+
Intel HEX format with extended linear address records (type 04) for addresses
192+
above 64KB. Extensions: .hex, .ihex.
193+
194+
.TP
195+
.B srec (Motorola S-record)
196+
Motorola S-record format with S0 header, S3 data records (32-bit addresses),
197+
and S7 end record. Extensions: .srec, .s19, .s28, .s37, .mot.
198+
199+
.SS Examples
200+
.nf
201+
radfu read -a 0x0 -s 0x10000 firmware.hex # Auto-detect Intel HEX output
202+
radfu read -a 0x0 -s 0x10000 -F srec out.dat # Force S-record format
203+
radfu read -a 0x08000000 -s 0x2000 data.bin # Binary output
204+
.fi
205+
176206
[usb vs uart]
177207
RA microcontrollers support two communication interfaces for the ROM bootloader:
178208

@@ -303,6 +333,8 @@ radfu init # Factory reset to SSD (erases flash)
303333
.nf
304334
radfu info
305335
radfu read -a 0x0 -s 0x10000 firmware.bin
336+
radfu read -a 0x0 -s 0x10000 firmware.hex # Intel HEX output
337+
radfu read -a 0x0 -s 0x10000 -F srec firmware.s19 # S-record output
306338
radfu write -b 1000000 -a 0x0 -v firmware.bin
307339
radfu verify -a 0x0 firmware.bin
308340
radfu erase -a 0x0 -s 0x10000

src/formats.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,145 @@ format_parse(const char *filename, input_format_t format, parsed_file_t *out) {
493493
return -1;
494494
}
495495
}
496+
497+
/*
498+
* Intel HEX encoder
499+
*/
500+
501+
#define IHEX_BYTES_PER_LINE 16
502+
503+
int
504+
ihex_write(const char *filename, const uint8_t *data, size_t size, uint32_t addr) {
505+
FILE *fp = fopen(filename, "w");
506+
if (!fp) {
507+
warn("failed to open %s", filename);
508+
return -1;
509+
}
510+
511+
uint32_t current_ext_addr = 0;
512+
size_t offset = 0;
513+
514+
while (offset < size) {
515+
uint32_t line_addr = addr + (uint32_t)offset;
516+
517+
/* Emit extended linear address record if needed (type 04) */
518+
uint32_t ext_addr = line_addr >> 16;
519+
if (ext_addr != current_ext_addr) {
520+
uint8_t sum = 0x02 + 0x00 + 0x00 + 0x04 + (ext_addr >> 8) + (ext_addr & 0xFF);
521+
fprintf(fp, ":02000004%04X%02X\n", ext_addr, (uint8_t)(~sum + 1));
522+
current_ext_addr = ext_addr;
523+
}
524+
525+
/* Data record (type 00) */
526+
size_t remaining = size - offset;
527+
size_t line_len = remaining < IHEX_BYTES_PER_LINE ? remaining : IHEX_BYTES_PER_LINE;
528+
uint16_t rec_addr = line_addr & 0xFFFF;
529+
530+
uint8_t sum = (uint8_t)line_len + (rec_addr >> 8) + (rec_addr & 0xFF) + 0x00;
531+
fprintf(fp, ":%02X%04X00", (unsigned)line_len, rec_addr);
532+
533+
for (size_t i = 0; i < line_len; i++) {
534+
fprintf(fp, "%02X", data[offset + i]);
535+
sum += data[offset + i];
536+
}
537+
538+
fprintf(fp, "%02X\n", (uint8_t)(~sum + 1));
539+
offset += line_len;
540+
}
541+
542+
/* EOF record (type 01) */
543+
fprintf(fp, ":00000001FF\n");
544+
545+
fclose(fp);
546+
return 0;
547+
}
548+
549+
/*
550+
* Motorola S-record encoder
551+
*/
552+
553+
#define SREC_BYTES_PER_LINE 16
554+
555+
int
556+
srec_write(const char *filename, const uint8_t *data, size_t size, uint32_t addr) {
557+
FILE *fp = fopen(filename, "w");
558+
if (!fp) {
559+
warn("failed to open %s", filename);
560+
return -1;
561+
}
562+
563+
/* S0 header record */
564+
const char *hdr = "HDR";
565+
size_t hdr_len = strlen(hdr);
566+
uint8_t sum = (uint8_t)(hdr_len + 3); /* byte count includes address (2) + data + checksum */
567+
fprintf(fp, "S0%02X0000", (unsigned)(hdr_len + 3));
568+
for (size_t i = 0; i < hdr_len; i++) {
569+
fprintf(fp, "%02X", (uint8_t)hdr[i]);
570+
sum += (uint8_t)hdr[i];
571+
}
572+
fprintf(fp, "%02X\n", (uint8_t)(~sum));
573+
574+
/* S3 data records (32-bit address) */
575+
size_t offset = 0;
576+
while (offset < size) {
577+
uint32_t line_addr = addr + (uint32_t)offset;
578+
size_t remaining = size - offset;
579+
size_t line_len = remaining < SREC_BYTES_PER_LINE ? remaining : SREC_BYTES_PER_LINE;
580+
581+
/* Byte count = address (4) + data + checksum (1) */
582+
uint8_t byte_count = (uint8_t)(4 + line_len + 1);
583+
sum = byte_count;
584+
sum += (line_addr >> 24) & 0xFF;
585+
sum += (line_addr >> 16) & 0xFF;
586+
sum += (line_addr >> 8) & 0xFF;
587+
sum += line_addr & 0xFF;
588+
589+
fprintf(fp, "S3%02X%08X", byte_count, line_addr);
590+
591+
for (size_t i = 0; i < line_len; i++) {
592+
fprintf(fp, "%02X", data[offset + i]);
593+
sum += data[offset + i];
594+
}
595+
596+
fprintf(fp, "%02X\n", (uint8_t)(~sum));
597+
offset += line_len;
598+
}
599+
600+
/* S7 end record (32-bit start address) */
601+
sum = 0x05 + ((addr >> 24) & 0xFF) + ((addr >> 16) & 0xFF) + ((addr >> 8) & 0xFF) + (addr & 0xFF);
602+
fprintf(fp, "S705%08X%02X\n", addr, (uint8_t)(~sum));
603+
604+
fclose(fp);
605+
return 0;
606+
}
607+
608+
int
609+
format_write(
610+
const char *filename, output_format_t format, const uint8_t *data, size_t size, uint32_t addr) {
611+
if (format == FORMAT_AUTO)
612+
format = format_detect(filename);
613+
614+
switch (format) {
615+
case FORMAT_BIN: {
616+
FILE *fp = fopen(filename, "wb");
617+
if (!fp) {
618+
warn("failed to open %s", filename);
619+
return -1;
620+
}
621+
if (fwrite(data, 1, size, fp) != size) {
622+
warn("failed to write %s", filename);
623+
fclose(fp);
624+
return -1;
625+
}
626+
fclose(fp);
627+
return 0;
628+
}
629+
case FORMAT_IHEX:
630+
return ihex_write(filename, data, size, addr);
631+
case FORMAT_SREC:
632+
return srec_write(filename, data, size, addr);
633+
default:
634+
warnx("unknown output format");
635+
return -1;
636+
}
637+
}

src/formats.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* SPDX-License-Identifier: AGPL-3.0-or-later
55
*
6-
* Input file format parsers for Intel HEX and Motorola S-record
6+
* File format parsers and encoders for Intel HEX and Motorola S-record
77
*
88
* Design goal: small and simple implementation covering common use cases.
99
* TODO: Consider using an upstream library (e.g., arkku/ihex, arkku/srec)
@@ -16,14 +16,17 @@
1616
#include <stddef.h>
1717
#include <stdint.h>
1818

19-
/* Supported input file formats */
19+
/* Supported file formats (used for both input and output) */
2020
typedef enum {
2121
FORMAT_AUTO, /* Auto-detect from file extension */
2222
FORMAT_BIN, /* Raw binary */
2323
FORMAT_IHEX, /* Intel HEX */
2424
FORMAT_SREC, /* Motorola S-record */
2525
} input_format_t;
2626

27+
/* Output format type (alias for clarity) */
28+
typedef input_format_t output_format_t;
29+
2730
/* Parsed file data */
2831
typedef struct {
2932
uint8_t *data; /* Binary data buffer (caller must free) */
@@ -69,4 +72,24 @@ int srec_parse(const char *filename, parsed_file_t *out);
6972
*/
7073
int bin_parse(const char *filename, parsed_file_t *out);
7174

75+
/*
76+
* Write data to file in specified format
77+
* If format is FORMAT_AUTO, detects from extension
78+
* Returns: 0 on success, -1 on error
79+
*/
80+
int format_write(
81+
const char *filename, output_format_t format, const uint8_t *data, size_t size, uint32_t addr);
82+
83+
/*
84+
* Write data as Intel HEX file
85+
* Returns: 0 on success, -1 on error
86+
*/
87+
int ihex_write(const char *filename, const uint8_t *data, size_t size, uint32_t addr);
88+
89+
/*
90+
* Write data as Motorola S-record file
91+
* Returns: 0 on success, -1 on error
92+
*/
93+
int srec_write(const char *filename, const uint8_t *data, size_t size, uint32_t addr);
94+
7295
#endif /* FORMATS_H */

src/main.c

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ usage(int status) {
6060
" -e, --erase-all Erase all areas using ALeRASE magic ID\n"
6161
" -v, --verify Verify after write\n"
6262
" -f, --input-format <fmt> Input file format (auto/bin/ihex/srec)\n"
63+
" -F, --output-format <fmt> Output file format (auto/bin/ihex/srec)\n"
6364
" -u, --uart Use plain UART mode (P109/P110 pins)\n"
6465
" --cfs1 <KB> Code flash secure region size without NSC\n"
6566
" --cfs2 <KB> Code flash secure region size (total)\n"
@@ -267,23 +268,24 @@ parse_key_type(const char *str) {
267268
#define OPT_SRS2 260
268269

269270
static const struct option longopts[] = {
270-
{ "port", required_argument, NULL, 'p' },
271-
{ "address", required_argument, NULL, 'a' },
272-
{ "size", required_argument, NULL, 's' },
273-
{ "baudrate", required_argument, NULL, 'b' },
274-
{ "id", required_argument, NULL, 'i' },
275-
{ "erase-all", no_argument, NULL, 'e' },
276-
{ "verify", no_argument, NULL, 'v' },
277-
{ "input-format", required_argument, NULL, 'f' },
278-
{ "uart", no_argument, NULL, 'u' },
279-
{ "cfs1", required_argument, NULL, OPT_CFS1 },
280-
{ "cfs2", required_argument, NULL, OPT_CFS2 },
281-
{ "dfs", required_argument, NULL, OPT_DFS },
282-
{ "srs1", required_argument, NULL, OPT_SRS1 },
283-
{ "srs2", required_argument, NULL, OPT_SRS2 },
284-
{ "help", no_argument, NULL, 'h' },
285-
{ "version", no_argument, NULL, 'V' },
286-
{ NULL, 0, NULL, 0 }
271+
{ "port", required_argument, NULL, 'p' },
272+
{ "address", required_argument, NULL, 'a' },
273+
{ "size", required_argument, NULL, 's' },
274+
{ "baudrate", required_argument, NULL, 'b' },
275+
{ "id", required_argument, NULL, 'i' },
276+
{ "erase-all", no_argument, NULL, 'e' },
277+
{ "verify", no_argument, NULL, 'v' },
278+
{ "input-format", required_argument, NULL, 'f' },
279+
{ "output-format", required_argument, NULL, 'F' },
280+
{ "uart", no_argument, NULL, 'u' },
281+
{ "cfs1", required_argument, NULL, OPT_CFS1 },
282+
{ "cfs2", required_argument, NULL, OPT_CFS2 },
283+
{ "dfs", required_argument, NULL, OPT_DFS },
284+
{ "srs1", required_argument, NULL, OPT_SRS1 },
285+
{ "srs2", required_argument, NULL, OPT_SRS2 },
286+
{ "help", no_argument, NULL, 'h' },
287+
{ "version", no_argument, NULL, 'V' },
288+
{ NULL, 0, NULL, 0 }
287289
};
288290

289291
int
@@ -300,6 +302,7 @@ main(int argc, char *argv[]) {
300302
bool erase_all = false;
301303
bool uart_mode = false;
302304
input_format_t input_format = FORMAT_AUTO;
305+
output_format_t output_format = FORMAT_AUTO;
303306
uint8_t dest_dlm = 0;
304307
uint8_t param_value = 0;
305308
uint8_t key_index = 0;
@@ -311,7 +314,7 @@ main(int argc, char *argv[]) {
311314
enum command cmd = CMD_NONE;
312315
int opt;
313316

314-
while ((opt = getopt_long(argc, argv, "p:a:s:b:i:evf:uhV", longopts, NULL)) != -1) {
317+
while ((opt = getopt_long(argc, argv, "p:a:s:b:i:evf:F:uhV", longopts, NULL)) != -1) {
315318
switch (opt) {
316319
case 'p':
317320
port = optarg;
@@ -346,6 +349,18 @@ main(int argc, char *argv[]) {
346349
else
347350
errx(EXIT_FAILURE, "unknown input format: %s (use auto/bin/ihex/srec)", optarg);
348351
break;
352+
case 'F':
353+
if (strcasecmp(optarg, "auto") == 0)
354+
output_format = FORMAT_AUTO;
355+
else if (strcasecmp(optarg, "bin") == 0 || strcasecmp(optarg, "binary") == 0)
356+
output_format = FORMAT_BIN;
357+
else if (strcasecmp(optarg, "ihex") == 0 || strcasecmp(optarg, "hex") == 0)
358+
output_format = FORMAT_IHEX;
359+
else if (strcasecmp(optarg, "srec") == 0 || strcasecmp(optarg, "s19") == 0)
360+
output_format = FORMAT_SREC;
361+
else
362+
errx(EXIT_FAILURE, "unknown output format: %s (use auto/bin/ihex/srec)", optarg);
363+
break;
349364
case 'u':
350365
uart_mode = true;
351366
break;
@@ -592,7 +607,7 @@ main(int argc, char *argv[]) {
592607
}
593608
break;
594609
case CMD_READ:
595-
ret = ra_read(&dev, file, address, size);
610+
ret = ra_read(&dev, file, address, size, output_format);
596611
break;
597612
case CMD_WRITE:
598613
ret = ra_write(&dev, file, address, size, verify, input_format);

0 commit comments

Comments
 (0)