Erriez MCP23017 library for Arduino  1.0.0
This is a MCP23017 16-pin I2C IO-Expander library for Arduino by Erriez.
ErriezMCP23017.cpp
1 /*
2  * MIT License
3  *
4  * Copyright (c) 2020 Erriez
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 
25 #include <ErriezMCP23017.h>
26 #include <Wire.h>
27 
28 
38 ErriezMCP23017::ErriezMCP23017(uint8_t i2cAddress, TwoWire *twoWire) :
39  _wire(twoWire), _i2cAddress(i2cAddress), _i2cStatus(0),
40  _gpioPort(0), _gpioDir(0), _gpioPullup(0)
41 {
42  // Nothing to do
43 }
44 
45 //------------------------------------------------------------------------------
55 bool ErriezMCP23017::begin(bool reset)
56 {
57  // Write default configuration to IOCON register
60 
61  // Return I2C write status
62  if (_i2cStatus != 0) {
63  // I2C register write failed
64  return false;
65  }
66 
67  if (reset) {
68  // Disable interrupt pins
70  // Interrupt pin change
72  // Write port
73  portWrite(_gpioPort);
74  // Set port pull-up
75  setPortPullup(_gpioPullup);
76  // Set port direction
77  setPortDirection(_gpioDir);
78  }
79 
80  // Read current port state and release INTA pin
81  _portStateLast = portRead();
82 
83  return true;
84 }
85 
95 void ErriezMCP23017::pinMode(uint8_t pin, uint8_t mode)
96 {
97  // Set pin mode
98  if (pin < MCP23017_NUM_PINS) {
99  switch (mode) {
100  case OUTPUT:
101  // Pin to output
102  _gpioDir |= (1<<pin);
103  break;
104  case INPUT:
105  // Disable pull-up
106  _gpioPullup &= ~(1<<pin);
107  // Pin to input
108  _gpioDir &= ~(1<<pin);
109  break;
110  case INPUT_PULLUP:
111  // Enable pull-up
112  _gpioPullup |= (1<<pin);
113  // Pin to input
114  _gpioDir &= ~(1<<pin);
115  break;
116  }
117  // Set port pull-up
118  setPortPullup(_gpioPullup);
119  // Set port direction
120  setPortDirection(_gpioDir);
121  }
122 }
123 
132 void ErriezMCP23017::digitalWrite(uint8_t pin, uint8_t state)
133 {
134  // Set pin level
135  pinWrite(pin, (bool)state);
136 }
137 
146 {
147  // Read pin level
148  return (int)pinRead(pin);
149 }
150 
151 //------------------------------------------------------------------------------
159 void ErriezMCP23017::setPortDirection(uint16_t outputPins)
160 {
161  // Store port direction
162  _gpioDir = outputPins;
163  // Set port direction
164  registerWrite(MCP23017_REG_IODIR, ~(_gpioDir));
165 }
166 
175 {
176  // Get port direction
177  // Inverse value, because IODIR register: Bit '0': OUTPUT, '1': INPUT
178  return ~(registerRead(MCP23017_REG_IODIR));
179 }
180 
188 void ErriezMCP23017::setPortPullup(uint16_t pullupPins)
189 {
190  // Store pull-up
191  _gpioPullup = pullupPins;
192  // Set pull-up pins
193  registerWrite(MCP23017_REG_GPPU, _gpioPullup);
194 }
195 
204 {
205  // Get pull-up pins
207 }
208 
209 //------------------------------------------------------------------------------
217 void ErriezMCP23017::pinWrite(uint8_t pin, bool level)
218 {
219  if (pin < MCP23017_NUM_PINS) {
220  if (level == HIGH) {
221  portWrite(_gpioPort | (1<<pin));
222  } else {
223  portWrite(_gpioPort & ~(1<<pin));
224  }
225  }
226 }
227 
233 void ErriezMCP23017::pinToggle(uint8_t pin)
234 {
235  if (pin < MCP23017_NUM_PINS) {
236  // Toggle pin
237  portToggle(1<<pin);
238  }
239 }
240 
248 bool ErriezMCP23017::pinRead(uint8_t pin)
249 {
250  bool pinLevel = LOW;
251 
252  if (pin < MCP23017_NUM_PINS) {
253  if (portRead() & (1<<pin)) {
254  pinLevel = HIGH;
255  }
256  }
257  return pinLevel;
258 }
259 
265 void ErriezMCP23017::portWrite(uint16_t value)
266 {
267  // Store pins
268  _gpioPort = value;
269  // Write GPIO pin levels
270  registerWrite(MCP23017_REG_GPIO, _gpioPort);
271 }
272 
278 void ErriezMCP23017::portToggle(uint16_t value)
279 {
280  // Toggle and store pin levels
281  _gpioPort ^= value;
282  // Write GPIO pin levels
283  registerWrite(MCP23017_REG_GPIO, _gpioPort);
284 }
285 
293 void ErriezMCP23017::portMask(uint16_t maskSet, uint16_t maskClear)
294 {
295  _gpioPort &= ~(maskClear);
296  _gpioPort |= maskSet;
297  registerWrite(MCP23017_REG_GPIO, _gpioPort);
298 }
299 
306 {
307  // NOTE: Bug in the MCP23017: Reading the GPIO register releases INT pin
309 
310  // Return port state
311  return portState;
312 }
313 
314 //------------------------------------------------------------------------------
321 {
322  uint16_t value;
323 
324  // Read configuration register
326 
327  // Set polarity INT pin
328  if (activeHigh) {
329  // Active high
330  value |= ((1<<IOCON_INTPOL) << 8) | (1<<IOCON_INTPOL);
331  } else {
332  // Active low [default]
333  value &= ~((1<<IOCON_INTPOL) << 8) | (1<<IOCON_INTPOL);
334  }
335 
336  // Write configuration register
338 }
339 
346 {
348 }
349 
358 {
359  uint16_t intenValue;
360 
361  // Input pins
362  setPortDirection(_gpioDir & ~(pins));
363 
364  // Enable interrupt pins
365  intenValue = registerRead(MCP23017_REG_GPINTEN);
366  intenValue |= pins;
367  registerWrite(MCP23017_REG_GPINTEN, intenValue);
368 }
369 
376 {
377  uint16_t value;
378 
379  // Disable interrupt pins
381  value &= ~(pins);
383 }
384 
402 {
403  // Read interrupt flag register which holds only one pin change
405 
406  // A read of GPIO registers clears INTF register releases MCP23017 INTA pin
407  portState = portRead();
408 
409  // Calculate changed pins
410  pinsChanged |= _portStateLast ^ portState;
411 
412  // Calculate pins falling
414  // Calculate pins rising
415  pinsRising = portState & pinsChanged;
416 
417  // Check if pin changed, but missed previous edge
418  if ((_portStateLast & pinsChanged) == (pinsChanged & portState)) {
421  }
422 
423  // Store current pin state
424  _portStateLast = portState;
425 
426  if (pinsChanged) {
427  return true;
428  } else {
429  return false;
430  }
431 }
432 
433 //------------------------------------------------------------------------------
441 uint16_t ErriezMCP23017::registerRead(uint8_t reg)
442 {
443  _wire->beginTransmission(_i2cAddress);
444  _wire->write(reg & MCP23017_MASK_REG_A);
445  _i2cStatus = _wire->endTransmission(false);
446 
447  // Read two 8-bit register values with auto address increment
448  (void)_wire->requestFrom(_i2cAddress, (uint8_t)2);
449 
450  // Return I2C read buffer
451  return _wire->read() | (_wire->read() << 8);
452 }
453 
461 void ErriezMCP23017::registerWrite(uint8_t reg, uint16_t value)
462 {
463  // Write 16-bit value to two MCP23017 registers
464  _wire->beginTransmission(_i2cAddress);
465  _wire->write(reg & MCP23017_MASK_REG_A);
466  _wire->write(value & 0xff);
467  _wire->write(value >> 8);
468  // Generate I2C stop and store transfer status
469  _i2cStatus = _wire->endTransmission(true);
470 }
471 
481 {
482  /* Status of the endTransmission (I2C addressing, repeated start ignored):
483  */
484  return _i2cStatus;
485 }
486 
494 void ErriezMCP23017::dumpRegisters(HardwareSerial *serial)
495 {
496  serial->println(F("MCP23017 registers:"));
497 
498  serial->print(F(" 00 IODIR: 0x"));
499  serial->println(registerRead(MCP23017_REG_IODIR), HEX);
500  serial->print(F(" 02 IPOL: 0x"));
501  serial->println(registerRead(MCP23017_REG_IPOL), HEX);
502  serial->print(F(" 04 GPINTEN: 0x"));
503  serial->println(registerRead(MCP23017_REG_GPINTEN), HEX);
504  serial->print(F(" 06 DEFVAL: 0x"));
505  serial->println(registerRead(MCP23017_REG_DEFVAL), HEX);
506  serial->print(F(" 08 INTCON: 0x"));
507  serial->println(registerRead(MCP23017_REG_INTCON), HEX);
508  serial->print(F(" 0A IOCON: 0x"));
509  serial->println(registerRead(MCP23017_REG_IOCON), HEX);
510  serial->print(F(" 0C GPPU: 0x"));
511  serial->println(registerRead(MCP23017_REG_GPPU), HEX);
512  serial->print(F(" 0E INTF: 0x"));
513  serial->println(registerRead(MCP23017_REG_INTF), HEX);
514  serial->print(F(" 10 INTCAP: 0x"));
515  serial->println(registerRead(MCP23017_REG_INTCAP), HEX);
516  serial->print(F(" 12 GPIO: 0x"));
517  serial->println(registerRead(MCP23017_REG_GPIO), HEX);
518  serial->print(F(" 14 OLAT: 0x"));
519  serial->println(registerRead(MCP23017_REG_OLAT), HEX);
520 }
bool pinRead(uint8_t pin)
Read state of a single pin (input and output pins)
#define MCP23017_REG_INTCON
Controls how the associated pin value is compared for the interrupt-on-change for port A...
#define MCP23017_REG_IODIR
Controls the direction of the data I/O for port A.
void digitalWrite(uint8_t pin, uint8_t level)
Set state of a single pin.
void portMask(uint16_t maskSet, uint16_t maskClear)
Clear and set pin states.
void setPortDirection(uint16_t outputPins)
Set PORT direction all pins.
#define MCP23017_REG_GPINTEN
Controls the interrupt-on-change for each pin of port A.
void pinMode(uint8_t pin, uint8_t mode)
Set direction of a single pin.
#define MCP23017_REG_GPPU
Controls the pull-up resistors for the port A pins.
uint16_t portState
Port state since last portRead() call.
#define MCP23017_REG_OLAT
Provides access to the port A output latches.
#define MCP23017_REG_INTCAP
Captures the port A value at the time the interrupt occured.
uint16_t pinsChanged
Pins change on interrupt enabled pins since last intPinChanged() call.
void pinToggle(uint8_t pin)
Toggle state of a single pin (only for output pins)
#define MCP23017_REG_GPIO
Reflects the value on the port A.
void dumpRegisters(HardwareSerial *serial)
Print I2C registers on serial port.
uint16_t getPortInterruptMask()
Get interrupt mask all pins.
void setInterruptPolarityINTA(bool activeHigh)
Set interrupt polarity INTA.
void registerWrite(uint8_t reg, uint16_t value)
MCP23017 I2C write register.
void portWrite(uint16_t value)
Set all pin states.
MCP23017 I2C IO expander library for Arduino.
#define MCP23017_REG_IOCON
Configuration register A.
#define REG_IOCON_VALUE
Default MCP23017 configuration.
#define MCP23017_REG_DEFVAL
Controls the default comparaison value for interrupt-on-change for port A.
uint16_t portRead()
Read PORT of all pins (input and output pins)
void setPortPullup(uint16_t pullupPins)
Set PORT pullup all pins.
uint16_t pinsFalling
Falling edge on interrupt enabled pins since last intPinChanged() call.
void pinWrite(uint8_t pin, bool level)
Set pin state.
#define MCP23017_NUM_PINS
Total number of pins port A + B.
bool begin(bool reset=true)
Initialize MCP23017.
uint16_t pinsRising
Rising edge on interrupt eanbled pins since last intPinChanged() call.
uint16_t getPortPullup()
Get PORT pullup all pins.
uint16_t registerRead(uint8_t reg)
MCP23017 I2C read register.
bool interruptINTA()
MCP23017 INTA pin changed.
void setPortInterruptDisable(uint16_t pins)
Disable interrupt on pins.
void setPortInterruptEnable(uint16_t pins)
Enable interrupt change on pins.
void portToggle(uint16_t value)
Toggle pin states (output pins only)
uint8_t getI2CStatus()
Return status of the last I2C write, returned by Wire endTransfer()
int digitalRead(uint8_t pin)
Get state of a single pin.
#define MCP23017_MASK_ALL_PINS
All 16-pins mask.
ErriezMCP23017(uint8_t i2cAddress=MCP23017_I2C_ADDRESS, TwoWire *twoWire=&Wire)
ErriezMCP23017 Constructor.
uint16_t getPortDirection()
Get PORT direction all pins.
#define MCP23017_REG_IPOL
Configures the polarity on the corresponding GPIO port bits for port A.
#define MCP23017_REG_INTF
Reflects the interrupt condition on the port A pins.
#define MCP23017_MASK_REG_A
Address mask to select A registers on even addresses.
#define IOCON_INTPOL
This bit sets the polarity of the INT output pin.