284 lines
7.5 KiB
C++
284 lines
7.5 KiB
C++
/***************************************************
|
|
This is a library for the MCP23017 i2c port expander
|
|
|
|
These displays use I2C to communicate, 2 pins are required to
|
|
interface
|
|
Adafruit invests time and resources providing this open source code,
|
|
please support Adafruit and open-source hardware by purchasing
|
|
products from Adafruit!
|
|
|
|
Written by Limor Fried/Ladyada for Adafruit Industries.
|
|
BSD license, all text above must be included in any redistribution
|
|
****************************************************/
|
|
|
|
#ifdef __AVR
|
|
#include <avr/pgmspace.h>
|
|
#elif defined(ESP8266)
|
|
#include <pgmspace.h>
|
|
#endif
|
|
#include "Adafruit_MCP23017.h"
|
|
|
|
#if ARDUINO >= 100
|
|
#include "Arduino.h"
|
|
#else
|
|
#include "WProgram.h"
|
|
#endif
|
|
|
|
// minihelper to keep Arduino backward compatibility
|
|
static inline void wiresend(uint8_t x) {
|
|
#if ARDUINO >= 100
|
|
Wire.write((uint8_t) x);
|
|
#else
|
|
Wire.send(x);
|
|
#endif
|
|
}
|
|
|
|
static inline uint8_t wirerecv(void) {
|
|
#if ARDUINO >= 100
|
|
return Wire.read();
|
|
#else
|
|
return Wire.receive();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Bit number associated to a give Pin
|
|
*/
|
|
uint8_t Adafruit_MCP23017::bitForPin(uint8_t pin){
|
|
return pin%8;
|
|
}
|
|
|
|
/**
|
|
* Register address, port dependent, for a given PIN
|
|
*/
|
|
uint8_t Adafruit_MCP23017::regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr){
|
|
return(pin<8) ?portAaddr:portBaddr;
|
|
}
|
|
|
|
/**
|
|
* Reads a given register
|
|
*/
|
|
uint8_t Adafruit_MCP23017::readRegister(uint8_t addr){
|
|
// read the current GPINTEN
|
|
Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
|
|
wiresend(addr);
|
|
Wire.endTransmission();
|
|
Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
|
|
return wirerecv();
|
|
}
|
|
|
|
|
|
/**
|
|
* Writes a given register
|
|
*/
|
|
void Adafruit_MCP23017::writeRegister(uint8_t regAddr, uint8_t regValue){
|
|
// Write the register
|
|
Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
|
|
wiresend(regAddr);
|
|
wiresend(regValue);
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper to update a single bit of an A/B register.
|
|
* - Reads the current register value
|
|
* - Writes the new register value
|
|
*/
|
|
void Adafruit_MCP23017::updateRegisterBit(uint8_t pin, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr) {
|
|
uint8_t regValue;
|
|
uint8_t regAddr=regForPin(pin,portAaddr,portBaddr);
|
|
uint8_t bit=bitForPin(pin);
|
|
regValue = readRegister(regAddr);
|
|
|
|
// set the value for the particular bit
|
|
bitWrite(regValue,bit,pValue);
|
|
|
|
writeRegister(regAddr,regValue);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Initializes the MCP23017 given its HW selected address, see datasheet for Address selection.
|
|
*/
|
|
void Adafruit_MCP23017::begin(uint8_t addr) {
|
|
if (addr > 7) {
|
|
addr = 7;
|
|
}
|
|
i2caddr = addr;
|
|
|
|
Wire.begin();
|
|
|
|
// set defaults!
|
|
// all inputs on port A and B
|
|
writeRegister(MCP23017_IODIRA,0xff);
|
|
writeRegister(MCP23017_IODIRB,0xff);
|
|
}
|
|
|
|
/**
|
|
* Initializes the default MCP23017, with 000 for the configurable part of the address
|
|
*/
|
|
void Adafruit_MCP23017::begin(void) {
|
|
begin(0);
|
|
}
|
|
|
|
/**
|
|
* Sets the pin mode to either INPUT or OUTPUT
|
|
*/
|
|
void Adafruit_MCP23017::pinMode(uint8_t p, uint8_t d) {
|
|
updateRegisterBit(p,(d==INPUT),MCP23017_IODIRA,MCP23017_IODIRB);
|
|
}
|
|
|
|
/**
|
|
* Reads all 16 pins (port A and B) into a single 16 bits variable.
|
|
*/
|
|
uint16_t Adafruit_MCP23017::readGPIOAB() {
|
|
uint16_t ba = 0;
|
|
uint8_t a;
|
|
|
|
// read the current GPIO output latches
|
|
Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
|
|
wiresend(MCP23017_GPIOA);
|
|
Wire.endTransmission();
|
|
|
|
Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 2);
|
|
a = wirerecv();
|
|
ba = wirerecv();
|
|
ba <<= 8;
|
|
ba |= a;
|
|
|
|
return ba;
|
|
}
|
|
|
|
/**
|
|
* Read a single port, A or B, and return its current 8 bit value.
|
|
* Parameter b should be 0 for GPIOA, and 1 for GPIOB.
|
|
*/
|
|
uint8_t Adafruit_MCP23017::readGPIO(uint8_t b) {
|
|
|
|
// read the current GPIO output latches
|
|
Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
|
|
if (b == 0)
|
|
wiresend(MCP23017_GPIOA);
|
|
else {
|
|
wiresend(MCP23017_GPIOB);
|
|
}
|
|
Wire.endTransmission();
|
|
|
|
Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
|
|
return wirerecv();
|
|
}
|
|
|
|
/**
|
|
* Writes all the pins in one go. This method is very useful if you are implementing a multiplexed matrix and want to get a decent refresh rate.
|
|
*/
|
|
void Adafruit_MCP23017::writeGPIOAB(uint16_t ba) {
|
|
Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
|
|
wiresend(MCP23017_GPIOA);
|
|
wiresend(ba & 0xFF);
|
|
wiresend(ba >> 8);
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
void Adafruit_MCP23017::digitalWrite(uint8_t pin, uint8_t d) {
|
|
uint8_t gpio;
|
|
uint8_t bit=bitForPin(pin);
|
|
|
|
|
|
// read the current GPIO output latches
|
|
uint8_t regAddr=regForPin(pin,MCP23017_OLATA,MCP23017_OLATB);
|
|
gpio = readRegister(regAddr);
|
|
|
|
// set the pin and direction
|
|
bitWrite(gpio,bit,d);
|
|
|
|
// write the new GPIO
|
|
regAddr=regForPin(pin,MCP23017_GPIOA,MCP23017_GPIOB);
|
|
writeRegister(regAddr,gpio);
|
|
}
|
|
|
|
void Adafruit_MCP23017::pullUp(uint8_t p, uint8_t d) {
|
|
updateRegisterBit(p,d,MCP23017_GPPUA,MCP23017_GPPUB);
|
|
}
|
|
|
|
uint8_t Adafruit_MCP23017::digitalRead(uint8_t pin) {
|
|
uint8_t bit=bitForPin(pin);
|
|
uint8_t regAddr=regForPin(pin,MCP23017_GPIOA,MCP23017_GPIOB);
|
|
return (readRegister(regAddr) >> bit) & 0x1;
|
|
}
|
|
|
|
/**
|
|
* Configures the interrupt system. both port A and B are assigned the same configuration.
|
|
* Mirroring will OR both INTA and INTB pins.
|
|
* Opendrain will set the INT pin to value or open drain.
|
|
* polarity will set LOW or HIGH on interrupt.
|
|
* Default values after Power On Reset are: (false, false, LOW)
|
|
* If you are connecting the INTA/B pin to arduino 2/3, you should configure the interupt handling as FALLING with
|
|
* the default configuration.
|
|
*/
|
|
void Adafruit_MCP23017::setupInterrupts(uint8_t mirroring, uint8_t openDrain, uint8_t polarity){
|
|
// configure the port A
|
|
uint8_t ioconfValue=readRegister(MCP23017_IOCONA);
|
|
bitWrite(ioconfValue,6,mirroring);
|
|
bitWrite(ioconfValue,2,openDrain);
|
|
bitWrite(ioconfValue,1,polarity);
|
|
writeRegister(MCP23017_IOCONA,ioconfValue);
|
|
|
|
// Configure the port B
|
|
ioconfValue=readRegister(MCP23017_IOCONB);
|
|
bitWrite(ioconfValue,6,mirroring);
|
|
bitWrite(ioconfValue,2,openDrain);
|
|
bitWrite(ioconfValue,1,polarity);
|
|
writeRegister(MCP23017_IOCONB,ioconfValue);
|
|
}
|
|
|
|
/**
|
|
* Set's up a pin for interrupt. uses arduino MODEs: CHANGE, FALLING, RISING.
|
|
*
|
|
* Note that the interrupt condition finishes when you read the information about the port / value
|
|
* that caused the interrupt or you read the port itself. Check the datasheet can be confusing.
|
|
*
|
|
*/
|
|
void Adafruit_MCP23017::setupInterruptPin(uint8_t pin, uint8_t mode) {
|
|
|
|
// set the pin interrupt control (0 means change, 1 means compare against given value);
|
|
updateRegisterBit(pin,(mode!=CHANGE),MCP23017_INTCONA,MCP23017_INTCONB);
|
|
// if the mode is not CHANGE, we need to set up a default value, different value triggers interrupt
|
|
|
|
// In a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1.
|
|
// In a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0.
|
|
updateRegisterBit(pin,(mode==FALLING),MCP23017_DEFVALA,MCP23017_DEFVALB);
|
|
|
|
// enable the pin for interrupt
|
|
updateRegisterBit(pin,HIGH,MCP23017_GPINTENA,MCP23017_GPINTENB);
|
|
|
|
}
|
|
|
|
uint8_t Adafruit_MCP23017::getLastInterruptPin(){
|
|
uint8_t intf;
|
|
|
|
// try port A
|
|
intf=readRegister(MCP23017_INTFA);
|
|
for(int i=0;i<8;i++) if (bitRead(intf,i)) return i;
|
|
|
|
// try port B
|
|
intf=readRegister(MCP23017_INTFB);
|
|
for(int i=0;i<8;i++) if (bitRead(intf,i)) return i+8;
|
|
|
|
return MCP23017_INT_ERR;
|
|
|
|
}
|
|
uint8_t Adafruit_MCP23017::getLastInterruptPinValue(){
|
|
uint8_t intPin=getLastInterruptPin();
|
|
if(intPin!=MCP23017_INT_ERR){
|
|
uint8_t intcapreg=regForPin(intPin,MCP23017_INTCAPA,MCP23017_INTCAPB);
|
|
uint8_t bit=bitForPin(intPin);
|
|
return (readRegister(intcapreg)>>bit) & (0x01);
|
|
}
|
|
|
|
return MCP23017_INT_ERR;
|
|
}
|
|
|
|
|