IchigoJam BASIC には、I2C通信を行う関数 I2CR
および I2CW
がある。
これらの関数は様々な引数の数で使用でき、引数の数によって挙動が変わるため、混乱しやすい。
そこで、今回は、これらの関数の使い方をまとめ、実際の動作例とともに示した。
今回は、IchigoJam BASIC 1.4.3 を用いて検証を行った。
他のバージョンでは、処理時間や挙動が異なったり、一部のモード (引数の数) が利用できなかったりする可能性がある。
※IchigoJamはjig.jpの登録商標です。
共通事項
この記事で用いる用語
- デバイスアドレス:通信相手のI2Cデバイスの7ビットアドレス
- 受信先アドレス:通信相手から受信したデータを書き込むIchigoJamの仮想アドレス
- 受信長さ:通信相手から受信するデータのバイト数
- 送信元アドレス:通信相手へ送信するデータが格納されているIchigoJamの仮想アドレス
- 送信長さ:通信相手へ送信するデータのバイト数
- 送信バイト:通信相手へ送信する値 (1バイト)
関数の返り値
I2CR
関数および I2CW
関数は、成功した場合は 0
、失敗した場合は 1
を返す。
たとえば、通信相手のデバイス (指定したアドレスに対して応答を返すデバイス) がバスに接続されていないとき、これらの関数は失敗する。
リセット後最初の使用
リセット後最初に I2CR
または I2CW
を使用すると、I2C通信を行う前に、SCLにクロックが10回送信される。
これは、I2Cの通信状態のリセットを試み、I2Cデバイスが正常に動作する確率を上げることを狙っていると考えられる。
例として、I2CW の2引数の形式のサンプルプログラム
10 OUT1,1:?I2CW(#50,#42):OUT1,0
をリセット後最初に実行した結果は、以下のようになった。
クロックの出力に、約0.5msかかっている。
OUT1に 1
が出力されている時間は、約1.8msであった。
クロックの出力後に、通常のI2C通信が行われている。
今回の通信相手
今回は、EEPROM S-24CM01C を通信相手として用いている。
アドレス設定は A1=0, A2=0
としており、7ビットアドレスは #50
(ページ0にアクセスする場合) および #51
(ページ1にアクセスする場合) である。
サンプルプログラム
今回のサンプルプログラムでは、I2C通信を行う前にOUT1ピンに 1
を出力し、I2C通信を行った後にOUT1ピンに 0
を出力することで、I2C通信の処理を行っている時間の目安がわかるようにした。
さらに、サンプルプログラムの出力に加え、I2C通信およびOUT1ピンの様子をPulseViewを用いてロジックアナライザで調べ、掲載した。
サンプルレートは4MHzに設定した。
今回のサンプルプログラムでは、#114A~#117F のI2Cバッファを使用している。
他のバージョン (IchigoCake BASIC を含む) ではアドレスが異なることがあるので、注意すること。
メモリマップ - イチゴジャム レシピ
データ取得 (I2CR)
3引数の形式
I2CR(デバイスアドレス, 受信先アドレス, 受信長さ)
指定したデバイスから、指定した長さのデータを受信する。
正常動作の例
10 OUT1,1:?I2CR(#50,#1150,4):OUT1,0
20 FOR I=#1150 TO #1153:?" ";HEX$(PEEK(I));:NEXT:?""
0
DE AD BE EF
OUT1 に 1
が出力されている時間は、約2.0msであった。
アドレスが出力され、その後データ DE AD BE EF
を受信している。
接続されていないアドレスを指定した例
10 OUT1,1:?I2CR(#52,#1150,4):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、リセットや次のI2C操作を行うまで、アドレスの送信が無限に繰り返される。
(少なくとも、10秒以上送信が繰り返された)
OUT1 に 1
が出力されている時間は、約2.0msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
4引数の形式
I2CR(デバイスアドレス, 送信バイト, 受信先アドレス, 受信長さ)
以下を行う。
- 指定したデバイスに1バイト送信する (送信する値を直接引数で指定する)
- ストップコンディションを送信する
- 指定したデバイスから指定した長さのデータを受信し、指定の仮想アドレスに格納する
正常動作の例
10 OUT1,1:?I2CR(#50,#42,#1150,4):OUT1,0
20 FOR I=#1150 TO #1153:?" ";HEX$(PEEK(I));:NEXT:?""
0
DE AD BE EF
OUT1 に 1
が出力されている時間は、約2.4msであった。
引数で指定した1バイトを出力し、Repeated Start を出力しているものの、その後ストップコンディションを出力してからデバイスからのデータの受信を行っていることがわかる。
接続されていないアドレスを指定した例
10 OUT1,1:?I2CR(#52,#42,#1150,4):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、アドレスの送信が301回 (約10ms間) 繰り返される。
OUT1 に 1
が出力されている時間は、約14.0msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
5引数の形式
I2CR(デバイスアドレス, 送信元アドレス, 送信長さ, 受信先アドレス, 受信長さ)
以下を行う。
- 指定したデバイスに指定の仮想アドレスから指定した長さのデータを送信する
- ストップコンディションを送信する
- 指定したデバイスから指定した長さのデータを受信し、指定した仮想アドレスに格納する
正常動作の例
10 POKE#1150,#13,#37
20 OUT1,1:?I2CR(#50,#1150,2,#1154,4):OUT1,0
30 FOR I=#1154 TO #1157:?" ";HEX$(PEEK(I));:NEXT:?""
0
DE AD BE EF
OUT1 に 1
が出力されている時間は、約2.7msであった。
引数で指定したバイト列を出力し、Repeated Start を出力しているものの、その後ストップコンディションを出力してからデバイスからのデータの受信を行っていることがわかる。
接続されていないアドレスを指定した例
10 POKE#1150,#13,#37
20 OUT1,1:?I2CR(#52,#1150,2,#1154,4):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、アドレスの送信が301回 (約10ms間) 繰り返される。
OUT1 に 1
が出力されている時間は、約14.3msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
「送信長さ」に 0
を指定し、データの送信を行わない場合は、繋がっていないデバイスアドレスを指定すると、「3引数の形式」と同様にアドレスの送信が無限に繰り返されるようである。
データ送信 (I2CW)
2引数の形式
I2CW(デバイスアドレス, 送信バイト)
指定したデバイスに1バイト送信する。
送信する値は、引数で直接指定する。
正常動作の例
10 OUT1,1:?I2CW(#50,#42):OUT1,0
0
OUT1 に 1
が出力されている時間は、約1.6msであった。
アドレスを出力し、続いてデータを出力している。
接続されていないアドレスを指定した例
10 OUT1,1:?I2CW(#52,#42):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、アドレスの送信が301回 (約10ms間) 繰り返される。
OUT1 に 1
が出力されている時間は、約13.4msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
3引数の形式
I2CW(デバイスアドレス, 送信元アドレス, 送信長さ)
指定したデバイスに指定の仮想アドレスから指定した長さのデータを送信する。
正常動作の例
10 POKE#1150,#13,#37
20 OUT1,1:?I2CW(#50,#1150,2):OUT1,0
0
OUT1 に 1
が出力されている時間は、約1.9msであった。
アドレスを出力し、続いてデータを出力している。
接続されていないアドレスを指定した例
10 POKE#1150,#13,#37
20 OUT1,1:?I2CW(#52,#1150,2):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、アドレスの送信が301回 (約10ms間) 繰り返される。
OUT1 に 1
が出力されている時間は、約13.7msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
4引数の形式
I2CW(デバイスアドレス, 送信バイト, 送信元アドレス, 送信長さ)
指定したデバイスに、まず引数で直接指定した1バイトを送信する。
続いて、指定の仮想アドレスから指定した長さのデータを送信する。
正常動作の例
10 POKE#1150,#13,#37
20 OUT1,1:?I2CW(#50,#42,#1150,2):OUT1,0
0
OUT1 に 1
が出力されている時間は、約2.2msであった。
アドレスに続いて引数で指定した値を出力し、続いて引数で指定したメモリ上の値を出力している。
2種類の値の出力の間に、スタートコンディションなどは挟まれていない。
接続されていないアドレスを指定した例
10 POKE#1150,#13,#37
20 OUT1,1:?I2CW(#52,#42,#1150,2):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、アドレスの送信が301回 (約10ms間) 繰り返される。
OUT1 に 1
が出力されている時間は、約14.0msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
5引数の形式
I2CW(デバイスアドレス, 送信元アドレス1, 送信長さ1, 送信元アドレス2, 送信長さ2)
指定したデバイスに、指定の仮想アドレスから指定した長さのデータを送信する。
2組の指定を受け取り、最初の指定・次の指定の順に続いて送信する。
正常動作の例
10 POKE#1150,#13,#37
20 POKE#1158,#DE,#AD,#BE,#EF
30 OUT1,1:?I2CW(#50,#1150,2,#1158,4):OUT1,0
0
OUT1 に 1
が出力されている時間は、約4.7msであった。
アドレス・第2引数と第3引数で指定したデータ・第4引数と第5引数で指定したデータを順に出力している。
2種類の値の出力の間に、スタートコンディションなどは挟まれていない。
接続されていないアドレスを指定した例
10 POKE#1150,#13,#37
20 POKE#1158,#DE,#AD,#BE,#EF
30 OUT1,1:?I2CW(#52,#1150,2,#1158,4):OUT1,0
1
繋がっていないデバイスアドレスを指定すると、アドレスの送信が301回 (約10ms間) 繰り返される。
OUT1 に 1
が出力されている時間は、約14.0msであった。
アドレスを送信し、ACKが返ってこず、ストップコンディションとスタートコンディションを送信して再びアドレスを送信する動作を繰り返していることがわかる。
まとめ
I2CR (データ取得)
I2CR(デバイスアドレス, 受信先アドレス, 受信長さ)
I2CR(デバイスアドレス, 送信バイト, 受信先アドレス, 受信長さ)
I2CR(デバイスアドレス, 送信元アドレス, 送信長さ, 受信先アドレス, 受信長さ)
送信を行わない形式 (3引数、および5引数で送信長さを 0 にした場合) では、指定したアドレスのデバイスがバスに接続されていない場合、リセットや次のI2C操作を行うまでアドレスを無限に送信し続ける。
送信を行う形式では、送信を行った後、ストップコンディションを送信してから受信を行う。
指定したアドレスのデバイスがバスに接続されていない場合、約10ms間アドレスの送信を行う。
I2CW (データ送信)
I2CW(デバイスアドレス, 送信バイト)
I2CW(デバイスアドレス, 送信元アドレス, 送信長さ)
I2CW(デバイスアドレス, 送信バイト, 送信元アドレス, 送信長さ)
I2CW(デバイスアドレス, 送信元アドレス1, 送信長さ1, 送信元アドレス2, 送信長さ2)
送信するデータを2個に分けて指定する形式では、2個のデータの間にスタートコンディションなどは挟まず、続けて送信を行う。
すべての形式において、指定したアドレスのデバイスがバスに接続されていない場合、約10ms間アドレスの送信を行う。
付録:解析のテクニック
I2C通信を修正して解析し、結果を合成する
PulseView のプロトコルアナライザによるI2Cの解析では、データの途中でストップコンディションやスタートコンディションが発生するケースに対応できず、解析結果がズレてしまうことがある。
この例では、90μs~95μsの場所にあるストップコンディションおよびスタートコンディションが無視され、その後の解析結果が不適切なものになってしまっている。
そこで、以下の方法で、データを一部手動で修正 (改変) することで後半部分の正しい解析結果を得て、それをもともとのデータの前半部分と画像合成することで正しい解析結果を得ることにした。
まず、修正を行いたい通信データを Save Selected Range As で保存後読み込み、Zoom to Fit で画面いっぱいに表示する。
次に、Export Comma-separated values を選択し、Time column を有効にしてデータを保存する。
すると、以下のような形式のデータが保存されるはずである。
nanoseconds,logic,logic,logic
0,1,1,1
250,1,1,1
500,1,1,1
750,1,1,1
1000,1,1,1
1250,1,1,1
1500,1,1,1
1750,1,1,1
2000,1,1,1
最初の列が時刻なので、PulseView上の表示と比較し、Repeated Start の場所、すなわち SCL が 1
の状態で SDA が 1
から 0
に変化している場所を探す。
見つかったら、これを Repeated Start でなくする。
すなわち、SDA が変化するタイミングを SCL が 0
の場所に移動する。
今回の場合は、以下のような修正を行った。
85000,0,1,1
- 85250,0,1,1
- 85500,1,1,1
- 85750,1,1,1
- 86000,1,1,1
- 86250,1,1,1
- 86500,1,1,1
- 86750,1,1,1
+ 85250,0,0,1
+ 85500,1,0,1
+ 85750,1,0,1
+ 86000,1,0,1
+ 86250,1,0,1
+ 86500,1,0,1
+ 86750,1,0,1
87000,1,0,1
修正したデータを、Import Comma-separated values で読み込む。
このとき、デフォルトの設定から以下の項目を以下の値に変更して読み込む。
項目 | 値 | 解説 |
---|---|---|
First column | 2 | 時刻の列を飛ばす |
Get channel names from first line | オフ | 先頭行は種類なので意味がない |
Number of logic channels | 3 | チャンネル数に合わせる |
Samplerate (Hz) | 4000000 | サンプルレートに合わせる |
Start line | 2 | 先頭行 (ヘッダ) を飛ばす |
最後に、チャンネル名とプロトコルアナライザを最初と同様に設定すれば、同じ条件 (表示位置) で修正したデータを表示できる。
これで、解析が不適切になる原因の Repeated Start が消え、後半部分の解析結果が正常なものになった。
あとは、スクリーンショットを撮り、適当な画像編集ソフトウェアで合成すればよい。
このとき、修正を行ったデータの後半の解析結果に加え、波形の一部が範囲に入るように選択してコピーすると、合成を行う際に位置を合わせやすい。
I2Cアドレスが送信された回数を効率よく数える
I2Cアドレスの送信が何回も繰り返されている際、1個ずつ手動で数えるのは大変である。
そこで、今回は以下の方法で数えた。
まず、PulseView のI2Cの解析結果が表示されている (薄い緑色の背景の) 部分を右クリックし、Export all annotations を選択する。
すると、以下のようなデータが保存できる。
6311-6322 I2C: Bits: 1
6322-6334 I2C: Bits: 0
6334-6346 I2C: Bits: 1
6346-6357 I2C: Bits: 0
6357-6369 I2C: Bits: 0
6369-6381 I2C: Bits: 1
6381-6393 I2C: Bits: 0
6393-6405 I2C: Bits: 0
(中略)
47535-47547 I2C: Bits: 0
47547-47559 I2C: Bits: 0
6311-6393 I2C: Address/data: Address write: 52
6295-6295 I2C: Address/data: Start
6393-6405 I2C: Address/data: Write
6405-6417 I2C: Address/data: NACK
6427-6427 I2C: Address/data: Stop
6433-6433 I2C: Address/data: Start
6448-6530 I2C: Address/data: Address write: 52
6530-6541 I2C: Address/data: Write
(中略)
47559-47571 I2C: Address/data: NACK
47580-47580 I2C: Address/data: Stop
これは、I2C通信の解析結果を表したテキストである。
このテキストに対し、サクラエディタで Address/data: Address write: 52
を適当な文字列に「すべて置換」すると、置換の回数が表示され、これがアドレス #52
が送信された回数である。