M5StickCにGPIOを16本増やせるIO拡張モジュールを作ってみた。速度的にはSPI仕様のMCP23S17を使いたかったがM5StickCのIOピンが足りないので断念しI2C仕様のMCP23017を選択。I2C仕様でも1.7MHzまで行けるようなので出来るだけ高速化するためI2Cのプルアップ抵抗を1KにしオシロでSCL信号を確認してみるとM5StickCは最大でも700KHz程度が限界のようである。とりあえず1MHzを指定し実測してみると一回のR/Wに50us程度かかるようだ。SPIの10MHz指定なら数us程度なのでそれに比べるとかなり遅いがしょうがない。
遅い分、GPIOの読み込みを少しでも高速化するためにMCP23017のINT出力を有効利用できるように以前作成したライブラリを改良してみた。どういうことをしたかと言うと、GPIOの入力変化でINT出力がONになったのを検出してから実際のリードを行うようにしただけ。常時リードよりもタイミング的に最大で約50us程度は早く処理することが可能になる。って、そんなタイミングを気にする用途ってあまりないのかもしれないが...ちなみにこのモジュールもmacsbugさんのStart Adaptor機能付きとなっている。
ライブラリの仕様は、Arduino準拠であるが次の点が異なる。また、HW的にはパワーオンで全ポートが入力ポートとなるが、ライブラリ的には、pinModeで明確に入力指定する必要があることに注意。
・inputInvert() … 入力を反転させる。
・flush()…ポート出力/設定を行う。
・refresh()…ポート入力を行う。
特に違う点は、実際に入出力を行うためにはflush/refreshを呼び出す必要があるということ。なぜそうなっているかと言うとタイミング的に複数ピンを同時制御できるようにするため。ピン単位の処理だと入出力に50usかかると仮定した場合、複数ピンの制御を完了させるためにピン数x50usの時間がかかってしまうことになる。これが問題となるかどうかは周辺回路の仕様しだいであるが...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
【複数ポート同時出力の例】 [1] digitalWrite(GPA0, HIGH); [1] digitalWrite(GPA1, HIGH); [1] digitalWrite(GPA2, HIGH).flush(); -------1----- GPA0 ..OOOO GPA1 ..OOOO GPA2 ..OOOO -------1----- 【複数ポート順次出力の例】 [1] digitalWrite(GPA0, HIGH).flush(); [2] digitalWrite(GPA1, HIGH).flush(); [3] digitalWrite(GPA2, HIGH).flush(); -------123--- GPA0 ..OOOO GPA1 ...OOO GPA2 ....OO -------123--- |
ポート入力は、ある時点での入力状態を保持することで複数ポートの入力処理をタイミング的に適切に処理できるようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
【複数ポート同時入力の例】 -------1----- GPA0 ..OOOO GPA1 ...OOO GPA2 ....OO -------1----- [1] refresh().digitalRead(GPA0); ... HIGH [1] digitalRead(GPA1);...LOW [1] digitalRead(GPA2);...LOW 【複数ポート順次入力の例】 -------123-- GPA0 ..OOOO GPA1 ...OOO GPA2 ....OO -------123-- [1] refresh().digitalRead(GPA0);...HIGH [2] refresh().digitalRead(GPA1);...HIGH [3] refresh().digitalRead(GPA2);...HIGH |
【Lチカさせてみた】
【アップデート】
2019-08-05
INTピンによる入力処理の改良と、digitalWrite()の値をflush()なしでdigitalRead()できるように変更。
【サンプル・スケッチ(Lチカ)】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <M5StickC.h> #include "mcp23017.h" MCP23017 mcp(0, 26, 0, 36); // ADDR=0, SDA=G26, SCL=G0, INT=G36 void setup() { M5.begin(); // mcp.begin(); mcp.setClock(1000000L); // Max 700KHz ??? mcp.pinMode(MCP_GPA0, OUTPUT); mcp.pinMode(MCP_GPB0, INPUT_PULLUP); mcp.flush(); } void loop() { if (mcp.refresh().digitalRead(MCP_GPB0) == HIGH) mcp.digitalWrite(MCP_GPA0, !mcp.digitalRead(MCP_GPA0)); else mcp.digitalWrite(MCP_GPA0, LOW); mcp.flush(); delay(1000); } |
【ライブラリ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/* * MCP23017 Library * * Sasapea's Lab. * 2019-07-21 * * I2C Default Wiring * * [ESP8266] * GPIO14 ---> SCL * GPIO2 <--> SDA */ #ifndef MCP23017_H #define MCP23017_H #include "mcp23x17.h" class MCP23017 : public MCP23X17 { private: uint8_t _sda; uint8_t _scl; void init(void) override; void writeIO(uint8_t *buf, uint8_t len) override; void readIO(uint8_t *buf, uint8_t out_len, uint8_t in_len) override; public: MCP23017(uint8_t addr, uint8_t sda = 2, uint8_t scl = 14, uint8_t ioc = 0xFF); ~MCP23017(void); void end(void) override; void setClock(uint32_t frequency) override; }; #endif // MCP23017_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
/* * MCP23017 Library * * Sasapea's Lab. * 2019-07-21 */ #include <Wire.h> #include "mcp23017.h" MCP23017::MCP23017(uint8_t addr, uint8_t sda, uint8_t scl, uint8_t ioc) : MCP23X17(addr, ioc) { _sda = sda; _scl = scl; } MCP23017::~MCP23017(void) { } void MCP23017::init(void) { // // I2C Init // Wire.begin(_sda, _scl); setClock(400000); // 400KHz } void MCP23017::end(void) { pinMode(_sda, INPUT); pinMode(_scl, INPUT); } void MCP23017::setClock(uint32_t frequency) { Wire.setClock(frequency); } void MCP23017::writeIO(uint8_t *buf, uint8_t len) { Wire.beginTransmission(buf[0]); Wire.write(buf + 1, len - 1); Wire.endTransmission(); } void MCP23017::readIO(uint8_t *buf, uint8_t out_len, uint8_t in_len) { Wire.beginTransmission(buf[0]); Wire.write(buf + 1, out_len - 1); Wire.endTransmission(false); Wire.requestFrom(buf[0], in_len); while (Wire.available() < in_len) yield(); for (buf += out_len; in_len > 0; --in_len) *buf++ = (uint8_t)Wire.read(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
/* * MCP23x17 Library * * Sasapea's Lab. * 2019-07-21 */ #ifndef MCP23X17_H #define MCP23X17_H #include <Arduino.h> #include <stdint.h> #include <stdbool.h> // // GPIO Symbol's // #define MCP_GPA0 0 #define MCP_GPA1 1 #define MCP_GPA2 2 #define MCP_GPA3 3 #define MCP_GPA4 4 #define MCP_GPA5 5 #define MCP_GPA6 6 #define MCP_GPA7 7 #define MCP_GPB0 8 #define MCP_GPB1 9 #define MCP_GPB2 10 #define MCP_GPB3 11 #define MCP_GPB4 12 #define MCP_GPB5 13 #define MCP_GPB6 14 #define MCP_GPB7 15 class MCP23X17 { private: uint8_t _opcode; uint16_t _iodir[2]; uint16_t _ipol[2]; uint16_t _inten[2]; uint16_t _gppu[2]; uint16_t _olat[2]; uint16_t _gpio; bool _refresh; uint8_t _ioc_pin; virtual void init(void) = 0; virtual void writeIO(uint8_t *buf, uint8_t len) = 0; virtual void readIO(uint8_t *buf, uint8_t out_len, uint8_t in_len) = 0; void writeReg(uint8_t reg, uint16_t val); uint16_t readReg(uint8_t reg); uint16_t getBit(uint8_t pin); public: MCP23X17(uint8_t addr, uint8_t ioc = 0xFF); ~MCP23X17(void); void begin(void); virtual void end(void) = 0; virtual void setClock(uint32_t frequency) = 0; MCP23X17& pinMode(uint8_t pin, uint8_t mode); MCP23X17& inputInvert(uint8_t pin, uint8_t mode); MCP23X17& digitalWrite(uint8_t pin, uint8_t value); MCP23X17& flush(void); uint8_t digitalRead(uint8_t pin); MCP23X17& refresh(void); }; #endif // MCP23X17_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
/* * MCP23x17 Library * * Sasapea's Lab. * 2019-07-21 */ #include "mcp23x17.h" // // OPCODE // #define MCP_OPCODE 0x20 // // REGISTER'S (IOCON[BANK, SEQOP] = 0) // #define MCP_IODIR 0x00 #define MCP_IPOL 0x02 #define MCP_GPINTEN 0x04 #define MCP_DEFVAL 0x06 #define MCP_INTCON 0x08 #define MCP_IOCON 0x0A #define MCP_GPPU 0x0C #define MCP_INTF 0x0E #define MCP_INTCAP 0x10 #define MCP_GPIO 0x12 #define MCP_OLAT 0x14 // // IOCON bits // #define IOCON_INTPOL 0x02 #define IOCON_ODR 0x04 #define IOCON_HAEN 0x08 #define IOCON_DISSLW 0x10 #define IOCON_SEQOP 0x20 #define IOCON_MIRROR 0x40 #define IOCON_BANK 0x80 #define IOCON_INIT (IOCON_HAEN | IOCON_MIRROR) MCP23X17::MCP23X17(uint8_t addr, uint8_t ioc) : _opcode(MCP_OPCODE | (addr & 0x07)) , _ioc_pin(ioc) { } MCP23X17::~MCP23X17(void) { } void MCP23X17::begin(void) { // // Init // init(); // // default value of register // _iodir[0] = _iodir[1] = 0xFFFF; _ipol[0] = _ipol[1] = 0x0000; _inten[0] = _inten[1] = 0x0000; _gppu[0] = _gppu[1] = 0x0000; _olat[0] = _olat[1] = 0x0000; _refresh = true; // // Initial Set (POR/RST value) // writeReg(0x05, 0); // Select BANK 0 uint8_t init[] = { _opcode, MCP_IODIR, // reg addr lowByte(_iodir[0]), highByte(_iodir[0]), // IODIR lowByte(_ipol[0]) , highByte(_ipol[0] ), // IPOL lowByte(_inten[0]), highByte(_inten[0]), // GPINTEN 0x00, 0x00, // DEFVAL 0x00, 0x00, // INTCON IOCON_INIT, IOCON_INIT, // IOCON lowByte(_gppu[0]), highByte(_gppu[0]), // GPPU 0x00, 0x00, // INTF 0x00, 0x00, // INTCAP lowByte(_olat[0]), highByte(_olat[0]) // GPIO (OLAT) }; writeIO(init, sizeof(init)); // // Setup INT Pin // if (_ioc_pin != 0xFF) ::pinMode(_ioc_pin, INPUT); } void MCP23X17::writeReg(uint8_t reg, uint16_t val) { uint8_t ctrl[] = {_opcode, reg, lowByte(val), highByte(val)}; writeIO(ctrl, sizeof(ctrl)); if (reg != MCP_OLAT) _refresh = true; } uint16_t MCP23X17::readReg(uint8_t reg) { struct { uint8_t ctrl[2]; uint16_t data; } x = {{_opcode, reg}, 0}; readIO(x.ctrl, sizeof(x.ctrl), sizeof(x.data)); _refresh = false; return x.data; } uint16_t MCP23X17::getBit(uint8_t pin) { uint16_t rv; if (pin == 0xFF) rv = 0xFFFF; else if (pin & ~0x0F) rv = 0; else rv = (1 << pin); return rv; } MCP23X17& MCP23X17::pinMode(uint8_t pin, uint8_t mode) { uint16_t bit = getBit(pin); if (mode == OUTPUT) { _iodir[1] &= ~bit; _inten[1] &= ~bit; _gppu[1] &= ~bit; } else { _iodir[1] |= bit; _inten[1] |= bit; if (mode == INPUT_PULLUP) _gppu[1] |= bit; else _gppu[1] &= ~bit; } return *this; } MCP23X17& MCP23X17::inputInvert(uint8_t pin, uint8_t mode) { uint16_t bit = getBit(pin); if (mode) _ipol[1] |= bit; else _ipol[1] &= ~bit; return *this; } MCP23X17& MCP23X17::digitalWrite(uint8_t pin, uint8_t value) { uint16_t bit = getBit(pin); if (value) _olat[1] |= bit; else _olat[1] &= ~bit; return *this; } MCP23X17& MCP23X17::flush(void) { if (_olat[0] != _olat[1]) writeReg(MCP_OLAT, _olat[0] = _olat[1]); if (_gppu[0] != _gppu[1]) writeReg(MCP_GPPU, _gppu[0] = _gppu[1]); if (_inten[0] != _inten[1]) writeReg(MCP_GPINTEN, _inten[0] = _inten[1]); if (_ipol[0] != _ipol[1]) writeReg(MCP_IPOL, _ipol[0] = _ipol[1]); if (_iodir[0] != _iodir[1]) writeReg(MCP_IODIR, _iodir[0] = _iodir[1]); return *this; } uint8_t MCP23X17::digitalRead(uint8_t pin) { return (_gpio | (_olat[1] & ~_iodir[1])) & getBit(pin) ? HIGH : LOW; } MCP23X17& MCP23X17::refresh(void) { if (_refresh || (_ioc_pin == 0xFF) || (::digitalRead(_ioc_pin) == LOW)) _gpio = readReg(MCP_GPIO) & _inten[0]; return *this; } |