I/Oエキスパンダー(PCF8575TS)の使い方

2023年2月16日技術開発Arduino,I/Oエキスパンダ,PCF8575

概要

7セグLED・蛍光表示管・バーLED等を使うとき、GPIOを大量に使うのが嫌だなーと思ったのでPCF8575TSを買ってみました。
使い方を調べてみると丁寧に説明されてるサイトが意外に少なかったので、調べた内容を整理しました。

テスト環境

MCU:Seeed XIAO RP2040
IDE:Arduino IDE 1.8.19
IC:PCF8575TS(Aliexpressにて購入)

※ICはAliexpressにて購入したものなので正規品ではないかもしれません。寄生容量が大きかったり寄生抵抗が小さかったりと微妙な品質ですし。。。

PCF8575TSについて

PCF8575TSは16bit I/Oエキスパンダーと呼ばれるICです。
I2C通信により、16個のGPIOをHigh/Low制御したりHigh/Low読み取りが出来ます。
どれかのピンのHigh/Lowが変わるとINTがLowになる外部割込み検知機能もあるので普通のGPIOのように使えます。

駆動電圧は2.5V~6.5Vなので、5V系でも3.3V系でも使えます。

A0~A2はI2Cアドレス変更用のピンです。High/Lowを切り替えることで8種類のアドレスを設定できるので、8個並べて使うと128個のGPIOを4線(VCC、GND、SCL、SDA)で制御することができます。

ネックとなるのは制御スピードですね。詳細は後述しますが普通に使うと1回の通信で274[us]、I2C通信の周波数を最大の400kHzまで上げても71.5[us]かかります。
表示器のスタティック点灯程度であれば余裕ですが、ダイナミック点灯だとなるべく複数のI/Oをまとめて制御することで通信回数を減らす工夫をしないと余裕がなくなりますね。ソフトウェアシリアルなんかは遅すぎて無理です。

PCF8575TSのピンアサイン (データシートより引用)

回路図

テスト用にLEDを4個、タクトスイッチを4個付けました。
A0~A2はすべてGNDに接続しているので、アドレスは「0x20」です。

A0~A2にはプルダウン抵抗は入っていないようなので、必ずVccかGNDに接続してください。横着してOPENのまま使ったらアドレスがコロコロ変わってかなりの頻度で通信失敗しました。。。

またI2C通信ラインにはプルアップ抵抗が必要です。寄生容量が大きいのか、マイコンの内蔵プルアップ抵抗では波形がなまってしまいました。(なまった波形を保存し忘れました。。。)
プルアップ抵抗を1.8kΩにしたところ綺麗な矩形波になりました。

※スイッチにチャタリング防止のコンデンサを入れていますが、今回のテストでは関係ないのでいらなかったかも。。。

I2Cアドレス設定 (データシートより引用)

OUTPUTの制御

サンプルコード

まずサンプルコードから紹介します。
4つのLEDを100[ms]ごとにHigh/Lowを変更するプログラムです。(4つが同時に点滅します)

※デフォルトはHighに設定されているので、電源投入後はすべてのピンからHighが出力されます。
※Wire.setSDA(SDA)、Wire.setSCL(SCL)はXIAO RP2040でI2C通信をする時に必要な設定なので、
 普通のArduinoであれば不要です。
※Wire.setClock(400000)はI2Cの周波数を変更したいときに入れてください。
 書かなかった場合はデフォルトの100[kHz]に設定されます。

#include <Wire.h>

const byte ADDRESS = 0x20;

void setup() {
  Wire.setSDA(SDA);
  Wire.setSCL(SCL);
  Wire.setClock(400000);
  Wire.begin();
}

void loop() {
  Wire.beginTransmission(ADDRESS);
  Wire.write(0b00001111);
  Wire.write(0b00000000);
  Wire.endTransmission();

  delay(100);

  Wire.beginTransmission(ADDRESS);
  Wire.write(0b00000000);
  Wire.write(0b00000000);
  Wire.endTransmission();

  delay(100);
}

解説

High/Low出力する際は、↓このフォーマットで2バイトを送ることで制御します。(High=1、Low=0)

I2C通信のデータフォーマット (データシートより引用)

アドレスを含めたフォーマットとHigh/Lowが切り替わるタイミングは↓こんな感じになります。
tpv(High/Lowを変更するための処理時間)は最大で4[us]です。

タイミングチャート/WRITE MODE (データシートより引用)

出力の電流値について

IC内部の出力を制御している部分は↓のような回路になっています。
IOH、IOLはMax 25[mA]となっているので、それを超えるようなものを動かす場合はFETを使うなど対策が必要です。

出力部の回路図 (データシートより引用)

INPUTの制御

サンプルコード

サンプルコードは↓これです。
1秒ごとに全てのピンのHigh/Lowを読み取ってシリアル通信で出力するプログラムです。

#include <Wire.h>

const byte ADDRESS = 0x20;

void setup() {
  Serial.begin(115200);

  Wire.setSDA(SDA);
  Wire.setSCL(SCL);
  Wire.setClock(400000);
  Wire.begin();
}

void loop() {
  Wire.requestFrom(ADDRESS, 2);
  if (Wire.available() > 0) {
    Serial.print(Wire.read(), BIN);
    Serial.print(",");
    Serial.println(Wire.read(), BIN);
  }
  delay(1000);
}

解説

ICから帰ってくるデータのフォーマットはOUTPUTと同じく↓このようになっています。

回路構成上、ピンの出力をHighにすると入力として使えるようになります。
※これがデフォルトでHighの理由です。電源投入時にPower-ON Resetが働いて全てのピンをHighに設定します。

I2C通信のデータフォーマット (データシートより引用)

サンプルプログラムの動作確認結果は↓です。
途中からSW3を押しっぱなしにした(GNDに接続した)ので、P06にあたるFirst Byteの6bit目が0になっています。

シリアルコンソールで動作確認

入力の電流値について

動作確認後にこの記事を書きながらデータシートを見直したところ、絶対最大定格のところに【入力電流はMax 20[mA]】と書かれていました。内部にプルアップ抵抗もなさそうなので、もしかしてGNDに直結はあまりよろしくない…?
今後使うときは電流制限抵抗を入れるようにします。

読み込み結果を配列に代入

byteデータのままでは条件分岐に使いにくいので、ビット演算で配列に置き換えてから出力する方式に変えるとこうなります。

#include <Wire.h>

const byte ADDRESS = 0x20;
bool input_flag[16] = {0};

void setup() {
  Serial.begin(115200);

  Wire.setSDA(SDA);
  Wire.setSCL(SCL);
  Wire.setClock(400000);
  Wire.begin();
}

void loop() {
  byte first_byte;
  byte second_byte;
  int read_data;

  Wire.requestFrom(ADDRESS, 2);
  if (Wire.available() > 0) {
    first_byte = Wire.read();
    second_byte = Wire.read();
    read_data = first_byte + (second_byte << 8);

    for (int i = 0; i < 16; i++) {
      if ((read_data >> i) & 0x01) {
        input_flag[i] = true;
      }else{
        input_flag[i] = false;
      }
    }

    for (int i = 0; i < 16; i++){
      if(input_flag[i]){
        Serial.print("1");
      }else{
        Serial.print("0");
      }
    }
    Serial.println();
  }
  delay(1000);
}

Second Byteはピン名と配列番号が↓の表のようにズレて対応しています。

ピン名P00P01P02P03P04P05P06P07P10P11P12P13P14P15P16P17
配列番号0123456789101112131415
ピン名と配列番号の対応表

動作確認結果は↓です。今回も途中からSW3を押したので、左から7番目が0になっています。

INT(外部割込み)

PCF8575TSには、どれかのピンのINPUT情報が変わるとINTピンがHighからLowに変わる機能が付いています。
これをマイコンの外部割込みピンに接続し、Lowになったら読み込みを実行することで外部割込みを実現することが出来ます。

動作確認

SW1を押したときのINTの電圧波形です。Lowになった電圧はI2Cでピン情報を読み取るとHIGHに戻ります。
読み取るまではLowのままなので、外部割込みではなく定期的に電圧を確認する方法でも検出できそうです。

解説

INTピンはオープンドレインとなっているようで、使う際にはプルアップ抵抗が必要です。
データシートには推奨値が書かれていませんが、1.8kΩ程度が良さそうです。

※INTピンとGNDの間の寄生抵抗が22kΩ程度のようで、抵抗値を大きくするとHIGHの電圧が下がりました。(4.7kΩで2.76V、1.8kΩで3.08V)

INTを使うときの回路 (データシートより引用)