MCP23017でI/Oポートを増設する（ラズパイ,M5StickCPlusなど）

# 目的
　マイコンのI/OポートをMCP23017で増設してみる。
# ハード・ソフト
- Mac Studio(2022)
- M5StickCPlus
- MCP23017
- VSCode(PlatformIO)

# MCP23017とは？？
　MCP23017とはI2C通信によってI/Oポートの増設及び制御を行えるIC（I/Oエキスパンダ）です。
今回は以下のサイトを参考にして実装してみました。

https://ss1.xrea.com/tmct.s1009.xrea.com/doc/ta-ja-7e5g03.html

データシートは以下のサイトに載っています。

https://akizukidenshi.com/download/ds/microchip/mcp23017_mcp23s17.pdf

![スクリーンショット 2023-08-22 17.59.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3403549/d939c31e-0a86-f46d-ba9d-d27bc8e1661f.png)

こちらはデータシートからの引用になりますが、I/Oポートとして機能するのはGPA[0:7]とGPB[0:7]の計16ポートになります。

# 回路
　今回は以下のような配線で実装しました。

![スクリーンショット 2023-08-22 18.42.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3403549/b31de53e-45d1-2994-c0a3-cfb756e23dc4.png)

この状態だと LEDはMCP23017からの信号を電源として使用しているので実用的ではありませんがご了承ください。

使用しているマイコンはM5StickCPlusで、I2C通信によってMCP23017を扱っています。

MCP23017側のA0〜２に関してはI2Cのアドレスを設定する部分ですので今回は全てグランドのデフォルト状態（0x20）の状態で実装します。

# コード
　コードはVSCode内の拡張機能「PlatformIO」を使用して書き込んでいます。こちらの記事を参考にセットアップしましたので参考にどうぞ。

https://qiita.com/ume-bar/items/234014927ae0857baf51

M5StickCPlusのコードについて、MCP23017を用いた解説を行っていた以下の記事を参考に書かせていただいております。（ほぼ変更なし）

~~~c++
#include <Wire.h>
#include <M5StickCPlus.h>

#define MCP23017_IODIRA     0x00
#define MCP23017_IODIRB     0x01
#define MCP23017_IPOLA      0x02
#define MCP23017_IPOLB      0x03
#define MCP23017_GPINTENA   0x04
#define MCP23017_GPINTENB   0x05
#define MCP23017_DEFVALA    0x06
#define MCP23017_DEFVALB    0x07
#define MCP23017_INTCONA    0x08
#define MCP23017_INTCONB    0x09
#define MCP23017_IOCON      0x0a
#define MCP23017_GPPUA      0x0c
#define MCP23017_GPPUB      0x0d
#define MCP23017_INTFA      0x0e
#define MCP23017_INTFB      0x0f
#define MCP23017_INTCAPA    0x10
#define MCP23017_INTCAPB    0x11
#define MCP23017_GPIOA      0x12
#define MCP23017_GPIOB      0x13
#define MCP23017_OLATA      0x14
#define MCP23017_OLATB      0x15
~~~
宣言部分になりますが、ほぼMCP23017関連の部分です。

~~~c++
//---------------------------------------------------------
//  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);
}
~~~
この箇所は参考にさせていただいた記事のまま、i2cによる書き込み、読み取り関数が書かれています。

~~~c++
void setup(){

  Wire.begin(0, 26); // I2C通信端子指定(SDA, SCL)
  M5.begin();// m5の初期化
}
~~~
　この部分ではM5StickCPlusのI2C通信用端子の設定、初期化を行なっています。（初期化を行わなかった際に５Vの出力が得られませんでした。）

~~~c++
void loop(){
  // unsigned char work;

  //---------------------------------------------------------
  //  初期化
  //---------------------------------------------------------
  // 出力初期値を設定する
  // 各ビット 1 = High, 0 = Low になります
  // 入力に設定するビットは何でも構いませんが初期値の0に設定します
  i2cWriteByte(0x20, MCP23017_OLATA, 0x00);   // GPA[7:0] = 'LLLL_LLLL'
  i2cWriteByte(0x20, MCP23017_OLATB, 0x00);   // GPB[7:0] = 'LLLL_LLLL'

  // 入力論理を設定する
  // 各ビット 1 = 負論理, 0 = 正論理 になります
  // 出力に設定するビットは何でも構いませんが初期値の0に設定します
  i2cWriteByte(0x20, MCP23017_IPOLA, 0x00);   // GPA[7:0] = 全ビット正論理
  i2cWriteByte(0x20, MCP23017_IPOLB, 0x00);   // GPB[7:0] = 全ビット正論理

  // 入力PullUpを設定する
  // 各ビット 1 = PullUp ON, 0 = PullUp OFF になります
  // 出力に設定するビットは何でも構いませんが初期値の0に設定します
  i2cWriteByte(0x20, MCP23017_GPPUA, 0x00);   // GPA[7:0] = 内部PullUp OFF
  i2cWriteByte(0x20, MCP23017_GPPUB, 0xff);   // GPB[7:0] = 内部PullUp ON

  // 入出力方向を設定する
  // 各ビット 1 = 入力, 0 = 出力 になります
  // 出力ポートの場合、出力に設定した瞬間に出力初期値で設定した論理レベルが出力されます
  i2cWriteByte(0x20, MCP23017_IODIRA, 0x00);  // GPA[7:0] = 出力
  i2cWriteByte(0x20, MCP23017_IODIRB, 0xff);  // GPB[7:0] = 全ビット入力

  M5.Axp.EnableCoulombcounter();// 5V出力開始

  //---------------------------------------------------------
  //  LEDを点灯
  //---------------------------------------------------------
  // LEDを点灯
  i2cWriteByte(0x20, MCP23017_OLATA, 0xff);   // GPA[7:0] = 'HHHH_HHHH'
  delay(300);                                 // 300ms wait

  // LEDを消灯
  i2cWriteByte(0x20, MCP23017_OLATA, 0x00);   // GPA[7:0] = 'LLLL_LLLL'
  delay(300);                                 // 300ms wait


}
~~~
　ほとんどの部分が参考にさせていただいた記事のままになっていますが、入出力設定やLEDの点灯部分で改変をした箇所があります。
（GPAポートを全て出力用にしてあり、LEDが繋がっていない箇所もプルアップしていますが実装する際には設定を見直すことをお勧めします。）


これにて複数のLEDが点滅を繰り返すだけのコードが完成しました。