diff --git a/IODevice.h b/IODevice.h index f3ffb17..72beb9e 100644 --- a/IODevice.h +++ b/IODevice.h @@ -408,5 +408,7 @@ private: #include "IO_MCP23017.h" #include "IO_PCF8574.h" #include "IO_duinoNodes.h" +#include "IO_EXIOExpander.h" + #endif // iodevice_h diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h new file mode 100644 index 0000000..151a319 --- /dev/null +++ b/IO_EXIOExpander.h @@ -0,0 +1,183 @@ +/* + * © 2021, Peter Cole. All rights reserved. + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices. +* This device driver will configure the device on startup, along with +* interacting with the device for all input/output duties. +* +* To create EX-IOExpander devices, these are defined in myHal.cpp: +* (Note the device driver is included by default) +* +* void halSetup() { +* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); +* EXIOExpander::create(800, 18, 0x65, 12, 8); +* } +* +* Note when defining the number of digital and analogue pins, there is no way to sanity check +* this from the device driver, and it is up to the user to define the correct values here. +* +* All pins available on the EX-IOExpander device must be accounted for. +* +* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will +* populate the first part of the specified vpin range, with the analogue pins populating the +* last part of the vpin range. +* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7). +*/ + +#ifndef IO_EX_IOEXPANDER_H +#define IO_EX_IOEXPANDER_H + +#include "I2CManager.h" +#include "DIAG.h" +#include "FSH.h" + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for EX-IOExpander. + */ +class EXIOExpander : public IODevice { +public: + static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); + } + +private: + // Constructor + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + _firstVpin = firstVpin; + _nPins = nPins; + _i2cAddress = i2cAddress; + _numDigitalPins = numDigitalPins; + _numAnaloguePins = numAnaloguePins; + int _dPinArrayLen = (_numDigitalPins + 7) / 8; + addDevice(this); + } + + void _begin() { + // Initialise EX-IOExander device + uint8_t _check = I2CManager.checkAddress(_i2cAddress); + if (I2CManager.exists(_i2cAddress)) { + _digitalOutBuffer[0] = EXIOINIT; + _digitalOutBuffer[1] = _numDigitalPins; + _digitalOutBuffer[2] = _numAnaloguePins; + // Send config, if EXIORDY returned, we're good, otherwise go offline + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + if (_digitalInBuffer[0] != EXIORDY) { + DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + return; + } + // Attempt to get version, if we don't get it, we don't care, don't go offline + // Using digital in buffer in reverse to save RAM + _digitalInBuffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1, &_i2crb); + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; +#ifdef DIAG_IO + _display(); +#endif + } else { + DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + } + } + + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { + if (configType != CONFIGURE_INPUT) return false; + if (paramCount != 1) return false; + bool pullup = params[0]; + int pin = vpin - _firstVpin; + uint8_t mask = 1 << ((pin-_firstVpin) % 8); + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + return true; + } + + int _readAnalogue(VPIN vpin) override { + int pin = vpin - _firstVpin; + _analogueOutBuffer[0] = EXIORDAN; + _analogueOutBuffer[1] = pin; + I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); + return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; + } + + int _read(VPIN vpin) override { + int pin = vpin - _firstVpin; + _digitalOutBuffer[0] = EXIORDD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + return _digitalInBuffer[0]; + } + + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + _digitalOutBuffer[0] = EXIOWRD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = value; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + } + + void _display() override { + int _firstAnalogue, _lastAnalogue; + if (_numAnaloguePins == 0) { + _firstAnalogue = 0; + _lastAnalogue = 0; + } else { + _firstAnalogue = _firstVpin + _numDigitalPins; + _lastAnalogue = _firstVpin + _nPins - 1; + } + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + _i2cAddress, _majorVer, _minorVer, _patchVer, + _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, + _numAnaloguePins, _firstAnalogue, _lastAnalogue, + _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + uint8_t _i2cAddress; + uint8_t _numDigitalPins; + uint8_t _numAnaloguePins; + int _digitalPinBytes; + int _analoguePinBytes; + byte _analogueInBuffer[2]; + byte _analogueOutBuffer[2]; + byte _digitalOutBuffer[3]; + byte _digitalInBuffer[1]; + uint8_t _versionBuffer[3]; + uint8_t _majorVer = 0; + uint8_t _minorVer = 0; + uint8_t _patchVer = 0; + I2CRB _i2crb; + + enum { + EXIOINIT = 0xE0, // Flag to initialise setup procedure + EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup + EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration + EXIOVER = 0xE3, // Flag to get version + EXIORDAN = 0xE4, // Flag to read an analogue input + EXIOWRD = 0xE5, // Flag for digital write + EXIORDD = 0xE6, // Flag to read digital input + }; +}; + +#endif \ No newline at end of file diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 32aa12e..c95aaed 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -20,6 +20,7 @@ #include "IO_HCSR04.h" // Ultrasonic range sensor #include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_DFPlayer.h" // MP3 sound player +//#include "IO_EXTurntable.h" // Turntable-EX turntable controller //========================================================================== @@ -160,6 +161,39 @@ void halSetup() { // DFPlayer::create(10000, 10, Serial1); + //======================================================================= + // The following directive defines an EX-Turntable turntable instance. + //======================================================================= + // EXTurntable::create(VPIN, Number of VPINs, I2C Address) + // + // The parameters are: + // VPIN=600 + // Number of VPINs=1 (Note there is no reason to change this) + // I2C address=0x60 + // + // Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default. + + //EXTurntable::create(600, 1, 0x60); + + + //======================================================================= + // The following directive defines an EX-IOExpander instance. + //======================================================================= + // EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count) + // + // The parameters are: + // VPIN=an available Vpin + // Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation) + // I2C address=an available I2C address (default 0x65) + // + // Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default. + // The first example is for an Arduino Nano with the default pin allocations. + // The second example is for an Arduino Uno using all pins as digital only. + + //EXIOExpander::create(800, 18, 0x65, 12, 8); + //EXIOExpander::create(820, 16, 0x66, 16, 0); + + } #endif