Tips-I2C
ESP32-micropython でIDを探す
from machine import I2C,Pin import time i2c = I2C(scl=Pin(22), sda=Pin(21),freq=400000) while True: print(str(i2c.scan())) time.sleep(1)
I2C テスト
ESP32 <-> STM32F103 で通信させてみる
Master(ESP32) | Slave(STM32) | ||
---|---|---|---|
Vcc(NC) | Vcc(NC) | ||
WireSCL(IO22) | SCL1(PB6) | 3K3でPullup | |
WireSDA(IO21) | SDA1(PB7) | 3K3でPullup | |
GND | GND |
サンプル
Slave( STM32F103 )
#include <Wire_slave.h> void setup() { Serial.begin(115200); // start serial for output Wire.begin(0x10); // join i2c bus with address #16 Wire.onReceive(receiveEvent) ;// register event Wire.onRequest(requestEvent) ;// マスタからのデータ取得要求のコールバック関数登録 } void loop(){ delay(100); } void receiveEvent(int howMany){ Serial.println("recept data"); // while(1 < Wire.available()){ // loop through all but the last while(1 < Wire.available()){ // loop through all but the last char c = Wire.read(); // receive byte as a character // Serial.print(c); // print the character Serial.println(c); // print the character } int x = Wire.read(); // receive byte as an integer // char x = Wire.read(); Serial.println(x); // print the integer } // マスターからのリクエストに対するデータ送信 void requestEvent() { Serial.println("resept request."); Wire.write("Hello"); }
Mastor ( ESP32_micropython)
# ここにコードを書いてね :-) from machine import I2C,Pin # i2c = I2C(scl=Pin(22),sda=Pin(21),freq=400000) # ポートに依存して 400kHz の周波数でI2Cペリフェラルを # # 作成します。使用するペリフェラルやピンを選択するために # # 追加のパラメータが必要になる場合があります print(i2c.scan()) # スレーブをスキャンし、7ビットアドレスのリストを返します i2c.writeto(16, b'12345') # i2c.writeto(42, b'123') # 7ビットアドレス 42 のスレーブに3バイトを書き込みます i2c.writeto(16, b'AB') # i2c.readfrom(42, 4) # 7ビットアドレス 42 のスレーブから4バイトを読み 込みます ( a ) = i2c.readfrom(16, 4) print ( a ) #
送信概ね前の章で書いたことがI2Cの送受信のやり方だが,送信だけなら難しいことはほぼ何も無い.
スタートコンディションアドレス送信データ送信ストップコンディションでおしまいである.あとはアドレス送信とデータ送信の間にスレーブからACKが返ってくるので,それを待つことさえ忘れなければ良い.
受信問題は受信である.ここで変に勘付いて「どうせ送信と逆でしょ」と
スタートコンディションアドレス送信データ受信ストップコンディションという流れを考え,「データ受信したらマスタからスレーブにACKを送っておけばOK」と思い込むとハマる.結論から言えばその考えは80%は間違ってなかっただろう.「最後の1バイトを受信したら,ストップコンディションの前にACKではなくNACKを返す必要がある」という点には十分な注意が必要である.
ACKと言う信号は,一般には通信応答の意味を示す.これはACKを返す=データを正しく受信したACKを返せない(NACKを返す)=データが正しく受信できていないという意味になる.マスタからスレーブに送信する場合, ACKの意味は上記で間違いない.
しかしI2Cの場合,マスタが受信する際に送るACKは「データをさらに要求するかどうか?」という意味に変化し,正しく受信できたか?という意味にはならない.ここを勘違いしたままでいると,(そしてArduinoライブラリなど,このあたりの処理が隠されたライブラリを使っていると),最初の1回は上手くいくのだがそれ以降通信がタイムアウトしてしまう.
受信におけるACKとは,ACKを返す=さらにデータを要求するACKを返せない(NACKを送る)=データの要求を終了すると言う意味である,ということを念頭に置かないと処理がおかしくなってしまうのである.
レジスタを使った通信さて,一般的なセンサデバイスと通信する場合,計測値の受信や設定値の送信はデバイス内のレジスタに対する読み書きという形で表現されることが多い.これをI2Cの流れに沿って考えると,
- レジスタに書き込む
【マスタ→スレーブ】スタートコンディションを送る(というか示す)【マスタ→スレーブ】アドレスを書き込みモードで送信する【スレーブ→マスタ】該当するアドレスを持つデバイスはマスタに向けてACKを返す【マスタ→スレーブ】書き込み対象のレジスタアドレスを送る【マスタ→スレーブ】レジスタに書き込むデータを送る【マスタ→スレーブ】ストップコンディションを送る(というか示す)これで書き込みは実現できる.もちろん,レジスタアドレスやデータを送る際には,スレーブからACKが返ってくる.
次に,レジスタから読み込みたい場合はどうすればよいだろう?レジスタアドレスの指定はマスタ→スレーブなのだから書き込みモードだが,読み込むのはスレーブ→マスタなのだから読み込みモードのはず・・・ ではデバイスにはどちらのモードで接続をかけるのが正解なのだろうか?
まず,余計なことを考えずに「送信と受信」で考えると以下のようになる.
【マスタ→スレーブ】スタートコンディションを送る(というか示す)【マスタ→スレーブ】アドレスを書き込みモードで送信する【マスタ→スレーブ】読み込み対象のレジスタアドレスを送る【マスタ→スレーブ】ストップコンディションを送る(というか示す)【マスタ→スレーブ】改めてスタートコンディションを送る(というか示す)【マスタ→スレーブ】アドレスを読み込みモードで送信する【スレーブ→マスタ】データを送信する【マスタ→スレーブ】最後だけNACKを返す【マスタ→スレーブ】ストップコンディションを送る(というか示す)「送信するものは送信し,受信するものは受信する」で処理を分けた.モノにもよるがこれでうまくいってくれるデバイスは多い.実際には,このうち4.を省略することが可能であるし,一般にはそのようにすることが多いようだ.その場合このようになる.
【マスタ→スレーブ】スタートコンディションを送る(というか示す)【マスタ→スレーブ】アドレスを書き込みモードで送信する【マスタ→スレーブ】読み込み対象のレジスタアドレスを送る【マスタ→スレーブ】改めてスタートコンディションを送る(というか示す)【マスタ→スレーブ】アドレスを読み込みモードで送信する【スレーブ→マスタ】データを送信する【マスタ→スレーブ】最後だけNACKを返す【マスタ→スレーブ】ストップコンディションを送る(というか示す)この場合,4.のスタートコンディションは別名「リスタートコンディション」と呼ばれる.しかし,実態は見ての通りスタートコンディションを送りなおしているだけであって,「リスタートコンディションという特別なコンディションは存在しない」
まとめそんなわけで,今までハマったポイントをまとめると以下のようになる.
送信は単純.各データを送った際に1バイトごとにスレーブからACKが来る受信の際はACKの意味が「さらなるデータを要求するかどうか」に変化していることに注意そのため,受信を最後はACKではなくNACKを送ってデータをもらい終わったことを示さなければならないレジスタし指定して受信する際は「スタート,スタート,ストップ」コンディションを送る2回目のスタートコンディションをリスタートコンディションと言うが,特別なコンディションではないこれを覚えておけば,多少なりともI2Cの挙動が理解しやすくなるし,また上手く通信ができない場合も原因究明に役立つことだろう.