はじめに

Arduinoを使っていて、誰もが一度はぶつかる壁。それが 「GPIO不足」 です。

「ここにLCDをつけて、ここにセンサーを5個並べて、さらにステータスLEDを…」 そう考えた瞬間、Arduino Unoのピンソケットを見つめて絶望したことはありませんか?Arduino Uno R4はCortex-M4搭載で処理能力は爆上がりしましたが、物理的な足の数(ピン数)は増えていません。

今回は、そんなピン不足の悩みをたった2本の信号線で解決するI/Oエキスパンダの決定版、「MCP23017」 を徹底解説します。

さらに、単にLチカするだけでは面白くないので、「全ポート連動ループバックテスト」 という、ちょっと通(ツウ)な実験を行い、入力と出力の仕組みを完全に理解することを目指します。


1. 今回の主役:MCP23017とは何か?

MCP23017は、Microchip社が製造する 16-bit I/O Expander です。電子工作界隈では「定番中の定番」ICですが、その実力は侮れません。

2. なぜ MCP23017 が選ばれるのか?

「ピンを増やすだけなら、安いシフトレジスタ(74HC595など)でいいのでは?」 そう思う方もいるかもしれません。しかし、MCP23017が電子工作から産業用途まで幅広く愛されるのには、明確な技術的理由があります。

① 「入力」と「出力」を自由に切り替え可能

シフトレジスタの多くは「出力専用」または「入力専用」ですが、MCP23017はArduinoのGPIOと同様、ピンごとに INPUT / OUTPUT を設定可能です。 「半分を入力スイッチ、半分をLED制御」といった柔軟な使い方が、たった一つのチップで完結します。

② 割り込み(Interrupt)機能が優秀

これが最大の強みです。 通常、スイッチの状態を監視するには、Arduino側で常に「押された?」と確認し続ける(ポーリング)必要がありますが、これはCPUリソースの無駄遣いです。 MCP23017は、入力ピンの状態が変化した瞬間に 「INTピン」から信号を出してArduinoに知らせる機能 を持っています。これにより、Arduinoは普段は別の計算をしておき、スイッチが押された時だけ反応する、といった高度な制御が可能になります。

③ 内部プルアップ抵抗(100kΩ)を内蔵

地味ですが、回路を組む際に最強の機能です。 スイッチを接続する場合、通常は外付けのプルアップ抵抗が必要ですが、MCP23017はコマンド一つで内部の100kΩプルアップ抵抗をONにできます。 これにより、16個のスイッチをつける際、16本の抵抗パーツとハンダ付け作業が不要になります。配線が劇的にスッキリします。

④ 5V完全対応 & ハイパワー駆動

最近のI2Cデバイスは3.3V専用が増えていますが、MCP23017は 2.7V〜5.5V の広い電圧範囲で動作します。つまり、Arduino Uno R4(5V系)とレベル変換なしで直結可能です。 また、出力電流は 1ピンあたり25mA。これはArduinoのGPIOと同等の強さで、LED程度ならトランジスタなしで直接駆動できます。

⚠️【重要】データシートの罠:総電流量に注意!

「1ピン25mAなら、16本で400mA流せる?」と考えるとチップが燃えます。 データシートによると、VSSピン(GND)に流せる電流の合計は 最大150mA です。 LEDを16個全点灯させる場合は、1個あたり数mA(抵抗を大きめにする)に抑える設計が必要です。ここがプロとアマチュアの分かれ道です。

⑤ アドレス設定で無限の拡張性

I2Cバス上に最大8個まで接続可能です(アドレス 0x200x27)。 理論上、16ピン × 8個 = 128ピン まで増設可能。Arduino Megaをも凌駕するIO数を、たった2本の線で実現できます。

3. 実験の構成:ただのLチカではない「ループバックテスト」

今回は、技術的な信頼性を担保するためのテスト手法を採用します。 MCP23017の16本のピンを以下のように分割します。

  • Port A (8本): 出力用(LEDを点灯)
  • Port B (8本): 入力用(信号を受け取る)

そして、「Arduino本体から出した信号をMCPのPort Bで受け取り、その内容を即座にPort AにコピーしてLEDを光らせる」 という構成にします。

なぜこんなことをするのか?

通常、入力のテストにはスイッチを使いますが、チャタリング(接点のブレ)や配線ミスが混入しがちです。 信号源として「正確無比なArduinoの出力」を使うことで、**「MCP23017が正しく信号を読み取れているか」「正しく出力できているか」**を同時に、かつ高速に検証できるのです。これは実際の組み込み開発でも行われる「ループバック(折り返し)テスト」の応用です。


4. ハードウェア接続と「アドレス設定」の掟

ブレッドボード上での配線は以下の通りです。Arduino Uno R4はI2C周りが強化されているので接続はスムーズです。

基本配線

Arduino Uno R4 MCP23017 (DIP28) 役割
5V VDD (Pin 9) 電源
GND VSS (Pin 10) グランド
SDA SDA (Pin 13) I2Cデータ
SCL SCL (Pin 12) I2Cクロック
5V RESET (Pin 18) リセット解除(重要!)

⚠️ 技術的注意点: MCP23017の RESETピンは「Lowアクティブ」 です。つまり、ここを繋ぎ忘れて電圧が不定になると、勝手にリセットがかかり動作しません。必ず5Vに接続(プルアップ)してください。

I2Cアドレスの設定 (Pin 15, 16, 17)

I2Cデバイスは「アドレス」で識別されます。MCP23017は A0, A1, A2 ピンのHigh/Lowの組み合わせでアドレスが決まります。

今回はシンプルに全てGNDに接続します。

  • A0, A1, A2 → 全て GND
  • アドレス: 0x20 (デフォルト)

入出力ループバック配線

ここが今回のキモです。

  1. Arduino D0〜D7MCP GPB0〜GPB7 (Pin 1〜8) に接続。
  2. MCP GPA0〜GPA7 (Pin 21〜28) にLED(と抵抗)を接続。

5. ライブラリの使い方:基本から「一括制御」まで

今回は標準的な Adafruit MCP23017 Arduino Library を使用します。 このライブラリには、初心者向けの「Arduino互換モード」と、上級者向けの「高速ポート操作モード」の2つの顔があります。

① インストールと初期化

まずはインスタンスを作成し、setup() 内で初期化します。

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

void setup() {
  // I2Cアドレスを指定して開始(A0-A2がGNDなら0x20)
  if (!mcp.begin_I2C(0x20)) {
    Serial.println("Error.");
    while (1);
  }
}

② ピン番号の「罠」を理解する

ライブラリ内では、MCP23017の16本のピンは 0番〜15番 の通し番号で管理されます。データシートのピン名(GPA0など)との対応関係を頭に入れておく必要があります。

  • 0 〜 7番: Port A (GPA0 〜 GPA7)
  • 8 〜 15番: Port B (GPB0 〜 GPB7)

③ 基本的な使い方(1ピン操作)

Arduino標準の関数と同じ感覚で使えます。LEDを1つつけたり、スイッチを1つ読むだけならこれで十分です。

// 0番ピン(GPA0)を出力に設定
mcp.pinMode(0, OUTPUT);

// 8番ピン(GPB0)を入力(プルアップ有効)に設定
// ※これで外付け抵抗なしでスイッチを繋げます!便利!
mcp.pinMode(8, INPUT_PULLUP); 

// 書き込みと読み込み
mcp.digitalWrite(0, HIGH);
int val = mcp.digitalRead(8);

④ 【重要】「一括読み書き」で高速化する

ここが今回のテストのキモです。 I2C通信は、CPU内部の処理に比べると非常に低速です。mcp.digitalWrite を16回ループさせると、通信が16回発生してしまい、動作が目に見えて遅くなることがあります。

そこで、16本分(または8本分)を1回の通信でまとめて処理する関数を使います。

まとめて読む: readGPIOAB()

16本のピン状態を uint16_t(16ビット整数)として一度に取得します。

// 1回の通信で全ピンの状態を取得
uint16_t allPins = mcp.readGPIOAB();

// ビット演算で特定のピンの状態を取り出す
// 例:10番ピン(GPB2)の状態を知りたい場合
int pin10_state = (allPins >> 10) & 1;

まとめて書く: writeGPIOAB(value)

16本のピン状態を一度に書き換えます。

// 0x00FF = 00000000 11111111 
// 上位8ビット(Port B)は全てLOW、下位8ビット(Port A)は全てHIGHにする
mcp.writeGPIOAB(0x00FF);

解説: 今回のサンプルコードで readGPIOAB() を使用しているのは、Arduinoからの高速なパルス信号を取りこぼさずにキャッチするためです。

⑤ 割り込み(Interrupt)の設定

(今回は使いませんが)スイッチ入力を作る際に便利な割り込み設定も簡単です。

// 8番ピンが変化したら、MCPのINTピン信号を出す設定
mcp.setupInterrupts(true, false, LOW); // ミラーリング, オープンドレイン, 極性
mcp.attachInterrupt(8, CHANGE);        // ピン8の変化を監視

ソースコード解説

このプログラムは、Arduino Uno R4のパワーとMCP23017の機能をフル活用します。

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

// Arduino側の送信ピン定義 (Uno R4のD0-D7を使用)
// 配列にしておくことでループ処理をスマートにします
const int arduinoPins[] = {0, 1, 2, 3, 4, 5, 6, 7};

void setup() {
  Serial.begin(115200);
  while (!Serial); // R4特有:シリアル接続待ち

  Serial.println(">>> MCP23017 System Test Initiated");

  // --- 1. MCP23017の初期化 ---
  // begin_I2C() 引数なしの場合はデフォルトの0x20アドレスを探しに行きます
  if (!mcp.begin_I2C()) {
    Serial.println("Fatal Error: MCP23017 not found. Check wiring!");
    while (1);
  }

  // --- 2. ポートの方向定義 ---
  // MCP23017のライブラリ仕様:
  // 0-7番ピン  = Port A (GPA0-7)
  // 8-15番ピン = Port B (GPB0-7)
  
  // Port A (0-7) -> LED駆動のため OUTPUT
  for (int i = 0; i <= 7; i++) {
    mcp.pinMode(i, OUTPUT);
  }

  // Port B (8-15) -> Arduinoからの信号監視のため INPUT
  // 必要に応じて INPUT_PULLUP も使えますが、今回はArduino側が確実にドライブするのでINPUTでOK
  for (int i = 8; i <= 15; i++) {
    mcp.pinMode(i, INPUT); 
  }

  // --- 3. Arduino側の準備 ---
  // MCPへ信号を送るホスト側の設定
  for (int i = 0; i < 8; i++) {
    pinMode(arduinoPins[i], OUTPUT);
  }
}

void loop() {
  static uint8_t counter = 0; // 0〜255をカウントアップする変数

  // --- Phase 1: Arduinoから信号を出力 ---
  // ビット演算を使って、counterの数値を8本のピンに展開します
  // 例: counterが 3 (00000011) の場合、Pin0とPin1だけHIGHにする
  for (int i = 0; i < 8; i++) {
    int state = (counter >> i) & 1; 
    digitalWrite(arduinoPins[i], state);
  }

  // 信号が安定するまでごくわずかに待機(高速なICですが念のため)
  delay(10);

  // --- Phase 2: MCP23017で入力状態を一括取得 ---
  // ここが重要! digitalReadを8回やるより、レジスタをガバっと読むほうが圧倒的に速い。
  // readGPIOAB() は16bit全てのピン状態を返します。
  uint16_t allPins = mcp.readGPIOAB(); 
  
  // 上位8bit (Port B) の部分だけを取り出します
  uint8_t inputVal = (allPins >> 8) & 0xFF;

  // デバッグ表示:正しく通信できているかシリアルモニタで確認
  Serial.print("Sent: "); Serial.print(counter);
  Serial.print(" \t Received: "); Serial.println(inputVal);

  // --- Phase 3: 受け取った値をそのまま出力ポートへ ---
  // 入力されたパターンと同じ形でLEDを光らせます。
  // ここでもビット演算で分解してセットします。
  for(int i=0; i<8; i++){
     int val = (inputVal >> i) & 1;
     mcp.digitalWrite(i, val); // Port A (0-7) に書き込み
  }
  
  counter++; // 次のパターンへ
  delay(200); // 目視確認用ウェイト
}

技術的な見どころ

コードの中で注目してほしいのは mcp.readGPIOAB() です。 初心者は mcp.digitalRead(8)mcp.digitalRead(9)… と書きがちですが、I2C通信はオーバーヘッド(通信の手間)が大きいため、1ピンずつ通信すると処理落ちの原因になります。 このように 「16ピン分まとめて読み書きする」 のが、IOエキスパンダを使いこなすプロの作法です。


6. Arduino Uno R4 でのトラブルシューティング

開発中に 「書き込みエラー (exit status 74)」 が出ることがあります。特に今回のようにGPIOに多くの配線をしていると、起動時の電流やノイズの影響で、Uno R4がUSBを見失うことがあります。

そんな時は、R4特有の 「救済儀式」 を行います。

  1. USBを繋いだままにする。
  2. Arduino上のリセットボタンを素早く「2回」カチカチッと押す
  3. 「L」LEDがフワッフワッと明滅し始めたら成功(DFUモード)。
  4. この状態で書き込めば復活します。

R4ユーザーならこの技は必須スキルなので、覚えておきましょう。


7. まとめ

今回はMCP23017を使って、Arduino Uno R4の入出力を拡張し、さらにループバックテストでその動作を詳細に検証しました。

  • 16本のピン増設はI2Cを使えば簡単。
  • アドレス設定で複数個の同時使用も可能。
  • ポート単位(8bit/16bit)での一括読み書きが高速化の鍵。

このICを使いこなせれば、巨大なLEDマトリクス、自作キーボード、あるいは複雑なロボット制御など、Arduino Uno R4の可能性は無限に広がります。

「ピンが足りない?」 これからは、そんな悩みとはサヨナラです。MCP23017で、あなたのArduinoを最強にアップグレードしてあげましょう!