I2Cの速度の設定

I2Cの速度と書き換えにかかる時間差

I2C通信の速度はSCL信号の速度次第ですが、元々は比較的低速での通信を想定した規格で、標準の速度は100kbit/s(=100kHz)と規定されています。また、Arduinoフレームワークでもこの速度が初期値となっています。

1秒あたり100kビットの速度なので、1ビットの送信には1/100k=0.01ミリ秒かかることになります。

ここで、現在のプログラムでのIOエクスパンダの出力レベルの書き換えは

  • 「レジスタのアドレスの指定」→「アノード側(GPIOA)の設定値送信」→「カソード側(GPIOB)の設定値送信」

という順序で1バイト(8ビット)のデータを順次送る形になっていますが、アノード側の設定値送信とカソード側の設定値送信の間には、少なくとも0.08ミリ秒のずれが生じることになります。実際にはデータの送信確認等も入りもう少し時間がかかりますが、仮に0.08ミリ秒だったとしても、ダイナミック点灯の1行あたりの点灯時間1ミリ秒に対して無視できない長さになっています。

そのため、この時間差の間、アノード側の出力レベルが新しい列のものになる一方でカソード側の出力レベルは以前の列のままとなり、結果的にドットマトリクスLEDの文字が滲んでしまったのです。

ライブラリの修正例1

この問題を解決する方法のひとつは、IOエクスパンダの出力レベルの書き換え時にいったんドットマトリクスLEDを全消灯することです。

例えば、IOエクスパンダの出力レベルの書き換えを

  • 「カソード側全消灯」→「アノード側設定」→「カソード側設定」

のようにすれば、アノード側設定の時点ではドットマトリクスLEDは全消灯しており、カソード側設定の完了後に改めて点灯するので、文字の滲みは防ぐことができると考えられます。

以下に、dotmatrixled.cppの修正例を示します。

dotmatrixled.cpp 修正例1

/*
 * ドットマトリクスLED点灯パターン設定関数
 * 引数: patternAnode 表示するパターン(アノード側)
 *       patternCathode 表示するパターン(カソード側)
 */
void setLed(byte patternAnode, byte patternCathode) {
  Wire.beginTransmission(ioxAdr); // IOエクスパンダとの通信の開始
  Wire.write(0x13); // レジスタのアドレスの設定(0x13=GPIOB出力レベル設定レジスタ)
  Wire.write(0xFF); // カソード側の出力レベルを全消灯に設定
  Wire.endTransmission(); // 送信の完了
  Wire.beginTransmission(ioxAdr); // IOエクスパンダとの通信の開始
  Wire.write(0x12); // レジスタのアドレスの設定(0x12=GPIOA出力レベル設定レジスタ)
  Wire.write(patternAnode); // アノード側の出力レベルの設定(0x12=GPIOA出力レベル設定レジスタ)
  Wire.write(~patternCathode); // カソード側の出力レベルの設定(0x13=GPIOB出力レベル設定レジスタ)
  Wire.endTransmission(); // 送信の完了
}
Code language: Arduino (arduino)

文字の滲みは解消されましたか?

IOエクスパンダで「A」と点灯

I2Cの速度の設定

I2C通信には次のような速度モードが規格上存在しています。

  • 低速モード: 10kbit/s(=10kHz)
  • 標準モード: 100kbit/s(=100kHz)
  • ファーストモード: 400kbit/s(=400kHz)
  • ハイスピードモード: 3.4Mbit/s(=3.4MHz)

ArduinoフレームワークのI2C通信の速度の初期値は、先述のとおり標準モードの100kHzになっています。

実際の速度は、下限はゼロから上限はシステムが対応可能な最高速度まで、マスターが設定可能な範囲内で任意に指定できます。

I2Cの速度の設定は、次の関数で行います。

Wire.setClock(clockFrequency)Code language: Arduino (arduino)

引数のclockFrequencyで、速度をHz単位で設定します。

ライブラリの修正例2

I2C通信の最高速度は、単にマスター側がその速度に対応しているだけでなく、スレーブ側もその速度に対応している必要があり、さらにマスター・スレーブ間の回路(信号線の長さや他の線との干渉等)によっても制約を受けます。

本コースのシステムでは、ESP32のI2Cインターフェースはデータシート上4MHzまで、MCP23017は1.7MHzまで対応しています。そのため、1.7MHzがデータシート上対応できる上限と考えられます。しかし、信号線の長さがやや長いこともあり、実際に安定して通信できる上限は 400kHz 程度です。

以下に、dotmatrixled.cppの修正例を示します。

dotmatrixled.cpp 修正例

/*
 * ドットマトリクスLED初期設定関数
 */
void initLed() {

  // I2Cの初期化
  Wire.begin();
  // I2Cを400kHzに設定
  Wire.setClock(400000);

  // IOエクスパンダの初期設定
  Wire.beginTransmission(ioxAdr); // IOエクスパンダとの通信の開始
  Wire.write(0x00); // レジスタのアドレスの設定(0x00=GPIOA入出力設定レジスタ)
  Wire.write(0x00); // GPIOAのピンを出力に設定(0x00=GPIOA入出力設定レジスタに0x00を書き込み)
  Wire.write(0x00); // GPIOBのピンを出力に設定(0x01=GPIOB入出力設定レジスタに0x00を書き込み)
  Wire.endTransmission(); // データを送信し通信を完了

  // IOエクスパンダのピンの初期値の設定
  Wire.beginTransmission(ioxAdr); // IOエクスパンダとの通信の開始
  Wire.write(0x12); // レジスタのアドレスの設定(0x12=GPIOA出力レベル設定レジスタ)
  Wire.write(0b00000000); // GPIOAのピンの出力レベルの設定(0x12=GPIOA出力レベル設定レジスタ)
  Wire.write(0b11111111); // GPIOBのピンの出力レベルの設定(0x13=GPIOB出力レベル設定レジスタ)
  Wire.endTransmission(); // データを送信し通信を完了
}
Code language: Arduino (arduino)

これよりも速度を速くすると、LED が点灯しなくなったり、描画速度が遅くなっているように見えたりと、正常に動作しなくなる場合があります。

次の記事

SWとは