From 08810dafd7eedc7cdb52c10626afb8a4b3ede97d Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Mon, 30 Aug 2021 16:37:06 -0500 Subject: [PATCH 01/49] Update retry counter will also display running total prior to its reset. RCOUNT step included in Verify program will count when Verify fails --- DCC.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DCC.h b/DCC.h index cf1680f..c7dcbe0 100644 --- a/DCC.h +++ b/DCC.h @@ -41,6 +41,7 @@ enum ackOp : byte ITCB7, // If True callback(byte &0x7F) NAKFAIL, // if false callback(-1) FAIL, // callback(-1) + RCOUNT, // increment ackRetry counter STARTMERGE, // Clear bit and byte settings ready for merge pass MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) SETBIT, // sets bit number to next prog byte @@ -115,9 +116,11 @@ public: static inline void setGlobalSpeedsteps(byte s) { globalSpeedsteps = s; }; - static inline void setAckRetry(byte retry) { + static inline int16_t setAckRetry(byte retry) { ackRetry = retry; + ackRetryPSum = ackRetrySum; ackRetrySum = 0; // reset running total + return ackRetryPSum; }; private: @@ -154,6 +157,7 @@ private: static byte ackManagerRetry; static byte ackRetry; static int16_t ackRetrySum; + static int16_t ackRetryPSum; static int ackManagerWord; static byte ackManagerStash; static bool ackReceived; From 4b87c879a9d5dc32c97d205a08b222a9a692cb4b Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Mon, 30 Aug 2021 16:52:50 -0500 Subject: [PATCH 02/49] RCOUNT step added to Verify byte program --- DCC.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index 79c5b19..f5470b2 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -359,6 +359,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = { BASELINE, VB,WACK, // validate byte ITCB, // if ok callback value + RCOUNT, // increment ackRetry counter STARTMERGE, //clear bit and byte values ready for merge pass // each bit is validated against 0 and the result inverted in MERGE // this is because there tend to be more zeros in cv values than ones. @@ -693,6 +694,7 @@ int DCC::ackManagerWord; byte DCC::ackManagerRetry; byte DCC::ackRetry = 2; int16_t DCC::ackRetrySum; +int16_t DCC::ackRetryPSum; int DCC::ackManagerCv; byte DCC::ackManagerBitNum; bool DCC::ackReceived; @@ -844,7 +846,11 @@ void DCC::ackManagerLoop() { case FAIL: // callback(-1) callback(-1); return; - + + case RCOUNT: // ackRetry counter + ackRetrySum++; + break; + case STARTMERGE: ackManagerBitNum=7; ackManagerByte=0; From b4fb76b6c865461318ca0923818401e383f12d0c Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Mon, 30 Aug 2021 17:02:05 -0500 Subject: [PATCH 03/49] Display running total ackRetrySum RCOUNT added to Verify program to report if Read step occurs. Report ackRetrySum on LCD when is sent --- DCCEXParser.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 91039f7..d09aa1b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -820,8 +820,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) LCD(0, F("Ack Max=%dus"), p[2]); // } else if (p[1] == HASH_KEYWORD_RETRY) { if (p[2] >255) p[2]=3; - DCC::setAckRetry(p[2]); - LCD(0, F("Ack Retry=%d"), p[2]); // + LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // } } else { StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off")); From 8d471d9f3f1e3e016c5f1909c69e178a1bd53719 Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:19:03 -0500 Subject: [PATCH 04/49] Restore ackManagerByte before retry Verify --- DCC.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DCC.h b/DCC.h index c7dcbe0..8cb5d97 100644 --- a/DCC.h +++ b/DCC.h @@ -38,10 +38,11 @@ enum ackOp : byte ITC1, // If True Callback(1) (if prevous WACK got an ACK) ITC0, // If True callback(0); ITCB, // If True callback(byte) + ITCBV, // If True callback(byte) - end of Verify Byte ITCB7, // If True callback(byte &0x7F) NAKFAIL, // if false callback(-1) FAIL, // callback(-1) - RCOUNT, // increment ackRetry counter + BIV, // Set ackManagerByte to initial value for Verify retry STARTMERGE, // Clear bit and byte settings ready for merge pass MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes) SETBIT, // sets bit number to next prog byte @@ -152,6 +153,7 @@ private: static ackOp const *ackManagerProg; static ackOp const *ackManagerProgStart; static byte ackManagerByte; + static byte ackManagerByteVerify; static byte ackManagerBitNum; static int ackManagerCv; static byte ackManagerRetry; From 4dff8a2b50ac23ce6aaba81fac0b870e9047d1ce Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:43:24 -0500 Subject: [PATCH 05/49] Restore ackManagerByte before retry Verify Identify where initial value was not verified, but initial value returned with subsequent Read. RCOUNT removed. BIV and ITCBV added. --- DCC.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index f5470b2..5165f72 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -357,9 +357,9 @@ const ackOp FLASH WRITE_BYTE_PROG[] = { const ackOp FLASH VERIFY_BYTE_PROG[] = { BASELINE, + BIV, // ackManagerByte initial value VB,WACK, // validate byte - ITCB, // if ok callback value - RCOUNT, // increment ackRetry counter + ITCB, // if ok callback value STARTMERGE, //clear bit and byte values ready for merge pass // each bit is validated against 0 and the result inverted in MERGE // this is because there tend to be more zeros in cv values than ones. @@ -377,7 +377,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, V0, WACK, MERGE, - VB, WACK, ITCB, // verify merged byte and return it if acked ok + VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report FAIL }; @@ -689,6 +689,7 @@ int DCC::nextLoco = 0; ackOp const * DCC::ackManagerProg; ackOp const * DCC::ackManagerProgStart; byte DCC::ackManagerByte; +byte DCC::ackManagerByteVerify; byte DCC::ackManagerStash; int DCC::ackManagerWord; byte DCC::ackManagerRetry; @@ -730,6 +731,7 @@ void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[] ackManagerProgStart = program; ackManagerRetry = ackRetry; ackManagerByte = byteValueOrBitnum; + ackManagerByteVerify = byteValueOrBitnum; ackManagerBitNum=byteValueOrBitnum; ackManagerCallback = callback; } @@ -828,7 +830,18 @@ void DCC::ackManagerLoop() { return; } break; - + + case ITCBV: // If True callback(byte) - Verify + if (ackReceived) { + if (ackManagerByte == ackManagerByteVerify) { + ackRetrySum ++; + LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum); + } + callback(ackManagerByte); + return; + } + break; + case ITCB7: // If True callback(byte & 0x7F) if (ackReceived) { callback(ackManagerByte & 0x7F); @@ -847,8 +860,8 @@ void DCC::ackManagerLoop() { callback(-1); return; - case RCOUNT: // ackRetry counter - ackRetrySum++; + case BIV: // ackManagerByte initial value + ackManagerByte = ackManagerByteVerify; break; case STARTMERGE: @@ -919,7 +932,7 @@ void DCC::callback(int value) { // check for automatic retry if (value == -1 && ackManagerRetry > 0) { ackRetrySum ++; - LCD(0, F("RETRY %d %d %d %d"), ackManagerCv, ackManagerRetry, ackRetry, ackRetrySum); + LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum); ackManagerRetry --; ackManagerProg = ackManagerProgStart; return; From f8311b8c56a7793db210884cde7c385c475b2cbb Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Wed, 8 Sep 2021 14:00:42 -0500 Subject: [PATCH 06/49] line added sentResetsSincePacket in DCCWaveform::setPowerMode(POWERMODE mode) -- to pause while power is off due to PROG TRACK POWER OVERLOAD and line added after case BASELINE in DCC.cpp --- DCCWaveform.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 0d23897..a82d36c 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -114,6 +114,7 @@ void DCCWaveform::setPowerMode(POWERMODE mode) { powerMode = mode; bool ison = (mode == POWERMODE::ON); motorDriver->setPower( ison); + sentResetsSincePacket=0; } From ebabbbe59e07685fad80941b34440cd23ce68f78 Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Wed, 8 Sep 2021 14:06:39 -0500 Subject: [PATCH 07/49] pause program steps if OVERLOAD line added to pause program steps during OVERLOAD. case BASELINE if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return; -- also added a line in DCCWaveform.cpp --- DCC.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DCC.cpp b/DCC.cpp index 5165f72..f747362 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -757,6 +757,7 @@ void DCC::ackManagerLoop() { // (typically waiting for a reset counter or ACK waiting, or when all finished.) switch (opcode) { case BASELINE: + if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return; if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return; DCCWaveform::progTrack.setAckBaseline(); callbackState=READY; From 254d83b6fc6e1e6cec06842331eb18d9b1c144e7 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 9 Sep 2021 10:12:27 +0100 Subject: [PATCH 08/49] Remove SERIAL warning --- RMFTMacros.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/RMFTMacros.h b/RMFTMacros.h index fc5140f..c66dc3c 100644 --- a/RMFTMacros.h +++ b/RMFTMacros.h @@ -19,8 +19,9 @@ #ifndef RMFTMacros_H #define RMFTMacros_H -// remove normal code LCD macro (will be restored later) +// remove normal code LCD & SERIAL macros (will be restored later) #undef LCD +#undef SERIAL // This file will include and build the EXRAIL script and associated helper tricks. @@ -299,8 +300,9 @@ const int StringMacroTracker1=__COUNTER__; const int StringMacroTracker2=__COUNTER__; #include "myAutomation.h" -// Restore normal code LCD macro +// Restore normal code LCD & SERIAL macro #undef LCD #define LCD StringFormatter::lcd - +#undef SERIAL +#define SERIAL 0x0 #endif From 70b59d491c547096a091af7b92fba6f70ece0a73 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 9 Sep 2021 10:23:27 +0100 Subject: [PATCH 09/49] Ash's OVERLOAD check Makes prog track accesses wait if track in overload --- DCC.cpp | 1 + DCCWaveform.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/DCC.cpp b/DCC.cpp index 79c5b19..0674fce 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -753,6 +753,7 @@ void DCC::ackManagerLoop() { // (typically waiting for a reset counter or ACK waiting, or when all finished.) switch (opcode) { case BASELINE: + if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return; if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return; DCCWaveform::progTrack.setAckBaseline(); callbackState=READY; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 0d23897..dd447ca 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -114,6 +114,7 @@ void DCCWaveform::setPowerMode(POWERMODE mode) { powerMode = mode; bool ison = (mode == POWERMODE::ON); motorDriver->setPower( ison); + sentResetsSincePacket=0; } From 2ed578821ffec6103342285e6b8a2ebf132a2fad Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 11 Sep 2021 13:35:11 +0100 Subject: [PATCH 10/49] Add analogue inputs to HAL. Add ability to read analogue inputs on arduino and on external ADS1115 I2C modules. --- IODevice.cpp | 54 +++++++++++++++++- IODevice.h | 14 ++++- IO_AnalogueInputs.h | 131 ++++++++++++++++++++++++++++++++++++++++++++ platformio.ini | 3 +- 4 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 IO_AnalogueInputs.h diff --git a/IODevice.cpp b/IODevice.cpp index e19ddef..98584ca 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -241,6 +241,18 @@ int IODevice::read(VPIN vpin) { return false; } +// Read analogue value from virtual pin. +int IODevice::readAnalogue(VPIN vpin) { + for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { + if (dev->owns(vpin)) + return dev->_readAnalogue(vpin); + } +#ifdef DIAG_IO + //DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); +#endif + return false; +} + #else // !defined(IO_NO_HAL) @@ -259,6 +271,13 @@ int IODevice::read(VPIN vpin) { pinMode(vpin, INPUT_PULLUP); return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1) } +int IODevice::readAnalogue(VPIN vpin) { + pinMode(vpin, INPUT); + noInterrupts(); + int value = analogRead(vpin); + interrupts(); + return value; +} void IODevice::loop() {} void IODevice::DumpAll() { DIAG(F("NO HAL CONFIGURED!")); @@ -330,7 +349,7 @@ void ArduinoPins::_write(VPIN vpin, int value) { } } -// Device-specific read function. +// Device-specific read function (digital input). int ArduinoPins::_read(VPIN vpin) { int pin = vpin; uint8_t mask = 1 << ((pin-_firstVpin) % 8); @@ -352,6 +371,39 @@ int ArduinoPins::_read(VPIN vpin) { return value; } +// Device-specific readAnalogue function (analogue input) +int ArduinoPins::_readAnalogue(VPIN vpin) { + int pin = vpin; + uint8_t mask = 1 << ((pin-_firstVpin) % 8); + uint8_t index = (pin-_firstVpin) / 8; + if (_pinModes[index] & mask) { + // Currently in write mode, change to read mode + _pinModes[index] &= ~mask; + // Since mode changes should be infrequent, use standard pinMode function + if (_pinPullups[index] & mask) + pinMode(pin, INPUT_PULLUP); + else + pinMode(pin, INPUT); + } + // Since AnalogRead is also called from interrupt code, disable interrupts + // while we're using it. There's only one ADC shared by all analogue inputs + // on the Arduino, so we don't want interruptions. + //****************************************************************************** + // NOTE: If the HAL is running on a computer without the DCC signal generator, + // then interrupts needn't be disabled. Also, the DCC signal generator puts + // the ADC into fast mode, so if it isn't present, analogueRead calls will be much + // slower!! + //****************************************************************************** + noInterrupts(); + int value = analogRead(pin); + interrupts(); + + #ifdef DIAG_IO + //DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value); + #endif + return value; +} + void ArduinoPins::_display() { DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1); } diff --git a/IODevice.h b/IODevice.h index 65f5d35..58de64b 100644 --- a/IODevice.h +++ b/IODevice.h @@ -141,6 +141,9 @@ public: // read invokes the IODevice instance's _read method. static int read(VPIN vpin); + // read invokes the IODevice instance's _readAnalogue method. + static int readAnalogue(VPIN vpin); + // loop invokes the IODevice instance's _loop method. static void loop(); @@ -188,12 +191,18 @@ protected: return false; } - // Method to read pin state (optionally implemented within device class) + // Method to read digital pin state (optionally implemented within device class) virtual int _read(VPIN vpin) { (void)vpin; return 0; }; + // Method to read analogue pin state (optionally implemented within device class) + virtual int _readAnalogue(VPIN vpin) { + (void)vpin; + return 0; + }; + // _isBusy returns true if the device is currently in an animation of some sort, e.g. is changing // the output over a period of time. Returns false unless overridden in sub class. virtual bool _isBusy(VPIN vpin) { @@ -343,8 +352,9 @@ private: bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; // Device-specific write function. void _write(VPIN vpin, int value) override; - // Device-specific read function. + // Device-specific read functions. int _read(VPIN vpin) override; + int _readAnalogue(VPIN vpin) override; void _display() override; diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h new file mode 100644 index 0000000..4c73329 --- /dev/null +++ b/IO_AnalogueInputs.h @@ -0,0 +1,131 @@ +/* + * © 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 . + */ + +#ifndef io_analogueinputs_h +#define io_analogueinputs_h + +// Uncomment following line to slow the scan cycle down to 1second ADC samples, with +// diagnostic output of scanned values. +//#define IO_ANALOGUE_SLOW + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" +#include "FSH.h" + +/********************************************************************************************** + * ADS111x class for I2C-connected analogue input modules ADS1113, ADS1114 and ADS1115. + * + * ADS1113 and ADS1114 are restricted to 1 input. ADS1115 has a multiplexer which allows + * any of four input pins to be read by its ADC. + * + * The driver polls the device in accordance with the constant 'scanInterval' below. On first loop + * entry, the multiplexer is set to pin A0 and the ADC is triggered. On second and subsequent + * entries, the analogue value is read from the conversion register and then the multiplexer and + * ADC are set up to read the next pin. + * + * The ADS111x is set up as follows: + * Single-shot scan + * Data rate 128 samples/sec (7.8ms/sample) + * Comparator off + * Gain FSR=6.144V + * The gain means that the maximum input voltage of 5V (when Vss=5V) gives a reading + * of 32767*(5.0/6.144) = 26666. + * + * Note: The device is simple and does not need initial configuration, so it should recover from + * temporary loss of communications or power. + **********************************************************************************************/ +class ADS111x: public IODevice { +public: + ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + _firstVpin = firstVpin; + _nPins = min(nPins,4); + _i2cAddress = i2cAddress; + _currentPin = _nPins; // Suppress read on first loop entry. + addDevice(this); + } + static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + new ADS111x(firstVpin, nPins, i2cAddress); + } + void _begin() { + // Initialise ADS device + if (I2CManager.exists(_i2cAddress)) { +#ifdef DIAG_IO + _display(); +#endif + } else { + DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress); + } + } + void _loop(unsigned long currentMicros) { + + if (currentMicros - _lastMicros >= scanInterval) { + // Check that previous non-blocking write has completed, if not then wait + _i2crb.wait(); + + // If _currentPin is in the valid range, continue reading the pin values + if (_currentPin < _nPins) { + _outBuffer[0] = 0x00; // Conversion register address + uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, 1, _outBuffer); // Read register + if (status == I2C_STATUS_OK) { + _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; + #ifdef IO_ANALOGUE_SLOW + DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); + #endif + } + } + // Move to next pin + if (++_currentPin >= _nPins) _currentPin = 0; + + // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details + // of configuration register settings. + _outBuffer[0] = 0x01; // Config register address + _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n + _outBuffer[2] = 0x83; // 128 samples/sec, comparator off + // Write command, without waiting for completion. + I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); + + _lastMicros = currentMicros; + } + } + int _readAnalogue(VPIN vpin) { + int pin = vpin - _firstVpin; + return _value[pin]; + } + void _display() { + DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1); + } + +protected: + // With ADC set to 128 samples/sec, that's 7.8ms/sample. So set the period between updates to 10ms + #ifndef IO_ANALOGUE_SLOW + const unsigned long scanInterval = 10000UL; // Period between successive ADC scans in microseconds. + #else + const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds. + #endif + uint16_t _value[4]; + uint8_t _i2cAddress; + uint8_t _outBuffer[3]; + uint8_t _inBuffer[2]; + uint8_t _currentPin; // ADC pin currently being scanned + unsigned long _lastMicros = 0; + I2CRB _i2crb; +}; + +#endif // io_analogueinputs_h \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 953a658..8bba297 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,6 +16,7 @@ default_envs = unowifiR2 nano src_dir = . +include_dir = . [env] build_flags = -Wall -Wextra @@ -41,7 +42,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_flags = --echo -build_flags = -DDIAG_IO +build_flags = -DDIAG_IO -DDIAG_LOOPTIMES [env:mega2560-no-HAL] platform = atmelavr From d316b720697d47e91e70ea293212efa80a5eacff Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 14 Sep 2021 12:34:31 +0100 Subject: [PATCH 11/49] VL53L0X Time-Of-Flight sensor driver HAL Driver for VL53L0X Time-Of-Flight sensor. Basic implementation, which doesn't include most of the calibration etc. so is very lean on memory and CPU but not as accurate as it could be. --- IO_VL53L0X.h | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 IO_VL53L0X.h diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h new file mode 100644 index 0000000..a0ac902 --- /dev/null +++ b/IO_VL53L0X.h @@ -0,0 +1,245 @@ +/* + * © 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 VL53L0X Time-Of-Flight sensor operates by sending a short laser pulse and detecting + * the reflection of the pulse. The time between the pulse and the receipt of reflections + * is measured and used to determine the distance to the reflecting object. + * + * For economy of memory and processing time, this driver includes only part of the code + * that ST provide in their API. Also, the API code isn't very clear and it is not easy + * to identify what operations are useful and what are not. + * The operation shown here doesn't include any calibration, so is probably not as accurate + * as using the full driver, but it's probably accurate enough for the purpose. + * + * The device driver allocates up to 3 vpins to the device. A digital read on any of the pins + * will return a value that indicates whether the object is within the threshold range (1) + * or not (0). An analogue read on the first pin returns the last measured distance (in mm), + * the second pin returns the signal strength, and the third pin returns detected + * ambient light level. + * + * The VL53L0X is initially set to respond to I2C address 0x29. If you only have one module, + * you can use this address. However, the address can be modified by software. If + * you select another address, that address will be written to the device and used until the device is reset. + * + * If you have more than one module, then you will need to specify a digital VPIN (Arduino + * digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now, + * when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once + * all VL53L0X modules are turned off, the driver works through each module in turn by + * setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address. + * In this way, many VL53L0X modules can be connected to the one I2C bus, each one + * using with a distinct I2C address. + * + * The driver is configured as follows: + * + * Single VL53L0X module: + * VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold); + * Where firstVpin is the first vpin reserved for reading the device, + * nPins is 1, 2 or 3, + * i2cAddress is the address of the device (normally 0x29), + * lowThreshold is the distance at which the digital vpin state is set to 1 (in mm), + * and highThreshold is the distance at which the digital vpin state is set to 0 (in mm). + * + * Multiple VL53L0X modules: + * VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold, xshutPin); + * ... + * Where firstVpin is the first vpin reserved for reading the device, + * nPins is 1, 2 or 3, + * i2cAddress is the address of the device (any valid address except 0x29), + * lowThreshold is the distance at which the digital vpin state is set to 1 (in mm), + * highThreshold is the distance at which the digital vpin state is set to 0 (in mm), + * and xshutPin is the VPIN number corresponding to a digital output that is connected to the + * XSHUT terminal on the module. + * + * Example: + * In mySetup function within mySetup.cpp: + * VL53L0X::create(4000, 3, 0x29, 200, 250); + * Sensor::create(4000, 4000, 0); // Create a sensor + * + * When an object comes within 200mm of the sensor, a message + * + * will be sent over the serial USB, and when the object moves more than 250mm from the sensor, + * a message + * + * will be sent. + * + */ + +#ifndef IO_VL53L0X_h +#define IO_VL53L0X_h + +#include "IODevice.h" + +class VL53L0X : public IODevice { +private: + uint8_t _i2cAddress; + uint16_t _ambient; + uint16_t _distance; + uint16_t _signal; + uint16_t _onThreshold; + uint16_t _offThreshold; + uint8_t _xshutPin; + bool _value; + bool _initialising = true; + uint8_t _entryCount = 0; + unsigned long _lastEntryTime = 0; + bool _scanInProgress = false; + // Register addresses + enum : uint8_t { + VL53L0X_REG_SYSRANGE_START=0x00, + VL53L0X_REG_RESULT_INTERRUPT_STATUS=0x13, + VL53L0X_REG_RESULT_RANGE_STATUS=0x14, + VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS=0x8A, + }; + const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29; + +public: + VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + _firstVpin = firstVpin; + _nPins = min(nPins, 3); + _i2cAddress = i2cAddress; + _onThreshold = onThreshold; + _offThreshold = offThreshold; + _xshutPin = xshutPin; + _value = 0; + addDevice(this); + } + static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); + } + +protected: + void _begin() override { + _initialising = true; + // Check if device is already responding on the nominated address. + if (I2CManager.exists(_i2cAddress)) { + // Yes, it's already on this address, so skip the address initialisation. + _entryCount = 3; + } else { + _entryCount = 0; + } + } + void _loop(unsigned long currentMicros) override { + if (_initialising) { + switch (_entryCount++) { + case 0: + // On first entry to loop, reset this module by pulling XSHUT low. All modules + // will be reset in turn. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); + break; + case 1: + // On second entry, set XSHUT pin high to allow the module to restart. + // On the module, there is a diode in series with the XSHUT pin to + // protect the low-voltage pin against +5V. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); + // Allow the module time to restart + delay(10); + // Then write the desired I2C address to the device, while this is the only + // module responding to the default address. + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); + break; + case 3: + if (I2CManager.exists(_i2cAddress)) { + _display(); + } + _initialising = false; + _entryCount = 0; + break; + default: + break; + } + } else if (_lastEntryTime - currentMicros > 10000UL) { + // Service device every 10ms + _lastEntryTime = currentMicros; + + if (!_scanInProgress) { + // Not scanning, so initiate a scan + write_reg(VL53L0X_REG_SYSRANGE_START, 0x01); + _scanInProgress = true; + + } else { + // Scan in progress, so check for completion. + uint8_t status = read_reg(VL53L0X_REG_RESULT_RANGE_STATUS); + if (status & 1) { + // Completed. Retrieve data + uint8_t inBuffer[12]; + read_registers(VL53L0X_REG_RESULT_RANGE_STATUS, inBuffer, 12); + uint8_t deviceRangeStatus = ((inBuffer[0] & 0x78) >> 3); + if (deviceRangeStatus == 0x0b) { + // Range status OK, so use data + _ambient = makeuint16(inBuffer[7], inBuffer[6]); + _signal = makeuint16(inBuffer[9], inBuffer[8]); + _distance = makeuint16(inBuffer[11], inBuffer[10]); + if (_distance <= _onThreshold) + _value = true; + else if (_distance > _offThreshold) + _value = false; + } + _scanInProgress = false; + } + } + } + } + // For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level. + int _readAnalogue(VPIN vpin) override { + int pin = vpin - _firstVpin; + switch (pin) { + case 0: + return _distance; + case 1: + return _signal; + case 2: + return _ambient; + default: + return -1; + } + } + // For digital read, return the same value for all pins. + int _read(VPIN vpin) override { + return _value; + } + void _display() override { + DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm"), + _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold); + } + + +private: + inline uint16_t makeuint16(byte lsb, byte msb) { + return (((uint16_t)msb) << 8) | lsb; + } + void write_reg(uint8_t reg, uint8_t data) { + // write byte to register + uint8_t outBuffer[2]; + outBuffer[0] = reg; + outBuffer[1] = data; + I2CManager.write(_i2cAddress, outBuffer, 2); + } + uint8_t read_reg(uint8_t reg) { + // read byte from register register + uint8_t inBuffer[1]; + I2CManager.read(_i2cAddress, inBuffer, 1, ®, 1); + return inBuffer[0]; + } + void read_registers(uint8_t reg, uint8_t buffer[], uint8_t size) { + I2CManager.read(_i2cAddress, buffer, size, ®, 1); + } +}; + +#endif // IO_VL53L0X_h From f7d34b92ee0e81fa46e4a71e3a76164922ae1c1b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 14 Sep 2021 17:14:29 +0100 Subject: [PATCH 12/49] Update mySetup.cpp_example.txt --- mySetup.cpp_example.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mySetup.cpp_example.txt b/mySetup.cpp_example.txt index 949088a..0e2be50 100644 --- a/mySetup.cpp_example.txt +++ b/mySetup.cpp_example.txt @@ -13,6 +13,7 @@ #include "Turnouts.h" #include "Sensors.h" #include "IO_HCSR04.h" +#include "IO_VL53L0X.h" // The #if directive prevent compile errors for Uno and Nano by excluding the @@ -89,6 +90,27 @@ //HCSR04 sonarModule1(2000, 30, 31, 20, 25); //HCSR04 sonarModule2(2001, 30, 32, 20, 25); +// The following directive defines a single VL53L0X Time-of-Flight sensor. +// The parameters are: +// VPIN=5000 +// Number of VPINs=1 +// I2C address=0x29 (default for this chip) +// Minimum trigger range=200mm (VPIN goes to 1 when <20cm) +// Maximum trigger range=250mm (VPIN goes to 0 when >25cm) + +//VL53L0X tofModule1(5000, 1, 0x29, 200, 250); + +// For multiple VL53L0X modules, add another parameter which is a VPIN connected to the +// module's XSHUT pin. This allows the modules to be configured, at start, +// with distinct I2C addresses. In this case, the address 0x29 is only used during +// initialisation to configure each device with the desired unique I2C address. +// The examples below have one module's XSHUT pin connected to Arduino pin 34, +// and the other's connected to the second pin on the first MCP23017 module (VPIN 165). +// The first module is given I2C address 0x30 and the second is 0x31. + +//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 34); +//VL53L0X tofModule2(5001, 1, 0x31, 200, 250, 165); + // The function mySetup() is invoked from CS if it exists within the build. // It is called just before mysetup.h is executed, so things set up within here can be From 02a715d54d29cbc2d35a62221d37e53d722869f1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Sep 2021 00:23:24 +0100 Subject: [PATCH 13/49] New DFPlayer MP3 device, and tidy comments in other drivers. --- IO_AnalogueInputs.h | 8 +- IO_DFPlayer.h | 219 ++++++++++++++++++++++++++++++++++++++++++++ IO_HCSR04.h | 68 ++++++++------ IO_VL53L0X.h | 8 +- 4 files changed, 273 insertions(+), 30 deletions(-) create mode 100644 IO_DFPlayer.h diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 4c73329..0628bc9 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -42,12 +42,18 @@ * * The ADS111x is set up as follows: * Single-shot scan - * Data rate 128 samples/sec (7.8ms/sample) + * Data rate 128 samples/sec (7.8ms/sample, but scanned every 10ms) * Comparator off * Gain FSR=6.144V * The gain means that the maximum input voltage of 5V (when Vss=5V) gives a reading * of 32767*(5.0/6.144) = 26666. * + * A device is configured by the following: + * ADS111x::create(firstVpin, nPins, i2cAddress); + * for example + * ADS111x::create(300, 1, 0x48); // single-input ADS1113 + * ADS111x::create(300, 4, 0x48); // four-input ADS1115 + * * Note: The device is simple and does not need initial configuration, so it should recover from * temporary loss of communications or power. **********************************************************************************************/ diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h new file mode 100644 index 0000000..bb0e5c7 --- /dev/null +++ b/IO_DFPlayer.h @@ -0,0 +1,219 @@ +/* + * © 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 . + */ + +/* + * DFPlayer is an MP3 player module with an SD card holder. It also has an integrated + * amplifier, so it only needs a power supply and a speaker. + * + * This driver allows the device to be controlled through IODevice::write() and + * IODevice::writeAnalogue() calls. + * + * The driver is configured as follows: + * + * DFPlayer::create(firstVpin, nPins, Serialn); + * Where firstVpin is the first vpin reserved for reading the device, + * nPins is the number of pins to be allocated (max 5) + * and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial2). + * + * + * Example: + * In mySetup function within mySetup.cpp: + * DFPlayer::create(3500, 5, Serial2); + * Writing a value 0-2999 to the first pin will select a numbered file from the SD card; + * Writing a value 0-30 to the second pin will set the volume of the output; + * Writing a digital value to the first pin will play or stop the file; + * + * From EX-RAIL, the following commands may be used: + * SET(3500) -- starts playing the first file on the SD card + * SET(3501) -- starts playing the second file on the SD card + * etc. + * RESET(3500) -- stops all playing on the player + * WAITFOR(3500) -- wait for the file currently being played by the player to complete + * + * NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly + * to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse. + * A 1k resistor in series with the module's RX terminal will alleviate this. + */ + +#ifndef IO_DFPlayer_h +#define IO_DFPlayer_h + +#include "IODevice.h" + +class DFPlayer : public IODevice { +private: + HardwareSerial *_serial; + bool _playing = false; + uint8_t _inputIndex = 0; + +public: + DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) { + _firstVpin = firstVpin; + _nPins = min(nPins, 3); + _serial = &serial; + addDevice(this); + } + static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) { + new DFPlayer(firstVpin, nPins, serial); + } + +protected: + void _begin() override { + _serial->begin(9600); + _display(); + } + + void _loop(unsigned long) override { + // Check for incoming data on _serial, and update busy flag accordingly. + // Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF" + while (_serial->available()) { + int c = _serial->read(); +// DIAG(F("Received: %x"), c); + if (c == 0x7E) + _inputIndex = 1; + else if ((c==0xFF && _inputIndex==1) || (c==0x06 && _inputIndex==2) + || (c==0x3D && _inputIndex==3) || (_inputIndex >=4 && _inputIndex <= 8)) + _inputIndex++; + else if (c==0xEF && _inputIndex==9) { + // End of play + #ifdef DIAG_IO + DIAG(F("DFPlayer: Finished")); + #endif + _playing = false; + _inputIndex = 0; + } + } + } + + // Write with value 1 starts playing a song. The relative pin number is the file number. + // Write with value 0 stops playing. + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + if (value) { + // Value 1, start playing + #ifdef DIAG_IO + DIAG(F("DFPlayer: Play %d"), pin+1); + #endif + sendPacket(0x03, pin+1); + _playing = true; + } else { + // Value 0, stop playing + #ifdef DIAG_IO + DIAG(F("DFPlayer: Stop")); + #endif + sendPacket(0x16); + _playing = false; + } + } + + // WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0. + // If value is zero, it stops playing. + // WriteAnalogue on second pin sets the output volume. + void _writeAnalogue(VPIN vpin, int value, uint8_t, uint16_t) override { + uint8_t pin = _firstVpin - vpin; + switch (pin) { + case 0: + if (value > 0) { + // Play global track + if (value > 2999) value = 2999; + #ifdef DIAG_IO + DIAG(F("DFPlayer: Play %d"), value); + #endif + sendPacket(0x03, value); + _playing = true; + } else { + #ifdef DIAG_IO + DIAG(F("DFPlayer: Stop")); + #endif + sendPacket(0x16); + _playing = false; + } + break; + case 1: + // Set volume (0-30) + if (value > 30) value = 30; + else if (value < 0) value = 0; + #ifdef DIAG_IO + DIAG(F("DFPlayer: Volume %d"), value); + #endif + sendPacket(0x06, value); + break; + default: + break; + } + } + + bool _isBusy(VPIN vpin) override { + (void)vpin; // avoid compiler warning. + return _playing; + } + + void _display() override { + DIAG(F("DFPlayer Configured on Vpins:%d-%d")); + } + +private: + // 7E FF 06 0F 00 01 01 xx xx EF + // 0 -> 7E is start code + // 1 -> FF is version + // 2 -> 06 is length + // 3 -> 0F is command + // 4 -> 00 is no receive + // 5~6 -> 01 01 is argument + // 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 ) + // 9 -> EF is end code + + void sendPacket(uint8_t command, uint16_t arg = 0) + { + uint8_t out[] = { 0x7E, + 0xFF, + 06, + command, + 00, + static_cast(arg >> 8), + static_cast(arg & 0x00ff), + 00, + 00, + 0xEF }; + + setChecksum(out); + + _serial->write(out, sizeof(out)); + } + + uint16_t calcChecksum(uint8_t* packet) + { + uint16_t sum = 0; + for (int i = 1; i < 7; i++) + { + sum += packet[i]; + } + return -sum; + } + + void setChecksum(uint8_t* out) + { + uint16_t sum = calcChecksum(out); + + out[7] = (sum >> 8); + out[8] = (sum & 0xff); + } +}; + +#endif // IO_DFPlayer_h diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 98340ff..76b8493 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -17,31 +17,37 @@ * along with CommandStation. If not, see . */ -/* +/* * The HC-SR04 module has an ultrasonic transmitter (40kHz) and a receiver. - * It is operated through two signal pins. When the transmit pin is set to 1 for - * 10us, on the falling edge the transmitter sends a short transmission of + * It is operated through two signal pins. When the transmit pin is set to 1 + * for 10us, on the falling edge the transmitter sends a short transmission of * 8 pulses (like a sonar 'ping'). This is reflected off objects and received * by the receiver. A pulse is sent on the receive pin whose length is equal * to the delay between the transmission of the pulse and the detection of * its echo. The distance of the reflecting object is calculated by halving * the time (to allow for the out and back distance), then multiplying by the * speed of sound (assumed to be constant). - * + * * This driver polls the HC-SR04 by sending the trigger pulse and then measuring - * the length of the received pulse. If the calculated distance is less than the - * threshold, the output changes to 1. If it is greater than the threshold plus - * a hysteresis margin, the output changes to 0. - * - * The measurement would be more reliable if interrupts were disabled while the - * pulse is being timed. However, this would affect other functions in the CS - * so the measurement is being performed with interrupts enabled. Also, we could - * use an interrupt pin in the Arduino for the timing, but the same consideration - * applies. - * - * Note: The timing accuracy required by this means that the pins have to be - * direct Arduino pins; GPIO pins on an IO Extender cannot provide the required - * accuracy. + * the length of the received pulse. If the calculated distance is less than + * the threshold, the output state returned by a read() call changes to 1. If + * the distance is greater than the threshold plus a hysteresis margin, the + * output changes to 0. The device also supports readAnalogue(), which returns + * the measured distance in cm, or 32767 if the distance exceeds the + * offThreshold. + * + * It might be thought that the measurement would be more reliable if interrupts + * were disabled while the pulse is being timed. However, this would affect + * other functions in the CS so the measurement is being performed with + * interrupts enabled. Also, we could use an interrupt pin in the Arduino for + * the timing, but the same consideration applies. In any case, the DCC + * interrupt occurs once every 58us, so any IRC code is much faster than that. + * And 58us corresponds to 1cm in the calculation, so the effect of + * interrupts is negligible. + * + * Note: The timing accuracy required for measuring the pulse length means that + * the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot + * provide the required accuracy. */ #ifndef IO_HCSR04_H @@ -58,6 +64,8 @@ private: // Thresholds for setting active state in cm. uint8_t _onThreshold; // cm uint8_t _offThreshold; // cm + // Last measured distance in cm. + uint16_t _distance; // Active=1/inactive=0 state uint8_t _value = 0; // Time of last loop execution @@ -101,12 +109,17 @@ protected: return _value; } + int _readAnalogue(VPIN vpin) override { + (void)vpin; // avoid compiler warning + return _distance; + } + // _loop function - read HC-SR04 once every 50 milliseconds. void _loop(unsigned long currentMicros) override { if (currentMicros - _lastExecutionTime > 50000UL) { _lastExecutionTime = currentMicros; - _value = read_HCSR04device(); + read_HCSR04device(); } } @@ -127,12 +140,13 @@ private: // measured distance is less than the onThreshold, and is set to 0 if the measured distance is // greater than the offThreshold. // - uint8_t read_HCSR04device() { + void read_HCSR04device() { // uint16 enough to time up to 65ms uint16_t startTime, waitTime, currentTime, maxTime; // If receive pin is still set on from previous call, abort the read. - if (ArduinoPins::fastReadDigital(_receivePin)) return _value; + if (ArduinoPins::fastReadDigital(_receivePin)) + return; // Send 10us pulse to trigger transmitter ArduinoPins::fastWriteDigital(_transmitPin, 1); @@ -148,7 +162,7 @@ private: waitTime = currentTime - startTime; if (waitTime > maxTime) { // Timeout waiting for pulse start, abort the read - return _value; + return; } } @@ -162,16 +176,16 @@ private: // and finish without waiting for end of pulse. if (waitTime > maxTime) { // Pulse length longer than maxTime, reset value. - return 0; + _value = 0; + _distance = 32767; + return; } } // Check if pulse length is below threshold, if so set value. //DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance); - uint16_t distance = waitTime / factor; // in centimetres - if (distance < _onThreshold) - return 1; - - return _value; + _distance = waitTime / factor; // in centimetres + if (_distance < _onThreshold) + _value = 1; } }; diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index a0ac902..08de1aa 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -94,7 +94,7 @@ private: uint16_t _signal; uint16_t _onThreshold; uint16_t _offThreshold; - uint8_t _xshutPin; + VPIN _xshutPin; bool _value; bool _initialising = true; uint8_t _entryCount = 0; @@ -105,6 +105,7 @@ private: VL53L0X_REG_SYSRANGE_START=0x00, VL53L0X_REG_RESULT_INTERRUPT_STATUS=0x13, VL53L0X_REG_RESULT_RANGE_STATUS=0x14, + VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV=0x89, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS=0x8A, }; const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29; @@ -157,6 +158,9 @@ protected: case 3: if (I2CManager.exists(_i2cAddress)) { _display(); + // Set 2.8V mode + write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); } _initialising = false; _entryCount = 0; @@ -211,7 +215,7 @@ protected: } } // For digital read, return the same value for all pins. - int _read(VPIN vpin) override { + int _read(VPIN) override { return _value; } void _display() override { From 592f87303e9e506833dc69e04c1b24682acedbf9 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Sep 2021 10:44:43 +0100 Subject: [PATCH 14/49] Update IO_AnalogueInputs.h Increase frequency of ADC conversions to 4ms, since 10ms driver cycle isn't enough time for a 7.8ms conversion to complete reliably. --- IO_AnalogueInputs.h | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 0628bc9..7d24586 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -69,6 +69,7 @@ public: static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { new ADS111x(firstVpin, nPins, i2cAddress); } +private: void _begin() { // Initialise ADS device if (I2CManager.exists(_i2cAddress)) { @@ -79,22 +80,25 @@ public: DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress); } } - void _loop(unsigned long currentMicros) { + void _loop(unsigned long currentMicros) override { if (currentMicros - _lastMicros >= scanInterval) { // Check that previous non-blocking write has completed, if not then wait - _i2crb.wait(); - - // If _currentPin is in the valid range, continue reading the pin values - if (_currentPin < _nPins) { - _outBuffer[0] = 0x00; // Conversion register address - uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, 1, _outBuffer); // Read register - if (status == I2C_STATUS_OK) { - _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; - #ifdef IO_ANALOGUE_SLOW - DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); - #endif + uint8_t status = _i2crb.wait(); + if (status == I2C_STATUS_OK) { + // If _currentPin is in the valid range, continue reading the pin values + if (_currentPin < _nPins) { + _outBuffer[0] = 0x00; // Conversion register address + uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1); // Read register + if (status == I2C_STATUS_OK) { + _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; + #ifdef IO_ANALOGUE_SLOW + DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); + #endif + } } + if (status != I2C_STATUS_OK) + DIAG(F("ADS111x I2C:x%d Error:%d"), _i2cAddress, status); } // Move to next pin if (++_currentPin >= _nPins) _currentPin = 0; @@ -103,23 +107,23 @@ public: // of configuration register settings. _outBuffer[0] = 0x01; // Config register address _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n - _outBuffer[2] = 0x83; // 128 samples/sec, comparator off + _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off // Write command, without waiting for completion. I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); _lastMicros = currentMicros; } } - int _readAnalogue(VPIN vpin) { + int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; return _value[pin]; } - void _display() { + void _display() override { DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1); } -protected: - // With ADC set to 128 samples/sec, that's 7.8ms/sample. So set the period between updates to 10ms + // ADC conversion rate is 250SPS, or 4ms per conversion. Set the period between updates to 10ms. + // This is enough to allow the conversion to reliably complete in time. #ifndef IO_ANALOGUE_SLOW const unsigned long scanInterval = 10000UL; // Period between successive ADC scans in microseconds. #else From 3dc0b1619c53d2b78863fd6cba287e0fe3e6f003 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Sep 2021 21:37:38 +0100 Subject: [PATCH 15/49] Update IO_DFPlayer.h --- IO_DFPlayer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index bb0e5c7..4072cae 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -131,13 +131,13 @@ protected: case 0: if (value > 0) { // Play global track - if (value > 2999) value = 2999; + if (value > 2999) return; #ifdef DIAG_IO DIAG(F("DFPlayer: Play %d"), value); #endif sendPacket(0x03, value); _playing = true; - } else { + } else if (value == 0){ #ifdef DIAG_IO DIAG(F("DFPlayer: Stop")); #endif From f3658aaee715f8b1505bf29d650f2532e662ce3a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Sep 2021 00:17:26 +0100 Subject: [PATCH 16/49] Update IO_HCSR04.h Change transmitPin to trigPin and receivePin to echoPin to match the markings on the device module. --- IO_HCSR04.h | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 76b8493..2df3733 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -59,8 +59,8 @@ class HCSR04 : public IODevice { private: // pins must be arduino GPIO pins, not extender pins or HAL pins. - int _transmitPin = -1; - int _receivePin = -1; + int _trigPin = -1; + int _echoPin = -1; // Thresholds for setting active state in cm. uint8_t _onThreshold; // cm uint8_t _offThreshold; // cm @@ -76,27 +76,27 @@ private: public: // Constructor perfroms static initialisation of the device object - HCSR04 (VPIN vpin, int transmitPin, int receivePin, uint16_t onThreshold, uint16_t offThreshold) { + HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { _firstVpin = vpin; _nPins = 1; - _transmitPin = transmitPin; - _receivePin = receivePin; + _trigPin = trigPin; + _echoPin = echoPin; _onThreshold = onThreshold; _offThreshold = offThreshold; addDevice(this); } // Static create function provides alternative way to create object - static void create(VPIN vpin, int transmitPin, int receivePin, uint16_t onThreshold, uint16_t offThreshold) { - new HCSR04(vpin, transmitPin, receivePin, onThreshold, offThreshold); + static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { + new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); } protected: // _begin function called to perform dynamic initialisation of the device void _begin() override { - pinMode(_transmitPin, OUTPUT); - pinMode(_receivePin, INPUT); - ArduinoPins::fastWriteDigital(_transmitPin, 0); + pinMode(_trigPin, OUTPUT); + pinMode(_echoPin, INPUT); + ArduinoPins::fastWriteDigital(_trigPin, 0); _lastExecutionTime = micros(); #if defined(DIAG_IO) _display(); @@ -120,12 +120,14 @@ protected: _lastExecutionTime = currentMicros; read_HCSR04device(); + // Delay next loop entry until 50ms have elapsed. + //delayUntil(currentMicros + 50000UL); } } void _display() override { DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"), - _firstVpin, _transmitPin, _receivePin, _onThreshold, _offThreshold); + _firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold); } private: @@ -145,18 +147,18 @@ private: uint16_t startTime, waitTime, currentTime, maxTime; // If receive pin is still set on from previous call, abort the read. - if (ArduinoPins::fastReadDigital(_receivePin)) + if (ArduinoPins::fastReadDigital(_echoPin)) return; // Send 10us pulse to trigger transmitter - ArduinoPins::fastWriteDigital(_transmitPin, 1); + ArduinoPins::fastWriteDigital(_trigPin, 1); delayMicroseconds(10); - ArduinoPins::fastWriteDigital(_transmitPin, 0); + ArduinoPins::fastWriteDigital(_trigPin, 0); // Wait for receive pin to be set startTime = currentTime = micros(); maxTime = factor * _offThreshold * 2; - while (!ArduinoPins::fastReadDigital(_receivePin)) { + while (!ArduinoPins::fastReadDigital(_echoPin)) { // lastTime = currentTime; currentTime = micros(); waitTime = currentTime - startTime; @@ -169,7 +171,7 @@ private: // Wait for receive pin to reset, and measure length of pulse startTime = currentTime = micros(); maxTime = factor * _offThreshold; - while (ArduinoPins::fastReadDigital(_receivePin)) { + while (ArduinoPins::fastReadDigital(_echoPin)) { currentTime = micros(); waitTime = currentTime - startTime; // If pulse is too long then set return value to zero, From 07cc45d86113533bd47500d6bc8929b06b71b073 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Sep 2021 12:39:51 +0100 Subject: [PATCH 17/49] Update IO_DFPlayer.h Fix volume control command. --- IO_DFPlayer.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 4072cae..061cd3f 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -126,7 +126,7 @@ protected: // If value is zero, it stops playing. // WriteAnalogue on second pin sets the output volume. void _writeAnalogue(VPIN vpin, int value, uint8_t, uint16_t) override { - uint8_t pin = _firstVpin - vpin; + uint8_t pin = vpin - _firstVpin; switch (pin) { case 0: if (value > 0) { @@ -159,13 +159,12 @@ protected: } } - bool _isBusy(VPIN vpin) override { - (void)vpin; // avoid compiler warning. + bool _isBusy(VPIN) override { return _playing; } void _display() override { - DIAG(F("DFPlayer Configured on Vpins:%d-%d")); + DIAG(F("DFPlayer Configured on Vpins:%d-%d"), _firstVpin, _firstVpin+_nPins-1); } private: From d077e3a2ffae500d5b097bb6f3a51f26f097f9ad Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 16 Sep 2021 16:47:47 +0100 Subject: [PATCH 18/49] Auto power on and POWEROFF macro --- RMFT2.cpp | 10 ++++++++++ RMFT2.h | 2 +- RMFTMacros.h | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/RMFT2.cpp b/RMFT2.cpp index 842b460..9ab89f6 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -19,6 +19,7 @@ #include #include "RMFT2.h" #include "DCC.h" +#include "DCCWaveform.h" #include "DIAG.h" #include "WiThrottle.h" #include "DCCEXParser.h" @@ -326,6 +327,8 @@ int RMFT2::locateRouteStart(int16_t _route) { void RMFT2::driveLoco(byte speed) { if (loco<=0) return; // Prevent broadcast! if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); + if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); DCC::setThrottle(loco,speed, forward^invert); speedo=speed; } @@ -477,6 +480,11 @@ void RMFT2::loop2() { if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); break; + case OPCODE_POWEROFF: + DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + break; + case OPCODE_RESUME: pausingTask=NULL; driveLoco(speedo); @@ -572,6 +580,8 @@ void RMFT2::loop2() { return; case OPCODE_JOIN: + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCC::setProgTrackSyncMain(true); break; diff --git a/RMFT2.h b/RMFT2.h index a254070..90080b7 100644 --- a/RMFT2.h +++ b/RMFT2.h @@ -40,7 +40,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN, OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM, OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO, - OPCODE_PAUSE, OPCODE_RESUME, + OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, OPCODE_PRINT, OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL diff --git a/RMFTMacros.h b/RMFTMacros.h index c66dc3c..9fd1c22 100644 --- a/RMFTMacros.h +++ b/RMFTMacros.h @@ -90,6 +90,7 @@ #define PAUSE #define PRINT(msg) #define POM(cv,value) +#define POWEROFF #define READ_LOCO #define RED(signal_id) #define RESERVE(blockid) @@ -188,6 +189,7 @@ const int StringMacroTracker1=__COUNTER__; #undef ONTHROW #undef PAUSE #undef POM +#undef POWEROFF #undef PRINT #undef READ_LOCO #undef RED @@ -265,6 +267,7 @@ const int StringMacroTracker1=__COUNTER__; #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define PAUSE OPCODE_PAUSE,NOP, #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), +#define POWEROFF OPCODE_POWEROFF,NOP, #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), #define READ_LOCO OPCODE_READ_LOCO1,NOP,OPCODE_READ_LOCO2,NOP, #define RED(signal_id) OPCODE_RED,V(signal_id), From ad7cd5f401d5c01f5ecb6f700d661d5d3fbbfa68 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 17 Sep 2021 11:36:08 +0100 Subject: [PATCH 19/49] Remove virtual _isBusy() function in favor of _read(). When writing to analogue outputs pins, the digital _read() function now returns the 'busy' status of the analogue pin. Consequently, the _isBusy() function becomes superfluous and has been removed. The static IODevice::isBusy() function now calls the object's _read() function instead. Also, limit in DFPlayer of 3 pins has been removed. --- IODevice.cpp | 19 ++++++++++++++----- IODevice.h | 19 +++++++++---------- IO_DFPlayer.h | 5 +++-- IO_PCA9685.cpp | 4 ++-- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 98584ca..cf90efe 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -53,7 +53,9 @@ void IODevice::begin() { MCP23017::create(180, 16, 0x21); // Call the begin() methods of each configured device in turn + unsigned long currentMicros = micros(); for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) { + dev->_nextEntryTime = currentMicros; dev->_begin(); } _initPhase = false; @@ -69,8 +71,14 @@ void IODevice::loop() { unsigned long currentMicros = micros(); // Call every device's loop function in turn, one per entry. if (!_nextLoopDevice) _nextLoopDevice = _firstDevice; - if (_nextLoopDevice) { + // Check if device exists, and is due to run + if (_nextLoopDevice /* && ((long)(currentMicros-_nextLoopDevice->_nextEntryTime) >= 0) */ ) { + // Move _nextEntryTime on, so that we can guarantee that the device will continue to + // be serviced if it doesn't update _nextEntryTime. + _nextLoopDevice->_nextEntryTime = currentMicros; + // Invoke device's _loop function _nextLoopDevice->_loop(currentMicros); + // Move to next device. _nextLoopDevice = _nextLoopDevice->_nextDevice; } @@ -157,12 +165,13 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur #endif } -// isBusy returns true if the device is currently in an animation of some sort, e.g. is changing -// the output over a period of time. +// isBusy, when called for a device pin is always a digital output or analogue output, +// returns input feedback state of the pin, i.e. whether the pin is busy performing +// an animation or fade over a period of time. bool IODevice::isBusy(VPIN vpin) { IODevice *dev = findDevice(vpin); if (dev) - return dev->_isBusy(vpin); + return dev->_read(vpin); else return false; } @@ -248,7 +257,7 @@ int IODevice::readAnalogue(VPIN vpin) { return dev->_readAnalogue(vpin); } #ifdef DIAG_IO - //DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); + DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); #endif return false; } diff --git a/IODevice.h b/IODevice.h index 58de64b..7e45816 100644 --- a/IODevice.h +++ b/IODevice.h @@ -129,7 +129,7 @@ public: static void write(VPIN vpin, int value); // write invokes the IODevice instance's _writeAnalogue method (not applicable for digital outputs) - static void writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration=0); + static void writeAnalogue(VPIN vpin, int value, uint8_t profile=0, uint16_t duration=0); // isBusy returns true if the device is currently in an animation of some sort, e.g. is changing // the output over a period of time. @@ -178,7 +178,7 @@ protected: }; // Method to write an 'analogue' value (optionally implemented within device class) - virtual void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { + virtual void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { (void)vpin; (void)value; (void) profile; (void)duration; }; @@ -203,13 +203,6 @@ protected: return 0; }; - // _isBusy returns true if the device is currently in an animation of some sort, e.g. is changing - // the output over a period of time. Returns false unless overridden in sub class. - virtual bool _isBusy(VPIN vpin) { - (void)vpin; - return false; - } - // Method to perform updates on an ongoing basis (optionally implemented within device class) virtual void _loop(unsigned long currentMicros) { (void)currentMicros; // Suppress compiler warning. @@ -220,6 +213,11 @@ protected: // Destructor virtual ~IODevice() {}; + + // Non-virtual function + void delayUntil(unsigned long futureMicrosCount) { + _nextEntryTime = futureMicrosCount; + } // Common object fields. VPIN _firstVpin; @@ -242,6 +240,7 @@ private: static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; + unsigned long _nextEntryTime; static IODevice *_firstDevice; static IODevice *_nextLoopDevice; @@ -276,7 +275,7 @@ private: // Device-specific write functions. void _write(VPIN vpin, int value) override; void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override; - bool _isBusy(VPIN vpin) override; + int _read(VPIN vpin) override; // returns the busy status of the device void _loop(unsigned long currentMicros) override; void updatePosition(uint8_t pin); void writeDevice(uint8_t pin, int value); diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 061cd3f..f7a1a9b 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -65,7 +65,7 @@ private: public: DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) { _firstVpin = firstVpin; - _nPins = min(nPins, 3); + _nPins = nPins; _serial = &serial; addDevice(this); } @@ -159,7 +159,8 @@ protected: } } - bool _isBusy(VPIN) override { + // A read on any pin indicates whether the player is still playing. + int _read(VPIN) override { return _playing; } diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index ddc45d8..d8c9795 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -169,9 +169,9 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur s->fromPosition = s->currentPosition; } -// _isBusy returns true if the device is currently in executing an animation, +// _read returns true if the device is currently in executing an animation, // changing the output over a period of time. -bool PCA9685::_isBusy(VPIN vpin) { +int PCA9685::_read(VPIN vpin) { int pin = vpin - _firstVpin; struct ServoData *s = _servoData[pin]; if (s == NULL) From fa650673eb7c6cf1201c9ec7efa99f54c99383a3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 17 Sep 2021 12:31:28 +0100 Subject: [PATCH 20/49] DFPlayer: allow volume to be set in play command. --- IO_DFPlayer.h | 80 +++++++++++++++++++++----------------- mySetup.cpp_example.txt | 85 +++++++++++++++++++++++++++++++++-------- 2 files changed, 114 insertions(+), 51 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index f7a1a9b..4c133b5 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -27,17 +27,19 @@ * The driver is configured as follows: * * DFPlayer::create(firstVpin, nPins, Serialn); + * * Where firstVpin is the first vpin reserved for reading the device, * nPins is the number of pins to be allocated (max 5) - * and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial2). - * + * and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1). * * Example: * In mySetup function within mySetup.cpp: - * DFPlayer::create(3500, 5, Serial2); - * Writing a value 0-2999 to the first pin will select a numbered file from the SD card; - * Writing a value 0-30 to the second pin will set the volume of the output; - * Writing a digital value to the first pin will play or stop the file; + * DFPlayer::create(3500, 5, Serial1); + * + * Writing an analogue value 0-2999 to the first pin will select a numbered file from the SD card; + * Writing an analogue value 0-30 to the second pin will set the volume of the output; + * Writing a digital value to the first pin will play or stop the file; + * Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise. * * From EX-RAIL, the following commands may be used: * SET(3500) -- starts playing the first file on the SD card @@ -45,6 +47,9 @@ * etc. * RESET(3500) -- stops all playing on the player * WAITFOR(3500) -- wait for the file currently being played by the player to complete + * SERVO(3500,23,0) -- plays file 23 at current volume + * SERVO(3500,23,30) -- plays file 23 at volume 30 (maximum) + * SERVO(3501,20,0) -- Sets the volume to 20 * * NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly * to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse. @@ -123,39 +128,44 @@ protected: } // WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0. - // If value is zero, it stops playing. + // Volume may be specified as second parameter to writeAnalogue. + // If value is zero, the player stops playing. // WriteAnalogue on second pin sets the output volume. - void _writeAnalogue(VPIN vpin, int value, uint8_t, uint16_t) override { + void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override { uint8_t pin = vpin - _firstVpin; - switch (pin) { - case 0: - if (value > 0) { - // Play global track - if (value > 2999) return; - #ifdef DIAG_IO - DIAG(F("DFPlayer: Play %d"), value); - #endif - sendPacket(0x03, value); - _playing = true; - } else if (value == 0){ - #ifdef DIAG_IO - DIAG(F("DFPlayer: Stop")); - #endif - sendPacket(0x16); - _playing = false; - } - break; - case 1: - // Set volume (0-30) - if (value > 30) value = 30; - else if (value < 0) value = 0; + + // Validate parameter. + volume = min(30,volume); + + if (pin == 0) { + // Play track + if (value > 0) { #ifdef DIAG_IO - DIAG(F("DFPlayer: Volume %d"), value); + DIAG(F("DFPlayer: Play %d"), value); #endif - sendPacket(0x06, value); - break; - default: - break; + sendPacket(0x03, value); // Play track + _playing = true; + if (volume > 0) { + #ifdef DIAG_IO + DIAG(F("DFPlayer: Volume %d"), volume); + #endif + sendPacket(0x06, volume); // Set volume + } + } else { + #ifdef DIAG_IO + DIAG(F("DFPlayer: Stop")); + #endif + sendPacket(0x16); // Stop play + _playing = false; + } + } else if (pin == 1) { + // Set volume (0-30) + if (value > 30) value = 30; + else if (value < 0) value = 0; + #ifdef DIAG_IO + DIAG(F("DFPlayer: Volume %d"), value); + #endif + sendPacket(0x06, value); } } diff --git a/mySetup.cpp_example.txt b/mySetup.cpp_example.txt index 0e2be50..0efc771 100644 --- a/mySetup.cpp_example.txt +++ b/mySetup.cpp_example.txt @@ -24,8 +24,9 @@ // Examples of statically defined HAL directives (alternative to the create() call). // These have to be outside of the mySetup() function. - +//======================================================================= // The following directive defines a PCA9685 PWM Servo driver module. +//======================================================================= // The parameters are: // First Vpin=100 // Number of VPINs=16 (numbered 100-115) @@ -34,13 +35,15 @@ //PCA9685 pwmModule1(100, 16, 0x40); +//======================================================================= // The following directive defines an MCP23017 16-port I2C GPIO Extender module. +//======================================================================= // The parameters are: -// First Vpin=164 -// Number of VPINs=16 (numbered 164-179) -// I2C address of module=0x20 +// First Vpin=196 +// Number of VPINs=16 (numbered 196-211) +// I2C address of module=0x22 -//MCP23017 gpioModule2(164, 16, 0x20); +//MCP23017 gpioModule2(196, 16, 0x22); // Alternative form, which allows the INT pin of the module to request a scan @@ -48,19 +51,23 @@ // all the time, only when a change takes place. Multiple modules' INT pins // may be connected to the same Arduino pin. -//MCP23017 gpioModule2(164, 16, 0x20, 40); +//MCP23017 gpioModule2(196, 16, 0x22, 40); +//======================================================================= // The following directive defines an MCP23008 8-port I2C GPIO Extender module. +//======================================================================= // The parameters are: // First Vpin=300 // Number of VPINs=8 (numbered 300-307) // I2C address of module=0x22 -//MCP23017 gpioModule3(300, 8, 0x22); +//MCP23008 gpioModule3(300, 8, 0x22); +//======================================================================= // The following directive defines a PCF8574 8-port I2C GPIO Extender module. +//======================================================================= // The parameters are: // First Vpin=200 // Number of VPINs=8 (numbered 200-207) @@ -74,7 +81,9 @@ //PCF8574 gpioModule4(200, 8, 0x23, 40); -// The following directive defines an HCSR04 ultrasonic module. +//======================================================================= +// The following directive defines an HCSR04 ultrasonic ranging module. +//======================================================================= // The parameters are: // Vpin=2000 (only one VPIN per directive) // Number of VPINs=1 @@ -90,7 +99,10 @@ //HCSR04 sonarModule1(2000, 30, 31, 20, 25); //HCSR04 sonarModule2(2001, 30, 32, 20, 25); -// The following directive defines a single VL53L0X Time-of-Flight sensor. + +//======================================================================= +// The following directive defines a single VL53L0X Time-of-Flight range sensor. +//======================================================================= // The parameters are: // VPIN=5000 // Number of VPINs=1 @@ -103,29 +115,33 @@ // For multiple VL53L0X modules, add another parameter which is a VPIN connected to the // module's XSHUT pin. This allows the modules to be configured, at start, // with distinct I2C addresses. In this case, the address 0x29 is only used during -// initialisation to configure each device with the desired unique I2C address. -// The examples below have one module's XSHUT pin connected to Arduino pin 34, -// and the other's connected to the second pin on the first MCP23017 module (VPIN 165). -// The first module is given I2C address 0x30 and the second is 0x31. +// initialisation to configure each device in turn with the desired unique I2C address. +// The examples below have the modules' XSHUT pins connected to the first two pins of +// the first MCP23017 module (164 and 165), but Arduino pins may be used instead. +// The first module here is given I2C address 0x30 and the second is 0x31. -//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 34); +//VL53L0X tofModule1(5000, 1, 0x30, 200, 250, 164); //VL53L0X tofModule2(5001, 1, 0x31, 200, 250, 165); +//======================================================================= // The function mySetup() is invoked from CS if it exists within the build. // It is called just before mysetup.h is executed, so things set up within here can be // referenced by commands in mySetup.h. +//======================================================================= void mySetup() { - // Alternative way of creating MCP23017, which has to be within the mySetup() function + // Alternative way of creating a module driver, which has to be within the mySetup() function // The other devices can also be created in this way. The parameter lists for the // create() function are identical to the parameter lists for the declarations. - //MCP23017::create(180, 16, 0x21); + //MCP23017::create(196, 16, 0x22); + //======================================================================= // Creating a Turnout + //======================================================================= // Parameters: same as command for Servo turnouts // ID and VPIN are 100, sonar moves between positions 102 and 490 with slow profile. // Profile may be Instant, Fast, Medium, Slow or Bounce. @@ -133,7 +149,9 @@ void mySetup() { //ServoTurnout::create(100, 100, 490, 102, PCA9685::Slow); + //======================================================================= // DCC Accessory turnout + //======================================================================= // Parameters: same as command for DCC Accessory turnouts // ID=3000 // Decoder address=23 @@ -142,7 +160,9 @@ void mySetup() { //DCCTurnout::create(3000, 23, 1); + //======================================================================= // Creating a Sensor + //======================================================================= // Parameters: As for the command, // id = 164, // Vpin = 164 (configured above as pin 0 of an MCP23017) @@ -151,11 +171,44 @@ void mySetup() { //Sensor::create(164, 164, 1); + //======================================================================= // Way of creating lots of identical sensors in a range + //======================================================================= //for (int i=165; i<180; i++) // Sensor::create(i, i, 1); + + //======================================================================= + // Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module. + //======================================================================= + // Parameters: + // 10000 = first VPIN allocated. + // 10 = number of VPINs allocated. + // Serial1 = name of serial port (usually Serial1 or Serial2). + // With these parameters, up to 10 files may be played on pins 10000-10009. + // Play is started from EX-RAIL with SET(10000) for first mp3 file, SET(10001) + // for second file, etc. Play may also be initiated by writing an analogue + // value to the first pin, e.g. SERVO(10000,23,0) will play the 23rd mp3 file. + // SERVO(10000,23,30) will do the same thing, as well as setting the volume to + // 30 (maximum value). + // Play is stopped by RESET(10000) (or any other allocated VPIN). + // Volume may also be set by writing an analogue value to the second pin for the player, + // e.g. SERVO(10001,30,0) sets volume to maximum (30). + // The EX-RAIL script may check for completion of play by calling WAITFOR(pin), which will only proceed to the + // following line when the player is no longer busy. + // E.g. + // SEQUENCE(1) + // AT(164) // Wait for sensor attached to pin 164 to activate + // SET(10003) // Play fourth MP3 file + // LCD(4, "Playing") // Display message on LCD/OLED + // WAITFOR(10003) // Wait for playing to finish + // LCD(4, " ") // Clear LCD/OLED line + // FOLLOW(1) // Go back to start + + // DFPlayer::create(10000, 10, Serial1); + + } #endif From afe2ecdc1483e7e5b37d059644974fedc8ce0ec9 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 17 Sep 2021 12:44:27 +0100 Subject: [PATCH 21/49] Update IODevice.cpp Remove potentially irritating diag messages --- IODevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IODevice.cpp b/IODevice.cpp index cf90efe..a48564b 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -257,7 +257,7 @@ int IODevice::readAnalogue(VPIN vpin) { return dev->_readAnalogue(vpin); } #ifdef DIAG_IO - DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); + //DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); #endif return false; } From bda3c052656c34881985cf6ed801c8ee5710ed52 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 18 Sep 2021 13:10:13 +0100 Subject: [PATCH 22/49] Auto power on tell JMR --- RMFT2.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RMFT2.cpp b/RMFT2.cpp index 9ab89f6..a117318 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -327,8 +327,10 @@ int RMFT2::locateRouteStart(int16_t _route) { void RMFT2::driveLoco(byte speed) { if (loco<=0) return; // Prevent broadcast! if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert); - if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) + if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) { DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + Serial.println(F("")); // tell JMRI + } DCC::setThrottle(loco,speed, forward^invert); speedo=speed; } @@ -483,6 +485,8 @@ void RMFT2::loop2() { case OPCODE_POWEROFF: DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + DCC::setProgTrackSyncMain(false); + Serial.println(F("")); // Tell JMRI break; case OPCODE_RESUME: @@ -583,6 +587,7 @@ void RMFT2::loop2() { DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); DCC::setProgTrackSyncMain(true); + Serial.println(F("")); // Tell JMRI break; case OPCODE_UNJOIN: From 302b16547ef9918c49c4eeef1de4482d19710543 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Sep 2021 11:02:23 +0100 Subject: [PATCH 23/49] HAL driver enhancements Performance enhancements in IODevice::loop() function. Improved error handling, device is placed off line if not responding. Improved error reporting, device shown as offline if not operational (faulty or not present). --- IODevice.cpp | 52 ++++++++++++++++++++++--------------- IODevice.h | 16 ++++++++---- IO_AnalogueInputs.h | 63 ++++++++++++++++++++++++--------------------- IO_DFPlayer.h | 33 +++++++++++++++++++----- IO_GPIOBase.h | 37 +++++++++++++------------- IO_HCSR04.h | 13 +++------- IO_PCA9685.cpp | 39 ++++++++++++++++------------ IO_VL53L0X.h | 45 ++++++++++++++++++++++---------- 8 files changed, 177 insertions(+), 121 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index a48564b..fc16bb0 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -53,9 +53,7 @@ void IODevice::begin() { MCP23017::create(180, 16, 0x21); // Call the begin() methods of each configured device in turn - unsigned long currentMicros = micros(); for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) { - dev->_nextEntryTime = currentMicros; dev->_begin(); } _initPhase = false; @@ -69,18 +67,24 @@ void IODevice::begin() { // doesn't need to invoke it. void IODevice::loop() { unsigned long currentMicros = micros(); - // Call every device's loop function in turn, one per entry. - if (!_nextLoopDevice) _nextLoopDevice = _firstDevice; - // Check if device exists, and is due to run - if (_nextLoopDevice /* && ((long)(currentMicros-_nextLoopDevice->_nextEntryTime) >= 0) */ ) { - // Move _nextEntryTime on, so that we can guarantee that the device will continue to - // be serviced if it doesn't update _nextEntryTime. - _nextLoopDevice->_nextEntryTime = currentMicros; - // Invoke device's _loop function - _nextLoopDevice->_loop(currentMicros); - // Move to next device. - _nextLoopDevice = _nextLoopDevice->_nextDevice; - } + + IODevice *lastLoopDevice = _nextLoopDevice; // So we know when to stop... + // Loop through devices until we find one ready to be serviced. + do { + if (!_nextLoopDevice) _nextLoopDevice = _firstDevice; + if (_nextLoopDevice) { + if (_nextLoopDevice->_deviceState != DEVSTATE_FAILED + && ((long)(currentMicros - _nextLoopDevice->_nextEntryTime)) >= 0) { + // Found one ready to run, so invoke its _loop method. + _nextLoopDevice->_nextEntryTime = currentMicros; + _nextLoopDevice->_loop(currentMicros); + _nextLoopDevice = _nextLoopDevice->_nextDevice; + break; + } + // Not this one, move to next one + _nextLoopDevice = _nextLoopDevice->_nextDevice; + } + } while (_nextLoopDevice != lastLoopDevice); // Stop looking when we've done all. // Report loop time if diags enabled #if defined(DIAG_LOOPTIMES) @@ -127,7 +131,8 @@ bool IODevice::hasCallback(VPIN vpin) { // Display (to diagnostics) details of the device. void IODevice::_display() { - DIAG(F("Unknown device Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1); + DIAG(F("Unknown device Vpins:%d-%d %S"), + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F("")); } // Find device associated with nominated Vpin and pass configuration values on to it. @@ -151,13 +156,18 @@ void IODevice::write(VPIN vpin, int value) { #endif } -// Write analogue value to virtual pin(s). If multiple devices are allocated the same pin -// then only the first one found will be used. Duration is the time that the -// operation is to be performed over (e.g. as an animation) in deciseconds (0-3276 sec) -void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { +// Write analogue value to virtual pin(s). If multiple devices are allocated +// the same pin then only the first one found will be used. +// +// The significance of param1 and param2 may vary from device to device. +// For servo controllers, param1 is the profile of the transition and param2 +// the duration, i.e. the time that the operation is to be animated over +// in deciseconds (0-3276 sec) +// +void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { IODevice *dev = findDevice(vpin); if (dev) { - dev->_writeAnalogue(vpin, value, profile, duration); + dev->_writeAnalogue(vpin, value, param1, param2); return; } #ifdef DIAG_IO @@ -257,7 +267,7 @@ int IODevice::readAnalogue(VPIN vpin) { return dev->_readAnalogue(vpin); } #ifdef DIAG_IO - //DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); + DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); #endif return false; } diff --git a/IODevice.h b/IODevice.h index 7e45816..3e00c10 100644 --- a/IODevice.h +++ b/IODevice.h @@ -163,7 +163,14 @@ public: protected: - // Method to perform initialisation of the device (optionally implemented within device class) + // Constructor + IODevice(VPIN firstVpin=0, int nPins=0) { + _firstVpin = firstVpin; + _nPins = nPins; + _nextEntryTime = 0; + } + + // Method to perform initialisation of the device (optionally implemented within device class) virtual void _begin() {} // Method to configure device (optionally implemented within device class) @@ -178,8 +185,8 @@ protected: }; // Method to write an 'analogue' value (optionally implemented within device class) - virtual void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { - (void)vpin; (void)value; (void) profile; (void)duration; + virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { + (void)vpin; (void)value; (void) param1; (void)param2; }; // Function called to check whether callback notification is supported by this pin. @@ -275,7 +282,7 @@ private: // Device-specific write functions. void _write(VPIN vpin, int value) override; void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override; - int _read(VPIN vpin) override; // returns the busy status of the device + int _read(VPIN vpin) override; // returns the digital state or busy status of the device void _loop(unsigned long currentMicros) override; void updatePosition(uint8_t pin); void writeDevice(uint8_t pin, int value); @@ -302,7 +309,6 @@ private: static const byte FLASH _bounceProfile[30]; const unsigned int refreshInterval = 50; // refresh every 50ms - unsigned long _lastRefreshTime; // last seen value of micros() count // structures for setting up non-blocking writes to servo controller I2CRB requestBlock; diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 7d24586..725f300 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -78,48 +78,52 @@ private: #endif } else { DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; } } void _loop(unsigned long currentMicros) override { - if (currentMicros - _lastMicros >= scanInterval) { - // Check that previous non-blocking write has completed, if not then wait - uint8_t status = _i2crb.wait(); - if (status == I2C_STATUS_OK) { - // If _currentPin is in the valid range, continue reading the pin values - if (_currentPin < _nPins) { - _outBuffer[0] = 0x00; // Conversion register address - uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1); // Read register - if (status == I2C_STATUS_OK) { - _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; - #ifdef IO_ANALOGUE_SLOW - DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); - #endif - } + // Check that previous non-blocking write has completed, if not then wait + uint8_t status = _i2crb.wait(); + if (status == I2C_STATUS_OK) { + // If _currentPin is in the valid range, continue reading the pin values + if (_currentPin < _nPins) { + _outBuffer[0] = 0x00; // Conversion register address + uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1); // Read register + if (status == I2C_STATUS_OK) { + _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; + #ifdef IO_ANALOGUE_SLOW + DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); + #endif } - if (status != I2C_STATUS_OK) - DIAG(F("ADS111x I2C:x%d Error:%d"), _i2cAddress, status); } - // Move to next pin - if (++_currentPin >= _nPins) _currentPin = 0; - - // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details - // of configuration register settings. - _outBuffer[0] = 0x01; // Config register address - _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n - _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off - // Write command, without waiting for completion. - I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); - - _lastMicros = currentMicros; } + if (status != I2C_STATUS_OK) { + DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; + } + // Move to next pin + if (++_currentPin >= _nPins) _currentPin = 0; + + // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details + // of configuration register settings. + _outBuffer[0] = 0x01; // Config register address + _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n + _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off + // Write command, without waiting for completion. + I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); + + delayUntil(currentMicros + scanInterval); } + int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; return _value[pin]; } + void _display() override { - DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1); + DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, + _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } // ADC conversion rate is 250SPS, or 4ms per conversion. Set the period between updates to 10ms. @@ -134,7 +138,6 @@ private: uint8_t _outBuffer[3]; uint8_t _inBuffer[2]; uint8_t _currentPin; // ADC pin currently being scanned - unsigned long _lastMicros = 0; I2CRB _i2crb; }; diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 4c133b5..5296ae0 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -66,6 +66,7 @@ private: HardwareSerial *_serial; bool _playing = false; uint8_t _inputIndex = 0; + unsigned long _commandSendTime; // Allows timeout processing public: DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) { @@ -81,21 +82,32 @@ public: protected: void _begin() override { _serial->begin(9600); - _display(); + _deviceState = DEVSTATE_INITIALISING; + + // Send a query to the device to see if it responds + sendPacket(0x42); + _commandSendTime = micros(); } - void _loop(unsigned long) override { + void _loop(unsigned long currentMicros) override { // Check for incoming data on _serial, and update busy flag accordingly. // Expected message is in the form "7F FF 06 3D xx xx xx xx xx EF" while (_serial->available()) { int c = _serial->read(); -// DIAG(F("Received: %x"), c); if (c == 0x7E) _inputIndex = 1; - else if ((c==0xFF && _inputIndex==1) || (c==0x06 && _inputIndex==2) - || (c==0x3D && _inputIndex==3) || (_inputIndex >=4 && _inputIndex <= 8)) + else if ((c==0xFF && _inputIndex==1) + || (c==0x3D && _inputIndex==3) + || (_inputIndex >=4 && _inputIndex <= 8)) _inputIndex++; - else if (c==0xEF && _inputIndex==9) { + else if (c==0x06 && _inputIndex==2) { + // Valid command prefix, so consider the device online. + _deviceState = DEVSTATE_NORMAL; + #ifdef DIAG_IO + _display(); + #endif + _inputIndex++; + } else if (c==0xEF && _inputIndex==9) { // End of play #ifdef DIAG_IO DIAG(F("DFPlayer: Finished")); @@ -104,6 +116,12 @@ protected: _inputIndex = 0; } } + // Check if the initial prompt to device has timed out. Allow 1 second + if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) { + DIAG(F("DFPlayer device not responding on serial port")); + _deviceState = DEVSTATE_FAILED; + } + delayUntil(currentMicros + 10000); // Only enter every 10ms } // Write with value 1 starts playing a song. The relative pin number is the file number. @@ -175,7 +193,8 @@ protected: } void _display() override { - DIAG(F("DFPlayer Configured on Vpins:%d-%d"), _firstVpin, _firstVpin+_nPins-1); + DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } private: diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 4ca8cce..1a782b6 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -59,7 +59,6 @@ protected: T _portPullup; // Interval between refreshes of each input port static const int _portTickTime = 4000; - unsigned long _lastLoopEntry = 0; // Virtual functions for interfacing with I2C GPIO Device virtual void _writeGpioPort() = 0; @@ -105,10 +104,12 @@ void GPIOBase::_begin() { _portMode = 0; // default to input mode _portPullup = -1; // default to pullup enabled _portInputState = -1; + _setupDevice(); + _deviceState = DEVSTATE_NORMAL; + } else { + DIAG(F("%S I2C:x%x Device not detected"), _deviceName, _I2CAddress); + _deviceState = DEVSTATE_FAILED; } - _setupDevice(); - _deviceState = DEVSTATE_NORMAL; - _lastLoopEntry = micros(); } // Configuration parameters for inputs: @@ -172,27 +173,25 @@ void GPIOBase::_loop(unsigned long currentMicros) { #endif } - // Check if interrupt configured. If so, and pin is not pulled down, finish. - if (_gpioInterruptPin >= 0) { - if (digitalRead(_gpioInterruptPin)) return; - } else - // No interrupt pin. Check if tick has elapsed. If not, finish. - if (currentMicros - _lastLoopEntry < (unsigned long)_portTickTime) return; + // Check if interrupt configured. If not, or if it is active (pulled down), then + // initiate a scan. + if (_gpioInterruptPin < 0 || !digitalRead(_gpioInterruptPin)) { + // TODO: Could suppress reads if there are no pins configured as inputs! - // TODO: Could suppress reads if there are no pins configured as inputs! - - // Read input - _lastLoopEntry = currentMicros; - if (_deviceState == DEVSTATE_NORMAL) { - _readGpioPort(false); // Initiate non-blocking read - _deviceState= DEVSTATE_SCANNING; + // Read input + if (_deviceState == DEVSTATE_NORMAL) { + _readGpioPort(false); // Initiate non-blocking read + _deviceState= DEVSTATE_SCANNING; + } } + // Delay next entry until tick elapsed. + delayUntil(currentMicros + _portTickTime); } template void GPIOBase::_display() { - DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d"), _deviceName, _I2CAddress, - _firstVpin, _firstVpin+_nPins-1); + DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress, + _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } template diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 2df3733..9bbd2f8 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -68,8 +68,6 @@ private: uint16_t _distance; // Active=1/inactive=0 state uint8_t _value = 0; - // Time of last loop execution - unsigned long _lastExecutionTime; // Factor for calculating the distance (cm) from echo time (ms). // Based on a speed of sound of 345 metres/second. const uint16_t factor = 58; // ms/cm @@ -97,7 +95,6 @@ protected: pinMode(_trigPin, OUTPUT); pinMode(_echoPin, INPUT); ArduinoPins::fastWriteDigital(_trigPin, 0); - _lastExecutionTime = micros(); #if defined(DIAG_IO) _display(); #endif @@ -116,13 +113,9 @@ protected: // _loop function - read HC-SR04 once every 50 milliseconds. void _loop(unsigned long currentMicros) override { - if (currentMicros - _lastExecutionTime > 50000UL) { - _lastExecutionTime = currentMicros; - - read_HCSR04device(); - // Delay next loop entry until 50ms have elapsed. - //delayUntil(currentMicros + 50000UL); - } + read_HCSR04device(); + // Delay next loop entry until 50ms have elapsed. + delayUntil(currentMicros + 50000UL); } void _display() override { diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index d8c9795..009ff63 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -107,12 +107,14 @@ void PCA9685::_begin() { #if defined(DIAG_IO) _display(); #endif - } + } else + _deviceState = DEVSTATE_FAILED; } // Device-specific write function, invoked from IODevice::write(). // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { + if (_deviceState == DEVSTATE_FAILED) return; #ifdef DIAG_IO DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); #endif @@ -137,6 +139,7 @@ void PCA9685::_write(VPIN vpin, int value) { // 4 (Bounce) Servo 'bounces' at extremes. // void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { + if (_deviceState == DEVSTATE_FAILED) return; #ifdef DIAG_IO DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d"), vpin, value, profile, duration); @@ -172,6 +175,7 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur // _read returns true if the device is currently in executing an animation, // changing the output over a period of time. int PCA9685::_read(VPIN vpin) { + if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; struct ServoData *s = _servoData[pin]; if (s == NULL) @@ -181,12 +185,10 @@ int PCA9685::_read(VPIN vpin) { } void PCA9685::_loop(unsigned long currentMicros) { - if (currentMicros - _lastRefreshTime >= refreshInterval * 1000) { - for (int pin=0; pin<_nPins; pin++) { - updatePosition(pin); - } - _lastRefreshTime = currentMicros; + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); } + delayUntil(currentMicros + refreshInterval * 1000UL); } // Private function to reposition servo @@ -238,20 +240,25 @@ void PCA9685::writeDevice(uint8_t pin, int value) { DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); #endif // Wait for previous request to complete - requestBlock.wait(); - // Set up new request. - outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; - outputBuffer[1] = 0; - outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on - outputBuffer[3] = value & 0xff; - outputBuffer[4] = value >> 8; - I2CManager.queueRequest(&requestBlock); + uint8_t status = requestBlock.wait(); + if (status != I2C_STATUS_OK) { + _deviceState = DEVSTATE_FAILED; + DIAG(F("PCA9685 I2C:x%x failed %S"), _I2CAddress, I2CManager.getErrorMessage(status)); + } else { + // Set up new request. + outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; + outputBuffer[1] = 0; + outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on + outputBuffer[3] = value & 0xff; + outputBuffer[4] = value >> 8; + I2CManager.queueRequest(&requestBlock); + } } // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d"), _I2CAddress, (int)_firstVpin, - (int)_firstVpin+_nPins-1); + DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } // Internal helper function for this device diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 08de1aa..33c6a16 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -44,7 +44,11 @@ * all VL53L0X modules are turned off, the driver works through each module in turn by * setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address. * In this way, many VL53L0X modules can be connected to the one I2C bus, each one - * using with a distinct I2C address. + * using a distinct I2C address. + * + * WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise, + * and the device may even reset when handled. If you're not using XSHUT, then it's + * best to tie it to +5V. * * The driver is configured as follows: * @@ -98,7 +102,6 @@ private: bool _value; bool _initialising = true; uint8_t _entryCount = 0; - unsigned long _lastEntryTime = 0; bool _scanInProgress = false; // Register addresses enum : uint8_t { @@ -134,8 +137,9 @@ protected: _entryCount = 3; } else { _entryCount = 0; - } - } + } + } + void _loop(unsigned long currentMicros) override { if (_initialising) { switch (_entryCount++) { @@ -156,11 +160,17 @@ protected: I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); break; case 3: + // After two more loops, check if device has been configured. if (I2CManager.exists(_i2cAddress)) { + #ifdef DIAG_IO _display(); + #endif // Set 2.8V mode write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); + } else { + DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; } _initialising = false; _entryCount = 0; @@ -168,14 +178,17 @@ protected: default: break; } - } else if (_lastEntryTime - currentMicros > 10000UL) { - // Service device every 10ms - _lastEntryTime = currentMicros; + } else { if (!_scanInProgress) { // Not scanning, so initiate a scan - write_reg(VL53L0X_REG_SYSRANGE_START, 0x01); - _scanInProgress = true; + uint8_t status = write_reg(VL53L0X_REG_SYSRANGE_START, 0x01); + if (status != I2C_STATUS_OK) { + DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; + _value = false; + } else + _scanInProgress = true; } else { // Scan in progress, so check for completion. @@ -198,8 +211,11 @@ protected: _scanInProgress = false; } } + // Next entry in 10 milliseconds. + delayUntil(currentMicros + 10000UL); } } + // For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level. int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; @@ -214,13 +230,16 @@ protected: return -1; } } + // For digital read, return the same value for all pins. int _read(VPIN) override { return _value; } + void _display() override { - DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm"), - _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold); + DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), + _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -228,12 +247,12 @@ private: inline uint16_t makeuint16(byte lsb, byte msb) { return (((uint16_t)msb) << 8) | lsb; } - void write_reg(uint8_t reg, uint8_t data) { + uint8_t write_reg(uint8_t reg, uint8_t data) { // write byte to register uint8_t outBuffer[2]; outBuffer[0] = reg; outBuffer[1] = data; - I2CManager.write(_i2cAddress, outBuffer, 2); + return I2CManager.write(_i2cAddress, outBuffer, 2); } uint8_t read_reg(uint8_t reg) { // read byte from register register From e59e07b97168e0d64c0b5d5729b85f41335ee212 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Sep 2021 13:43:52 +0100 Subject: [PATCH 24/49] Improved HAL diagnostics Looptime diagnostic enhanced, and duplicated diagnostic messages removed from DFPlayer class. --- IODevice.cpp | 22 +++++++++++++++------- IO_DFPlayer.h | 35 +++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index fc16bb0..e212cf5 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -89,24 +89,32 @@ void IODevice::loop() { // Report loop time if diags enabled #if defined(DIAG_LOOPTIMES) static unsigned long lastMicros = 0; - static unsigned long maxElapsed = 0; + // Measure time since loop() method started. + unsigned long halElapsed = micros() - currentMicros; + // Measure time between loop() method entries. + unsigned long elapsed = currentMicros - lastMicros; + static unsigned long maxElapsed = 0, maxHalElapsed = 0; static unsigned long lastOutputTime = 0; + static unsigned long halTotal = 0, total = 0; static unsigned long count = 0; const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec - unsigned long elapsed = currentMicros - lastMicros; + // Ignore long loop counts while message is still outputting if (currentMicros - lastOutputTime > 3000UL) { if (elapsed > maxElapsed) maxElapsed = elapsed; + if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed; + halTotal += halElapsed; + total += elapsed; + count++; } - count++; if (currentMicros - lastOutputTime > interval) { if (lastOutputTime > 0) - LCD(1,F("Loop=%lus,%lus max"), interval/count, maxElapsed); - maxElapsed = 0; - count = 0; + DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"), + total/count, maxElapsed, halTotal/count, maxHalElapsed); + maxElapsed = maxHalElapsed = total = halTotal = count = 0; lastOutputTime = currentMicros; } - lastMicros = micros(); + lastMicros = currentMicros; #endif } diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 5296ae0..bdf0626 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -69,12 +69,14 @@ private: unsigned long _commandSendTime; // Allows timeout processing public: - DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) { - _firstVpin = firstVpin; - _nPins = nPins; - _serial = &serial; + // Constructor + DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) : + IODevice(firstVpin, nPins), + _serial(&serial) + { addDevice(this); } + static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) { new DFPlayer(firstVpin, nPins, serial); } @@ -101,20 +103,25 @@ protected: || (_inputIndex >=4 && _inputIndex <= 8)) _inputIndex++; else if (c==0x06 && _inputIndex==2) { - // Valid command prefix, so consider the device online. - _deviceState = DEVSTATE_NORMAL; - #ifdef DIAG_IO - _display(); - #endif + // Valid message prefix, so consider the device online + if (_deviceState==DEVSTATE_INITIALISING) { + _deviceState = DEVSTATE_NORMAL; + #ifdef DIAG_IO + _display(); + #endif + } _inputIndex++; } else if (c==0xEF && _inputIndex==9) { // End of play - #ifdef DIAG_IO - DIAG(F("DFPlayer: Finished")); - #endif - _playing = false; + if (_playing) { + #ifdef DIAG_IO + DIAG(F("DFPlayer: Finished")); + #endif + _playing = false; + } _inputIndex = 0; - } + } else + _inputIndex = 0; // Unrecognised character sequence, start again! } // Check if the initial prompt to device has timed out. Allow 1 second if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 1000000UL) { From e287af83fffad1617e6d4f3205d725be9be97919 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Sep 2021 10:38:11 +0100 Subject: [PATCH 25/49] DCC Turnouts: Store address/subaddress separately. Enable address 0. The range of accessory decoder addresses for the command is 0-511 in line with the DCC packet contents. The turnout command previously rejected address 0; this has been changed to the same range of addresses can be used by both commands, i.e. address 0-511 and subaddress 0-3. The linear address mapping remains so that linear address 1 is addr/subaddr 1/0; i.e. the first decoder address is not accessible by linear address. --- DCCEXParser.cpp | 8 +++++--- Turnouts.cpp | 17 ++++++++--------- Turnouts.h | 8 +++++--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d09aa1b..b5f472f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -736,15 +736,17 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) if (!VpinTurnout::create(p[0], p[2])) return false; } else if (params >= 3 && p[1] == HASH_KEYWORD_DCC) { - if (params==4 && p[2]>0 && p[2]<=512 && p[3]>=0 && p[3]<4) { // + // 0<=addr<=511, 0<=subadd<=3 (like command). + if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // if (!DCCTurnout::create(p[0], p[2], p[3])) return false; } else if (params==3 && p[2]>0 && p[2]<=512*4) { // , 1<=nn<=2048 + // Linearaddress 1 maps onto decoder address 1/0 (not 0/0!). if (!DCCTurnout::create(p[0], (p[2]-1)/4+1, (p[2]-1)%4)) return false; } else return false; } else - if (params==3) { // legacy for DCC accessory - if (p[1]>0 && p[1]<=512 && p[2]>=0 && p[2]<4) { + if (params==3) { // legacy for DCC accessory + if (p[1]>=0 && p[1]<512 && p[2]>=0 && p[2]<4) { if (!DCCTurnout::create(p[0], p[1], p[2])) return false; } else return false; diff --git a/Turnouts.cpp b/Turnouts.cpp index 6dbc402..dcbff73 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -340,7 +340,8 @@ DCCTurnout::DCCTurnout(uint16_t id, uint16_t address, uint8_t subAdd) : Turnout(id, TURNOUT_DCC, false) { - _dccTurnoutData.address = ((address-1) << 2) + subAdd + 1; + _dccTurnoutData.address = address; + _dccTurnoutData.subAddress = subAdd; } // Create function @@ -351,7 +352,8 @@ if (tt->isType(TURNOUT_DCC)) { // Yes, so set parameters DCCTurnout *dt = (DCCTurnout *)tt; - dt->_dccTurnoutData.address = ((add-1) << 2) + subAdd + 1; + dt->_dccTurnoutData.address = add; + dt->_dccTurnoutData.subAddress = subAdd; // Don't touch the _closed parameter, retain the original value. return tt; } else { @@ -371,27 +373,24 @@ EEStore::advance(sizeof(dccTurnoutData)); // Create new object - DCCTurnout *tt = new DCCTurnout(turnoutData->id, (((dccTurnoutData.address-1) >> 2)+1), ((dccTurnoutData.address-1) & 3)); + DCCTurnout *tt = new DCCTurnout(turnoutData->id, dccTurnoutData.address, dccTurnoutData.subAddress); return tt; } void DCCTurnout::print(Print *stream) { StringFormatter::send(stream, F("\n"), _turnoutData.id, - (((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3), - !_turnoutData.closed); + _dccTurnoutData.address, _dccTurnoutData.subAddress, !_turnoutData.closed); // Also report using classic DCC++ syntax for DCC accessory turnouts, since JMRI expects this. StringFormatter::send(stream, F("\n"), _turnoutData.id, - (((_dccTurnoutData.address-1) >> 2)+1), ((_dccTurnoutData.address-1) & 3), - !_turnoutData.closed); + _dccTurnoutData.address, _dccTurnoutData.subAddress, !_turnoutData.closed); } bool DCCTurnout::setClosedInternal(bool close) { // DCC++ Classic behaviour is that Throw writes a 1 in the packet, // and Close writes a 0. // RCN-213 specifies that Throw is 0 and Close is 1. - DCC::setAccessory((((_dccTurnoutData.address-1) >> 2) + 1), - ((_dccTurnoutData.address-1) & 3), close ^ !rcn213Compliant); + DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant); _turnoutData.closed = close; return true; } diff --git a/Turnouts.h b/Turnouts.h index 9c14089..6a55c15 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -213,9 +213,11 @@ private: // DCCTurnoutData contains data specific to this subclass that is // written to EEPROM when the turnout is saved. struct DCCTurnoutData { - // DCC address (Address in bits 15-2, subaddress in bits 1-0 - uint16_t address; // CS currently supports linear address 1-2048 - // That's DCC accessory address 1-512 and subaddress 0-3. + // DCC address (Address in bits 15-2, subaddress in bits 1-0) + struct { + uint16_t address : 14; + uint8_t subAddress : 2; + }; } _dccTurnoutData; // 2 bytes // Constructor From 32eb8fe8c76c844a07d5289b20b7d7b7e6600c3b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Sep 2021 14:00:05 +0100 Subject: [PATCH 26/49] Update version.h (3.1.7draft) Added partial list of changes from 3.1.6 to 3.1.7draft. --- version.h | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 09211fa..1d76a7e 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,25 @@ #include "StringFormatter.h" -#define VERSION "3.1.6" +#define VERSION "3.1.7draft" +// 3.1.7 Major functional and non-functional changes. +// New HAL added for I/O (digital and analogue inputs and outputs, servos etc). +// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules. +// Support for PCA9685 PWM (servo) control modules. +// Support for analogue inputs on Arduino pins and on ADS111x I2C modules. +// Support for MP3 sound playback via DFPlayer module. +// Support for HC-SR04 Ultrasonic range sensor module. +// Support for VL53L0X Laser range sensor module (Time-Of-Flight). +// Native non-blocking I2C drivers for AVR and Nano architectures (fallback +// to blocking Wire library for other platforms). +// EEPROM layout change - deletes EEPROM contents on first start following upgrade. +// New EX-RAIL automation capability. +// Turnout class revised to expand turnout capabilities, new commands added. +// Output class now allows ID > 255. +// Configuration options to globally flip polarity of DCC Accessory states when driven +// from command and command. +// Increased use of display for showing loco decoder programming information. +// ... // 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM // 3.1.5 Fix LCD corruption on power-up // 3.1.4 Refactor OLED and LCD drivers and remove unused code From e11fd188492c3a409960b68281b0b45954a7ff7d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Sep 2021 14:12:23 +0100 Subject: [PATCH 27/49] Update IO_DCCAccessory.cpp Ensure the full range of addresses including 0 are handled. --- IO_DCCAccessory.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/IO_DCCAccessory.cpp b/IO_DCCAccessory.cpp index 5e1c8f4..e40198b 100644 --- a/IO_DCCAccessory.cpp +++ b/IO_DCCAccessory.cpp @@ -22,17 +22,9 @@ #include "DIAG.h" #include "defines.h" -// Note: For DCC Accessory Decoders, a particular output can be specified by -// a linear address, or by an address/subaddress pair, where the subaddress is -// in the range 0 to 3 and specifies an output within a group of 4. -// NMRA and DCC++EX accepts addresses in the range 0-511. Linear addresses -// are not specified by the NMRA and so different manufacturers may calculate them -// in different ways. DCC++EX uses a range of 1-2044 which excludes decoder address 0. -// Linear address 1 corresponds to address 1 subaddress 0. - -#define LINEARADDRESS(addr, subaddr) (((addr-1) << 2) + subaddr + 1) -#define ADDRESS(linearaddr) (((linearaddr-1) >> 2) + 1) -#define SUBADDRESS(linearaddr) ((linearaddr-1) % 4) +#define PACKEDADDRESS(addr, subaddr) (((addr) << 2) + (subaddr)) +#define ADDRESS(packedaddr) ((packedaddr) >> 2) +#define SUBADDRESS(packedaddr) ((packedaddr) % 4) void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) { new DCCAccessoryDecoder(vpin, nPins, DCCAddress, DCCSubaddress); @@ -42,7 +34,7 @@ void DCCAccessoryDecoder::create(VPIN vpin, int nPins, int DCCAddress, int DCCSu DCCAccessoryDecoder::DCCAccessoryDecoder(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) { _firstVpin = vpin; _nPins = nPins; - _packedAddress = LINEARADDRESS(DCCAddress, DCCSubaddress); + _packedAddress = PACKEDADDRESS(DCCAddress, DCCSubaddress); addDevice(this); } @@ -66,8 +58,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) { void DCCAccessoryDecoder::_display() { int endAddress = _packedAddress + _nPins - 1; - DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Linear Address:%d-%d (%d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1, - _packedAddress, _packedAddress+_nPins-1, + DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1, ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress)); } From ffc5d91561a6e3cc359c437e2933775b43b7160d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Sep 2021 08:59:43 +0100 Subject: [PATCH 28/49] Update version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 1d76a7e..bcf78f6 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "3.1.7draft" +#define VERSION "3.1.7 draft" // 3.1.7 Major functional and non-functional changes. // New HAL added for I/O (digital and analogue inputs and outputs, servos etc). // Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules. From 9fc805831dcc01873652990879414560f281979a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Sep 2021 10:54:27 +0100 Subject: [PATCH 29/49] HAL: Minor optimisations Remove virtual method hasCallback(). Optimise findDevice() method (used by read, write etc.). Simplify Sensor handling with regard to IO Devices that support callbacks. --- IODevice.cpp | 8 +++--- IODevice.h | 12 +++------ IO_GPIOBase.h | 11 +++------ Sensors.cpp | 67 ++++++++++++++------------------------------------- Sensors.h | 11 ++------- 5 files changed, 32 insertions(+), 77 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index e212cf5..5fd27ff 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -134,7 +134,7 @@ bool IODevice::exists(VPIN vpin) { bool IODevice::hasCallback(VPIN vpin) { IODevice *dev = findDevice(vpin); if (!dev) return false; - return dev->_hasCallback(vpin); + return dev->_hasCallback; } // Display (to diagnostics) details of the device. @@ -221,10 +221,12 @@ void IODevice::addDevice(IODevice *newDevice) { newDevice->_begin(); } -// Private helper function to locate a device by VPIN. Returns NULL if not found +// Private helper function to locate a device by VPIN. Returns NULL if not found. +// This is performance-critical, so minimises the calculation and function calls necessary. IODevice *IODevice::findDevice(VPIN vpin) { for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { - if (dev->owns(vpin)) + VPIN firstVpin = dev->_firstVpin; + if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins) return dev; } return NULL; diff --git a/IODevice.h b/IODevice.h index 3e00c10..fe1d3e6 100644 --- a/IODevice.h +++ b/IODevice.h @@ -189,15 +189,6 @@ protected: (void)vpin; (void)value; (void) param1; (void)param2; }; - // Function called to check whether callback notification is supported by this pin. - // Defaults to no, if not overridden by the device. - // The same value should be returned by all pins on the device, so only one need - // be checked. - virtual bool _hasCallback(VPIN vpin) { - (void) vpin; - return false; - } - // Method to read digital pin state (optionally implemented within device class) virtual int _read(VPIN vpin) { (void)vpin; @@ -230,6 +221,9 @@ protected: VPIN _firstVpin; int _nPins; + // Flag whether the device supports callbacks. + bool _hasCallback = false; + // Pin number of interrupt pin for GPIO extender devices. The extender module will pull this // pin low if an input changes state. int16_t _gpioInterruptPin = -1; diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 1a782b6..9b1bee5 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -45,10 +45,6 @@ protected: int _read(VPIN vpin) override; void _display() override; void _loop(unsigned long currentMicros) override; - bool _hasCallback(VPIN vpin) { - (void)vpin; // suppress compiler warning - return true; // Enable callback if caller wants to use it. - } // Data fields uint8_t _I2CAddress; @@ -79,12 +75,13 @@ protected: // Constructor template -GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) { +GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) : + IODevice(firstVpin, nPins) +{ _deviceName = deviceName; - _firstVpin = firstVpin; - _nPins = nPins; _I2CAddress = I2CAddress; _gpioInterruptPin = interruptPin; + _hasCallback = true; // Add device to list of devices. addDevice(this); } diff --git a/Sensors.cpp b/Sensors.cpp index 4d3283a..d6d3d81 100644 --- a/Sensors.cpp +++ b/Sensors.cpp @@ -103,12 +103,6 @@ void Sensor::checkAll(Print *stream){ // Required time elapsed since last read cycle started, // so initiate new scan through the sensor list readingSensor = firstSensor; -#ifdef USE_NOTIFY - if (firstSensor == firstPollSensor) - pollSignalPhase = true; - else - pollSignalPhase = false; -#endif lastReadCycle = thisTime; } } @@ -117,12 +111,6 @@ void Sensor::checkAll(Print *stream){ bool pause = false; while (readingSensor != NULL && !pause) { -#ifdef USE_NOTIFY - // Check if we have reached the start of the polled portion of the sensor list. - if (readingSensor == firstPollSensor) - pollSignalPhase = true; -#endif - // Where the sensor is attached to a pin, read pin status. For sources such as LCN, // which don't have an input pin to read, the LCN class calls setState() to update inputState when // a message is received. The IODevice::read() call returns 1 for active pins (0v) and 0 for inactive (5v). @@ -130,10 +118,8 @@ void Sensor::checkAll(Print *stream){ // routine when an input signal change is detected, and this updates the inputState directly, // so these inputs don't need to be polled here. VPIN pin = readingSensor->data.pin; -#ifdef USE_NOTIFY - if (pollSignalPhase) -#endif - if (pin!=VPIN_NONE) readingSensor->inputState = IODevice::read(pin); + if (readingSensor->pollingRequired && pin != VPIN_NONE) + readingSensor->inputState = IODevice::read(pin); // Check if changed since last time, and process changes. if (readingSensor->inputState == readingSensor->active) { @@ -156,22 +142,12 @@ void Sensor::checkAll(Print *stream){ // Move to next sensor in list. readingSensor = readingSensor->nextSensor; - // Currently process max of 16 sensors per entry for polled sensors, and - // 16 per entry for sensors notified by callback. - // Performance measurements taken during development indicate that, with 64 sensors configured - // on 8x 8-pin PCF8574 GPIO expanders, all inputs can be read within 1.4ms (400Mhz I2C bus speed), and a - // full cycle of scanning 64 sensors for changes takes between 1.9 and 3.2 milliseconds. + // Currently process max of 16 sensors per entry. + // Performance measurements taken during development indicate that, with 128 sensors configured + // on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices + // within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond. sensorCount++; -#ifdef USE_NOTIFY - if (pollSignalPhase) { -#endif - if (sensorCount >= 16) pause = true; -#ifdef USE_NOTIFY - } else - { - if (sensorCount >= 16) pause = true; - } -#endif + if (sensorCount >= 16) pause = true; } } // Sensor::checkAll @@ -223,23 +199,18 @@ Sensor *Sensor::create(int snum, VPIN pin, int pullUp){ tt = (Sensor *)calloc(1,sizeof(Sensor)); if (!tt) return tt; // memory allocation failure -#ifdef USE_NOTIFY - if (pin == VPIN_NONE || IODevice::hasCallback(pin)) { - // Callback available, or no pin to read, so link sensor on to the start of the list - tt->nextSensor = firstSensor; - firstSensor = tt; - if (lastSensor == NULL) lastSensor = tt; // This is only item in list. - } else { - // No callback, so add to end of list so it's polled. - if (lastSensor != NULL) lastSensor->nextSensor = tt; - lastSensor = tt; - if (!firstSensor) firstSensor = tt; - if (!firstPollSensor) firstPollSensor = tt; - } -#else + if (pin == VPIN_NONE) + tt->pollingRequired = false; + #ifdef USE_NOTIFY + else if (IODevice::hasCallback(pin)) + tt->pollingRequired = false; + #endif + else + tt->pollingRequired = true; + + // Add to the start of the list tt->nextSensor = firstSensor; firstSensor = tt; -#endif tt->data.snum = snum; tt->data.pin = pin; @@ -248,9 +219,8 @@ Sensor *Sensor::create(int snum, VPIN pin, int pullUp){ tt->inputState = 0; tt->latchDelay = minReadCount; - int params[] = {pullUp}; if (pin != VPIN_NONE) - IODevice::configure(pin, IODevice::CONFIGURE_INPUT, 1, params); + IODevice::configureInput(pin, pullUp); // Generally, internal pull-up resistors are not, on their own, sufficient // for external infrared sensors --- each sensor must have its own 1K external pull-up resistor @@ -343,6 +313,5 @@ unsigned long Sensor::lastReadCycle=0; #ifdef USE_NOTIFY Sensor *Sensor::firstPollSensor = NULL; Sensor *Sensor::lastSensor = NULL; -bool Sensor::pollSignalPhase = false; bool Sensor::inputChangeCallbackRegistered = false; #endif \ No newline at end of file diff --git a/Sensors.h b/Sensors.h index 60e414f..7547784 100644 --- a/Sensors.h +++ b/Sensors.h @@ -45,14 +45,6 @@ struct SensorData { class Sensor{ // The sensor list is a linked list where each sensor's 'nextSensor' field points to the next. // The pointer is null in the last on the list. - // To partition the sensor into those sensors which require polling through cyclic calls - // to 'IODevice::read(vpin)', and those which support callback on change, 'firstSensor' - // points to the start of the overall list, and 'lastSensor' points to the end of the list - // (the last sensor object). This structure allows sensors to be added to the start or the - // end of the list easily. So if an input pin supports change notification, it is placed at the - // end of the list. If not, it is placed at the beginning. And the pointer 'firstPollSensor' - // is set to the first of the sensor objects that requires scanning. Thus, we can iterate - // through the whole list, or just through the part that requires scanning. public: SensorData data; @@ -74,6 +66,7 @@ public: // Constructor Sensor(); Sensor *nextSensor; + void setState(int state); static void load(); static void store(); @@ -88,9 +81,9 @@ public: static const unsigned int minReadCount = 1; // number of additional scans before acting on change // E.g. 1 means that a change is ignored for one scan and actioned on the next. // Max value is 63 + bool pollingRequired = true; #ifdef USE_NOTIFY - static bool pollSignalPhase; static void inputChangeCallback(VPIN vpin, int state); static bool inputChangeCallbackRegistered; #endif From bfc2b75eb51b9c26faf1f2a16a106cf10652171d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 1 Oct 2021 11:01:32 +0100 Subject: [PATCH 30/49] SERIAL2 Typo --- RMFTMacros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RMFTMacros.h b/RMFTMacros.h index 9fd1c22..01bfb46 100644 --- a/RMFTMacros.h +++ b/RMFTMacros.h @@ -149,7 +149,7 @@ const int StringMacroTracker1=__COUNTER__; #define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break; #define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break; #define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break; -#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(L&Serial2,F(msg));break; +#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break; #define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break; #define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break; #include "myAutomation.h" From 7aed7de6cd4305657032788385cddb2fb4f406ce Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 3 Oct 2021 12:24:14 +0100 Subject: [PATCH 31/49] Change default LCD address. LCD Backpack Address in example config.h changed to 0x27 (to match the most commonly available PCF8574 device). --- config.example.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.example.h b/config.example.h index 9b1855f..5f26ca7 100644 --- a/config.example.h +++ b/config.example.h @@ -114,11 +114,11 @@ The configuration file for DCC-EX Command Station // DEFINE LCD SCREEN USAGE BY THE BASE STATION // // Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780 -// controller and a PCF8574 based I2C 'backpack'. +// controller and a commonly available PCF8574 based I2C 'backpack'. // To enable, uncomment one of the #define lines below -// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows -// #define LCD_DRIVER 0x3F,16,2 +// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows +// #define LCD_DRIVER 0x27,16,2 //OR define OLED_DRIVER width,height in pixels (address auto detected) // 128x32 or 128x64 I2C SSD1306-based devices are supported. From 6dde81127930388cb3892b39243aea86fd28a6dd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 5 Oct 2021 12:48:45 +0100 Subject: [PATCH 32/49] Optimise HAL drivers for TOF sensor and Analogue Inputs Increased use of async I2C in HAL drivers to reduce overall loop time overhead. --- IO_AnalogueInputs.h | 65 ++++++++++----- IO_VL53L0X.h | 191 +++++++++++++++++++++++++------------------- 2 files changed, 154 insertions(+), 102 deletions(-) diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 725f300..85c224a 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -63,7 +63,9 @@ public: _firstVpin = firstVpin; _nPins = min(nPins,4); _i2cAddress = i2cAddress; - _currentPin = _nPins; // Suppress read on first loop entry. + _currentPin = 0; + for (int8_t i=0; i<_nPins; i++) + _value[i] = -1; addDevice(this); } static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { @@ -73,6 +75,7 @@ private: void _begin() { // Initialise ADS device if (I2CManager.exists(_i2cAddress)) { + _nextState = STATE_STARTSCAN; #ifdef DIAG_IO _display(); #endif @@ -84,36 +87,48 @@ private: void _loop(unsigned long currentMicros) override { // Check that previous non-blocking write has completed, if not then wait - uint8_t status = _i2crb.wait(); + uint8_t status = _i2crb.status; + if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything. if (status == I2C_STATUS_OK) { - // If _currentPin is in the valid range, continue reading the pin values - if (_currentPin < _nPins) { - _outBuffer[0] = 0x00; // Conversion register address - uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1); // Read register - if (status == I2C_STATUS_OK) { + switch (_nextState) { + case STATE_STARTSCAN: + // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details + // of configuration register settings. + _outBuffer[0] = 0x01; // Config register address + _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n + _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off + // Write command, without waiting for completion. + I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); + + delayUntil(currentMicros + scanInterval); + _nextState = STATE_STARTREAD; + break; + + case STATE_STARTREAD: + // Reading the pin value + _outBuffer[0] = 0x00; // Conversion register address + I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register + _nextState = STATE_GETVALUE; + break; + + case STATE_GETVALUE: _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; #ifdef IO_ANALOGUE_SLOW DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); #endif - } + + // Move to next pin + if (++_currentPin >= _nPins) _currentPin = 0; + _nextState = STATE_STARTSCAN; + break; + + default: + break; } - } - if (status != I2C_STATUS_OK) { + } else { // error status DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } - // Move to next pin - if (++_currentPin >= _nPins) _currentPin = 0; - - // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details - // of configuration register settings. - _outBuffer[0] = 0x01; // Config register address - _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n - _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off - // Write command, without waiting for completion. - I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); - - delayUntil(currentMicros + scanInterval); } int _readAnalogue(VPIN vpin) override { @@ -133,12 +148,18 @@ private: #else const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds. #endif + enum : uint8_t { + STATE_STARTSCAN, + STATE_STARTREAD, + STATE_GETVALUE, + }; uint16_t _value[4]; uint8_t _i2cAddress; uint8_t _outBuffer[3]; uint8_t _inBuffer[2]; uint8_t _currentPin; // ADC pin currently being scanned I2CRB _i2crb; + uint8_t _nextState; }; #endif // io_analogueinputs_h \ No newline at end of file diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 33c6a16..bcdbc49 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -28,11 +28,12 @@ * The operation shown here doesn't include any calibration, so is probably not as accurate * as using the full driver, but it's probably accurate enough for the purpose. * - * The device driver allocates up to 3 vpins to the device. A digital read on any of the pins + * The device driver allocates up to 3 vpins to the device. A digital read on the first pin * will return a value that indicates whether the object is within the threshold range (1) * or not (0). An analogue read on the first pin returns the last measured distance (in mm), * the second pin returns the signal strength, and the third pin returns detected - * ambient light level. + * ambient light level. By default the device takes around 60ms to complete a ranging + * operation, so we do a 100ms cycle (10 samples per second). * * The VL53L0X is initially set to respond to I2C address 0x29. If you only have one module, * you can use this address. However, the address can be modified by software. If @@ -100,9 +101,22 @@ private: uint16_t _offThreshold; VPIN _xshutPin; bool _value; - bool _initialising = true; - uint8_t _entryCount = 0; - bool _scanInProgress = false; + uint8_t _nextState = 0; + I2CRB _rb; + uint8_t _inBuffer[12]; + uint8_t _outBuffer[2]; + // State machine states. + enum : uint8_t { + STATE_INIT = 0, + STATE_CONFIGUREADDRESS = 1, + STATE_SKIP = 2, + STATE_CONFIGUREDEVICE = 3, + STATE_INITIATESCAN = 4, + STATE_CHECKSTATUS = 5, + STATE_GETRESULTS = 6, + STATE_DECODERESULTS = 7, + }; + // Register addresses enum : uint8_t { VL53L0X_REG_SYSRANGE_START=0x00, @@ -130,89 +144,107 @@ public: protected: void _begin() override { - _initialising = true; - // Check if device is already responding on the nominated address. - if (I2CManager.exists(_i2cAddress)) { - // Yes, it's already on this address, so skip the address initialisation. - _entryCount = 3; - } else { - _entryCount = 0; + if (_xshutPin == VPIN_NONE) { + // Check if device is already responding on the nominated address. + if (I2CManager.exists(_i2cAddress)) { + // Yes, it's already on this address, so skip the address initialisation. + _nextState = STATE_CONFIGUREDEVICE; + } else { + _nextState = STATE_INIT; + } } } void _loop(unsigned long currentMicros) override { - if (_initialising) { - switch (_entryCount++) { - case 0: - // On first entry to loop, reset this module by pulling XSHUT low. All modules - // will be reset in turn. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); - break; - case 1: - // On second entry, set XSHUT pin high to allow the module to restart. - // On the module, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); - // Allow the module time to restart - delay(10); - // Then write the desired I2C address to the device, while this is the only - // module responding to the default address. - I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); - break; - case 3: - // After two more loops, check if device has been configured. - if (I2CManager.exists(_i2cAddress)) { - #ifdef DIAG_IO - _display(); - #endif - // Set 2.8V mode - write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, - read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); - } else { - DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); - _deviceState = DEVSTATE_FAILED; - } - _initialising = false; - _entryCount = 0; - break; - default: - break; - } - } else { - - if (!_scanInProgress) { + uint8_t status; + switch (_nextState) { + case STATE_INIT: + // On first entry to loop, reset this module by pulling XSHUT low. All modules + // will be reset in turn. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); + _nextState = STATE_CONFIGUREADDRESS; + break; + case STATE_CONFIGUREADDRESS: + // On second entry, set XSHUT pin high to allow the module to restart. + // On the module, there is a diode in series with the XSHUT pin to + // protect the low-voltage pin against +5V. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); + // Allow the module time to restart + delay(10); + // Then write the desired I2C address to the device, while this is the only + // module responding to the default address. + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); + _nextState = STATE_SKIP; + break; + case STATE_SKIP: + // Do nothing on the third entry. + _nextState = STATE_CONFIGUREDEVICE; + break; + case STATE_CONFIGUREDEVICE: + // On next entry, check if device address has been set. + if (I2CManager.exists(_i2cAddress)) { + #ifdef DIAG_IO + _display(); + #endif + // Set 2.8V mode + write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); + } else { + DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + } + _nextState = STATE_INITIATESCAN; + break; + case STATE_INITIATESCAN: // Not scanning, so initiate a scan - uint8_t status = write_reg(VL53L0X_REG_SYSRANGE_START, 0x01); + _outBuffer[0] = VL53L0X_REG_SYSRANGE_START; + _outBuffer[1] = 0x01; + I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb); + _nextState = STATE_CHECKSTATUS; + break; + case STATE_CHECKSTATUS: + status = _rb.status; + if (status == I2C_STATUS_PENDING) return; // try next time if (status != I2C_STATUS_OK) { DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; _value = false; } else - _scanInProgress = true; - - } else { - // Scan in progress, so check for completion. - uint8_t status = read_reg(VL53L0X_REG_RESULT_RANGE_STATUS); - if (status & 1) { - // Completed. Retrieve data - uint8_t inBuffer[12]; - read_registers(VL53L0X_REG_RESULT_RANGE_STATUS, inBuffer, 12); - uint8_t deviceRangeStatus = ((inBuffer[0] & 0x78) >> 3); + _nextState = 2; + delayUntil(currentMicros + 95000); // wait for 95 ms before checking. + _nextState = STATE_GETRESULTS; + break; + case STATE_GETRESULTS: + // Ranging completed. Request results + _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS; + I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb); + _nextState = 3; + delayUntil(currentMicros + 5000); // Allow 5ms to get data + _nextState = STATE_DECODERESULTS; + break; + case STATE_DECODERESULTS: + // If I2C write still busy, return. + status = _rb.status; + if (status == I2C_STATUS_PENDING) return; // try again next time + if (status == I2C_STATUS_OK) { + if (!(_inBuffer[0] & 1)) return; // device still busy + uint8_t deviceRangeStatus = ((_inBuffer[0] & 0x78) >> 3); if (deviceRangeStatus == 0x0b) { // Range status OK, so use data - _ambient = makeuint16(inBuffer[7], inBuffer[6]); - _signal = makeuint16(inBuffer[9], inBuffer[8]); - _distance = makeuint16(inBuffer[11], inBuffer[10]); + _ambient = makeuint16(_inBuffer[7], _inBuffer[6]); + _signal = makeuint16(_inBuffer[9], _inBuffer[8]); + _distance = makeuint16(_inBuffer[11], _inBuffer[10]); if (_distance <= _onThreshold) _value = true; else if (_distance > _offThreshold) _value = false; } - _scanInProgress = false; } - } - // Next entry in 10 milliseconds. - delayUntil(currentMicros + 10000UL); + // Completed. Restart scan on next loop entry. + _nextState = STATE_INITIATESCAN; + break; + default: + break; } } @@ -231,9 +263,12 @@ protected: } } - // For digital read, return the same value for all pins. - int _read(VPIN) override { - return _value; + // For digital read, return zero for all but first pin. + int _read(VPIN vpin) override { + if (vpin == _firstVpin) + return _value; + else + return 0; } void _display() override { @@ -255,13 +290,9 @@ private: return I2CManager.write(_i2cAddress, outBuffer, 2); } uint8_t read_reg(uint8_t reg) { - // read byte from register register - uint8_t inBuffer[1]; - I2CManager.read(_i2cAddress, inBuffer, 1, ®, 1); - return inBuffer[0]; - } - void read_registers(uint8_t reg, uint8_t buffer[], uint8_t size) { - I2CManager.read(_i2cAddress, buffer, size, ®, 1); + // read byte from register and return value + I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1); + return _inBuffer[0]; } }; From 80472a76dc2e83c9c973ee5fe7924fea335c9f4b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 8 Oct 2021 13:28:43 +0100 Subject: [PATCH 33/49] I2CManager - support slower I2C speeds. Previously the driver allowed speeds down to 32kHz but lower speeds were not implemented correctly. --- I2CManager.cpp | 6 +++--- I2CManager_AVR.h | 13 ++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 94c4baf..a3ea611 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -179,7 +179,7 @@ I2CManagerClass I2CManager = I2CManagerClass(); /*************************************************************************** * Block waiting for request block to complete, and return completion status. * Since such a loop could potentially last for ever if the RB status doesn't - * change, we set a high limit (0.1sec, 100ms) on the wait time and, if it + * change, we set a high limit (1sec, 1000ms) on the wait time and, if it * hasn't changed by that time we assume it's not going to, and just return * a timeout status. This means that CS will not lock up. ***************************************************************************/ @@ -187,8 +187,8 @@ uint8_t I2CRB::wait() { unsigned long waitStart = millis(); do { I2CManager.loop(); - // Rather than looping indefinitely, let's set a very high timeout (100ms). - if ((millis() - waitStart) > 100UL) { + // Rather than looping indefinitely, let's set a very high timeout (1s). + if ((millis() - waitStart) > 1000UL) { DIAG(F("I2C TIMEOUT I2C:x%x I2CRB:x%x"), i2cAddress, this); status = I2C_STATUS_TIMEOUT; // Note that, although the timeout is posted, the request may yet complete. diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 310afa2..6492e00 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -62,7 +62,18 @@ * Set I2C clock speed register. ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { - TWBR = ((F_CPU / i2cClockSpeed) - 16) / 2; + unsigned long temp = ((F_CPU / i2cClockSpeed) - 16) / 2; + for (uint8_t preScaler = 0; preScaler<=3; preScaler++) { + if (temp <= 255) { + TWBR = temp; + TWSR = (TWSR & 0xfc) | preScaler; + return; + } else + temp /= 4; + } + // Set slowest speed ~= 500 bits/sec + TWBR = 255; + TWSR |= 0x03; } /*************************************************************************** From 9097a62f42c9345c42f8defe7dd80ccee0939c27 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 8 Oct 2021 13:30:23 +0100 Subject: [PATCH 34/49] Add new and commands. Alias for existing command added as (since not all analogue outputs are servos). Also, added to display the value of an analogue input pin. --- DCCEXParser.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b5f472f..ed1f684 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -71,6 +71,8 @@ const int16_t HASH_KEYWORD_T=84; const int16_t HASH_KEYWORD_LCN = 15137; const int16_t HASH_KEYWORD_HAL = 10853; const int16_t HASH_KEYWORD_SHOW = -21309; +const int16_t HASH_KEYWORD_ANIN = -10424; +const int16_t HASH_KEYWORD_ANOUT = -26399; #ifdef HAS_ENOUGH_MEMORY const int16_t HASH_KEYWORD_WIFI = -5583; const int16_t HASH_KEYWORD_ETHERNET = -30767; @@ -879,9 +881,14 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) return true; case HASH_KEYWORD_SERVO: // + case HASH_KEYWORD_ANOUT: // IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); break; + case HASH_KEYWORD_ANIN: // Display analogue input value + DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1])); + break; + #if !defined(IO_MINIMAL_HAL) case HASH_KEYWORD_HAL: if (p[1] == HASH_KEYWORD_SHOW) From 4f16a4ca06a8bd8c59ecb675a28470b5243b681b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 15 Oct 2021 18:34:47 +0100 Subject: [PATCH 35/49] Fix GPIO Expander initial output state. Previously, pullups were enabled on GPIO Expander digital pins by default, even if the pin was only ever used as an output. This could lead to a spurious HIGH state being seen by external equipment before the output is initialised to LOW. To avoid this, the pin pullup is now not enabled until a configure or read operation is issued for the pin. --- IODevice.h | 4 ++-- IO_GPIOBase.h | 25 +++++++++++++++++++------ IO_MCP23008.h | 14 +++++++++----- IO_MCP23017.h | 15 ++++++++++----- IO_PCF8574.h | 20 +++++++++++++++++++- 5 files changed, 59 insertions(+), 19 deletions(-) diff --git a/IODevice.h b/IODevice.h index fe1d3e6..5d6abc3 100644 --- a/IODevice.h +++ b/IODevice.h @@ -120,7 +120,7 @@ public: } // User-friendly function for configuring a servo pin. - inline static bool configureServo(VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile, uint16_t duration, uint8_t initialState=0) { + inline static bool configureServo(VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile=0, uint16_t duration=0, uint8_t initialState=0) { int params[] = {(int)activePosition, (int)inactivePosition, profile, (int)duration, initialState}; return IODevice::configure(vpin, CONFIGURE_SERVO, 5, params); } @@ -203,7 +203,7 @@ protected: // Method to perform updates on an ongoing basis (optionally implemented within device class) virtual void _loop(unsigned long currentMicros) { - (void)currentMicros; // Suppress compiler warning. + delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls. }; // Method for displaying info on DIAG output (optionally implemented within device class) diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 9b1bee5..4269a70 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -53,6 +53,7 @@ protected: T _portOutputState; T _portMode; T _portPullup; + T _portInUse; // Interval between refreshes of each input port static const int _portTickTime = 4000; @@ -100,7 +101,8 @@ void GPIOBase::_begin() { #endif _portMode = 0; // default to input mode _portPullup = -1; // default to pullup enabled - _portInputState = -1; + _portInputState = -1; + _portInUse = 0; _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { @@ -126,11 +128,15 @@ bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun _portPullup |= mask; else _portPullup &= ~mask; + // Mark that port has been accessed + _portInUse |= mask; + // Set input mode + _portMode &= ~mask; // Call subclass's virtual function to write to device + _writePortModes(); _writePullups(); - // Re-read port following change - _readGpioPort(); + // Port change will be notified on next loop entry. return true; } @@ -149,6 +155,8 @@ void GPIOBase::_loop(unsigned long currentMicros) { I2CManager.getErrorMessage(status)); } _processCompletion(status); + // Set unused pin and write mode pin value to 1 + _portInputState |= ~_portInUse | _portMode; // Scan for changes in input states and invoke callback (if present) T differences = lastPortStates ^ _portInputState; @@ -199,8 +207,9 @@ void GPIOBase::_write(VPIN vpin, int value) { DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, value); #endif - // Set port mode output + // Set port mode output if currently not output mode if (!(_portMode & mask)) { + _portInUse |= mask; _portMode |= mask; _writePortModes(); } @@ -220,12 +229,16 @@ int GPIOBase::_read(VPIN vpin) { int pin = vpin - _firstVpin; T mask = 1 << pin; - // Set port mode to input - if (_portMode & mask) { + // Set port mode to input if currently output or first use + if ((_portMode | ~_portInUse) & mask) { _portMode &= ~mask; + _portInUse |= mask; + _writePullups(); _writePortModes(); // Port won't have been read yet, so read it now. _readGpioPort(); + // Set unused pin and write mode pin value to 1 + _portInputState |= ~_portInUse | _portMode; #ifdef DIAG_IO DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState); #endif diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 3557b49..18ff12f 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -42,14 +42,18 @@ private: I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState); } void _writePullups() override { - I2CManager.write(_I2CAddress, 2, REG_GPPU, _portPullup); + // Set pullups only for in-use pins. This prevents pullup being set for a pin that + // is intended for use as an output but hasn't been written to yet. + I2CManager.write(_I2CAddress, 2, REG_GPPU, _portPullup & _portInUse); } void _writePortModes() override { - // Each bit is 1 for an input, 0 for an output, i.e. inverted. - I2CManager.write(_I2CAddress, 2, REG_IODIR, ~_portMode); - // Enable interrupt-on-change for pins that are inputs (_portMode=0) + // Write 0 to IODIR for in-use pins that are outputs, 1 for others. + uint8_t temp = ~(_portMode & _portInUse); + I2CManager.write(_I2CAddress, 2, REG_IODIR, temp); + // Enable interrupt-on-change for in-use pins that are inputs (_portMode=0) + temp = ~_portMode & _portInUse; I2CManager.write(_I2CAddress, 2, REG_INTCON, 0x00); - I2CManager.write(_I2CAddress, 2, REG_GPINTEN, ~_portMode); + I2CManager.write(_I2CAddress, 2, REG_GPINTEN, temp); } void _readGpioPort(bool immediate) override { if (immediate) { diff --git a/IO_MCP23017.h b/IO_MCP23017.h index d7c27ce..930b051 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -48,14 +48,19 @@ private: I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8); } void _writePullups() override { - I2CManager.write(_I2CAddress, 3, REG_GPPUA, _portPullup, _portPullup>>8); + // Set pullups only for in-use pins. This prevents pullup being set for a pin that + // is intended for use as an output but hasn't been written to yet. + uint16_t temp = _portPullup & _portInUse; + I2CManager.write(_I2CAddress, 3, REG_GPPUA, temp, temp>>8); } void _writePortModes() override { - // Write 1 to IODIR for pins that are inputs, 0 for outputs (i.e. _portMode inverted) - I2CManager.write(_I2CAddress, 3, REG_IODIRA, ~_portMode, (~_portMode)>>8); - // Enable interrupt for those pins which are inputs (_portMode=0) + // Write 0 to IODIR for in-use pins that are outputs, 1 for others. + uint16_t temp = ~(_portMode & _portInUse); + I2CManager.write(_I2CAddress, 3, REG_IODIRA, temp, temp>>8); + // Enable interrupt for in-use pins which are inputs (_portMode=0) + temp = ~_portMode & _portInUse; I2CManager.write(_I2CAddress, 3, REG_INTCONA, 0x00, 0x00); - I2CManager.write(_I2CAddress, 3, REG_GPINTENA, ~_portMode, (~_portMode)>>8); + I2CManager.write(_I2CAddress, 3, REG_GPINTENA, temp, temp>>8); } void _readGpioPort(bool immediate) override { if (immediate) { diff --git a/IO_PCF8574.h b/IO_PCF8574.h index 2a8d363..dea5e2c 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -17,6 +17,24 @@ * along with CommandStation. If not, see . */ +/* + * The PCF8574 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_PCF8574_H #define IO_PCF8574_H @@ -70,7 +88,7 @@ private: if (status == I2C_STATUS_OK) _portInputState = ((uint16_t)inputBuffer[0]) & 0xff; else - _portInputState = 0xff; + _portInputState = 0xff; } // Set up device ports From b7bcd133470581d7bbdf491b372d14d35edeb32a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 21 Oct 2021 16:43:42 +0100 Subject: [PATCH 36/49] Fix Arduino pin pullup initial state. If an Arduino pin was used as an input (e.g. by EXRAIL) without previously configuring it, the default pullup wouldn't be set up. Now, on first call to the _read() method the pullup will be enabled. --- IODevice.cpp | 14 ++++++++++---- IODevice.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 5fd27ff..3456af5 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -327,12 +327,14 @@ IONotifyCallback *IONotifyCallback::first = 0; ArduinoPins::ArduinoPins(VPIN firstVpin, int nPins) { _firstVpin = firstVpin; _nPins = nPins; - uint8_t arrayLen = (_nPins+7)/8; - _pinPullups = (uint8_t *)calloc(2, arrayLen); + int arrayLen = (_nPins+7)/8; + _pinPullups = (uint8_t *)calloc(3, arrayLen); _pinModes = (&_pinPullups[0]) + arrayLen; + _pinInUse = (&_pinPullups[0]) + 2*arrayLen; for (int i=0; i Date: Thu, 21 Oct 2021 22:44:25 +0100 Subject: [PATCH 37/49] Fixup EXRAIL Read Loco issues --- RMFT2.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/RMFT2.cpp b/RMFT2.cpp index a117318..a6dd935 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -44,6 +44,7 @@ const int16_t HASH_KEYWORD_ROUTES=-3702; // The thrrads exist in a ring, each time through loop() the next thread in the ring is serviced. // Statics +const int16_t LOCO_ID_WAITING=-99; // waiting for loco id from prog track int16_t RMFT2::progtrackLocoId; // used for callback when detecting a loco on prograck bool RMFT2::diag=false; // RMFT2 * RMFT2::loopTask=NULL; // loopTask contains the address of ONE of the tasks in a ring. @@ -595,14 +596,20 @@ void RMFT2::loop2() { break; case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes + progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet DCC::getLocoId(readLocoCallback); break; case OPCODE_READ_LOCO2: - if (progtrackLocoId<0) { + if (progtrackLocoId==LOCO_ID_WAITING) { delayMe(100); return; // still waiting for callback } + if (progtrackLocoId<0) { + kill(F("No Loco Found"),progtrackLocoId); + return; // still waiting for callback + } + loco=progtrackLocoId; speedo=0; forward=true; From a9c31eb1ae045b9ca3d7a554a526250bb8b4e804 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 29 Oct 2021 22:30:01 +0200 Subject: [PATCH 38/49] YFROBOT: One more motor board with L298P --- MotorDrivers.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MotorDrivers.h b/MotorDrivers.h index 992e1de..1088a3b 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -82,5 +82,9 @@ #define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \ new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) +// YFROBOT Motor Shield (V3.1) +#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \ + new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ + new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) #endif From 250c372f5c50d9c56dba96a7742a9439367123c0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 29 Oct 2021 20:30:40 +0000 Subject: [PATCH 39/49] Committing a SHA --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6ffec91..928a720 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "50fcbc0" +#define GITHUB_SHA "a9c31eb" From e3cbaf5f240facd49a4713bb5b023a6c18895213 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 22:17:51 +0100 Subject: [PATCH 40/49] unknown locos should have speed forward --- DCC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index 226425b..7d5592b 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -137,7 +137,7 @@ uint8_t DCC::getThrottleSpeed(int cab) { bool DCC::getThrottleDirection(int cab) { int reg=lookupSpeedTable(cab); - if (reg<0) return false ; + if (reg<0) return true; return (speedTable[reg].speedCode & 0x80) !=0; } From 79ce71c2f9e8096774d44be1fc120bace6be37f3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 21:18:17 +0000 Subject: [PATCH 41/49] Committing a SHA --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 928a720..803759a 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "a9c31eb" +#define GITHUB_SHA "e3cbaf5" From 055bc7bfe2ef2a31ffe794ef6b6da22a664730c1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 22:20:59 +0100 Subject: [PATCH 42/49] unknown locos should have speed forward --- DCC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index 70f30e2..f42ddf7 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -141,7 +141,7 @@ uint8_t DCC::getThrottleSpeed(int cab) { bool DCC::getThrottleDirection(int cab) { int reg=lookupSpeedTable(cab); - if (reg<0) return false ; + if (reg<0) return true; return (speedTable[reg].speedCode & 0x80) !=0; } From e3d771a24da09bb9c14d4b9b94c80f5c48bf6790 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Nov 2021 21:57:06 +0100 Subject: [PATCH 43/49] set default pullup in EXRAIL begin code --- RMFT2.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RMFT2.cpp b/RMFT2.cpp index a6dd935..a4f1758 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -66,6 +66,14 @@ byte RMFT2::flags[MAX_FLAGS]; byte opcode=GET_OPCODE; if (opcode==OPCODE_ENDEXRAIL) break; + switch (opcode) { + case OPCODE_AT: + case OPCODE_AFTER: + case OPCODE_IF: + case OPCODE_IFNOT: + IODevice::configureInput((VPIN)GET_OPERAND(0),true); + } + if (opcode==OPCODE_SIGNAL) { VPIN red=GET_OPERAND(0); VPIN amber=GET_OPERAND(1); From a16f6c8749093a9fab16faed6817e24cfab64456 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Nov 2021 22:12:32 +0100 Subject: [PATCH 44/49] configure pins correct even when HAL not used --- IODevice.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 3456af5..1f9f53f 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -160,7 +160,7 @@ void IODevice::write(VPIN vpin, int value) { return; } #ifdef DIAG_IO - //DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin); + DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin); #endif } @@ -179,7 +179,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para return; } #ifdef DIAG_IO - //DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin); + DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin); #endif } @@ -265,7 +265,7 @@ int IODevice::read(VPIN vpin) { return dev->_read(vpin); } #ifdef DIAG_IO - //DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin); + DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin); #endif return false; } @@ -288,7 +288,17 @@ int IODevice::readAnalogue(VPIN vpin) { // Minimal implementations of public HAL interface, to support Arduino pin I/O and nothing more. void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); } -bool IODevice::configure(VPIN, ConfigTypeEnum, int, int []) { return true; } +bool IODevice::configure(VPIN pin, ConfigTypeEnum, int, int p[]) { + #ifdef DIAG_IO + DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]); + #endif + if (p[0]) { + pinMode(pin, INPUT_PULLUP); + } else { + pinMode(pin, INPUT); + } + return true; +} void IODevice::write(VPIN vpin, int value) { digitalWrite(vpin, value); pinMode(vpin, OUTPUT); @@ -297,7 +307,6 @@ void IODevice::writeAnalogue(VPIN, int, uint8_t, uint16_t) {} bool IODevice::isBusy(VPIN) { return false; } bool IODevice::hasCallback(VPIN) { return false; } int IODevice::read(VPIN vpin) { - pinMode(vpin, INPUT_PULLUP); return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1) } int IODevice::readAnalogue(VPIN vpin) { @@ -434,7 +443,7 @@ int ArduinoPins::_readAnalogue(VPIN vpin) { interrupts(); #ifdef DIAG_IO - //DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value); + DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value); #endif return value; } From 8853b23f8811869a2991b1ae9f2300eba235c31f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Nov 2021 16:04:49 +0100 Subject: [PATCH 45/49] uopdate version.h --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 09211fa..1c63c85 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "3.1.6" +#define VERSION "3.1.7" +// 3.1.7 Bugfix: Unknown locos should have speed forward // 3.1.6 Make output ID two bytes and guess format/size of registered outputs found in EEPROM // 3.1.5 Fix LCD corruption on power-up // 3.1.4 Refactor OLED and LCD drivers and remove unused code From d3381c6b2d06b96c848ec84c09fb631622652009 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Nov 2021 15:05:58 +0000 Subject: [PATCH 46/49] Committing a SHA --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 803759a..f29ac14 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "e3cbaf5" +#define GITHUB_SHA "8853b23" From 37904b5fa6bde89fe90df4c0da170ddcd0e231eb Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Nov 2021 17:03:28 +0100 Subject: [PATCH 47/49] make rc1 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 2a7eba0..5a0f943 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,7 @@ #include "StringFormatter.h" -#define VERSION "3.2.0" +#define VERSION "3.2.0 rc1" // 3.2.0 Major functional and non-functional changes. // New HAL added for I/O (digital and analogue inputs and outputs, servos etc). // Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules. From 89dcafb2d7b6796e91c0d95e70f3837d34a4011c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Nov 2021 16:04:52 +0000 Subject: [PATCH 48/49] Committing a SHA --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f29ac14..4338846 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "8853b23" +#define GITHUB_SHA "37904b5" From 6c7556377966af6b3899c1db9890149d35355677 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 8 Nov 2021 00:19:23 +0100 Subject: [PATCH 49/49] handle negative pins --- RMFT2.cpp | 4 +++- version.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RMFT2.cpp b/RMFT2.cpp index a4f1758..5a91541 100644 --- a/RMFT2.cpp +++ b/RMFT2.cpp @@ -71,7 +71,9 @@ byte RMFT2::flags[MAX_FLAGS]; case OPCODE_AFTER: case OPCODE_IF: case OPCODE_IFNOT: - IODevice::configureInput((VPIN)GET_OPERAND(0),true); + int16_t pin = (int16_t)GET_OPERAND(0); + if (pin<0) pin = -pin; + IODevice::configureInput((VPIN)pin,true); } if (opcode==OPCODE_SIGNAL) { diff --git a/version.h b/version.h index 5a0f943..617673c 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,7 @@ #include "StringFormatter.h" -#define VERSION "3.2.0 rc1" +#define VERSION "3.2.0 rc2" // 3.2.0 Major functional and non-functional changes. // New HAL added for I/O (digital and analogue inputs and outputs, servos etc). // Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.