エンディアンに関するメモ

出典: Wikimura

メモリ上では1バイトごとにアドレスが割り振られているため、 マルチバイトデータをどのように格納するかは何通りかあります。これをバイトオーダー(byte order)というそうです。

中でもビッグエンディアンとリトルエンディアンという言葉をよく聞くのですが...よく混乱します。 他は知らなくても全く問題ないそうです。

SPIでSDカードを動かすにあたって、マルチバイトデータをどう扱ったら良いのか分からなくなったので、調べたことをメモします。


目次

バイト・ハーフワード・ワード・ビット番号

ここでは、バイトは8ビット、ハーフワードは16ビット、ワードは32ビットとします。

二進数で表すときは左側をMSB、右側をLSBにして書きます。これは、十進数で数字を書くとき右側を一の位にするのと同じです。

ビット番号は、LSB側からMSB側に向かって0,1,...と付けていきます。これは二進数で表した数字を十進数に直す際の重みに対応しているからです。

ビット番号 31(MSB) 30 29 \cdots 2 1 0(LSB)
重み 231 230 229 \cdots 22 21 20

x = \sum_{i=0}^{31}{2^i b_i}

ビット番号を指定するときはよくdata[15:8]という書き方がされます。 これはdataの15番ビットから8番ビットまでという意味です。 この書き方は、暗黙のうちに「右側をLSBとした書き方」をしていることを表しています。 VHDLでいうところの「downto」に相当します。

エンディアン

リトルエンディアン

色々な場所で色々な説明のされ方がありますが...リトルエンディアンメモリシステムではマルチバイトデータをLSB側を含むバイトから格納します。

ワードで格納されたデータの場合、アドレスが若い方からLSB側(word[7:0])から順番に入ります。

オフセット ワードアクセス ハーフワードアクセス バイトアクセス
+0 word[7:0] hword(0)[7:0] byte(0)[7:0]
+1 word[15:8] hword(0)[15:8] byte(1)[7:0]
+2 word[23:16] hword(1)[7:0] byte(2)[7:0]
+3 word[31:24] hword(1)[15:8] byte(3)[7:0]

ビッグエンディアン

ビッグエンディアンメモリシステムではマルチバイトデータをMSB側を含むバイトから格納します。

ワードで格納されたデータの場合、アドレスが若い方からMSB側(word[31:24)から順番に入ります。

オフセット ワードアクセス ハーフワードアクセス バイトアクセス
+0 word[31:24] hword(0)[15:8] byte(0)[7:0]
+1 word[23:16] hword(0)[7:0] byte(1)[7:0]
+2 word[15:8] hword(1)[15:8] byte(2)[7:0]
+3 word[7:0] hword(1)[7:0] byte(3)[7:0]

何が困るって...

異なるプロセッサ間でデータのやり取りを行う際に、エンディアンが問題になることがあります。

特にマイコンでは、USARTやSPIでバイト単位でデータを送ることがよくあります。 DMAを使って、アドレスの自動インクリメントをしながら連続的にデータを送る場合、送信データはバッファに連続的に蓄えられているはずです。

テキストデータなら問題が起きないのですが...マルチバイトをバイナリで書くとき悲惨なことが起きたりします。


データが壊れるとき

エンディアンの異なるプロセッサで1バイトごとにデータをやり取りすることを考えます。 ここでは、プロセッサAはリトルエンディアン、プロセッサBはビッグエンディアンとします。


プロセッサAからBへ、ワードをバイト単位で送信すると、以下のような処理が行われると思います。

  • プロセッサAでワードをバイト単位で送信バッファに書き込む(表左列)
  • プロセッサAは、アドレスをインクリメントしながらバイト単位で送信する(表中央列)
  • プロセッサBは、アドレスをインクリメントしながら受信データを格納する(表右列)

1バイト文字なら問題なく送られると思います。片方がインクリメントしながら送っているのに、もう片方がデクリメントしたら、文字列が逆さになってしまいます。

ところが、ワードだと以下のようなことが起きてしまいます。 どちらのプロセッサから見ても、iバイト目はiバイト目として受け取られていますが... 同じバイトの意味が両者で異なっていることが分かります。

一番重みの軽いはずだったバイトは、一番重いバイトとして受け取られています。 これではデータが壊れてしまいます。

オフセット プロセッサA 転送バイト プロセッサB
+0 word[7:0] byte(0)[7:0] word[31:24]
+1 word[15:8] byte(1)[7:0] word[23:16]
+2 word[23:16] byte(2)[7:0] word[15:8]
+3 word[31:24] byte(3)[7:0] word[7:0]


壊さないためには

通信やストレージなどではエンディアンがきちんと定められているはずです。 どっちのバイトから送るかをきちんと調べるしかありません。

SPIなどのシリアル通信では、どっちのビットから送るかも指定されているはずです。

従って、

  • マルチバイトデータをどっちのバイトから送るか(MSB側かLSB側か)
  • バイトをどっちのビットから送るか

以上の二点に気を付ければ良いと思います。


具体例

名称 エンディアン 備考
Cortex-M3コア Little/Big コア単体としてはどちらも対応可。
ペリフェラルで決まる。
ARMは全体的にリトルエンディアンらしい。
STM32マイコン Little 変更はできないようです。
FAT ファイルシステム Little 文献[2][3]参照


参考文献

  1. エンディアン
  2. FAT(File Allocation Table)
  3. FAT12ファイルシステムのFATテーブル
個人用ツール