はじめに
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個まで接続可能です(アドレス 0x20 〜 0x27)。
理論上、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(デフォルト)
入出力ループバック配線
ここが今回のキモです。
- Arduino D0〜D7 を MCP GPB0〜GPB7 (Pin 1〜8) に接続。
- 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特有の 「救済儀式」 を行います。
- USBを繋いだままにする。
- Arduino上のリセットボタンを素早く「2回」カチカチッと押す。
- 「L」LEDがフワッフワッと明滅し始めたら成功(DFUモード)。
- この状態で書き込めば復活します。
R4ユーザーならこの技は必須スキルなので、覚えておきましょう。
7. まとめ
今回はMCP23017を使って、Arduino Uno R4の入出力を拡張し、さらにループバックテストでその動作を詳細に検証しました。
- 16本のピン増設はI2Cを使えば簡単。
- アドレス設定で複数個の同時使用も可能。
- ポート単位(8bit/16bit)での一括読み書きが高速化の鍵。
このICを使いこなせれば、巨大なLEDマトリクス、自作キーボード、あるいは複雑なロボット制御など、Arduino Uno R4の可能性は無限に広がります。
「ピンが足りない?」 これからは、そんな悩みとはサヨナラです。MCP23017で、あなたのArduinoを最強にアップグレードしてあげましょう!