Post

Reading Motorola S-records with Haskell

Initial steps to parsing and reading Motorola S-records using Haskell

Reading Motorola S-records with Haskell

Brief introduction to S-record format

A wonderful and detailed description of the full SREC format is available on Wikipedia.

A summary of the record structure is:

STypeByte CountAddressDataChecksum
Record start - “S”Record Type - “0” to “9”Byte Count - two hex bytes indicating the number of bytes that follow (address + data + checksum)Address - four, six or eight big-endian hex bytesData - 2n hex digitsChecksum - two hex digits

Calculating the checksum

This is also a simplified version of the full Wikipedia example:

  1. Sum all of the bytes in the Byte Count, Address, and Data fields.
  2. Discard the most significant byte.
  3. Subtract the least significant byte of the sum from 255.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import Data.Bits
import Data.Char
import Data.List.Split

-- Give Int the name Byte, i.e. typedef or #define in c
type Byte = Int

-- Take a String containing the byte count, address and data fields and calculate the srec checksum
checksum' :: String -> Byte
checksum' bytes = 0xFF - (sumBytes bytes .&. 0xFF)

-- 1. Split the string into chunks of 2
-- 2. Parse each hex byte from a String to a Byte
-- 3. Sum the results
sumBytes :: String -> Byte
sumBytes = sum . map parseByte . chunksOf 2

-- Note: parseByte of an empty list returns -1, this is a *bit* of a hack to ensure that
-- the checksum comparision will fail when given an empty string ("") and a string with an
-- odd number of digits ("FFF").
parseByte :: String -> Byte
parseByte [msb, lsb] = digitToInt msb * 16 + digitToInt lsb
parseByte []         = -1
parseByte _          = 0

splitChecksum :: String -> (String, String)
splitChecksum raw = splitAt count raw
  where
    -- The 'Byte Count field is the first to char of the S-record (take 2)
    -- count is the number of two hex digits (2 *).
    count = 2 * sumBytes (take 2 raw)

compareChecksum :: (String, String) -> Bool
compareChecksum (bytes, checksum) = parseByte checksum == checksum' bytes

-- Drop 2 since the type of Srec is not relevant
validateChecksum :: String -> Bool
validateChecksum = compareChecksum . splitChecksum . drop 2

prettyValid :: Bool -> String
prettyValid True = "valid"
prettyValid False = "invalid"

testChecksum :: IO ()
testChecksum = 
  let
    input = "S00F000068656C6C6F202020202000003C"
    valid = validateChecksum input
  in
    putStrLn ("Checksum is: " ++ prettyValid valid)

This post is licensed under CC BY 4.0 by the author.