yoshiyuki's blog

Arduino/Teensy/Raspberry pi pico を利用した I2C, SPI 通信アプリを紹介します

Arduino で I2C / I2C コマンドの使用例と解説

この記事では自作アプリの使い方を説明しています。自作アプリに関しては以下の記事をご参照ください。
ysin1128.hatenablog.com

Windows アプリの「CMD_Multi」タブ、または、Android アプリで I2C 通信を行った場合の例を元に、その処理を少し説明します。

接続

I2C 通信を行う場合、Arduino の A4 を Slaveデバイスの SDA に、A5 を SCL に、GND を GND に接続します。また、Arduino では SDA/SCL を Pull-up していないので Slave デバイス側に SDA/SCL の Pull-up が必要です。

f:id:ysin1128:20200815234324p:plain

ここでの動作例では、Slave デバイスとして Slave デバイス用の Sketch を書き込んだ Arduino を使用します。

f:id:ysin1128:20200813161553p:plain
f:id:ysin1128:20200813161259j:plain

Master/Slave 間では A4 (SDA), A5 (SCL), GND に加えて 5V も接続していますが、この 5V は Slave側の Arduino への電源供給用であり、 I2C 通信そのものに必要なものではありません。また、SDA/SCL の Pull-up に関しては Slave 側の Arduino の内蔵 Pull-up を有効にしています。

ちなみに、Slave デバイス用の Arduino に関しては以下のリンクで紹介しています。

ysin1128.hatenablog.com

Read 実行例

PC/Android との接続ができたらアプリの起動、スタートを完了させ、さっそくコマンドの実行です。

ここでは下記の三つのコマンドを続けて実行してみます。

  1. Slave Address = 0x24 の Register Address = 0x00 - 0x20 の値を読み出す
  2. Slave Address = 0x24 の Register Address = 0x02 の値を読み出す
  3. Slave Address = 0x24 の Register Address = 0x08 - 0x0B の値を読み出す

下図は I2C Read の実行例です。「Command Input」に3つのコマンドを記述し「Start」をクリックすることで「Output」に結果が出力されます。ちなみに「Command Input」には改行によって複数のコマンドを記述することが可能で、それらコマンドは「Start」クリック時に連続で実行されます。この例では3個のコマンドを連続で実行しています。
f:id:ysin1128:20200813163438p:plain

コマンドの概要

1個目のコマンド
r 24 00 20

7-bit Slave Address = 0x24 のデバイスの Register Address = 0x00 から順に 0x20 = 32-byte 分の値を読み出します。以下はその実行結果です。

> r 24 00 20
00 10 02 30 04 50 06 70 08 90 0A B0 0C D0 0E F0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

0x00 から 0xF0 までの 16-byte は Slave デバイスから読み出されたレジスタ値となります。 0xF0 から先は値が全て 0xFF となっていますが、これは Slave デバイスが 0x00 から 0x0F までの 16-byte のレジスタしか持っていないのに対してコマンドが 0x20 = 32-byte 分の値を要求したため、17-byte目以降は値が返ってこなかったことを意味しています。

2個目のコマンド
r 24 02 01

7-bit Slave Address = 0x24 のデバイスの Register Address = 0x02 から 1-byte 分の値を読み出します。以下はその実行結果です。

> r 24 02 01
02
3個目のコマンド
r 24 08 04

7-bit Slave Address = 0x24 のデバイスの Register Address = 0x08 から 4-byte 分の値を読み出します。以下はその実行結果です。

> r 24 08 04
08 90 0A B0


1個目のコマンドで Register Address = 0x00 から 0x0F までの値を読み出しているので、その一部だけを読み出す 2個目、3個目のコマンドにおいて指定した Register Address の値がちゃんと読み出せていることが値の比較により確認できます。
f:id:ysin1128:20200813175356p:plain

コマンドの詳細

1個目のコマンドを参照しながら、その詳細を説明します。

r 24 00 20

r はこのアプリ専用の Command word で、 I2C Read を行うことを示しています。

24 は読み出す相手の Slave Address (Hex表記) ですが、ここでは 7-bit Slave Address を使用することに注意してください。この場合、実際に通信で用いられる Slave Address は下記のようになります。

B7 B6 B5 B4 B3 B2 B1 B0
0 1 0 0 1 0 0 RW

Master は RW の値を 1 または 0 にすることで Read/Write のどちらを行うかを Slave に伝えます。7-bit Slave Address は、このうち Read/Write によって値が変わる B0 を除いた残りの 7-bit を指します。B0 を除く際に桁を 1-bit 分シフトさせるため値は 010_0100 = 0x24 となります。ちなみに、デバイスによっては Slave Address を 8-bit で表記する場合もあり、その場合は 0100_1000 = 0x48 (W), 0100_1001 = 0x49 (R) のように Read/Write の場合の 8-bit の値が並行してデータシートに記載されていると思われます。
RW の値は Command word を元にアプリが勝手に処理をするので、ユーザーが意識する必要はありません。

00 は読み出したいレジスタの Register Address (Hex表記) を指定しています。ただし、Register Address の指定が必要かどうかは通信相手のデバイス次第となりますのでそちらのデータシートをご確認ください。デバイスによっては Register Address が不要な場合があり、その場合は r 24 20 というように Register Address の記述を省くことで対応します。また、逆に Register Address が 2-byte となっている場合がありますが、その場合は r 24 00 00 20 と、Register Address の記述を 2-byte に増やすことで対応できます。

20 は読み出したい値の数 (Byte数 (Hex表記)) となります。

さらに詳しい動作

r 24 00 20

この1個目のコマンドに対してアプリが実際に行う処理は以下の通りです。
f:id:ysin1128:20200813223127p:plain

Read を行う前に Write コマンドで読み出したいレジスタの Register Address を書き込んでいます。これは Register Address を指定する一般的な方法なのでほとんどのデバイスに対応すると思います。なお、コマンドが 0x20 = 32-byte の読出しを要求するため図では 「Register[0x1F] Data」が送信されたように記載していますが、前述の通り実験に使用した Slave デバイスは 16-byte しかレジスタを持たないため、Register[0x10] 以降の Data の読出しに対しては応答していません。

r 24 20

Register Address を指定しない場合、アプリが実際に行う処理は以下の通りです。
f:id:ysin1128:20200813225600p:plain

事前の Register Address の書き込みが必要無いので、いきなり Read を行います。

r 24 01 02 20

Register Address が 2-byte (上記の例では 0x0102) の場合にアプリが実際に行う処理は以下の通りです。
f:id:ysin1128:20200813225757p:plain

単純に、Read前に書き込む Register Address の Byte数が増えます。

Write 実行例

続いて I2C Write の実行例を紹介します。ここでは下記のコマンドを実行してみます。

  1. Slave Address = 0x24 の Register Address = 0x00 - 0x0F の値を読み出す
  2. Slave Address = 0x24 の Register Address = 0x00 に 0x04 を書き込む
  3. Slave Address = 0x24 の Register Address = 0x00 - 0x0F の値を読み出す

f:id:ysin1128:20200813230104p:plain

コマンドの概要

w 24 00 04

7-bit Slave Address = 0x24 のデバイスの Register Address = 0x00 に値 0x04 を書き込みます。成功した場合は実行結果に Succeeded が出力されます。

f:id:ysin1128:20200824103955p:plain

1個目と3個目のコマンドは、Writeコマンド実行前後の比較のために全レジスタの値を読み出しているだけです。Writeコマンドにより Register Address = 0x00 の値だけが変更されていることが確認できます。

コマンドの詳細

w 24 00 04

w はこのアプリ専用の Command word で、 I2C Write を行うことを示しています。

24 は書き込む相手の Slave Address (Hex表記) ですが、ここでは 7-bit Slave Address を使用することに注意してください。

00 は書き込みを行うレジスタの Register Address (Hex表記) を指定しています。ただし、Register Address の指定が必要かどうかは通信相手のデバイス次第となりますのでそちらのデータシートをご確認ください。デバイスによっては Register Address が不要な場合があり、その場合は w 24 04 というように Register Address の記述を省くことで対応します。また、逆に Register Address が 2-byte となっている場合がありますが、その場合は w 24 00 00 04 と、Register Address の記述を 2-byte に増やすことで対応できます。

04 は書き込む値 (Hex表記) となります。

連続書き込み (Burst mode)

下図の例のように連続した複数のレジスタに対する書き込み (Burst mode) にも対応しています。
f:id:ysin1128:20200813231250p:plain
f:id:ysin1128:20200824104038p:plain

さらに詳しい動作

w 24 05 51 52 53 54

このコマンドに対してアプリが実際に行う処理は以下の通りです。
f:id:ysin1128:20200813231822p:plain

実は w コマンドは与えられた値を順番に Slave に送る (書き込む) だけで、特にこの値が Register Address だレジスタに書き込む値だと区別しているわけではありません。それら区別は Slaveデバイスが行います。
Slave デバイスは送られてきた値を自分のルールで処理するだけなので、Master 側は Slave デバイスのルールに合わせて値を送る必要があります。今回の実験で使用した Slave デバイスは 1個目の値が Register Address の指定、2個目以降を書き込む値として処理するので、この記事ではそのルールに合わせた例を紹介しています。ここで、もし、Slave デバイスが最初の2個の値を 2-byte の Register Address の指定として処理するものだった場合、上記のコマンドにより受け取った値を Register Address = 0x0551 を指定と認識します。「Master は Register Address として 0x05 だけを送ってきて 2-byte目の値を忘れている。だから Register Address は 0x0005 だ」などと解釈したりはしません。
一般的には 1個目の値を 1-byte Register Address、2個目以降を書き込む値と認識するデバイスが多いのですが、 受け取った値を Slave デバイスがどのようなルールで処理するかはデバイス次第となりますので、仕様書等でご確認ください。