はじめに
年末年始にかけてI2CタイプのOLEDディスプレイ(SSD1306)で鬼滅の刃の禰豆子を描画している中で、改めてI2Cについて勉強し直したのでそのまとめを記事にしたいと思います。
内容としてはI2Cの規格と通信方法の紹介、そしてArduinoでI2Cデバイスを動かす際に使用するやり方について紹介いたします。
I2C(Inter Integrated Circuit)とは
I2Cとはフィリップス社が提唱しているシリアル・インターフェースで同じバスライン上に複数のデバイスを接続することが可能です。とは言っても際限なく接続できることはなく、I2Cデバイスは個々にアドレスを持っています。このアドレスは7bitのうち予約アドレスの16個を差し引いた112個から割り当てることが出来るので実質このアドレスの数が上限です。また、接続デバイスが多すぎてバスライン上の静電容量が大きすぎてもダメです、通信速度が落ちます。実際電子工作レベルであればデバイスに割り当てられたアドレスが干渉しない程度に使用すると思うのでそこまで接続数を意識する必要はないかと思います。
通信はSCL(クロック)とSDA(データ)の2線で行われます。バスラインに複数のデバイスがぶら下がっている場合はターゲットのデバイスに対してアドレス指定し、デバイス毎のI2Cコマンドを送ることによって通信を行います。なので新しいI2Cデバイスを使用するときはデータシートの中の【コマンドリスト】【レジスタマップ】を確認することが必須です(ライブラリを使用する場合は別ですが)。注意したいのがI2Cデバイスはオープンドレインかオープンコレクタになっているので10kΩ程度の抵抗でプルアップさせる必要があります。デバイスによっては基板内でプルアップしてくれるものもあるので要確認です。
余談ですがI2Cと比較される通信方式に4線式のSPI(Serial Peripheral Interface)と呼ばれるものがあります。I2C通信と同様に同期式の通信方式でありますが、通信速度がI2Cよりも圧倒的に早いです。I2Cが数Mbit/sに対してSPIは数10Mbit/sの高速通信が可能であり、ディスプレイ制御でI2CとSPIを比較すると画面切り替え速度が顕著にわかります。
【まとめ】
I2C | SPI | |
信号線 | 2線 | 3~4線 |
伝送速度 | 1Mbps程度 | 数20Mbps |
デバイス選択 | デバイス毎にアドレス指定 | スレーブセレクトにより通信先選択 |
プルアップ抵抗 | 必要 | 不要 |
I2Cの通信方式
I2Cの通信はスタートコンデションから始まりデータを送信しつつクロックで同期を取り、受信側からのACK信号で確認を取ります。通信終了はストップコンディションで定義されます。って説明だけだとわからないと思うので下記にディスプレイデバイスSSD1306のデータシートを参考に説明します。。
- スタートコンディション:SCLがHIGHのままでSDAをHIGHからLOWにすることで定義されます。
- 7bitのスレーブアドレス:SSD1306の場合は外部抵抗の付け替えでSA0のbitを調整することで3Ch(111100)または3Ah(111010)になります(注1)。なおR/W#ビットは書き込みオンリーなので0になります。
- ACK確認:スレーブアドレスの送信が正常に完了したらスレーブデバイスからACK(確認信号)が生成されます。ACKはクロックがHighの時にデータがLOWになることで定義されます。
- コントロールバイト:続くbyteが制御コマンドなのかデータなのか、1byteなのか連続byteなのかコントロールバイトと呼ばれるところで定義します。SSD1306ではコントロールバイト中のCoビットが1ならコントロールバイトに続くデータが1byteのみとして扱われ、0なら連続byteとして扱われます。そしてD/Cビットが0の場合はコントロールバイトに続くデータが制御コマンドとして定義され、1の場合は描画データとして定義されます。例えば(0b11000000)とした場合は続くデータが1byteのみの描画データとして扱われます。
- 制御コマンドまたは描画データ:実際にスレーブデバイスに書き込むデータになります。前のbyteの定義により制御コマンドか描画データに分かれます。
- ストップコンディション:I2Cの通信終了の定義になります。SCLがHIGHのままで、SDAをLOWからHIGHに引き上げることで定義されます。
(注1)SSD1306のデバイスにはアドレスの選択が【0x78】or【0x7C】と書かれていますが、実際にI2Cでは7bitアドレスを使用するので8bit目を0にして【0x3A】【0x3C】なので注意が必要です。
ArduinoでI2C通信をするには
Arduinoには標準としてI2C通信用のライブラリ<Wire.h>が用意されています。
前述したスタートコンディションやエンドコンディション、ACK確認などをこのライブラリで行ってくれるのでI2C通信をやる上で必須のライブラリと言えます。基本的な手順(書き込み)について下記にまとめたいと思います。
1.Wire.begin(address)
Wireライブラリの初期化ですスレーブデバイスの使用の場合はaddressの部分はブランクです。ブランクにすることによりArduinoをマスターとして扱います。
2.Wire.beginTransmission(Slave address)
Slave addressに対してマスターからデータを送信する準備を開始します。おそらくスタートコンディションとスレーブアドレスのACKの確認を行っていると思われる。
3.Wire.write(data)
Slaveに対してデータを送信します。基本的には1byte単位のデータの書き込みになります。文字列、byte配列での送信も可能です。
4.Wire.endTransmission()
Slaveに対してのデータの送信処理を完了します。なお、WireライブラリではbeginTransmissionからendTransmissionまでのデータバッファは32byteなので送信データが多い場合は適宜endTransmissionで区切ることをオススメします。
実際のI2Cコード例(SSD1306)
実際にSSD1306ディスプレイに描画するときの通信コードを示します。画面のイニシャライズに関してはsetupで行っていると改定しています。(イニシャライズ方法はSSD1306データシートの最後の方にフローチャートで載っています)
1 2 3 4 5 6 7 8 |
Wire.beginTransmission(0x3C);//スレーブアドレス3Chと通信準備 Wire.write(0b10000000);//1byte制御コマンドを送る Wire.write(0xB0);//開始ページ設定の制御コマンド Wire.write(0b00000000);//連続制御コマンドを送る Wire.write(0x21);//開始/終了列セットの制御コマンド Wire.write(24);//開始列セット Wire.write(127);//終了列セット Wire.endTransmission();//スレーブ3Chに対して通信終了 |
おわりに
今回はI2C通信の基本的な説明からOLEDディスプレイデバイスを例にした通信フローの説明を行いました。最後にはArduinoで実際にI2C通信を行う際のライブラリの紹介と実際の参考コードを載せております。
実際にはWireライブラリとI2Cデバイスのライブラリを使用すればさらに簡単に通信することが可能です。今回の目的としてはライブラリ不使用でI2Cの基礎から説明したかったのでライブラリを使用しませんでした。
ちなみに描画ライブラリを使用するとわかると思いますがバカみたいに容量を食いますので、必要最低限に描画したい場合はライブラリを使用しない方が自由度はあると思います。
では、今回はこんなところで終わりたいと思います。
ではでは~
コメント