Chapter 06
Intel HEX と Motorola SREC — アドレス付きテキスト形式
この章のゴール.
Intel HEX と S-record の行の構造を手で読めるようになり、 アドレスとデータの対応、チェックサムの検算、
.binとの相互変換ができるようになること。 「どのアドレスに何が書かれるか」を取り違えないこと。この章で使う既出の用語(定義は各リンク先). メモリマップ(01 章 3 節)、オフセット(03 章 1 節)、チェックサム(03 章 8 節)、ブートローダ(04 章 5 節)
1. なぜテキスト形式があるのか
.bin は「アドレス 0 から順にバイトを並べただけ」なので、飛び飛びの領域(ブートローダは 0x08000000、設定は 0x08078000、その間は空き)を表せない。空きを 0xFF で埋めれば巨大になる。
Intel HEX と Motorola S-record(SREC) は、この問題を解くアドレス付きのテキスト形式である。 「このアドレスに、この数バイトを書け」というレコードの列で、飛び地を素直に表せる。書き込みツール・ブートローダ・製造装置が広く扱う。 中身は ASCII なので、テキストエディタでも開ける。
2. Intel HEX の 1 行
各行は : で始まり、すべて 16 進の文字である。
:10 8000 00 0004002 0C581000 8... CC
│ │ │ └ データ(Byte count 個のバイト) └ チェックサム
│ │ └ レコード種別(00=データ)
│ └ アドレス(下位 16 ビット)
└ Byte count(このレコードのデータ長)実例:
:108000000004002 0C58100088180008838000 08 7Aを分解すると:
| 部分 | 値 | 意味 |
|---|---|---|
: | — | 行の始まり |
10 | 16 | データが 16 バイト |
8000 | 0x8000 | 書き込み先アドレス(下位 16 ビット) |
00 | — | 種別 = データレコード |
0004002 0C5810008... | — | 16 バイトのデータ |
| 末尾 2 桁 | 7A | チェックサム |
レコード種別が重要である。
| 種別 | 名前 | 役割 |
|---|---|---|
00 | Data | データ本体 |
01 | End Of File | ファイルの終わり(:00000001FF) |
02 | Extended Segment Address | 以降のアドレス上位(×16)。古い |
04 | Extended Linear Address | 以降のアドレスの上位 16 ビット。これで 32 ビット空間を表す |
05 | Start Linear Address | 実行開始アドレス(エントリポイント) |
アドレスの落とし穴: データレコードのアドレス欄は下位 16 ビットだけである。 上位は直前の 04 レコードで決まる。:020000040800F2 は「以降の上位 16 ビットは 0x0800」の意味で、続く :10 8000 ... は実アドレス 0x08008000 を指す。 04 レコードを見落とすと、アドレスを 0x8000 と誤読する。
3. チェックサムの検算
Intel HEX のチェックサムは、: を除く全バイトの和の下位 8 ビットの 2 の補数(和 + チェックサム = 0 mod 256)。
line = "108000000004002 0C58100088180008838000087A".replace(" ","")
b = bytes.fromhex(line)
assert (sum(b) & 0xFF) == 0 # データ + チェックサムを全部足すと 0手で確かめるより、変換ツールが検算してくれる。壊れた行を探すときだけ手計算する。
4. Motorola S-record(SREC)
同じ発想の別形式。各行は S + 種別数字で始まる。
S3 15 08008000 0004002 0C5810008... CC
│ │ │ │ └ チェックサム
│ │ │ └ データ
│ │ └ アドレス(種別で幅が変わる)
│ └ Byte count(アドレス+データ+チェックサムのバイト数)
└ 種別| 種別 | アドレス幅 | 用途 |
|---|---|---|
S0 | — | ヘッダ(ファイル名などのコメント) |
S1 | 16 ビット | データ(小さいアドレス空間) |
S2 | 24 ビット | データ |
S3 | 32 ビット | データ(Cortex-M で普通) |
S5 / S6 | — | データレコードの個数 |
S7 / S8 / S9 | 32/24/16 | 実行開始アドレス(終端) |
S3 はアドレスがフル 32 ビットなので、Intel HEX のような「上位を別レコードで持つ」仕組みが要らず、1 行で実アドレスが読める。 チェックサムは「Byte count 以降の全バイトの和の 1 の補数(反転)」。
5. 変換 — srec_cat と objcopy
srecord パッケージの srec_cat が最強の変換・加工ツールである。
# HEX → BIN(空き region は指定した値で埋める)
srec_cat fw.hex -intel -o fw.bin -binary
# BIN → HEX(この BIN が置かれる先頭アドレスを指定するのが肝心)
srec_cat fw.bin -binary -offset 0x08000000 -o fw.hex -intel
# HEX → SREC
srec_cat fw.hex -intel -o fw.srec -motorola
# 情報表示(どのアドレス範囲が埋まっているか)
srec_info fw.hex -intelsrec_info の出力("Data: 08000000 - 0807FFFF" のような範囲一覧)は、メモリマップ作り(01 章)にそのまま使える。
objcopy でも変換できる。
arm-none-eabi-objcopy -I ihex -O binary fw.hex fw.bin
arm-none-eabi-objcopy -I binary -O ihex --change-addresses 0x08000000 fw.bin fw.hexBIN → HEX で最も多い間違いは、オフセット(この BIN が焼かれる先頭アドレス)を指定し忘れて、アドレス 0 のイメージができてしまうことである。必ず -offset / --change-addresses を付ける。
6. Python で読む
from intelhex import IntelHex
ih = IntelHex("fw.hex")
print(hex(ih.minaddr()), hex(ih.maxaddr())) # 埋まっている範囲
print(ih.segments()) # 飛び地の一覧 [(start,end), ...]
data = ih.tobinarray(start=0x08008000, size=16) # 特定アドレスのバイトを取り出す
ih.tobinfile("fw.bin") # BIN に落とす(空きは 0xFF 相当で詰まる)bincopy ライブラリは HEX / SREC / TI-TXT を統一的に扱え、bincopy info fw.hex で範囲を表示できる。
7. UF2 — もう 1 つの配布形式
Raspberry Pi Pico や Adafruit のボードで使う UF2(USB Flashing Format)は、512 バイト固定のブロックの列で、各ブロックが書き込み先アドレスとデータを持つ点は HEX / SREC と同じ発想である。 マジックは 0x0A324655(UF2\n)で始まる。uf2conv.py で .bin と相互変換できる。08 章で触れる。
8. 手を動かす
HEX の 1 行を解剖する
この章のポイント
- HEX / SREC は飛び地を表せるアドレス付きテキスト。書き込み・製造・ブートローダで使う
- Intel HEX のデータ行のアドレスは下位 16 ビットだけ。上位は
04レコードで決まる。見落とすとアドレスを誤る - SREC の S3 はフル 32 ビットアドレスを 1 行に持つ
- 変換は
srec_cat/objcopy。BIN → HEX ではオフセット(焼く先頭アドレス)を必ず指定 srec_infoの範囲一覧はメモリマップにそのまま使える