【GPIO】PCA9535でRaspberry PiやArduinoのI/Oを増やす
I/Oエキスパンダ PCA9535
PCA9535は「I/Oエキスパンダ」と呼ばれるICで、マイコンなどのGPIOを増やすために使用されます。
パッケージが小型で安価なこともあり、LEDやモーター・スイッチなどが多く載った家電にはわりと使われていたりします。実際、私は洗濯機の表示/操作パネルと複写機の小基板で使われているのを見たことがあります。

以前の記事で紹介したMCP23017と機能は似ていますが、PCA9535の方が機能がシンプルな分、扱いやすいです。
| MCP23017 | MCP23017でRaspberry PiやArduinoのI/Oを増やす |
|---|
MCP23017との違い
じゃあ何が違うのか、というのをまとめてみました。
| 項目 | PCA9535 | MCP23017 |
|---|---|---|
| 電源電圧 | 2.3V〜5.5V | 1.8V〜5.5V |
| I/O本数 | 16本 | 16本 |
| 入力PullUp | なし 内蔵PullUpが必要ならPCA9555を使用する (ただしプログラムからPullUpのOFF/ON制御はできない) |
あり(プログラムからON/OFF制御可能) |
| 入力許容電圧 | 全端子 電源電圧に関係なく -0.5V〜5.5V SDA,SCLは電源電圧までを推奨 |
全端子 -0.6V〜電源電圧+0.6V ※電源電圧を超えると壊れる |
| 出力可能電流 | ±50mA 全ピンの合計は +160mA,-250mAまで |
±25mA ※ただし全ピンの合計は±125mAまで |
| 割り込み出力 | Nch.オープンドレイン固定 割り込み要因は状態変化のみ チップ自体にバグがある(後述) |
Push-Pull オープンドレイン 出力ドライバ・割り込み要因をプログラムで選択可能 |
| パッケージ | SSOP QFN VQFN |
DIP SOP TSSOP QFN |
PCA9535で最も特徴的なのは電源電圧に関わらず5.5Vまで入力可能なことでしょう。きちんと過電圧対策を行えばI2Cバスのホットスワップも可能ですし、PCA9535の電源がOFFの状態でGPIO端子に信号が加わってもVCC端子側に漏れて誤動作することがありません。(MCP23017で同じことをやるとVDDの電圧が上がってしまい、システムが誤動作することがあります。)
DIYで使う場合、高機能でDIPパッケージがあるが少々高価なMCP23017をとるか、低機能・小型・安価だが面実装しかないPCA9535をとるかは用途とはんだ付けの腕次第ですね。(笑)
似た型番のICにTCA9535もあります。
こちらはピンアサインや仕様などほぼ互換性はありますが、I2Cの最大周波数は100kHzまでなので代わりに使う場合は注意が必要です。
とりあえず使ってみる
周辺に必要な回路はほぼMCP23017と同じです。ただしピン数・ピンアサインとも違うのでそこは要注意です。
ハードウェア環境
Arduino MEGA2560とPCA9535を接続する例を示します。
MCP23017と同様、Raspberry Piで試す場合は3.3Vで動作させる必要があることに注意してください。

上図のP07〜00、P17〜10がGPIOです。MCP23017と同様、PCA9535を1つ使うと16本のGPIOが増やせます。A2〜0の接続を変えることで異なるスレーブアドレスに設定することができるのもMCP23017と同様です。
試しにP07〜04にスイッチ、P03〜02は空き、P01〜00にLEDを接続した例を示します。 PCA9535のGPIOポートはPullUp抵抗を一切内蔵していないので、スイッチを接続する場合は必ずPullUpなどの処理が必要です。

ソフトウェア環境
ライブラリもどきとして、Arduino上に以下のようなアクセス関数と定義を実装しました。レジスタアドレスの定義以外はMCP23017と同じです。
もしこのページのコードをそのままコピペして使いたい場合はコメント部のURLを削除せずご利用くださいませ。商業用途でも構いません。サポートはありませんけどね…。
#include <Wire.h>
#define PCA9535_INPORT0 0x00
#define PCA9535_INPORT1 0x01
#define PCA9535_OUTPORT0 0x02
#define PCA9535_OUTPORT1 0x03
#define PCA9535_POLINV0 0x04
#define PCA9535_POLINV1 0x05
#define PCA9535_CONFIG0 0x06
#define PCA9535_CONFIG1 0x07
//---------------------------------------------------------
// I2CバスにWriteアクセスする
//---------------------------------------------------------
//[Arguments]
// slave_addr : スレーブデバイスのアドレス
// device_addr : デバイス内部のレジスタアドレス
// data : 書き込むデータ
//[Returns]
// なし
//--------------- https://ss1.xrea.com/tmct.s1009.xrea.com/
void i2cWriteByte(int slave_addr, int device_addr, int data) {
Wire.beginTransmission(slave_addr);
Wire.write(device_addr);
Wire.write(data);
Wire.endTransmission();
}
//---------------------------------------------------------
// I2CバスにReadアクセスする
//---------------------------------------------------------
//[Arguments]
// slave_addr : スレーブデバイスのアドレス
// device_addr : デバイス内部のレジスタアドレス
//[Returns]
// unsigned char : 読みだしたデータ
//--------------- https://ss1.xrea.com/tmct.s1009.xrea.com/
unsigned char i2cReadByte(int slave_addr, int device_addr) {
unsigned char r;
Wire.beginTransmission(slave_addr);
Wire.write(device_addr);
Wire.endTransmission();
Wire.requestFrom(slave_addr, 1);
if (Wire.available()) r = Wire.read();
Wire.endTransmission();
return(r);
}
たとえば上記に挙げた回路で使う場合は以下のように使います。
unsigned char work;
//---------------------------------------------------------
// 初期化
//---------------------------------------------------------
// 出力初期値を設定する
// 各ビット 1 = High, 0 = Low になります
// 入力に設定するビットは何でも構いませんが初期値の0に設定します
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x00); // P0[7:0] = 'LLLL_LLLL'
i2cWriteByte(0x20, PCA9535_OUTPORT1, 0x00); // P1[7:0] = 'LLLL_LLLL'
// 入力論理を設定する
// 各ビット 1 = 負論理, 0 = 正論理 になります
// 出力に設定するビットは何でも構いませんが初期値の0に設定します
i2cWriteByte(0x20, PCA9535_POLINV0, 0x00); // P0[7:0] = 全ビット正論理
i2cWriteByte(0x20, PCA9535_POLINV1, 0x00); // P1[7:0] = 全ビット正論理
// 入出力方向を設定する
// 各ビット 1 = 入力, 0 = 出力 になります
// 出力ポートの場合、出力に設定した瞬間に出力初期値で設定した論理レベルが出力されます
i2cWriteByte(0x20, PCA9535_CONFIG0, 0xfc); // P0[7:2] = 入力, P0[1:0] = 出力
i2cWriteByte(0x20, PCA9535_CONFIG1, 0x00); // P0[7:0] = 全ビット出力
//---------------------------------------------------------
// LEDを点灯
//---------------------------------------------------------
// P00に繋いだLEDを点灯
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x01); // P0[7:0] = 'LLLL_LLLH'
delay(300); // 300ms wait
// P01に繋いだLEDを点灯
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x02); // P0[7:0] = 'LLLL_LLHL'
delay(300); // 300ms wait
// P00,P01に繋いだLEDを点灯
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x03); // P0[7:0] = 'LLLL_LLHH'
delay(300); // 300ms wait
// LEDを消灯
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x00); // P0[7:0] = 'LLLL_LLLL'
delay(300); // 300ms wait
//---------------------------------------------------------
// スイッチの状態を取得
//---------------------------------------------------------
work = i2cReadByte(0x20, PCA9535_INPORT0); // P0[7:0]の状態を取得
if ((work & 0x10) == 0x10) {
// P04に繋いだスイッチが開放されている
}
else {
// P04に繋いだスイッチが押されている
}
if ((work & 0x20) == 0x20) {
// P05に繋いだスイッチが開放されている
}
else {
// P05に繋いだスイッチが押されている
}
if ((work & 0x40) == 0x40) {
// P06に繋いだスイッチが開放されている
}
else {
// P06に繋いだスイッチが押されている
}
if ((work & 0x80) == 0x80) {
// P07に繋いだスイッチが開放されている
}
else {
// P07に繋いだスイッチが押されている
}
ちなみに未使用の端子は以下のいずれかの処理を行うようにしましょう。とくにPCA9535はプログラムからOFF/ON可能な内蔵PullUp抵抗がないため注意が必要です。
- PullDownして入力端子にする。
※上記の例ではこの処理を行っています。PCA9535では最も推奨されます。 - VSSかVDDに直結する。
(※非推奨)
2.は制御がバグったときに大電流が流れてICが壊れてしまう可能性があるのでお勧めしません。
また、INT端子は必ずPullUpかPullDownしましょう。INT端子を使用する場合は4.7kΩ〜10kΩ程度でPullUp、使用しない場合は10kΩ〜100kΩでPullDownすると良いでしょう。
姉妹ICのPCA9555はPullUpを内蔵しています。
こちらは100kΩのVCC PullUp抵抗を内蔵しているので外付けPull Upする手間が省けますが、100kΩという抵抗値はPullUp抵抗としてはかなり弱いです。
人間が触れる部分(操作パネルや表示パネルなど)に使用すると静電気によって誤動作する恐れがあるためこのICはお勧めしません。多少部品が増えてもPCA9535+外部PullUpが安定です。
もうちょっと詳しく知る
上記の例で何をやっているのか理解するためにも、もう少し詳しく解説していきます。
初期化する
MCP23017と同様、I/Oエキスパンダは初期化しないと期待通りに動いてくれません。以下のような順で初期化します。
- 出力初期値を設定する
OUTPORTレジスタに初期値を書き込みます。
入力に設定するビットの初期値は何でも構いません。 - 入力論理を設定する
POLINVレジスタに入力ポートの正論理・負論理の設定をします。
出力に設定するビットの値は何でも構いませんが0(正論理)に設定します。 - 入出力方向を設定する
CONFIGレジスタに入出力の方向を設定します。
ネット上のコードではいきなり入出力設定をしているケースがほとんどですが、その場合だと外部に接続している負論理の回路が誤動作することがあるためお勧めしません。上記のようにきちんと初期値を設定してから入出力を切り替えるようにしましょう。
初期化のコード例を以下に示します。I2Cスレーブアドレスは[A2][A1][A0]=‘000’(‘010_0000’=0x20)を想定しています。もちろん設定値などは回路に応じて変更が必要です。
// 出力初期値を設定する
// 各ビット 1 = High, 0 = Low になります
// 入力に設定するビットは何でも構いませんが初期値の0に設定します
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x00); // GPA[7:0] = 'LLLL_LLLL'
i2cWriteByte(0x20, PCA9535_OUTPORT1, 0x00); // GPB[7:0] = 'LLLL_LLLL'
// 入力論理を設定する
// 各ビット 1 = 負論理, 0 = 正論理 になります
// 出力に設定するビットは何でも構いませんが初期値の0に設定します
i2cWriteByte(0x20, PCA9535_POLINV0, 0x00); // P0[7:0] = 全ビット正論理
i2cWriteByte(0x20, PCA9535_POLINV1, 0x00); // P1[7:0] = 全ビット正論理
// 入出力方向を設定する
// 各ビット 1 = 入力, 0 = 出力 になります
// 出力ポートの場合、出力に設定した瞬間に出力初期値で設定した論理レベルが出力されます
i2cWriteByte(0x20, PCA9535_CONFIG0, 0x00); // P0[7:2] = 全ビット出力
i2cWriteByte(0x20, PCA9535_CONFIG1, 0xff); // P0[7:0] = 全ビット入力
スレーブアドレスはA2・A1・A0の端子状態によって以下のように変動します。(1=VCCに接続,0=GNDに接続)
偶然か移植性を考慮しているのか、MCP23017とまったく同じです。
| A2 | A1 | A0 | スレーブアドレス |
|---|---|---|---|
| 0 | 0 | 0 | ‘010_0000’=0x20 |
| 0 | 0 | 1 | ‘010_0001’=0x21 |
| 0 | 1 | 0 | ‘010_0010’=0x22 |
| 0 | 1 | 1 | ‘010_0011’=0x23 |
| 1 | 0 | 0 | ‘010_0100’=0x24 |
| 1 | 0 | 1 | ‘010_0101’=0x25 |
| 1 | 1 | 0 | ‘010_0110’=0x26 |
| 1 | 1 | 1 | ‘010_0111’=0x27 |
入力ポートの状態を取得する
入力ポートの状態はINPORTレジスタを読むことで取得することができます。以下に例を示します。
int a, b;
a = (int)i2cReadByte(0x20, PCA9535_INPORT0); // P0[7:0]の状態を取得
b = (int)i2cReadByte(0x20, PCA9535_INPORT1); // P1[7:0]の状態を取得
ICの仕様上、ポート単位(8ビット単位)でないと状態を取得することができません。1ビットずつ判定したい場合は以下のようにビット演算でビットマスクして判定することができます。
// 0x40(0100_0000)と取得値aをAnd演算し、結果が0x40ならP0[6]='1'です
if ((a & 0x40) == 0x40) {
// P0[6]='1'の場合
}
else {
// P0[6]='0'の場合
}
// 0x02(0000_0010)と取得値bをAnd演算し、結果が0x02ならP1[1]='1'です
if ((b & 0x02) == 0x02) {
// P1[1]='1'の場合
}
else {
// P1[1]='0'の場合
}
複数ビットをまとめて判定したい場合…たとえばP0[5:3]にDipスイッチを接続して設定状態によって処理を分岐したい場合は以下のように記述することができます。
// 0x38(0011_1000)と取得値aをAnd演算し、結果によって処理を分岐します
switch (a & 0x38) {
case 0x00: // P0[5:3] = '000'
break;
case 0x08: // P0[5:3] = '001'
break;
case 0x10: // P0[5:3] = '010'
break;
case 0x18: // P0[5:3] = '011'
break;
case 0x20: // P0[5:3] = '100'
break;
case 0x28: // P0[5:3] = '101'
break;
case 0x30: // P0[5:3] = '110'
break;
case 0x38: // P0[5:3] = '111'
break;
default: // 上記のどれでもない場合(ありえないが)
break;
}
なお、INPORTレジスタから読まれる値はPOLINVレジスタ設定(正負論理設定)の影響を受けるので負論理に設定している場合は気をつけましょう。
出力ポートの状態を設定する
出力ポートの状態はOUTPORTレジスタに値を書き込むことで設定することができます。以下に例を示します。
入力に設定しているビットには何を書き込んでも影響しません。
i2cWriteByte(0x20, PCA9535_OUTPORT0, 0x12); // P0[7:0]の出力状態を'0001_0010'(LLLH_LLHL)に設定する
i2cWriteByte(0x20, PCA9535_OUTPORT1, 0x34); // P1[7:0]の出力状態を'0011_0100'(LLHH_LHLL)に設定する
ICの仕様上、ポート単位(8ビット単位)でないと状態を設定することができません。1ビットずつ設定したい場合は以下のようにいったん現在の設定値を読み、ビット演算でビットマスク・ビット加算した結果を書き戻すことで設定します。
int a, b;
a = (int)i2cReadByte(0x20, PCA9535_OUTPORT0); // P0[7:0]の状態を取得
// たとえばP0[5]だけを0に設定したい場合、
// 5ビット目だけが0の値'1101_1111'=0xdfとAnd演算することで該当ビットだけ0にします
a &= 0xdf;
// 演算した値を書き戻して設定します
i2cWriteByte(0x20, PCA9535_OUTPORT0, (int)a);
b = (int)i2cReadByte(0x20, PCA9535_OUTPORT1); // P1[7:0]の状態を取得
// たとえばP1[7]だけを1に設定したい場合、
// 7ビット目だけが1の値'1000_0000'=0x80とOr演算することで該当ビットだけ1にします
b |= 0x80;
// 演算した値を書き戻して設定します
i2cWriteByte(0x20, PCA9535_OUTPORT1, (int)b);
主なハードウェア仕様と注意事項
主なハードウェア仕様と注意事項を以下に載せます。
スイッチやLEDを繋ぐ程度ならあまり問題になりませんが、そもそも作ろうとしている物に使えない場合もあるのでうまく動かない場合は下記も参照してみてください。
| 項目 | 仕様 |
|---|---|
| 動作電源* | 2.3V〜5.5V |
| I/O本数 | 16本 ※入出力の方向はプログラムから自由に変更できます。 ※電源ONおよびリセット直後は全部入力端子になります。 ※プログラムでOFF/ON可能なPullUp抵抗はありません。 |
| 入力許容電圧(全端子)* | -0.5V〜+5.5V ※3.3V電源で動作させているときも5V系の信号を入力できます。I2Cバスも同様(ただしメーカーは非推奨)。 ※電源OFF時に信号が印加されても誤動作しません。 |
| 出力電流* | Highレベル・Lowレベルとも50mA(±0.05A) ※ただし全ピンの合計がLow側合計250mA・High側合計160mAを超えてはなりません。 |
| Highレベルとして認識する最低電圧 | 電源電圧の70% 5.0V動作時: 3.5V 3.3V動作時: 2.31V ※5V動作時に3.3V系のI2Cバスに接続した場合、ギリギリ動作しません。 ※3.3V動作時に5V系のI2Cバスに接続できます(ただしメーカー非推奨)。 |
| Lowレベルとして認識する最高電圧 | 電源電圧の30% 5.0V動作時: 1.5V 3.3V動作時: 0.99V |
| I2C仕様 | - 100KHz - 400KHz |
| スレーブアドレス | [A2][A1][A0]端子の状態により8種類から選択可能 ※上位4bitは0100固定、下位3bitを[A2][A1][A0]で設定できます。 |
| 割り込み出力 | 可能 Nch.オープンドレイン出力 ※フラグクリア条件にバグあり(後述) |
* この値を超えて使用した場合、ICが焼損する可能性があります。何事もなく正常に動作するように見えることもありますが、IC内部にダメージが蓄積されていくためいずれ正常に動作しなくなります。
とくに気をつけないといけないのが 入力許容電圧 と 出力電流 です。接続先と電源系統を合わせないといけませんし、大電流を消費するもの(たとえばモーター・リレー・照明用LEDなど)を直結することはできません。
各ピンの仕様
一応各ピンの仕様も書いておきます。ピン番号はSSOPパッケージのものです。
| ピン名称 | ピン番号 | 入出力 | 機能 |
|---|---|---|---|
| ~INT | 1 | 出力 | 割り込み信号出力です。Nch.オープンドレイン出力です。 IC外部でPullUpまたはPullDownが必要です。 |
| A1 | 2 | 入力 | I2Cスレーブアドレス設定端子(1ビット目)です。 必ずVCCまたはVSSに接続してください。未接続のままだと誤動作します。 |
| A2 | 3 | 入力 | I2Cスレーブアドレス設定端子(2ビット目)です。 必ずVCCまたはVSSに接続してください。未接続のままだと誤動作します。 |
| P00 | 4 | 入出力 | GPIO端子0です。 入出力方向はプログラムから自由に変更できます。 信号状態の変化に伴ってINT端子に割り込み信号を出力するよう設定できます。 電源電圧に関わらず5.5Vまでの信号入力が許容されます。 |
| P01 | 5 | 入出力 | (同上) |
| P02 | 6 | 入出力 | (同上) |
| P03 | 7 | 入出力 | (同上) |
| P04 | 8 | 入出力 | (同上) |
| P05 | 9 | 入出力 | (同上) |
| P06 | 10 | 入出力 | (同上) |
| P07 | 11 | 入出力 | (同上) |
| GND | 12 | GND | GNDを接続してください。 |
| P10 | 13 | 入出力 | GPIO端子1です。 入出力方向はプログラムから自由に変更できます。 信号状態の変化に伴ってINT端子に割り込み信号を出力するよう設定できます。 電源電圧に関わらず5.5Vまでの信号入力が許容されます。 |
| P11 | 14 | 入出力 | (同上) |
| P12 | 15 | 入出力 | (同上) |
| P13 | 16 | 入出力 | (同上) |
| P14 | 17 | 入出力 | (同上) |
| P15 | 18 | 入出力 | (同上) |
| P16 | 19 | 入出力 | (同上) |
| P17 | 20 | 入出力 | (同上) |
| A0 | 21 | 入力 | I2Cスレーブアドレス設定端子(0ビット目)です。 必ずVCCまたはVSSに接続してください。未接続のままだと誤動作します。 |
| SCL | 22 | 入力 | I2Cクロック入力端子です。必ずVCCでPullUpしてください。 |
| SDA | 23 | 入出力 | I2Cデータ入出力端子です。必ずVCCでPullUpしてください。 |
| VCC | 24 | 電源入力 | 電源入力です。 |
割り込みを使ってみる
PCA9535は端子の状態が変化した瞬間、その変化を示す信号(割り込み信号)を出力することができます。この機能を利用すると、ソフト的に変化を監視しなくてもハードが勝手に比較して結果を通知してくれるのでプログラム側の負担が少なくなります。
割り込みの発生条件
MCP23017の場合は割り込み設定などありましたが、PCA9535はありません。入力に設定されているGPIO端子のうち、いずれかの端子がLow→HighまたはHigh→Lowに変化した場合にINT端子がLowになります。
自分が出力する信号には反応しませんが、入出力の方向を切り替えた場合は割り込みが発生してしまう場合があります。
割り込みがクリアされる条件
いったんINT端子がLowになった後、割り込みをクリア(INT端子の状態を割り込み前の状態に戻す)にはINPORTレジスタを読みます。
ただしP00〜P07とP10〜P17はIC内部で割り込みフラグを別々に持っているため、完全にクリアするには両方のINPORTレジスタを読む必要があります。
// 割り込みをクリアする
i2cReadByte(0x20, PCA9535_INPORT0); // P0系をクリア
i2cReadByte(0x20, PCA9535_INPORT1); // P1系をクリア
が、このICにはバグがあり、 INPORT0(レジスタアドレス0x00)に読み書きを行った後に他のレジスタにアクセスしていない状態で、ほかのスレーブデバイスに読み取りアクセスを行い、相手のデバイスがアクノリッジ(ACK)を返したとき に割り込み状態が勝手にクリアされてしまいます。
……ややこしいですね。(笑)
たとえばINPORT0レジスタを読んだ状態のまま他のスレーブデバイスにガンガンアクセスしていると、割り込みフラグが勝手にクリアされてしまいタイミングによっては割り込みをトラップできないかもしれない、ということです。
これを回避するにはINPORT0レジスタを読み書きした後は別のレジスタを空読みしてくれ…とICの仕様書には書いてあります。
それでも割り込みが発生するタイミングによっては割り込みがトラップできない可能性は残るので、とくに同じI2Cバスに複数のスレーブデバイスをぶら下げて使用する場合はこのICの割り込み機能は使わない方が良いでしょう。
まとめ
PCA9535の使い方について記載しました。
割り込みと内蔵PullUp有無以外はMCP23017とソフト的な互換性は高いので、もし安価なI/Oエキスパンダーを探している場合は検討してみてください。