diff --git a/IODevice.h b/IODevice.h index 5155aaf..99394b4 100644 --- a/IODevice.h +++ b/IODevice.h @@ -1,4 +1,5 @@ /* + * © 2023, Paul Antoine, Discord user @ADUBOURG * © 2021, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API @@ -410,6 +411,7 @@ private: #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_PCF8575.h" #include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" diff --git a/IO_PCF8575.h b/IO_PCF8575.h new file mode 100644 index 0000000..1b271ec --- /dev/null +++ b/IO_PCF8575.h @@ -0,0 +1,106 @@ +/* + * © 2023, Paul Antoine, and Discord user @ADUBOURG + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * 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 PCF8575 is a simple device; it only has one register. The device + * input/output mode and pullup are configured through this, and the + * output state is written and the input state read through it too. + * + * This is accomplished by having a weak resistor in series with the output, + * and a read-back of the other end of the resistor. As an output, the + * pin state is set to 1 or 0, and the output voltage goes to +5V or 0V + * (through the weak resistor). + * + * In order to use the pin as an input, the output is written as + * a '1' in order to pull up the resistor. Therefore the input will be + * 1 unless the pin is pulled down externally, in which case it will be 0. + * + * As a consequence of this approach, it is not possible to use the device for + * inputs without pullups. + */ + +#ifndef IO_PCF8575_H +#define IO_PCF8575_H + +#include "IO_GPIOBase.h" +#include "FSH.h" + +class PCF8575 : public GPIOBase { +public: + static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), I2CAddress, interruptPin); + } + +private: + PCF8575(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, I2CAddress, interruptPin) + { + requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); + } + + // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + void _writeGpioPort() override { + I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + } + + // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. + // Therefore, writing '1' in _writePortModes is enough to set the module to input mode + // and enable pull-up. + void _writePullups() override { } + + // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + void _writePortModes() override { + I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + } + + // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. + // When not in immediate mode, it initiates a request using the request block and returns. + // When the request completes, _processCompletion finishes the operation. + void _readGpioPort(bool immediate) override { + if (immediate) { + uint8_t buffer[2]; + I2CManager.read(_I2CAddress, buffer, 2); + _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + } else { + requestBlock.wait(); // Wait for preceding operation to complete + // Issue new request to read GPIO register + I2CManager.queueRequest(&requestBlock); + } + } + + // This function is invoked when an I/O operation on the requestBlock completes. + void _processCompletion(uint8_t status) override { + if (status == I2C_STATUS_OK) + _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + else + _portInputState = 0xffff; + } + + // Set up device ports + void _setupDevice() override { + _writePortModes(); + _writeGpioPort(); + _writePullups(); + } + + uint8_t inputBuffer[2]; +}; + +#endif \ No newline at end of file diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 5470f76..1c5e701 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -88,6 +88,21 @@ void halSetup() { //PCF8574::create(200, 8, 0x23, 40); + //======================================================================= + // The following directive defines a PCF8575 16-port I2C GPIO Extender module. + //======================================================================= + // The parameters are: + // First Vpin=200 + // Number of VPINs=16 (numbered 200-215) + // I2C address of module=0x23 + + //PCF8575::create(200, 16, 0x23); + + + // Alternative form using INT pin (see above) + + //PCF8575::create(200, 16, 0x23, 40); + //======================================================================= // The following directive defines an HCSR04 ultrasonic ranging module. //=======================================================================