yoshiyuki's blog

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

Arduino / I2C 関数をイチから作る (6)

最後に Master Read の処理を説明します。

-: 事前コマンドの書き込み

事前のコマンド書き込み (読み出しを行うレジスタアドレスの書き込み等) が必要な場合は Master Write と同じ処理で行います。ただし、書き込み完了後の Stop Condition の取得は不要です。

マイコン: Start Condition の取得要求

マイコン: 取得完了

Start Condition の取得までの処理は Master Write の処理と同じです。

マイコン: Slave address + R の入力

Slave Address + R を送信します。

  status_code = i2c_send_data((slave_adr<<1) + 1);

7-bit Slave Address を左シフトしたものに 1 を足すと Slave Address + R になります。これを i2c_send_data の中でレジスタページ TWDR に書き込みます。

マイコン: Data 送信を指示

マイコン: 送信完了

マイコン: ACK or NACK

Master Write の Slave Address 送信の処理と同じです。

マイコン: Data 取得を指示

データを取得して ACK を送ります。

byte i2c_get_data(bool repeat){
  byte status_code;

  if(repeat){
    TWCR = _BV(TWEA)|_BV(TWINT)|_BV(TWEN);
  }
  else{
    TWCR = _BV(TWINT)|_BV(TWEN);    
  }

  if(i2c_get_status_code(CONST_TIMEOUT) == SC_SUCCESS){
    return TWDR;
  }
  else{
    return 0xFF;
  }
}

レジスタページ TWCR の TWEA と TWEN 、または TWEN のみに 1 をセットした状態で TWINT に 1 を書き込み、処理を開始します。
この操作はデータを送る場合とほぼ同じですが、マイコンは Slave Address 送信時の R/W コマンドを記憶しており、コマンドが W だった場合はこの操作で TWDR に格納されたデータを送信し、R だった場合は取得したデータを TWDR に格納します。
repeat による分岐は Burst read を行うかどうかです。データを 1 Byte 取得した後に続けて次の Byte も読み出す場合は TWEA = 1 をセットして ACK を送ります。受け取った Byte でデータ取得を完了する場合は ACK を送らないので TWEA をセットしません。
f:id:ysin1128:20200612174857p:plain
このように、ここではマイコンが ACK を出すかどうかが重要なのですが、私はこの仕様を知らないまま I2C を扱っていたので気付くまでだいぶ躓きました。知らないままでも正しい通信ができる Wire ライブラリは偉大です。
Burst read を行う場合、つまり、連続して複数Byte のデータを読み出す場合、一つのByteの取得の取得が完了したところでマイコン (Master) から通信相手 (Slave) に対して ACK を送って次の Byte を読み出す意思があることを伝える必要があります。ACK を送らなかった場合、続けて SCL を送っても Slave は応答せず、データを取得できません。逆に次の Byte を読み出す気が無いのに ACK を送ってしまうと、Slave は次の Byte も読出してもらえるつもりで SDA を Low にして待つ場合があるため、それを無視して Master が別の通信を始めようと思っても Start condition を取れず、一切の通信が不可能になります。

マイコン: 取得完了

レジスタページ TWCR の TWINT を見張りながら処理の完了を待ちます。(Master Write時の処理と同じです)

マイコン: ACK or NACK

処理が完了したら Status code を取得します。(Master Write時の処理と同じです)

マイコン: 取得した Data の読み出し

Status code が SC_SUCCESS の場合は取得したデータがレジスタページ TWDR に格納されているのでそれを読み出します。
Status code が SC_NACK や SC_TIMEOUT の場合はデータの取得が行われていません。私の関数では、これらの場合に中断等のエラー処理は行わず、代わりに読み出し値として 0xFF を返します。

...

続けてデータを読み出す場合は「 Data取得を指示」以降を繰り返します。

マイコン: Stop Condition を指示

読み出しが終わったら Stop Condition を指示します。 (Master Write時の処理と同じです)