I²Cデバイスを初めて使う際にありがちなトラブルです. サンプル・チップを入手し,ブレッドボードやユニバーサル基板で試してみるときには,I²Cの信号名を確認しながらマイコンなどに接続します. このとき,ついやってしまいがちなのか「プルアップ抵抗のつけ忘れ」です.
I²Cの信号は「オープンドレイン」と呼ばれる出力になっています.外部にプルアップ抵抗を付けなければHIGHレベルの電圧が得られません. ブレッドボードなどで簡単に動作をチェックしようとしている時に,ついこの抵抗を忘れてしまいます.
SDAとSCLの信号にはプルアップ抵抗を忘れずに!
プルアップ抵抗の値はどうやって決めているでしょうか?一般には2.2kΩや4.7kΩといった値がよく使われます.あまり気にせずにこの値を使う場合も多いのではないかと思います.しかしもし,もっと小さい値やもっと大きい値を使うとどうなるのでしょう?
小さい抵抗値のプルアップでは「信号をLOWに引ききれない」問題が起こります.デバイスがLOWを出力すると,プルアップ抵抗には電流が流れます.抵抗値が小さいと,ここに流れる電流は大きくなり,その電流がLOWを出力しているデバイスに流れ込みます.デバイス内部の信号をLOWに引き込むトランジスタは抵抗分を持っているため,この抵抗分と電流で電圧が発生します.このため電流が大きくなると想定以上の電圧が発生し,充分低いLOWレベル電圧が得られないことになります.
逆に大きい抵抗値のプルアップでは,充分な通信速度が得られない問題が発生し得ます.信号がLOWからHIGHへの遷移する時には,信号線が持っている静電容量に対して,プルアップ抵抗を介して充電が行われることにより電圧が上昇します.プルアップに大きい抵抗値を用いると,この時の電流が小さくなり信号の立ち上がり(電圧の上昇)に時間がかかります.
プルアップ抵抗の値には計算の方法があります.これはI²C仕様書[英語版(rev7)] [日本語版(rev5)]の第7節に詳細が掲載されています.一般に,たとえばI²Cのファーストモード(400kHz)でプルアップ電圧を3.3Vで使用の際には1kΩから8kΩ程度のプルアップならば問題なく使うことができます[図1].
図1:I²C信号線のプルアップ抵抗値
マイコンとターゲットを接続して,プルアップ抵抗もつけて,信号のHIGHレベルもオシロスコープで確認したのに,ターゲットが反応しない.
「ターゲットが反応しない」とは,ターゲット・アドレスを送信した後のACKが返ってこない状態です.
評価の初期段階ではこのようなこともよくあります. ターゲットはその設定によってターゲット・アドレスが変更できるものも多く存在し(アドレス設定ピンを電源やGNDに繋ぐなど),どのアドレスが設定されているか再確認が必要です.
また,アドレスを正しく設定しているつもりでも,データシートに16進数で書かれたアドレスが,7ビット表現(右詰め)か,8ビット表現(左詰め)なのかを確認しておかなければなりません.さらに使っているマイコンの開発ツール(SDK)によっても16進数でのアドレス指定を7ビット表現にしているか(MicroPythonなど),8ビット表現にしているか(Arm Mbedなど)の違いもあります.
I²Cの仕様書ではいずれの表現も用いず,アドレスは2進数表現に統一されているので,このような誤解はないのですが,16進数で指定する場合は要注意です.
ターゲット・アドレスがどのように指定できるか.これをデバイスの仕様書と開発ツールで確認しましょう. その上でデバイスのアドレスを意図通りに設定できているか再確認しましょう.
実機での確認作業では,例えばMicroPythonなどではI2Cクラスにはscan()メソッドが用意されており,そのバスに接続されたACKを返してくるデバイスをリストで表示してくれます. このような機能を使い,どのような設定となっているのかを手早く見てみるのもひとつのテクニックです[図2].使用するSDKにI2C::scan()のような関数がなければ,アドレスを順に送信してみてACK/NACKを確認してみます.
図2:I2C.scan()を試してみた様子
I²Cは仕様書により,信号レベルやプロトコルが詳細に決まっています.どのターゲットデバイスでもライト/リードは規定の方法で実行できます. しかしそのターゲット内のレジスタやデータバッファにどのようにアクセスするかは,デバイスごとに仕様が決まっています. 内部レジスタを複数持つようなターゲット・デバイスにおいては,一般には[図3]のような通信によってレジスタの指定とその読み書きが可能です. しかしこの通りに通信を実行しても,期待通りのデータが読めないことがあります.
レジスタ・アドレスを指定するためのライト・トランザクションと,レジスタを読んでくるリード・トランザクションを連続で実行する際には,2回目のトランザクションの開始にリピート・スタート・コンディションがよく使われます.
リピート・スタート・コンディションは,次のスタート・コンディションを発生させる前に一旦ストップ・コンディション発生後のバス・フリー状態を経ることを省略するために用いられ,通常はターゲットデバイスから見て,リピート・スタート・コンディションを使うのもストップ・コンディションとスタート・コンディションを使うのも等価です.
しかしそのようになってないデバイスも存在します.このようなデバイスではレジスタの読み出しはどちらかの方法を用いなければなりません.
一部のマイコンのSDKなどではI²Cターゲットのレジスタ読み出しを行うAPIが用意されていて,これを用いるとリピート・スタート・コンディションでのアクセスを自動でやってくれるのですが,ストップ・コンディションを挿入できない仕様のものがあったりします. このような場合にはそのAPIをそのまま使えないので,より低レベルでプロトコルを扱う必要が出てきます.
デバイスやマイコンSDKにはこのような後から気づく「仕様の落とし穴」があったりするので注意が必要です.
図3:一般的なレジスタ・アクセス
I²Cにはオプション機能として「クロック・ストレッチ」という機能が用意されています.最近でもまれにこの機能を備えたデバイスが存在します. 多くのマイコンに搭載される一般的なI²Cコントローラはこのクロック・ストレッチ機能に対応しているので,あまり気にすることはありませんが,自身でコントローラを実装などの場合には,このオプション機能について認識しておく必要があります.
I²Cはコントローラ側が出力するクロックのタイミングによってデータの送受信を行います.しかしクロック・ストレッチはターゲット側からコントローラを待たせることができるようにしたオプションです[図4]. コントローラはクロックもオープンドレインで出力します.コントローラは自身のクロック出力を監視していて,HIGHを出した時に信号がHIGHになっているかを見ています. もしターゲット側がコントローラを待たせたい時にはこのクロック信号をLOWに保持します.コントローラはHIGHになっているはずのクロックがLOWになっていることで待たされていることを知ります.
もしコントローラがこのようなターゲットの挙動を無視すれば,出力されるクロックの数が少なくなるなどの通信不具合の原因となります.
図4:クロック・ストレッチ
マルチ・コントローラもI²Cのオプション機能です.複数のコントローラが同一バス上で互いの通信に影響することなく通信を制御できます.
マルチ・コントローラが使われる例は一部のアプリケーションを除き,あまり多くはありません.でも多くのマイコンに内蔵されるI²Cコントローラはこの機能を備えています.
この機能を使って,例えば稼働中のシステムに不具合があった際に,I²Cバスに別のマイコンを接続して,各ターゲットのレジスタ設定の中身を読んだり,あるいは上書きして検証するような用途に使えます[図5]. 通常のデバッグは自分が書いたコードの確認作業なのでこのようなことを行う機会はないと思いますが,もしソースコードが入手できない未知のシステムを検証する場合などには使えるかもしれません.
図5:マルチマスタでデバッグ
I²Cの帯域をギリギリまで使う例はあまり多くはありません.しかしもし自分が意図した速度から大きく外れるクロック周波数で通信が行われていることに気づいてしまったら.. 先の項で紹介したクロック・ストレッチは周波数低下に関係します.
下では100kHz時の例を示していますが,使用する周波数が高くなるほどこの影響(周波数の低下率)は大きくなります.
クロック・ストレッチはコントローラを待たせる機能です.しかしこれに影響するのはターゲットだけではありません. 信号線長が長くなり,接続されるデバイスが多くなると,バスの静電容量が増加します.この時,値の大きな抵抗をプルアップに使っていると,信号の立ち上がりが鈍ります.
鈍った信号の立ち上がりにより,クロックがHIGHになるまでの時間が長くなります.コントローラはクロックがHIGHになったあとに,設定したHIGHの保持時間を確保するように動作するので,その結果LOWからHIGHへの遷移時間が長くなったことによりLOW期間が伸びたことになります.これが原因でクロック周波数の低下が起こります[図6].
コントローラによっては,クロックの周波数を細かく設定できたり,あるいはクロックのLOWとHIGHの期間を設定できるがあります.周波数が気になる際には波形を確認し,クロック設定を調整します.
I²Cは2本線のシリアルバスで手軽に使えます.低速な信号なので(環境にもよりますが)数十センチ程度なら,信号線を伸ばしても安定して動きます. しかしI²Cの特性を考えて気をつけなければ,思わぬ挙動を目にすることになります.
I²Cはオープンドレイン信号なので,HIGHレベルが出ている時には,信号線のインピーダンスは高い状態にあります. プッシュプル信号であれば,信号を出力しているデバイスがLOWならGND,HIGHなら電源にショートに近い状態になっているのですが,I²Cの場合はプルアップ抵抗を介して電源に接続されます. ここで信号線にノイズが乗ってきた場合には,ノイズの電流は抵抗を介して電源に逃げることになります. またI²Cの互いの信号線の電流も影響を与え合い,これがクロストークとなります.
ノイズやクロストークを抑えるためには,配線に工夫が必要です.例えばデータとクロックの間にGNDや電源の配線を挟んで引き回すなどです[図7].
または長い距離をケーブルで伝送する場合にはバスバッファの使用が有効です.ただしI²Cは双方向のオープンドレイン信号なので,標準ロジックのバッファをそのまま使うことはできません.I²Cには各種バッファが用意されており,中には信号電圧変換機能やシングルエンドのI²Cを差動信号に変換するものなども存在します.これらを組み合わせることで,長く信号線を引き回しても,安定した信頼性の高いシステムを組むことが可能です.
図7:クロストーク,ノイズ
400kHzまでI²Cであれば400pFまで.1MHzのI²Cなら550pFまでのバス容量で通信を行えます. しかしこのバス容量.どうやって知ることができるでしょうか?
このブログ記事ではバス容量に関連した項目がいくつか紹介されています.これはらどれも信号の立ち上がり時間が長くなることを指摘しています.バス容量はこの立ち上がり時間から知ることができます.
[図8]はその波形例です.5V電源に2.2kΩでプルアップしたI²C信号で,LOW側の基準電圧VOL=1.5VからHIGH側の基準電圧VOH=3.5Vに達するまでの立ち上がり時間(T)が154nsになっています.
I²C仕様書[英語版(rev7)] [日本語版(rev5)] 7.1節の式は「T = 0.8473 x RC」であることから「C = T / (0.8473 x R)」に当てはめると約82pF.測定に使ったプローブの容量(15pF)を差し引くと約67pFであることがわかります.
図8:バスの静電容量を求める
コントローラとターゲットは互いにクロックを基準に通信を行います. もしノイズなどの原因で,このクロックの数が合わなくなってしまったら.. ビットずれを起こした通信となるだけでなく,「バス・スタック」と呼ばれる状態に陥ってしまいます.コントローラ側が9回のクロックパルスを送って通信を終了したつもりになっているのに,ターゲットはまだ最後のビットとしてACKを返している例が[図9-a]です.
このような状態から抜け出すには2つの方法があります. ひとつはターゲットのリセット.もうひとつはバス・クリアです.バス・クリアはI²C仕様書[英語版(rev7)] [日本語版(rev5)]にその記載があります(3.1.16節).
SDAがLOWに貼りついている場合には,クロック・パルスを9回発生させることでターゲットの状態を戻すことができます. [図9-b]はマイコン側のプログラムのミスで,リード転送の最後にACKを出してしまった例です.次に読み出されるデータのMSBが,たまたま0だったためにSDAがLOWに固定されてしまいました.
この状況を脱するためにクロックを9回発行しバス・クリアを行ったあと,ターゲットが元に戻ったことがわかります.
なお,I²Cを仕様として採用しているシステム・マネジメント・バス(SMBus)では,通常のI²C仕様の上にタイムアウト機能を設け,ターゲットデバイスにおいては最大35ms経過後には自動的に復帰するように規定されています.
I²C仕様ではこのようなタイムアウト規定はありません.なので一度スタックしてしまった状況はリセットやバスクリアを行わなければ復帰できません.
図9-a:バス・スタックの発生例
図9-b:バス・クリア
先述のバス・スタックや,マルチ・コントローラでのバスの状態を見ているときに,どのデバイスが信号を出しているのかを知りたくなるときがあります. LOWを出力している時の電圧は,各デバイスの駆動能力の差によってばらつきがあり,それによってどのデバイスが出した信号なのかを知ることができます.
[図10]はマルチ・コントローラの波形例です. この波形は二つのコントローラが同時に通信を開始しようとし,クロック同期と調停が行われている様子を見ています. 一方のコントローラ(マイコン1)は400kHzで,もう一方のコントローラ(マイコン0)は100kHzで通信を開始しようとしており,それぞれがSCL信号を駆動しています.
クロックの波形を見てみると,この100kHz側のコントローラの端子は100Ωの抵抗を介してバスに接続されているため,マイコン0がLOWを出力した時には,このシリーズ抵抗によって電流を引き込む能力が制限され,LOW電圧が少し高くなります.このような仕掛けを用意することでどちらのデバイスがLOWを出力したのかを判別できます.
図10:誰が信号を出しているか?
I2C バス仕様およびユーザーマニュアル (Rev5.0 日本語版)
https://www.nxp.com/docs/ja/user-guide/UM10204.pdf
I2C バス仕様およびユーザーマニュアル (Rev7.0 英語版)
https://www.nxp.com/docs/en/user-guide/UM10204.pdf
インターフェース 2024年3月号(CQ出版):特集『ゼロから作るシリアル通信[UART/I2C/SPIをPicoで]』
https://interface.cqpub.co.jp/magazine/202403/
トランジスタ技術スペシャル No.161 『測る 量る 計る 回路&テクニック集』付録:「2線シリアル・インターフェース I²C詳解」 - 上記雑誌記事は,この記事から抜粋
https://www.cqpub.co.jp/trs/trsp161.htm
NXPコミュニティ・ブログ:I²Cバスの概要
https://community.nxp.com/t5/NXP-Tech-Blog/I-C%E3%83%90%E3%82%B9%E3%81%AE%E6%A6%82%E8%A6%81/ba-p/203...
[初出:インターフェース 2024年3月号(CQ出版)「UART,I2C,SPI,CAN…さまざまな規格で起こり得るトラブル」p101-115.ブログ掲載にあたりI²C関連部分を抜粋,加筆修正]
変更履歴:
2025-02-04:初版
2025-08-14:「3. リピート・スタートが必要だった/使用できなかった」の項の誤字を訂正.「7. 長距離の引き回し.ノイズやクロストークで通信がおかしくなる」の項の品文中に改行を追加
=========================
本投稿の「Comment」欄にコメントをいただいても,現在返信に対応しておりません.
お手数をおかけしますが,お問い合わせの際には,NXP代理店,もしくはNXPまでお問い合わせください.
ここにコメントを追加するには、ご登録いただく必要があります。 ご登録済みの場合は、ログインしてください。 ご登録がまだの場合は、ご登録後にログインしてください。