トップ 一覧 検索 ヘルプ RSS ログイン

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

tips-MH_ET_Live_esp32_minikit

PRG-stm32-tips

----
サンプル
! 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の挙動が理解しやすくなるし,
また上手く通信ができない場合も原因究明に役立つことだろう.