├── .gitignore ├── .travis.yml ├── .vscode └── c_cpp_properties.json ├── LICENSE.md ├── README.md ├── examples ├── PortCopy │ └── PortCopy.ino ├── PortCopyOnInterrupt │ └── PortCopyOnInterrupt.ino └── RegistersDumper │ └── RegistersDumper.ino ├── library.properties └── src ├── MCP23017.cpp └── MCP23017.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Bb]in/ 3 | 4 | #Visual Studio code 5 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | env: 3 | global: 4 | - ARDUINO_IDE_VERSION="1.8.12" 5 | - ARDUINO_SAMD_VERSION="1.8.6" 6 | - STM32_STM32_VERSION="1.8.0" 7 | - LIBRARY_NAME="MCP23017" 8 | matrix: 9 | - BOARD="arduino:avr:uno" CORE="" CORE_URL="" 10 | - BOARD="arduino:samd:nano_33_iot" CORE="arduino:samd:$ARDUINO_SAMD_VERSION" CORE_URL="" 11 | - BOARD="STM32:stm32:Nucleo_64" CORE="STM32:stm32:$STM32_STM32_VERSION" CORE_URL="https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json" 12 | before_install: 13 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16" 14 | - sleep 3 15 | - export DISPLAY=:1.0 16 | - wget http://downloads.arduino.cc/arduino-$ARDUINO_IDE_VERSION-linux64.tar.xz 17 | - tar xf arduino-$ARDUINO_IDE_VERSION-linux64.tar.xz 18 | - sudo mv arduino-$ARDUINO_IDE_VERSION /usr/local/share/arduino 19 | - sudo ln -s /usr/local/share/arduino/arduino /usr/local/bin/arduino 20 | - if [ $CORE_URL ]; then arduino --pref boardsmanager.additional.urls=$CORE_URL --save-prefs; fi 21 | - if [ $CORE ]; then arduino --install-boards $CORE; fi 22 | install: 23 | - ln -s $PWD /usr/local/share/arduino/libraries/$LIBRARY_NAME 24 | script: 25 | - arduino --verify --board $BOARD $PWD/examples/RegistersDumper/PortCopy.ino 26 | - arduino --verify --board $BOARD $PWD/examples/PortCopyOnInterrupt/PortCopyOnInterrupt.ino 27 | - arduino --verify --board $BOARD $PWD/examples/RegistersDumper/RegistersDumper.ino 28 | notifications: 29 | email: 30 | on_success: change 31 | on_failure: change -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "${workspaceFolder}/src/**", 7 | "${config:arduino.path}/tools/**", 8 | "${config:arduino.path}/hardware/arduino/avr/**", 9 | "${config:arduino.path}/hardware/tools/avr/avr/include/**" 10 | ], 11 | "intelliSenseMode": "clang-x64", 12 | "cStandard": "c11", 13 | "cppStandard": "c++11" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bertrand Lemasle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCP23017 2 | [![Build Status](https://travis-ci.org/blemasle/arduino-mcp23017.svg?branch=master)](https://travis-ci.org/blemasle/arduino-mcp23017) 3 | [![License](https://img.shields.io/badge/license-MIT%20License-blue.svg)](http://doge.mit-license.org) 4 | 5 | This library provides full control over the Microchip's [MCP23017](https://www.microchip.com/wwwproducts/en/MCP23017), including interrupt support. 6 | 7 | ## Features 8 | * Individual pins read & write 9 | * Ports read & write 10 | * Registers read & write 11 | * Full interrupt support 12 | 13 | ## Usage 14 | Unlike most Arduino library, no default instance is created when the library is included. It's up to you to create one using the appropriate I2C address based on MCP23017 `A0`, `A1` and `A2` pins wirings. 15 | Available addresses go from `0x20` to `0x27`, allowing up to 8 MCP23017 on the same I2C bus. 16 | 17 | ```cpp 18 | #include 19 | #include 20 | 21 | MCP23017 mcp = MCP23017(0x24); 22 | ``` 23 | 24 | Additionaly, you can specify the `Wire` instance to use as a second argument. For instance `MCP23017(0x24, Wire1)`. 25 | See included examples for further usage. 26 | 27 | ## Warning about GPA7 & GPB7 28 | 29 | GPA7 or GPB7 should not be used as inputs despite what the configuration registers allow. As [stated by Microchip](https://microchip.my.site.com/s/article/GPA7---GPB7-Cannot-Be-Used-as-Inputs-In-MCP23017), it can lead to SDA signal corruption or even malfunction in the host bus under some conditions. 30 | 31 | ## Breaking changes in v2.0.0 32 | Major renames have been performed in v2.0.0 to improve compatibility with a variety of platforms. Existing code *will* break when you update from version v1.x. 33 | 34 | | Name in v1.x | Name in v2.x | 35 | |-----------------------|---------------------------| 36 | | `MCP23017_PORT` | `MCP23017Port` | 37 | | `MCP23017_REGISTER` | `MCP23017Register` | 38 | | `MCP23017_INTMODE` | `MCP23017InterruptMode` | 39 | 40 | In addition to this, every member of the `MCP23017Register` enum were renamed to avoid possible conflicts with macro definitions. `GPIOA` was renamed to `GPIO_A`, `INTCAPA` to `INTCAP_A` and so on... 41 | -------------------------------------------------------------------------------- /examples/PortCopy/PortCopy.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * On every loop, the state of the port B is copied to port A. 3 | * 4 | * Use active low inputs on port B. Internal pullups are enabled by default by the library so there is no need for external resistors. 5 | * Place LEDS on port A for instance. 6 | * When pressing a button, the corresponding led is shut down. 7 | * 8 | * You can also uncomment one line to invert the input (when pressing a button the corresponding led is lit) 9 | */ 10 | #include 11 | #include 12 | 13 | #define MCP23017_ADDR 0x20 14 | MCP23017 mcp = MCP23017(MCP23017_ADDR); 15 | 16 | void setup() { 17 | Wire.begin(); 18 | Serial.begin(115200); 19 | 20 | mcp.init(); 21 | mcp.portMode(MCP23017Port::A, 0); //Port A as output 22 | mcp.portMode(MCP23017Port::B, 0b11111111); //Port B as input 23 | 24 | mcp.writeRegister(MCP23017Register::GPIO_A, 0x00); //Reset port A 25 | mcp.writeRegister(MCP23017Register::GPIO_B, 0x00); //Reset port B 26 | 27 | // GPIO_B reflects the same logic as the input pins state 28 | mcp.writeRegister(MCP23017Register::IPOL_B, 0x00); 29 | // Uncomment this line to invert inputs (press a button to lit a led) 30 | //mcp.writeRegister(MCP23017Register::IPOL_B, 0xFF); 31 | } 32 | 33 | void loop() { 34 | uint8_t currentB; 35 | 36 | currentB = mcp.readPort(MCP23017Port::B); 37 | mcp.writePort(MCP23017Port::A, currentB); 38 | } 39 | -------------------------------------------------------------------------------- /examples/PortCopyOnInterrupt/PortCopyOnInterrupt.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * When a pin on port B is falling, the same pin on port A is switched. 3 | * Connect a range of switches to port B and leds on port A to see it happening. 4 | * 5 | * Connect digital pin 3 to MCP23017 INTB pin. 6 | * You'll also need a small cap (100nF tipically) between the arduino interrupt pin and GND. 7 | */ 8 | #include 9 | #include 10 | 11 | #define MCP23017_ADDR 0x20 12 | #define INT_PIN 3 13 | 14 | MCP23017 mcp = MCP23017(MCP23017_ADDR); 15 | 16 | volatile bool interrupted = false; 17 | 18 | void userInput() { 19 | interrupted = true; 20 | } 21 | 22 | void setup() { 23 | Wire.begin(); 24 | Serial.begin(115200); 25 | 26 | mcp.init(); 27 | mcp.portMode(MCP23017Port::A, 0); //Port A as output 28 | mcp.portMode(MCP23017Port::B, 0b11111111); //Port B as input 29 | 30 | mcp.interruptMode(MCP23017InterruptMode::Separated); 31 | mcp.interrupt(MCP23017Port::B, FALLING); 32 | 33 | mcp.writeRegister(MCP23017Register::IPOL_A, 0x00); 34 | mcp.writeRegister(MCP23017Register::IPOL_B, 0x00); 35 | 36 | mcp.writeRegister(MCP23017Register::GPIO_A, 0x00); 37 | mcp.writeRegister(MCP23017Register::GPIO_B, 0x00); 38 | 39 | mcp.clearInterrupts(); 40 | 41 | pinMode(INT_PIN, INPUT_PULLUP); 42 | attachInterrupt(digitalPinToInterrupt(INT_PIN), userInput, FALLING); 43 | } 44 | 45 | void loop() { 46 | uint8_t a, b; 47 | uint8_t captureA, captureB; 48 | uint8_t currentA, currentB; 49 | 50 | if(!interrupted) 51 | { 52 | // just to be sure that arduino and mcp are in the "same state" 53 | // regarding interrupts 54 | mcp.clearInterrupts(); 55 | return; 56 | } 57 | 58 | //debouncing 59 | delay(100); 60 | interrupted = false; 61 | 62 | mcp.interruptedBy(a, b); 63 | // this is the state of the port the moment the interrupt was triggered 64 | mcp.clearInterrupts(captureA, captureB); 65 | // this is the state of the B port right now, after the delay to act as debouncing 66 | currentB = mcp.readPort(MCP23017Port::B); 67 | 68 | if((b & ~currentB) == (b & ~captureB)) { 69 | // the pin that triggered the interrupt is still in the same state after the deboucing delay 70 | currentA = mcp.readPort(MCP23017Port::A); 71 | mcp.writeRegister(MCP23017Register::GPIO_A, currentA ^ b); 72 | } 73 | } -------------------------------------------------------------------------------- /examples/RegistersDumper/RegistersDumper.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | MCP23017 mcp = MCP23017(0x20); 5 | 6 | void setup() { 7 | Wire.begin(); 8 | mcp.init(); 9 | 10 | Serial.begin(115200); 11 | 12 | uint8_t conf = mcp.readRegister(MCP23017Register::IODIR_A); 13 | Serial.print("IODIR_A : "); 14 | Serial.print(conf, BIN); 15 | Serial.println(); 16 | 17 | conf = mcp.readRegister(MCP23017Register::IODIR_B); 18 | Serial.print("IODIR_B : "); 19 | Serial.print(conf, BIN); 20 | Serial.println(); 21 | 22 | conf = mcp.readRegister(MCP23017Register::IPOL_A); 23 | Serial.print("IPOL_A : "); 24 | Serial.print(conf, BIN); 25 | Serial.println(); 26 | 27 | conf = mcp.readRegister(MCP23017Register::IPOL_B); 28 | Serial.print("IPOL_B : "); 29 | Serial.print(conf, BIN); 30 | Serial.println(); 31 | 32 | conf = mcp.readRegister(MCP23017Register::GPINTEN_A); 33 | Serial.print("GPINTEN_A : "); 34 | Serial.print(conf, BIN); 35 | Serial.println(); 36 | 37 | conf = mcp.readRegister(MCP23017Register::GPINTEN_B); 38 | Serial.print("GPINTEN_B : "); 39 | Serial.print(conf, BIN); 40 | Serial.println(); 41 | 42 | conf = mcp.readRegister(MCP23017Register::DEFVAL_A); 43 | Serial.print("DEFVAL_A : "); 44 | Serial.print(conf, BIN); 45 | Serial.println(); 46 | 47 | conf = mcp.readRegister(MCP23017Register::DEFVAL_B); 48 | Serial.print("DEFVAL_B : "); 49 | Serial.print(conf, BIN); 50 | Serial.println(); 51 | 52 | conf = mcp.readRegister(MCP23017Register::INTCON_A); 53 | Serial.print("INTCON_A : "); 54 | Serial.print(conf, BIN); 55 | Serial.println(); 56 | 57 | conf = mcp.readRegister(MCP23017Register::INTCON_B); 58 | Serial.print("INTCON_B : "); 59 | Serial.print(conf, BIN); 60 | Serial.println(); 61 | 62 | conf = mcp.readRegister(MCP23017Register::IOCON); 63 | Serial.print("IOCON : "); 64 | Serial.print(conf, BIN); 65 | Serial.println(); 66 | 67 | //conf = mcp.readRegister(IOCONB); 68 | //Serial.print("IOCONB : "); 69 | //Serial.print(conf, BIN); 70 | //Serial.println(); 71 | 72 | conf = mcp.readRegister(MCP23017Register::GPPU_A); 73 | Serial.print("GPPU_A : "); 74 | Serial.print(conf, BIN); 75 | Serial.println(); 76 | 77 | conf = mcp.readRegister(MCP23017Register::GPPU_B); 78 | Serial.print("GPPU_B : "); 79 | Serial.print(conf, BIN); 80 | Serial.println(); 81 | 82 | conf = mcp.readRegister(MCP23017Register::INTF_A); 83 | Serial.print("INTF_A : "); 84 | Serial.print(conf, BIN); 85 | Serial.println(); 86 | 87 | conf = mcp.readRegister(MCP23017Register::INTF_B); 88 | Serial.print("INTF_B : "); 89 | Serial.print(conf, BIN); 90 | Serial.println(); 91 | 92 | conf = mcp.readRegister(MCP23017Register::INTCAP_A); 93 | Serial.print("INTCAP_A : "); 94 | Serial.print(conf, BIN); 95 | Serial.println(); 96 | 97 | conf = mcp.readRegister(MCP23017Register::INTCAP_B); 98 | Serial.print("INTCAP_B : "); 99 | Serial.print(conf, BIN); 100 | Serial.println(); 101 | 102 | conf = mcp.readRegister(MCP23017Register::GPIO_A); 103 | Serial.print("GPIO_A : "); 104 | Serial.print(conf, BIN); 105 | Serial.println(); 106 | 107 | conf = mcp.readRegister(MCP23017Register::GPIO_B); 108 | Serial.print("GPIO_B : "); 109 | Serial.print(conf, BIN); 110 | Serial.println(); 111 | 112 | conf = mcp.readRegister(MCP23017Register::OLAT_A); 113 | Serial.print("OLAT_A : "); 114 | Serial.print(conf, BIN); 115 | Serial.println(); 116 | 117 | conf = mcp.readRegister(MCP23017Register::OLAT_B); 118 | Serial.print("OLAT_B : "); 119 | Serial.print(conf, BIN); 120 | Serial.println(); 121 | 122 | Serial.end(); 123 | } 124 | 125 | void loop() {} -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=MCP23017 2 | version=2.0.0 3 | author=Bertrand Lemasle 4 | maintainer=Bertrand Lemasle 5 | sentence=MCP23017 I2C Port expander library. 6 | paragraph=Support all MCP23017 features, including interrupts. Allows full control over the chip registers. 7 | category=Signal Input/Output 8 | url=https://github.com/blemasle/arduino-mcp23017 9 | architectures=* 10 | 11 | -------------------------------------------------------------------------------- /src/MCP23017.cpp: -------------------------------------------------------------------------------- 1 | #include "MCP23017.h" 2 | 3 | MCP23017::MCP23017(uint8_t address, TwoWire& bus) { 4 | _deviceAddr = address; 5 | _bus = &bus; 6 | } 7 | 8 | MCP23017::MCP23017(TwoWire& bus) { 9 | _deviceAddr = MCP23017_I2C_ADDRESS; 10 | _bus = &bus; 11 | } 12 | 13 | MCP23017::~MCP23017() {} 14 | 15 | void MCP23017::init() 16 | { 17 | //BANK = 0 : sequential register addresses 18 | //MIRROR = 0 : use configureInterrupt 19 | //SEQOP = 1 : sequential operation disabled, address pointer does not increment 20 | //DISSLW = 0 : slew rate enabled 21 | //HAEN = 0 : hardware address pin is always enabled on 23017 22 | //ODR = 0 : active driver output (INTPOL bit sets the polarity.) 23 | 24 | //INTPOL = 0 : interrupt active low 25 | //UNIMPLMENTED 0 : unimplemented: Read as ‘0’ 26 | 27 | writeRegister(MCP23017Register::IOCON, 0b00100000); 28 | } 29 | 30 | void MCP23017::begin() 31 | { 32 | init(); 33 | } 34 | 35 | void MCP23017::begin(uint8_t address) 36 | { 37 | _deviceAddr = address; 38 | begin(); 39 | } 40 | 41 | void MCP23017::portMode(MCP23017Port port, uint8_t directions, uint8_t pullups, uint8_t inverted) 42 | { 43 | writeRegister(MCP23017Register::IODIR_A + port, directions); 44 | writeRegister(MCP23017Register::GPPU_A + port, pullups); 45 | writeRegister(MCP23017Register::IPOL_A + port, inverted); 46 | } 47 | 48 | void MCP23017::pinMode(uint8_t pin, uint8_t mode, bool inverted) 49 | { 50 | MCP23017Register iodirreg = MCP23017Register::IODIR_A; 51 | MCP23017Register pullupreg = MCP23017Register::GPPU_A; 52 | MCP23017Register polreg = MCP23017Register::IPOL_A; 53 | uint8_t iodir, pol, pull; 54 | 55 | if(pin > 7) 56 | { 57 | iodirreg = MCP23017Register::IODIR_B; 58 | pullupreg = MCP23017Register::GPPU_B; 59 | polreg = MCP23017Register::IPOL_B; 60 | pin -= 8; 61 | } 62 | 63 | iodir = readRegister(iodirreg); 64 | if(mode == INPUT || mode == INPUT_PULLUP) bitSet(iodir, pin); 65 | else bitClear(iodir, pin); 66 | 67 | pull = readRegister(pullupreg); 68 | if(mode == INPUT_PULLUP) bitSet(pull, pin); 69 | else bitClear(pull, pin); 70 | 71 | pol = readRegister(polreg); 72 | if(inverted) bitSet(pol, pin); 73 | else bitClear(pol, pin); 74 | 75 | writeRegister(iodirreg, iodir); 76 | writeRegister(pullupreg, pull); 77 | writeRegister(polreg, pol); 78 | } 79 | 80 | void MCP23017::digitalWrite(uint8_t pin, uint8_t state) 81 | { 82 | MCP23017Register gpioreg = MCP23017Register::GPIO_A; 83 | uint8_t gpio; 84 | if(pin > 7) 85 | { 86 | gpioreg = MCP23017Register::GPIO_B; 87 | pin -= 8; 88 | } 89 | 90 | gpio = readRegister(gpioreg); 91 | if(state == HIGH) bitSet(gpio, pin); 92 | else bitClear(gpio, pin); 93 | 94 | writeRegister(gpioreg, gpio); 95 | } 96 | 97 | uint8_t MCP23017::digitalRead(uint8_t pin) 98 | { 99 | MCP23017Register gpioreg = MCP23017Register::GPIO_A; 100 | uint8_t gpio; 101 | if(pin > 7) 102 | { 103 | gpioreg = MCP23017Register::GPIO_B; 104 | pin -=8; 105 | } 106 | 107 | gpio = readRegister(gpioreg); 108 | if(bitRead(gpio, pin)) return HIGH; 109 | return LOW; 110 | } 111 | 112 | void MCP23017::writePort(MCP23017Port port, uint8_t value) 113 | { 114 | writeRegister(MCP23017Register::GPIO_A + port, value); 115 | } 116 | 117 | void MCP23017::write(uint16_t value) 118 | { 119 | writeRegister(MCP23017Register::GPIO_A, lowByte(value), highByte(value)); 120 | } 121 | 122 | uint8_t MCP23017::readPort(MCP23017Port port) 123 | { 124 | return readRegister(MCP23017Register::GPIO_A + port); 125 | } 126 | 127 | uint16_t MCP23017::read() 128 | { 129 | uint8_t a = readPort(MCP23017Port::A); 130 | uint8_t b = readPort(MCP23017Port::B); 131 | 132 | return a | b << 8; 133 | } 134 | 135 | void MCP23017::writeRegister(MCP23017Register reg, uint8_t value) 136 | { 137 | _bus->beginTransmission(_deviceAddr); 138 | _bus->write(static_cast(reg)); 139 | _bus->write(value); 140 | _bus->endTransmission(); 141 | } 142 | 143 | void MCP23017::writeRegister(MCP23017Register reg, uint8_t portA, uint8_t portB) 144 | { 145 | _bus->beginTransmission(_deviceAddr); 146 | _bus->write(static_cast(reg)); 147 | _bus->write(portA); 148 | _bus->write(portB); 149 | _bus->endTransmission(); 150 | } 151 | 152 | 153 | uint8_t MCP23017::readRegister(MCP23017Register reg) 154 | { 155 | _bus->beginTransmission(_deviceAddr); 156 | _bus->write(static_cast(reg)); 157 | _bus->endTransmission(); 158 | _bus->requestFrom(_deviceAddr, (uint8_t)1); 159 | return _bus->read(); 160 | } 161 | 162 | void MCP23017::readRegister(MCP23017Register reg, uint8_t& portA, uint8_t& portB) 163 | { 164 | _bus->beginTransmission(_deviceAddr); 165 | _bus->write(static_cast(reg)); 166 | _bus->endTransmission(); 167 | _bus->requestFrom(_deviceAddr, (uint8_t)2); 168 | portA = _bus->read(); 169 | portB = _bus->read(); 170 | } 171 | 172 | #ifdef _MCP23017_INTERRUPT_SUPPORT_ 173 | 174 | void MCP23017::interruptMode(MCP23017InterruptMode intMode) 175 | { 176 | uint8_t iocon = readRegister(MCP23017Register::IOCON); 177 | if(intMode == MCP23017InterruptMode::Or) iocon |= static_cast(MCP23017InterruptMode::Or); 178 | else iocon &= ~(static_cast(MCP23017InterruptMode::Or)); 179 | 180 | writeRegister(MCP23017Register::IOCON, iocon); 181 | } 182 | 183 | void MCP23017::interrupt(MCP23017Port port, uint8_t mode) 184 | { 185 | MCP23017Register defvalreg = MCP23017Register::DEFVAL_A + port; 186 | MCP23017Register intconreg = MCP23017Register::INTCON_A + port; 187 | 188 | //enable interrupt for port 189 | writeRegister(MCP23017Register::GPINTEN_A + port, 0xFF); 190 | switch(mode) 191 | { 192 | case CHANGE: 193 | //interrupt on change 194 | writeRegister(intconreg, 0); 195 | break; 196 | case FALLING: 197 | //interrupt falling : compared against defval, 0xff 198 | writeRegister(intconreg, 0xFF); 199 | writeRegister(defvalreg, 0xFF); 200 | break; 201 | case RISING: 202 | //interrupt rising : compared against defval, 0x00 203 | writeRegister(intconreg, 0xFF); 204 | writeRegister(defvalreg, 0x00); 205 | break; 206 | } 207 | } 208 | 209 | void MCP23017::interruptedBy(uint8_t& portA, uint8_t& portB) 210 | { 211 | readRegister(MCP23017Register::INTF_A, portA, portB); 212 | } 213 | 214 | void MCP23017::disableInterrupt(MCP23017Port port) 215 | { 216 | writeRegister(MCP23017Register::GPINTEN_A + port, 0x00); 217 | } 218 | 219 | void MCP23017::clearInterrupts() 220 | { 221 | uint8_t a, b; 222 | clearInterrupts(a, b); 223 | } 224 | 225 | void MCP23017::clearInterrupts(uint8_t& portA, uint8_t& portB) 226 | { 227 | readRegister(MCP23017Register::INTCAP_A, portA, portB); 228 | } 229 | 230 | #endif 231 | -------------------------------------------------------------------------------- /src/MCP23017.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define MCP23017_I2C_ADDRESS 0x20 ///< The default I2C address of MCP23017. 7 | #define _MCP23017_INTERRUPT_SUPPORT_ ///< Enables support for MCP23017 interrupts. 8 | 9 | enum class MCP23017Port : uint8_t 10 | { 11 | A = 0, 12 | B = 1 13 | }; 14 | 15 | struct MCP23017Pin 16 | { 17 | enum Names { 18 | GPA0 = 0, 19 | GPA1, 20 | GPA2, 21 | GPA3, 22 | GPA4, 23 | GPA5, 24 | GPA6, 25 | GPA7, 26 | GPB0 = 8, 27 | GPB1, 28 | GPB2, 29 | GPB3, 30 | GPB4, 31 | GPB5, 32 | GPB6, 33 | GPB7 34 | }; 35 | }; 36 | 37 | /** 38 | * Controls if the two interrupt pins mirror each other. 39 | * See "3.6 Interrupt Logic". 40 | */ 41 | enum class MCP23017InterruptMode : uint8_t 42 | { 43 | Separated = 0, ///< Interrupt pins are kept independent 44 | Or = 0b01000000 ///< Interrupt pins are mirrored 45 | }; 46 | 47 | /** 48 | * Registers addresses. 49 | * The library use addresses for IOCON.BANK = 0. 50 | * See "3.2.1 Byte mode and Sequential mode". 51 | */ 52 | enum class MCP23017Register : uint8_t 53 | { 54 | IODIR_A = 0x00, ///< Controls the direction of the data I/O for port A. 55 | IODIR_B = 0x01, ///< Controls the direction of the data I/O for port B. 56 | IPOL_A = 0x02, ///< Configures the polarity on the corresponding GPIO_ port bits for port A. 57 | IPOL_B = 0x03, ///< Configures the polarity on the corresponding GPIO_ port bits for port B. 58 | GPINTEN_A = 0x04, ///< Controls the interrupt-on-change for each pin of port A. 59 | GPINTEN_B = 0x05, ///< Controls the interrupt-on-change for each pin of port B. 60 | DEFVAL_A = 0x06, ///< Controls the default comparaison value for interrupt-on-change for port A. 61 | DEFVAL_B = 0x07, ///< Controls the default comparaison value for interrupt-on-change for port B. 62 | INTCON_A = 0x08, ///< Controls how the associated pin value is compared for the interrupt-on-change for port A. 63 | INTCON_B = 0x09, ///< Controls how the associated pin value is compared for the interrupt-on-change for port B. 64 | IOCON = 0x0A, ///< Controls the device. 65 | GPPU_A = 0x0C, ///< Controls the pull-up resistors for the port A pins. 66 | GPPU_B = 0x0D, ///< Controls the pull-up resistors for the port B pins. 67 | INTF_A = 0x0E, ///< Reflects the interrupt condition on the port A pins. 68 | INTF_B = 0x0F, ///< Reflects the interrupt condition on the port B pins. 69 | INTCAP_A = 0x10, ///< Captures the port A value at the time the interrupt occured. 70 | INTCAP_B = 0x11, ///< Captures the port B value at the time the interrupt occured. 71 | GPIO_A = 0x12, ///< Reflects the value on the port A. 72 | GPIO_B = 0x13, ///< Reflects the value on the port B. 73 | OLAT_A = 0x14, ///< Provides access to the port A output latches. 74 | OLAT_B = 0x15, ///< Provides access to the port B output latches. 75 | }; 76 | 77 | inline MCP23017Register operator+(MCP23017Register a, MCP23017Port b) { 78 | return static_cast(static_cast(a) + static_cast(b)); 79 | }; 80 | 81 | class MCP23017 82 | { 83 | private: 84 | TwoWire* _bus; 85 | uint8_t _deviceAddr; 86 | public: 87 | /** 88 | * Instantiates a new instance to interact with a MCP23017 at the specified address. 89 | */ 90 | MCP23017(uint8_t address, TwoWire& bus = Wire); 91 | /** 92 | * Instantiates a new instance to interact with a MCP23017 at the 93 | * MCP23017_I2C_ADDRESS default. 94 | */ 95 | MCP23017(TwoWire& bus = Wire); 96 | ~MCP23017(); 97 | #ifdef _DEBUG 98 | void debug(); 99 | #endif 100 | /** 101 | * Uses the I2C address set during construction. Implicitly calls init(). 102 | */ 103 | void begin(); 104 | /** 105 | * Overrides the I2C address set by the constructor. Implicitly calls begin(). 106 | 107 | */ 108 | void begin(uint8_t address); 109 | /** 110 | * Initializes the chip with the default configuration. 111 | * Enables Byte mode (IOCON.BANK = 0 and IOCON.SEQOP = 1). 112 | * Enables pull-up resistors for all pins. This will only be effective for input pins. 113 | * 114 | * See "3.2.1 Byte mode and Sequential mode". 115 | */ 116 | void init(); 117 | /** 118 | * Controls the pins direction on a whole port at once. 119 | * 120 | * 1 = Pin is configured as an input. 121 | * 0 = Pin is configured as an output. 122 | * 123 | * See "3.5.1 I/O Direction register". 124 | */ 125 | void portMode(MCP23017Port port, uint8_t directions, uint8_t pullups = 0xFF, uint8_t inverted = 0x00); 126 | /** 127 | * Controls a single pin direction. 128 | * Pin 0-7 for port A, 8-15 fo port B. 129 | * 130 | * 1 = Pin is configured as an input. 131 | * 0 = Pin is configured as an output. 132 | * 133 | * See "3.5.1 I/O Direction register". 134 | * 135 | * Beware! 136 | * On Arduino platform, INPUT = 0, OUTPUT = 1, which is the inverse 137 | * of the MCP23017 definition where a pin is an input if its IODIR bit is set to 1. 138 | * This library pinMode function behaves like Arduino's standard pinMode for consistency. 139 | * [ OUTPUT | INPUT | INPUT_PULLUP ] 140 | */ 141 | void pinMode(uint8_t pin, uint8_t mode, bool inverted = false); 142 | 143 | /** 144 | * Writes a single pin state. 145 | * Pin 0-7 for port A, 8-15 for port B. 146 | * 147 | * 1 = Logic-high 148 | * 0 = Logic-low 149 | * 150 | * See "3.5.10 Port register". 151 | */ 152 | void digitalWrite(uint8_t pin, uint8_t state); 153 | /** 154 | * Reads a single pin state. 155 | * Pin 0-7 for port A, 8-15 for port B. 156 | * 157 | * 1 = Logic-high 158 | * 0 = Logic-low 159 | * 160 | * See "3.5.10 Port register". 161 | */ 162 | uint8_t digitalRead(uint8_t pin); 163 | 164 | /** 165 | * Writes pins state to a whole port. 166 | * 167 | * 1 = Logic-high 168 | * 0 = Logic-low 169 | * 170 | * See "3.5.10 Port register". 171 | */ 172 | void writePort(MCP23017Port port, uint8_t value); 173 | /** 174 | * Writes pins state to both ports. 175 | * 176 | * 1 = Logic-high 177 | * 0 = Logic-low 178 | * 179 | * See "3.5.10 Port register". 180 | */ 181 | void write(uint16_t value); 182 | 183 | /** 184 | * Reads pins state for a whole port. 185 | * 186 | * 1 = Logic-high 187 | * 0 = Logic-low 188 | * 189 | * See "3.5.10 Port register". 190 | */ 191 | uint8_t readPort(MCP23017Port port); 192 | /** 193 | * Reads pins state for both ports. 194 | * 195 | * 1 = Logic-high 196 | * 0 = Logic-low 197 | * 198 | * See "3.5.10 Port register". 199 | */ 200 | uint16_t read(); 201 | 202 | /** 203 | * Writes a single register value. 204 | */ 205 | void writeRegister(MCP23017Register reg, uint8_t value); 206 | /** 207 | * Writes values to a register pair. 208 | * 209 | * For portA and portB variable to effectively match the desired port, 210 | * you have to supply a portA register address to reg. Otherwise, values 211 | * will be reversed due to the way the MCP23017 works in Byte mode. 212 | */ 213 | void writeRegister(MCP23017Register reg, uint8_t portA, uint8_t portB); 214 | /** 215 | * Reads a single register value. 216 | */ 217 | uint8_t readRegister(MCP23017Register reg); 218 | /** 219 | * Reads the values from a register pair. 220 | * 221 | * For portA and portB variable to effectively match the desired port, 222 | * you have to supply a portA register address to reg. Otherwise, values 223 | * will be reversed due to the way the MCP23017 works in Byte mode. 224 | */ 225 | void readRegister(MCP23017Register reg, uint8_t& portA, uint8_t& portB); 226 | 227 | #ifdef _MCP23017_INTERRUPT_SUPPORT_ 228 | 229 | /** 230 | * Controls how the interrupt pins act with each other. 231 | * If intMode is Separated, interrupt conditions on a port will cause its respective INT pin to active. 232 | * If intMode is Or, interrupt pins are OR'ed so an interrupt on one of the port will cause both pints to active. 233 | * 234 | * Controls the IOCON.MIRROR bit. 235 | * See "3.5.6 Configuration register". 236 | */ 237 | void interruptMode(MCP23017InterruptMode intMode); 238 | /** 239 | * Configures interrupt registers using an Arduino-like API. 240 | * mode can be one of CHANGE, FALLING or RISING. 241 | */ 242 | void interrupt(MCP23017Port port, uint8_t mode); 243 | /** 244 | * Disable interrupts for the specified port. 245 | */ 246 | void disableInterrupt(MCP23017Port port); 247 | /** 248 | * Reads which pin caused the interrupt. 249 | */ 250 | void interruptedBy(uint8_t& portA, uint8_t& portB); 251 | /** 252 | * Clears interrupts on both ports. 253 | */ 254 | void clearInterrupts(); 255 | /** 256 | * Clear interrupts on both ports. Returns port values at the time the interrupt occured. 257 | */ 258 | void clearInterrupts(uint8_t& portA, uint8_t& portB); 259 | 260 | #endif 261 | }; 262 | --------------------------------------------------------------------------------