/* * Copyright (c) 2014, Majenko Technologies * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of Majenko Technologies nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include /*! The constructor takes three parameters. The first is an SPI class * pointer. This is the address of an SPI object (either the default * SPI object on the Arduino, or an object made using the DSPIx classes * on the chipKIT). The second parameter is the chip select pin number * to use when communicating with the chip. The third is the internal * address number of the chip. This is controlled by the three Ax pins * on the chip. * * Example: * * * MCP23S17 myExpander(&SPI, 10, 0); * */ #ifdef __PIC32MX__ MCP23S17::MCP23S17(DSPI *spi, uint8_t cs, uint8_t addr) { #else MCP23S17::MCP23S17(SPIClass *spi, uint8_t cs, uint8_t addr) { #endif _spi = spi; _cs = cs; _addr = addr; _reg[IODIRA] = 0xFF; _reg[IODIRB] = 0xFF; _reg[IPOLA] = 0x00; _reg[IPOLB] = 0x00; _reg[GPINTENA] = 0x00; _reg[GPINTENB] = 0x00; _reg[DEFVALA] = 0x00; _reg[DEFVALB] = 0x00; _reg[INTCONA] = 0x00; _reg[INTCONB] = 0x00; _reg[IOCONA] = 0x18; _reg[IOCONB] = 0x18; _reg[GPPUA] = 0x00; _reg[GPPUB] = 0x00; _reg[INTFA] = 0x00; _reg[INTFB] = 0x00; _reg[INTCAPA] = 0x00; _reg[INTCAPB] = 0x00; _reg[GPIOA] = 0x00; _reg[GPIOB] = 0x00; _reg[OLATA] = 0x00; _reg[OLATB] = 0x00; } #ifdef __PIC32MX__ MCP23S17::MCP23S17(DSPI &spi, uint8_t cs, uint8_t addr) { #else MCP23S17::MCP23S17(SPIClass &spi, uint8_t cs, uint8_t addr) { #endif _spi = &spi; _cs = cs; _addr = addr; _reg[IODIRA] = 0xFF; _reg[IODIRB] = 0xFF; _reg[IPOLA] = 0x00; _reg[IPOLB] = 0x00; _reg[GPINTENA] = 0x00; _reg[GPINTENB] = 0x00; _reg[DEFVALA] = 0x00; _reg[DEFVALB] = 0x00; _reg[INTCONA] = 0x00; _reg[INTCONB] = 0x00; _reg[IOCONA] = 0x18; _reg[IOCONB] = 0x18; _reg[GPPUA] = 0x00; _reg[GPPUB] = 0x00; _reg[INTFA] = 0x00; _reg[INTFB] = 0x00; _reg[INTCAPA] = 0x00; _reg[INTCAPB] = 0x00; _reg[GPIOA] = 0x00; _reg[GPIOB] = 0x00; _reg[OLATA] = 0x00; _reg[OLATB] = 0x00; } /*! The begin function performs the initial configuration of the IO expander chip. * Not only does it set up the SPI communications, but it also configures the chip * for address-based communication and sets the default parameters and registers * to sensible values. * * Example: * * myExpander.begin(); * */ void MCP23S17::begin() { _spi->begin(); ::pinMode(_cs, OUTPUT); ::digitalWrite(_cs, HIGH); uint8_t cmd = 0b01000000; ::digitalWrite(_cs, LOW); _spi->transfer(cmd); _spi->transfer(IOCONA); _spi->transfer(0x18); ::digitalWrite(_cs, HIGH); writeAll(); } /*! This private function reads a value from the specified register on the chip and * stores it in the _reg array for later usage. */ void MCP23S17::readRegister(uint8_t addr) { if (addr > 21) { return; } uint8_t cmd = 0b01000001 | ((_addr & 0b111) << 1); ::digitalWrite(_cs, LOW); _spi->transfer(cmd); _spi->transfer(addr); _reg[addr] = _spi->transfer(0xFF); ::digitalWrite(_cs, HIGH); } /*! This private function writes the current value of a register (as stored in the * _reg array) out to the register in the chip. */ void MCP23S17::writeRegister(uint8_t addr) { if (addr > 21) { return; } uint8_t cmd = 0b01000000 | ((_addr & 0b111) << 1); ::digitalWrite(_cs, LOW); _spi->transfer(cmd); _spi->transfer(addr); _spi->transfer(_reg[addr]); ::digitalWrite(_cs, HIGH); } /*! This private function performs a bulk read on all the registers in the chip to * ensure the _reg array contains all the correct current values. */ void MCP23S17::readAll() { uint8_t cmd = 0b01000001 | ((_addr & 0b111) << 1); ::digitalWrite(_cs, LOW); _spi->transfer(cmd); _spi->transfer(0); for (uint8_t i = 0; i < 22; i++) { _reg[i] = _spi->transfer(0xFF); } ::digitalWrite(_cs, HIGH); } /*! This private function performs a bulk write of all the data in the _reg array * out to all the registers on the chip. It is mainly used during the initialisation * of the chip. */ void MCP23S17::writeAll() { uint8_t cmd = 0b01000000 | ((_addr & 0b111) << 1); ::digitalWrite(_cs, LOW); _spi->transfer(cmd); _spi->transfer(0); for (uint8_t i = 0; i < 22; i++) { _spi->transfer(_reg[i]); } ::digitalWrite(_cs, HIGH); } /*! Just like the pinMode() function of the Arduino API, this function sets the * direction of the pin. The first parameter is the pin nimber (0-15) to use, * and the second parameter is the direction of the pin. There are standard * Arduino macros for different modes which should be used. The supported macros are: * * * OUTPUT * * INPUT * * INPUT_PULLUP * * The INPUT_PULLUP mode enables the weak pullup that is available on any pin. * * Example: * * myExpander.pinMode(5, INPUT_PULLUP); */ void MCP23S17::pinMode(uint8_t pin, uint8_t mode) { if (pin >= 16) { return; } uint8_t dirReg = IODIRA; uint8_t puReg = GPPUA; if (pin >= 8) { pin -= 8; dirReg = IODIRB; puReg = GPPUB; } switch (mode) { case OUTPUT: _reg[dirReg] &= ~(1<= 16) { return; } uint8_t dirReg = IODIRA; uint8_t puReg = GPPUA; uint8_t latReg = OLATA; if (pin >= 8) { pin -= 8; dirReg = IODIRB; puReg = GPPUB; latReg = OLATB; } uint8_t mode = (_reg[dirReg] & (1<= 16) { return 0; } uint8_t dirReg = IODIRA; uint8_t portReg = GPIOA; uint8_t latReg = OLATA; if (pin >= 8) { pin -= 8; dirReg = IODIRB; portReg = GPIOB; latReg = OLATB; } uint8_t mode = (_reg[dirReg] & (1<> 8; _reg[OLATA] = val & 0xFF; writeRegister(OLATA); writeRegister(OLATB); } /*! This enables the interrupt functionality of a pin. The interrupt type can be one of: * * * CHANGE * * RISING * * FALLING * * When an interrupt occurs the corresponding port's INT pin will be driven to it's configured * level, and will remain there until either the port is read with a readPort or digitalRead, or the * captured port status at the time of the interrupt is read using getInterruptValue. * * Example: * * myExpander.enableInterrupt(4, RISING); */ void MCP23S17::enableInterrupt(uint8_t pin, uint8_t type) { if (pin >= 16) { return; } uint8_t intcon = INTCONA; uint8_t defval = DEFVALA; uint8_t gpinten = GPINTENA; if (pin >= 8) { pin -= 8; intcon = INTCONB; defval = DEFVALB; gpinten = GPINTENB; } switch (type) { case CHANGE: _reg[intcon] &= ~(1<= 16) { return; } uint8_t gpinten = GPINTENA; if (pin >= 8) { pin -= 8; gpinten = GPINTENB; } _reg[gpinten] &= ~(1<