2020年3月8日日曜日

ArduinoのI2Cは内部抵抗でプルアップされるのが標準仕様らしい


背景

Arduinoとは電子工作に利用しやすい開発ボードです。
I2Cとは、通信規格の1つです。

I2CはSDAとSCLという2種類の信号線それぞれプルアップして(電圧を高くして)利用します。
今までArduinoでI2C通信を利用する際にプルアップ抵抗を使ってきたのですが、何と内部抵抗でプルアップするのが標準仕様だと分かりました。
ArduinoのUnoやLeonardoなどに使われるAVRマイコンと、自分が最近よく使うWiFiやBluetooth機能が使えるESP32マイコンに関して、調べたり確認した内容を共有します。

記事の結論を先に挙げると、こちらです。
  • AVRマイコンは内部抵抗のプルアップを解除できる
    Wire.beginの後にpinMode(SDA, INPUT); pinMode(SCL, INPUT);を呼べば解除できました。
  • ESP32は内部抵抗でのプルアップ解除方法が分からなかった
    方法をご存知でしたら、良ければコメントなどで教えてください。
  • 3.3Vと5Vで動かすatmega328pをI2C通信させると、信号線の電圧が4Vくらいになり、3.3Vに引っ張られた
    ロジックレベルを合わせる方が信号強度を保てそうでした。

注意: 外部プルアップを利用すると良い場面もあります

I2Cのプルアップには4.9kΩを推奨する仕様書を良く見かける中、ESP32の内部抵抗は数十kΩあるためか外部抵抗でプルアップすると通信が安定するという呟きを見かけることがあります。
内部抵抗でプルアップされる仕様ではありますが、安定性しない場合は10kΩなどでプルアップしてみると安定するかもしれません。

確認に利用したもの

  • Arduinoにプログラムを書き込めるPC
  • Arduino Uno
  • ESP32開発ボード
    (動作確認したものの期待するようには動かなかったので、画像は出てきません)
  • WiredController
  • ブレッドボード
  • ジャンパワイヤ
  • ワニ口クリップ
  • 電圧計
  • Analog Discovery2

該当するソースコード

ArduinoでI2Cを利用する場合は、Wireというクラスを利用するのが一般的だと思うので、Wireの実装を調べてみました。

ArduinoのAVRマイコン(UnoやLeonardoで使われる集積回路)向けのコードを調べると、Wireのbegin関数で呼び出しているtwi_initで、信号線をプルアップしていると分かりました。


ESP32をArduinoのように使えるコードを調べると、Wireのbegin -> i2cInit -> i2cAttachSDAもしくはi2cAttachSCL -> pinModeをPULLUPで初期化(SDA, SCL)していると分かりました。


Wireの実装はマイコンごとにコードが分かれているのでプルアップが標準ではないマイコンがあるかもしれませんが、自分がよく使うAVRとESP32マイコンはプルアップするのが標準でした。

内部抵抗でのプルアップをやめる方法

AVRマイコンの場合

WireプルアップせずにSDAやSCLを初期化する機能は無さそうでした。
AVRマイコンの場合は、Arduinoのフォーラムで紹介されているようにWire.beginを実行後にpinModeをINPUTにすればプルアップを止められました。
Wire.begin();
// Wire.begin(address); Slaveモードも同様
pinMode(SDA, INPUT);
pinMode(SCL, INPUT);

ESP32の場合: 持続的な方法は不明

ESP32の場合はWire.begin実行後にPULLUPのフラグだけを削除したSDASCLピンの初期化を行なえば一時的にプルアップを止められますが、Wireの関数を呼ぶとプルアップが再開してしまい、持続的にプルアップ止める方法は分かりませんでした

下記の記述をすれば、Wireの関数が呼ばれるまでの一時的な間だけプルアップを止められます。
なお、INPUTフラグだけではI2C通信できなかったので、ESP32のI2C通信にはOPEN_DRAINフラグは必須なようです。
Wire.begin();
pinMode(SDA, OPEN_DRAIN | INPUT);
pinMode(SCL, OPEN_DRAIN | INPUT);

ESP32で持続的にI2Cのプルアップを停止する方法をご存知でしたら、コメントなどで共有していただけると嬉しいです。


ちなみに、記事を書いている時点ではarduino-esp32はI2Cのスレーブモード非対応でした。
下記のissueが閉じられていたら、ESP32でスレーブモードが使えるようになっているかもしれません。

I2C Slave not implemented · Issue #118 · espressif/arduino-esp32

動作確認: atmega328pマスター + atmega328pスレーブ

利用したマスターとスレーブはこちらです。
  • マスター: WiredControllerのReadAndWriteプログラムをI2Cプルアップ無効化の記述と共に書き込んだArduino Uno
    コントローラーのLEDの光り方の変更と、ボタンやジョイスティックの状態を読み取ります
  • スレーブ: WiredController(atmega328p利用)のファームウェアをI2Cプルアップ無効化の記述と共に書き込んだコントローラー

プルアップ無効化する前は、マスターもスレーブも外部抵抗無しの内部抵抗でプルアップされていました。

5Vで動くマスターのUnoが内部抵抗でプルアップされていたときの様子


3.3Vで動くスレーブのコントローラーが内部抵抗でプルアップされていたときの様子


下記の画像は、I2Cの信号線を接続して抵抗でプルアップしていない状態です。
SCLが21mVと電源電圧5Vに比べて低い値を示しています。
マスター(Uno)は制御信号を送信していますが、コントローラーのLEDの光り方は変わりません。


10kΩの抵抗でSDAとSCLをプルアップします。


プルアップしたことにより3.86Vになりました。
コントローラーのLEDが変化するので、I2C通信出来ているようです。


5Vにプルアップしてるものの電圧が4V弱なのが気になったので、Analog Discovery2で電圧の様子を見てみました。


100msのdelayでloopしているマスターのプログラムどおりに100msごとくらいに信号が出ていました。
Analog Discovery2で見ても最高電圧は4V弱なので、5Vにプルアップしているものの3.3Vのレギュレータを電源として動かしているコントローラーの電圧に近づくようでした。


抵抗を抜くと電圧が62mVに下がり、I2Cの通信ができなくなってコントローラーのLEDの更新が止まりました。


プルアップ抵抗を付けたままコントローラーを抜くと信号線の電圧が4.8Vになりプルアップ元の電圧(4.84V)とほぼ同じになったので、信号線の電圧降下は3.3Vで動くコントローラーが引き起こしているようでした。


I2Cの通信線はプルアップした電圧になると思っていましたが、低い電圧で駆動するatmega328pが居ると、その駆動電圧に近づくと分かりました。

まとめ

ArduinoのAVRマイコンとESP32はI2Cの信号線を内部抵抗でプルアップするのが標準仕様だと分かりました。
ESP32は持続的に内部抵抗によるプルアップを解除する方法が分かりませんでしたが、AVRマイコンでの解除方法は分かりました。
AVRマイコンでI2Cのスレーブ端末を作る場合は、プルアップの抵抗の減少を抑えるために、内部抵抗は利用しない設定にしても良さそうだと思いました。

異なる電圧で動く装置で動作確認したことにより分かったこととして、I2C通信は電圧を通信時のみ降下させるものと思っていましたが、atmega328pを信号線の電圧より低い電圧で駆動する場合は、信号線の上限電圧を電源電圧に近づける傾向があるようでした。
そのため、I2Cのバスで接続する機器は駆動電圧を揃えるか、電圧が異なる場合はロジックレベルを変換する装置を使うのが、信号の強度を保てて良さそうだと思いました。

参考

Topic: I2C with internal pullups

変更履歴

2022.04.22
内部プルアップ抵抗は大きく信号の立ち上がりが遅く通信不良になることがあるため、外付けの抵抗を使うと良い場合がある説明を追加しました。

0 件のコメント :