バイナリデータを「Motorola S-record」に変換する。

# はじめに
ROMライターでマイコンやフラッシュメモリへバイナリデータを書き込むフォーマット、**Motorola S-record**についてまとめる。また、Pythonでフォーマット変換するコードも書いてみた。

# 目次
+ [Motorola S-recordフォーマット](#motorola-s-recordフォーマット)
+ [Motorola S-record変換例](#motorola-s-record変換例)


## Motorola S-recordフォーマット
Motorolaによって開発されたフォーマットで拡張子は```.mot```。ASCIIコードの16進数のビックエンディアンで表現するテキストフォーマット。「S」から始まるS-Recordという構造で、「Type」によって、後に続くフォーマットが規定されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299802/815827ff-5381-7236-af6d-2dc2b98702fb.png)

パラメータ|内容
---|---
S|各行の先頭に付ける識別子。
Type:0|Option：ASCIIコードのNULL終端の文字列(16進数表記)。
Type:1|16bitアドレスのデータレコード。
Type:2|24bitアドレスのデータレコード。
Type:3|32bitアドレスのデータレコード。
Type:4|Reserved
Type:5|Option：データレコードのカウント(65,535 (0xFFFF)まで)。Addressに表記。Dataなし。
Type:6|Option：データレコードのカウント(16,777,215 (0xFFFFFF) まで)。Addressに表記。Dataなし。
Type:7|S3と対で使う。32bitの終端アドレス。Addressに表記。Dataなし。
Type:8|S2と対で使う。24bitの終端アドレス。Addressに表記。Dataなし。
Type:9|S1と対で使う。16bitの終端アドレス。Addressに表記。Dataなし。
Length|AddressとDataとCheckSumの桁数の和を2で割った数(1Byte=2桁)
Address|Type別に下記の通り。<br>S0 : 0000固定<br>S1 : 4桁:Dataを書き込む先頭アドレス　XXXX(16bit Address)<br>S2 : 6桁:Dataを書き込む先頭アドレス　XXXXXX(24bit Address)<br>S3 : 8桁:Dataを書き込む先頭アドレス　XXXXXXXX(32bit Address)<br>S9 : 4桁:終端アドレス　XXXX(16bit Address)<br>S8 : 6桁:終端アドレス　XXXXXX(24bit Address)<br>S7 : 8桁:終端アドレス　XXXXXXXX(32bit Address)<br>S5 : レコードの総数 0xFFFFまで<br>S6 : レコードの総数 0xFFFFFFまで
Data|書き込むデータ。2桁で 1Byte。S0の場合は書き込むデータではない。
CheckSum|LengthとAddressとDataの各バイトの合計の1の補数。<br>２文字（１バイト）データ。レコード長、アドレスフィールド、データの各バイト値の合計の１の補数。 計算式はPythonの変換コードの例を参照。

[戻る](#目次)

## Motorola S-record変換例
例として、データ列からS-Recordフォーマットのデータに変換するPythonコードを書いてみた。アドレスフォーマット(32bit,24bit,16bit)、開始アドレス、書き込みデータ列、1レコードのサイズを指定。関数名```mot_srecord()```。
```python:
data ='\
000102030405060708090A0B0C0D0E0F\
101112131415161718191A1B1C1D1E1F\
202122232425262728292A2B2C2D2E2F\
303132333435363738393A3B3C3D3E3F\
404142434445464748494A4B4C4D4E4F\
505152535455565758595A5B5C5D5E5F\
606162636465666768696A6B6C6D6E6F\
707172737475767778797A7B7C7D7E7F\
808182838485868788898A8B8C8D8E8F\
909192939495969798999A9B9C9D9E9F\
A0A1A2A3A4A5A6A7A8A9AAABACADAEAF\
B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF\
C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF\
D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF\
E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF\
F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF\
ABCDEF\
'

#------------------------------------------------------
# AddrType   : アドレスのビット数を指定。(16,24,32)
# StartAddr  : 開始アドレスを16進数数値で指定。
# Data       : 書き込むデータをHEX文字列で指定。
# SplitBytes : S-RecordのサイズをByte数で指定。
#------------------------------------------------------
def mot_srecord(AddrType, StartAddr, Data, SplitBytes):
	
	#S-Record1行分のデータ毎にリストに分割
	data_list = [Data[i:i+SplitBytes*2] for i in range(0,len(Data), SplitBytes*2)]
	
	# データレコード作成
	for cnt,data in enumerate(data_list):
		if(AddrType == 32):
			#StartAddrからoffset(cnt*SplitBytes)を足して8桁のHEX文字列にする。
			addr     = f'{(StartAddr+(cnt*SplitBytes)&0xFFFFFFFF):08X}'
			
			#addr桁とdata桁とchecksumの2桁を足して2で割って(byte数なので)2桁のHEX文字列にする。
			length   = f'{((len(addr)+len(data)+2)//2):02X}'
			
			#sum_dataの16進数文字列を2文字ずつ切り取り数値変換してsum()し、1の補数をとる。
			sum_data = length+addr+data
			checksum = f'{(0xFF&~sum([int(sum_data[i:i+2],16) for i in range(0,len(sum_data), 2)])):02X}'
			
			#ヘッダをつけてS-Recordフォーマットにする
			data_list[cnt]=f'S3{length}{addr}{data}{checksum}\n'
			
		elif(AddrType == 24):
			addr     = f'{(StartAddr+(cnt*SplitBytes)&0xFFFFFF):06X}'
			length   = f'{((len(addr)+len(data)+2)//2):02X}'
			sum_data = length+addr+data
			checksum = f'{(0xFF&~sum([int(sum_data[i:i+2],16) for i in range(0,len(sum_data), 2)])):02X}'
			data_list[cnt]=f'S2{length}{addr}{data}{checksum}\n'
			
		elif(AddrType == 16):
			addr     = f'{(StartAddr+(cnt*SplitBytes)&0xFFFF):04X}'
			length   = f'{((len(addr)+len(data)+2)//2):02X}'
			sum_data = length+addr+data
			checksum = f'{(0xFF&~sum([int(sum_data[i:i+2],16) for i in range(0,len(sum_data), 2)])):02X}'
			data_list[cnt]=f'S1{length}{addr}{data}{checksum}\n'
	
	return ''.join(data_list)


# data列を32bitアドレスの0x80000000を開始アドレスとして、16byte1レコードのS-Recordデータに変換。
print(mot_srecord(32,0x80000000,data,16))

# S31580000000000102030405060708090A0B0C0D0E0FF2
# S31580000010101112131415161718191A1B1C1D1E1FE2
# S31580000020202122232425262728292A2B2C2D2E2FD2
# S31580000030303132333435363738393A3B3C3D3E3FC2
# S31580000040404142434445464748494A4B4C4D4E4FB2
# S31580000050505152535455565758595A5B5C5D5E5FA2
# S31580000060606162636465666768696A6B6C6D6E6F92
# S31580000070707172737475767778797A7B7C7D7E7F82
# S31580000080808182838485868788898A8B8C8D8E8F72
# S31580000090909192939495969798999A9B9C9D9E9F62
# S315800000A0A0A1A2A3A4A5A6A7A8A9AAABACADAEAF52
# S315800000B0B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF42
# S315800000C0C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF32
# S315800000D0D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF22
# S315800000E0E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF12
# S315800000F0F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF02
# S30880000100ABCDEF0F

# data列を16bitアドレスの0x1000を開始アドレスとして、16byte1レコードのS-Recordデータに変換。
print(mot_srecord(16,0x1000,data,16))

# S1131000000102030405060708090A0B0C0D0E0F64
# S1131010101112131415161718191A1B1C1D1E1F54
# S1131020202122232425262728292A2B2C2D2E2F44
# S1131030303132333435363738393A3B3C3D3E3F34
# S1131040404142434445464748494A4B4C4D4E4F24
# S1131050505152535455565758595A5B5C5D5E5F14
# S1131060606162636465666768696A6B6C6D6E6F04
# S1131070707172737475767778797A7B7C7D7E7FF4
# S1131080808182838485868788898A8B8C8D8E8FE4
# S1131090909192939495969798999A9B9C9D9E9FD4
# S11310A0A0A1A2A3A4A5A6A7A8A9AAABACADAEAFC4
# S11310B0B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFB4
# S11310C0C0C1C2C3C4C5C6C7C8C9CACBCCCDCECFA4
# S11310D0D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF94
# S11310E0E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF84
# S11310F0F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF74
# S1061100ABCDEF81
```
[戻る](#目次)