diff --git a/IODevice.h b/IODevice.h index ce47267..f3ffb17 100644 --- a/IODevice.h +++ b/IODevice.h @@ -161,6 +161,8 @@ public: // once the GPIO port concerned has been read. void setGPIOInterruptPin(int16_t pinNumber); + // Method to check if pins will overlap before creating new device. + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); protected: @@ -234,9 +236,6 @@ protected: // pin low if an input changes state. int16_t _gpioInterruptPin = -1; - // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); - // Static support function for subclass creation static void addDevice(IODevice *newDevice); @@ -408,5 +407,6 @@ private: #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_duinoNodes.h" #endif // iodevice_h diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h new file mode 100644 index 0000000..ae7d40c --- /dev/null +++ b/IO_duinoNodes.h @@ -0,0 +1,172 @@ +/* + * © 2022, Chris Harlow. 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 . + */ +#ifndef IO_duinoNodes_h + #define IO_duinoNodes_h +#include +#include "defines.h" +#include "IODevice.h" + +#define PIN_MASK(bit) (0x80>>(bit%8)) +#define GET_BIT(x) (_pinValues[(x)/8] & PIN_MASK((x)) ) +#define SET_BIT(x) _pinValues[(x)/8] |= PIN_MASK((x)) +#define CLR_BIT(x) _pinValues[(x)/8] &= ~PIN_MASK((x)) +#define DIAG_IO + + + +class IO_duinoNodes : public IODevice { + +public: + IO_duinoNodes(VPIN firstVpin, int nPins, + byte clockPin, byte latchPin, byte dataPin, + const byte* pinmap) : + IODevice(firstVpin, nPins) { + + _latchPin=latchPin; + _clockPin=clockPin; + _dataPin=dataPin; + _pinMap=pinmap; + _nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits + _pinValues=(byte*) calloc(_nShiftBytes,1); + // Connect to HAL so my _write, _read and _loop will be called as required. + IODevice::addDevice(this); + } + +// Called by HAL to start handling this device + void _begin() override { + _deviceState = DEVSTATE_NORMAL; + pinMode(_latchPin,OUTPUT); + pinMode(_clockPin,OUTPUT); + pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT); + _display(); + } + +// loop called by HAL supervisor +void _loop(unsigned long currentMicros) override { + if (_pinMap) _loopInput(currentMicros); + else if (_xmitPending) _loopOutput(); +} + +void _loopInput(unsigned long currentMicros) { + + if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do + _prevMicros=currentMicros; + + //set latch to HIGH to freeze & store parallel data + ArduinoPins::fastWriteDigital(_latchPin, HIGH); + delayMicroseconds(1); + //set latch to LOW to enable the data to be transmitted serially + ArduinoPins::fastWriteDigital(_latchPin, LOW); + + // stream in the bitmap using mapping order provided at constructor + for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) { + byte newByte=0; + for (int xmitBit=0;xmitBit<8; xmitBit++) { + ArduinoPins::fastWriteDigital(_clockPin, LOW); + delayMicroseconds(1); + bool data = ArduinoPins::fastReadDigital(_dataPin); + byte map=_pinMap[xmitBit]; + if (data) newByte |= map; + else newByte &= ~map; + ArduinoPins::fastWriteDigital(_clockPin, HIGH); + delayMicroseconds(1); + } + _pinValues[xmitByte]=newByte; + // DIAG(F("DIN %x=%x"),xmitByte, newByte); + } + } + +void _loopOutput() { + // stream out the bitmap (highest pin first) + _xmitPending=false; + ArduinoPins::fastWriteDigital(_latchPin, LOW); + for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) { + ArduinoPins::fastWriteDigital(_dataPin,GET_BIT(xmitBit)); + ArduinoPins::fastWriteDigital(_clockPin,HIGH); + ArduinoPins::fastWriteDigital(_clockPin,LOW); + } + digitalWrite(_latchPin, HIGH); + } + + int _read(VPIN vpin) override { + int pin=vpin - _firstVpin; + bool b=GET_BIT(pin); + return b?1:0; + } + + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + bool oldval=GET_BIT(pin); + bool newval=value!=0; + if (newval==oldval) return; // no change + if (newval) SET_BIT(pin); + else CLR_BIT(pin); + _xmitPending=true; // shift register will be sent on next _loop() + } + + void _display() override { + DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"), + _pinMap?F("IN"):F("OUT"), + (int)_firstVpin, + (int)_firstVpin+_nPins-1, _nShiftBytes*8); + } + +private: + static const unsigned long POLL_MICROS=100000; // 10 / S + unsigned long _prevMicros; + int _nShiftBytes=0; + VPIN _latchPin,_clockPin,_dataPin; + byte* _pinValues; + bool _xmitPending; // Only relevant in output mode + const byte* _pinMap; // NULL in output mode +}; + +class IO_DNIN8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0,7,6,5,1,2,3,4 + static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } + +}; + +class IO_DNIN8K { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7 + static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } +}; + +class IO_DNOU8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL); + } + +}; +#endif \ No newline at end of file diff --git a/Release_Notes/duinoNodes.md b/Release_Notes/duinoNodes.md new file mode 100644 index 0000000..64a2323 --- /dev/null +++ b/Release_Notes/duinoNodes.md @@ -0,0 +1,39 @@ +Using Lew's Duino Gear boards: + +1. DNIN8 Input + This is a shift-register implementation of a digital input collector. + Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software + configuratuion correctly represents the number of boards connected otherwise the results will be meaningless. + + Use in myAnimation.h + + HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 400, 16, 40, 42, 44) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + + + + This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence. + Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second. + + Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes. + + This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable. + + The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards. + + + DNOU8 works the same way, + Use in myAnimation.h + + HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 450, 16, 45, 47, 49) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + +This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.