srec:   Motorola S-records
1 Quick start
2 Records
srec-record
parse-srec
parse-srec-line
3 Writing
write-srec-record
bytes->srec
4 Images
srec->bytes
srec-start-address
5 License
9.3

srec: Motorola S-records🔗ℹ

turinglambdaai

 (require srec) package: srec

The "srec" package reads and writes Motorola S-record files — the line-oriented firmware image format (also known as SRECORD, S19/S28/S37) burned into microcontrollers and attached to bug reports everywhere. Everything is implemented in pure Racket: no FFI, no system dependency.

The writer is validated byte-for-byte against the Python bincopy library (its outputs ship as golden fixtures), and the parser accepts everything bincopy produces — plus a few things it gets wrong: record types are strictly validated, byte counts and checksums are enforced, and addresses are checked against the declared record width (bincopy silently emits malformed 16-bit records for larger addresses).

1 Quick start🔗ℹ

Examples:
> (define image (bytes 222 173 190 239))
> (define text (bytes->srec image
                            #:base-address 4096
                            #:address-length-bits 16
                            #:start-address 8192))
> (display text)

S1071000DEADBEEFB0

S5030001FB

S9032000DC

> (srec->bytes text)

#"\336\255\276\357"

> (srec-start-address text)

8192

2 Records🔗ℹ

An S-record line is S<type><count><address><data><checksum>, all hexadecimal, where count covers the address, the data and the checksum itself. The checksum is the one’s complement of the LSB of the sum of count, address and data bytes — srec-checksum.

struct

(struct srec-record (kind address data))

  kind : (or/c 'header 'data 'count 'start)
  address : exact-nonnegative-integer?
  data : bytes?
One record line. kind maps the record type letters: S0 → 'header (its data is the header comment), S1/S2/S3 → 'data (16/24/32-bit addresses), S5/S6 → 'count (address carries the count value), and S7/S8/S9 → 'start (the execution entry address).

procedure

(parse-srec text) → (listof srec-record?)

  text : string?
Parses a whole file. Blank lines are skipped and \r\n endings are accepted. Violations of the grammar — bad byte counts, checksum mismatches, odd hex, unknown record types, data on S5/S7/ S8/S9 records — raise exn:fail with the line number.

procedure

(parse-srec-line line line-no) → srec-record?

  line : string?
  line-no : exact-nonnegative-integer?
Parses one record line; line-no only appears in error messages.

3 Writing🔗ℹ

procedure

(write-srec-record rec    
  [#:address-length-bits bits]) → string?
  rec : srec-record?
  bits : (or/c 16 24 32) = 32
Renders one record line (no newline). Data records become S1/S2/S3 per bits; count records choose S5 or S6 by magnitude; start records choose S9/S8/S7. Addresses that do not fit the declared width are an error.

procedure

(bytes->srec bs    
  [#:base-address base    
  #:address-length-bits bits    
  #:data-bytes-per-line per-line    
  #:header header    
  #:start-address start    
  #:trailing-newline? trailing-newline?]) → string?
  bs : bytes?
  base : exact-nonnegative-integer? = 0
  bits : (or/c 16 24 32) = 32
  per-line : (integer-in 1 253) = 32
  header : (or/c bytes? #f) = #f
  start : (or/c exact-nonnegative-integer? #f) = #f
  trailing-newline? : boolean? = #t
Renders a complete image: the optional S0 header, the data records, the S5/S6 record count, and the optional start record. An image whose end address does not fit bits is an error.

4 Images🔗ℹ

procedure

(srec->bytes text [#:fill fill]) → bytes?

  text : string?
  fill : (integer-in 0 255) = 255
Assembles the data records into one byte string spanning the lowest record address through the end of the highest. Gaps are filled with fill; overlapping records are an error; header, count and start records are ignored.

procedure

(srec-start-address text)

 → (or/c exact-nonnegative-integer? #f)
  text : string?
The entry address of the last S7/S8/S9 record, or #f when the file declares none.

5 License🔗ℹ

MIT.