From 7397a4089b9fb93a1d9289be1c04170e30e5400f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Sep 2021 00:31:05 +0200 Subject: [PATCH 001/870] first waveform on esp --- CommandStation-EX.ino | 1 - DCC.cpp | 24 ++++++++++++------------ DCC.h | 4 +++- DCCEXParser.cpp | 2 +- DCCTimer.cpp | 28 +++++++++++++++++++++++----- MotorDriver.cpp | 6 +++--- MotorDriver.h | 4 +++- freeMemory.cpp | 17 ++++++++++++++++- 8 files changed, 61 insertions(+), 25 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index c8a3aed07..c112c54a4 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -111,7 +111,6 @@ void loop() // Responsibility 1: Handle DCC background processes // (loco reminders and power checks) DCC::loop(); - // Responsibility 2: handle any incoming commands on USB connection serialParser.loop(Serial); diff --git a/DCC.cpp b/DCC.cpp index 226425b4b..286e7c3fe 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -304,14 +304,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = { W0,WACK, V0, WACK, // validate bit is 0 ITC1, // if acked, callback(1) - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH WRITE_BIT1_PROG[] = { BASELINE, W1,WACK, V1, WACK, // validate bit is 1 ITC1, // if acked, callback(1) - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH VERIFY_BIT0_PROG[] = { @@ -320,7 +320,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = { ITC0, // if acked, callback(0) V1, WACK, // validate bit is 1 ITC1, - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH VERIFY_BIT1_PROG[] = { BASELINE, @@ -328,7 +328,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = { ITC1, // if acked, callback(1) V0, WACK, ITC0, - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH READ_BIT_PROG[] = { @@ -337,7 +337,7 @@ const ackOp FLASH READ_BIT_PROG[] = { ITC1, // if acked, callback(1) V0, WACK, // validate bit is zero ITC0, // if acked callback 0 - FAIL // bit not readable + CALLFAIL // bit not readable }; const ackOp FLASH WRITE_BYTE_PROG[] = { @@ -345,7 +345,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = { WB,WACK,ITC1, // Write and callback(1) if ACK // handle decoders that dont ack a write VB,WACK,ITC1, // validate byte and callback(1) if correct - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH VERIFY_BYTE_PROG[] = { @@ -370,7 +370,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, VB, WACK, ITCB, // verify merged byte and return it if acked ok - FAIL }; + CALLFAIL }; const ackOp FLASH READ_CV_PROG[] = { @@ -393,7 +393,7 @@ const ackOp FLASH READ_CV_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, VB, WACK, ITCB, // verify merged byte and return it if acked ok - FAIL }; // verification failed + CALLFAIL }; // verification failed const ackOp FLASH LOCO_ID_PROG[] = { @@ -459,7 +459,7 @@ const ackOp FLASH LOCO_ID_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, VB, WACK, ITCB, // verify merged byte and callback - FAIL + CALLFAIL }; const ackOp FLASH SHORT_LOCO_ID_PROG[] = { @@ -476,7 +476,7 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = { SETBYTEL, // low byte of word WB,WACK, // some decoders don't ACK writes VB,WACK,ITCB, - FAIL + CALLFAIL }; const ackOp FLASH LONG_LOCO_ID_PROG[] = { @@ -500,7 +500,7 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = { SETBYTEL, // low byte of word WB,WACK, VB,WACK,ITC1, // callback(1) means Ok - FAIL + CALLFAIL }; void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { @@ -828,7 +828,7 @@ void DCC::ackManagerLoop() { } break; - case FAIL: // callback(-1) + case CALLFAIL: // callback(-1) callback(-1); return; diff --git a/DCC.h b/DCC.h index 1bdd5f0c1..b4736737a 100644 --- a/DCC.h +++ b/DCC.h @@ -40,7 +40,7 @@ enum ackOp : byte ITCB, // If True callback(byte) ITCB7, // If True callback(byte &0x7F) NAKFAIL, // if false callback(-1) - FAIL, // callback(-1) + CALLFAIL, // callback(-1) 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 @@ -191,6 +191,8 @@ class DCC #define ARDUINO_TYPE "TEENSY40" #elif defined(ARDUINO_TEENSY41) #define ARDUINO_TYPE "TEENSY41" +#elif defined(ARDUINO_ARCH_ESP8266) +#define ARDUINO_TYPE "ESP8266" #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2c3885abe..8591844cf 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -30,7 +30,7 @@ #include "EEStore.h" #include "DIAG.h" -#include +//#include // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command diff --git a/DCCTimer.cpp b/DCCTimer.cpp index 727caa196..0ea749138 100644 --- a/DCCTimer.cpp +++ b/DCCTimer.cpp @@ -44,7 +44,7 @@ #include "DCCTimer.h" const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle -const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1; +const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME); INTERRUPT_CALLBACK interruptHandler=0; @@ -53,11 +53,11 @@ INTERRUPT_CALLBACK interruptHandler=0; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; - noInterrupts(); + noInterrupts(); ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us - TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset + TCB0.CCMP = (CLOCK_CYCLES>>1) -1; // 1 tick less for timer reset TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt TCB0.CNT = 0; @@ -146,6 +146,24 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) { } #endif +#elif defined(ARDUINO_ARCH_ESP8266) +// ESP8266 !!!!!!!!!!!!!!!!!!!!! +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + timer1_attachInterrupt(interruptHandler); + timer1_write(CLOCK_CYCLES); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); + interrupts(); +} +bool DCCTimer::isPWMPin(byte pin) { + return false; +} +void DCCTimer::setPWM(byte pin, bool high) { +} + + + #else // Arduino nano, uno, mega etc #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) @@ -159,10 +177,10 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) { void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; - noInterrupts(); + noInterrupts(); ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time TCCR1A = 0; - ICR1 = CLOCK_CYCLES; + ICR1 = CLOCK_CYCLES>>1; TCNT1 = 0; TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 TIMSK1 = _BV(TOIE1); // Enable Software interrupt diff --git a/MotorDriver.cpp b/MotorDriver.cpp index f51ee0479..769c7d6a6 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -176,13 +176,13 @@ int MotorDriver::mA2raw( unsigned int mA) { void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { // DIAG(F("MotorDriver %S Pin=%d,"),type,pin); - (void) type; // avoid compiler warning if diag not used above. - uint8_t port = digitalPinToPort(pin); + (void) type; // avoid compiler warning if diag not used above. + PORTTYPE port = digitalPinToPort(pin); if (input) result.inout = portInputRegister(port); else result.inout = portOutputRegister(port); result.maskHIGH = digitalPinToBitMask(pin); result.maskLOW = ~result.maskHIGH; - // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); + DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } diff --git a/MotorDriver.h b/MotorDriver.h index 08db04978..ffa838732 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -26,13 +26,15 @@ #define UNUSED_PIN 127 // inside int8_t #endif -#if defined(__IMXRT1062__) +#if defined(__IMXRT1062__) || defined (ARDUINO_ARCH_ESP8266) +typedef uint32_t PORTTYPE; struct FASTPIN { volatile uint32_t *inout; uint32_t maskHIGH; uint32_t maskLOW; }; #else +typedef uint8_t PORTTYPE; struct FASTPIN { volatile uint8_t *inout; uint8_t maskHIGH; diff --git a/freeMemory.cpp b/freeMemory.cpp index 5514e8db3..6eb822ea9 100644 --- a/freeMemory.cpp +++ b/freeMemory.cpp @@ -27,6 +27,8 @@ extern "C" char* sbrk(int); #elif defined(__AVR__) extern char *__brkval; extern char *__malloc_heap_start; +#elif defined(ARDUINO_ARCH_ESP8266) +// fine as well #else #error Unsupported board type #endif @@ -34,7 +36,7 @@ extern char *__malloc_heap_start; static volatile int minimum_free_memory = __INT_MAX__; -#if !defined(__IMXRT1062__) +#if !defined(__IMXRT1062__) && !defined(ARDUINO_ARCH_ESP8266) static inline int freeMemory() { char top; #if defined(__arm__) @@ -55,7 +57,20 @@ int minimumFreeMemory() { return retval; } +#elif defined(ARDUINO_ARCH_ESP8266) +// ESP8266 +static inline int freeMemory() { + return ESP.getFreeHeap(); +} +// Return low memory value. +int minimumFreeMemory() { + noInterrupts(); // Disable interrupts + int retval = minimum_free_memory; + interrupts(); // interrupts + return retval; +} #else +// All types of TEENSYs #if defined(ARDUINO_TEENSY40) static const unsigned DTCM_START = 0x20000000UL; static const unsigned OCRAM_START = 0x20200000UL; From 34474cbf5c9dd9448a6678417bcb4558391104a4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 21 Sep 2021 09:23:52 +0200 Subject: [PATCH 002/870] WifiESP skeleton files --- WifiESP.cpp | 31 +++++++++++++++++++++++++++++++ WifiESP.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 WifiESP.cpp create mode 100644 WifiESP.h diff --git a/WifiESP.cpp b/WifiESP.cpp new file mode 100644 index 000000000..5435ca0d8 --- /dev/null +++ b/WifiESP.cpp @@ -0,0 +1,31 @@ +/* + © 2021, Harald Barth. + + This file is part of CommandStation-EX + + 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 . +*/ + +#include "WifiESP.h" +#include "DIAG.h" + +bool WifiESP::setup(const FSH *wifiESSID, + const FSH *wifiPassword, + const FSH *hostname, + const int port, + const byte channel) { + +} +void WifiESP::loop() { +} diff --git a/WifiESP.h b/WifiESP.h new file mode 100644 index 000000000..d73c74143 --- /dev/null +++ b/WifiESP.h @@ -0,0 +1,34 @@ +/* + * © 2021, Harald Barth. + * + * This file is part of CommandStation-EX + * + * 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 WifiESP_h +#define WifiESP_h + +class WifiESP +{ + +public: + static bool setup(const FSH *wifiESSID, + const FSH *wifiPassword, + const FSH *hostname, + const int port, + const byte channel); + static void loop(); +private: +}; +#endif From b048879eaafcaee4fc616e725fbcdce7dc87db06 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 25 Sep 2021 23:18:10 +0200 Subject: [PATCH 003/870] Wifi active --- CommandStation-EX.ino | 7 ++- DCCEX.h | 1 + DCCTimer.cpp | 12 ++++- DCCWaveform.cpp | 15 ++---- MotorDriver.cpp | 11 ++--- MotorDriver.h | 17 ++++++- WifiESP.cpp | 106 ++++++++++++++++++++++++++++++++++++++++-- WifiESP.h | 9 ++-- config.example.h | 11 ++++- freeMemory.cpp | 4 +- 10 files changed, 157 insertions(+), 36 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index c112c54a4..2b89708c6 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -59,6 +59,7 @@ void setup() // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); + Serial.setDebugOutput(true); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); @@ -74,7 +75,7 @@ void setup() #if WIFI_ON WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL); #endif // WIFI_ON - + WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, 2560, 1); #if ETHERNET_ON EthernetInterface::setup(); #endif // ETHERNET_ON @@ -118,6 +119,7 @@ void loop() #if WIFI_ON WifiInterface::loop(); #endif + WifiESP::loop(); #if ETHERNET_ON EthernetInterface::loop(); #endif @@ -134,11 +136,12 @@ void loop() // Report any decrease in memory (will automatically trigger on first call) static int ramLowWatermark = __INT_MAX__; // replaced on first loop - +/* int freeNow = minimumFreeMemory(); if (freeNow < ramLowWatermark) { ramLowWatermark = freeNow; LCD(2,F("Free RAM=%5db"), ramLowWatermark); } + */ } diff --git a/DCCEX.h b/DCCEX.h index 150449064..f6668a6be 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -31,6 +31,7 @@ #include "DCCEXParser.h" #include "version.h" #include "WifiInterface.h" +#include "WifiESP.h" #if ETHERNET_ON == true #include "EthernetInterface.h" #endif diff --git a/DCCTimer.cpp b/DCCTimer.cpp index 0ea749138..5228501fa 100644 --- a/DCCTimer.cpp +++ b/DCCTimer.cpp @@ -150,16 +150,24 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) { // ESP8266 !!!!!!!!!!!!!!!!!!!!! void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; + timer1_disable(); +// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); +// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler); + timer1_attachInterrupt(interruptHandler); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); + timer1_write(CLOCK_CYCLES); +/* noInterrupts(); timer1_attachInterrupt(interruptHandler); timer1_write(CLOCK_CYCLES); timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); interrupts(); +*/ } -bool DCCTimer::isPWMPin(byte pin) { +IRAM_ATTR bool DCCTimer::isPWMPin(byte pin) { return false; } -void DCCTimer::setPWM(byte pin, bool high) { +void ICACHE_RAM_ATTR DCCTimer::setPWM(byte pin, bool high) { } diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index df88e5d35..aaffc2ac2 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -53,31 +53,26 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { DCCTimer::begin(DCCWaveform::interruptHandler); } -void DCCWaveform::loop(bool ackManagerActive) { +void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { mainTrack.checkPowerOverload(false); progTrack.checkPowerOverload(ackManagerActive); } -void DCCWaveform::interruptHandler() { +void IRAM_ATTR DCCWaveform::interruptHandler() { // call the timer edge sensitive actions for progtrack and maintrack // member functions would be cleaner but have more overhead byte sigMain=signalTransform[mainTrack.state]; byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; - // Set the signal state for both tracks mainTrack.motorDriver->setSignal(sigMain); progTrack.motorDriver->setSignal(sigProg); - // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; progTrack.state=stateTransform[progTrack.state]; - - // WAVE_PENDING means we dont yet know what the next bit is if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); else if (progTrack.ackPending) progTrack.checkAck(); - } @@ -197,7 +192,7 @@ const bool DCCWaveform::signalTransform[]={ /* WAVE_LOW_0 -> */ LOW, /* WAVE_PENDING (should not happen) -> */ LOW}; -void DCCWaveform::interrupt2() { +void ICACHE_RAM_ATTR DCCWaveform::interrupt2() { // calculate the next bit to be sent: // set state WAVE_MID_1 for a 1=bit // or WAVE_HIGH_0 for a 0 bit. @@ -207,7 +202,7 @@ void DCCWaveform::interrupt2() { remainingPreambles--; // Update free memory diagnostic as we don't have anything else to do this time. // Allow for checkAck and its called functions using 22 bytes more. - updateMinimumFreeMemory(22); +// might break ESP8266 updateMinimumFreeMemory(22); return; } @@ -306,7 +301,7 @@ byte DCCWaveform::getAck() { return(0); // pending set off but not detected means no ACK. } -void DCCWaveform::checkAck() { +void ICACHE_RAM_ATTR DCCWaveform::checkAck() { // This function operates in interrupt() time so must be fast and can't DIAG if (sentResetsSincePacket > 6) { //ACK timeout ackCheckDuration=millis()-ackCheckStart; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 769c7d6a6..c6c37cf53 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -21,11 +21,6 @@ #include "DCCTimer.h" #include "DIAG.h" -#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH -#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW -#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) -#define isLOW(fastpin) (!isHIGH(fastpin)) - bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; @@ -109,8 +104,8 @@ void MotorDriver::setBrake(bool on) { if (on ^ invertBrake) setHIGH(fastBrakePin); else setLOW(fastBrakePin); } - -void MotorDriver::setSignal( bool high) { +/* +IRAM_ATTR void MotorDriver::setSignal( bool high) { if (usePWM) { DCCTimer::setPWM(signalPin,high); } @@ -125,7 +120,7 @@ void MotorDriver::setSignal( bool high) { } } } - +*/ #if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36) volatile unsigned int overflow_count=0; #endif diff --git a/MotorDriver.h b/MotorDriver.h index ffa838732..06c19b11e 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -42,12 +42,27 @@ struct FASTPIN { }; #endif +#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH +#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW +#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) +#define isLOW(fastpin) (!isHIGH(fastpin)) + class MotorDriver { public: MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); virtual void setPower( bool on); - virtual void setSignal( bool high); + virtual void setSignal( bool high) { + if (high) { + setHIGH(fastSignalPin); + if (dualSignal) setLOW(fastSignalPin2); + } + else { + setLOW(fastSignalPin); + if (dualSignal) setHIGH(fastSignalPin2); + } + }; + virtual void setBrake( bool on); virtual int getCurrentRaw(); virtual unsigned int raw2mA( int raw); diff --git a/WifiESP.cpp b/WifiESP.cpp index 5435ca0d8..e99b1a9ff 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -17,15 +17,111 @@ along with CommandStation. If not, see . */ +#include +#include +#include + #include "WifiESP.h" #include "DIAG.h" +#include "RingStream.h" +#include "CommandDistributor.h" +#include + +static std::vector clients; // a list to hold all clients +static AsyncServer *server; + +static RingStream *outboundRing = new RingStream(2048); + +static void handleError(void* arg, AsyncClient* client, int8_t error) { + DIAG(F("connection error %s from client %s"), client->errorToString(error), client->remoteIP().toString().c_str()); +} + +static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { + DIAG(F("data received from client %s"), client->remoteIP().toString().c_str()); + uint8_t clientId; + for (clientId=0; clientIdspace() >= (c=outboundRing->count()) && client->canSend()) { + char cmd[c+1]; + int i; + for (i=0;iread(); + } + cmd[i]=0; + client->add(cmd, strlen(cmd)); + client->send(); + } +} + +static void handleDisconnect(void* arg, AsyncClient* client) { + DIAG(F("client %s disconnected"), client->remoteIP().toString().c_str()); +} + +static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) { + DIAG(F("client ACK timeout ip: %s"), client->remoteIP().toString().c_str()); +} + + +static void handleNewClient(void* arg, AsyncClient* client) { + DIAG(F("New client has been connected to server, ip: %s"), client->remoteIP().toString().c_str()); + + // add to list + clients.push_back(client); + + // register events + client->onData(&handleData, NULL); + client->onError(&handleError, NULL); + client->onDisconnect(&handleDisconnect, NULL); + client->onTimeout(&handleTimeOut, NULL); + +} + +bool WifiESP::setup(const char *wifiESSID, + const char *wifiPassword, + const char *hostname, + int port, + const byte channel) { + DIAG(F("START")); + // connects to access point + wifi_set_sleep_type(NONE_SLEEP_T); + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect(true); + DIAG(F("BEGIN")); + WiFi.begin(wifiESSID, wifiPassword); + DIAG(F("STATUS")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print('.'); + delay(500); + } + + DIAG(F("SERVER")); + + server = new AsyncServer(port); // start listening on tcp port + + DIAG(F("CLIENT")); + server->onClient(&handleNewClient, server); + DIAG(F("SBEGIN")); + + server->begin(); -bool WifiESP::setup(const FSH *wifiESSID, - const FSH *wifiPassword, - const FSH *hostname, - const int port, - const byte channel) { + DIAG(F("ENDSETUP")); + return true; } void WifiESP::loop() { + static unsigned long last = 0; + if (millis() - last > 60000) { + last = millis(); + DIAG(F("+")); + } + ESP.wdtFeed(); } diff --git a/WifiESP.h b/WifiESP.h index d73c74143..971ccc746 100644 --- a/WifiESP.h +++ b/WifiESP.h @@ -16,16 +16,19 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ + #ifndef WifiESP_h #define WifiESP_h +#include "FSH.h" + class WifiESP { public: - static bool setup(const FSH *wifiESSID, - const FSH *wifiPassword, - const FSH *hostname, + static bool setup(const char *wifiESSID, + const char *wifiPassword, + const char *hostname, const int port, const byte channel); static void loop(); diff --git a/config.example.h b/config.example.h index 1d1977a2a..eb7e02b8b 100644 --- a/config.example.h +++ b/config.example.h @@ -41,7 +41,14 @@ The configuration file for DCC-EX Command Station // | // +-----------------------v // -#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD +//#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD + +#define ESP_MOTOR_SHIELD F("ESP"), \ + new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\ + new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN) + +#define MOTOR_SHIELD_TYPE ESP_MOTOR_SHIELD + ///////////////////////////////////////////////////////////////////////////////////// // // The IP port to talk to a WIFI or Ethernet shield. @@ -53,7 +60,7 @@ The configuration file for DCC-EX Command Station // NOTE: Only supported on Arduino Mega // Set to false if you not even want it on the Arduino Mega // -#define ENABLE_WIFI true +//#define ENABLE_WIFI true ///////////////////////////////////////////////////////////////////////////////////// // diff --git a/freeMemory.cpp b/freeMemory.cpp index 6eb822ea9..af7bd16b9 100644 --- a/freeMemory.cpp +++ b/freeMemory.cpp @@ -64,9 +64,7 @@ static inline int freeMemory() { } // Return low memory value. int minimumFreeMemory() { - noInterrupts(); // Disable interrupts int retval = minimum_free_memory; - interrupts(); // interrupts return retval; } #else @@ -118,7 +116,7 @@ int minimumFreeMemory() { // So even if all of the heap is freed, the reported minimum free // memory will not increase. // -void updateMinimumFreeMemory(unsigned char extraBytes) { +void ICACHE_RAM_ATTR updateMinimumFreeMemory(unsigned char extraBytes) { int spare = freeMemory()-extraBytes; if (spare < 0) spare = 0; if (spare < minimum_free_memory) minimum_free_memory = spare; From fa1d1619b607ddfbd9cd6f133dbe73cb6fbc7c13 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 26 Sep 2021 08:37:59 +0200 Subject: [PATCH 004/870] wifi sendData --- WifiESP.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index e99b1a9ff..36040f15d 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -46,20 +46,22 @@ static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { byte cmd[len+1]; memcpy(cmd,data,len); cmd[len]=0; + outboundRing->mark(clientId); CommandDistributor::parse(clientId,cmd,outboundRing); + outboundRing->commit(); } +} + +bool sendData(uint8_t clientId, char* data, int count) { + AsyncClient *client = clients[clientId]; + // reply to client - int c; - if (client->space() >= (c=outboundRing->count()) && client->canSend()) { - char cmd[c+1]; - int i; - for (i=0;iread(); - } - cmd[i]=0; - client->add(cmd, strlen(cmd)); + if (client->space() >= count && client->canSend()) { + client->add(data, count); client->send(); + return true; } + return false; } static void handleDisconnect(void* arg, AsyncClient* client) { @@ -117,7 +119,12 @@ bool WifiESP::setup(const char *wifiESSID, return true; } + void WifiESP::loop() { + + // Do something with outboundRing + // call sendData + static unsigned long last = 0; if (millis() - last > 60000) { last = millis(); From 35cba02ee7990fa8d6bbcc05e3b8328b5db581f2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 26 Sep 2021 10:59:07 +0200 Subject: [PATCH 005/870] outboundRing uses sendData --- WifiESP.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index 36040f15d..fd0a4b273 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -124,7 +124,18 @@ void WifiESP::loop() { // Do something with outboundRing // call sendData - + int clientId=outboundRing->read(); + if (clientId>=0) { + int count=outboundRing->count(); + DIAG(F("Wifi reply client=%d, count=:%d"), clientId,count); + { + char buffer[count]; + for(uint8_t i=0;iread(); + sendData(clientId, buffer, count); + } + } + static unsigned long last = 0; if (millis() - last > 60000) { last = millis(); From 696d12fc5ed3246e009dbe6eea0beee2ba56dac5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 26 Sep 2021 11:57:15 +0200 Subject: [PATCH 006/870] test A0 --- config.example.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.h b/config.example.h index eb7e02b8b..f66e52584 100644 --- a/config.example.h +++ b/config.example.h @@ -45,7 +45,7 @@ The configuration file for DCC-EX Command Station #define ESP_MOTOR_SHIELD F("ESP"), \ new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\ - new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN) + new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, A0 , 2.99, 2000, UNUSED_PIN) #define MOTOR_SHIELD_TYPE ESP_MOTOR_SHIELD From a194b8965c1249266f95ce33b4ac7c90b3ffab14 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 27 Sep 2021 20:01:46 +0200 Subject: [PATCH 007/870] Ack read outside interrupt --- DCCWaveform.cpp | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index aaffc2ac2..3e7e98f52 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -53,9 +53,22 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { DCCTimer::begin(DCCWaveform::interruptHandler); } +#define SLOW_ANALOG_READ +#ifdef SLOW_ANALOG_READ +// Flag to hold if we need to run ack checking in loop +static bool ackflag = 0; +#endif + void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { mainTrack.checkPowerOverload(false); progTrack.checkPowerOverload(ackManagerActive); +#ifdef SLOW_ANALOG_READ + if (ackflag) { + progTrack.checkAck(); + // reset flag AFTER check is done + ackflag = 0; + } +#endif } void IRAM_ATTR DCCWaveform::interruptHandler() { @@ -70,9 +83,17 @@ void IRAM_ATTR DCCWaveform::interruptHandler() { mainTrack.state=stateTransform[mainTrack.state]; progTrack.state=stateTransform[progTrack.state]; // WAVE_PENDING means we dont yet know what the next bit is - if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); - if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); - else if (progTrack.ackPending) progTrack.checkAck(); + if (mainTrack.state==WAVE_PENDING) + mainTrack.interrupt2(); + if (progTrack.state==WAVE_PENDING) + progTrack.interrupt2(); +#ifdef SLOW_ANALOG_READ + else if (progTrack.ackPending && ackflag == 0) // We need AND we are not already checking + ackflag = 1; +#else + else if (progTrack.ackPending) + progTrack.checkAck(); +#endif } @@ -308,7 +329,7 @@ void ICACHE_RAM_ATTR DCCWaveform::checkAck() { ackPending = false; return; } - + int current=motorDriver->getCurrentRaw(); numAckSamples++; if (current > ackMaxCurrent) ackMaxCurrent=current; From afd4626988a1d46cfd78618ed2c9fc7e5af24e76 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 28 Sep 2021 17:20:44 +0200 Subject: [PATCH 008/870] send diag --- WifiESP.cpp | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index fd0a4b273..93d1bc37a 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -54,13 +54,27 @@ static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { bool sendData(uint8_t clientId, char* data, int count) { AsyncClient *client = clients[clientId]; + size_t willsend = 0; // reply to client - if (client->space() >= count && client->canSend()) { - client->add(data, count); - client->send(); + if (client->canSend()) { + while (count > 0) { + willsend = client->add(data, count); // add checks for space() + if (willsend < count) { + DIAG(F("Willsend %d of count %d"), willsend, count); + } + if (client->send()) { + count = count - willsend; + data = data + willsend; + } else { + DIAG(F("Could not send promised %d"), count); + return false; + } + } + // Did send all bytes we wanted return true; } + DIAG(F("Aborting: Busy or space=0")); return false; } @@ -87,6 +101,25 @@ static void handleNewClient(void* arg, AsyncClient* client) { } +/* Things one _might_ want to do: + Disable soft watchdog: ESP.wdtDisable() + Enable soft watchdog: ESP.wdtEnable(X) ignores the value of X and enables it for fixed + time at least in version 3.0.2 of the esp8266 package. + +Internet says: + +I manage to complety disable the hardware watchdog on ESP8266 in order to run the benchmark CoreMark. + +void hw_wdt_disable(){ + *((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF +} + +void hw_wdt_enable(){ + *((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON +} + +*/ + bool WifiESP::setup(const char *wifiESSID, const char *wifiPassword, const char *hostname, @@ -127,11 +160,13 @@ void WifiESP::loop() { int clientId=outboundRing->read(); if (clientId>=0) { int count=outboundRing->count(); - DIAG(F("Wifi reply client=%d, count=:%d"), clientId,count); + DIAG(F("Wifi reply client=%d, count=%d"), clientId,count); { - char buffer[count]; + char buffer[count+1]; for(uint8_t i=0;iread(); + buffer[count]=0; + DIAG(F("SEND:%s COUNT:%d"),buffer,count); sendData(clientId, buffer, count); } } From 1c7a5320d86331e1f3f654da0eb29f7440e51df1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 28 Sep 2021 17:31:12 +0200 Subject: [PATCH 009/870] more send diag --- WifiESP.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index 93d1bc37a..a461e43ec 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -163,8 +163,15 @@ void WifiESP::loop() { DIAG(F("Wifi reply client=%d, count=%d"), clientId,count); { char buffer[count+1]; - for(uint8_t i=0;iread(); + for(uint8_t i=0;iread(); + if (c >= 0) + buffer[i] = (char)c; + else { + DIAG(F("Ringread fail at %d"),i); + break; + } + } buffer[count]=0; DIAG(F("SEND:%s COUNT:%d"),buffer,count); sendData(clientId, buffer, count); From 19b4893b5f64f40224939c6849b389dc1a5db5cb Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 28 Sep 2021 21:08:41 +0200 Subject: [PATCH 010/870] counter should be int, not uint8_t --- WifiESP.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index a461e43ec..b46a13d1f 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -37,7 +37,7 @@ static void handleError(void* arg, AsyncClient* client, int8_t error) { } static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { - DIAG(F("data received from client %s"), client->remoteIP().toString().c_str()); + //DIAG(F("data received from client %s"), client->remoteIP().toString().c_str()); uint8_t clientId; for (clientId=0; clientIdread(); if (clientId>=0) { int count=outboundRing->count(); - DIAG(F("Wifi reply client=%d, count=%d"), clientId,count); + //DIAG(F("Wifi reply client=%d, count=%d"), clientId,count); { char buffer[count+1]; - for(uint8_t i=0;iread(); if (c >= 0) buffer[i] = (char)c; @@ -173,8 +173,11 @@ void WifiESP::loop() { } } buffer[count]=0; - DIAG(F("SEND:%s COUNT:%d"),buffer,count); - sendData(clientId, buffer, count); + //DIAG(F("SEND:%s COUNT:%d"),buffer,count); + while (! sendData(clientId, buffer, count)) { + DIAG(F("senData fail")); + yield(); + } } } From 426b27f0dd01f800dc0cb31cf71782bbb7cb85f9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 30 Sep 2021 22:55:14 +0200 Subject: [PATCH 011/870] Reworked use of ringbuffer --- RingStream.cpp | 15 ++++++++++++--- RingStream.h | 7 +++++-- WifiESP.cpp | 49 +++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index 2c28d1bb7..baa4cc1a3 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -45,10 +45,11 @@ size_t RingStream::write(uint8_t b) { return 1; } -int RingStream::read() { - if ((_pos_read==_pos_write) && !_overflow) return -1; // empty +int RingStream::read(byte advance) { + if ((_pos_read==_pos_write) && !_overflow) return -1; // empty + if (_pos_read == _mark) return -1; byte b=_buffer[_pos_read]; - _pos_read++; + _pos_read += advance; if (_pos_read==_len) _pos_read=0; _overflow=false; return b; @@ -68,6 +69,7 @@ int RingStream::freeSpace() { // mark start of message with client id (0...9) void RingStream::mark(uint8_t b) { + //DIAG(F("Mark1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); _mark=_pos_write; write(b); // client id write((uint8_t)0); // count MSB placemarker @@ -81,7 +83,12 @@ uint8_t RingStream::peekTargetMark() { return _buffer[_mark]; } +void RingStream::info() { + DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); +} + bool RingStream::commit() { + //DIAG(F("Commit1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); if (_overflow) { DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); // just throw it away @@ -101,5 +108,7 @@ bool RingStream::commit() { _mark++; if (_mark==_len) _mark=0; _buffer[_mark]=lowByte(_count); + _mark=_len+1; + //DIAG(F("Commit2 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); return true; // commit worked } diff --git a/RingStream.h b/RingStream.h index 790c66e59..974a2352c 100644 --- a/RingStream.h +++ b/RingStream.h @@ -28,14 +28,17 @@ class RingStream : public Print { virtual size_t write(uint8_t b); using Print::write; - int read(); + inline int read() { return read(1); }; + inline int peek() { return read(0); }; int count(); int freeSpace(); void mark(uint8_t b); bool commit(); uint8_t peekTargetMark(); - + void info(); + private: + int read(byte advance); int _len; int _pos_write; int _pos_read; diff --git a/WifiESP.cpp b/WifiESP.cpp index b46a13d1f..8948e3d40 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -52,18 +52,22 @@ static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { } } -bool sendData(uint8_t clientId, char* data, int count) { - AsyncClient *client = clients[clientId]; +//static AsyncClient *debugclient = NULL; + +bool sendData(AsyncClient *client, char* data, size_t count) { size_t willsend = 0; // reply to client if (client->canSend()) { while (count > 0) { - willsend = client->add(data, count); // add checks for space() + if (client->connected()) + willsend = client->add(data, count); // add checks for space() + else + willsend = 0; if (willsend < count) { DIAG(F("Willsend %d of count %d"), willsend, count); } - if (client->send()) { + if (client->connected() && client->send()) { count = count - willsend; data = data + willsend; } else { @@ -78,12 +82,23 @@ bool sendData(uint8_t clientId, char* data, int count) { return false; } +static void deleteClient(AsyncClient* client) { + uint8_t clientId; + for (clientId=0; clientIdremoteIP().toString().c_str()); + deleteClient(client); } static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) { DIAG(F("client ACK timeout ip: %s"), client->remoteIP().toString().c_str()); + deleteClient(client); } @@ -154,11 +169,27 @@ bool WifiESP::setup(const char *wifiESSID, } void WifiESP::loop() { - + AsyncClient *client = NULL; // Do something with outboundRing // call sendData - int clientId=outboundRing->read(); - if (clientId>=0) { + int clientId=outboundRing->peek(); + if (clientId >= 0) { + if (clientId > clients.size()) { + // something is wrong with the ringbuffer position + outboundRing->info(); + client = NULL; + } else { + client = clients[clientId]; + } +// if (client != debugclient) { +// DIAG(F("new client pointer = %x from id %d"), client, clientId); +// debugclient = client; +// } + } else { + client = NULL; + } + if (clientId>=0 && client && client->connected() && client->canSend()) { + outboundRing->read(); int count=outboundRing->count(); //DIAG(F("Wifi reply client=%d, count=%d"), clientId,count); { @@ -174,9 +205,11 @@ void WifiESP::loop() { } buffer[count]=0; //DIAG(F("SEND:%s COUNT:%d"),buffer,count); - while (! sendData(clientId, buffer, count)) { + uint8_t tries = 3; + while (! sendData(client, buffer, count)) { DIAG(F("senData fail")); yield(); + if (tries == 0) break; } } } From cf0c818138210fb6f3c9fe16512b340e24f864f0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 1 Oct 2021 09:09:30 +0200 Subject: [PATCH 012/870] Cleanup ESP specific details --- CommandStation-EX.ino | 17 +++++++++++++---- DCCEX.h | 2 ++ DCCEXParser.cpp | 5 ++++- DCCTimer.cpp | 23 +++++++++++------------ DCCWaveform.cpp | 10 ++++++---- MotorDriver.cpp | 8 ++++---- MotorDriver.h | 17 ++++++++++------- WifiESP.cpp | 35 +++++++++++++++++++---------------- WifiESP.h | 2 +- defines.h | 12 ++++++++++-- freeMemory.cpp | 5 ++--- 11 files changed, 82 insertions(+), 54 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 2b89708c6..71c1d6730 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -59,7 +59,9 @@ void setup() // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); +#ifdef ESP_DEBUG Serial.setDebugOutput(true); +#endif DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); @@ -73,9 +75,12 @@ void setup() // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi // Start Ethernet if it exists #if WIFI_ON +#ifndef ESP_FAMILY WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL); +#else + WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL); +#endif #endif // WIFI_ON - WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, 2560, 1); #if ETHERNET_ON EthernetInterface::setup(); #endif // ETHERNET_ON @@ -117,9 +122,12 @@ void loop() // Responsibility 3: Optionally handle any incoming WiFi traffic #if WIFI_ON +#ifndef ESP_FAMILY WifiInterface::loop(); -#endif +#else WifiESP::loop(); +#endif +#endif //WIFI_ON #if ETHERNET_ON EthernetInterface::loop(); #endif @@ -136,12 +144,13 @@ void loop() // Report any decrease in memory (will automatically trigger on first call) static int ramLowWatermark = __INT_MAX__; // replaced on first loop -/* +#ifdef ESP_FAMILY + updateMinimumFreeMemory(128); +#endif int freeNow = minimumFreeMemory(); if (freeNow < ramLowWatermark) { ramLowWatermark = freeNow; LCD(2,F("Free RAM=%5db"), ramLowWatermark); } - */ } diff --git a/DCCEX.h b/DCCEX.h index f6668a6be..1c2c52279 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -31,7 +31,9 @@ #include "DCCEXParser.h" #include "version.h" #include "WifiInterface.h" +#ifdef ESP_FAMILY #include "WifiESP.h" +#endif #if ETHERNET_ON == true #include "EthernetInterface.h" #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8591844cf..2bbcc5b02 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -17,6 +17,7 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#include "defines.h" #include "StringFormatter.h" #include "DCCEXParser.h" #include "DCC.h" @@ -30,7 +31,9 @@ #include "EEStore.h" #include "DIAG.h" -//#include +#ifndef ESP_FAMILY +#include +#endif // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command diff --git a/DCCTimer.cpp b/DCCTimer.cpp index 5228501fa..063a6ae17 100644 --- a/DCCTimer.cpp +++ b/DCCTimer.cpp @@ -147,27 +147,26 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) { #endif #elif defined(ARDUINO_ARCH_ESP8266) -// ESP8266 !!!!!!!!!!!!!!!!!!!!! + void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; timer1_disable(); -// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); -// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler); + + // There seem to be differnt ways to attach interrupt handler + // ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); + // ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler); + // Let us choose the one from the API timer1_attachInterrupt(interruptHandler); + + // not exactly sure of order: timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); timer1_write(CLOCK_CYCLES); -/* - noInterrupts(); - timer1_attachInterrupt(interruptHandler); - timer1_write(CLOCK_CYCLES); - timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); - interrupts(); -*/ } -IRAM_ATTR bool DCCTimer::isPWMPin(byte pin) { +// We do not support to use PWM to make the Waveform on ESP +bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { return false; } -void ICACHE_RAM_ATTR DCCTimer::setPWM(byte pin, bool high) { +void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { } diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 3e7e98f52..df599de22 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -20,6 +20,7 @@ #pragma GCC optimize ("-O3") #include +#include "defines.h" #include "DCCWaveform.h" #include "DCCTimer.h" #include "DIAG.h" @@ -53,7 +54,6 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { DCCTimer::begin(DCCWaveform::interruptHandler); } -#define SLOW_ANALOG_READ #ifdef SLOW_ANALOG_READ // Flag to hold if we need to run ack checking in loop static bool ackflag = 0; @@ -213,7 +213,7 @@ const bool DCCWaveform::signalTransform[]={ /* WAVE_LOW_0 -> */ LOW, /* WAVE_PENDING (should not happen) -> */ LOW}; -void ICACHE_RAM_ATTR DCCWaveform::interrupt2() { +void IRAM_ATTR DCCWaveform::interrupt2() { // calculate the next bit to be sent: // set state WAVE_MID_1 for a 1=bit // or WAVE_HIGH_0 for a 0 bit. @@ -223,7 +223,9 @@ void ICACHE_RAM_ATTR DCCWaveform::interrupt2() { remainingPreambles--; // Update free memory diagnostic as we don't have anything else to do this time. // Allow for checkAck and its called functions using 22 bytes more. -// might break ESP8266 updateMinimumFreeMemory(22); +#ifndef ESP_FAMILY + updateMinimumFreeMemory(22); +#endif return; } @@ -322,7 +324,7 @@ byte DCCWaveform::getAck() { return(0); // pending set off but not detected means no ACK. } -void ICACHE_RAM_ATTR DCCWaveform::checkAck() { +void IRAM_ATTR DCCWaveform::checkAck() { // This function operates in interrupt() time so must be fast and can't DIAG if (sentResetsSincePacket > 6) { //ACK timeout ackCheckDuration=millis()-ackCheckStart; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c6c37cf53..8bcb03c94 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -104,8 +104,8 @@ void MotorDriver::setBrake(bool on) { if (on ^ invertBrake) setHIGH(fastBrakePin); else setLOW(fastBrakePin); } -/* -IRAM_ATTR void MotorDriver::setSignal( bool high) { + +void IRAM_ATTR MotorDriver::setSignal( bool high) { if (usePWM) { DCCTimer::setPWM(signalPin,high); } @@ -120,7 +120,7 @@ IRAM_ATTR void MotorDriver::setSignal( bool high) { } } } -*/ + #if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36) volatile unsigned int overflow_count=0; #endif @@ -179,5 +179,5 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res result.inout = portOutputRegister(port); result.maskHIGH = digitalPinToBitMask(pin); result.maskLOW = ~result.maskHIGH; - DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); + // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } diff --git a/MotorDriver.h b/MotorDriver.h index 06c19b11e..a020ef344 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -52,17 +52,20 @@ class MotorDriver { MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); virtual void setPower( bool on); - virtual void setSignal( bool high) { - if (high) { + void setSignal( bool high);/* { + if (usePWM) { + DCCTimer::setPWM(signalPin,high); + } + + if (high) { setHIGH(fastSignalPin); if (dualSignal) setLOW(fastSignalPin2); - } - else { + } + else { setLOW(fastSignalPin); if (dualSignal) setHIGH(fastSignalPin2); - } - }; - + } + };*/ virtual void setBrake( bool on); virtual int getCurrentRaw(); virtual unsigned int raw2mA( int raw); diff --git a/WifiESP.cpp b/WifiESP.cpp index 8948e3d40..d94967978 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -17,6 +17,8 @@ along with CommandStation. If not, see . */ +#include "defines.h" +#ifdef ESP_FAMILY #include #include #include @@ -92,7 +94,7 @@ static void deleteClient(AsyncClient* client) { } } static void handleDisconnect(void* arg, AsyncClient* client) { - DIAG(F("client %s disconnected"), client->remoteIP().toString().c_str()); + DIAG(F("Client disconnected")); deleteClient(client); } @@ -103,11 +105,11 @@ static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) { static void handleNewClient(void* arg, AsyncClient* client) { - DIAG(F("New client has been connected to server, ip: %s"), client->remoteIP().toString().c_str()); + DIAG(F("New client %s"), client->remoteIP().toString().c_str()); // add to list clients.push_back(client); - + // register events client->onData(&handleData, NULL); client->onError(&handleError, NULL); @@ -118,7 +120,7 @@ static void handleNewClient(void* arg, AsyncClient* client) { /* Things one _might_ want to do: Disable soft watchdog: ESP.wdtDisable() - Enable soft watchdog: ESP.wdtEnable(X) ignores the value of X and enables it for fixed + Enable soft watchdog: ESP.wdtEnable(X) ignores the value of X and enables it for fixed time at least in version 3.0.2 of the esp8266 package. Internet says: @@ -140,30 +142,29 @@ bool WifiESP::setup(const char *wifiESSID, const char *hostname, int port, const byte channel) { - DIAG(F("START")); - // connects to access point + // We are server and should not sleep wifi_set_sleep_type(NONE_SLEEP_T); + // connects to access point WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); - DIAG(F("BEGIN")); WiFi.begin(wifiESSID, wifiPassword); - DIAG(F("STATUS")); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(500); } - - DIAG(F("SERVER")); + if (WiFi.status() == WL_CONNECTED) + DIAG(F("Wifi IP %s"),WiFi.localIP().toString().c_str()); + else { + DIAG(F("Wifi fail")); + // no idea to go on + return false; + } server = new AsyncServer(port); // start listening on tcp port - DIAG(F("CLIENT")); server->onClient(&handleNewClient, server); - DIAG(F("SBEGIN")); - server->begin(); - - DIAG(F("ENDSETUP")); + DIAG(F("Server up port %d"),port); return true; } @@ -213,11 +214,13 @@ void WifiESP::loop() { } } } - +#ifdef ESP_DEBUG static unsigned long last = 0; if (millis() - last > 60000) { last = millis(); DIAG(F("+")); } +#endif ESP.wdtFeed(); } +#endif //ESP_FAMILY diff --git a/WifiESP.h b/WifiESP.h index 971ccc746..47de4ed5b 100644 --- a/WifiESP.h +++ b/WifiESP.h @@ -1,6 +1,6 @@ /* * © 2021, Harald Barth. - * + * * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify diff --git a/defines.h b/defines.h index b018c54d9..afb18e806 100644 --- a/defines.h +++ b/defines.h @@ -1,5 +1,5 @@ /* - © 2020, Harald Barth. + © 2020,2021 Harald Barth. This file is part of CommandStation-EX @@ -18,12 +18,20 @@ */ +//////////////////////////////////////////////////////////////////////////////// +// +#if defined (ARDUINO_ARCH_ESP8266) +#define ESP_FAMILY +//#define ESP_DEBUG +#define SLOW_ANALOG_READ +#endif + //////////////////////////////////////////////////////////////////////////////// // // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined (ESP_FAMILY)) #define WIFI_ON true #ifndef WIFI_CHANNEL #define WIFI_CHANNEL 1 diff --git a/freeMemory.cpp b/freeMemory.cpp index af7bd16b9..f2dc29d9d 100644 --- a/freeMemory.cpp +++ b/freeMemory.cpp @@ -28,7 +28,7 @@ extern "C" char* sbrk(int); extern char *__brkval; extern char *__malloc_heap_start; #elif defined(ARDUINO_ARCH_ESP8266) -// fine as well +// supported but nothing needed here #else #error Unsupported board type #endif @@ -116,9 +116,8 @@ int minimumFreeMemory() { // So even if all of the heap is freed, the reported minimum free // memory will not increase. // -void ICACHE_RAM_ATTR updateMinimumFreeMemory(unsigned char extraBytes) { +void updateMinimumFreeMemory(unsigned char extraBytes) { int spare = freeMemory()-extraBytes; if (spare < 0) spare = 0; if (spare < minimum_free_memory) minimum_free_memory = spare; } - From 0bb6b577fa32795d8479ee2d53abd06d3b415d22 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 1 Oct 2021 11:32:09 +0200 Subject: [PATCH 013/870] Wifi STA or AP mode --- WifiESP.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index d94967978..f4e9773e0 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "WifiESP.h" #include "DIAG.h" @@ -137,25 +138,62 @@ void hw_wdt_enable(){ */ -bool WifiESP::setup(const char *wifiESSID, - const char *wifiPassword, +bool WifiESP::setup(const char *SSid, + const char *password, const char *hostname, int port, const byte channel) { + bool havePassword = true; + bool haveSSID = true; + bool wifiUp = false; + // We are server and should not sleep wifi_set_sleep_type(NONE_SLEEP_T); // connects to access point - WiFi.mode(WIFI_STA); - WiFi.setAutoReconnect(true); - WiFi.begin(wifiESSID, wifiPassword); - while (WiFi.status() != WL_CONNECTED) { - Serial.print('.'); - delay(500); + + const char *yourNetwork = "Your network "; + if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0) + haveSSID = false; + if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0) + havePassword = false; + + if (haveSSID && havePassword) { + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect(true); + WiFi.begin(SSid, password); + while (WiFi.status() != WL_CONNECTED) { + Serial.print('.'); + delay(500); + } + if (WiFi.status() == WL_CONNECTED) { + DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); + wifiUp = true; + } } - if (WiFi.status() == WL_CONNECTED) - DIAG(F("Wifi IP %s"),WiFi.localIP().toString().c_str()); - else { - DIAG(F("Wifi fail")); + if (!haveSSID) { + // prepare all strings + String strSSID("DCC_"); + String strPass("PASS_"); + String strMac = WiFi.macAddress(); + strMac.remove(0,9); + strMac.replace(":",""); + strMac.replace(":",""); + strSSID.concat(strMac); + strPass.concat(strMac); + + WiFi.mode(WIFI_AP); + if (WiFi.softAP(strSSID.c_str(), + havePassword ? password : strPass.c_str(), + channel, false, 8)) { + DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); + DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); + wifiUp = true; + } + } + + + if (!wifiUp) { + DIAG(F("Wifi all fail")); // no idea to go on return false; } From 50bb1c950b00b29c29662f1cb5ce06395539fbe5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 3 Oct 2021 19:39:43 +0200 Subject: [PATCH 014/870] less warnings --- WifiESP.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WifiESP.cpp b/WifiESP.cpp index f4e9773e0..2c6f1697a 100644 --- a/WifiESP.cpp +++ b/WifiESP.cpp @@ -36,10 +36,12 @@ static AsyncServer *server; static RingStream *outboundRing = new RingStream(2048); static void handleError(void* arg, AsyncClient* client, int8_t error) { + (void)arg; DIAG(F("connection error %s from client %s"), client->errorToString(error), client->remoteIP().toString().c_str()); } static void handleData(void* arg, AsyncClient* client, void *data, size_t len) { + (void)arg; //DIAG(F("data received from client %s"), client->remoteIP().toString().c_str()); uint8_t clientId; for (clientId=0; clientIdremoteIP().toString().c_str()); deleteClient(client); } static void handleNewClient(void* arg, AsyncClient* client) { + (void)arg; DIAG(F("New client %s"), client->remoteIP().toString().c_str()); // add to list @@ -213,7 +219,7 @@ void WifiESP::loop() { // call sendData int clientId=outboundRing->peek(); if (clientId >= 0) { - if (clientId > clients.size()) { + if ((unsigned int)clientId > clients.size()) { // something is wrong with the ringbuffer position outboundRing->info(); client = NULL; From 43191e225e2c2638f39e00c81ae21f7263e28568 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 4 Oct 2021 23:03:36 +0200 Subject: [PATCH 015/870] first stab at ESP32 --- DCCTimer.cpp | 19 +++++++++++++++++++ DCCWaveform.cpp | 7 ++++++- defines.h | 11 +++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/DCCTimer.cpp b/DCCTimer.cpp index 063a6ae17..6b204fe57 100644 --- a/DCCTimer.cpp +++ b/DCCTimer.cpp @@ -169,7 +169,26 @@ bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { } +#elif defined(ARDUINO_ARCH_ESP32) +// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx +portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler = callback; + hw_timer_t *timer = NULL; + timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2 + timerAttachInterrupt(timer, interruptHandler, true); + timerAlarmWrite(timer, CLOCK_CYCLES / 2, true); // divide by prescaler + timerAlarmEnable(timer); +} + +// We do not support to use PWM to make the Waveform on ESP +bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { + return false; +} +void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { +} #else // Arduino nano, uno, mega etc diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index df599de22..dbabc19ce 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -66,7 +66,9 @@ void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { if (ackflag) { progTrack.checkAck(); // reset flag AFTER check is done + portENTER_CRITICAL(&timerMux); ackflag = 0; + portEXIT_CRITICAL(&timerMux); } #endif } @@ -88,8 +90,11 @@ void IRAM_ATTR DCCWaveform::interruptHandler() { if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); #ifdef SLOW_ANALOG_READ - else if (progTrack.ackPending && ackflag == 0) // We need AND we are not already checking + else if (progTrack.ackPending && ackflag == 0) { // We need AND we are not already checking + portENTER_CRITICAL(&timerMux); ackflag = 1; + portEXIT_CRITICAL(&timerMux); + } #else else if (progTrack.ackPending) progTrack.checkAck(); diff --git a/defines.h b/defines.h index afb18e806..a79ddca98 100644 --- a/defines.h +++ b/defines.h @@ -26,6 +26,17 @@ #define SLOW_ANALOG_READ #endif +//////////////////////////////////////////////////////////////////////////////// +// +#if defined (ARDUINO_ARCH_ESP32) +#define ESP_FAMILY +#define SLOW_ANALOG_READ +#else +#define portENTER_CRITICAL(A) do {} while (0) +#define portEXIT_CRITICAL(A) do {} while (0) +#endif + + //////////////////////////////////////////////////////////////////////////////// // // WIFI_ON: All prereqs for running with WIFI are met From 0a10dbea0b433096548591d2760c4cc739d80b35 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 4 Oct 2021 23:12:47 +0200 Subject: [PATCH 016/870] not forget volatile --- DCCWaveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index dbabc19ce..33f739212 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -56,7 +56,7 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { #ifdef SLOW_ANALOG_READ // Flag to hold if we need to run ack checking in loop -static bool ackflag = 0; +volatile bool ackflag = 0; #endif void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { From 75dffd9dfab08b5ae6fd74b36152b113c86e37ef Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 5 Oct 2021 10:39:08 +0200 Subject: [PATCH 017/870] first ESP32 compile --- DCC.h | 2 ++ DCCEX.h | 7 ++++-- DCCEXParser.cpp | 8 +++++-- DCCTimer.h | 1 + DCCWaveform.cpp | 9 +++++--- MotorDriver.h | 3 ++- WifiESP32.cpp | 39 ++++++++++++++++++++++++++++++++++ WifiESP.h => WifiESP32.h | 8 ++++--- WifiESP.cpp => WifiESP8266.cpp | 4 ++-- WifiESP8266.h | 39 ++++++++++++++++++++++++++++++++++ WifiInterface.cpp | 6 ++++-- WifiInterface.h | 5 ++++- defines.h | 6 +++--- freeMemory.cpp | 10 ++++----- 14 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 WifiESP32.cpp rename WifiESP.h => WifiESP32.h (89%) rename WifiESP.cpp => WifiESP8266.cpp (99%) create mode 100644 WifiESP8266.h diff --git a/DCC.h b/DCC.h index b4736737a..715a38d36 100644 --- a/DCC.h +++ b/DCC.h @@ -193,6 +193,8 @@ class DCC #define ARDUINO_TYPE "TEENSY41" #elif defined(ARDUINO_ARCH_ESP8266) #define ARDUINO_TYPE "ESP8266" +#elif defined(ARDUINO_ARCH_ESP32) +#define ARDUINO_TYPE "ESP32" #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif diff --git a/DCCEX.h b/DCCEX.h index 1c2c52279..8ff2f514d 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -30,9 +30,12 @@ #include "DIAG.h" #include "DCCEXParser.h" #include "version.h" +#if defined(ARDUINO_ARCH_ESP8266) +#include "WifiESP8266.h" +#elif defined(ARDUINO_ARCH_ESP32) +#include "WifiESP32.h" +#else #include "WifiInterface.h" -#ifdef ESP_FAMILY -#include "WifiESP.h" #endif #if ETHERNET_ON == true #include "EthernetInterface.h" diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2bbcc5b02..c8d7fdefd 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -789,8 +789,12 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_RESET: { - wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms - delay(50); // wait for the prescaller time to expire +#ifndef ESP_FAMILY + wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms + delay(50); // wait for the prescaler time to expire +#else + /* XXX do right thing to reboot */ +#endif break; // and if we didnt restart } diff --git a/DCCTimer.h b/DCCTimer.h index b5187011b..d421828c9 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -37,4 +37,5 @@ class DCCTimer { private: }; +extern portMUX_TYPE timerMux; #endif diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 33f739212..439e78117 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -255,7 +255,8 @@ void IRAM_ATTR DCCWaveform::interrupt2() { transmitRepeats--; } else if (packetPending) { - // Copy pending packet to transmit packet + portENTER_CRITICAL(&timerMux); + // Copy pending packet to transmit packet // a fixed length memcpy is faster than a variable length loop for these small lengths // for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b]; memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket)); @@ -264,6 +265,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() { transmitRepeats = pendingRepeats; packetPending = false; sentResetsSincePacket=0; + portEXIT_CRITICAL(&timerMux); } else { // Fortunately reset and idle packets are the same length @@ -282,7 +284,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() { void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum while (packetPending); - + portENTER_CRITICAL(&timerMux); byte checksum = 0; for (byte b = 0; b < byteCount; b++) { checksum ^= buffer[b]; @@ -294,6 +296,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea pendingRepeats = repeats; packetPending = true; sentResetsSincePacket=0; + portEXIT_CRITICAL(&timerMux); } // Operations applicable to PROG track ONLY. @@ -330,7 +333,7 @@ byte DCCWaveform::getAck() { } void IRAM_ATTR DCCWaveform::checkAck() { - // This function operates in interrupt() time so must be fast and can't DIAG + // This function operates in interrupt() time (not on ESP) so must be fast and can't DIAG if (sentResetsSincePacket > 6) { //ACK timeout ackCheckDuration=millis()-ackCheckStart; ackPending = false; diff --git a/MotorDriver.h b/MotorDriver.h index a020ef344..9193828aa 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -18,6 +18,7 @@ */ #ifndef MotorDriver_h #define MotorDriver_h +#include "defines.h" #include "FSH.h" // Virtualised Motor shield 1-track hardware Interface @@ -26,7 +27,7 @@ #define UNUSED_PIN 127 // inside int8_t #endif -#if defined(__IMXRT1062__) || defined (ARDUINO_ARCH_ESP8266) +#if defined(__IMXRT1062__) || defined(ESP_FAMILY) typedef uint32_t PORTTYPE; struct FASTPIN { volatile uint32_t *inout; diff --git a/WifiESP32.cpp b/WifiESP32.cpp new file mode 100644 index 000000000..93ec7020f --- /dev/null +++ b/WifiESP32.cpp @@ -0,0 +1,39 @@ +/* + © 2021, Harald Barth. + + This file is part of CommandStation-EX + + 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 . +*/ + +#include "defines.h" +#if defined(ARDUINO_ARCH_ESP32) +#include +#include "WifiESP32.h" +#include "DIAG.h" +#include "RingStream.h" +#include "CommandDistributor.h" + +bool WifiESP::setup(const char *SSid, + const char *password, + const char *hostname, + int port, + const byte channel) { + return false; +} + +void WifiESP::loop() { + +} +#endif //ESP32 diff --git a/WifiESP.h b/WifiESP32.h similarity index 89% rename from WifiESP.h rename to WifiESP32.h index 47de4ed5b..100e3931b 100644 --- a/WifiESP.h +++ b/WifiESP32.h @@ -17,8 +17,9 @@ * along with CommandStation. If not, see . */ -#ifndef WifiESP_h -#define WifiESP_h +#if defined(ARDUINO_ARCH_ESP32) +#ifndef WifiESP32_h +#define WifiESP32_h #include "FSH.h" @@ -34,4 +35,5 @@ class WifiESP static void loop(); private: }; -#endif +#endif //WifiESP8266_h +#endif //ESP8266 diff --git a/WifiESP.cpp b/WifiESP8266.cpp similarity index 99% rename from WifiESP.cpp rename to WifiESP8266.cpp index 2c6f1697a..7b3dec5f1 100644 --- a/WifiESP.cpp +++ b/WifiESP8266.cpp @@ -18,13 +18,13 @@ */ #include "defines.h" -#ifdef ESP_FAMILY +#if defined(ARDUINO_ARCH_ESP8266) #include #include #include #include -#include "WifiESP.h" +#include "WifiESP8266.h" #include "DIAG.h" #include "RingStream.h" #include "CommandDistributor.h" diff --git a/WifiESP8266.h b/WifiESP8266.h new file mode 100644 index 000000000..6fd71ddc1 --- /dev/null +++ b/WifiESP8266.h @@ -0,0 +1,39 @@ +/* + * © 2021, Harald Barth. + * + * This file is part of CommandStation-EX + * + * 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 . + */ + +#if defined(ARDUINO_ARCH_ESP8266) +#ifndef WifiESP8266_h +#define WifiESP8266_h + +#include "FSH.h" + +class WifiESP +{ + +public: + static bool setup(const char *wifiESSID, + const char *wifiPassword, + const char *hostname, + const int port, + const byte channel); + static void loop(); +private: +}; +#endif //WifiESP8266_h +#endif //ESP8266 diff --git a/WifiInterface.cpp b/WifiInterface.cpp index bd7b7f2d0..cb40a79df 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -17,9 +17,10 @@ You should have received a copy of the GNU General Public License along with CommandStation. If not, see . */ +#include "WifiInterface.h" /* config.h included there */ +#ifndef ESP_FAMILY #ifndef ARDUINO_AVR_UNO_WIFI_REV2 // This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture -#include "WifiInterface.h" /* config.h included there */ #include #include "DIAG.h" #include "StringFormatter.h" @@ -370,4 +371,5 @@ void WifiInterface::loop() { } } -#endif +#endif //ARDUINO_AVR_UNO_WIFI_REV2 +#endif //ESP_FAMILY diff --git a/WifiInterface.h b/WifiInterface.h index 19f8a3a00..d674beec3 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -19,6 +19,8 @@ */ #ifndef WifiInterface_h #define WifiInterface_h +#include "defines.h" +#ifndef ESP_FAMILY #include "FSH.h" #include "DCCEXParser.h" #include @@ -50,4 +52,5 @@ class WifiInterface static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true); static bool connected; }; -#endif +#endif //ESP_FAMILY +#endif diff --git a/defines.h b/defines.h index a79ddca98..7ad98fe49 100644 --- a/defines.h +++ b/defines.h @@ -20,7 +20,7 @@ //////////////////////////////////////////////////////////////////////////////// // -#if defined (ARDUINO_ARCH_ESP8266) +#if defined(ARDUINO_ARCH_ESP8266) #define ESP_FAMILY //#define ESP_DEBUG #define SLOW_ANALOG_READ @@ -28,7 +28,7 @@ //////////////////////////////////////////////////////////////////////////////// // -#if defined (ARDUINO_ARCH_ESP32) +#if defined(ARDUINO_ARCH_ESP32) #define ESP_FAMILY #define SLOW_ANALOG_READ #else @@ -42,7 +42,7 @@ // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined (ESP_FAMILY)) +#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ESP_FAMILY)) #define WIFI_ON true #ifndef WIFI_CHANNEL #define WIFI_CHANNEL 1 diff --git a/freeMemory.cpp b/freeMemory.cpp index f2dc29d9d..4758f8382 100644 --- a/freeMemory.cpp +++ b/freeMemory.cpp @@ -1,5 +1,5 @@ /* - * © 2020, Harald Barth + * © 2020,2021 Harald Barth * © 2021, Neil McKechnie * * This file is part of Asbelos DCC-EX @@ -27,7 +27,7 @@ extern "C" char* sbrk(int); #elif defined(__AVR__) extern char *__brkval; extern char *__malloc_heap_start; -#elif defined(ARDUINO_ARCH_ESP8266) +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) // supported but nothing needed here #else #error Unsupported board type @@ -36,7 +36,7 @@ extern char *__malloc_heap_start; static volatile int minimum_free_memory = __INT_MAX__; -#if !defined(__IMXRT1062__) && !defined(ARDUINO_ARCH_ESP8266) +#if !defined(__IMXRT1062__) && !defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32) static inline int freeMemory() { char top; #if defined(__arm__) @@ -57,8 +57,8 @@ int minimumFreeMemory() { return retval; } -#elif defined(ARDUINO_ARCH_ESP8266) -// ESP8266 +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +// ESP8266 and ESP32 static inline int freeMemory() { return ESP.getFreeHeap(); } From d174c05127ec44de655fb32e489466b9300a28e6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 5 Oct 2021 21:53:02 +0200 Subject: [PATCH 018/870] Wifi connect and waveform --- DCCTimer.cpp | 2 +- WifiESP32.cpp | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/DCCTimer.cpp b/DCCTimer.cpp index 6b204fe57..5de7e6a29 100644 --- a/DCCTimer.cpp +++ b/DCCTimer.cpp @@ -179,7 +179,7 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { hw_timer_t *timer = NULL; timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2 timerAttachInterrupt(timer, interruptHandler, true); - timerAlarmWrite(timer, CLOCK_CYCLES / 2, true); // divide by prescaler + timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz) timerAlarmEnable(timer); } diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 93ec7020f..10dbdb261 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -25,15 +25,138 @@ #include "RingStream.h" #include "CommandDistributor.h" +static std::vector clients; // a list to hold all clients +static WiFiServer *server = NULL; +static RingStream *outboundRing = new RingStream(2048); + bool WifiESP::setup(const char *SSid, const char *password, const char *hostname, int port, const byte channel) { + bool havePassword = true; + bool haveSSID = true; + bool wifiUp = false; + + const char *yourNetwork = "Your network "; + if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0) + haveSSID = false; + if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0) + havePassword = false; + + if (haveSSID && havePassword) { + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect(true); + WiFi.begin(SSid, password); + while (WiFi.status() != WL_CONNECTED) { + Serial.print('.'); + delay(500); + } + if (WiFi.status() == WL_CONNECTED) { + DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); + wifiUp = true; + } + } + if (!haveSSID) { + // prepare all strings + String strSSID("DCC_"); + String strPass("PASS_"); + String strMac = WiFi.macAddress(); + strMac.remove(0,9); + strMac.replace(":",""); + strMac.replace(":",""); + strSSID.concat(strMac); + strPass.concat(strMac); + + WiFi.mode(WIFI_AP); + if (WiFi.softAP(strSSID.c_str(), + havePassword ? password : strPass.c_str(), + channel, false, 8)) { + DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); + DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); + wifiUp = true; + } + } + + + if (!wifiUp) { + DIAG(F("Wifi all fail")); + // no idea to go on + return false; + } + server = new WiFiServer(port); // start listening on tcp port + server->begin(); + DIAG(F("Server up port %d"),port); + + return true; return false; } void WifiESP::loop() { + int clientId; //tmp loop var + + if (WiFi.status() == WL_CONNECTED /* || what for AP? */) { + if (server->hasClient()) { + // loop over all clients and remove inactive + for (clientId=0; clientIdavailable()) { + clients.push_back(client); + DIAG(F("New client %s"), client.remoteIP().toString().c_str()); + } + } + // loop over all connected clients + for (clientId=0; clientId 0) { + // read data from client + byte cmd[len+1]; + for(int i=0; imark(clientId); + CommandDistributor::parse(clientId,cmd,outboundRing); + outboundRing->commit(); + } + } + } // all clients + // something to write out? + clientId=outboundRing->peek(); + if (clientId >= 0) { + if ((unsigned int)clientId > clients.size()) { + // something is wrong with the ringbuffer position + outboundRing->info(); + } else { + // we have data to send in outboundRing + if(clients[clientId].connected()) { + outboundRing->read(); // read over peek() + int count=outboundRing->count(); + { + char buffer[count+1]; + for(int i=0;iread(); + if (c >= 0) + buffer[i] = (char)c; + else { + DIAG(F("Ringread fail at %d"),i); + break; + } + } + buffer[count]=0; + clients[clientId].write(buffer,count); + } + } + } + } + } //connected } #endif //ESP32 From 26bd3ac342da5795bfaf4a41ca8cda1b41c99380 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 5 Oct 2021 21:55:13 +0200 Subject: [PATCH 019/870] Example ESP motor shields --- config.example.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config.example.h b/config.example.h index f66e52584..76b661071 100644 --- a/config.example.h +++ b/config.example.h @@ -43,11 +43,15 @@ The configuration file for DCC-EX Command Station // //#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD -#define ESP_MOTOR_SHIELD F("ESP"), \ +#define ESP8266_MOTOR_SHIELD F("ESP8266"), \ new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\ new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, A0 , 2.99, 2000, UNUSED_PIN) -#define MOTOR_SHIELD_TYPE ESP_MOTOR_SHIELD +#define ESP32_MOTOR_SHIELD F("ESP32"), \ + new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 36, 2.99, 2000, UNUSED_PIN),\ + new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 37, 2.99, 2000, UNUSED_PIN) + +#define MOTOR_SHIELD_TYPE ESP8266_MOTOR_SHIELD ///////////////////////////////////////////////////////////////////////////////////// // From faeb3194dbc429523bafe5db5eb649df783e2c3f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 22 Oct 2021 08:21:44 +0200 Subject: [PATCH 020/870] ESP32 motorshield as default --- config.example.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.h b/config.example.h index 76b661071..5b9a22823 100644 --- a/config.example.h +++ b/config.example.h @@ -51,7 +51,7 @@ The configuration file for DCC-EX Command Station new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 36, 2.99, 2000, UNUSED_PIN),\ new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 37, 2.99, 2000, UNUSED_PIN) -#define MOTOR_SHIELD_TYPE ESP8266_MOTOR_SHIELD +#define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD ///////////////////////////////////////////////////////////////////////////////////// // From 8a0ddb0d748cf37feccf30076ff03699cf9cf8b0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 22 Oct 2021 08:35:29 +0200 Subject: [PATCH 021/870] ESP32 I/O info --- config.example.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config.example.h b/config.example.h index 5b9a22823..5c6da0622 100644 --- a/config.example.h +++ b/config.example.h @@ -47,9 +47,15 @@ The configuration file for DCC-EX Command Station new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\ new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, A0 , 2.99, 2000, UNUSED_PIN) +// ADC1 CH4 = GPIO32 +// ADC1 CH5 = GPIO33 +// Adjust pin usage according to info in +// https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/ +// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ +// Adjust conversion factor according to your voltage divider #define ESP32_MOTOR_SHIELD F("ESP32"), \ - new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 36, 2.99, 2000, UNUSED_PIN),\ - new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 37, 2.99, 2000, UNUSED_PIN) + new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.99, 2000, UNUSED_PIN),\ + new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.99, 2000, UNUSED_PIN) #define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD From b6cfc39d23c8a84602102de5d0108d8b58d0d155 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 24 Oct 2021 12:09:54 +0200 Subject: [PATCH 022/870] ESP32 watchdog workaround (with diag code) --- WifiESP32.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 10dbdb261..b11f9061f 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -24,6 +24,45 @@ #include "DIAG.h" #include "RingStream.h" #include "CommandDistributor.h" +/* +#include "soc/rtc_wdt.h" +#include "esp_task_wdt.h" +*/ + +#include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" +void feedTheDog0(){ + // feed dog 0 + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable + TIMERG0.wdt_feed=1; // feed dog + TIMERG0.wdt_wprotect=0; // write protect + // feed dog 1 + //TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable + //TIMERG1.wdt_feed=1; // feed dog + //TIMERG1.wdt_wprotect=0; // write protect +} + +/* +void enableCoreWDT(byte core){ + TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core); + if(idle == NULL){ + DIAG(F("Get idle rask on core %d failed"),core); + } else { + if(esp_task_wdt_add(idle) != ESP_OK){ + DIAG(F("Failed to add Core %d IDLE task to WDT"),core); + } else { + DIAG(F("Added Core %d IDLE task to WDT"),core); + } + } +} + +void disableCoreWDT(byte core){ + TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core); + if(idle == NULL || esp_task_wdt_delete(idle) != ESP_OK){ + DIAG(F("Failed to remove Core %d IDLE task from WDT"),core); + } +} +*/ static std::vector clients; // a list to hold all clients static WiFiServer *server = NULL; @@ -38,6 +77,10 @@ bool WifiESP::setup(const char *SSid, bool haveSSID = true; bool wifiUp = false; + // tests + // enableCoreWDT(1); + // disableCoreWDT(0); + const char *yourNetwork = "Your network "; if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0) haveSSID = false; @@ -158,5 +201,14 @@ void WifiESP::loop() { } } } //connected + yield(); + // when loop() is running on core0 we must + // feed the core0 wdt ourselves as yield() + // is not necessarily yielding to a low + // prio task. On core1 this is not a problem + // as there the wdt is disabled by the + // arduio IDE startup routines. + if (xPortGetCoreID() == 0) + feedTheDog0(); } #endif //ESP32 From 05eb0d763a93b25ce39f3fa6876d71e7bccdd59c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 24 Oct 2021 12:59:28 +0200 Subject: [PATCH 023/870] explain ESP32 watchdog --- esp32wdt.txt | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 esp32wdt.txt diff --git a/esp32wdt.txt b/esp32wdt.txt new file mode 100644 index 000000000..dbfb4ef58 --- /dev/null +++ b/esp32wdt.txt @@ -0,0 +1,67 @@ +The ESP-IDF has interrupt watchdogs and task watchdogs. Normally on +each core there is a very low prio idle task (IDLE0, ILDE1) that feeds +the watchdog (an internal timer) and if that timer expires a +reboot/reset happens. This thought to enure that even the lowest prio +tasks get run ever. + +Now enter the Arduino IDE generated task loop(). When there are two cores, +loop() is run on core1. If loop runs continiously, IDLE1 is never run and +triggers the watchdog. It is said that this can be previented by one +of the following: + +1. Call delay(X) with big enough X +2. Call yield() + +While the delay() method works, big enough X to run idle seem to be in +the ms range and there are definitely applications that can not accept +a several ms long pause in loop(). + +The yield() method does not work because it only seems not to yield to +a low prio task like IDLE1 in all circumstances. + +Then the makers of the Arduino IDE did get the brilliant idea to +disable that IDLE1 calls the watchdog. Then loop() can spin on core1 +and other tasks (like wifi or interrupts or whatever) can run on core0 +and are watched by the IDLE0 watchdog. All swell and well. Almost. + +Enter: SINGLE CORE ESP32 + +As the IDLE0 watchdog is not disabled it will fire when loop() runs on +core0. The next idea is to feed the watchdog from loop() just +alongside the yield. There is a function called esp_task_wdt_feed(), +so can that be used to feed the watchdog? Yes and no. While it +will feed the watchdog, there is as well as check in the ESP-IDF +that the watchdog is fed from ALL tasks that should feed it. So +if the setup is that IDLE0 should feed the watchdog, we can not +get away by calling esp_task_wdt_feed() from loop(). BUMMER! + +But there seems to be a way around this. The watchdog is implemented +by low level timers/counters and these are accessible. So we can feed +the dog behind the back of the ESP-IDF: + +#include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" +void feedTheDog(){ + // feed dog 0 + TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable + TIMERG0.wdt_feed=1; // feed dog + TIMERG0.wdt_wprotect=0; // write protect + // feed dog 1 + TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable + TIMERG1.wdt_feed=1; // feed dog + TIMERG1.wdt_wprotect=0; // write protect +} + +As I do not have a single core ESP32 I tested this by enabling the +IDLE1 watchdog (which normally is disabled) and checking that I +do get watchdog resets. Then I call feedTheDog() from loop() +and the resets disappear. So I guess the feeding operation is +successful. For a single core ESP32 of course only dog0 has +to be fed. + +Feed dog directly behind back of the ESP-IDF routines: +https://forum.arduino.cc/t/esp32-a-better-way-than-vtaskdelay-to-get-around-watchdog-crash/596889/13 +Disable/Endable WDT code: +https://github.com/espressif/arduino-esp32/commit/b8f8502f +Get/set taskid on cores: +https://techtutorialsx.com/2017/05/09/esp32-get-task-execution-core/ From 7d7b337f82d149eebbe92fa2e5cb67c9305c20c5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 24 Oct 2021 19:38:07 +0200 Subject: [PATCH 024/870] on ESP32 currently WIFI should be on --- config.example.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.example.h b/config.example.h index 5c6da0622..73380519a 100644 --- a/config.example.h +++ b/config.example.h @@ -70,7 +70,9 @@ The configuration file for DCC-EX Command Station // NOTE: Only supported on Arduino Mega // Set to false if you not even want it on the Arduino Mega // -//#define ENABLE_WIFI true +// Currently ESP32 single core only works with WIFI ON because of Watchdog code +// and if you have an ESP32 you probably want WIFI anyway. +#define ENABLE_WIFI true ///////////////////////////////////////////////////////////////////////////////////// // From 31059a615c20bfb1814685ea0e9b9f09383c8fe3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 27 Oct 2021 23:03:37 +0200 Subject: [PATCH 025/870] use ESP-IDF ADC functions instead of analogRead() which breaks waveform --- MotorDriver.cpp | 12 ++++++++++++ config.example.h | 15 ++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 8bcb03c94..8959f42ce 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -20,6 +20,10 @@ #include "MotorDriver.h" #include "DCCTimer.h" #include "DIAG.h" +#if defined(ARDUINO_ARCH_ESP32) +#include +#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28) +#endif bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; @@ -55,7 +59,13 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 currentPin=current_pin; if (currentPin!=UNUSED_PIN) { pinMode(currentPin, INPUT); +#if defined(ARDUINO_ARCH_ESP32) + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11); + senseOffset = adc1_get_raw(pinToADC1Channel(currentPin)); +#else senseOffset=analogRead(currentPin); // value of sensor at zero current +#endif } faultPin=fault_pin; @@ -150,6 +160,8 @@ int MotorDriver::getCurrentRaw() { current = analogRead(currentPin)-senseOffset; overflow_count = 0; SREG = sreg_backup; /* restore interrupt state */ +#elif defined(ARDUINO_ARCH_ESP32) + current = adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset; #else current = analogRead(currentPin)-senseOffset; #endif diff --git a/config.example.h b/config.example.h index 73380519a..2d345e234 100644 --- a/config.example.h +++ b/config.example.h @@ -43,19 +43,24 @@ The configuration file for DCC-EX Command Station // //#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD +// https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/ +// 4 high at boot #define ESP8266_MOTOR_SHIELD F("ESP8266"), \ new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\ new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, A0 , 2.99, 2000, UNUSED_PIN) -// ADC1 CH4 = GPIO32 -// ADC1 CH5 = GPIO33 +// ESP32 ADC1 only supported GPIO pins 32 to 39, for example +// ADC1 CH4 = GPIO32, ADC1 CH5 = GPIO33, ADC1 CH0 = GPIO36 +// // Adjust pin usage according to info in // https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/ // https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ -// Adjust conversion factor according to your voltage divider +// +// Adjust conversion factor according to your voltage divider. +// #define ESP32_MOTOR_SHIELD F("ESP32"), \ - new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.99, 2000, UNUSED_PIN),\ - new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.99, 2000, UNUSED_PIN) + new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN),\ + new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.00, 2000, UNUSED_PIN) #define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD From 9d74b0f6a55d33aee7c5be4d31b0b35b40d359ba Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 29 Oct 2021 22:19:23 +0200 Subject: [PATCH 026/870] set pinMode analog --- MotorDriver.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 8959f42ce..11d56d854 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -58,12 +58,13 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 currentPin=current_pin; if (currentPin!=UNUSED_PIN) { - pinMode(currentPin, INPUT); #if defined(ARDUINO_ARCH_ESP32) + pinMode(currentPin, ANALOG); adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11); senseOffset = adc1_get_raw(pinToADC1Channel(currentPin)); #else + pinMode(currentPin, INPUT); senseOffset=analogRead(currentPin); // value of sensor at zero current #endif } From 278f7618f4c6018dc9a4e812871f1cfb381d2e92 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 00:10:58 +0200 Subject: [PATCH 027/870] do something i AP mode --- WifiESP32.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index b11f9061f..cc3d33509 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -67,6 +67,7 @@ void disableCoreWDT(byte core){ static std::vector clients; // a list to hold all clients static WiFiServer *server = NULL; static RingStream *outboundRing = new RingStream(2048); +static bool APmode = false; bool WifiESP::setup(const char *SSid, const char *password, @@ -118,6 +119,7 @@ bool WifiESP::setup(const char *SSid, DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str()); DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); wifiUp = true; + APmode = true; } } @@ -138,7 +140,8 @@ bool WifiESP::setup(const char *SSid, void WifiESP::loop() { int clientId; //tmp loop var - if (WiFi.status() == WL_CONNECTED /* || what for AP? */) { + // really no good way to check for LISTEN especially in AP mode? + if (APmode || WiFi.status() == WL_CONNECTED) { if (server->hasClient()) { // loop over all clients and remove inactive for (clientId=0; clientId Date: Sun, 31 Oct 2021 00:40:35 +0200 Subject: [PATCH 028/870] more diag messages --- WifiESP32.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index cc3d33509..56765baac 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -99,6 +99,8 @@ bool WifiESP::setup(const char *SSid, if (WiFi.status() == WL_CONNECTED) { DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str()); wifiUp = true; + } else { + DIAG(F("Could not connect to Wifi SSID %s"),SSid); } } if (!haveSSID) { @@ -120,12 +122,14 @@ bool WifiESP::setup(const char *SSid, DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str()); wifiUp = true; APmode = true; + } else { + DIAG(F("Could not set up AP with Wifi SSID %s"),strSSID.c_str()); } } if (!wifiUp) { - DIAG(F("Wifi all fail")); + DIAG(F("Wifi setup all fail (STA and AP mode)")); // no idea to go on return false; } From c5b283bd8ccc59351dfb6231ecd46c2ca8640412 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 01:10:13 +0200 Subject: [PATCH 029/870] should compile for all boards --- DCCEXParser.cpp | 2 +- DCCTimer.h | 9 +++++++++ DCCWaveform.cpp | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index c8d7fdefd..c83c0e0d6 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -17,10 +17,10 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#include "DCC.h" // includes "Motordriver.h" and #include "defines.h" #include "StringFormatter.h" #include "DCCEXParser.h" -#include "DCC.h" #include "DCCWaveform.h" #include "Turnouts.h" #include "Outputs.h" diff --git a/DCCTimer.h b/DCCTimer.h index d421828c9..0af361429 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -37,5 +37,14 @@ class DCCTimer { private: }; +#if defined(ARDUINO_ARCH_ESP32) extern portMUX_TYPE timerMux; #endif + +#if !(defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)) +#ifndef IRAM_ATTR +#define IRAM_ATTR +#endif +#endif + +#endif //DCCTimer.h diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 439e78117..a8b83e415 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ - #pragma GCC optimize ("-O3") +#pragma GCC optimize ("-O3") #include #include "defines.h" From c87a80928bfefef00883fb68412df7d6ecbdb075 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 22:06:22 +0100 Subject: [PATCH 030/870] special tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6ffec91c4..8fdecc092 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "50fcbc0" +#define GITHUB_SHA ESP32-20211031-11:05" From a109ba4e019001c8ff297403c5c91bdd03600188 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 23:35:28 +0100 Subject: [PATCH 031/870] 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 286e7c3fe..ee1918d03 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 837b0a9fb63412b497d721f7f602d581a31dfa50 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Oct 2021 23:46:25 +0100 Subject: [PATCH 032/870] typo --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8fdecc092..c2b4c7e42 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA ESP32-20211031-11:05" +#define GITHUB_SHA "ESP32-20211031-11:05" From 77ee57eb83ebf4a7decff17b31e1537938230d5a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Nov 2021 17:50:32 +0100 Subject: [PATCH 033/870] give up eventually --- WifiESP32.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 56765baac..c907448f6 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -77,6 +77,7 @@ bool WifiESP::setup(const char *SSid, bool havePassword = true; bool haveSSID = true; bool wifiUp = false; + uint8_t tries = 40; // tests // enableCoreWDT(1); @@ -92,8 +93,9 @@ bool WifiESP::setup(const char *SSid, WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); WiFi.begin(SSid, password); - while (WiFi.status() != WL_CONNECTED) { + while (WiFi.status() != WL_CONNECTED && tries) { Serial.print('.'); + tries--; delay(500); } if (WiFi.status() == WL_CONNECTED) { From 836ccc143e98c783a6eb86d21d13fcf8d21bb951 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 3 Nov 2021 09:45:30 +0100 Subject: [PATCH 034/870] check power overload only when not ack check --- DCCEX.h | 9 +++++---- DCCWaveform.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/DCCEX.h b/DCCEX.h index 8ff2f514d..fa7f8b850 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -44,9 +44,10 @@ #include "LCN.h" #include "freeMemory.h" -#if __has_include ( "myAutomation.h") - #include "RMFT.h" - #define RMFT_ACTIVE -#endif +// not yet in this branch +//#if __has_include ( "myAutomation.h") +// #include "RMFT.h" +// #define RMFT_ACTIVE +//#endif #endif diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index a8b83e415..94cd91df3 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -60,8 +60,6 @@ volatile bool ackflag = 0; #endif void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { - mainTrack.checkPowerOverload(false); - progTrack.checkPowerOverload(ackManagerActive); #ifdef SLOW_ANALOG_READ if (ackflag) { progTrack.checkAck(); @@ -69,8 +67,13 @@ void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { portENTER_CRITICAL(&timerMux); ackflag = 0; portEXIT_CRITICAL(&timerMux); + } else { + progTrack.checkPowerOverload(ackManagerActive); } +#else + progTrack.checkPowerOverload(ackManagerActive); #endif + mainTrack.checkPowerOverload(false); } void IRAM_ATTR DCCWaveform::interruptHandler() { From 4901f12fcd0aec0af4acc5ce6048ee134063a24e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Nov 2021 02:40:49 +0100 Subject: [PATCH 035/870] make own task on core0 for WifiESP::loop() on ESP32 --- CommandStation-EX.ino | 3 ++- WifiESP32.cpp | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 71c1d6730..57b281be4 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -124,7 +124,8 @@ void loop() #if WIFI_ON #ifndef ESP_FAMILY WifiInterface::loop(); -#else +#endif +#if defined(ARDUINO_ARCH_ESP8266) // on ESP32 own task WifiESP::loop(); #endif #endif //WIFI_ON diff --git a/WifiESP32.cpp b/WifiESP32.cpp index c907448f6..0aaacd3e1 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -69,6 +69,12 @@ static WiFiServer *server = NULL; static RingStream *outboundRing = new RingStream(2048); static bool APmode = false; +void wifiLoop(void *){ + for(;;){ + WifiESP::loop(); + } +} + bool WifiESP::setup(const char *SSid, const char *password, const char *hostname, @@ -139,6 +145,13 @@ bool WifiESP::setup(const char *SSid, server->begin(); DIAG(F("Server up port %d"),port); + xTaskCreatePinnedToCore(wifiLoop, /* Task function. */ + "wifiLoop",/* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 0); /* pin task to core 0 */ return true; return false; } From 877db433a471757dec8b2ac2445886cd4ef9bb5a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Nov 2021 02:59:57 +0100 Subject: [PATCH 036/870] make task startup nicer --- WifiESP32.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 0aaacd3e1..49b211627 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -143,17 +143,25 @@ bool WifiESP::setup(const char *SSid, } server = new WiFiServer(port); // start listening on tcp port server->begin(); - DIAG(F("Server up port %d"),port); + // server started here + + //start loop task + if (pdPASS != xTaskCreatePinnedToCore( + wifiLoop, /* Task function. */ + "wifiLoop",/* name of task. */ + 10000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 0)) { /* pin task to core 0 */ + DIAG(F("Could not create wifiLoop task")); + return false; + } - xTaskCreatePinnedToCore(wifiLoop, /* Task function. */ - "wifiLoop",/* name of task. */ - 10000, /* Stack size of task */ - NULL, /* parameter of the task */ - 1, /* priority of the task */ - NULL, /* Task handle to keep track of created task */ - 0); /* pin task to core 0 */ + // report server started after wifiLoop creation + // when everything looks good + DIAG(F("Server up port %d"),port); return true; - return false; } void WifiESP::loop() { From d7e46ac625d9d1308ede9913eb9b98162dd17861 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Nov 2021 03:04:50 +0100 Subject: [PATCH 037/870] set version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c2b4c7e42..98ed83776 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-20211031-11:05" +#define GITHUB_SHA "ESP32-20211106-03:03" From 55c7a0a1e823cabd198a270cf6421b2f31147dc0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Nov 2021 23:51:32 +0100 Subject: [PATCH 038/870] protect ringstream --- RingStream.cpp | 16 +++++++++++++++- RingStream.h | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/RingStream.cpp b/RingStream.cpp index baa4cc1a3..0f91f78aa 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -30,28 +30,36 @@ RingStream::RingStream( const uint16_t len) _overflow=false; _mark=0; _count=0; +#if defined(ARDUINO_ARCH_ESP32) + portMUX_TYPE _bufMux = portMUX_INITIALIZER_UNLOCKED; +#endif } size_t RingStream::write(uint8_t b) { if (_overflow) return 0; + portENTER_CRITICAL(&_bufMux); _buffer[_pos_write] = b; ++_pos_write; if (_pos_write==_len) _pos_write=0; if (_pos_write==_pos_read) { _overflow=true; + portEXIT_CRITICAL(&_bufMux); return 0; } _count++; + portEXIT_CRITICAL(&_bufMux); return 1; } int RingStream::read(byte advance) { if ((_pos_read==_pos_write) && !_overflow) return -1; // empty if (_pos_read == _mark) return -1; + portENTER_CRITICAL(&_bufMux); byte b=_buffer[_pos_read]; _pos_read += advance; if (_pos_read==_len) _pos_read=0; _overflow=false; + portEXIT_CRITICAL(&_bufMux); return b; } @@ -70,11 +78,13 @@ int RingStream::freeSpace() { // mark start of message with client id (0...9) void RingStream::mark(uint8_t b) { //DIAG(F("Mark1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); + portENTER_CRITICAL(&_bufMux); _mark=_pos_write; write(b); // client id write((uint8_t)0); // count MSB placemarker write((uint8_t)0); // count LSB placemarker _count=0; + portEXIT_CRITICAL(&_bufMux); } // peekTargetMark is used by the parser stash routines to know which client @@ -89,16 +99,19 @@ void RingStream::info() { bool RingStream::commit() { //DIAG(F("Commit1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); + portENTER_CRITICAL(&_bufMux); if (_overflow) { DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); // just throw it away _pos_write=_mark; _overflow=false; - return false; // commit failed + portEXIT_CRITICAL(&_bufMux); + return false; // commit failed } if (_count==0) { // ignore empty response _pos_write=_mark; + portEXIT_CRITICAL(&_bufMux); return true; // true=commit ok } // Go back to the _mark and inject the count 1 byte later @@ -110,5 +123,6 @@ bool RingStream::commit() { _buffer[_mark]=lowByte(_count); _mark=_len+1; //DIAG(F("Commit2 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); + portEXIT_CRITICAL(&_bufMux); return true; // commit worked } diff --git a/RingStream.h b/RingStream.h index 974a2352c..6d5317179 100644 --- a/RingStream.h +++ b/RingStream.h @@ -46,6 +46,9 @@ class RingStream : public Print { int _mark; int _count; byte * _buffer; +#if defined(ARDUINO_ARCH_ESP32) + portMUX_TYPE _bufMux; +#endif }; #endif From c02e976c9facd9de515038a945ea1fb57cc0d760 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Nov 2021 00:12:11 +0100 Subject: [PATCH 039/870] protect ringstream typo fix --- GITHUB_SHA.h | 2 +- RingStream.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 98ed83776..7548d9ae4 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-20211106-03:03" +#define GITHUB_SHA "ESP32-20211107-00:09" diff --git a/RingStream.cpp b/RingStream.cpp index 0f91f78aa..1fa9ec6b4 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -31,7 +31,7 @@ RingStream::RingStream( const uint16_t len) _mark=0; _count=0; #if defined(ARDUINO_ARCH_ESP32) - portMUX_TYPE _bufMux = portMUX_INITIALIZER_UNLOCKED; + _bufMux = portMUX_INITIALIZER_UNLOCKED; #endif } From 5cbf0c2cad6ffb38786fb0559f7f8545991e7d4e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Nov 2021 00:21:15 +0100 Subject: [PATCH 040/870] defines.h needed to get ESP32 macro on non-ESP32 --- RingStream.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/RingStream.cpp b/RingStream.cpp index 1fa9ec6b4..70b66c226 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -18,6 +18,7 @@ */ #include "RingStream.h" +#include "defines.h" #include "DIAG.h" RingStream::RingStream( const uint16_t len) From 4668e116f4fb26a7937116de103b156495970004 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 14 Nov 2021 13:10:16 +0100 Subject: [PATCH 041/870] preambles running --- DCCRMT.cpp | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ DCCRMT.h | 51 ++++++++++++++++++ DCCWaveform.cpp | 7 +++ WifiESP32.cpp | 4 +- 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 DCCRMT.cpp create mode 100644 DCCRMT.h diff --git a/DCCRMT.cpp b/DCCRMT.cpp new file mode 100644 index 000000000..5186ecf8a --- /dev/null +++ b/DCCRMT.cpp @@ -0,0 +1,138 @@ +/* + * © 2021, Harald Barth. + * + * This file is part of DCC-EX + * + * 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 . + */ + +#include "defines.h" +#include "DIAG.h" +#include "DCCRMT.h" +#include "soc/periph_defs.h" +#include "driver/periph_ctrl.h" + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0) +#error wrong IDF version +#endif + +void setDCCBit1(rmt_item32_t* item) { + item->level0 = 1; + item->duration0 = DCC_1_HALFPERIOD; + item->level1 = 0; + item->duration1 = DCC_1_HALFPERIOD; +} + +void setDCCBit0(rmt_item32_t* item) { + item->level0 = 1; + item->duration0 = DCC_0_HALFPERIOD; + item->level1 = 0; + item->duration1 = DCC_0_HALFPERIOD; +} + +void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { + BaseType_t wtf = pdFALSE; + RMTPin *tt = (RMTPin *)t; + //DIAG(F("interrupt %d"), tt->idleLen); + tt->RMTinterrupt(channel,t); + rmt_tx_start(channel,true); + portYIELD_FROM_ISR(wtf); +} + +RMTPin::RMTPin(byte pin, byte ch, byte plen) { + + // preamble + preambleLen = plen+1; + preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t)); + for (byte n=0; npreambleNext) { + rmt_fill_tx_items(channel, obj->preamble, obj->preambleLen, 0); + //obj->preambleNext = false; + } else { + if (obj->dataNext) { + rmt_fill_tx_items(channel, obj->packetBits, obj->packetLen, 0); + } else { + // here we should not get as now we need to send idle packet + rmt_fill_tx_items(channel, obj->idle, obj->idleLen, 0); + } + obj->preambleNext = true; + } + rmt_tx_start(channel,true); + DIAG(F("START")); + */ +} diff --git a/DCCRMT.h b/DCCRMT.h new file mode 100644 index 000000000..58f77de90 --- /dev/null +++ b/DCCRMT.h @@ -0,0 +1,51 @@ +/* + * © 2021, Harald Barth. + * + * This file is part of DCC-EX + * + * 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 . + */ + +#pragma once +#include +#include "driver/rmt.h" +#include "soc/rmt_reg.h" +#include "soc/rmt_struct.h" + +#define DCC_1_HALFPERIOD 4640 // 1 / 80000000 * 4640 = 58us +#define DCC_0_HALFPERIOD 8000 + +class RMTPin { + public: + RMTPin(byte pin, byte ch, byte plen); + void IRAM_ATTR RMTinterrupt(rmt_channel_t, void *t); + + static RMTPin mainRMTPin; + static RMTPin progRMTPin; + + // private: + + rmt_channel_t channel; + // 3 types of data to send, preamble and then idle or data + // if this is prog track, idle will contain reset instead + rmt_item32_t *idle; + byte idleLen; + rmt_item32_t *preamble; + byte preambleLen; + rmt_item32_t packetBits[64]; + byte packetLen; + // flags + volatile bool preambleNext = true; // alternate between preamble and content + volatile bool dataNext = false; // do we have real data available or send idle +}; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 94cd91df3..505583f7f 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -25,6 +25,7 @@ #include "DCCTimer.h" #include "DIAG.h" #include "freeMemory.h" +#include "DCCRMT.h" DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); @@ -37,6 +38,9 @@ volatile uint8_t DCCWaveform::numAckSamples=0; uint8_t DCCWaveform::trailingEdgeCounter=0; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { + + RMTPin *p = new RMTPin(21, 0, PREAMBLE_BITS_MAIN); + mainTrack.motorDriver=mainDriver; progTrack.motorDriver=progDriver; progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static @@ -47,11 +51,14 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { && (mainDriver->getFaultPin() != UNUSED_PIN)); // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); + /* if (MotorDriver::usePWM) DIAG(F("Signal pin config: high accuracy waveform")); else DIAG(F("Signal pin config: normal accuracy waveform")); DCCTimer::begin(DCCWaveform::interruptHandler); + */ + DIAG(F("No waveform")); } #ifdef SLOW_ANALOG_READ diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 49b211627..48d40d67d 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -17,6 +17,7 @@ along with CommandStation. If not, see . */ +#include #include "defines.h" #if defined(ARDUINO_ARCH_ESP32) #include @@ -231,7 +232,7 @@ void WifiESP::loop() { } } } //connected - yield(); + // when loop() is running on core0 we must // feed the core0 wdt ourselves as yield() // is not necessarily yielding to a low @@ -240,5 +241,6 @@ void WifiESP::loop() { // arduio IDE startup routines. if (xPortGetCoreID() == 0) feedTheDog0(); + yield(); } #endif //ESP32 From 97065e892dedcbc04130803b700fa83783c5c893 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 14 Nov 2021 14:48:32 +0100 Subject: [PATCH 042/870] transmit preamble and idle --- DCCRMT.cpp | 64 ++++++++++++++++++++----------------------------- DCCRMT.h | 4 ++-- DCCWaveform.cpp | 3 --- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 5186ecf8a..abe1cf870 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -41,34 +41,37 @@ void setDCCBit0(rmt_item32_t* item) { item->duration1 = DCC_0_HALFPERIOD; } +void setEOT(rmt_item32_t* item) { + item->val = 0; +} + void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { - BaseType_t wtf = pdFALSE; RMTPin *tt = (RMTPin *)t; - //DIAG(F("interrupt %d"), tt->idleLen); - tt->RMTinterrupt(channel,t); - rmt_tx_start(channel,true); - portYIELD_FROM_ISR(wtf); + tt->RMTinterrupt(channel); } RMTPin::RMTPin(byte pin, byte ch, byte plen) { // preamble - preambleLen = plen+1; + preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t)); for (byte n=0; npreambleNext) { - rmt_fill_tx_items(channel, obj->preamble, obj->preambleLen, 0); - //obj->preambleNext = false; +void IRAM_ATTR RMTPin::RMTinterrupt(rmt_channel_t channel) { + + if (preambleNext) { + rmt_fill_tx_items(channel, preamble, preambleLen, 0); + preambleNext = false; } else { - if (obj->dataNext) { - rmt_fill_tx_items(channel, obj->packetBits, obj->packetLen, 0); + if (dataNext) { + rmt_fill_tx_items(channel, packetBits, packetLen, 0); } else { // here we should not get as now we need to send idle packet - rmt_fill_tx_items(channel, obj->idle, obj->idleLen, 0); + rmt_fill_tx_items(channel, idle, idleLen, 0); } - obj->preambleNext = true; + preambleNext = true; } rmt_tx_start(channel,true); - DIAG(F("START")); - */ } diff --git a/DCCRMT.h b/DCCRMT.h index 58f77de90..78eaaa30b 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -29,12 +29,12 @@ class RMTPin { public: RMTPin(byte pin, byte ch, byte plen); - void IRAM_ATTR RMTinterrupt(rmt_channel_t, void *t); + void IRAM_ATTR RMTinterrupt(rmt_channel_t); static RMTPin mainRMTPin; static RMTPin progRMTPin; - // private: + private: rmt_channel_t channel; // 3 types of data to send, preamble and then idle or data diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 505583f7f..ee3e9f1c6 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -51,14 +51,11 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { && (mainDriver->getFaultPin() != UNUSED_PIN)); // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); - /* if (MotorDriver::usePWM) DIAG(F("Signal pin config: high accuracy waveform")); else DIAG(F("Signal pin config: normal accuracy waveform")); DCCTimer::begin(DCCWaveform::interruptHandler); - */ - DIAG(F("No waveform")); } #ifdef SLOW_ANALOG_READ From 71117bc7a1fed796bd95d2331ac1627b03c60060 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 14 Nov 2021 14:49:55 +0100 Subject: [PATCH 043/870] special version --- GITHUB_SHA.h | 2 +- version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7548d9ae4..50fb4ee7b 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-20211107-00:09" +#define GITHUB_SHA "ESP32-2021114-14:49" diff --git a/version.h b/version.h index 09211fa6f..cf06ed83d 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "3.1.6" +#define VERSION "3.1.90" // 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 10209ed6f353762a54fb29e3c82f0ec58d8f1241 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 14 Nov 2021 15:35:26 +0100 Subject: [PATCH 044/870] remove uneccessary workaround, compensate for interrupt length --- DCCRMT.cpp | 27 +++++++++++++++++++-------- GITHUB_SHA.h | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index abe1cf870..4a5ec5905 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -20,8 +20,9 @@ #include "defines.h" #include "DIAG.h" #include "DCCRMT.h" -#include "soc/periph_defs.h" -#include "driver/periph_ctrl.h" + +//#include "soc/periph_defs.h" +//#include "driver/periph_ctrl.h" #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0) #error wrong IDF version @@ -41,6 +42,13 @@ void setDCCBit0(rmt_item32_t* item) { item->duration1 = DCC_0_HALFPERIOD; } +void setDCCBit0Last(rmt_item32_t* item) { + item->level0 = 1; + item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10; + item->level1 = 0; + item->duration1 = DCC_0_HALFPERIOD; +} + void setEOT(rmt_item32_t* item) { item->val = 0; } @@ -56,9 +64,9 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t)); for (byte n=0; n Date: Mon, 15 Nov 2021 22:28:30 +0100 Subject: [PATCH 045/870] Transmit DCC packet to loco --- DCCRMT.cpp | 75 ++++++++++++++++++++++++++++++++++++------------- DCCRMT.h | 13 +++++---- DCCWaveform.cpp | 14 +++++++-- DCCWaveform.h | 3 ++ GITHUB_SHA.h | 2 +- 5 files changed, 78 insertions(+), 29 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 4a5ec5905..5287e162a 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -55,7 +55,7 @@ void setEOT(rmt_item32_t* item) { void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { RMTPin *tt = (RMTPin *)t; - tt->RMTinterrupt(channel); + tt->RMTinterrupt(); } RMTPin::RMTPin(byte pin, byte ch, byte plen) { @@ -65,7 +65,7 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t)); for (byte n=0; n 0) // we have still old work to do + return false; + byte bitcounter = 0; + for(byte n=0; n 0) + dataRepeat--; + return; } diff --git a/DCCRMT.h b/DCCRMT.h index 78eaaa30b..c197757df 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -29,8 +29,10 @@ class RMTPin { public: RMTPin(byte pin, byte ch, byte plen); - void IRAM_ATTR RMTinterrupt(rmt_channel_t); - + void IRAM_ATTR RMTinterrupt(); + void RMTprefill(); + bool fillData(const byte buffer[], byte byteCount, byte repeatCount); + static RMTPin mainRMTPin; static RMTPin progRMTPin; @@ -43,9 +45,10 @@ class RMTPin { byte idleLen; rmt_item32_t *preamble; byte preambleLen; - rmt_item32_t packetBits[64]; - byte packetLen; + rmt_item32_t *data; + byte dataLen; // flags volatile bool preambleNext = true; // alternate between preamble and content - volatile bool dataNext = false; // do we have real data available or send idle + volatile bool dataReady = false; // do we have real data available or send idle + volatile byte dataRepeat = 0; }; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index ee3e9f1c6..0e32c70ee 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -25,7 +25,6 @@ #include "DCCTimer.h" #include "DIAG.h" #include "freeMemory.h" -#include "DCCRMT.h" DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); @@ -39,7 +38,7 @@ uint8_t DCCWaveform::trailingEdgeCounter=0; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { - RMTPin *p = new RMTPin(21, 0, PREAMBLE_BITS_MAIN); + mainTrack.rmtPin = new RMTPin(21, 0, PREAMBLE_BITS_MAIN); mainTrack.motorDriver=mainDriver; progTrack.motorDriver=progDriver; @@ -64,6 +63,13 @@ volatile bool ackflag = 0; #endif void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { + + if (mainTrack.packetPendingRMT) { + mainTrack.rmtPin->fillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats); + mainTrack.packetPendingRMT=false; + // sentResetsSincePacket = 0 // later when progtrack + } + #ifdef SLOW_ANALOG_READ if (ackflag) { progTrack.checkAck(); @@ -122,6 +128,7 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { isMainTrack = isMain; packetPending = false; + packetPendingRMT = false; memcpy(transmitPacket, idlePacket, sizeof(idlePacket)); state = WAVE_START; // The +1 below is to allow the preamble generator to create the stop bit @@ -290,7 +297,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() { // Wait until there is no packet pending, then make this pending void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum - while (packetPending); + while (packetPending||packetPendingRMT); portENTER_CRITICAL(&timerMux); byte checksum = 0; for (byte b = 0; b < byteCount; b++) { @@ -302,6 +309,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea pendingLength = byteCount + 1; pendingRepeats = repeats; packetPending = true; + packetPendingRMT = true; sentResetsSincePacket=0; portEXIT_CRITICAL(&timerMux); } diff --git a/DCCWaveform.h b/DCCWaveform.h index 29d6a29e9..9822a879e 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -20,6 +20,7 @@ #ifndef DCCWaveform_h #define DCCWaveform_h +#include "DCCRMT.h" #include "MotorDriver.h" // Wait times for power management. Unit: milliseconds @@ -82,6 +83,7 @@ class DCCWaveform { } void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; + volatile bool packetPendingRMT; volatile byte sentResetsSincePacket; volatile bool autoPowerOff=false; void setAckBaseline(); //prog track only @@ -122,6 +124,7 @@ class DCCWaveform { bool isMainTrack; MotorDriver* motorDriver; + RMTPin* rmtPin; // Transmission controller byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte transmitLength; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e47a6b344..512e5445c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-2021114-15:35" +#define GITHUB_SHA "ESP32-2021115-22:27" From 114686d124105803c0b81e109812764a41df59f5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 15 Nov 2021 23:10:23 +0100 Subject: [PATCH 046/870] cleanup comments --- DCCRMT.cpp | 33 ++++++++++----------------------- DCCRMT.h | 3 ++- DCCWaveform.cpp | 2 +- GITHUB_SHA.h | 2 +- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 5287e162a..c07805b66 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -21,9 +21,6 @@ #include "DIAG.h" #include "DCCRMT.h" -//#include "soc/periph_defs.h" -//#include "driver/periph_ctrl.h" - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0) #error wrong IDF version #endif @@ -82,8 +79,8 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { setEOT(idle + 28); // EOT marker // data: max packet size today is 5 + checksum - dataLen = (5+1)*9+2; // Each byte has one bit extra and one 0 bit and one EOF marker - data = (rmt_item32_t*)malloc(dataLen*sizeof(rmt_item32_t)); + maxDataLen = (5+1)*9+2; // Each byte has one bit extra and one 0 bit and one EOF marker + data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t)); rmt_config_t config; // Configure the RMT channel for TX @@ -97,10 +94,6 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { // 11*9 + extrazero + EOT = 124 // 2 mem block of 64 RMT items should be enough - // this was not our problem https://esp32.com/viewtopic.php?t=5252 - //periph_module_disable(PERIPH_RMT_MODULE); - //periph_module_enable(PERIPH_RMT_MODULE); - ESP_ERROR_CHECK(rmt_config(&config)); // NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED)); @@ -110,11 +103,6 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { rmt_register_tx_end_callback(interrupt, this); rmt_set_tx_intr_en(channel, true); - // rmt_set_source_clk() // not needed as APB only supported currently - - - //rmt_register_tx_end_callback() - DIAG(F("Starting channel %d signal generator"), config.channel); // send one bit to kickstart the signal, remaining data will come from the @@ -133,9 +121,13 @@ void RMTPin::RMTprefill() { const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; -bool RMTPin::fillData(const byte buffer[], byte byteCount, byte repeatCount=1) { +bool RMTPin::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1) { if (dataReady == true || dataRepeat > 0) // we have still old work to do return false; + if (byteCount*9+2 > maxDataLen) // this would overun our allocated memory for data + return false; // something very broken, can not convert packet + + // convert bytes to RMT stream of "bits" byte bitcounter = 0; for(byte n=0; n 0) + if (dataRepeat > 0) // if a repeat count was specified, work on that dataRepeat--; return; } diff --git a/DCCRMT.h b/DCCRMT.h index c197757df..2e119e640 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -31,7 +31,7 @@ class RMTPin { RMTPin(byte pin, byte ch, byte plen); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); - bool fillData(const byte buffer[], byte byteCount, byte repeatCount); + bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); static RMTPin mainRMTPin; static RMTPin progRMTPin; @@ -47,6 +47,7 @@ class RMTPin { byte preambleLen; rmt_item32_t *data; byte dataLen; + byte maxDataLen; // flags volatile bool preambleNext = true; // alternate between preamble and content volatile bool dataReady = false; // do we have real data available or send idle diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 0e32c70ee..24f605005 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -65,7 +65,7 @@ volatile bool ackflag = 0; void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { if (mainTrack.packetPendingRMT) { - mainTrack.rmtPin->fillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats); + mainTrack.rmtPin->RMTfillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats); mainTrack.packetPendingRMT=false; // sentResetsSincePacket = 0 // later when progtrack } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 512e5445c..c2c1e5568 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-2021115-22:27" +#define GITHUB_SHA "ESP32-2021115-23:10" From a69b7ee1131a746127b7db725f521b5d14688b05 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 18 Nov 2021 23:57:53 +0100 Subject: [PATCH 047/870] change to RMT loop mode --- DCCRMT.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index c07805b66..21002cf40 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -100,6 +100,7 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { DIAG(F("Register interrupt on core %d"), xPortGetCoreID()); + ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true)); rmt_register_tx_end_callback(interrupt, this); rmt_set_tx_intr_en(channel, true); @@ -148,7 +149,8 @@ bool RMTPin::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1 } void IRAM_ATTR RMTPin::RMTinterrupt() { - rmt_tx_start(channel,true); // preamble is always loaded, stat right away + //no rmt_tx_start(channel,true) as we run in loop mode + //preamble is always loaded at beginning of buffer if (dataReady) { // if we have new data, fill while preamble is running rmt_fill_tx_items(channel, data, dataLen, preambleLen-1); dataReady = false; From 55a789d65a808f29e153427b6001650bee826ddf Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 19 Nov 2021 00:03:21 +0100 Subject: [PATCH 048/870] set RMT clock to microseconds --- DCCRMT.cpp | 2 +- DCCRMT.h | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 21002cf40..a0d87566d 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -87,7 +87,7 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { bzero(&config, sizeof(rmt_config_t)); config.rmt_mode = RMT_MODE_TX; config.channel = channel = (rmt_channel_t)ch; - config.clk_div = 1; // use 80Mhz clock directly + config.clk_div = RMT_CLOCK_DIVIDER; config.gpio_num = (gpio_num_t)pin; config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion) // number of bits needed is 22preamble + start + diff --git a/DCCRMT.h b/DCCRMT.h index 2e119e640..bd235b769 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -23,8 +23,10 @@ #include "soc/rmt_reg.h" #include "soc/rmt_struct.h" -#define DCC_1_HALFPERIOD 4640 // 1 / 80000000 * 4640 = 58us -#define DCC_0_HALFPERIOD 8000 +// make calculations easy and set up for microseconds +#define RMT_CLOCK_DIVIDER 80 +#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us +#define DCC_0_HALFPERIOD 100 //8000 class RMTPin { public: From 50b854c5266d02ec9ff9fa0d5715f89347d2c6b6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 19 Nov 2021 00:34:56 +0100 Subject: [PATCH 049/870] remove extra zero bit --- DCCRMT.cpp | 25 ++++++++++++++++--------- GITHUB_SHA.h | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index a0d87566d..a56191744 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -17,9 +17,13 @@ * along with CommandStation. If not, see . */ +#include "config.h" #include "defines.h" #include "DIAG.h" #include "DCCRMT.h" +#include "DCCWaveform.h" // for MAX_PACKET_SIZE + +#define DATA_LEN(X) ((X)*9+1) // Each byte has one bit extra and we have one EOF marker #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0) #error wrong IDF version @@ -39,11 +43,12 @@ void setDCCBit0(rmt_item32_t* item) { item->duration1 = DCC_0_HALFPERIOD; } -void setDCCBit0Last(rmt_item32_t* item) { +// special long zero to trigger scope +void setDCCBit0Long(rmt_item32_t* item) { item->level0 = 1; item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10; item->level1 = 0; - item->duration1 = DCC_0_HALFPERIOD; + item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10; } void setEOT(rmt_item32_t* item) { @@ -62,11 +67,15 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t)); for (byte n=0; n 0) // we have still old work to do return false; - if (byteCount*9+2 > maxDataLen) // this would overun our allocated memory for data + if (DATA_LEN(byteCount) > maxDataLen) // this would overun our allocated memory for data return false; // something very broken, can not convert packet // convert bytes to RMT stream of "bits" @@ -140,7 +148,6 @@ bool RMTPin::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1 setDCCBit0(data + bitcounter++); // zero at end of each byte } setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit - setDCCBit0Last(data + bitcounter++); // extra 0 bit after end bit setEOT(data + bitcounter++); // EOT marker dataLen = bitcounter; dataReady = true; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c2c1e5568..6bcd0870c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-2021115-23:10" +#define GITHUB_SHA "ESP32-2021119-00:26" From c8e5123c0a0016099f0572acffbb51cbfc92ae84 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 21 Nov 2021 00:51:59 +0100 Subject: [PATCH 050/870] fix compile errors on ESP32 --- IO_MCP23008.h | 8 +++++++- IO_PCF8574.h | 6 +++--- RMFTMacros.h | 26 +++++++++++++------------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 18ff12f89..1b8471ed7 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -22,6 +22,12 @@ #include "IO_GPIOBase.h" +#if defined(ARDUINO_ARCH_ESP32) // min seems to be missing from that package +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif +#endif + class MCP23008 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { @@ -97,4 +103,4 @@ class MCP23008 : public GPIOBase { }; -#endif \ No newline at end of file +#endif diff --git a/IO_PCF8574.h b/IO_PCF8574.h index dea5e2c46..8403a1a7f 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -75,7 +75,7 @@ class PCF8574 : public GPIOBase { if (immediate) { uint8_t buffer[1]; I2CManager.read(_I2CAddress, buffer, 1); - _portInputState = ((uint16_t)buffer) & 0xff; + _portInputState = buffer[0]; } else { requestBlock.wait(); // Wait for preceding operation to complete // Issue new request to read GPIO register @@ -86,7 +86,7 @@ class PCF8574 : public GPIOBase { // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = ((uint16_t)inputBuffer[0]) & 0xff; + _portInputState = inputBuffer[0]; else _portInputState = 0xff; } @@ -99,4 +99,4 @@ class PCF8574 : public GPIOBase { uint8_t inputBuffer[1]; }; -#endif \ No newline at end of file +#endif diff --git a/RMFTMacros.h b/RMFTMacros.h index 01bfb46f0..8c98acdcc 100644 --- a/RMFTMacros.h +++ b/RMFTMacros.h @@ -226,16 +226,16 @@ const int StringMacroTracker1=__COUNTER__; // Define macros for route code creation #define V(val) ((int16_t)(val))&0x00FF,((int16_t)(val)>>8)&0x00FF -#define NOP 0,0 +#define NOOPERAND 0,0 #define ALIAS(name,value) #define EXRAIL const FLASH byte RMFT2::RouteCode[] = { #define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id), #define ROUTE(id, description) OPCODE_ROUTE, V(id), #define SEQUENCE(id) OPCODE_SEQUENCE, V(id), -#define ENDTASK OPCODE_ENDTASK,NOP, -#define DONE OPCODE_ENDTASK,NOP, -#define ENDEXRAIL OPCODE_ENDTASK,NOP,OPCODE_ENDEXRAIL,NOP }; +#define ENDTASK OPCODE_ENDTASK,NOOPERAND, +#define DONE OPCODE_ENDTASK,NOOPERAND, +#define ENDEXRAIL OPCODE_ENDTASK,NOOPERAND,OPCODE_ENDEXRAIL,NOOPERAND }; #define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id), #define AMBER(signal_id) OPCODE_AMBER,V(signal_id), @@ -245,7 +245,7 @@ const int StringMacroTracker1=__COUNTER__; #define DELAY(ms) OPCODE_DELAY,V(ms/100L), #define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay), #define DELAYRANDOM(mindelay,maxdelay) OPCODE_DELAY,V(mindelay/100L),OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L), -#define ENDIF OPCODE_ENDIF,NOP, +#define ENDIF OPCODE_ENDIF,NOOPERAND, #define ESTOP OPCODE_SPEED,V(1), #define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L), #define FOFF(func) OPCODE_FOFF,V(func), @@ -258,23 +258,23 @@ const int StringMacroTracker1=__COUNTER__; #define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id), #define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent), #define IFRESERVE(block) OPCODE_IFRESERVE,V(block), -#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOP, -#define JOIN OPCODE_JOIN,NOP, +#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOOPERAND, +#define JOIN OPCODE_JOIN,NOOPERAND, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), -#define PAUSE OPCODE_PAUSE,NOP, +#define PAUSE OPCODE_PAUSE,NOOPERAND, #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), -#define POWEROFF OPCODE_POWEROFF,NOP, +#define POWEROFF OPCODE_POWEROFF,NOOPERAND, #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), -#define READ_LOCO OPCODE_READ_LOCO1,NOP,OPCODE_READ_LOCO2,NOP, +#define READ_LOCO OPCODE_READ_LOCO1,NOOPERAND,OPCODE_READ_LOCO2,NOOPERAND, #define RED(signal_id) OPCODE_RED,V(signal_id), #define RESERVE(blockid) OPCODE_RESERVE,V(blockid), #define RESET(pin) OPCODE_RESET,V(pin), -#define RESUME OPCODE_RESUME,NOP, -#define RETURN OPCODE_RETURN,NOP, +#define RESUME OPCODE_RESUME,NOOPERAND, +#define RETURN OPCODE_RETURN,NOOPERAND, #define REV(speed) OPCODE_REV,V(speed), #define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route), #define SERIAL(msg) PRINT(msg) @@ -293,7 +293,7 @@ const int StringMacroTracker1=__COUNTER__; #define PIN_TURNOUT(id,pin) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define THROW(id) OPCODE_THROW,V(id), #define TURNOUT(id,addr,subaddr) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr), -#define UNJOIN OPCODE_UNJOIN,NOP, +#define UNJOIN OPCODE_UNJOIN,NOOPERAND, #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), #define WAITFOR(pin) OPCODE_WAITFOR,V(pin), #define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func), From 2632d44ec9b4e6310f003a293a891c6c69915b13 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 21 Nov 2021 21:28:56 +0100 Subject: [PATCH 051/870] remove packetPendingRMT from wrong if --- DCCWaveform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 094e39544..91f0a5d3f 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -302,7 +302,7 @@ void IRAM_ATTR DCCWaveform::interrupt2() { // Wait until there is no packet pending, then make this pending void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum - while (packetPending||packetPendingRMT); + while (packetPending); portENTER_CRITICAL(&timerMux); byte checksum = 0; for (byte b = 0; b < byteCount; b++) { From f7e90e7b738b943f078fa95e7ad50cf16002f2e5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 21 Nov 2021 22:53:17 +0100 Subject: [PATCH 052/870] MotorDriverContainer (multi-motordriver) start --- CommandStation-EX.ino | 6 +----- DCC.cpp | 10 ++++++---- DCC.h | 2 +- DCCWaveform.cpp | 13 ++++++++++--- MotorDriver.cpp | 24 ++++++++++++++++++++++++ MotorDriver.h | 21 +++++++++++++++++++++ 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index ee7245b5b..872dc2bb2 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -85,11 +85,7 @@ void setup() #endif // ETHERNET_ON // Responsibility 3: Start the DCC engine. - // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) - // Standard supported devices have pre-configured macros but custome hardware installations require - // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - DCC::begin(MOTOR_SHIELD_TYPE); + DCC::begin(); // Start RMFT (ignored if no automnation) RMFT::begin(); diff --git a/DCC.cpp b/DCC.cpp index 76bb9173b..4d30b986a 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -26,6 +26,9 @@ #include "FSH.h" #include "IODevice.h" +#include "MotorDriver.h" +extern MotorDriverContainer mDC; + // This module is responsible for converting API calls into // messages to be sent to the waveform generator. // It has no visibility of the hardware, timers, interrupts @@ -49,9 +52,8 @@ FSH* DCC::shieldName=NULL; byte DCC::joinRelay=UNUSED_PIN; byte DCC::globalSpeedsteps=128; -void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { - shieldName=(FSH *)motorShieldName; - StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); +void DCC::begin() { + StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), mDC.getMotorShieldName(), F(GITHUB_SHA)); // Initialise HAL layer before reading EEprom. IODevice::begin(); @@ -60,7 +62,7 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); - DCCWaveform::begin(mainDriver,progDriver); + DCCWaveform::begin(mDC.mainTrack(),mDC.progTrack()); } void DCC::setJoinRelayPin(byte joinRelayPin) { diff --git a/DCC.h b/DCC.h index cb03949af..19b4e02e1 100644 --- a/DCC.h +++ b/DCC.h @@ -77,7 +77,7 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); + static void begin(); static void setJoinRelayPin(byte joinRelayPin); static void loop(); diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 91f0a5d3f..a9f09f978 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -92,8 +92,10 @@ void IRAM_ATTR DCCWaveform::interruptHandler() { byte sigMain=signalTransform[mainTrack.state]; byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; // Set the signal state for both tracks - mainTrack.motorDriver->setSignal(sigMain); - progTrack.motorDriver->setSignal(sigProg); + if (mainTrack.motorDriver) + mainTrack.motorDriver->setSignal(sigMain); + if (progTrack.motorDriver) + progTrack.motorDriver->setSignal(sigProg); // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; progTrack.state=stateTransform[progTrack.state]; @@ -148,12 +150,14 @@ POWERMODE DCCWaveform::getPowerMode() { void DCCWaveform::setPowerMode(POWERMODE mode) { powerMode = mode; bool ison = (mode == POWERMODE::ON); - motorDriver->setPower( ison); + if (motorDriver) + motorDriver->setPower( ison); sentResetsSincePacket=0; } void DCCWaveform::checkPowerOverload(bool ackManagerActive) { + if (!motorDriver) return; if (millis() - lastSampleTaken < sampleDelay) return; lastSampleTaken = millis(); int tripValue= motorDriver->getRawCurrentTripValue(); @@ -323,6 +327,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea // (yes I know I could have subclassed the main track but...) void DCCWaveform::setAckBaseline() { + if (!motorDriver) return; if (isMainTrack) return; int baseline=motorDriver->getCurrentRaw(); ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); @@ -345,6 +350,7 @@ void DCCWaveform::setAckPending() { } byte DCCWaveform::getAck() { + if (!motorDriver) return 0; if (ackPending) return (2); // still waiting if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps); @@ -355,6 +361,7 @@ byte DCCWaveform::getAck() { #pragma GCC push_options #pragma GCC optimize ("-O3") void IRAM_ATTR DCCWaveform::checkAck() { + if (!motorDriver) return; // This function operates in interrupt() time so must be fast and can't DIAG if (sentResetsSincePacket > 6) { //ACK timeout ackCheckDuration=millis()-ackCheckStart; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 11d56d854..267524987 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -17,6 +17,8 @@ * along with CommandStation. If not, see . */ #include +#include "config.h" +#include "defines.h" #include "MotorDriver.h" #include "DCCTimer.h" #include "DIAG.h" @@ -194,3 +196,25 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res result.maskLOW = ~result.maskHIGH; // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } + +MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName, + MotorDriver *m0, + MotorDriver *m1, + MotorDriver *m2, + MotorDriver *m3, + MotorDriver *m4, + MotorDriver *m5, + MotorDriver *m6, + MotorDriver *m7) { + mD[0]=m0; + mD[1]=m1; + mD[2]=m2; + mD[3]=m3; + mD[4]=m4; + mD[5]=m5; + mD[6]=m6; + mD[7]=m7; + shieldName = (FSH *)motorShieldName; +} + +MotorDriverContainer mDC(MOTOR_SHIELD_TYPE); diff --git a/MotorDriver.h b/MotorDriver.h index 9193828aa..cd311a81d 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -106,4 +106,25 @@ class MotorDriver { } #endif }; + +class MotorDriverContainer { +public: + MotorDriverContainer(const FSH * motorShieldName, + MotorDriver *m0=NULL, + MotorDriver *m1=NULL, + MotorDriver *m2=NULL, + MotorDriver *m3=NULL, + MotorDriver *m4=NULL, + MotorDriver *m5=NULL, + MotorDriver *m6=NULL, + MotorDriver *m7=NULL); + // void SetCapability(byte n, byte cap, char [] name); + inline FSH *getMotorShieldName() { return shieldName; }; + inline MotorDriver *mainTrack() { return mD[0]; }; //start fixed + inline MotorDriver *progTrack() { return mD[1]; }; + +private: + MotorDriver *mD[8]; + FSH *shieldName; +}; #endif From 35ee03537d91712b33094cadab9ac53910d29b79 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 21 Nov 2021 22:56:14 +0100 Subject: [PATCH 053/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7663d2e5b..4fe3dd619 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-2021120-11:30" +#define GITHUB_SHA "ESP32-motordriver-2021121-22:55" From c00d3a825df838b22381aeb9e30dc68e215de9b1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Nov 2021 03:24:15 +0100 Subject: [PATCH 054/870] Shield RMT stuff with ifdef ESP32 --- DCCRMT.cpp | 2 ++ DCCRMT.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index a56191744..294663b3e 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -19,6 +19,7 @@ #include "config.h" #include "defines.h" +#if defined(ARDUINO_ARCH_ESP32) #include "DIAG.h" #include "DCCRMT.h" #include "DCCWaveform.h" // for MAX_PACKET_SIZE @@ -166,3 +167,4 @@ void IRAM_ATTR RMTPin::RMTinterrupt() { dataRepeat--; return; } +#endif //ESP32 diff --git a/DCCRMT.h b/DCCRMT.h index bd235b769..078a65226 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -19,6 +19,7 @@ #pragma once #include +#if defined(ARDUINO_ARCH_ESP32) #include "driver/rmt.h" #include "soc/rmt_reg.h" #include "soc/rmt_struct.h" @@ -55,3 +56,4 @@ class RMTPin { volatile bool dataReady = false; // do we have real data available or send idle volatile byte dataRepeat = 0; }; +#endif //ESP32 From 82df3a21dc1abf595810858af33ad0655cb7f63b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Nov 2021 03:55:00 +0100 Subject: [PATCH 055/870] Rename RMTPin to RMTChannel --- DCCRMT.cpp | 10 +++++----- DCCRMT.h | 8 ++++---- DCCWaveform.cpp | 2 +- DCCWaveform.h | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 294663b3e..108bac9af 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -57,11 +57,11 @@ void setEOT(rmt_item32_t* item) { } void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { - RMTPin *tt = (RMTPin *)t; + RMTChannel *tt = (RMTChannel *)t; tt->RMTinterrupt(); } -RMTPin::RMTPin(byte pin, byte ch, byte plen) { +RMTChannel::RMTChannel(byte pin, byte ch, byte plen) { // preamble preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker @@ -124,14 +124,14 @@ RMTPin::RMTPin(byte pin, byte ch, byte plen) { RMTinterrupt(); } -void RMTPin::RMTprefill() { +void RMTChannel::RMTprefill() { rmt_fill_tx_items(channel, preamble, preambleLen, 0); rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1); } const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; -bool RMTPin::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1) { +bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1) { if (dataReady == true || dataRepeat > 0) // we have still old work to do return false; if (DATA_LEN(byteCount) > maxDataLen) // this would overun our allocated memory for data @@ -156,7 +156,7 @@ bool RMTPin::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1 return true; } -void IRAM_ATTR RMTPin::RMTinterrupt() { +void IRAM_ATTR RMTChannel::RMTinterrupt() { //no rmt_tx_start(channel,true) as we run in loop mode //preamble is always loaded at beginning of buffer if (dataReady) { // if we have new data, fill while preamble is running diff --git a/DCCRMT.h b/DCCRMT.h index 078a65226..33339c488 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -29,15 +29,15 @@ #define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us #define DCC_0_HALFPERIOD 100 //8000 -class RMTPin { +class RMTChannel { public: - RMTPin(byte pin, byte ch, byte plen); + RMTChannel(byte pin, byte ch, byte plen); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); - static RMTPin mainRMTPin; - static RMTPin progRMTPin; + static RMTChannel mainRMTChannel; + static RMTChannel progRMTChannel; private: diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index a9f09f978..1cf301152 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -38,7 +38,7 @@ uint8_t DCCWaveform::trailingEdgeCounter=0; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { - mainTrack.rmtPin = new RMTPin(21, 0, PREAMBLE_BITS_MAIN); + mainTrack.rmtPin = new RMTChannel(21, 0, PREAMBLE_BITS_MAIN); mainTrack.motorDriver=mainDriver; progTrack.motorDriver=progDriver; diff --git a/DCCWaveform.h b/DCCWaveform.h index 9822a879e..ef8773a37 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -124,7 +124,7 @@ class DCCWaveform { bool isMainTrack; MotorDriver* motorDriver; - RMTPin* rmtPin; + RMTChannel* rmtPin; // Transmission controller byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte transmitLength; From ed2aa4c1d8fd5b314ae06b012b6c74f02cbe2c3a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Nov 2021 04:01:48 +0100 Subject: [PATCH 056/870] remove virtual --- MotorDriver.h | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index cd311a81d..a4a389d97 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -52,25 +52,12 @@ class MotorDriver { public: MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); - virtual void setPower( bool on); - void setSignal( bool high);/* { - if (usePWM) { - DCCTimer::setPWM(signalPin,high); - } - - if (high) { - setHIGH(fastSignalPin); - if (dualSignal) setLOW(fastSignalPin2); - } - else { - setLOW(fastSignalPin); - if (dualSignal) setHIGH(fastSignalPin2); - } - };*/ - virtual void setBrake( bool on); - virtual int getCurrentRaw(); - virtual unsigned int raw2mA( int raw); - virtual int mA2raw( unsigned int mA); + void setPower( bool on); + void setSignal( bool high); + void setBrake( bool on); + int getCurrentRaw(); + unsigned int raw2mA( int raw); + int mA2raw( unsigned int mA); inline int getRawCurrentTripValue() { return rawCurrentTripValue; } From c711be798079883a5d215c9cbec0573033f1c7c0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Nov 2021 23:26:04 +0100 Subject: [PATCH 057/870] DCCTrack::schedulePacket allows multiple different motordrivers side by side --- DCC.cpp | 30 +++++++++++++++--------- DCCEXParser.cpp | 3 ++- DCCPacket.h | 12 ++++++++++ DCCRMT.cpp | 20 ++++++++++++---- DCCRMT.h | 4 +++- DCCTrack.cpp | 38 ++++++++++++++++++++++++++++++ DCCTrack.h | 20 ++++++++++++++++ DCCWaveform.cpp | 24 ++++++++++--------- DCCWaveform.h | 10 ++++---- GITHUB_SHA.h | 2 +- IO_GPIOBase.h | 6 ++++- IO_MCP23008.h | 6 ----- MotorDriver.cpp | 61 +++++++++++++++++++++++++++++++++++++++---------- MotorDriver.h | 31 +++++++++++++++++++++++-- 14 files changed, 211 insertions(+), 56 deletions(-) create mode 100644 DCCPacket.h create mode 100644 DCCTrack.cpp create mode 100644 DCCTrack.h diff --git a/DCC.cpp b/DCC.cpp index 4d30b986a..fe0fbb44a 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -20,6 +20,7 @@ #include "DIAG.h" #include "DCC.h" #include "DCCWaveform.h" +#include "DCCTrack.h" #include "EEStore.h" #include "GITHUB_SHA.h" #include "version.h" @@ -62,7 +63,13 @@ void DCC::begin() { (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); - DCCWaveform::begin(mDC.mainTrack(),mDC.progTrack()); + DCCWaveform::begin(mDC.mainTrack(),mDC.progTrack()); + DCCTrack::mainTrack.addDriver(mDC.mainTrack()); + DCCTrack::progTrack.addDriver(mDC.progTrack()); + + MotorDriver *md; + mDC.add(2, md = new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); + DCCTrack::mainTrack.addDriver(md); } void DCC::setJoinRelayPin(byte joinRelayPin) { @@ -118,7 +125,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) { } - DCCWaveform::mainTrack.schedulePacket(b, nB, 0); + DCCTrack::mainTrack.schedulePacket(b, nB, 0); } void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) { @@ -132,7 +139,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) { if (byte1!=0) b[nB++] = byte1; b[nB++] = byte2; - DCCWaveform::mainTrack.schedulePacket(b, nB, 0); + DCCTrack::mainTrack.schedulePacket(b, nB, 0); } uint8_t DCC::getThrottleSpeed(int cab) { @@ -167,7 +174,7 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) { b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag b[nB++] = functionNumber >>7 ; // high order bits } - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCTrack::mainTrack.schedulePacket(b, nB, 3); return; } @@ -254,7 +261,7 @@ void DCC::setAccessory(int address, byte number, bool activate) { b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate - DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times + DCCTrack::mainTrack.schedulePacket(b, 2, 3); // Repeat the packet four times (3 2 1 0) } // @@ -272,7 +279,7 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) { b[nB++] = cv2(cv); b[nB++] = bValue; - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCTrack::mainTrack.schedulePacket(b, nB, 3); } // @@ -293,7 +300,7 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { b[nB++] = cv2(cv); b[nB++] = WRITE_BIT | (bValue ? BIT_ON : BIT_OFF) | bNum; - DCCWaveform::mainTrack.schedulePacket(b, nB, 4); + DCCTrack::mainTrack.schedulePacket(b, nB, 3); } void DCC::setProgTrackSyncMain(bool on) { @@ -571,6 +578,7 @@ byte DCC::loopStatus=0; void DCC::loop() { DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks + mDC.loop(); ackManagerLoop(); // maintain prog track ack manager issueReminders(); } @@ -771,7 +779,7 @@ void DCC::ackManagerLoop() { if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum); byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum; byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.setAckPending(); callbackState=AFTER_WRITE; } @@ -782,7 +790,7 @@ void DCC::ackManagerLoop() { if (checkResets( RESET_MIN)) return; if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.setAckPending(); callbackState=AFTER_WRITE; } @@ -793,7 +801,7 @@ void DCC::ackManagerLoop() { if (checkResets( RESET_MIN)) return; if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte); byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.setAckPending(); } break; @@ -805,7 +813,7 @@ void DCC::ackManagerLoop() { if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum; byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + DCCTrack::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); DCCWaveform::progTrack.setAckPending(); } break; diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 0757c295b..7a8ed3bb9 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -22,6 +22,7 @@ #include "StringFormatter.h" #include "DCCEXParser.h" #include "DCCWaveform.h" +#include "DCCTrack.h" #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" @@ -413,7 +414,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) packet[i]=(byte)p[i+1]; if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]); } - (opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3); + (opcode=='M'?DCCTrack::mainTrack:DCCTrack::progTrack).schedulePacket(packet,params,3); } return; diff --git a/DCCPacket.h b/DCCPacket.h new file mode 100644 index 000000000..261cff33a --- /dev/null +++ b/DCCPacket.h @@ -0,0 +1,12 @@ +#pragma once + +const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. + +class dccPacket { + public: + byte data[MAX_PACKET_SIZE+1]; // space for checksum if needed + byte length : 4; // future proof up to 15 + byte repeat : 4; // hopefully 15 enough for ever + //byte priority : 2; // 0 repeats; 1 mobile function ; 2 accessory ; 3 mobile speed +}; + diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 108bac9af..95d0e9a7d 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -131,15 +131,25 @@ void RMTChannel::RMTprefill() { const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; -bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1) { +//bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) { +bool RMTChannel::RMTfillData(dccPacket packet) { + // dataReady: Signals to then interrupt routine. It is set when + // we have data in the channel buffer which can be copied out + // to the HW. dataRepeat on the other hand signals back to + // the caller of this function if the data has been sent enough + // times (0 to 3 means 1 to 4 times in total). if (dataReady == true || dataRepeat > 0) // we have still old work to do return false; - if (DATA_LEN(byteCount) > maxDataLen) // this would overun our allocated memory for data - return false; // something very broken, can not convert packet + if (DATA_LEN(packet.length) > maxDataLen) { // this would overun our allocated memory for data + DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), packet.length, maxDataLen); + return false; // something very broken, can not convert packet + } + byte *buffer = packet.data; + // convert bytes to RMT stream of "bits" byte bitcounter = 0; - for(byte n=0; n #if defined(ARDUINO_ARCH_ESP32) +#include "DCCPacket.h" #include "driver/rmt.h" #include "soc/rmt_reg.h" #include "soc/rmt_struct.h" @@ -34,7 +35,8 @@ class RMTChannel { RMTChannel(byte pin, byte ch, byte plen); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); - bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); + bool RMTfillData(dccPacket packet); + //bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); static RMTChannel mainRMTChannel; static RMTChannel progRMTChannel; diff --git a/DCCTrack.cpp b/DCCTrack.cpp new file mode 100644 index 000000000..e0896d0e9 --- /dev/null +++ b/DCCTrack.cpp @@ -0,0 +1,38 @@ + +#include "defines.h" +#include "DCCTrack.h" +#include "DIAG.h" + +DCCTrack::DCCTrack(DCCWaveform *w) { + waveform = w; +} + +void DCCTrack::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { + dccPacket packet; + + // add checksum now, makes stuff easier later + byte checksum = 0; + for (byte b = 0; b < byteCount; b++) { + checksum ^= buffer[b]; + packet.data[b] = buffer[b]; + } + packet.data[byteCount] = checksum; + packet.length = byteCount + 1; + packet.repeat = repeats; + schedulePacket(packet); +}; + +void DCCTrack::schedulePacket(dccPacket packet) { + bool once=true; + for (const auto& driver: mD) { + if (driver->type() == RMT_MAIN || driver->type() == RMT_PROG) { + //DIAG(F("DCCTrack::schedulePacket RMT l=%d d=%x"),packet.length, packet.data[0]); + driver->schedulePacket(packet); + } + if (driver->type() == TIMERINTERRUPT && waveform && once) { + //DIAG(F("DCCTrack::schedulePacket WAVE l=%d d=%x"),packet.length, packet.data[0]); + waveform->schedulePacket(packet); + once=false; + } + } +} diff --git a/DCCTrack.h b/DCCTrack.h new file mode 100644 index 000000000..86ce98d45 --- /dev/null +++ b/DCCTrack.h @@ -0,0 +1,20 @@ + +#pragma once +#include +#include "DCCPacket.h" +#include "DCCWaveform.h" + +class DCCTrack { + public: + DCCTrack(DCCWaveform *w); + void schedulePacket(const byte buffer[], byte byteCount, byte repeats); + void schedulePacket(dccPacket packet); + inline void addDriver(MotorDriver *m) { mD.push_back(m); }; + static DCCTrack mainTrack; + static DCCTrack progTrack; + private: + DCCWaveform *waveform; + std::vectormD; +}; + + diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 1cf301152..85bf40bd3 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -22,6 +22,7 @@ #include "defines.h" #include "DCCWaveform.h" +#include "DCCTrack.h" #include "DCCTimer.h" #include "DIAG.h" #include "freeMemory.h" @@ -29,6 +30,9 @@ DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); +DCCTrack DCCTrack::mainTrack(&DCCWaveform::mainTrack); +DCCTrack DCCTrack::progTrack(&DCCWaveform::progTrack); + bool DCCWaveform::progTrackSyncMain=false; bool DCCWaveform::progTrackBoosted=false; int DCCWaveform::progTripValue=0; @@ -38,8 +42,6 @@ uint8_t DCCWaveform::trailingEdgeCounter=0; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { - mainTrack.rmtPin = new RMTChannel(21, 0, PREAMBLE_BITS_MAIN); - mainTrack.motorDriver=mainDriver; progTrack.motorDriver=progDriver; progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static @@ -62,11 +64,11 @@ volatile bool ackflag = 0; void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) { - if (mainTrack.packetPendingRMT) { - mainTrack.rmtPin->RMTfillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats); - mainTrack.packetPendingRMT=false; + //if (mainTrack.packetPendingRMT) { + // mainTrack.rmtPin->RMTfillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats); + // mainTrack.packetPendingRMT=false; // sentResetsSincePacket = 0 // later when progtrack - } + //} #ifdef SLOW_ANALOG_READ if (ackflag) { @@ -305,17 +307,17 @@ void IRAM_ATTR DCCWaveform::interrupt2() { // Wait until there is no packet pending, then make this pending void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { - if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum + if (byteCount > MAX_PACKET_SIZE+1) return; // has chksum while (packetPending); portENTER_CRITICAL(&timerMux); - byte checksum = 0; + //byte checksum = 0; for (byte b = 0; b < byteCount; b++) { - checksum ^= buffer[b]; + //checksum ^= buffer[b]; pendingPacket[b] = buffer[b]; } // buffer is MAX_PACKET_SIZE but pendingPacket is one bigger - pendingPacket[byteCount] = checksum; - pendingLength = byteCount + 1; + //pendingPacket[byteCount] = checksum; + pendingLength = byteCount /*+ 1*/; pendingRepeats = repeats; packetPending = true; packetPendingRMT = true; diff --git a/DCCWaveform.h b/DCCWaveform.h index ef8773a37..4f61fa45d 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -28,10 +28,8 @@ const int POWER_SAMPLE_ON_WAIT = 100; const int POWER_SAMPLE_OFF_WAIT = 1000; const int POWER_SAMPLE_OVERLOAD_WAIT = 20; -// Number of preamble bits. -const int PREAMBLE_BITS_MAIN = 16; -const int PREAMBLE_BITS_PROG = 22; -const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. +//const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. +#include "DCCPacket.h" // The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic // to the transform array. @@ -81,6 +79,9 @@ class DCCWaveform { } return tripmA; } + inline void schedulePacket(dccPacket packet) { + schedulePacket(packet.data, packet.length, packet.repeat); + }; void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; volatile bool packetPendingRMT; @@ -124,7 +125,6 @@ class DCCWaveform { bool isMainTrack; MotorDriver* motorDriver; - RMTChannel* rmtPin; // Transmission controller byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte transmitLength; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4fe3dd619..fba9fe35f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-motordriver-2021121-22:55" +#define GITHUB_SHA "ESP32-motordriver-2021122-23:21" diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 4269a70b7..3bc75afa8 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -69,6 +69,10 @@ class GPIOBase : public IODevice { I2CRB requestBlock; FSH *_deviceName; +#if defined(ARDUINO_ARCH_ESP32) + // workaround: Has somehow no min function for all types + static inline T min(T a, int b) { return a < b ? a : b; }; +#endif }; // Because class GPIOBase is a template, the implementation (below) must be contained within the same @@ -246,4 +250,4 @@ int GPIOBase::_read(VPIN vpin) { return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) } -#endif \ No newline at end of file +#endif diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 1b8471ed7..fa19d0159 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -22,12 +22,6 @@ #include "IO_GPIOBase.h" -#if defined(ARDUINO_ARCH_ESP32) // min seems to be missing from that package -#ifndef min -#define min(a,b) ((a)<(b)?(a):(b)) -#endif -#endif - class MCP23008 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 267524987..cb8e55de8 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -31,22 +31,33 @@ bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { + byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin, + driverType dt) { + dtype = dt; powerPin=power_pin; getFastPin(F("POWER"),powerPin,fastPowerPin); pinMode(powerPin, OUTPUT); - - signalPin=signal_pin; - getFastPin(F("SIG"),signalPin,fastSignalPin); - pinMode(signalPin, OUTPUT); - - signalPin2=signal_pin2; - if (signalPin2!=UNUSED_PIN) { - dualSignal=true; - getFastPin(F("SIG2"),signalPin2,fastSignalPin2); - pinMode(signalPin2, OUTPUT); + + if (dtype == RMT_MAIN) { + signalPin=signal_pin; +#if defined(ARDUINO_ARCH_ESP32) + rmtChannel = new RMTChannel(signalPin, 0, PREAMBLE_BITS_MAIN); +#endif + dualSignal=false; + } else if (dtype == TIMERINTERRUPT) { + signalPin=signal_pin; + getFastPin(F("SIG"),signalPin,fastSignalPin); + pinMode(signalPin, OUTPUT); + + signalPin2=signal_pin2; + if (signalPin2!=UNUSED_PIN) { + dualSignal=true; + getFastPin(F("SIG2"),signalPin2,fastSignalPin2); + pinMode(signalPin2, OUTPUT); + } else { + dualSignal=false; + } } - else dualSignal=false; brakePin=brake_pin; if (brake_pin!=UNUSED_PIN){ @@ -197,6 +208,21 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } +bool MotorDriver::schedulePacket(dccPacket packet) { + if(!rmtChannel) return true; // fake success if functionality is not there + + outQueue.push(packet); + if (outQueue.size() > 10) { + DIAG(F("Warning: outQueue > 10")); + } + return true; +} + +void MotorDriver::loop() { + if (rmtChannel && !outQueue.empty() && rmtChannel->RMTfillData(outQueue.front())) + outQueue.pop(); +} + MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName, MotorDriver *m0, MotorDriver *m1, @@ -217,4 +243,15 @@ MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName, shieldName = (FSH *)motorShieldName; } +void MotorDriverContainer::loop() { + static byte i = 0; + + // loops over MotorDrivers which have loop tasks + if (mD[i]) + if (mD[i]->type() == RMT_MAIN || mD[i]->type() == RMT_PROG) + mD[i]->loop(); + i++; + if(i > 7) i=0; +} + MotorDriverContainer mDC(MOTOR_SHIELD_TYPE); diff --git a/MotorDriver.h b/MotorDriver.h index a4a389d97..77f645abe 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -16,12 +16,20 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ + #ifndef MotorDriver_h #define MotorDriver_h #include "defines.h" #include "FSH.h" -// Virtualised Motor shield 1-track hardware Interface +#if defined(ARDUINO_ARCH_ESP32) +#include +#include "DCCRMT.h" +#endif + +// Number of preamble bits (moved here so MotorDriver and Waveform know) +const int PREAMBLE_BITS_MAIN = 16; +const int PREAMBLE_BITS_PROG = 22; #ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h #define UNUSED_PIN 127 // inside int8_t @@ -48,10 +56,13 @@ struct FASTPIN { #define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) #define isLOW(fastpin) (!isHIGH(fastpin)) +enum driverType { TIMERINTERRUPT, RMT_MAIN, RMT_PROG, DC_ENA, DC_BRAKE }; + class MotorDriver { public: MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); + byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin, + driverType t=TIMERINTERRUPT); void setPower( bool on); void setSignal( bool high); void setBrake( bool on); @@ -68,6 +79,12 @@ class MotorDriver { inline byte getFaultPin() { return faultPin; } +#if defined(ARDUINO_ARCH_ESP32) + void loop(); + inline driverType type() { return dtype; }; + bool schedulePacket(dccPacket packet); +#endif + private: void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { @@ -92,6 +109,11 @@ class MotorDriver { if (doit) __enable_irq(); } #endif +#if defined(ARDUINO_ARCH_ESP32) + RMTChannel* rmtChannel; + std::queue outQueue; + driverType dtype; +#endif }; class MotorDriverContainer { @@ -105,10 +127,15 @@ class MotorDriverContainer { MotorDriver *m5=NULL, MotorDriver *m6=NULL, MotorDriver *m7=NULL); + inline void add(byte n, MotorDriver *m) { + if (n>8) return; + mD[n] = m; + }; // void SetCapability(byte n, byte cap, char [] name); inline FSH *getMotorShieldName() { return shieldName; }; inline MotorDriver *mainTrack() { return mD[0]; }; //start fixed inline MotorDriver *progTrack() { return mD[1]; }; + void loop(); private: MotorDriver *mD[8]; From 8db937e98578c416f1996c056669b01f9fda114b Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 22 Feb 2022 01:27:27 +0000 Subject: [PATCH 058/870] Initial Track Manager code --- DCCEXParser.cpp | 6 +++ TrackManager.cpp | 137 +++++++++++++++++++++++++++++++++++++++++++++++ TrackManager.h | 58 ++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 TrackManager.cpp create mode 100644 TrackManager.h diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index f84c12353..796abd23b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -37,6 +37,7 @@ #include "CommandDistributor.h" #include "EEStore.h" #include "DIAG.h" +#include "TrackManager.h" #include //////////////////////////////////////////////////////////////////////////////// @@ -483,6 +484,11 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; return; + case 'J': // < > + if (TrackManager::parseJ(stream, params, p)) + return; + break; + case '#': // NUMBER OF LOCOSLOTS <#> StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS); return; diff --git a/TrackManager.cpp b/TrackManager.cpp new file mode 100644 index 000000000..75c758f07 --- /dev/null +++ b/TrackManager.cpp @@ -0,0 +1,137 @@ +/* + * © 2022 Chris Harlow + * All rights reserved. + * + * This file is part of Asbelos DCC 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 . + */ +#include "TrackManager.h" +#include "FSH.h" +#include "MotorDriver.h" +#include "DIAG.h" +// Virtualised Motor shield multi-track hardware Interface + +#define LOOPMODE(findmode,function) \ + for (byte t=0;t<8;t++) \ + if (trackMode[t]==findmode) \ + track[t]->function; + +const int16_t HASH_KEYWORD_PROG = -29718; +const int16_t HASH_KEYWORD_MAIN = 11339; +const int16_t HASH_KEYWORD_OFF = 9191; // TODO +const int16_t HASH_KEYWORD_DC = 9192; // TODO + +MotorDriver * TrackManager::track[MAX_TRACKS]; +int16_t TrackManager::trackMode[MAX_TRACKS]; + +void TrackManager::Setup(const FSH * shieldname, + MotorDriver * track0, MotorDriver * track1, MotorDriver * track2, + MotorDriver * track3, MotorDriver * track4, MotorDriver * track5, + MotorDriver * track6, MotorDriver * track7 ) { + (void) shieldname; // TODO + track[0]=track0; + track[1]=track1; + track[2]=track2; + track[3]=track3; + track[4]=track4; + track[5]=track5; + track[6]=track6; + track[7]=track7; + + trackMode[0]=TRACK_MODE_MAIN; + trackMode[1]=TRACK_MODE_PROG; + trackMode[2]=TRACK_MODE_OFF; + trackMode[3]=TRACK_MODE_OFF; + trackMode[4]=TRACK_MODE_OFF; + trackMode[5]=TRACK_MODE_OFF; + trackMode[6]=TRACK_MODE_OFF; + trackMode[7]=TRACK_MODE_OFF; + +} + +void TrackManager::setDCCSignal( bool on) { + LOOPMODE(TRACK_MODE_MAIN,setSignal(on)); +} + +void TrackManager::setCutout( bool on) { + (void) on; + // TODO LOOPMODE(TRACK_MODE_MAIN,setCutout(on)); +} + +void TrackManager::setPROGSignal( bool on) { + LOOPMODE(TRACK_MODE_PROG,setSignal(on)); +} + +void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { + // TODO LOOPMODE(cab,setDC(speedbyte)); +} + +bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { + if (trackToSet>=8 || track[trackToSet]==NULL) return false; + trackMode[trackToSet]=modeOrAddr; + // re-evaluate HighAccuracy mode + bool canDo=true; + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG ) + canDo &= track[t]->isPWMCapable(); + MotorDriver::usePWM=canDo; + return true; +} + +bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) +{ + int16_t mode; + + if (params==0) { // List track assignments + for (byte t=0;t<8;t++) { + if (track[t]==NULL) break; + StringFormatter::send(stream,F("\n")); + } + return true; + } + + if (params>1 && (p[1]<0 || p[1]>=MAX_TRACKS)) + return false; + + if (params==2 && p[1]==HASH_KEYWORD_MAIN) // + return setTrackMode(p[1],TRACK_MODE_MAIN); + + if (params==2 && p[1]==HASH_KEYWORD_PROG) // + return setTrackMode(p[1],TRACK_MODE_PROG); + + if (params==2 && p[1]==HASH_KEYWORD_OFF) // + return setTrackMode(p[1],TRACK_MODE_OFF); + + if (params==3 && p[1]==HASH_KEYWORD_DC) // + return setTrackMode(p[1],p[2]); + + return false; +} + + diff --git a/TrackManager.h b/TrackManager.h new file mode 100644 index 000000000..19c1b2362 --- /dev/null +++ b/TrackManager.h @@ -0,0 +1,58 @@ +/* + * © 2022 Chris Harlow + * All rights reserved. + * + * This file is part of Asbelos DCC 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 TrackManager_h +#define TrackManager_h +#include "FSH.h" +#include "MotorDriver.h" +// Virtualised Motor shield multi-track hardware Interface + + +class TrackManager { + public: + static void Setup(const FSH * shieldName, + MotorDriver * track0, + MotorDriver * track1=NULL, + MotorDriver * track2=NULL, + MotorDriver * track3=NULL, + MotorDriver * track4=NULL, + MotorDriver * track5=NULL, + MotorDriver * track6=NULL, + MotorDriver * track7=NULL + ); + + static void setDCCSignal( bool on); + static void setCutout( bool on); + static void setPROGSignal( bool on); + static void setDCSignal(int16_t cab, byte speedbyte); + static const int16_t TRACK_MODE_MAIN=32760; + static const int16_t TRACK_MODE_PROG=32761; + static const int16_t TRACK_MODE_OFF=0; + static const int16_t MAX_TRACKS=8; + static bool setTrackMode(byte track, int16_t DCaddrOrMode); + static bool parseJ(Print * stream, int16_t params, int16_t p[]); + + + + private: + static MotorDriver* track[MAX_TRACKS]; + static int16_t trackMode[MAX_TRACKS]; // dc address or TRACK_MODE_DCC, TRACK_MODE_PROG, TRACK_MODE_OFF +}; + +#endif From a7740d652d9f65a9726dc114c0ad5b880dea6f86 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Feb 2022 15:44:34 +0000 Subject: [PATCH 059/870] It builds.... massive track reorganization --- CommandDistributor.cpp | 7 +- CommandStation-EX.ino | 2 +- DCC.cpp | 355 ++----------------------------- DCC.h | 85 +------- DCCACK.cpp | 467 +++++++++++++++++++++++++++++++++++++++++ DCCACK.h | 156 ++++++++++++++ DCCEX.h | 2 +- DCCEXParser.cpp | 35 ++- DCCWaveform.cpp | 206 +++--------------- DCCWaveform.h | 102 +-------- EXRAIL2.cpp | 22 +- MotorDriver.cpp | 72 ++++++- MotorDriver.h | 27 ++- TrackManager.cpp | 68 +++++- TrackManager.h | 15 +- WiThrottle.cpp | 12 +- 16 files changed, 886 insertions(+), 747 deletions(-) create mode 100644 DCCACK.cpp create mode 100644 DCCACK.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4e5ecb9b6..c4b489d52 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -28,6 +28,7 @@ #include "defines.h" #include "DCCWaveform.h" #include "DCC.h" +#include "TrackManager.h" #if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON) // This section of CommandDistributor is simply not relevant on a uno or similar @@ -119,9 +120,9 @@ void CommandDistributor::broadcastLoco(byte slot) { } void CommandDistributor::broadcastPower() { - bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON; - bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON; - bool join=DCCWaveform::progTrackSyncMain; + bool main=TrackManager::getMainPower()==POWERMODE::ON; + bool prog=TrackManager::getProgPower()==POWERMODE::ON; + bool join=DCCWaveform::isJoined(); const FSH * reason=F(""); char state='1'; if (main && prog && join) reason=F(" JOIN"); diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 80d8b4e0c..52cf822aa 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -89,7 +89,7 @@ void setup() // Standard supported devices have pre-configured macros but custome hardware installations require // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - DCC::begin(MOTOR_SHIELD_TYPE); + TrackManager::Setup(MOTOR_SHIELD_TYPE); // Start RMFT aka EX-RAIL (ignored if no automnation) RMFT::begin(); diff --git a/DCC.cpp b/DCC.cpp index 6674fb912..77909360d 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -56,10 +56,9 @@ const byte FN_GROUP_4=0x08; const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; -byte DCC::joinRelay=UNUSED_PIN; byte DCC::globalSpeedsteps=128; -void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) { +void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); @@ -72,16 +71,9 @@ void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriv EEStore::init(); #endif - DCCWaveform::begin(mainDriver,progDriver); + DCCWaveform::begin(); } -void DCC::setJoinRelayPin(byte joinRelayPin) { - joinRelay=joinRelayPin; - if (joinRelay!=UNUSED_PIN) { - pinMode(joinRelay,OUTPUT); - digitalWrite(joinRelay,LOW); // LOW is relay disengaged - } -} void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { byte speedCode = (tSpeed & 0x7F) + tDirection * 128; @@ -296,14 +288,6 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { DCCWaveform::mainTrack.schedulePacket(b, nB, 4); } -void DCC::setProgTrackSyncMain(bool on) { - if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW); - DCCWaveform::progTrackSyncMain=on; -} -void DCC::setProgTrackBoost(bool on) { - DCCWaveform::progTrackBoosted=on; -} - FSH* DCC::getMotorShieldName() { return shieldName; } @@ -514,35 +498,35 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = { }; void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { - ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback); + DCCACK::Setup(cv, byteValue, WRITE_BYTE_PROG, callback); } void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback); + else DCCACK::Setup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback); } void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { - ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback); + DCCACK::Setup(cv, byteValue, VERIFY_BYTE_PROG, callback); } void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback); + else DCCACK::Setup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback); } void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) { if (bitNum >= 8) callback(-1); - else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback); + else DCCACK::Setup(cv, bitNum,READ_BIT_PROG, callback); } void DCC::readCV(int16_t cv, ACK_CALLBACK callback) { - ackManagerSetup(cv, 0,READ_CV_PROG, callback); + DCCACK::Setup(cv, 0,READ_CV_PROG, callback); } void DCC::getLocoId(ACK_CALLBACK callback) { - ackManagerSetup(0,0, LOCO_ID_PROG, callback); + DCCACK::Setup(0,0, LOCO_ID_PROG, callback); } void DCC::setLocoId(int id,ACK_CALLBACK callback) { @@ -551,9 +535,9 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) { return; } if (id<=HIGHEST_SHORT_ADDR) - ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback); + DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback); else - ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback); + DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback); } void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco @@ -570,8 +554,8 @@ void DCC::forgetAllLocos() { // removes all speed reminders byte DCC::loopStatus=0; void DCC::loop() { - DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks - ackManagerLoop(); // maintain prog track ack manager + DCCWaveform::loop(); // power overload checks + DCCACK::loop(); // maintain prog track ack manager issueReminders(); } @@ -695,319 +679,6 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { DCC::LOCO DCC::speedTable[MAX_LOCOS]; int DCC::nextLoco = 0; -//ACK MANAGER -ackOp const * DCC::ackManagerProg; -ackOp const * DCC::ackManagerProgStart; -byte DCC::ackManagerByte; -byte DCC::ackManagerByteVerify; -byte DCC::ackManagerStash; -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; -bool DCC::ackManagerRejoin; - -CALLBACK_STATE DCC::callbackState=READY; - -ACK_CALLBACK DCC::ackManagerCallback; - -void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) { - if (!DCCWaveform::progTrack.canMeasureCurrent()) { - callback(-2); - return; - } - - ackManagerRejoin=DCCWaveform::progTrackSyncMain; - if (ackManagerRejoin ) { - // Change from JOIN must zero resets packet. - setProgTrackSyncMain(false); - DCCWaveform::progTrack.sentResetsSincePacket = 0; - } - - DCCWaveform::progTrack.autoPowerOff=false; - if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) { - DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards - if (Diag::ACK) DIAG(F("Auto Prog power on")); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - if (MotorDriver::commonFaultPin) - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.sentResetsSincePacket = 0; - } - - ackManagerCv = cv; - ackManagerProg = program; - ackManagerProgStart = program; - ackManagerRetry = ackRetry; - ackManagerByte = byteValueOrBitnum; - ackManagerByteVerify = byteValueOrBitnum; - ackManagerBitNum=byteValueOrBitnum; - ackManagerCallback = callback; -} - -void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) { - ackManagerWord=wordval; - ackManagerSetup(0, 0, program, callback); - } - -const byte RESET_MIN=8; // tuning of reset counter before sending message - -// checkRessets return true if the caller should yield back to loop and try later. -bool DCC::checkResets(uint8_t numResets) { - return DCCWaveform::progTrack.sentResetsSincePacket < numResets; -} - -void DCC::ackManagerLoop() { - while (ackManagerProg) { - byte opcode=GETFLASH(ackManagerProg); - - // breaks from this switch will step to next prog entry - // returns from this switch will stay on same entry - // (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; - break; - case W0: // write 0 bit - case W1: // write 1 bit - { - if (checkResets(RESET_MIN)) return; - if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum); - byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum; - byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - callbackState=AFTER_WRITE; - } - break; - - case WB: // write byte - { - if (checkResets( RESET_MIN)) return; - if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); - byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - callbackState=AFTER_WRITE; - } - break; - - case VB: // Issue validate Byte packet - { - if (checkResets( RESET_MIN)) return; - if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte); - byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte}; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - } - break; - - case V0: - case V1: // Issue validate bit=0 or bit=1 packet - { - if (checkResets(RESET_MIN)) return; - if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); - byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum; - byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction }; - DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); - DCCWaveform::progTrack.setAckPending(); - } - break; - - case WACK: // wait for ack (or absence of ack) - { - byte ackState=2; // keep polling - - ackState=DCCWaveform::progTrack.getAck(); - if (ackState==2) return; // keep polling - ackReceived=ackState==1; - break; // we have a genuine ACK result - } - case ITC0: - case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK) - if (ackReceived) { - callback(opcode==ITC0?0:1); - return; - } - break; - - case ITCB: // If True callback(byte) - if (ackReceived) { - callback(ackManagerByte); - 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); - return; - } - break; - - case NAKFAIL: // If nack callback(-1) - if (!ackReceived) { - callback(-1); - return; - } - break; - - case FAIL: // callback(-1) - callback(-1); - return; - - case BIV: // ackManagerByte initial value - ackManagerByte = ackManagerByteVerify; - break; - - case STARTMERGE: - ackManagerBitNum=7; - ackManagerByte=0; - break; - - case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes) - ackManagerByte <<= 1; - // ackReceived means bit is zero. - if (!ackReceived) ackManagerByte |= 1; - ackManagerBitNum--; - break; - - case SETBIT: - ackManagerProg++; - ackManagerBitNum=GETFLASH(ackManagerProg); - break; - - case SETCV: - ackManagerProg++; - ackManagerCv=GETFLASH(ackManagerProg); - break; - - case SETBYTE: - ackManagerProg++; - ackManagerByte=GETFLASH(ackManagerProg); - break; - - case SETBYTEH: - ackManagerByte=highByte(ackManagerWord); - break; - - case SETBYTEL: - ackManagerByte=lowByte(ackManagerWord); - break; - - case STASHLOCOID: - ackManagerStash=ackManagerByte; // stash value from CV17 - break; - - case COMBINELOCOID: - // ackManagerStash is cv17, ackManagerByte is CV 18 - callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8))); - return; - - case ITSKIP: - if (!ackReceived) break; - // SKIP opcodes until SKIPTARGET found - while (opcode!=SKIPTARGET) { - ackManagerProg++; - opcode=GETFLASH(ackManagerProg); - } - break; - case SKIPTARGET: - break; - default: - DIAG(F("!! ackOp %d FAULT!!"),opcode); - callback( -1); - return; - - } // end of switch - ackManagerProg++; - } -} - -void DCC::callback(int value) { - // check for automatic retry - if (value == -1 && ackManagerRetry > 0) { - ackRetrySum ++; - LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum); - ackManagerRetry --; - ackManagerProg = ackManagerProgStart; - return; - } - - static unsigned long callbackStart; - // We are about to leave programming mode - // Rule 1: If we have written to a decoder we must maintain power for 100mS - // Rule 2: If we are re-joining the main track we must power off for 30mS - - switch (callbackState) { - case AFTER_WRITE: // first attempt to callback after a write operation - if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) { - callbackState=READY; - break; - } // lines 906-910 added. avoid wait after write. use 1 PROG - callbackStart=millis(); - callbackState=WAITING_100; - if (Diag::ACK) DIAG(F("Stable 100mS")); - break; - - case WAITING_100: // waiting for 100mS - if (millis()-callbackStart < 100) break; - // stable after power maintained for 100mS - - // If we are going to power off anyway, it doesnt matter - // but if we will keep the power on, we must off it for 30mS - if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY; - else { // Need to cycle power off and on - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); - callbackStart=millis(); - callbackState=WAITING_30; - if (Diag::ACK) DIAG(F("OFF 30mS")); - } - break; - - case WAITING_30: // waiting for 30mS with power off - if (millis()-callbackStart < 30) break; - //power has been off for 30mS - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - callbackState=READY; - break; - - case READY: // ready after read, or write after power delay and off period. - // power off if we powered it on - if (DCCWaveform::progTrack.autoPowerOff) { - if (Diag::ACK) DIAG(F("Auto Prog power off")); - DCCWaveform::progTrack.doAutoPowerOff(); - if (MotorDriver::commonFaultPin) - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - } - // Restore <1 JOIN> to state before BASELINE - if (ackManagerRejoin) { - setProgTrackSyncMain(true); - if (Diag::ACK) DIAG(F("Auto JOIN")); - } - - ackManagerProg=NULL; // no more steps to execute - if (Diag::ACK) DIAG(F("Callback(%d)"),value); - (ackManagerCallback)( value); - } -} void DCC::displayCabList(Print * stream) { diff --git a/DCC.h b/DCC.h index fbeb6034f..0effee2d8 100644 --- a/DCC.h +++ b/DCC.h @@ -36,48 +36,9 @@ #error short addr greater than 127 does not make sense #endif #endif +#include "DCCACK.h" const uint16_t LONG_ADDR_MARKER = 0x4000; -typedef void (*ACK_CALLBACK)(int16_t result); - -enum ackOp : byte -{ // Program opcodes for the ack Manager - BASELINE, // ensure enough resets sent before starting and obtain baseline current - W0, - W1, // issue write bit (0..1) packet - WB, // issue write byte packet - VB, // Issue validate Byte packet - V0, // Issue validate bit=0 packet - V1, // issue validate bit=1 packlet - WACK, // wait for ack (or absence of ack) - 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) - 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 - SETCV, // sets cv number to next prog byte - SETBYTE, // sets current byte to next prog byte - SETBYTEH, // sets current byte to word high byte - SETBYTEL, // sets current byte to word low byte - STASHLOCOID, // keeps current byte value for later - COMBINELOCOID, // combines current value with stashed value and returns it - ITSKIP, // skip to SKIPTARGET if ack true - SKIPTARGET = 0xFF // jump to target -}; - -enum CALLBACK_STATE : byte { - AFTER_WRITE, // Start callback sequence after something was written to the decoder - WAITING_100, // Waiting for 100mS of stable power - WAITING_30, // waiting to 30ms of power off gap. - READY, // Ready to complete callback - }; - // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created @@ -92,8 +53,7 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver); - static void setJoinRelayPin(byte joinRelayPin); + static void begin(const FSH * motorShieldName); static void loop(); // Public DCC API functions @@ -110,9 +70,7 @@ class DCC static void updateGroupflags(byte &flags, int16_t functionNumber); static void setAccessory(int aAdd, byte aNum, bool activate); static bool writeTextPacket(byte *b, int nBytes); - static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable - static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply - + // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 static void readCV(int16_t cv, ACK_CALLBACK callback); static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error @@ -133,13 +91,7 @@ class DCC static inline void setGlobalSpeedsteps(byte s) { globalSpeedsteps = s; }; - static inline int16_t setAckRetry(byte retry) { - ackRetry = retry; - ackRetryPSum = ackRetrySum; - ackRetrySum = 0; // reset running total - return ackRetryPSum; - }; - + struct LOCO { int loco; @@ -148,9 +100,9 @@ class DCC unsigned long functions; }; static LOCO speedTable[MAX_LOCOS]; - + static byte cv1(byte opcode, int cv); + static byte cv2(int cv); private: - static byte joinRelay; static byte loopStatus; static void setThrottle2(uint16_t cab, uint8_t speedCode); static void updateLocoReminder(int loco, byte speedCode); @@ -160,34 +112,11 @@ class DCC static FSH *shieldName; static byte globalSpeedsteps; - static byte cv1(byte opcode, int cv); - static byte cv2(int cv); + static int lookupSpeedTable(int locoId); static void issueReminders(); static void callback(int value); - // ACK MANAGER - static ackOp const *ackManagerProg; - static ackOp const *ackManagerProgStart; - static byte ackManagerByte; - static byte ackManagerByteVerify; - static byte ackManagerBitNum; - static int ackManagerCv; - static byte ackManagerRetry; - static byte ackRetry; - static int16_t ackRetrySum; - static int16_t ackRetryPSum; - static int ackManagerWord; - static byte ackManagerStash; - static bool ackReceived; - static bool ackManagerRejoin; - static ACK_CALLBACK ackManagerCallback; - static CALLBACK_STATE callbackState; - static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback); - static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback); - static void ackManagerLoop(); - static bool checkResets( uint8_t numResets); - static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) // NMRA codes # static const byte SET_SPEED = 0x3f; diff --git a/DCCACK.cpp b/DCCACK.cpp new file mode 100644 index 000000000..919889329 --- /dev/null +++ b/DCCACK.cpp @@ -0,0 +1,467 @@ +/* + * © 2021 M Steve Todd + * © 2021 Mike S + * © 2021 Fred Decker + * © 2020-2021 Harald Barth + * © 2020-2022 Chris Harlow + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 . + */ +#include "DCCACK.h" +#include "DIAG.h" +#include "DCC.h" +#include "DCCWaveform.h" +#include "TrackManager.h" + +unsigned int DCCACK::minAckPulseDuration = 4000; // micros +unsigned int DCCACK::maxAckPulseDuration = 8500; // micros + +MotorDriver * DCCACK::progDriver=NULL; +ackOp const * DCCACK::ackManagerProg; +ackOp const * DCCACK::ackManagerProgStart; +byte DCCACK::ackManagerByte; +byte DCCACK::ackManagerByteVerify; +byte DCCACK::ackManagerStash; +int DCCACK::ackManagerWord; +byte DCCACK::ackManagerRetry; +byte DCCACK::ackRetry = 2; +int16_t DCCACK::ackRetrySum; +int16_t DCCACK::ackRetryPSum; +int DCCACK::ackManagerCv; +byte DCCACK::ackManagerBitNum; +bool DCCACK::ackReceived; +bool DCCACK::ackManagerRejoin; +volatile uint8_t DCCACK::numAckGaps=0; +volatile uint8_t DCCACK::numAckSamples=0; +uint8_t DCCACK::trailingEdgeCounter=0; + + + unsigned int DCCACK::ackPulseDuration; // micros + unsigned long DCCACK::ackPulseStart; // micros + volatile bool DCCACK::ackDetected; + unsigned long DCCACK::ackCheckStart; // millis + volatile bool DCCACK::ackPending; + bool DCCACK::autoPowerOff; + int DCCACK::ackThreshold; + int DCCACK::ackLimitmA; + int DCCACK::ackMaxCurrent; + unsigned int DCCACK::ackCheckDuration; // millis + + +CALLBACK_STATE DCCACK::callbackState=READY; + +ACK_CALLBACK DCCACK::ackManagerCallback; + +void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) { + progDriver=TrackManager::getProgDriver(); + if (progDriver==NULL) { + callback(-3); // we dont have a prog track! + return; + } + if (!progDriver->canMeasureCurrent()) { + callback(-2); // our prog track cant measure current + return; + } + + ackManagerRejoin=DCCWaveform::isJoined(); + if (ackManagerRejoin ) { + // Change from JOIN must zero resets packet. + DCCWaveform::setJoin(false); + DCCWaveform::progTrack.sentResetsSincePacket = 0; + } + + autoPowerOff=false; + if (progDriver->getPower() == POWERMODE::OFF) { + autoPowerOff=true; // power off afterwards + if (Diag::ACK) DIAG(F("Auto Prog power on")); + progDriver->setPower(POWERMODE::ON); + + /* TODO !!! in MotorDriver surely! + if (MotorDriver::commonFaultPin) + DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); + DCCWaveform::progTrack.sentResetsSincePacket = 0; + **/ + } + + + ackManagerCv = cv; + ackManagerProg = program; + ackManagerProgStart = program; + ackManagerRetry = ackRetry; + ackManagerByte = byteValueOrBitnum; + ackManagerByteVerify = byteValueOrBitnum; + ackManagerBitNum=byteValueOrBitnum; + ackManagerCallback = callback; +} + +void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) { + ackManagerWord=wordval; + Setup(0, 0, program, callback); + } + +const byte RESET_MIN=8; // tuning of reset counter before sending message + +// checkRessets return true if the caller should yield back to loop and try later. +bool DCCACK::checkResets(uint8_t numResets) { + return DCCWaveform::progTrack.sentResetsSincePacket < numResets; +} +// Operations applicable to PROG track ONLY. +// (yes I know I could have subclassed the main track but...) + +void DCCACK::setAckBaseline() { + int baseline=progDriver->getCurrentRaw(); + ackThreshold= baseline + progDriver->mA2raw(ackLimitmA); + if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"), + baseline,progDriver->raw2mA(baseline), + ackThreshold,progDriver->raw2mA(ackThreshold), + minAckPulseDuration, maxAckPulseDuration); +} + +void DCCACK::setAckPending() { + ackMaxCurrent=0; + ackPulseStart=0; + ackPulseDuration=0; + ackDetected=false; + ackCheckStart=millis(); + numAckSamples=0; + numAckGaps=0; + ackPending=true; // interrupt routines will now take note +} + +byte DCCACK::getAck() { + if (ackPending) return (2); // still waiting + if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, + ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps); + if (ackDetected) return (1); // Yes we had an ack + return(0); // pending set off but not detected means no ACK. +} + + +void DCCACK::loop() { + while (ackManagerProg) { + byte opcode=GETFLASH(ackManagerProg); + + // breaks from this switch will step to next prog entry + // returns from this switch will stay on same entry + // (typically waiting for a reset counter or ACK waiting, or when all finished.) + switch (opcode) { + case BASELINE: + if (progDriver->getPower()==POWERMODE::OVERLOAD) return; + if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return; + setAckBaseline(); + callbackState=AFTER_READ; + break; + case W0: // write 0 bit + case W1: // write 1 bit + { + if (checkResets(RESET_MIN)) return; + if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum); + byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum; + byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction }; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + callbackState=AFTER_WRITE; + } + break; + + case WB: // write byte + { + if (checkResets( RESET_MIN)) return; + if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte); + byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte}; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + callbackState=AFTER_WRITE; + } + break; + + case VB: // Issue validate Byte packet + { + if (checkResets( RESET_MIN)) return; + if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte); + byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte}; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + } + break; + + case V0: + case V1: // Issue validate bit=0 or bit=1 packet + { + if (checkResets(RESET_MIN)) return; + if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum); + byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum; + byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction }; + DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS); + setAckPending(); + } + break; + + case WACK: // wait for ack (or absence of ack) + { + byte ackState=2; // keep polling + + ackState=getAck(); + if (ackState==2) return; // keep polling + ackReceived=ackState==1; + break; // we have a genuine ACK result + } + case ITC0: + case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK) + if (ackReceived) { + callback(opcode==ITC0?0:1); + return; + } + break; + + case ITCB: // If True callback(byte) + if (ackReceived) { + callback(ackManagerByte); + 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); + return; + } + break; + + case NAKFAIL: // If nack callback(-1) + if (!ackReceived) { + callback(-1); + return; + } + break; + + case FAIL: // callback(-1) + callback(-1); + return; + + case BIV: // ackManagerByte initial value + ackManagerByte = ackManagerByteVerify; + break; + + case STARTMERGE: + ackManagerBitNum=7; + ackManagerByte=0; + break; + + case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes) + ackManagerByte <<= 1; + // ackReceived means bit is zero. + if (!ackReceived) ackManagerByte |= 1; + ackManagerBitNum--; + break; + + case SETBIT: + ackManagerProg++; + ackManagerBitNum=GETFLASH(ackManagerProg); + break; + + case SETCV: + ackManagerProg++; + ackManagerCv=GETFLASH(ackManagerProg); + break; + + case SETBYTE: + ackManagerProg++; + ackManagerByte=GETFLASH(ackManagerProg); + break; + + case SETBYTEH: + ackManagerByte=highByte(ackManagerWord); + break; + + case SETBYTEL: + ackManagerByte=lowByte(ackManagerWord); + break; + + case STASHLOCOID: + ackManagerStash=ackManagerByte; // stash value from CV17 + break; + + case COMBINELOCOID: + // ackManagerStash is cv17, ackManagerByte is CV 18 + callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8))); + return; + + case ITSKIP: + if (!ackReceived) break; + // SKIP opcodes until SKIPTARGET found + while (opcode!=SKIPTARGET) { + ackManagerProg++; + opcode=GETFLASH(ackManagerProg); + } + break; + case SKIPTARGET: + break; + default: + DIAG(F("!! ackOp %d FAULT!!"),opcode); + callback( -1); + return; + + } // end of switch + ackManagerProg++; + } +} + +void DCCACK::callback(int value) { + // check for automatic retry + if (value == -1 && ackManagerRetry > 0) { + ackRetrySum ++; + LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum); + ackManagerRetry --; + ackManagerProg = ackManagerProgStart; + return; + } + + static unsigned long callbackStart; + // We are about to leave programming mode + // Rule 1: If we have written to a decoder we must maintain power for 100mS + // Rule 2: If we are re-joining the main track we must power off for 30mS + + switch (callbackState) { + case AFTER_READ: + if (ackManagerRejoin && autoPowerOff) { + progDriver->setPower(POWERMODE::OFF); + callbackStart=millis(); + callbackState=WAITING_30; + if (Diag::ACK) DIAG(F("OFF 30mS")); + } else { + callbackState=READY; + } + break; + + case AFTER_WRITE: // first attempt to callback after a write operation + if (!ackManagerRejoin && !autoPowerOff) { + callbackState=READY; + break; + } // lines 906-910 added. avoid wait after write. use 1 PROG + callbackStart=millis(); + callbackState=WAITING_100; + if (Diag::ACK) DIAG(F("Stable 100mS")); + break; + + case WAITING_100: // waiting for 100mS + if (millis()-callbackStart < 100) break; + // stable after power maintained for 100mS + + // If we are going to power off anyway, it doesnt matter + // but if we will keep the power on, we must off it for 30mS + if (autoPowerOff) callbackState=READY; + else { // Need to cycle power off and on + progDriver->setPower(POWERMODE::OFF); + callbackStart=millis(); + callbackState=WAITING_30; + if (Diag::ACK) DIAG(F("OFF 30mS")); + } + break; + + case WAITING_30: // waiting for 30mS with power off + if (millis()-callbackStart < 30) break; + //power has been off for 30mS + progDriver->setPower(POWERMODE::ON); + callbackState=READY; + break; + + case READY: // ready after read, or write after power delay and off period. + // power off if we powered it on + if (autoPowerOff) { + if (Diag::ACK) DIAG(F("Auto Prog power off")); + progDriver->setPower(POWERMODE::OFF); + /* TODO + if (MotorDriver::commonFaultPin) + DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + **/ + } + // Restore <1 JOIN> to state before BASELINE + if (ackManagerRejoin) { + DCCWaveform::setJoin(true); + if (Diag::ACK) DIAG(F("Auto JOIN")); + } + + ackManagerProg=NULL; // no more steps to execute + if (Diag::ACK) DIAG(F("Callback(%d)"),value); + (ackManagerCallback)( value); + } +} + + +void DCCACK::checkAck(byte sentResetsSincePacket) { + if (!ackPending) return; + // This function operates in interrupt() time so must be fast and can't DIAG + if (sentResetsSincePacket > 6) { //ACK timeout + ackCheckDuration=millis()-ackCheckStart; + ackPending = false; + return; + } + + int current=progDriver->getCurrentRaw(); + numAckSamples++; + if (current > ackMaxCurrent) ackMaxCurrent=current; + // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) + + if (current>ackThreshold) { + if (trailingEdgeCounter > 0) { + numAckGaps++; + trailingEdgeCounter = 0; + } + if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected + return; + } + + // not in pulse + if (ackPulseStart==0) return; // keep waiting for leading edge + + // if we reach to this point, we have + // detected trailing edge of pulse + if (trailingEdgeCounter == 0) { + ackPulseDuration=micros()-ackPulseStart; + } + + // but we do not trust it yet and return (which will force another + // measurement) and first the third time around with low current + // the ack detection will be finalized. + if (trailingEdgeCounter < 2) { + trailingEdgeCounter++; + return; + } + trailingEdgeCounter = 0; + + if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) { + ackCheckDuration=millis()-ackCheckStart; + ackDetected=true; + ackPending=false; + DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets + return; // we have a genuine ACK result + } + ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge +} + diff --git a/DCCACK.h b/DCCACK.h new file mode 100644 index 000000000..8536ce12e --- /dev/null +++ b/DCCACK.h @@ -0,0 +1,156 @@ +/* + * © 2021 M Steve Todd + * © 2021 Mike S + * © 2021 Fred Decker + * © 2020-2021 Harald Barth + * © 2020-2022 Chris Harlow + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 DCCACK_h +#define DCCACK_h + +#include "MotorDriver.h" + +typedef void (*ACK_CALLBACK)(int16_t result); + +enum ackOp : byte +{ // Program opcodes for the ack Manager + BASELINE, // ensure enough resets sent before starting and obtain baseline current + W0, + W1, // issue write bit (0..1) packet + WB, // issue write byte packet + VB, // Issue validate Byte packet + V0, // Issue validate bit=0 packet + V1, // issue validate bit=1 packlet + WACK, // wait for ack (or absence of ack) + 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) + 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 + SETCV, // sets cv number to next prog byte + SETBYTE, // sets current byte to next prog byte + SETBYTEH, // sets current byte to word high byte + SETBYTEL, // sets current byte to word low byte + STASHLOCOID, // keeps current byte value for later + COMBINELOCOID, // combines current value with stashed value and returns it + ITSKIP, // skip to SKIPTARGET if ack true + SKIPTARGET = 0xFF // jump to target +}; + +enum CALLBACK_STATE : byte { + + AFTER_READ, // Start callback sequence after something was read from the decoder + AFTER_WRITE, // Start callback sequence after something was written to the decoder + WAITING_100, // Waiting for 100mS of stable power + WAITING_30, // waiting to 30ms of power off gap. + READY, // Ready to complete callback + }; + + + +class DCCACK { + public: + static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting + static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker + static inline void setAckLimit(int mA) { + ackLimitmA = mA; + } + static inline void setMinAckPulseDuration(unsigned int i) { + minAckPulseDuration = i; + } + static inline void setMaxAckPulseDuration(unsigned int i) { + maxAckPulseDuration = i; + } + + static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback); + static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback); + static void loop(); + static bool isActive() { return ackManagerProg!=NULL;} + static inline int16_t setAckRetry(byte retry) { + ackRetry = retry; + ackRetryPSum = ackRetrySum; + ackRetrySum = 0; // reset running total + return ackRetryPSum; + }; + + + private: + static const byte SET_SPEED = 0x3f; + static const byte WRITE_BYTE = 0x7C; + static const byte VERIFY_BYTE = 0x74; + static const byte BIT_MANIPULATE = 0x78; + static const byte WRITE_BIT = 0xF0; + static const byte VERIFY_BIT = 0xE0; + static const byte BIT_ON = 0x08; + static const byte BIT_OFF = 0x00; + + static void setAckBaseline(); + static void setAckPending(); + static void callback(int value); + + static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable) + + // ACK management (Prog track only) + static void checkAck(); + static bool checkResets(uint8_t numResets); + + static volatile bool ackPending; + static volatile bool ackDetected; + static int ackThreshold; + static int ackLimitmA; + static int ackMaxCurrent; + static unsigned long ackCheckStart; // millis + static unsigned int ackCheckDuration; // millis + + static unsigned int ackPulseDuration; // micros + static unsigned long ackPulseStart; // micros + + static unsigned int minAckPulseDuration ; // micros + static unsigned int maxAckPulseDuration ; // micros + static MotorDriver* progDriver; + static volatile uint8_t numAckGaps; + static volatile uint8_t numAckSamples; + static uint8_t trailingEdgeCounter; + static ackOp const * ackManagerProg; +static ackOp const * ackManagerProgStart; +static byte ackManagerByte; +static byte ackManagerByteVerify; +static byte ackManagerStash; +static int ackManagerWord; +static byte ackManagerRetry; +static byte ackRetry; +static int16_t ackRetrySum; +static int16_t ackRetryPSum; +static int ackManagerCv; +static byte ackManagerBitNum; +static bool ackReceived; +static bool ackManagerRejoin; +static bool autoPowerOff; +static CALLBACK_STATE callbackState; +static ACK_CALLBACK ackManagerCallback; + + +}; +#endif diff --git a/DCCEX.h b/DCCEX.h index 00d89de37..a78c6c17f 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -45,5 +45,5 @@ #include "Outputs.h" #include "EXRAIL.h" #include "CommandDistributor.h" - +#include "TrackManager.h" #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 796abd23b..7c32c13ca 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -402,9 +402,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) } else break; // will reply } - if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(join); + if (main) TrackManager::setMainPower(POWERMODE::ON); + if (prog) TrackManager::setProgPower(POWERMODE::ON); + DCCWaveform::setJoin(join); CommandDistributor::broadcastPower(); return; @@ -429,12 +429,12 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) else break; // will reply } - if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); + if (main) TrackManager::setMainPower(POWERMODE::OFF); if (prog) { - DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + DCCWaveform::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off + TrackManager::setProgPower(POWERMODE::OFF); } - DCC::setProgTrackSyncMain(false); + DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); return; @@ -445,18 +445,14 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; case 'c': // SEND METER RESPONSES - // - StringFormatter::send(stream, F("\n"), DCCWaveform::mainTrack.getCurrentmA(), - DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA()); - StringFormatter::send(stream, F("\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available - return; + // No longer supported because of multiple tracks + break; case 'Q': // SENSORS Sensor::printAll(stream); return; case 's': // - StringFormatter::send(stream, F("\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON); StringFormatter::send(stream, F("\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); Turnout::printAll(stream); //send all Turnout states Output::printAll(stream); //send all Output states @@ -508,8 +504,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) case '+': // Complex Wifi interface command (not usual parse) if (atCommandCallback && !ringStream) { - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); + TrackManager::setPower(POWERMODE::OFF); atCommandCallback((HardwareSerial *)stream,com); return; } @@ -743,17 +738,17 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_ACK: // if (params >= 3) { if (p[1] == HASH_KEYWORD_LIMIT) { - DCCWaveform::progTrack.setAckLimit(p[2]); + DCCACK::setAckLimit(p[2]); LCD(1, F("Ack Limit=%dmA"), p[2]); // } else if (p[1] == HASH_KEYWORD_MIN) { - DCCWaveform::progTrack.setMinAckPulseDuration(p[2]); + DCCACK::setMinAckPulseDuration(p[2]); LCD(0, F("Ack Min=%uus"), p[2]); // } else if (p[1] == HASH_KEYWORD_MAX) { - DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]); + DCCACK::setMaxAckPulseDuration(p[2]); LCD(0, F("Ack Max=%uus"), p[2]); // } else if (p[1] == HASH_KEYWORD_RETRY) { if (p[2] >255) p[2]=3; - LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // + LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // } } else { StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off")); @@ -784,7 +779,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) #endif case HASH_KEYWORD_PROGBOOST: - DCC::setProgTrackBoost(true); + DCCWaveform::progTrackBoosted=true; return true; case HASH_KEYWORD_RESET: diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 2c5ab9a76..79c8c97c5 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -25,7 +25,9 @@ #include #include "DCCWaveform.h" +#include "TrackManager.h" #include "DCCTimer.h" +#include "DCCACK.h" #include "DIAG.h" #include "freeMemory.h" @@ -34,30 +36,17 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; bool DCCWaveform::progTrackBoosted=false; -int DCCWaveform::progTripValue=0; -volatile uint8_t DCCWaveform::numAckGaps=0; -volatile uint8_t DCCWaveform::numAckSamples=0; -uint8_t DCCWaveform::trailingEdgeCounter=0; +int16_t DCCWaveform::joinRelay=UNUSED_PIN; -void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { - mainTrack.motorDriver=mainDriver; - progTrack.motorDriver=progDriver; - progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static - mainTrack.setPowerMode(POWERMODE::OFF); - progTrack.setPowerMode(POWERMODE::OFF); - // Fault pin config for odd motor boards (example pololu) - MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) - && (mainDriver->getFaultPin() != UNUSED_PIN)); - // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work - MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); - DIAG(F("Signal pin config: %S accuracy waveform"), - MotorDriver::usePWM ? F("high") : F("normal") ); +void DCCWaveform::begin() { + + TrackManager::setPower(POWERMODE::OFF); DCCTimer::begin(DCCWaveform::interruptHandler); } -void DCCWaveform::loop(bool ackManagerActive) { - mainTrack.checkPowerOverload(false); - progTrack.checkPowerOverload(ackManagerActive); +void DCCWaveform::loop() { + DCCACK::loop(); + TrackManager::loop(DCCACK::isActive() || progTrackSyncMain || progTrackBoosted ); } #pragma GCC push_options @@ -69,8 +58,8 @@ void DCCWaveform::interruptHandler() { byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; // Set the signal state for both tracks - mainTrack.motorDriver->setSignal(sigMain); - progTrack.motorDriver->setSignal(sigProg); + TrackManager::setDCCSignal(sigMain); + TrackManager::setPROGSignal(sigProg); // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; @@ -80,10 +69,22 @@ void DCCWaveform::interruptHandler() { // WAVE_PENDING means we dont yet know what the next bit is if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); - else if (progTrack.ackPending) progTrack.checkAck(); + else DCCACK::checkAck(progTrack.sentResetsSincePacket); } #pragma GCC push_options +void DCCWaveform::setJoinRelayPin(byte joinRelayPin) { + joinRelay=joinRelayPin; + if (joinRelay!=UNUSED_PIN) { + pinMode(joinRelay,OUTPUT); + digitalWrite(joinRelay,LOW); // LOW is relay disengaged + } +} + +void DCCWaveform::setJoin(bool joined) { + progTrackSyncMain=joined; + if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW); +} // An instance of this class handles the DCC transmissions for one track. (main or prog) // Interrupts are marshalled via the statics. @@ -105,87 +106,10 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { requiredPreambles = preambleBits+1; bytes_sent = 0; bits_sent = 0; - sampleDelay = 0; - lastSampleTaken = millis(); - ackPending=false; -} - -POWERMODE DCCWaveform::getPowerMode() { - return powerMode; } -void DCCWaveform::setPowerMode(POWERMODE mode) { - powerMode = mode; - bool ison = (mode == POWERMODE::ON); - motorDriver->setPower( ison); - sentResetsSincePacket=0; -} -void DCCWaveform::checkPowerOverload(bool ackManagerActive) { - if (millis() - lastSampleTaken < sampleDelay) return; - lastSampleTaken = millis(); - int tripValue= motorDriver->getRawCurrentTripValue(); - if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted) - tripValue=progTripValue; - - // Trackname for diag messages later - const FSH*trackname = isMainTrack ? F("MAIN") : F("PROG"); - switch (powerMode) { - case POWERMODE::OFF: - sampleDelay = POWER_SAMPLE_OFF_WAIT; - break; - case POWERMODE::ON: - // Check current - lastCurrent=motorDriver->getCurrentRaw(); - if (lastCurrent < 0) { - // We have a fault pin condition to take care of - lastCurrent = -lastCurrent; - setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again - if (MotorDriver::commonFaultPin) { - if (lastCurrent <= tripValue) { - setPowerMode(POWERMODE::ON); // maybe other track - } - // Write this after the fact as we want to turn on as fast as possible - // because we don't know which output actually triggered the fault pin - DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S"), trackname); - } else { - DIAG(F("%S FAULT PIN ACTIVE - OVERLOAD"), trackname); - if (lastCurrent < tripValue) { - lastCurrent = tripValue; // exaggerate - } - } - } - if (lastCurrent < tripValue) { - sampleDelay = POWER_SAMPLE_ON_WAIT; - if(power_good_counter<100) - power_good_counter++; - else - if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; - } else { - setPowerMode(POWERMODE::OVERLOAD); - unsigned int mA=motorDriver->raw2mA(lastCurrent); - unsigned int maxmA=motorDriver->raw2mA(tripValue); - power_good_counter=0; - sampleDelay = power_sample_overload_wait; - DIAG(F("%S TRACK POWER OVERLOAD current=%d max=%d offtime=%d"), trackname, mA, maxmA, sampleDelay); - if (power_sample_overload_wait >= 10000) - power_sample_overload_wait = 10000; - else - power_sample_overload_wait *= 2; - } - break; - case POWERMODE::OVERLOAD: - // Try setting it back on after the OVERLOAD_WAIT - setPowerMode(POWERMODE::ON); - sampleDelay = POWER_SAMPLE_ON_WAIT; - // Debug code.... - DIAG(F("%S TRACK POWER RESET delay=%d"), trackname, sampleDelay); - break; - default: - sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. - } -} // For each state of the wave nextState=stateTransform[currentState] const WAVE_STATE DCCWaveform::stateTransform[]={ /* WAVE_START -> */ WAVE_PENDING, @@ -282,88 +206,6 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea sentResetsSincePacket=0; } -// Operations applicable to PROG track ONLY. -// (yes I know I could have subclassed the main track but...) - -void DCCWaveform::setAckBaseline() { - if (isMainTrack) return; - int baseline=motorDriver->getCurrentRaw(); - ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); - if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"), - baseline,motorDriver->raw2mA(baseline), - ackThreshold,motorDriver->raw2mA(ackThreshold), - minAckPulseDuration, maxAckPulseDuration); -} - -void DCCWaveform::setAckPending() { - if (isMainTrack) return; - ackMaxCurrent=0; - ackPulseStart=0; - ackPulseDuration=0; - ackDetected=false; - ackCheckStart=millis(); - numAckSamples=0; - numAckGaps=0; - ackPending=true; // interrupt routines will now take note -} - -byte DCCWaveform::getAck() { - if (ackPending) return (2); // still waiting - if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, - ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps); - if (ackDetected) return (1); // Yes we had an ack - return(0); // pending set off but not detected means no ACK. -} - #pragma GCC push_options #pragma GCC optimize ("-O3") -void DCCWaveform::checkAck() { - // This function operates in interrupt() time so must be fast and can't DIAG - if (sentResetsSincePacket > 6) { //ACK timeout - ackCheckDuration=millis()-ackCheckStart; - ackPending = false; - return; - } - - int current=motorDriver->getCurrentRaw(); - numAckSamples++; - if (current > ackMaxCurrent) ackMaxCurrent=current; - // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) - - if (current>ackThreshold) { - if (trailingEdgeCounter > 0) { - numAckGaps++; - trailingEdgeCounter = 0; - } - if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected - return; - } - - // not in pulse - if (ackPulseStart==0) return; // keep waiting for leading edge - - // if we reach to this point, we have - // detected trailing edge of pulse - if (trailingEdgeCounter == 0) { - ackPulseDuration=micros()-ackPulseStart; - } - - // but we do not trust it yet and return (which will force another - // measurement) and first the third time around with low current - // the ack detection will be finalized. - if (trailingEdgeCounter < 2) { - trailingEdgeCounter++; - return; - } - trailingEdgeCounter = 0; - - if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) { - ackCheckDuration=millis()-ackCheckStart; - ackDetected=true; - ackPending=false; - transmitRepeats=0; // shortcut remaining repeat packets - return; // we have a genuine ACK result - } - ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge -} #pragma GCC pop_options diff --git a/DCCWaveform.h b/DCCWaveform.h index 14f2f1153..2ad1c6737 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -26,10 +26,7 @@ #include "MotorDriver.h" -// Wait times for power management. Unit: milliseconds -const int POWER_SAMPLE_ON_WAIT = 100; -const int POWER_SAMPLE_OFF_WAIT = 1000; -const int POWER_SAMPLE_OVERLOAD_WAIT = 20; + // Number of preamble bits. const int PREAMBLE_BITS_MAIN = 16; @@ -45,7 +42,6 @@ enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WA // one instance is created for each track. -enum class POWERMODE : byte { OFF, ON, OVERLOAD }; const byte idlePacket[] = {0xFF, 0x00, 0xFF}; const byte resetPacket[] = {0x00, 0x00, 0x00}; @@ -53,66 +49,24 @@ const byte resetPacket[] = {0x00, 0x00, 0x00}; class DCCWaveform { public: DCCWaveform( byte preambleBits, bool isMain); - static void begin(MotorDriver * mainDriver, MotorDriver * progDriver); - static void loop(bool ackManagerActive); + static void begin(); + static void loop(); static DCCWaveform mainTrack; static DCCWaveform progTrack; void beginTrack(); - void setPowerMode(POWERMODE); - POWERMODE getPowerMode(); - void checkPowerOverload(bool ackManagerActive); - inline int get1024Current() { - if (powerMode == POWERMODE::ON) - return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue()); - return 0; - } - inline int getCurrentmA() { - if (powerMode == POWERMODE::ON) - return motorDriver->raw2mA(lastCurrent); - return 0; - } - inline int getMaxmA() { - if (maxmA == 0) { //only calculate this for first request, it doesn't change - maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc - } - return maxmA; - } - inline int getTripmA() { - if (tripmA == 0) { //only calculate this for first request, it doesn't change - tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); - } - return tripmA; - } + static void setJoin(bool join); + static bool isJoined() { return progTrackSyncMain;} + void clearRepeats() {pendingRepeats=0;} void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; - volatile byte sentResetsSincePacket; - volatile bool autoPowerOff=false; - void setAckBaseline(); //prog track only - void setAckPending(); //prog track only - byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting - static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackBoosted; // true when prog track is not current limited - inline void doAutoPowerOff() { - if (autoPowerOff) { - setPowerMode(POWERMODE::OFF); - autoPowerOff=false; - } - }; - inline bool canMeasureCurrent() { - return motorDriver->canMeasureCurrent(); - }; - inline void setAckLimit(int mA) { - ackLimitmA = mA; - } - inline void setMinAckPulseDuration(unsigned int i) { - minAckPulseDuration = i; - } - inline void setMaxAckPulseDuration(unsigned int i) { - maxAckPulseDuration = i; - } - + volatile byte sentResetsSincePacket; + static void setJoinRelayPin(byte joinRelayPin); + static int16_t joinRelay; + private: + static bool progTrackSyncMain; // true when prog track is a siding switched to main // For each state of the wave nextState=stateTransform[currentState] static const WAVE_STATE stateTransform[6]; @@ -122,10 +76,8 @@ class DCCWaveform { static void interruptHandler(); void interrupt2(); - void checkAck(); bool isMainTrack; - MotorDriver* motorDriver; // Transmission controller byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte transmitLength; @@ -138,38 +90,6 @@ class DCCWaveform { byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte pendingLength; byte pendingRepeats; - int lastCurrent; - static int progTripValue; - int maxmA; - int tripmA; - - // current sampling - POWERMODE powerMode; - unsigned long lastSampleTaken; - unsigned int sampleDelay; - // Trip current for programming track, 250mA. Change only if you really - // need to be non-NMRA-compliant because of decoders that are not either. - static const int TRIP_CURRENT_PROG=250; - unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; - unsigned int power_good_counter = 0; - - // ACK management (Prog track only) - volatile bool ackPending; - volatile bool ackDetected; - int ackThreshold; - int ackLimitmA = 60; - int ackMaxCurrent; - unsigned long ackCheckStart; // millis - unsigned int ackCheckDuration; // millis - - unsigned int ackPulseDuration; // micros - unsigned long ackPulseStart; // micros - - unsigned int minAckPulseDuration = 4000; // micros - unsigned int maxAckPulseDuration = 8500; // micros - volatile static uint8_t numAckGaps; - volatile static uint8_t numAckSamples; - static uint8_t trailingEdgeCounter; }; #endif diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index af6f0a10f..f02159492 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -50,7 +50,7 @@ #include "DCCEXParser.h" #include "Turnouts.h" #include "CommandDistributor.h" - +#include "TrackManager.h" // Command parsing keywords const int16_t HASH_KEYWORD_EXRAIL=15435; @@ -465,10 +465,14 @@ void RMFT2::createNewTask(int route, uint16_t cab) { 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); + /* TODO..... + power on appropriate track if DC or main if dcc + if (TrackManager::getMainPowerMode()==POWERMODE::OFF) { + TrackManager::setMainPower(POWERMODE::ON); CommandDistributor::broadcastPower(); } + **********/ + DCC::setThrottle(loco,speed, forward^invert); speedo=speed; } @@ -648,9 +652,8 @@ void RMFT2::loop2() { break; case OPCODE_POWEROFF: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF); - DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF); - DCC::setProgTrackSyncMain(false); + TrackManager::setPower(POWERMODE::OFF); + DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); break; @@ -789,14 +792,13 @@ void RMFT2::loop2() { return; case OPCODE_JOIN: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(true); + TrackManager::setPower(POWERMODE::ON); + DCCWaveform::setJoin(true); CommandDistributor::broadcastPower(); break; case OPCODE_UNJOIN: - DCC::setProgTrackSyncMain(false); + DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); break; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index d65d2ea93..79167bcff 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -82,6 +82,12 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 else DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"), currentPin-A0, senseOffset,rawCurrentTripValue); + + // prepare values for current detection + sampleDelay = 0; + lastSampleTaken = millis(); + progTripValue = mA2raw(TRIP_CURRENT_PROG); + } bool MotorDriver::isPWMCapable() { @@ -89,7 +95,8 @@ bool MotorDriver::isPWMCapable() { } -void MotorDriver::setPower(bool on) { +void MotorDriver::setPower(POWERMODE mode) { + bool on=mode==POWERMODE::ON; if (on) { // toggle brake before turning power on - resets overcurrent error // on the Pololu board if brake is wired to ^D2. @@ -98,6 +105,7 @@ void MotorDriver::setPower(bool on) { setHIGH(fastPowerPin); } else setLOW(fastPowerPin); + powerMode=mode; } // setBrake applies brake if on == true. So to get @@ -190,3 +198,65 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res result.maskLOW = ~result.maskHIGH; // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } + +void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { + if (millis() - lastSampleTaken < sampleDelay) return; + lastSampleTaken = millis(); + int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); + + // Trackname for diag messages later + switch (powerMode) { + case POWERMODE::OFF: + sampleDelay = POWER_SAMPLE_OFF_WAIT; + break; + case POWERMODE::ON: + // Check current + lastCurrent=getCurrentRaw(); + if (lastCurrent < 0) { + // We have a fault pin condition to take care of + lastCurrent = -lastCurrent; + setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again + if (commonFaultPin) { + if (lastCurrent <= tripValue) { + setPower(POWERMODE::ON); // maybe other track + } + // Write this after the fact as we want to turn on as fast as possible + // because we don't know which output actually triggered the fault pin + DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %d"), trackno); + } else { + DIAG(F("TRACK %d FAULT PIN ACTIVE - OVERLOAD"), trackno); + if (lastCurrent < tripValue) { + lastCurrent = tripValue; // exaggerate + } + } + } + if (lastCurrent < tripValue) { + sampleDelay = POWER_SAMPLE_ON_WAIT; + if(power_good_counter<100) + power_good_counter++; + else + if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; + } else { + setPower(POWERMODE::OVERLOAD); + unsigned int mA=raw2mA(lastCurrent); + unsigned int maxmA=raw2mA(tripValue); + power_good_counter=0; + sampleDelay = power_sample_overload_wait; + DIAG(F("TRACK %d POWER OVERLOAD current=%d max=%d offtime=%d"), trackno, mA, maxmA, sampleDelay); + if (power_sample_overload_wait >= 10000) + power_sample_overload_wait = 10000; + else + power_sample_overload_wait *= 2; + } + break; + case POWERMODE::OVERLOAD: + // Try setting it back on after the OVERLOAD_WAIT + setPower(POWERMODE::ON); + sampleDelay = POWER_SAMPLE_ON_WAIT; + // Debug code.... + DIAG(F("TRACK %d POWER RESET delay=%d"), trackno, sampleDelay); + break; + default: + sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. + } +} diff --git a/MotorDriver.h b/MotorDriver.h index 62d9cb5e0..105bff657 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -43,11 +43,15 @@ struct FASTPIN { }; #endif +enum class POWERMODE : byte { OFF, ON, OVERLOAD }; + class MotorDriver { public: + MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); - virtual void setPower( bool on); + virtual void setPower( POWERMODE mode); + virtual POWERMODE getPower() { return powerMode;} virtual void setSignal( bool high); virtual void setBrake( bool on); virtual int getCurrentRaw(); @@ -63,6 +67,7 @@ class MotorDriver { inline byte getFaultPin() { return faultPin; } + void checkPowerOverload(bool useProgLimit, byte trackno); private: void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { @@ -76,6 +81,26 @@ class MotorDriver { int senseOffset; unsigned int tripMilliamps; int rawCurrentTripValue; + // current sampling + POWERMODE powerMode; + unsigned long lastSampleTaken; + unsigned int sampleDelay; + int progTripValue; + int lastCurrent; + int maxmA; + int tripmA; + + // Wait times for power management. Unit: milliseconds + static const int POWER_SAMPLE_ON_WAIT = 100; + static const int POWER_SAMPLE_OFF_WAIT = 1000; + static const int POWER_SAMPLE_OVERLOAD_WAIT = 20; + + // Trip current for programming track, 250mA. Change only if you really + // need to be non-NMRA-compliant because of decoders that are not either. + static const int TRIP_CURRENT_PROG=250; + unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + unsigned int power_good_counter = 0; + #if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) static bool disableInterrupts() { uint32_t primask; diff --git a/TrackManager.cpp b/TrackManager.cpp index 75c758f07..10b61c2f5 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -19,6 +19,7 @@ */ #include "TrackManager.h" #include "FSH.h" +#include "DCCWaveform.h" #include "MotorDriver.h" #include "DIAG.h" // Virtualised Motor shield multi-track hardware Interface @@ -35,6 +36,8 @@ const int16_t HASH_KEYWORD_DC = 9192; // TODO MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackMode[MAX_TRACKS]; + POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; + void TrackManager::Setup(const FSH * shieldname, MotorDriver * track0, MotorDriver * track1, MotorDriver * track2, @@ -50,15 +53,19 @@ void TrackManager::Setup(const FSH * shieldname, track[6]=track6; track[7]=track7; - trackMode[0]=TRACK_MODE_MAIN; - trackMode[1]=TRACK_MODE_PROG; - trackMode[2]=TRACK_MODE_OFF; - trackMode[3]=TRACK_MODE_OFF; - trackMode[4]=TRACK_MODE_OFF; - trackMode[5]=TRACK_MODE_OFF; - trackMode[6]=TRACK_MODE_OFF; - trackMode[7]=TRACK_MODE_OFF; - + setTrackMode(0,TRACK_MODE_MAIN); + setTrackMode(1,TRACK_MODE_PROG); + setTrackMode(2,TRACK_MODE_OFF); + setTrackMode(3,TRACK_MODE_OFF); + setTrackMode(4,TRACK_MODE_OFF); + setTrackMode(5,TRACK_MODE_OFF); + setTrackMode(6,TRACK_MODE_OFF); + setTrackMode(7,TRACK_MODE_OFF); + // TODO Fault pin config for odd motor boards (example pololu) + // MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) + // && (mainDriver->getFaultPin() != UNUSED_PIN)); + DIAG(F("Signal pin config: %S accuracy waveform"), + MotorDriver::usePWM ? F("high") : F("normal") ); } void TrackManager::setDCCSignal( bool on) { @@ -80,11 +87,16 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { if (trackToSet>=8 || track[trackToSet]==NULL) return false; + if (modeOrAddr==TRACK_MODE_PROG) { + // only allow 1 track to be prog + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_PROG) trackMode[t]=TRACK_MODE_OFF; + } trackMode[trackToSet]=modeOrAddr; // re-evaluate HighAccuracy mode bool canDo=true; for (byte t=0;t<8;t++) - if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG ) + if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG) canDo &= track[t]->isPWMCapable(); MotorDriver::usePWM=canDo; return true; @@ -92,7 +104,6 @@ bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) { - int16_t mode; if (params==0) { // List track assignments for (byte t=0;t<8;t++) { @@ -134,4 +145,39 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) return false; } +byte TrackManager::nextCycleTrack=MAX_TRACKS; + +void TrackManager::loop(bool dontLimitProg) { + nextCycleTrack++; + if (nextCycleTrack>=MAX_TRACKS) nextCycleTrack=0; + if (track[nextCycleTrack]==NULL) return; + MotorDriver * motorDriver=track[nextCycleTrack]; + bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG; + motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack); +} +MotorDriver * TrackManager::getProgDriver() { + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_PROG) return track[t]; + return NULL; +} +void TrackManager::setPower2(bool setProg,POWERMODE mode) { + if (setProg) { + LOOPMODE(TRACK_MODE_PROG,setPower(mode)) + } + else { + mainPowerGuess=mode; + for (byte t=0;t<8;t++) + if (track[t] + && trackMode[t]!=TRACK_MODE_OFF + && trackMode[t]!=TRACK_MODE_PROG + ) track[t]->setPower(mode); + } +} + POWERMODE TrackManager::getProgPower() { + for (byte t=0;t<8;t++) + if (trackMode[t]==TRACK_MODE_PROG) + return track[t]->getPower(); + return POWERMODE::OFF; + } + diff --git a/TrackManager.h b/TrackManager.h index 19c1b2362..fe0642237 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -41,16 +41,27 @@ class TrackManager { static void setCutout( bool on); static void setPROGSignal( bool on); static void setDCSignal(int16_t cab, byte speedbyte); + static MotorDriver * getProgDriver(); + static void setPower2(bool progTrack,POWERMODE mode); + static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);} + static void setMainPower(POWERMODE mode) {setPower2(false,mode);} + static void setProgPower(POWERMODE mode) {setPower2(true,mode);} + static const int16_t TRACK_MODE_MAIN=32760; static const int16_t TRACK_MODE_PROG=32761; static const int16_t TRACK_MODE_OFF=0; static const int16_t MAX_TRACKS=8; static bool setTrackMode(byte track, int16_t DCaddrOrMode); static bool parseJ(Print * stream, int16_t params, int16_t p[]); - - + static void loop(bool dontLimitProg); + static POWERMODE getMainPower() {return mainPowerGuess;} + static POWERMODE getProgPower(); + private: + static byte nextCycleTrack; + static POWERMODE mainPowerGuess; + static MotorDriver* track[MAX_TRACKS]; static int16_t trackMode[MAX_TRACKS]; // dc address or TRACK_MODE_DCC, TRACK_MODE_PROG, TRACK_MODE_OFF }; diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 312cf9369..82c10640a 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -55,6 +55,7 @@ #include "version.h" #include "EXRAIL2.h" #include "CommandDistributor.h" +#include "TrackManager.h" #define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;locomultithrottle(stashStream, (byte *)addcmd); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away + TrackManager::setMainPower(POWERMODE::ON); + DCCWaveform::setJoin(true); // <1 JOIN> so we can drive loco away } } stashStream->commit(); From 1afb4753ec66feffc397ecb32d778fc829a3e779 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Feb 2022 16:03:15 +0000 Subject: [PATCH 060/870] Cleaner ack check And drop CPU specific stuff no longer needed. --- DCCACK.cpp | 2 +- MotorDriver.cpp | 30 +++++++++++++++--------------- MotorDriver.h | 1 + 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index 919889329..8afe0f9cf 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -423,7 +423,7 @@ void DCCACK::checkAck(byte sentResetsSincePacket) { return; } - int current=progDriver->getCurrentRaw(); + int current=progDriver->getCurrentRawInInterrupt(); numAckSamples++; if (current > ackMaxCurrent) ackMaxCurrent=current; // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 79167bcff..c441f8a7b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -156,28 +156,28 @@ bool MotorDriver::canMeasureCurrent() { int MotorDriver::getCurrentRaw() { if (currentPin==UNUSED_PIN) return 0; int current; -#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) - bool irq = disableInterrupts(); + // This function should NOT be called in an interruot so we + // dont need to fart about saving and restoring CPU specific + // interrupt registers. + noInterrupts(); current = analogRead(currentPin)-senseOffset; - enableInterrupts(irq); -#else // Uno, Mega and all the TEENSY3* but not TEENSY4* - unsigned char sreg_backup; - sreg_backup = SREG; /* save interrupt enable/disable state */ - cli(); - current = analogRead(currentPin)-senseOffset; -#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36) - overflow_count = 0; -#endif - if (sreg_backup & 128) sei(); /* restore interrupt state */ -#endif // outer # + interrupts(); if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin)) return (current == 0 ? -1 : -current); return current; - // IMPORTANT: This function can be called in Interrupt() time within the 56uS timer + +} + +int MotorDriver::getCurrentRawInInterrupt() { + + // IMPORTANT: This function must be called in Interrupt() time within the 56uS timer // The default analogRead takes ~100uS which is catastrphic // so DCCTimer has set the sample time to be much faster. -} + + if (currentPin==UNUSED_PIN) return 0; + return analogRead(currentPin)-senseOffset; +} unsigned int MotorDriver::raw2mA( int raw) { return (unsigned int)(raw * senseFactor); diff --git a/MotorDriver.h b/MotorDriver.h index 105bff657..80453eaf5 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -55,6 +55,7 @@ class MotorDriver { virtual void setSignal( bool high); virtual void setBrake( bool on); virtual int getCurrentRaw(); + virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); virtual int mA2raw( unsigned int mA); inline int getRawCurrentTripValue() { From 74bbe595fc1ccb7493c27203f880172968abd247 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Feb 2022 16:21:45 +0000 Subject: [PATCH 061/870] DC hooks still requires pin jiggling --- DCC.cpp | 2 ++ MotorDriver.cpp | 5 +++++ MotorDriver.h | 1 + TrackManager.cpp | 2 +- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/DCC.cpp b/DCC.cpp index 77909360d..6d19a26f4 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -35,6 +35,7 @@ #include "IODevice.h" #include "EXRAIL2.h" #include "CommandDistributor.h" +#include "TrackManager.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -78,6 +79,7 @@ void DCC::begin(const FSH * motorShieldName) { void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { byte speedCode = (tSpeed & 0x7F) + tDirection * 128; setThrottle2(cab, speedCode); + TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr // retain speed for loco reminders updateLocoReminder(cab, speedCode ); } diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c441f8a7b..7f2d3e12b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -169,6 +169,11 @@ int MotorDriver::getCurrentRaw() { } +void MotorDriver::setDCSignal(byte speedcode) { + // spedcxode is a dcc speed /direction + // TODO jiggle the DC speed pin PWMs + +} int MotorDriver::getCurrentRawInInterrupt() { // IMPORTANT: This function must be called in Interrupt() time within the 56uS timer diff --git a/MotorDriver.h b/MotorDriver.h index 80453eaf5..ca30706e0 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -54,6 +54,7 @@ class MotorDriver { virtual POWERMODE getPower() { return powerMode;} virtual void setSignal( bool high); virtual void setBrake( bool on); + virtual void setDCSignal(byte speedByte); virtual int getCurrentRaw(); virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); diff --git a/TrackManager.cpp b/TrackManager.cpp index 10b61c2f5..8235984e4 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -82,7 +82,7 @@ void TrackManager::setPROGSignal( bool on) { } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { - // TODO LOOPMODE(cab,setDC(speedbyte)); + LOOPMODE(cab,setDCSignal(speedbyte)); } bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { From 99e636974a43acb59c29b2ad7d17301e4d8e07f9 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 24 Feb 2022 11:50:22 +0000 Subject: [PATCH 062/870] SET_TRACK_DC/SET_TRACK_DCC EXRAIL macros for TrackManager SET_TRACK_DC sets the track to DC using the cab address of the current loco so you can drive.. --- EXRAIL2.cpp | 8 +++++++- EXRAIL2.h | 2 +- EXRAIL2MacroReset.h | 4 ++++ EXRAILMacros.h | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index f02159492..2324ea8eb 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -656,7 +656,13 @@ void RMFT2::loop2() { DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); break; - + + case OPCODE_SET_TRACK: + // operand is track id and +128= Use my loco for DC + TrackManager::setTrackMode(operand & 0x0F, + operand>=128 ? loco : TrackManager::TRACK_MODE_MAIN); + break; + case OPCODE_RESUME: pausingTask=NULL; driveLoco(speedo); diff --git a/EXRAIL2.h b/EXRAIL2.h index b44ada684..29b981686 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -48,7 +48,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, OPCODE_PRINT,OPCODE_DCCACTIVATE, OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT, - OPCODE_ROSTER, + OPCODE_ROSTER,OPCODE_SET_TRACK, OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,OPCODE_ENDTASK,OPCODE_ENDEXRAIL }; diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 852eb7012..962322a3b 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -98,6 +98,8 @@ #undef SERVO2 #undef SERVO_TURNOUT #undef SET +#undef SET_TRACK_DC +#undef SET_TRACK_DCC #undef SETLOCO #undef SIGNAL #undef SPEED @@ -187,6 +189,8 @@ #define SERVO2(id,position,duration) #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) #define SET(pin) +#define SET_TRACK_DC(trackid) +#define SET_TRACK_DCC(trackid) #define SETLOCO(loco) #define SIGNAL(redpin,amberpin,greenpin) #define SPEED(speed) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 7efb44508..7e70e83bf 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -236,6 +236,8 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L), #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile), #define SET(pin) OPCODE_SET,V(pin), +#define SET_TRACK_DC(track) OPCODE_SET_TRACK,V(128+track), +#define SET_TRACK_DCC(track) OPCODE_SET_TRACK,V(track), #define SETLOCO(loco) OPCODE_SETLOCO,V(loco), #define SIGNAL(redpin,amberpin,greenpin) #define SPEED(speed) OPCODE_SPEED,V(speed), From b29b8c999eaa67ad648fa6f09c5c08afeec75a98 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 24 Feb 2022 11:58:40 +0000 Subject: [PATCH 063/870] keyword values --- TrackManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 8235984e4..21f4dda04 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -31,8 +31,8 @@ const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_MAIN = 11339; -const int16_t HASH_KEYWORD_OFF = 9191; // TODO -const int16_t HASH_KEYWORD_DC = 9192; // TODO +const int16_t HASH_KEYWORD_OFF = 22479; +const int16_t HASH_KEYWORD_DC = 2183; MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackMode[MAX_TRACKS]; From 4f781074eb2d0fbdc25d92e20c4d53be6ea0216f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 28 Feb 2022 09:32:26 +0000 Subject: [PATCH 064/870] tidy and shorten loops --- TrackManager.cpp | 118 +++++++++++++++++++++++++---------------------- TrackManager.h | 2 + 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 21f4dda04..5c67f8315 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -23,9 +23,10 @@ #include "MotorDriver.h" #include "DIAG.h" // Virtualised Motor shield multi-track hardware Interface - -#define LOOPMODE(findmode,function) \ - for (byte t=0;t<8;t++) \ +#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++) + +#define APPLY_BY_MODE(findmode,function) \ + FOR_EACH_TRACK(t) \ if (trackMode[t]==findmode) \ track[t]->function; @@ -36,66 +37,72 @@ const int16_t HASH_KEYWORD_DC = 2183; MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackMode[MAX_TRACKS]; - POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; +POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; +byte TrackManager::lastTrack=0; - +// The setup call is done this way so that the tracks can be in a list +// from the config... the tracks default to NULL in the declaration void TrackManager::Setup(const FSH * shieldname, MotorDriver * track0, MotorDriver * track1, MotorDriver * track2, MotorDriver * track3, MotorDriver * track4, MotorDriver * track5, MotorDriver * track6, MotorDriver * track7 ) { (void) shieldname; // TODO - track[0]=track0; - track[1]=track1; - track[2]=track2; - track[3]=track3; - track[4]=track4; - track[5]=track5; - track[6]=track6; - track[7]=track7; - + addTrack(0,track0); + addTrack(1,track1); + addTrack(2,track2); + addTrack(3,track3); + addTrack(4,track4); + addTrack(5,track5); + addTrack(6,track6); + addTrack(7,track7); + + // Default the first 2 tracks (which mat be null) and perform HA waveform check. setTrackMode(0,TRACK_MODE_MAIN); setTrackMode(1,TRACK_MODE_PROG); - setTrackMode(2,TRACK_MODE_OFF); - setTrackMode(3,TRACK_MODE_OFF); - setTrackMode(4,TRACK_MODE_OFF); - setTrackMode(5,TRACK_MODE_OFF); - setTrackMode(6,TRACK_MODE_OFF); - setTrackMode(7,TRACK_MODE_OFF); - // TODO Fault pin config for odd motor boards (example pololu) + + // TODO Fault pin config for odd motor boards (example pololu) // MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) // && (mainDriver->getFaultPin() != UNUSED_PIN)); DIAG(F("Signal pin config: %S accuracy waveform"), MotorDriver::usePWM ? F("high") : F("normal") ); } - + +void TrackManager::addTrack(byte t, MotorDriver* driver) { + track[t]=driver; + trackMode[t]=TRACK_MODE_OFF; + if (driver) lastTrack=t; +} + void TrackManager::setDCCSignal( bool on) { - LOOPMODE(TRACK_MODE_MAIN,setSignal(on)); + APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); } void TrackManager::setCutout( bool on) { (void) on; - // TODO LOOPMODE(TRACK_MODE_MAIN,setCutout(on)); + // TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on)); } void TrackManager::setPROGSignal( bool on) { - LOOPMODE(TRACK_MODE_PROG,setSignal(on)); + APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { - LOOPMODE(cab,setDCSignal(speedbyte)); + APPLY_BY_MODE(cab,setDCSignal(speedbyte)); } bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { - if (trackToSet>=8 || track[trackToSet]==NULL) return false; + if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; if (modeOrAddr==TRACK_MODE_PROG) { // only allow 1 track to be prog - for (byte t=0;t<8;t++) + FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_PROG) trackMode[t]=TRACK_MODE_OFF; } trackMode[trackToSet]=modeOrAddr; + // re-evaluate HighAccuracy mode + // We can only do this is all main and prog tracks agree bool canDo=true; - for (byte t=0;t<8;t++) + FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG) canDo &= track[t]->isPWMCapable(); MotorDriver::usePWM=canDo; @@ -106,24 +113,24 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) { if (params==0) { // List track assignments - for (byte t=0;t<8;t++) { - if (track[t]==NULL) break; - StringFormatter::send(stream,F("\n")); - } + FOR_EACH_TRACK(t) + if (track[t]!=NULL) { + StringFormatter::send(stream,F("\n")); + } return true; } @@ -149,7 +156,7 @@ byte TrackManager::nextCycleTrack=MAX_TRACKS; void TrackManager::loop(bool dontLimitProg) { nextCycleTrack++; - if (nextCycleTrack>=MAX_TRACKS) nextCycleTrack=0; + if (nextCycleTrack>lastTrack) nextCycleTrack=0; if (track[nextCycleTrack]==NULL) return; MotorDriver * motorDriver=track[nextCycleTrack]; bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG; @@ -157,27 +164,28 @@ void TrackManager::loop(bool dontLimitProg) { } MotorDriver * TrackManager::getProgDriver() { - for (byte t=0;t<8;t++) + FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_PROG) return track[t]; return NULL; } void TrackManager::setPower2(bool setProg,POWERMODE mode) { if (setProg) { - LOOPMODE(TRACK_MODE_PROG,setPower(mode)) + APPLY_BY_MODE(TRACK_MODE_PROG,setPower(mode)) } else { mainPowerGuess=mode; - for (byte t=0;t<8;t++) + FOR_EACH_TRACK(t) if (track[t] && trackMode[t]!=TRACK_MODE_OFF && trackMode[t]!=TRACK_MODE_PROG ) track[t]->setPower(mode); } } - POWERMODE TrackManager::getProgPower() { - for (byte t=0;t<8;t++) - if (trackMode[t]==TRACK_MODE_PROG) + +POWERMODE TrackManager::getProgPower() { + FOR_EACH_TRACK(t) + if (trackMode[t]==TRACK_MODE_PROG) return track[t]->getPower(); - return POWERMODE::OFF; + return POWERMODE::OFF; } diff --git a/TrackManager.h b/TrackManager.h index fe0642237..f861edb66 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -59,6 +59,8 @@ class TrackManager { private: + static void addTrack(byte t, MotorDriver* driver); + static byte lastTrack; static byte nextCycleTrack; static POWERMODE mainPowerGuess; From dd9152864bbed583f81f5294fccfdacabeccccf8 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 28 Feb 2022 10:38:26 +0000 Subject: [PATCH 065/870] Missing DCC startup!!! + EXRAIL POWERON catchup --- EXRAIL2.cpp | 6 ++++++ EXRAIL2.h | 2 +- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 1 + TrackManager.cpp | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 2324ea8eb..acdd5ba07 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -803,6 +803,12 @@ void RMFT2::loop2() { CommandDistributor::broadcastPower(); break; + case OPCODE_POWERON: + TrackManager::setMainPower(POWERMODE::ON); + DCCWaveform::setJoin(false); + CommandDistributor::broadcastPower(); + break; + case OPCODE_UNJOIN: DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); diff --git a/EXRAIL2.h b/EXRAIL2.h index 29b981686..3e855d9a1 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -44,7 +44,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_POWEROFF, + OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, OPCODE_PRINT,OPCODE_DCCACTIVATE, OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,OPCODE_IFGTE,OPCODE_IFLT, diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 962322a3b..5ef34c625 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -79,6 +79,7 @@ #undef PRINT #undef POM #undef POWEROFF +#undef POWERON #undef READ_LOCO #undef RED #undef RESERVE @@ -170,6 +171,7 @@ #define PRINT(msg) #define POM(cv,value) #define POWEROFF +#define POWERON #define READ_LOCO #define RED(signal_id) #define RESERVE(blockid) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 7e70e83bf..2a9057b04 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -216,6 +216,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), #define POWEROFF OPCODE_POWEROFF,0,0, +#define POWERON OPCODE_POWERON,0,0, #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), #define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0, #define RED(signal_id) OPCODE_RED,V(signal_id), diff --git a/TrackManager.cpp b/TrackManager.cpp index 5c67f8315..bfb61993e 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -20,6 +20,7 @@ #include "TrackManager.h" #include "FSH.h" #include "DCCWaveform.h" +#include "DCC.h" #include "MotorDriver.h" #include "DIAG.h" // Virtualised Motor shield multi-track hardware Interface @@ -65,6 +66,7 @@ void TrackManager::Setup(const FSH * shieldname, // && (mainDriver->getFaultPin() != UNUSED_PIN)); DIAG(F("Signal pin config: %S accuracy waveform"), MotorDriver::usePWM ? F("high") : F("normal") ); + DCC::begin(shieldname); } void TrackManager::addTrack(byte t, MotorDriver* driver) { From 6fc223d80b2c5183a68d2863073cfddb15b7f893 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 1 Mar 2022 12:52:25 +0000 Subject: [PATCH 066/870] Timer stuff with incomplete teensy --- CommandStation-EX.ino | 2 +- DCCEX.h | 4 +- DCCEXParser.cpp | 4 +- DCCTimer.h | 57 ++++++++++-- DCCTimerAVR.cpp | 113 +++++++++++++++++++++++ DCCTimer.cpp => DCCTimerMEGAAVR.cpp | 134 +++------------------------- DCCTimerTEENSY.cpp | 122 +++++++++++++++++++++++++ DCCWaveform.cpp | 4 +- MotorDriver.cpp | 3 - TrackManager.cpp | 3 +- freeMemory.cpp | 112 ----------------------- freeMemory.h | 25 ------ 12 files changed, 305 insertions(+), 278 deletions(-) create mode 100644 DCCTimerAVR.cpp rename DCCTimer.cpp => DCCTimerMEGAAVR.cpp (50%) create mode 100644 DCCTimerTEENSY.cpp delete mode 100644 freeMemory.cpp delete mode 100644 freeMemory.h diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 52cf822aa..8d650f95f 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -147,7 +147,7 @@ void loop() // Report any decrease in memory (will automatically trigger on first call) static int ramLowWatermark = __INT_MAX__; // replaced on first loop - int freeNow = minimumFreeMemory(); + int freeNow = DCCTimer::getMinimumFreeMemory(); if (freeNow < ramLowWatermark) { ramLowWatermark = freeNow; LCD(3,F("Free RAM=%5db"), ramLowWatermark); diff --git a/DCCEX.h b/DCCEX.h index a78c6c17f..9c0b1304f 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -38,12 +38,12 @@ #endif #include "LCD_Implementation.h" #include "LCN.h" -#include "freeMemory.h" #include "IODevice.h" #include "Turnouts.h" #include "Sensors.h" #include "Outputs.h" #include "EXRAIL.h" #include "CommandDistributor.h" -#include "TrackManager.h" +#include "TrackManager.h" +#include "DCCTimer.h" #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 7c32c13ca..ae2a10940 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -30,7 +30,6 @@ #include "Turnouts.h" #include "Outputs.h" #include "Sensors.h" -#include "freeMemory.h" #include "GITHUB_SHA.h" #include "version.h" #include "defines.h" @@ -38,6 +37,7 @@ #include "EEStore.h" #include "DIAG.h" #include "TrackManager.h" +#include "DCCTimer.h" #include //////////////////////////////////////////////////////////////////////////////// @@ -732,7 +732,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) return true; case HASH_KEYWORD_RAM: // - StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory()); + StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory()); break; case HASH_KEYWORD_ACK: // diff --git a/DCCTimer.h b/DCCTimer.h index 8f8f5bcb0..e4ebf5ee1 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -20,6 +20,34 @@ * along with CommandStation. If not, see . */ +/* There are several different implementations of this class which the compiler will select + according to the hardware. + */ + +/* This timer class is used to manage the single timer required to handle the DCC waveform. + * All timer access comes through this class so that it can be compiled for + * various hardware CPU types. + * + * DCCEX works on a single timer interrupt at a regular 58uS interval. + * The DCCWaveform class generates the signals to the motor shield + * based on this timer. + * + * If the motor drivers are BOTH configured to use the correct 2 pins for the architecture, + * (see isPWMPin() function. ) + * then this allows us to use a hardware driven pin switching arrangement which is + * achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on + * the required pin state. (see setPWM()) + * This is more accurate than the software interrupt but at the expense of + * limiting the choice of available pins. + * Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM... + * Other shields may be jumpered to PWM pins or run directly using the software interrupt. + * + * Because the PWM-based waveform is effectively set half a cycle after the software version, + * it is not acceptable to drive the two tracks on different methiods or it would cause + * problems for <1 JOIN> etc. + * + */ + #ifndef DCCTimer_h #define DCCTimer_h #include "Arduino.h" @@ -32,11 +60,30 @@ class DCCTimer { static void getSimulatedMacAddress(byte mac[6]); static bool isPWMPin(byte pin); static void setPWM(byte pin, bool high); -#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__)) - static void read_mac(byte mac[6]); - static void read(uint8_t word, uint8_t *mac, uint8_t offset); -#endif - private: + +// Update low ram level. Allow for extra bytes to be specified +// by estimation or inspection, that may be used by other +// called subroutines. Must be called with interrupts disabled. +// +// Although __brkval may go up and down as heap memory is allocated +// and freed, this function records only the worst case encountered. +// So even if all of the heap is freed, the reported minimum free +// memory will not increase. +// +static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { + int spare = freeMemory()-extraBytes; + if (spare < 0) spare = 0; + if (spare < minimum_free_memory) minimum_free_memory = spare; +} + + static int getMinimumFreeMemory(); + +private: + static int freeMemory(); + static volatile int minimum_free_memory; + static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle + static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1; + }; #endif diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp new file mode 100644 index 000000000..ff0143b2d --- /dev/null +++ b/DCCTimerAVR.cpp @@ -0,0 +1,113 @@ +/* + * © 2021 Mike S + * © 2021 Harald Barth + * © 2021 Fred Decker + * © 2021 Chris Harlow + * © 2021 David Cutting + * All rights reserved. + * + * This file is part of Asbelos DCC 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 . + */ + +// ATTENTION: this file only compiles on a UNO or MEGA +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef ARDUINO_ARCH_AVR + +#include +#include "DCCTimer.h" +INTERRUPT_CALLBACK interruptHandler=0; + + // Arduino nano, uno, mega etc +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + #define TIMER1_A_PIN 11 + #define TIMER1_B_PIN 12 + #define TIMER1_C_PIN 13 +#else + #define TIMER1_A_PIN 9 + #define TIMER1_B_PIN 10 +#endif + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time + TCCR1A = 0; + ICR1 = CLOCK_CYCLES; + TCNT1 = 0; + TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 + TIMSK1 = _BV(TOIE1); // Enable Software interrupt + interrupts(); + } + +// ISR called by timer interrupt every 58uS + ISR(TIMER1_OVF_vect){ interruptHandler(); } + +// Alternative pin manipulation via PWM control. + bool DCCTimer::isPWMPin(byte pin) { + return pin==TIMER1_A_PIN + || pin==TIMER1_B_PIN + #ifdef TIMER1_C_PIN + || pin==TIMER1_C_PIN + #endif + ; + } + + void DCCTimer::setPWM(byte pin, bool high) { + if (pin==TIMER1_A_PIN) { + TCCR1A |= _BV(COM1A1); + OCR1A= high?1024:0; + } + else if (pin==TIMER1_B_PIN) { + TCCR1A |= _BV(COM1B1); + OCR1B= high?1024:0; + } + #ifdef TIMER1_C_PIN + else if (pin==TIMER1_C_PIN) { + TCCR1A |= _BV(COM1C1); + OCR1C= high?1024:0; + } + #endif + } + + void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + for (byte i=0; i<6; i++) { + mac[i]=boot_signature_byte_get(0x0E + i); + } + mac[0] &= 0xFE; + mac[0] |= 0x02; + } + + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = minimum_free_memory; + interrupts(); + return retval; +} + +extern char *__brkval; +extern char *__malloc_heap_start; + +int DCCTimer::freeMemory() { + char top; + return __brkval ? &top - __brkval : &top - __malloc_heap_start; +} + +#endif diff --git a/DCCTimer.cpp b/DCCTimerMEGAAVR.cpp similarity index 50% rename from DCCTimer.cpp rename to DCCTimerMEGAAVR.cpp index cd35293b0..624008af0 100644 --- a/DCCTimer.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -47,14 +47,17 @@ * */ +// ATTENTION: this file only compiles on a UnoWifiRev3 or NanoEvery +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef ARDUINO_ARCH_MEGAAVR + #include "DCCTimer.h" -const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle -const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1; INTERRUPT_CALLBACK interruptHandler=0; +extern char *__brkval; +extern char *__malloc_heap_start; -#ifdef ARDUINO_ARCH_MEGAAVR - // Arduino unoWifi Rev2 and nanoEvery architectire void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; @@ -93,126 +96,9 @@ INTERRUPT_CALLBACK interruptHandler=0; mac[0] |= 0x02; } -#elif defined(TEENSYDUINO) - IntervalTimer myDCCTimer; - - void DCCTimer::begin(INTERRUPT_CALLBACK callback) { - interruptHandler=callback; - - myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME); - - } - - bool DCCTimer::isPWMPin(byte pin) { - //Teensy: digitalPinHasPWM, todo - (void) pin; - return false; // TODO what are the relevant pins? - } - - void DCCTimer::setPWM(byte pin, bool high) { - // TODO what are the relevant pins? - (void) pin; - (void) high; +int DCCTimer::freeMemory() { + char top; + return __brkval ? &top - __brkval : &top - __malloc_heap_start; } - void DCCTimer::getSimulatedMacAddress(byte mac[6]) { -#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1 - uint32_t m1 = HW_OCOTP_MAC1; - uint32_t m2 = HW_OCOTP_MAC0; - mac[0] = m1 >> 8; - mac[1] = m1 >> 0; - mac[2] = m2 >> 24; - mac[3] = m2 >> 16; - mac[4] = m2 >> 8; - mac[5] = m2 >> 0; -#else - read_mac(mac); -#endif - } - -#if !defined(__IMXRT1062__) - void DCCTimer::read_mac(byte mac[6]) { - read(0xe,mac,0); - read(0xf,mac,3); - } - -// http://forum.pjrc.com/threads/91-teensy-3-MAC-address -void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) { - FTFL_FCCOB0 = 0x41; // Selects the READONCE command - FTFL_FCCOB1 = word; // read the given word of read once area - - // launch command and wait until complete - FTFL_FSTAT = FTFL_FSTAT_CCIF; - while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF)); - - *(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes, - *(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian). - *(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0. -} -#endif - -#else - // Arduino nano, uno, mega etc -#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - #define TIMER1_A_PIN 11 - #define TIMER1_B_PIN 12 - #define TIMER1_C_PIN 13 -#else - #define TIMER1_A_PIN 9 - #define TIMER1_B_PIN 10 -#endif - - void DCCTimer::begin(INTERRUPT_CALLBACK callback) { - interruptHandler=callback; - noInterrupts(); - ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time - TCCR1A = 0; - ICR1 = CLOCK_CYCLES; - TCNT1 = 0; - TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 - TIMSK1 = _BV(TOIE1); // Enable Software interrupt - interrupts(); - } - -// ISR called by timer interrupt every 58uS - ISR(TIMER1_OVF_vect){ interruptHandler(); } - -// Alternative pin manipulation via PWM control. - bool DCCTimer::isPWMPin(byte pin) { - return pin==TIMER1_A_PIN - || pin==TIMER1_B_PIN - #ifdef TIMER1_C_PIN - || pin==TIMER1_C_PIN - #endif - ; - } - - void DCCTimer::setPWM(byte pin, bool high) { - if (pin==TIMER1_A_PIN) { - TCCR1A |= _BV(COM1A1); - OCR1A= high?1024:0; - } - else if (pin==TIMER1_B_PIN) { - TCCR1A |= _BV(COM1B1); - OCR1B= high?1024:0; - } - #ifdef TIMER1_C_PIN - else if (pin==TIMER1_C_PIN) { - TCCR1A |= _BV(COM1C1); - OCR1C= high?1024:0; - } - #endif - } - - - #include - void DCCTimer::getSimulatedMacAddress(byte mac[6]) { - for (byte i=0; i<6; i++) { - mac[i]=boot_signature_byte_get(0x0E + i); - } - mac[0] &= 0xFE; - mac[0] |= 0x02; - - } - #endif diff --git a/DCCTimerTEENSY.cpp b/DCCTimerTEENSY.cpp new file mode 100644 index 000000000..99b147a00 --- /dev/null +++ b/DCCTimerTEENSY.cpp @@ -0,0 +1,122 @@ +/* + * © 2021 Mike S + * © 2021 Harald Barth + * © 2021 Fred Decker + * © 2021 Chris Harlow + * © 2021 David Cutting + * All rights reserved. + * + * This file is part of Asbelos DCC 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 . + */ + +// ATTENTION: this file only compiles on a TEENSY +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef TEENSYDUINO + +#include "DCCTimer.h" + +INTERRUPT_CALLBACK interruptHandler=0; + +IntervalTimer myDCCTimer; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME); + } + +bool DCCTimer::isPWMPin(byte pin) { + //Teensy: digitalPinHasPWM, todo + (void) pin; + return false; // TODO what are the relevant pins? + } + +void DCCTimer::setPWM(byte pin, bool high) { + // TODO what are the relevant pins? + (void) pin; + (void) high; +} + +#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1 +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + uint32_t m1 = HW_OCOTP_MAC1; + uint32_t m2 = HW_OCOTP_MAC0; + mac[0] = m1 >> 8; + mac[1] = m1 >> 0; + mac[2] = m2 >> 24; + mac[3] = m2 >> 16; + mac[4] = m2 >> 8; + mac[5] = m2 >> 0; + } + +#else + +// http://forum.pjrc.com/threads/91-teensy-3-MAC-address +void teensyRead(uint8_t word, uint8_t *mac, uint8_t offset) { + FTFL_FCCOB0 = 0x41; // Selects the READONCE command + FTFL_FCCOB1 = word; // read the given word of read once area + + // launch command and wait until complete + FTFL_FSTAT = FTFL_FSTAT_CCIF; + while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF)); + + *(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes, + *(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian). + *(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0. +} + +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + teensyRead(0xe,mac,0); + teensyRead(0xf,mac,3); + } +#endif + +#if !defined(__IMXRT1062__) +static inline int freeMemory() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} + +#else +#if defined(ARDUINO_TEENSY40) + static const unsigned DTCM_START = 0x20000000UL; + static const unsigned OCRAM_START = 0x20200000UL; + static const unsigned OCRAM_SIZE = 512; + static const unsigned FLASH_SIZE = 1984; +#elif defined(ARDUINO_TEENSY41) + static const unsigned DTCM_START = 0x20000000UL; + static const unsigned OCRAM_START = 0x20200000UL; + static const unsigned OCRAM_SIZE = 512; + static const unsigned FLASH_SIZE = 7936; +#if TEENSYDUINO>151 + extern "C" uint8_t external_psram_size; +#endif +#endif + +static inline int freeMemory() { + extern unsigned long _ebss; + extern unsigned long _sdata; + extern unsigned long _estack; + const unsigned DTCM_START = 0x20000000UL; + unsigned dtcm = (unsigned)&_estack - DTCM_START; + unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0); + unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata; + unsigned freemem = dtcm - (stackinuse + varsinuse); + return freemem; +} + +#endif +#endif \ No newline at end of file diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 79c8c97c5..c51a18eed 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -29,7 +29,7 @@ #include "DCCTimer.h" #include "DCCACK.h" #include "DIAG.h" -#include "freeMemory.h" + DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); @@ -140,7 +140,7 @@ void DCCWaveform::interrupt2() { remainingPreambles--; // Update free memory diagnostic as we don't have anything else to do this time. // Allow for checkAck and its called functions using 22 bytes more. - updateMinimumFreeMemory(22); + DCCTimer::updateMinimumFreeMemoryISR(22); return; } diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 7f2d3e12b..a20bf0dc8 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -138,9 +138,6 @@ void MotorDriver::setSignal( bool high) { } } -#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36) -volatile unsigned int overflow_count=0; -#endif bool MotorDriver::canMeasureCurrent() { return currentPin!=UNUSED_PIN; diff --git a/TrackManager.cpp b/TrackManager.cpp index bfb61993e..69fcf7801 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -46,8 +46,7 @@ byte TrackManager::lastTrack=0; void TrackManager::Setup(const FSH * shieldname, MotorDriver * track0, MotorDriver * track1, MotorDriver * track2, MotorDriver * track3, MotorDriver * track4, MotorDriver * track5, - MotorDriver * track6, MotorDriver * track7 ) { - (void) shieldname; // TODO + MotorDriver * track6, MotorDriver * track7 ) { addTrack(0,track0); addTrack(1,track1); addTrack(2,track2); diff --git a/freeMemory.cpp b/freeMemory.cpp deleted file mode 100644 index df5ab8a79..000000000 --- a/freeMemory.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * © 2021 Neil McKechnie - * © 2021 Mike S - * © 2020 Harald Barth - * - * This file is part of Asbelos DCC-EX - * - * 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 . - */ - -#include -#include "freeMemory.h" - -// thanks go to https://github.com/mpflaga/Arduino-MemoryFree -#if defined(__arm__) -extern "C" char* sbrk(int); -#elif defined(__AVR__) -extern char *__brkval; -extern char *__malloc_heap_start; -#else -#error Unsupported board type -#endif - - -static volatile int minimum_free_memory = __INT_MAX__; - -#if !defined(__IMXRT1062__) -static inline int freeMemory() { - char top; -#if defined(__arm__) - return &top - reinterpret_cast(sbrk(0)); -#elif defined(__AVR__) - return __brkval ? &top - __brkval : &top - __malloc_heap_start; -#else -#error bailed out already above -#endif -} - -// Return low memory value. -int minimumFreeMemory() { - byte sreg_save = SREG; - noInterrupts(); // Disable interrupts - int retval = minimum_free_memory; - SREG = sreg_save; // Restore interrupt state - return retval; -} - -#else -#if defined(ARDUINO_TEENSY40) - static const unsigned DTCM_START = 0x20000000UL; - static const unsigned OCRAM_START = 0x20200000UL; - static const unsigned OCRAM_SIZE = 512; - static const unsigned FLASH_SIZE = 1984; -#elif defined(ARDUINO_TEENSY41) - static const unsigned DTCM_START = 0x20000000UL; - static const unsigned OCRAM_START = 0x20200000UL; - static const unsigned OCRAM_SIZE = 512; - static const unsigned FLASH_SIZE = 7936; -#if TEENSYDUINO>151 - extern "C" uint8_t external_psram_size; -#endif -#endif - -static inline int freeMemory() { - extern unsigned long _ebss; - extern unsigned long _sdata; - extern unsigned long _estack; - const unsigned DTCM_START = 0x20000000UL; - unsigned dtcm = (unsigned)&_estack - DTCM_START; - unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0); - unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata; - unsigned freemem = dtcm - (stackinuse + varsinuse); - return freemem; -} - -// Return low memory value. -int minimumFreeMemory() { - //byte sreg_save = SREG; - //noInterrupts(); // Disable interrupts - int retval = minimum_free_memory; - //SREG = sreg_save; // Restore interrupt state - return retval; -} -#endif - - -// Update low ram level. Allow for extra bytes to be specified -// by estimation or inspection, that may be used by other -// called subroutines. Must be called with interrupts disabled. -// -// Although __brkval may go up and down as heap memory is allocated -// and freed, this function records only the worst case encountered. -// So even if all of the heap is freed, the reported minimum free -// memory will not increase. -// -void updateMinimumFreeMemory(unsigned char extraBytes) { - int spare = freeMemory()-extraBytes; - if (spare < 0) spare = 0; - if (spare < minimum_free_memory) minimum_free_memory = spare; -} - diff --git a/freeMemory.h b/freeMemory.h deleted file mode 100644 index af93566a9..000000000 --- a/freeMemory.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * © 2021 Neil McKechnie - * © 2020 Harald Barth - * - * This file is part of DCC-EX - * - * 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 freeMemory_h -#define freeMemory_h -void updateMinimumFreeMemory(unsigned char extraBytes=0); -int minimumFreeMemory(); -#endif From 524afc6cafe81dc8c9dfe24c86493b2a53fde902 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 2 Mar 2022 14:24:49 +0000 Subject: [PATCH 067/870] move more cpu specifics --- DCC.cpp | 1 + DCC.h | 35 +++-------------------------------- DCCEXParser.cpp | 9 --------- DCCTimer.h | 31 +++++++++++++++++++++++++++++++ DCCTimerMEGAAVR.cpp | 13 +++++++++++++ MotorDriver.h | 11 ----------- WiThrottle.cpp | 1 + 7 files changed, 49 insertions(+), 52 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 6d19a26f4..25d56b558 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -36,6 +36,7 @@ #include "EXRAIL2.h" #include "CommandDistributor.h" #include "TrackManager.h" +#include "DCCTimer.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. diff --git a/DCC.h b/DCC.h index 0effee2d8..db27b176c 100644 --- a/DCC.h +++ b/DCC.h @@ -42,12 +42,10 @@ const uint16_t LONG_ADDR_MARKER = 0x4000; // Allocations with memory implications..! // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created -#if defined(ARDUINO_AVR_UNO) -const byte MAX_LOCOS = 20; -#elif defined(ARDUINO_AVR_NANO) -const byte MAX_LOCOS = 30; -#else +#if defined(HAS_ENOUGH_MEMORY) const byte MAX_LOCOS = 50; +#else +const byte MAX_LOCOS = 30; #endif class DCC @@ -131,31 +129,4 @@ class DCC static const byte BIT_OFF = 0x00; }; -#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical) -#define ARDUINO_AVR_MEGA2560 -#endif - -#if defined(ARDUINO_AVR_UNO) -#define ARDUINO_TYPE "UNO" -#elif defined(ARDUINO_AVR_NANO) -#define ARDUINO_TYPE "NANO" -#elif defined(ARDUINO_AVR_MEGA2560) -#define ARDUINO_TYPE "MEGA" -#elif defined(ARDUINO_ARCH_MEGAAVR) -#define ARDUINO_TYPE "MEGAAVR" -#elif defined(ARDUINO_TEENSY32) -#define ARDUINO_TYPE "TEENSY32" -#elif defined(ARDUINO_TEENSY35) -#define ARDUINO_TYPE "TEENSY35" -#elif defined(ARDUINO_TEENSY36) -#define ARDUINO_TYPE "TEENSY36" -#elif defined(ARDUINO_TEENSY40) -#define ARDUINO_TYPE "TEENSY40" -#elif defined(ARDUINO_TEENSY41) -#define ARDUINO_TYPE "TEENSY41" -#else -#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 -#endif - - #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index ae2a10940..ab1f12207 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -40,15 +40,6 @@ #include "DCCTimer.h" #include -//////////////////////////////////////////////////////////////////////////////// -// -// Figure out if we have enough memory for advanced features -// -#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) -// nope -#else -#define HAS_ENOUGH_MEMORY -#endif // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command diff --git a/DCCTimer.h b/DCCTimer.h index e4ebf5ee1..f87d9670a 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -86,4 +86,35 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { }; +//////////////////////////////////////////////////////////////////////////////// +// Create a cpu type we can share and +// gigure out if we have enough memory for advanced features +// so define HAS_ENOUGH_MEMORY until proved otherwise. +#define HAS_ENOUGH_MEMORY + +#if defined(ARDUINO_AVR_UNO) +#define ARDUINO_TYPE "UNO" +#undef HAS_ENOUGH_MEMORY +#elif defined(ARDUINO_AVR_NANO) +#define ARDUINO_TYPE "NANO" +#undef HAS_ENOUGH_MEMORY +#elif defined(ARDUINO_AVR_MEGA) +#define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_AVR_MEGA2560) +#define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_ARCH_MEGAAVR) +#define ARDUINO_TYPE "MEGAAVR" +#elif defined(ARDUINO_TEENSY32) +#define ARDUINO_TYPE "TEENSY32" +#elif defined(ARDUINO_TEENSY35) +#define ARDUINO_TYPE "TEENSY35" +#elif defined(ARDUINO_TEENSY36) +#define ARDUINO_TYPE "TEENSY36" +#elif defined(ARDUINO_TEENSY40) +#define ARDUINO_TYPE "TEENSY40" +#elif defined(ARDUINO_TEENSY41) +#define ARDUINO_TYPE "TEENSY41" +#else +#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 +#endif #endif diff --git a/DCCTimerMEGAAVR.cpp b/DCCTimerMEGAAVR.cpp index 624008af0..8b43340de 100644 --- a/DCCTimerMEGAAVR.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -96,6 +96,19 @@ extern char *__malloc_heap_start; mac[0] |= 0x02; } +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = minimum_free_memory; + interrupts(); + return retval; +} + +extern char *__brkval; +extern char *__malloc_heap_start; + int DCCTimer::freeMemory() { char top; return __brkval ? &top - __brkval : &top - __malloc_heap_start; diff --git a/MotorDriver.h b/MotorDriver.h index ca30706e0..0e4af4f99 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -103,16 +103,5 @@ class MotorDriver { unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; unsigned int power_good_counter = 0; -#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) - static bool disableInterrupts() { - uint32_t primask; - __asm__ volatile("mrs %0, primask\n" : "=r" (primask)::); - __disable_irq(); - return (primask == 0) ? true : false; - } - static void enableInterrupts(bool doit) { - if (doit) __enable_irq(); - } -#endif }; #endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 82c10640a..ace3d16cf 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -56,6 +56,7 @@ #include "EXRAIL2.h" #include "CommandDistributor.h" #include "TrackManager.h" +#include "DCCTimer.h" #define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco Date: Wed, 2 Mar 2022 15:34:01 +0000 Subject: [PATCH 068/870] Add DC signal generation code. --- MotorDriver.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a20bf0dc8..348a9548d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -167,10 +167,17 @@ int MotorDriver::getCurrentRaw() { } void MotorDriver::setDCSignal(byte speedcode) { - // spedcxode is a dcc speed /direction - // TODO jiggle the DC speed pin PWMs - + // spedcoode is a dcc speed /direction + byte tSpeed=speedcode & 0x7F; + // DCC Speed with 0,1 stop and speed steps 2 to 127 + byte brake; + if (tSpeed <= 1) brake = 255; + else if (tSpeed >= 127) brake = 0; + else brake = 2 * (128-tSpeed); + analogWrite(brakePin,brake); + setSignal(speedcode & 0x80); } + int MotorDriver::getCurrentRawInInterrupt() { // IMPORTANT: This function must be called in Interrupt() time within the 56uS timer From 03372f21e28adf03808c2b8dda725d7f9692306b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Mar 2022 23:35:14 +0100 Subject: [PATCH 069/870] rename FAIL to CALLFAIL because of conflict in ESP32 IDE --- DCC.cpp | 22 +++++++++++----------- DCCACK.cpp | 2 +- DCCACK.h | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 25d56b558..5188cda5c 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -300,14 +300,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = { W0,WACK, V0, WACK, // validate bit is 0 ITC1, // if acked, callback(1) - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH WRITE_BIT1_PROG[] = { BASELINE, W1,WACK, V1, WACK, // validate bit is 1 ITC1, // if acked, callback(1) - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH VERIFY_BIT0_PROG[] = { @@ -316,7 +316,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = { ITC0, // if acked, callback(0) V1, WACK, // validate bit is 1 ITC1, - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH VERIFY_BIT1_PROG[] = { BASELINE, @@ -324,7 +324,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = { ITC1, // if acked, callback(1) V0, WACK, ITC0, - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH READ_BIT_PROG[] = { @@ -333,7 +333,7 @@ const ackOp FLASH READ_BIT_PROG[] = { ITC1, // if acked, callback(1) V0, WACK, // validate bit is zero ITC0, // if acked callback 0 - FAIL // bit not readable + CALLFAIL // bit not readable }; const ackOp FLASH WRITE_BYTE_PROG[] = { @@ -341,7 +341,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = { WB,WACK,ITC1, // Write and callback(1) if ACK // handle decoders that dont ack a write VB,WACK,ITC1, // validate byte and callback(1) if correct - FAIL // callback (-1) + CALLFAIL // callback (-1) }; const ackOp FLASH VERIFY_BYTE_PROG[] = { @@ -367,7 +367,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report - FAIL }; + CALLFAIL }; const ackOp FLASH READ_CV_PROG[] = { @@ -390,7 +390,7 @@ const ackOp FLASH READ_CV_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, VB, WACK, ITCB, // verify merged byte and return it if acked ok - FAIL }; // verification failed + CALLFAIL }; // verification failed const ackOp FLASH LOCO_ID_PROG[] = { @@ -456,7 +456,7 @@ const ackOp FLASH LOCO_ID_PROG[] = { V0, WACK, MERGE, V0, WACK, MERGE, VB, WACK, ITCB, // verify merged byte and callback - FAIL + CALLFAIL }; const ackOp FLASH SHORT_LOCO_ID_PROG[] = { @@ -473,7 +473,7 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = { SETBYTEL, // low byte of word WB,WACK, // some decoders don't ACK writes VB,WACK,ITCB, - FAIL + CALLFAIL }; const ackOp FLASH LONG_LOCO_ID_PROG[] = { @@ -497,7 +497,7 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = { SETBYTEL, // low byte of word WB,WACK, VB,WACK,ITC1, // callback(1) means Ok - FAIL + CALLFAIL }; void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) { diff --git a/DCCACK.cpp b/DCCACK.cpp index 8afe0f9cf..bc115c0dc 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -260,7 +260,7 @@ void DCCACK::loop() { } break; - case FAIL: // callback(-1) + case CALLFAIL: // callback(-1) callback(-1); return; diff --git a/DCCACK.h b/DCCACK.h index 8536ce12e..7d39319a5 100644 --- a/DCCACK.h +++ b/DCCACK.h @@ -44,7 +44,7 @@ enum ackOp : byte ITCBV, // If True callback(byte) - end of Verify Byte ITCB7, // If True callback(byte &0x7F) NAKFAIL, // if false callback(-1) - FAIL, // callback(-1) + CALLFAIL, // callback(-1) 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) From 2890a7928b492ce4679ac23b04595cbff3917f3a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Mar 2022 23:36:31 +0100 Subject: [PATCH 070/870] restart with AVR WDT or ESP.restart() --- DCCEXParser.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index ab1f12207..643d0a4a3 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -38,7 +38,9 @@ #include "DIAG.h" #include "TrackManager.h" #include "DCCTimer.h" +#ifdef HAS_AVR_WDT #include +#endif // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. @@ -775,8 +777,12 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_RESET: { +#ifdef HAS_AVR_WDT wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms - delay(50); // wait for the prescaller time to expire + delay(50); // wait for the prescaller time to expire +#else + ESP.restart(); +#endif break; // and if we didnt restart } From f7e2c0ca99d85a1d4994437f29e2daa3352913fe Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Mar 2022 23:37:27 +0100 Subject: [PATCH 071/870] add ESP defines --- DCCTimer.h | 9 +++- DCCTimerESP.cpp | 127 ++++++++++++++++++++++++++++++++++++++++++++ EthernetInterface.h | 2 +- IO_GPIOBase.h | 6 ++- MotorDriver.h | 2 +- WifiInterface.cpp | 2 +- WifiInterface.h | 2 +- defines.h | 2 +- 8 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 DCCTimerESP.cpp diff --git a/DCCTimer.h b/DCCTimer.h index f87d9670a..fb8184b38 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -91,6 +91,7 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { // gigure out if we have enough memory for advanced features // so define HAS_ENOUGH_MEMORY until proved otherwise. #define HAS_ENOUGH_MEMORY +#define HAS_AVR_WDT #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" @@ -114,7 +115,13 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { #define ARDUINO_TYPE "TEENSY40" #elif defined(ARDUINO_TEENSY41) #define ARDUINO_TYPE "TEENSY41" +#elif defined(ARDUINO_ARCH_ESP8266) +#define ARDUINO_TYPE "ESP8266" +#undef HAS_AVR_WDT +#elif defined(ARDUINO_ARCH_ESP32) +#define ARDUINO_TYPE "ESP32" +#undef HAS_AVR_WDT #else -#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 +#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h #endif #endif diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp new file mode 100644 index 000000000..d4b4a2bba --- /dev/null +++ b/DCCTimerESP.cpp @@ -0,0 +1,127 @@ +/* + * © 2020-2022 Harald Barth + * + * This file is part of CommandStation-EX + * + * 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 . + */ + +// ATTENTION: this file only compiles on an ESP8266 and ESP32 +// On ESP32 we do not even use the functions but they are here for completeness sake +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. + +#ifdef ARDUINO_ARCH_ESP8266 + +#include "DCCTimer.h" +INTERRUPT_CALLBACK interruptHandler=0; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + timer1_disable(); + + // There seem to be differnt ways to attach interrupt handler + // ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); + // ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler); + // Let us choose the one from the API + timer1_attachInterrupt(interruptHandler); + + // not exactly sure of order: + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); + timer1_write(CLOCK_CYCLES); +} +// We do not support to use PWM to make the Waveform on ESP +bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { + return false; +} +void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { +} + +// Fake this as it should not be used +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + mac[0] = 0xFE; + mac[1] = 0xBE; + mac[2] = 0xEF; + mac[3] = 0xC0; + mac[4] = 0xFF; + mac[5] = 0xEE; +} + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = minimum_free_memory; + interrupts(); + return retval; +} + +int DCCTimer::freeMemory() { + return ESP.getFreeHeap(); +} +#endif + +//////////////////////////////////////////////////////////////////////// + +#ifdef ARDUINO_ARCH_ESP32 + +#include "DCCTimer.h" +INTERRUPT_CALLBACK interruptHandler=0; + +// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx + +portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler = callback; + hw_timer_t *timer = NULL; + timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2 + timerAttachInterrupt(timer, interruptHandler, true); + timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz) + timerAlarmEnable(timer); +} + +// We do not support to use PWM to make the Waveform on ESP +bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { + return false; +} +void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { +} + +// Fake this as it should not be used +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + mac[0] = 0xFE; + mac[1] = 0xBE; + mac[2] = 0xEF; + mac[3] = 0xC0; + mac[4] = 0xFF; + mac[5] = 0xEE; +} + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = minimum_free_memory; + interrupts(); + return retval; +} + +int DCCTimer::freeMemory() { + return ESP.getFreeHeap(); +} +#endif + diff --git a/EthernetInterface.h b/EthernetInterface.h index 2a94ac68f..e79996d79 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -31,7 +31,7 @@ #include "defines.h" #include "DCCEXParser.h" #include -#include +//#include #if defined (ARDUINO_TEENSY41) #include //TEENSY Ethernet Treiber #include diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 4269a70b7..3bc75afa8 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -69,6 +69,10 @@ class GPIOBase : public IODevice { I2CRB requestBlock; FSH *_deviceName; +#if defined(ARDUINO_ARCH_ESP32) + // workaround: Has somehow no min function for all types + static inline T min(T a, int b) { return a < b ? a : b; }; +#endif }; // Because class GPIOBase is a template, the implementation (below) must be contained within the same @@ -246,4 +250,4 @@ int GPIOBase::_read(VPIN vpin) { return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) } -#endif \ No newline at end of file +#endif diff --git a/MotorDriver.h b/MotorDriver.h index 0e4af4f99..c69f63dd4 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -29,7 +29,7 @@ #define UNUSED_PIN 127 // inside int8_t #endif -#if defined(__IMXRT1062__) +#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) struct FASTPIN { volatile uint32_t *inout; uint32_t maskHIGH; diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 01200741c..b7b059ed0 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -22,7 +22,7 @@ #ifndef ARDUINO_AVR_UNO_WIFI_REV2 // This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture #include "WifiInterface.h" /* config.h included there */ -#include +//#include #include "DIAG.h" #include "StringFormatter.h" diff --git a/WifiInterface.h b/WifiInterface.h index 0a4e47712..7c0a43330 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -23,7 +23,7 @@ #include "FSH.h" #include "DCCEXParser.h" #include -#include +//#include enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED }; diff --git a/defines.h b/defines.h index 67dbc9622..9ce154d85 100644 --- a/defines.h +++ b/defines.h @@ -40,7 +40,7 @@ // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) || defined(ARDUINO_AVR_NANO_EVERY) +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) #define BIG_RAM #endif #if ENABLE_WIFI From 7e3dcb8e8c403dbe73b462535f67496dab4aac78 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 7 Mar 2022 18:46:29 +0000 Subject: [PATCH 072/870] Correct merge issues from master --- EXRAIL2.cpp | 6 ------ WiThrottle.cpp | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index ecd8e0ad3..633421d9c 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -808,12 +808,6 @@ void RMFT2::loop2() { DCCWaveform::setJoin(false); CommandDistributor::broadcastPower(); break; - - case OPCODE_POWERON: - DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(false); - CommandDistributor::broadcastPower(); - break; case OPCODE_UNJOIN: DCCWaveform::setJoin(false); diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 22a6f98c1..1ac93f61f 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -542,8 +542,8 @@ void WiThrottle::getLocoCallback(int16_t locoid) { char addcmd[20]={'M',stashThrottleChar,'+', addrchar}; itoa(locoid,addcmd+4,10); stashInstance->multithrottle(stashStream, (byte *)addcmd); - DCCWaveform::progTrack.setPowerMode(POWERMODE::ON); - DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away + TrackManager::setMainPower(POWERMODE::ON); + DCCWaveform::setJoin(true); // <1 JOIN> so we can drive loco away stashStream->commit(); CommandDistributor::broadcastPower(); From 75b16c9047a0206073378c008cc42e076bba46fa Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 18 Mar 2022 16:41:52 +0000 Subject: [PATCH 073/870] Change track manager cmd to = And fix the wrong param number at the same time! --- DCCEXParser.cpp | 2 +- TrackManager.cpp | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 643d0a4a3..6237e48e1 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -473,7 +473,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; return; - case 'J': // < > + case '=': // <= Track manager control > if (TrackManager::parseJ(stream, params, p)) return; break; diff --git a/TrackManager.cpp b/TrackManager.cpp index 69fcf7801..991ff37a7 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -35,6 +35,7 @@ const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_OFF = 22479; const int16_t HASH_KEYWORD_DC = 2183; +const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackMode[MAX_TRACKS]; @@ -113,10 +114,10 @@ bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) { - if (params==0) { // List track assignments + if (params==0) { // <=> List track assignments FOR_EACH_TRACK(t) if (track[t]!=NULL) { - StringFormatter::send(stream,F("1 && (p[1]<0 || p[1]>=MAX_TRACKS)) + p[0]-=HASH_KEYWORD_A; // convert A... to 0.... + + if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS)) return false; - if (params==2 && p[1]==HASH_KEYWORD_MAIN) // - return setTrackMode(p[1],TRACK_MODE_MAIN); + if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN> + return setTrackMode(p[0],TRACK_MODE_MAIN); - if (params==2 && p[1]==HASH_KEYWORD_PROG) // - return setTrackMode(p[1],TRACK_MODE_PROG); + if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG> + return setTrackMode(p[0],TRACK_MODE_PROG); - if (params==2 && p[1]==HASH_KEYWORD_OFF) // - return setTrackMode(p[1],TRACK_MODE_OFF); + if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF> + return setTrackMode(p[0],TRACK_MODE_OFF); - if (params==3 && p[1]==HASH_KEYWORD_DC) // - return setTrackMode(p[1],p[2]); + if (params==3 && p[1]==HASH_KEYWORD_DC) // <= id DC cab> + return setTrackMode(p[0],p[2]); return false; } From 4f2dc0934f876d585e324616436a43aa598707d8 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 18 Mar 2022 20:03:19 +0000 Subject: [PATCH 074/870] prevent DC addr 0 --- TrackManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 991ff37a7..17dd7548f 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -2,7 +2,7 @@ * © 2022 Chris Harlow * All rights reserved. * - * This file is part of Asbelos DCC API + * This file is part of DCC++EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -150,7 +150,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF> return setTrackMode(p[0],TRACK_MODE_OFF); - if (params==3 && p[1]==HASH_KEYWORD_DC) // <= id DC cab> + if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab> return setTrackMode(p[0],p[2]); return false; From be186b967b257373244728cc51897285fa011e10 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 19 Mar 2022 11:22:31 +0000 Subject: [PATCH 075/870] CODE TIDY Moved join code out of DCCWaveform to reduce footprint for ESP32 waveform replacement. --- CommandDistributor.cpp | 2 +- DCC.cpp | 3 +- DCCACK.cpp | 6 ++-- DCCEXParser.cpp | 10 +++--- DCCWaveform.cpp | 78 ++++++++++++++++-------------------------- DCCWaveform.h | 21 +----------- EXRAIL2.cpp | 8 ++--- TrackManager.cpp | 21 +++++++++++- TrackManager.h | 10 ++++-- WiThrottle.cpp | 2 +- 10 files changed, 74 insertions(+), 87 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 6e6fe60fc..85df03bc8 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -122,7 +122,7 @@ void CommandDistributor::broadcastLoco(byte slot) { void CommandDistributor::broadcastPower() { bool main=TrackManager::getMainPower()==POWERMODE::ON; bool prog=TrackManager::getProgPower()==POWERMODE::ON; - bool join=DCCWaveform::isJoined(); + bool join=TrackManager::isJoined(); const FSH * reason=F(""); char state='1'; if (main && prog && join) reason=F(" JOIN"); diff --git a/DCC.cpp b/DCC.cpp index 5188cda5c..977a30439 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -557,8 +557,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders byte DCC::loopStatus=0; void DCC::loop() { - DCCWaveform::loop(); // power overload checks - DCCACK::loop(); // maintain prog track ack manager + TrackManager::loop(); // power overload checks issueReminders(); } diff --git a/DCCACK.cpp b/DCCACK.cpp index bc115c0dc..7c159b854 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -77,10 +77,10 @@ void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_C return; } - ackManagerRejoin=DCCWaveform::isJoined(); + ackManagerRejoin=TrackManager::isJoined(); if (ackManagerRejoin ) { // Change from JOIN must zero resets packet. - DCCWaveform::setJoin(false); + TrackManager::setJoin(false); DCCWaveform::progTrack.sentResetsSincePacket = 0; } @@ -403,7 +403,7 @@ void DCCACK::callback(int value) { } // Restore <1 JOIN> to state before BASELINE if (ackManagerRejoin) { - DCCWaveform::setJoin(true); + TrackManager::setJoin(true); if (Diag::ACK) DIAG(F("Auto JOIN")); } diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 6237e48e1..4d478cb68 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -397,7 +397,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) } if (main) TrackManager::setMainPower(POWERMODE::ON); if (prog) TrackManager::setProgPower(POWERMODE::ON); - DCCWaveform::setJoin(join); + TrackManager::setJoin(join); CommandDistributor::broadcastPower(); return; @@ -424,10 +424,10 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) if (main) TrackManager::setMainPower(POWERMODE::OFF); if (prog) { - DCCWaveform::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off + TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off TrackManager::setProgPower(POWERMODE::OFF); } - DCCWaveform::setJoin(false); + TrackManager::setJoin(false); CommandDistributor::broadcastPower(); return; @@ -772,8 +772,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) #endif case HASH_KEYWORD_PROGBOOST: - DCCWaveform::progTrackBoosted=true; - return true; + TrackManager::progTrackBoosted=true; + return true; case HASH_KEYWORD_RESET: { diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index c51a18eed..02586273b 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -21,7 +21,8 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ - +#ifndef ARDUINO_ARCH_ESP32 + // This code is replaced entirely on an ESP32 #include #include "DCCWaveform.h" @@ -34,19 +35,38 @@ DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); -bool DCCWaveform::progTrackSyncMain=false; -bool DCCWaveform::progTrackBoosted=false; -int16_t DCCWaveform::joinRelay=UNUSED_PIN; -void DCCWaveform::begin() { +// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits. +const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; - TrackManager::setPower(POWERMODE::OFF); +const byte idlePacket[] = {0xFF, 0x00, 0xFF}; +const byte resetPacket[] = {0x00, 0x00, 0x00}; + + +// For each state of the wave nextState=stateTransform[currentState] +const WAVE_STATE stateTransform[]={ + /* WAVE_START -> */ WAVE_PENDING, + /* WAVE_MID_1 -> */ WAVE_START, + /* WAVE_HIGH_0 -> */ WAVE_MID_0, + /* WAVE_MID_0 -> */ WAVE_LOW_0, + /* WAVE_LOW_0 -> */ WAVE_START, + /* WAVE_PENDING (should not happen) -> */ WAVE_PENDING}; + +// For each state of the wave, signal pin is HIGH or LOW +const bool signalTransform[]={ + /* WAVE_START -> */ HIGH, + /* WAVE_MID_1 -> */ LOW, + /* WAVE_HIGH_0 -> */ HIGH, + /* WAVE_MID_0 -> */ LOW, + /* WAVE_LOW_0 -> */ LOW, + /* WAVE_PENDING (should not happen) -> */ LOW}; + +void DCCWaveform::begin() { DCCTimer::begin(DCCWaveform::interruptHandler); } void DCCWaveform::loop() { - DCCACK::loop(); - TrackManager::loop(DCCACK::isActive() || progTrackSyncMain || progTrackBoosted ); + // empty placemarker in case ESP32 needs something here } #pragma GCC push_options @@ -55,7 +75,7 @@ void DCCWaveform::interruptHandler() { // call the timer edge sensitive actions for progtrack and maintrack // member functions would be cleaner but have more overhead byte sigMain=signalTransform[mainTrack.state]; - byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; + byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state]; // Set the signal state for both tracks TrackManager::setDCCSignal(sigMain); @@ -73,18 +93,6 @@ void DCCWaveform::interruptHandler() { } #pragma GCC push_options -void DCCWaveform::setJoinRelayPin(byte joinRelayPin) { - joinRelay=joinRelayPin; - if (joinRelay!=UNUSED_PIN) { - pinMode(joinRelay,OUTPUT); - digitalWrite(joinRelay,LOW); // LOW is relay disengaged - } -} - -void DCCWaveform::setJoin(bool joined) { - progTrackSyncMain=joined; - if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW); -} // An instance of this class handles the DCC transmissions for one track. (main or prog) // Interrupts are marshalled via the statics. @@ -92,9 +100,6 @@ void DCCWaveform::setJoin(bool joined) { // When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer. -// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits. -const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; - DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { isMainTrack = isMain; @@ -110,25 +115,7 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { -// For each state of the wave nextState=stateTransform[currentState] -const WAVE_STATE DCCWaveform::stateTransform[]={ - /* WAVE_START -> */ WAVE_PENDING, - /* WAVE_MID_1 -> */ WAVE_START, - /* WAVE_HIGH_0 -> */ WAVE_MID_0, - /* WAVE_MID_0 -> */ WAVE_LOW_0, - /* WAVE_LOW_0 -> */ WAVE_START, - /* WAVE_PENDING (should not happen) -> */ WAVE_PENDING}; - -// For each state of the wave, signal pin is HIGH or LOW -const bool DCCWaveform::signalTransform[]={ - /* WAVE_START -> */ HIGH, - /* WAVE_MID_1 -> */ LOW, - /* WAVE_HIGH_0 -> */ HIGH, - /* WAVE_MID_0 -> */ LOW, - /* WAVE_LOW_0 -> */ LOW, - /* WAVE_PENDING (should not happen) -> */ LOW}; -#pragma GCC push_options #pragma GCC optimize ("-O3") void DCCWaveform::interrupt2() { // calculate the next bit to be sent: @@ -185,8 +172,6 @@ void DCCWaveform::interrupt2() { } } } -#pragma GCC pop_options - // Wait until there is no packet pending, then make this pending void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { @@ -205,7 +190,4 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea packetPending = true; sentResetsSincePacket=0; } - -#pragma GCC push_options -#pragma GCC optimize ("-O3") -#pragma GCC pop_options +#endif \ No newline at end of file diff --git a/DCCWaveform.h b/DCCWaveform.h index 2ad1c6737..667e22080 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -33,19 +33,14 @@ const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. + // The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic // to the transform array. enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5}; - // NOTE: static functions are used for the overall controller, then // one instance is created for each track. - - -const byte idlePacket[] = {0xFF, 0x00, 0xFF}; -const byte resetPacket[] = {0x00, 0x00, 0x00}; - class DCCWaveform { public: DCCWaveform( byte preambleBits, bool isMain); @@ -53,26 +48,12 @@ class DCCWaveform { static void loop(); static DCCWaveform mainTrack; static DCCWaveform progTrack; - - void beginTrack(); - static void setJoin(bool join); - static bool isJoined() { return progTrackSyncMain;} void clearRepeats() {pendingRepeats=0;} void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; - static bool progTrackBoosted; // true when prog track is not current limited volatile byte sentResetsSincePacket; - static void setJoinRelayPin(byte joinRelayPin); - static int16_t joinRelay; private: - static bool progTrackSyncMain; // true when prog track is a siding switched to main - -// For each state of the wave nextState=stateTransform[currentState] - static const WAVE_STATE stateTransform[6]; - -// For each state of the wave, signal pin is HIGH or LOW - static const bool signalTransform[6]; static void interruptHandler(); void interrupt2(); diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 381d04302..e4451a25e 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -665,7 +665,7 @@ void RMFT2::loop2() { case OPCODE_POWEROFF: TrackManager::setPower(POWERMODE::OFF); - DCCWaveform::setJoin(false); + TrackManager::setJoin(false); CommandDistributor::broadcastPower(); break; @@ -811,18 +811,18 @@ void RMFT2::loop2() { case OPCODE_JOIN: TrackManager::setPower(POWERMODE::ON); - DCCWaveform::setJoin(true); + TrackManager::setJoin(true); CommandDistributor::broadcastPower(); break; case OPCODE_POWERON: TrackManager::setMainPower(POWERMODE::ON); - DCCWaveform::setJoin(false); + TrackManager::setJoin(false); CommandDistributor::broadcastPower(); break; case OPCODE_UNJOIN: - DCCWaveform::setJoin(false); + TrackManager::setJoin(false); CommandDistributor::broadcastPower(); break; diff --git a/TrackManager.cpp b/TrackManager.cpp index 17dd7548f..470e039f9 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -41,6 +41,10 @@ MotorDriver * TrackManager::track[MAX_TRACKS]; int16_t TrackManager::trackMode[MAX_TRACKS]; POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; byte TrackManager::lastTrack=0; +bool TrackManager::progTrackSyncMain=false; +bool TrackManager::progTrackBoosted=false; +int16_t TrackManager::joinRelay=UNUSED_PIN; + // The setup call is done this way so that the tracks can be in a list // from the config... the tracks default to NULL in the declaration @@ -158,7 +162,10 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) byte TrackManager::nextCycleTrack=MAX_TRACKS; -void TrackManager::loop(bool dontLimitProg) { +void TrackManager::loop() { + DCCWaveform::loop(); + DCCACK::loop(); + bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted; nextCycleTrack++; if (nextCycleTrack>lastTrack) nextCycleTrack=0; if (track[nextCycleTrack]==NULL) return; @@ -193,3 +200,15 @@ POWERMODE TrackManager::getProgPower() { return POWERMODE::OFF; } +void TrackManager::setJoinRelayPin(byte joinRelayPin) { + joinRelay=joinRelayPin; + if (joinRelay!=UNUSED_PIN) { + pinMode(joinRelay,OUTPUT); + digitalWrite(joinRelay,LOW); // LOW is relay disengaged + } +} + +void TrackManager::setJoin(bool joined) { + progTrackSyncMain=joined; + if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW); +} diff --git a/TrackManager.h b/TrackManager.h index f861edb66..18a073f2a 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -53,10 +53,16 @@ class TrackManager { static const int16_t MAX_TRACKS=8; static bool setTrackMode(byte track, int16_t DCaddrOrMode); static bool parseJ(Print * stream, int16_t params, int16_t p[]); - static void loop(bool dontLimitProg); + static void loop(); static POWERMODE getMainPower() {return mainPowerGuess;} static POWERMODE getProgPower(); - + static void setJoin(bool join); + static bool isJoined() { return progTrackSyncMain;} + static void setJoinRelayPin(byte joinRelayPin); + static int16_t joinRelay; + static bool progTrackSyncMain; // true when prog track is a siding switched to main + static bool progTrackBoosted; // true when prog track is not current limited + private: static void addTrack(byte t, MotorDriver* driver); diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 1ac93f61f..fed5078b8 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -543,7 +543,7 @@ void WiThrottle::getLocoCallback(int16_t locoid) { itoa(locoid,addcmd+4,10); stashInstance->multithrottle(stashStream, (byte *)addcmd); TrackManager::setMainPower(POWERMODE::ON); - DCCWaveform::setJoin(true); // <1 JOIN> so we can drive loco away + TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away stashStream->commit(); CommandDistributor::broadcastPower(); From 865c8dd3bd654f727f476d0111cd5207454b9161 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 19 Mar 2022 16:26:29 +0000 Subject: [PATCH 076/870] DCX Track mode And unified SET_TRACK Exrail macro --- EXRAIL2.cpp | 10 +++++++--- EXRAIL2MacroReset.h | 6 ++---- EXRAILMacros.h | 3 +-- TrackManager.cpp | 37 ++++++++++++++++++++++++++++--------- TrackManager.h | 22 ++++++++++++++++------ 5 files changed, 54 insertions(+), 24 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index e4451a25e..c7a61dcda 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -670,9 +670,13 @@ void RMFT2::loop2() { break; case OPCODE_SET_TRACK: - // operand is track id and +128= Use my loco for DC - TrackManager::setTrackMode(operand & 0x0F, - operand>=128 ? loco : TrackManager::TRACK_MODE_MAIN); + // operand is trackmode<<8 | track id + // If DC/DCX use my loco for DC address + { + TRACK_MODE mode = (TRACK_MODE)(operand>>8); + int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0; + TrackManager::setTrackMode(operand & 0x0F, mode, cab); + } break; case OPCODE_RESUME: diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index e140a3232..c7c106c9e 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -103,8 +103,7 @@ #undef SERVO_TURNOUT #undef SERVO_SIGNAL #undef SET -#undef SET_TRACK_DC -#undef SET_TRACK_DCC +#undef SET_TRACK #undef SETLOCO #undef SIGNAL #undef SIGNALH @@ -200,8 +199,7 @@ #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) #define SET(pin) -#define SET_TRACK_DC(trackid) -#define SET_TRACK_DCC(trackid) +#define SET_TRACK(track,mode) #define SETLOCO(loco) #define SIGNAL(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 4e7e06592..d32cfaedd 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -247,8 +247,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) #define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile), #define SET(pin) OPCODE_SET,V(pin), -#define SET_TRACK_DC(track) OPCODE_SET_TRACK,V(128+track), -#define SET_TRACK_DCC(track) OPCODE_SET_TRACK,V(track), +#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track), #define SETLOCO(loco) OPCODE_SETLOCO,V(loco), #define SIGNAL(redpin,amberpin,greenpin) #define SIGNALH(redpin,amberpin,greenpin) diff --git a/TrackManager.cpp b/TrackManager.cpp index 470e039f9..074f8dfac 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -35,10 +35,13 @@ const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_OFF = 22479; const int16_t HASH_KEYWORD_DC = 2183; +const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. MotorDriver * TrackManager::track[MAX_TRACKS]; -int16_t TrackManager::trackMode[MAX_TRACKS]; +TRACK_MODE TrackManager::trackMode[MAX_TRACKS]; +int16_t TrackManager::trackDCAddr[MAX_TRACKS]; + POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; byte TrackManager::lastTrack=0; bool TrackManager::progTrackSyncMain=false; @@ -61,7 +64,7 @@ void TrackManager::Setup(const FSH * shieldname, addTrack(6,track6); addTrack(7,track7); - // Default the first 2 tracks (which mat be null) and perform HA waveform check. + // Default the first 2 tracks (which may be null) and perform HA waveform check. setTrackMode(0,TRACK_MODE_MAIN); setTrackMode(1,TRACK_MODE_PROG); @@ -93,17 +96,24 @@ void TrackManager::setPROGSignal( bool on) { } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { - APPLY_BY_MODE(cab,setDCSignal(speedbyte)); -} + FOR_EACH_TRACK(t) { + if (trackDCAddr[t]!=cab) continue; + if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); + else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); + } +} + -bool TrackManager::setTrackMode(byte trackToSet, int16_t modeOrAddr) { +bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; - if (modeOrAddr==TRACK_MODE_PROG) { + if (mode==TRACK_MODE_PROG) { // only allow 1 track to be prog FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_PROG) trackMode[t]=TRACK_MODE_OFF; } - trackMode[trackToSet]=modeOrAddr; + trackMode[trackToSet]=mode; + trackDCAddr[trackToSet]=dcAddr; + // re-evaluate HighAccuracy mode // We can only do this is all main and prog tracks agree @@ -132,8 +142,14 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) case TRACK_MODE_OFF: StringFormatter::send(stream,F("OFF")); break; + case TRACK_MODE_DC: + StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]); + break; + case TRACK_MODE_DCX: + StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]); + break; default: - StringFormatter::send(stream,F("DC %d"),trackMode[t]); + break; // unknown, dont care } StringFormatter::send(stream,F(">\n")); } @@ -155,7 +171,10 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) return setTrackMode(p[0],TRACK_MODE_OFF); if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab> - return setTrackMode(p[0],p[2]); + return setTrackMode(p[0],TRACK_MODE_DC,p[2]); + + if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab> + return setTrackMode(p[0],TRACK_MODE_DCX,p[2]); return false; } diff --git a/TrackManager.h b/TrackManager.h index 18a073f2a..dde4b905b 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -23,6 +23,18 @@ #include "MotorDriver.h" // Virtualised Motor shield multi-track hardware Interface +enum TRACK_MODE : byte {TRACK_MODE_MAIN, TRACK_MODE_PROG, TRACK_MODE_OFF, + TRACK_MODE_DC, TRACK_MODE_DCX}; + +// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. +const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0; +const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1; +const byte TRACK_NUMBER_2=2, TRACK_NUMBER_C=2; +const byte TRACK_NUMBER_3=3, TRACK_NUMBER_D=3; +const byte TRACK_NUMBER_4=4, TRACK_NUMBER_E=4; +const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5; +const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6; +const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7; class TrackManager { public: @@ -47,11 +59,8 @@ class TrackManager { static void setMainPower(POWERMODE mode) {setPower2(false,mode);} static void setProgPower(POWERMODE mode) {setPower2(true,mode);} - static const int16_t TRACK_MODE_MAIN=32760; - static const int16_t TRACK_MODE_PROG=32761; - static const int16_t TRACK_MODE_OFF=0; static const int16_t MAX_TRACKS=8; - static bool setTrackMode(byte track, int16_t DCaddrOrMode); + static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0); static bool parseJ(Print * stream, int16_t params, int16_t p[]); static void loop(); static POWERMODE getMainPower() {return mainPowerGuess;} @@ -71,7 +80,8 @@ class TrackManager { static POWERMODE mainPowerGuess; static MotorDriver* track[MAX_TRACKS]; - static int16_t trackMode[MAX_TRACKS]; // dc address or TRACK_MODE_DCC, TRACK_MODE_PROG, TRACK_MODE_OFF -}; + static TRACK_MODE trackMode[MAX_TRACKS]; + static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX + }; #endif From 2a87a6e997c3151243250b7c58bf341b8a82ec59 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Mar 2022 12:09:41 +0000 Subject: [PATCH 077/870] Created TrackManager.md --- Release_Notes/TrackManager.md | 133 ++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 Release_Notes/TrackManager.md diff --git a/Release_Notes/TrackManager.md b/Release_Notes/TrackManager.md new file mode 100644 index 000000000..5085d96a3 --- /dev/null +++ b/Release_Notes/TrackManager.md @@ -0,0 +1,133 @@ +# DCC++EX Track Manager + + +**If you are only interested in a standard setup using just a DCC track and PROG track, then you DO NOT need to read the rest of this document.** + +What follows is for advanced users interested in managing power districts and/or running DC locomotives through DCC++EX. + +## What is the Track Manager +Track Manger (TM from now on) is an integral part of DCC++EX software that is responsible for: +- Managing track power state. +- Monitoring track overloads and shorts. +- Routing the DCC main or prog track waveforms to the correct Motor Driver and thus track. +- Managing the JOIN feature. +- Intercepting throttle commands to locos running on DC tracks. +- Handling user or EXRAIL commands to switch track status. + +In the default scenario of a single DCC track and a PROG track, the TM behaves as for the previous versions of DCC++EX so if thats what you want, you dont need to mess with it. + +The TM is able to handle up to 8 separate track domains. Each domain requires a hardware driver to supply track voltage. A typical motor driver shield supplies two tracks, which is what we have used in the past as main and prog. + +Unlike the previous version where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows : +- None, any or all the tracks can be DCC Main. +- None or ONE track may be DCC prog at any given time. +- Any track may be powered on or off independently of the others. +- Any track may be disconnected from the DCC signal and used as a DC track with a given loco address. (See DC discussion later) + +With such flexibility comes responsibility... the scope for making mistakes is right there! + +## Using the Track Manager (DCC) +TM names the tracks A to H. In a default setup, you will normally have tracks A and B where A will default to be the DCC main signal and B will be the DCC prog. + +There is a new user command `<=>` which is used to control the TM but the `<0>` and `<1>` commands operate as before. + +- `<=>` lists the current track settings. +In a default setup this will normally return +``` + <=A DCC> + <=B PROG> +``` +- `<=t DCC>` sets track t (A..H) to use the DCC main track. For example `<=C DCC>` sets track C. All tracks that are set to DCC will receive the same DCC signal waveform. +- `<=t PROG>` Sets track t (A..H) to be the one and only PROG track. Any previous PROG track is turned off. +- `<=t OFF>` turns off the track t. It will not power on with `<1>` because it will not know what signal to send. + +In an all-DCC environment it is unlikely that you will need to do anything other than setting any additional tracks (C...H) as DCC in your `mySetup.h` file. + +Bear in mind that a track may actually be only connected to DCC accessories such as signals and turnouts... your layout, your choice. + +## using the Track Manager (DC) + +TM allows any or all of your tracks to be individually selected as a DC track which responds to throttle commands on any given loco address. So for example if track A is set to DC address 55, then any throttle commands to loco 55 will be transmitted as DC onto track A and thus a DC loco can be driven along that track. almost exactly as if it was DCC. +Your throttle (JMRI, EX-Webthrottle, Wkithrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change. + +For a simple Command Station setup to run just two DC tracks instead of DCC, you only need to assign DC addresses to tracks A and B. If you want DCC on track A and DC on track B, you just need to set track B to a suitable DC address. + +The command to set a track to a DC address is as follows +- `<=t DC a>` Sets track t (A..H) to use loco address a. e.g. <=A DC 3> + +A simple 2 separate loop DC track, wired the traditional way in opposite directions, may be set like this to use loco addresses 1 and 2. +``` +<=A DC 1> +<=A DC 2> +``` + +### Crossing between DC tracks + +There are some slightly mind-bending issues to be addressed, especially if you want to be able to cross between two separate DC tracks or use your layout in DCC or DC mode. This is because the control of DC loco direction is relative to the TRACK and not the LOCO. (you turn a DC loco round on the track and it continues in the same geographical direction. You turn a DCC loco around and it continues to go forwards or backwards in the opposite geographical direction.) + +Generally DC tracks are wired so that two mainline tracks are in opposite direction which makes operation easy BUT crossovers between tracks will cause shorts unless you have very complex switching arrangements. +This is generally incompatible with DCC wiring which expects to be able to cross between tracks with impunity because they are all wired with the same polarity. + +To get over this issue TM allows the polarity of a DC track to be swapped so that tracks wired for DCC may be switched to DC with a polarity chosen at run time according to your operations. So, for example, you may have two loops with a crossing between them. Normally you need them in opposite directions, but when you need to drive over the crossing, you need to switch one or other track so that they are at the same polarity. +(This is a good case for using EXRAIL to help) + +The command `<=t DCX a>` will set track t (A..H) to be DC but with reversed polarity compared with a track set to DC. + +Its perfectly OK to cross between DC tracks by setting them to the same loco address and making sure you get the polarity right! + +## Connecting Hardware +Each track requires hardware to control it +- Power on/off +- Polarity (direction, signal etc) +- Brake (shorts tracks together) +- Current (analog reading) + +The standard motor shields provide this for two separate tracks and are predictable and easy to use. However STACKING shields is not a viable way of adding more tracks because it prevents the software from gaining access to the individual track pins. Similarly, wiring all the signal pins together for example, will give you a shared DCC signal but it will eliminate any possibility of switching the track purpose at run time. So, you are going to have to understand enough to wire track drivers to various pins if you wish to extend beyond 2 tracks and take advantage of TM. + +You will also need to consider the implications of differing electronic implementations that would cause unexpected issues when a loco moves between tracks. We know this works fine for a typical shield because we use `<1 JOIN>` quite happily but this may be different if you mix hardware types..... (NOT MY PROBLEM !) + +The easiest way to consider the wiring is to treat each track individually (either as a separate driver or as half of a shield). + +You will require,for each track, on the Arduino: +- A GPIO pin (or a HAL vpin perhaps on an I2C extender, code TBA!!!) to switch power. +- A GPIO pin to switch the signal direction +- A GPIO pin with PWM capability to switch the Brake (you may omit this if you dont want any DC capability) +- Optionally An Analog pin to read the current (unless your hardware cant do that, perhaps its just feeding a booster) +- Optionally a GPIO fault pin if thats how your hardware works. (NOT recommended as you're going to run out of pins) + +IF you have no more than 3 tracks and you can arrange for the signal pins to be one of 11,12,13 on a Mega, THEN there is a slight advantage internally and the waveform will be super-sharp. + +**Hardware that has two signal pins still needs some code thought!!!!!!!!** + + +## Configuring the Software + +Configuring the software to provide more tracks is a simple extension of the existing method of customising the #define of MOTOR_SHIELD_TYPE in config.h +Since there can be no standard setup of your wiring and hardware choices, it will be necessary to create your custom built MOTOR_SHIELD_TYPE in the manner described in MotorDrivers.h and simply continue to add more `new MotorDriver(` definitions to the list, providing all the pin numbers and electronic limits for each track. (or even shorten the list to 1) + +## Using EXRAIL to control TM +EXRAIL has a single additional command that can be used to automate TM. + +- `SET_TRACK(t,mode)` +where t is the track letter A..H and mode is one of +- `OFF` track is switched off +- `DCC` track gets DCC signal +- `PROG` track gets DCC prog signal +- `DC` track is set to DC mode with the cab address of the currently executing EXRAIL sequence. +- `DCX` as DC but with reversed polarity. + +DC/DCX are designed so that you can be automating a DCC loco, drive it onto a separate track and switch to DC without having to know the cab address. (e.g AUTOMATION) +If however you are just running a ROUTE you can always do something like this: +``` + ROUTE(77,"Set track G to DC 123") + SETLOCO(123) + SET_TRACK(G,DC) + DONE +``` + +## Where and How for the Code. +The TM code is primarily in TrackManager.cpp which is responsible for coordinating the track settings and commands. + +Each individual track is handled by an instance of MotorDriver created from the MOTOPR_SHIELD_TYPE definition in config.h + +Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow. \ No newline at end of file From 99c7ff6c3fa656d6011ae965fed2d477d4027061 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Mar 2022 12:30:21 +0000 Subject: [PATCH 078/870] EXPERIMENTAL vpin for motorDriver power No need to use fastpin for power, so we can allow a remoted vpin which helps when TrafficManger is running short of pins --- CommandStation-EX.ino | 3 +++ DCC.cpp | 3 --- MotorDriver.cpp | 11 +++++------ MotorDriver.h | 8 +++++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 8d650f95f..cd23f0e71 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -84,6 +84,9 @@ void setup() EthernetInterface::setup(); #endif // ETHERNET_ON +// Initialise HAL layer before reading EEprom or setting up MotorDrivers + IODevice::begin(); + // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) // Standard supported devices have pre-configured macros but custome hardware installations require diff --git a/DCC.cpp b/DCC.cpp index 977a30439..05fb00a32 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -64,9 +64,6 @@ void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); - // Initialise HAL layer before reading EEprom. - IODevice::begin(); - #ifndef DISABLE_EEPROM // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 348a9548d..0967ec5e1 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -33,11 +33,10 @@ bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; -MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, +MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { powerPin=power_pin; - getFastPin(F("POWER"),powerPin,fastPowerPin); - pinMode(powerPin, OUTPUT); + IODevice::write(powerPin,LOW);// set to OUTPUT and off signalPin=signal_pin; getFastPin(F("SIG"),signalPin,fastSignalPin); @@ -102,9 +101,9 @@ void MotorDriver::setPower(POWERMODE mode) { // on the Pololu board if brake is wired to ^D2. setBrake(true); setBrake(false); - setHIGH(fastPowerPin); + IODevice::write(powerPin,HIGH); } - else setLOW(fastPowerPin); + else IODevice::write(powerPin,LOW); powerMode=mode; } @@ -160,7 +159,7 @@ int MotorDriver::getCurrentRaw() { current = analogRead(currentPin)-senseOffset; interrupts(); if (current<0) current=0-current; - if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin)) + if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) return (current == 0 ? -1 : -current); return current; diff --git a/MotorDriver.h b/MotorDriver.h index c69f63dd4..7f3139721 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -22,6 +22,7 @@ #ifndef MotorDriver_h #define MotorDriver_h #include "FSH.h" +#include "IODevice.h" // Virtualised Motor shield 1-track hardware Interface @@ -48,7 +49,7 @@ enum class POWERMODE : byte { OFF, ON, OVERLOAD }; class MotorDriver { public: - MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, + MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); virtual void setPower( POWERMODE mode); virtual POWERMODE getPower() { return powerMode;} @@ -75,8 +76,9 @@ class MotorDriver { void getFastPin(const FSH* type,int pin, FASTPIN & result) { getFastPin(type, pin, 0, result); } - byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin; - FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; + VPIN powerPin; + byte signalPin, signalPin2, currentPin, faultPin, brakePin; + FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; bool dualSignal; // true to use signalPin2 bool invertBrake; // brake pin passed as negative means pin is inverted float senseFactor; From a10fca2b12379731c1934aae1303d58659bc5f55 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 23 Mar 2022 17:06:15 +0000 Subject: [PATCH 079/870] TM Power setting fixes --- Release_Notes/TrackManager.md | 8 ++++++-- TrackManager.cpp | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Release_Notes/TrackManager.md b/Release_Notes/TrackManager.md index 5085d96a3..b7361dbd9 100644 --- a/Release_Notes/TrackManager.md +++ b/Release_Notes/TrackManager.md @@ -1,5 +1,6 @@ # DCC++EX Track Manager +Chris Harlow 2022/03/23 **If you are only interested in a standard setup using just a DCC track and PROG track, then you DO NOT need to read the rest of this document.** @@ -45,10 +46,13 @@ In an all-DCC environment it is unlikely that you will need to do anything other Bear in mind that a track may actually be only connected to DCC accessories such as signals and turnouts... your layout, your choice. +Note that when setting a track to PROG or OFF, its power is switched off automatically. (The PROG track manages power on an as-needed basis under normal circumstances. +When setting a track to MAIN (or DC, DCX see later) the power is applied according to the most recent `<1>` or `<0>` command as being the most compatible with previous versions. + ## using the Track Manager (DC) TM allows any or all of your tracks to be individually selected as a DC track which responds to throttle commands on any given loco address. So for example if track A is set to DC address 55, then any throttle commands to loco 55 will be transmitted as DC onto track A and thus a DC loco can be driven along that track. almost exactly as if it was DCC. -Your throttle (JMRI, EX-Webthrottle, Wkithrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change. +Your throttle (JMRI, EX-Webthrottle, Withrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change. For a simple Command Station setup to run just two DC tracks instead of DCC, you only need to assign DC addresses to tracks A and B. If you want DCC on track A and DC on track B, you just need to set track B to a suitable DC address. @@ -105,7 +109,7 @@ IF you have no more than 3 tracks and you can arrange for the signal pins to be Configuring the software to provide more tracks is a simple extension of the existing method of customising the #define of MOTOR_SHIELD_TYPE in config.h Since there can be no standard setup of your wiring and hardware choices, it will be necessary to create your custom built MOTOR_SHIELD_TYPE in the manner described in MotorDrivers.h and simply continue to add more `new MotorDriver(` definitions to the list, providing all the pin numbers and electronic limits for each track. (or even shorten the list to 1) -## Using EXRAIL to control TM +## Using EXRAIL to control Track Manager EXRAIL has a single additional command that can be used to automate TM. - `SET_TRACK(t,mode)` diff --git a/TrackManager.cpp b/TrackManager.cpp index 074f8dfac..896d3d9f2 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -79,7 +79,10 @@ void TrackManager::Setup(const FSH * shieldname, void TrackManager::addTrack(byte t, MotorDriver* driver) { track[t]=driver; trackMode[t]=TRACK_MODE_OFF; - if (driver) lastTrack=t; + if (driver) { + lastTrack=t; + track[t]->setPower(POWERMODE::OFF); + } } void TrackManager::setDCCSignal( bool on) { @@ -109,12 +112,14 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr if (mode==TRACK_MODE_PROG) { // only allow 1 track to be prog FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_PROG) trackMode[t]=TRACK_MODE_OFF; + if (trackMode[t]==TRACK_MODE_PROG) { + track[t]->setPower(POWERMODE::OFF); + trackMode[t]=TRACK_MODE_OFF; + } } trackMode[trackToSet]=mode; trackDCAddr[trackToSet]=dcAddr; - // re-evaluate HighAccuracy mode // We can only do this is all main and prog tracks agree bool canDo=true; @@ -122,6 +127,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG) canDo &= track[t]->isPWMCapable(); MotorDriver::usePWM=canDo; + + // Normal running tracks are set to the global power state + track[trackToSet]->setPower( + (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? + mainPowerGuess : POWERMODE::OFF); + return true; } From 78810d0e3650e5f0bb541dce9cc1346b57bf8afb Mon Sep 17 00:00:00 2001 From: Kcsmith0708 Date: Thu, 24 Mar 2022 11:35:00 -0400 Subject: [PATCH 080/870] Update version.h (#220) * Update version.h Updated 4.0.2 release features cleaned up 4.0.0 feature list * Update version.h Added and Updated 4.0.0 Features * Update version.h modified 4.0.0 new JMRI features * Update version.h Updated4.0.0 release information --- version.h | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/version.h b/version.h index cb476146e..7ba1ec49d 100644 --- a/version.h +++ b/version.h @@ -6,41 +6,51 @@ #define VERSION "4.0.2" // 4.0.2 EXRAIL additions: +// Automatic ALIAS(name) +// DCCEXPArser.cpp now accepts Underscore in Alias Names +// SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF) +// New DC track function and DCX reverse polarity function // Servo signals (SERVO_SIGNAL) // High-On signal pins (SIGNALH) // Wait for analog value (ATGTE, ATLT) +// TrafficManager DCC & DC up to 8 Districts Architecture // 4.0.1 EXRAIL BROADCAST("msg") // EXRAIL POWERON // 4.0.0 Major functional and non-functional changes. // Engine Driver "DriveAway" feature enhancement -//.......JMRI feature enhancement. Provides for multiple additional DCC++EX wifi -//.........connections as accessory controllers or CS for a programming track when -//.........motor shield is added -// New HAL added for I/O (digital and analogue inputs and outputs, servos etc). +// 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station +// New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability. +// EX-Rail Function commands for creating Automation, Route & Sequence Scripts +// EX-RAIL “ROSTER” Engines Id & Function key layout on Engine Driver or WiThrottle +// EX-RAIL DCC++EX Commands to Control EX-RAIL via JMRI Send pane and IDE Serial monitors +// New JMRI feature enhancements; +// Reads DCC++EX EEPROM & automatically uploades any Signals, DCC Turnouts, Servo Turnouts, Vpin Turnouts , & Output pane +// Turnout class revised to expand turnout capabilities, new commands added. +// Provides for multiple additional DCC++EX WiFi connections as accessory controllers or CS for a programming track when Motor Shields are added +// Supports Multiple Command Station connections and individual tracking of Send DCC++ Command panes and DCC++ Traffic Monitor panes +// New HAL added for I/O (digital and analogue inputs and outputs, servos etc) +// Automatically detects & connects to supported devices included in your config.h file // 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). -//.........Added diagnostic command to show configured devices -// Native non-blocking I2C drivers for AVR and Nano architectures (fallback -// to blocking Wire library for other platforms). +// Added diagnostic command to show configured devices +// New Processor Support added +// Compiles on Nano Every and Teensy +// Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms). +// Can disable EEPROM code // 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. +// 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. -// Can disable EEPROM code // Can define border between long and short addresses // Turnout and accessory states (thrown/closed = 0/1 or 1/0) can be set to match RCN-213 // Bugfix: one-off error in CIPSEND drop -// Compiles on Nano Every // Bugfix: disgnostic display of ack pulses >32kus -//.......Bugfix: Current read from wrong ADC during interrupt -// ... +// Bugfix: Current read from wrong ADC during interrupt +// 3.2.0 Development Release Includes all of 3.1.1 thru 3.1.7 enhancements // 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 From 49e0a0e5f58ea80512591c89c941ae38118e4865 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 28 Mar 2022 14:44:41 +0100 Subject: [PATCH 081/870] DC track change fixes --- MotorDriver.h | 3 +++ TrackManager.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/MotorDriver.h b/MotorDriver.h index 7f3139721..981b25928 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -60,6 +60,9 @@ class MotorDriver { virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); virtual int mA2raw( unsigned int mA); + inline bool canBrake() { + return brakePin!=UNUSED_PIN; + } inline int getRawCurrentTripValue() { return rawCurrentTripValue; } diff --git a/TrackManager.cpp b/TrackManager.cpp index 896d3d9f2..13a3280d2 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -109,6 +109,14 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; + + // DC tracks require a motorDriver that can set brake! + if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) + && !track[trackToSet]->canBrake()) { + DIAG(F("No brake:no DC")); + return false; + } + if (mode==TRACK_MODE_PROG) { // only allow 1 track to be prog FOR_EACH_TRACK(t) @@ -120,6 +128,29 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr trackMode[trackToSet]=mode; trackDCAddr[trackToSet]=dcAddr; + // When a track is switched, we must clear any side effects of its previous + // state, otherwise trains run away or just dont move. + if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) { + // DC tracks need to be given speed of the throttle for that cab address + // otherwise will not match other tracks on same cab. + // This also needs to allow for inverted DCX + + int16_t speed1=DCC::getThrottleSpeed(dcAddr); + byte speedByte; + if (speed1<0) speedByte=0; + else { + speedByte=speed1; + bool direction=DCC::getThrottleDirection(dcAddr); + if (mode==TRACK_MODE_DCX) direction=!direction; + if (direction) speedByte|=0x80; + } + track[trackToSet]->setDCSignal(speedByte); + } + else { + // DCC tracks need to have the brake set off or they will not work. + track[trackToSet]->setBrake(false); + } + // re-evaluate HighAccuracy mode // We can only do this is all main and prog tracks agree bool canDo=true; @@ -128,6 +159,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr canDo &= track[t]->isPWMCapable(); MotorDriver::usePWM=canDo; + // Normal running tracks are set to the global power state track[trackToSet]->setPower( (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? From 2727332be3a943bc59630df7c1e362ab11858782 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 28 Mar 2022 14:53:16 +0100 Subject: [PATCH 082/870] Update version.h --- version.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/version.h b/version.h index 7ba1ec49d..2772cd66e 100644 --- a/version.h +++ b/version.h @@ -4,16 +4,17 @@ #include "StringFormatter.h" -#define VERSION "4.0.2" +#define VERSION "4.0.3" +// 4.0.3 Tarck Manager additions: +// SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF) +// New DC track function and DCX reverse polarity function +// TrackManager DCC & DC up to 8 Districts Architecture // 4.0.2 EXRAIL additions: // Automatic ALIAS(name) // DCCEXPArser.cpp now accepts Underscore in Alias Names -// SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF) -// New DC track function and DCX reverse polarity function // Servo signals (SERVO_SIGNAL) // High-On signal pins (SIGNALH) // Wait for analog value (ATGTE, ATLT) -// TrafficManager DCC & DC up to 8 Districts Architecture // 4.0.1 EXRAIL BROADCAST("msg") // EXRAIL POWERON // 4.0.0 Major functional and non-functional changes. From 3f283620d3508855065adc235a82e0473d3d3d79 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 28 Mar 2022 15:21:07 +0100 Subject: [PATCH 083/870] Include default brake pins for standard shield. --- MotorDrivers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index cfb0dd848..b2b798dc3 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -47,8 +47,8 @@ // // Arduino standard Motor Shield #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 2000, UNUSED_PIN) // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ From ef50665c1697140ccb4e143baeca0c9f3fa561ee Mon Sep 17 00:00:00 2001 From: Fred Date: Wed, 30 Mar 2022 09:28:52 -0400 Subject: [PATCH 084/870] Update TrackManager.md Add note section about zero stretching --- Release_Notes/TrackManager.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Release_Notes/TrackManager.md b/Release_Notes/TrackManager.md index b7361dbd9..db80f7c26 100644 --- a/Release_Notes/TrackManager.md +++ b/Release_Notes/TrackManager.md @@ -19,13 +19,15 @@ In the default scenario of a single DCC track and a PROG track, the TM behaves a The TM is able to handle up to 8 separate track domains. Each domain requires a hardware driver to supply track voltage. A typical motor driver shield supplies two tracks, which is what we have used in the past as main and prog. -Unlike the previous version where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows : +Unlike the previous version of DCC++EX, where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows : - None, any or all the tracks can be DCC Main. - None or ONE track may be DCC prog at any given time. - Any track may be powered on or off independently of the others. - Any track may be disconnected from the DCC signal and used as a DC track with a given loco address. (See DC discussion later) -With such flexibility comes responsibility... the scope for making mistakes is right there! +With such flexibility comes responsibility... the potential for making mistakes means taking extra care with your configuration! + +**NOTE** TM does NOT use "zero stretching" to control your DC motor. Instead, it uses true Pulse Width Modulation (PWM) to efficiently run your loco using the same method a decoder uses to control a DCC loco's motor. DC locos can even run better on TM than they can on a normal analog throttle, especially at low speed, since it is always applying the full track voltage, albeit in pulses of varying duration. ## Using the Track Manager (DCC) TM names the tracks A to H. In a default setup, you will normally have tracks A and B where A will default to be the DCC main signal and B will be the DCC prog. @@ -134,4 +136,4 @@ The TM code is primarily in TrackManager.cpp which is responsible for coordinati Each individual track is handled by an instance of MotorDriver created from the MOTOPR_SHIELD_TYPE definition in config.h -Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow. \ No newline at end of file +Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow. From 7dd680ccd5704982351a53d33e60ee31a8a47084 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 31 Mar 2022 10:03:27 +0100 Subject: [PATCH 085/870] Brake fix --- .gitignore | 1 + MotorDriver.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2bf0b5e1c..5e5d3557d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ myFilter.cpp myAutomation.h myFilter.cpp myLayout.h +.vscode/extensions.json diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 0967ec5e1..ecad82654 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -56,7 +56,7 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 brakePin=invertBrake ? 0-brake_pin : brake_pin; getFastPin(F("BRAKE"),brakePin,fastBrakePin); pinMode(brakePin, OUTPUT); - setBrake(false); + setBrake(true); // start with brake on in case we hace DC stuff going on } else brakePin=UNUSED_PIN; From ece342f037ef0298e6e144aeb556d36d20419dcc Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 31 Mar 2022 22:19:13 +0100 Subject: [PATCH 086/870] DC power fix --- MotorDriver.cpp | 4 --- TrackManager.cpp | 69 +++++++++++++++++++++++++++++++++--------------- TrackManager.h | 1 + 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index ecad82654..517e2ddcf 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -97,10 +97,6 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { - // toggle brake before turning power on - resets overcurrent error - // on the Pololu board if brake is wired to ^D2. - setBrake(true); - setBrake(false); IODevice::write(powerPin,HIGH); } else IODevice::write(powerPin,LOW); diff --git a/TrackManager.cpp b/TrackManager.cpp index 13a3280d2..5618644c1 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -134,17 +134,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // DC tracks need to be given speed of the throttle for that cab address // otherwise will not match other tracks on same cab. // This also needs to allow for inverted DCX - - int16_t speed1=DCC::getThrottleSpeed(dcAddr); - byte speedByte; - if (speed1<0) speedByte=0; - else { - speedByte=speed1; - bool direction=DCC::getThrottleDirection(dcAddr); - if (mode==TRACK_MODE_DCX) direction=!direction; - if (direction) speedByte|=0x80; - } - track[trackToSet]->setDCSignal(speedByte); + applyDCSpeed(trackToSet); + } else { // DCC tracks need to have the brake set off or they will not work. @@ -168,6 +159,20 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr return true; } +void TrackManager::applyDCSpeed(byte t) { + + int16_t speed1=DCC::getThrottleSpeed(trackDCAddr[t]); + byte speedByte; + if (speed1<0) speedByte=0; + else { + speedByte=speed1; + bool direction=DCC::getThrottleDirection(trackDCAddr[t]); + if (trackMode[t]==TRACK_MODE_DCX) direction=!direction; + if (direction) speedByte|=0x80; + } + track[t]->setDCSignal(speedByte); +} + bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) { @@ -240,18 +245,38 @@ MotorDriver * TrackManager::getProgDriver() { FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_PROG) return track[t]; return NULL; -} +} + void TrackManager::setPower2(bool setProg,POWERMODE mode) { - if (setProg) { - APPLY_BY_MODE(TRACK_MODE_PROG,setPower(mode)) - } - else { - mainPowerGuess=mode; - FOR_EACH_TRACK(t) - if (track[t] - && trackMode[t]!=TRACK_MODE_OFF - && trackMode[t]!=TRACK_MODE_PROG - ) track[t]->setPower(mode); + if (!setProg) mainPowerGuess=mode; + FOR_EACH_TRACK(t) { + MotorDriver * driver=track[t]; + if (!driver) continue; + switch (trackMode[t]) { + case TRACK_MODE_MAIN: + if (setProg) break; + // toggle brake before turning power on - resets overcurrent error + // on the Pololu board if brake is wired to ^D2. + driver->setBrake(true); + driver->setBrake(false); // DCC runs with brake off + driver->setPower(mode); + break; + case TRACK_MODE_DC: + case TRACK_MODE_DCX: + if (setProg) break; + driver->setBrake(true); // DC starts with brake on + applyDCSpeed(t); // speed match DCC throttles + driver->setPower(mode); + break; + case TRACK_MODE_PROG: + if (!setProg) break; + driver->setBrake(true); + driver->setBrake(false); + driver->setPower(mode); + break; + case TRACK_MODE_OFF: + break; + } } } diff --git a/TrackManager.h b/TrackManager.h index dde4b905b..8b12f8610 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -78,6 +78,7 @@ class TrackManager { static byte lastTrack; static byte nextCycleTrack; static POWERMODE mainPowerGuess; + static void applyDCSpeed(byte t); static MotorDriver* track[MAX_TRACKS]; static TRACK_MODE trackMode[MAX_TRACKS]; From f878c1d01cdf256f4eda074cde5f7d94013e5142 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 1 Apr 2022 21:28:21 +0800 Subject: [PATCH 087/870] Initial SAMD defines --- DCCTimer.h | 3 +++ defines.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DCCTimer.h b/DCCTimer.h index fb8184b38..04d171ff7 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -121,6 +121,9 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { #elif defined(ARDUINO_ARCH_ESP32) #define ARDUINO_TYPE "ESP32" #undef HAS_AVR_WDT +#elif defined(ARDUINO_ARCH_SAMD) +#define ARDUINO_TYPE "SAMD21" +#undef HAS_AVR_WDT #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h #endif diff --git a/defines.h b/defines.h index 9ce154d85..c24d6af45 100644 --- a/defines.h +++ b/defines.h @@ -40,7 +40,7 @@ // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) #define BIG_RAM #endif #if ENABLE_WIFI From cd0dfc565c8faf1f3c363ea8ed5f7cabe8f779e1 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 1 Apr 2022 21:28:21 +0800 Subject: [PATCH 088/870] Initial SAMD defines --- DCCTimer.h | 3 +++ defines.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DCCTimer.h b/DCCTimer.h index fb8184b38..04d171ff7 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -121,6 +121,9 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { #elif defined(ARDUINO_ARCH_ESP32) #define ARDUINO_TYPE "ESP32" #undef HAS_AVR_WDT +#elif defined(ARDUINO_ARCH_SAMD) +#define ARDUINO_TYPE "SAMD21" +#undef HAS_AVR_WDT #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h #endif diff --git a/defines.h b/defines.h index 9ce154d85..c24d6af45 100644 --- a/defines.h +++ b/defines.h @@ -40,7 +40,7 @@ // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) #define BIG_RAM #endif #if ENABLE_WIFI From 5dfc014f495d6879fae6d0767f9d5dae7f8aa24f Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 5 Apr 2022 09:24:29 +0800 Subject: [PATCH 089/870] Some useful code plug debug goo --- CommandStation-EX.ino | 31 +++++++-------- DCCEXParser.cpp | 2 + DCCTimerSAMD.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++ I2CManager.h | 4 +- MotorDriver.cpp | 6 ++- MotorDriver.h | 2 +- SerialManager.cpp | 8 +++- defines.h | 17 ++++---- platformio.ini | 16 +++----- 9 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 DCCTimerSAMD.cpp diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index cd23f0e71..d2627ea41 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -66,6 +66,7 @@ void setup() SerialManager::init(); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); + DIAG(F("Platform: %s"), F(ARDUINO_TYPE)); // PMA - temporary CONDITIONAL_LCD_START { // This block is still executed for DIAGS if LCD not in use @@ -85,7 +86,7 @@ void setup() #endif // ETHERNET_ON // Initialise HAL layer before reading EEprom or setting up MotorDrivers - IODevice::begin(); +// IODevice::begin(); // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) @@ -100,18 +101,18 @@ void setup() // Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h. // This can be used to create turnouts, outputs, sensors etc. through the normal text commands. +// PMA - how to handle __has_include?? #if __has_include ( "mySetup.h") - #define SETUP(cmd) DCCEXParser::parse(F(cmd)) - #include "mySetup.h" - #undef SETUP + #define SETUP(cmd) DCCEXParser::parse(F(cmd)) + #include "mySetup.h" + #undef SETUP #endif - #if defined(LCN_SERIAL) - LCN_SERIAL.begin(115200); - LCN::init(LCN_SERIAL); - #endif - - LCD(3,F("Ready")); +// #if defined(LCN_SERIAL) +// LCN_SERIAL.begin(115200); +// LCN::init(LCN_SERIAL); +// #endif + LCD(3, F("Ready")); CommandDistributor::broadcastPower(); } @@ -121,7 +122,7 @@ void loop() // Responsibility 1: Handle DCC background processes // (loco reminders and power checks) - DCC::loop(); +// DCC::loop(); // Responsibility 2: handle any incoming commands on USB connection SerialManager::loop(); @@ -134,18 +135,18 @@ void loop() EthernetInterface::loop(); #endif - RMFT::loop(); // ignored if no automation +// RMFT::loop(); // ignored if no automation #if defined(LCN_SERIAL) LCN::loop(); #endif - LCDDisplay::loop(); // ignored if LCD not in use +// LCDDisplay::loop(); // ignored if LCD not in use // Handle/update IO devices. - IODevice::loop(); + //IODevice::loop(); - Sensor::checkAll(); // Update and print changes + //Sensor::checkAll(); // Update and print changes // Report any decrease in memory (will automatically trigger on first call) static int ramLowWatermark = __INT_MAX__; // replaced on first loop diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2fc8f6b5b..ad4054d9d 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -781,7 +781,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms delay(50); // wait for the prescaller time to expire #else +#ifdef ARDUINO_ARCH_ESP ESP.restart(); +#endif #endif break; // and if we didnt restart } diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp new file mode 100644 index 000000000..74ec18b0b --- /dev/null +++ b/DCCTimerSAMD.cpp @@ -0,0 +1,90 @@ +/* + * © 2021 Mike S + * © 2021 Harald Barth + * © 2021 Fred Decker + * © 2021 Chris Harlow + * © 2021 David Cutting + * © 2022 Paul M. Antoine + * All rights reserved. + * + * This file is part of Asbelos DCC 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 . + */ + +// ATTENTION: this file only compiles on a TEENSY +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef ARDUINO_ARCH_SAMD + +#include "FSH.h" //PMA temp debug +#include "DIAG.h" //PMA temp debug +#include "DCCTimer.h" + +INTERRUPT_CALLBACK interruptHandler=0; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + + + + + } + +bool DCCTimer::isPWMPin(byte pin) { + //SAMD: digitalPinHasPWM, todo + (void) pin; + return false; // TODO what are the relevant pins? + } + +void DCCTimer::setPWM(byte pin, bool high) { + // TODO what are the relevant pins? + (void) pin; + (void) high; +} + +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C; + volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040; +// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044; +// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048; + + volatile uint32_t m1 = *serno1; + volatile uint32_t m2 = *serno2; + mac[0] = m1 >> 8; + mac[1] = m1 >> 0; + mac[2] = m2 >> 24; + mac[3] = m2 >> 16; + mac[4] = m2 >> 8; + mac[5] = m2 >> 0; +} + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = freeMemory(); + interrupts(); + return retval; +} + +extern "C" char* sbrk(int incr); + +int DCCTimer::freeMemory() { + char top; + return (int)(&top - reinterpret_cast(sbrk(0))); +} + +#endif \ No newline at end of file diff --git a/I2CManager.h b/I2CManager.h index 116326134..67fd1ad79 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -111,10 +111,10 @@ */ // Uncomment following line to enable Wire library instead of native I2C drivers -//#define I2C_USE_WIRE +#define I2C_USE_WIRE // Uncomment following line to disable the use of interrupts by the native I2C drivers. -//#define I2C_NO_INTERRUPTS +#define I2C_NO_INTERRUPTS // Default to use interrupts within the native I2C drivers. #ifndef I2C_NO_INTERRUPTS diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 517e2ddcf..c40a0f1f4 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -192,8 +192,12 @@ int MotorDriver::mA2raw( unsigned int mA) { void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { // DIAG(F("MotorDriver %S Pin=%d,"),type,pin); - (void) type; // avoid compiler warning if diag not used above. + (void) type; // avoid compiler warning if diag not used above. +#if defined(ARDUINO_ARCH_SAMD) + PortGroup *port = digitalPinToPort(pin); +#else uint8_t port = digitalPinToPort(pin); +#endif if (input) result.inout = portInputRegister(port); else diff --git a/MotorDriver.h b/MotorDriver.h index 981b25928..3a21b89ee 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -30,7 +30,7 @@ #define UNUSED_PIN 127 // inside int8_t #endif -#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) struct FASTPIN { volatile uint32_t *inout; uint32_t maskHIGH; diff --git a/SerialManager.cpp b/SerialManager.cpp index 871722760..2dce51bda 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -32,9 +32,15 @@ SerialManager::SerialManager(Stream * myserial) { } void SerialManager::init() { - while (!Serial && millis() < 5000); // wait max 5s for Serial to start +// while (!Serial && millis() < 5000); // wait max 5s for Serial to start +#if defined(ARDUINO_ARCH_SAMD) + SerialUSB.begin(115200); + while (!SerialUSB); // PMA - temporary for debuggering purpoises + new SerialManager(&SerialUSB); +#else Serial.begin(115200); new SerialManager(&Serial); +#endif #ifdef SERIAL3_COMMANDS Serial3.begin(115200); new SerialManager(&Serial3); diff --git a/defines.h b/defines.h index c24d6af45..aba5daf32 100644 --- a/defines.h +++ b/defines.h @@ -77,14 +77,15 @@ // This defines the speed at which the Arduino will communicate with the ESP8266 module. // Currently only devices which can communicate at 115200 are supported. // -#define WIFI_SERIAL_LINK_SPEED 115200 +#define WIFI_SERIAL_LINK_SPEED 9600 -#if __has_include ( "myAutomation.h") - #if defined(BIG_RAM) || defined(DISABLE_EEPROM) - #define EXRAIL_ACTIVE - #else - #define EXRAIL_WARNING - #endif -#endif +// TODO: PMA - figure out why enabling this causes the CS to crashe immediately after starting the motor driver +//#if __has_include ( "myAutomation.h") +// #if defined(BIG_RAM) || defined(DISABLE_EEPROM) +// #define EXRAIL_ACTIVE +// #else +// #define EXRAIL_WARNING +// #endif +//#endif #endif diff --git a/platformio.ini b/platformio.ini index 8bba2973e..59136b375 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,13 +10,9 @@ [platformio] default_envs = - mega2560 - uno - mega328 - unowifiR2 - nano -src_dir = . -include_dir = . + samd21 +src_dir = /Users/paul/Projects/CommandStation-EX +include_dir = /Users/paul/Projects/CommandStation-EX [env] build_flags = -Wall -Wextra @@ -25,12 +21,13 @@ build_flags = -Wall -Wextra platform = atmelsam board = sparkfun_samd21_dev_usb framework = arduino -upload_protocol = atmel-ice +upload_protocol = sam-ba lib_deps = ${env.lib_deps} SparkFun External EEPROM Arduino Library monitor_speed = 115200 monitor_flags = --echo +build_flags = -std=c++17 [env:mega2560-debug] platform = atmelavr @@ -131,7 +128,6 @@ platform = atmelavr board = nanoatmega328new board_upload.maximum_size = 32256 framework = arduino -lib_deps = - ${env.lib_deps} +lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo From 7312951b2bbde1adc9f91bf43d96c56af0abeceb Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 5 Apr 2022 09:38:33 +0800 Subject: [PATCH 090/870] Platformio.ini to test workflow --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 59136b375..7283f4214 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,8 +11,8 @@ [platformio] default_envs = samd21 -src_dir = /Users/paul/Projects/CommandStation-EX -include_dir = /Users/paul/Projects/CommandStation-EX +src_dir = . +include_dir = . [env] build_flags = -Wall -Wextra From 5ccef35074e47188e11028030a0b2dfe70807f3e Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 5 Apr 2022 12:53:11 +0800 Subject: [PATCH 091/870] ADC config --- CommandStation-EX.ino | 1 - DCCTimerSAMD.cpp | 25 +++++++++++++++++++++++++ MotorDrivers.h | 8 ++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index d2627ea41..219e24553 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -101,7 +101,6 @@ void setup() // Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h. // This can be used to create turnouts, outputs, sensors etc. through the normal text commands. -// PMA - how to handle __has_include?? #if __has_include ( "mySetup.h") #define SETUP(cmd) DCCEXParser::parse(F(cmd)) #include "mySetup.h" diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 74ec18b0b..a3f9c00ca 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -37,8 +37,33 @@ INTERRUPT_CALLBACK interruptHandler=0; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; + // PMA - SAMC used on Firebox has 2 ADCs, so choose which to set up based on pin being used + // TODO: this code will need to be fixed - ADCpin is not in scope... as this is stolen from + // the abandoned rf24 branch + #if defined(ARDUINO_ARCH_SAMC) + Adc* ADC; + if ( (g_APinDescription[ADCpin].ulPeripheralAttribute & PER_ATTR_ADC_MASK) == PER_ATTR_ADC_STD ) { + ADC = ADC0; + } else { + ADC = ADC1; + } + #endif + + // PMA - Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, + // and we need sub-100uS. This code sets it to a read speed of around 21uS, and enables 12-bit + ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 + ADC_CTRLB_RESSEL_12BIT; // Result on 12 bits + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization } diff --git a/MotorDrivers.h b/MotorDrivers.h index b2b798dc3..accddf178 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -46,9 +46,17 @@ // (HIGH == release brake) // // Arduino standard Motor Shield +#if defined(ARDUINO_ARCH_SAMD) +// PMA - senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, +// and for 12-bit samples it's more like 0.488, but we probably need to tweak both these +#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 2000, UNUSED_PIN) +#else #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 2000, UNUSED_PIN) +#endif // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ From 46e4dc26288fcd8cc8dd7ff6e83bc365b38d9536 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 7 Apr 2022 16:53:50 +0800 Subject: [PATCH 092/870] Motor driver senseFactor and 10bit ADC --- DCCTimerSAMD.cpp | 2 +- MotorDrivers.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index a3f9c00ca..eb4849be6 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -56,7 +56,7 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 - ADC_CTRLB_RESSEL_12BIT; // Result on 12 bits + ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 diff --git a/MotorDrivers.h b/MotorDrivers.h index accddf178..74aab48bd 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -50,8 +50,8 @@ // PMA - senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, // and for 12-bit samples it's more like 0.488, but we probably need to tweak both these #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 2000, UNUSED_PIN) + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) #else #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ From 63c9ca414d7fcc2af5b9289d369c6962a46b5cb7 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 7 Apr 2022 16:55:33 +0800 Subject: [PATCH 093/870] Initial timer setup code --- DCCTimerSAMD.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index a3f9c00ca..337b1bf01 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -65,7 +65,26 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { ADC->CTRLA.bit.ENABLE = 1; // enable ADC while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization - } + // PMA - actual timer setup goo + // Setup clock sources first + REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1 + GCLK_GENDIV_ID(4); // Apply to GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK + GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source + GCLK_GENCTRL_ID(4); // Select GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock + 4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4 + GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 + while (GCLK->STATUS.bit.SYNCBUSY); + + + + +} bool DCCTimer::isPWMPin(byte pin) { //SAMD: digitalPinHasPWM, todo From 084ddf01e140b76e7d3bc8853e42ca9dc9d06d53 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 8 Apr 2022 10:59:30 +0800 Subject: [PATCH 094/870] More SAMD timer setup --- DCCTimerMEGAAVR.cpp | 2 +- DCCTimerSAMD.cpp | 50 ++++++++++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/DCCTimerMEGAAVR.cpp b/DCCTimerMEGAAVR.cpp index 8b43340de..628b4690d 100644 --- a/DCCTimerMEGAAVR.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -75,7 +75,7 @@ extern char *__malloc_heap_start; // ISR called by timer interrupt every 58uS ISR(TCB0_INT_vect){ - TCB0.INTFLAGS = TCB_CAPT_bm; + TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt request flag interruptHandler(); } diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 19684b28b..b3324a9e9 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -36,18 +36,7 @@ INTERRUPT_CALLBACK interruptHandler=0; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; - - // PMA - SAMC used on Firebox has 2 ADCs, so choose which to set up based on pin being used - // TODO: this code will need to be fixed - ADCpin is not in scope... as this is stolen from - // the abandoned rf24 branch - #if defined(ARDUINO_ARCH_SAMC) - Adc* ADC; - if ( (g_APinDescription[ADCpin].ulPeripheralAttribute & PER_ATTR_ADC_MASK) == PER_ATTR_ADC_STD ) { - ADC = ADC0; - } else { - ADC = ADC1; - } - #endif + noInterrupts(); // PMA - Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, // and we need sub-100uS. This code sets it to a read speed of around 21uS, and enables 12-bit @@ -80,20 +69,47 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { 4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4 GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 while (GCLK->STATUS.bit.SYNCBUSY); - - - + // PMA - assume we're using TCC0 + REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1 + GCLK_GENDIV_ID(4); // Apply to GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK + GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source + GCLK_GENCTRL_ID(4); // Select GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock + 4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4 + GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform + while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync + TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Interrupt on overflow + NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt + interrupts(); } +// PMA - IRQ handler copied from rf24 branch +// TODO: test +void TCC0_Handler() { + if(TCC0->INTFLAG.bit.OVF) { + TCC0->INTFLAG.bit.OVF = 1; + interruptHandler(); + } +} + + bool DCCTimer::isPWMPin(byte pin) { - //SAMD: digitalPinHasPWM, todo + //TODO: SAMD digitalPinHasPWM (void) pin; return false; // TODO what are the relevant pins? } void DCCTimer::setPWM(byte pin, bool high) { - // TODO what are the relevant pins? + // TODO: what are the relevant pins? (void) pin; (void) high; } From a52551babe6cee31f0d08cab4012d910a23a664f Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 8 Apr 2022 12:35:19 +0800 Subject: [PATCH 095/870] SAMD timer code --- DCCTimerSAMD.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index b3324a9e9..deff05b19 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -88,6 +88,18 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Interrupt on overflow + + // PMA - set the frequency + TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val); + unsigned long cycles = F_CPU / 10000000 * 58; // 58uS to cycles + unsigned long pwmPeriod = cycles / 2; + TCC0->PER.reg = pwmPeriod; + while (TCC0->SYNCBUSY.bit.PER); + + // PMA - start it + TCC0->CTRLA.bit.ENABLE = 1; + while (TCC0->SYNCBUSY.bit.ENABLE); + NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt interrupts(); } From 8fa1ba3039ba0e985f000302a4be680f8fc0cb92 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 12 Apr 2022 14:32:10 +0800 Subject: [PATCH 096/870] SAMD21 DCC waveform working --- CommandStation-EX.ino | 22 +++++++++--------- DCCTimerSAMD.cpp | 54 +++++++++++++++++++++++-------------------- MotorDrivers.h | 3 ++- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 219e24553..18342283a 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -85,8 +85,8 @@ void setup() EthernetInterface::setup(); #endif // ETHERNET_ON -// Initialise HAL layer before reading EEprom or setting up MotorDrivers -// IODevice::begin(); + // Initialise HAL layer before reading EEprom or setting up MotorDrivers + IODevice::begin(); // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) @@ -107,10 +107,10 @@ void setup() #undef SETUP #endif -// #if defined(LCN_SERIAL) -// LCN_SERIAL.begin(115200); -// LCN::init(LCN_SERIAL); -// #endif + #if defined(LCN_SERIAL) + LCN_SERIAL.begin(115200); + LCN::init(LCN_SERIAL); + #endif LCD(3, F("Ready")); CommandDistributor::broadcastPower(); } @@ -121,7 +121,7 @@ void loop() // Responsibility 1: Handle DCC background processes // (loco reminders and power checks) -// DCC::loop(); + DCC::loop(); // Responsibility 2: handle any incoming commands on USB connection SerialManager::loop(); @@ -134,18 +134,18 @@ void loop() EthernetInterface::loop(); #endif -// RMFT::loop(); // ignored if no automation + RMFT::loop(); // ignored if no automation #if defined(LCN_SERIAL) LCN::loop(); #endif -// LCDDisplay::loop(); // ignored if LCD not in use + LCDDisplay::loop(); // ignored if LCD not in use // Handle/update IO devices. - //IODevice::loop(); + IODevice::loop(); - //Sensor::checkAll(); // Update and print changes + Sensor::checkAll(); // Update and print changes // Report any decrease in memory (will automatically trigger on first call) static int ramLowWatermark = __INT_MAX__; // replaced on first loop diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index deff05b19..159dd55b2 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -70,55 +70,59 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 while (GCLK->STATUS.bit.SYNCBUSY); - // PMA - assume we're using TCC0 - REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1 - GCLK_GENDIV_ID(4); // Apply to GCLK4 - while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization - - REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK - GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source - GCLK_GENCTRL_ID(4); // Select GCLK4 - while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization - - REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock - 4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4 - GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 - while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization - + // PMA - assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway + // for "normal accuracy" DCC waveform generation. For high accuracy we're going to need + // to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and OP pins + // for each TCC are only available on certain pins TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync - TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Interrupt on overflow // PMA - set the frequency TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val); - unsigned long cycles = F_CPU / 10000000 * 58; // 58uS to cycles - unsigned long pwmPeriod = cycles / 2; - TCC0->PER.reg = pwmPeriod; + TCC0->PER.reg = CLOCK_CYCLES * 2; while (TCC0->SYNCBUSY.bit.PER); // PMA - start it TCC0->CTRLA.bit.ENABLE = 1; while (TCC0->SYNCBUSY.bit.ENABLE); + // PMA - set interrupt condition, priority and enable + TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow + NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt interrupts(); } -// PMA - IRQ handler copied from rf24 branch +// PMA - Timer IRQ handlers replace the dummy handlers (cortex_handlers) +// copied from rf24 branch // TODO: test void TCC0_Handler() { if(TCC0->INTFLAG.bit.OVF) { - TCC0->INTFLAG.bit.OVF = 1; + TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + +void TCC1_Handler() { + if(TCC1->INTFLAG.bit.OVF) { + TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + +void TCC2_Handler() { + if(TCC2->INTFLAG.bit.OVF) { + TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag interruptHandler(); } } bool DCCTimer::isPWMPin(byte pin) { - //TODO: SAMD digitalPinHasPWM - (void) pin; - return false; // TODO what are the relevant pins? - } + //TODO: SAMD test this works! +// return digitalPinHasPWM(pin); + return false; +} void DCCTimer::setPWM(byte pin, bool high) { // TODO: what are the relevant pins? diff --git a/MotorDrivers.h b/MotorDrivers.h index 74aab48bd..5237298da 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -47,7 +47,8 @@ // // Arduino standard Motor Shield #if defined(ARDUINO_ARCH_SAMD) -// PMA - senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, +// PMA - Setup for SAMD21 Sparkfun DEV board +// senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, // and for 12-bit samples it's more like 0.488, but we probably need to tweak both these #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 2000, UNUSED_PIN), \ From 8522e05b1370e9ae512db520a3d30c1f3b0c3906 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 12 Apr 2022 23:28:21 +0100 Subject: [PATCH 097/870] merge issue with prog split --- DCC.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DCC.h b/DCC.h index 9333e1771..0e4dce11b 100644 --- a/DCC.h +++ b/DCC.h @@ -98,7 +98,9 @@ class DCC }; static LOCO speedTable[MAX_LOCOS]; static int lookupSpeedTable(int locoId, bool autoCreate=true); - + static byte cv1(byte opcode, int cv); + static byte cv2(int cv); + private: static byte loopStatus; static void setThrottle2(uint16_t cab, uint8_t speedCode); @@ -109,8 +111,6 @@ class DCC static FSH *shieldName; static byte globalSpeedsteps; - static byte cv1(byte opcode, int cv); - static byte cv2(int cv); static void issueReminders(); static void callback(int value); From 5bdbe3895d723bf251f116dcf5f37c738ab104f9 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 29 Apr 2022 14:55:50 +0100 Subject: [PATCH 098/870] spelling --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 3e03ea467..2dcb4966c 100644 --- a/version.h +++ b/version.h @@ -5,7 +5,7 @@ #define VERSION "4.0.3" -// 4.0.3 Tarck Manager additions: +// 4.0.3 Track Manager additions: // SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF) // New DC track function and DCX reverse polarity function // TrackManager DCC & DC up to 8 Districts Architecture From 43bac3f78e5d5dda7586db982055e1385173a1c5 Mon Sep 17 00:00:00 2001 From: Ash-4 <81280775+Ash-4@users.noreply.github.com> Date: Fri, 29 Apr 2022 19:24:56 -0500 Subject: [PATCH 099/870] ACK defaults now 50-2000-20000 --- DCCACK.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index 7c159b854..de66355b1 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -27,8 +27,8 @@ #include "DCCWaveform.h" #include "TrackManager.h" -unsigned int DCCACK::minAckPulseDuration = 4000; // micros -unsigned int DCCACK::maxAckPulseDuration = 8500; // micros +unsigned int DCCACK::minAckPulseDuration = 2000; // micros +unsigned int DCCACK::maxAckPulseDuration = 20000; // micros MotorDriver * DCCACK::progDriver=NULL; ackOp const * DCCACK::ackManagerProg; @@ -57,7 +57,7 @@ uint8_t DCCACK::trailingEdgeCounter=0; volatile bool DCCACK::ackPending; bool DCCACK::autoPowerOff; int DCCACK::ackThreshold; - int DCCACK::ackLimitmA; + int DCCACK::ackLimitmA = 50; int DCCACK::ackMaxCurrent; unsigned int DCCACK::ackCheckDuration; // millis From 7a2fd90bfce236f446aaa62b23dfc3b8995e763d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Apr 2022 23:24:51 +0200 Subject: [PATCH 100/870] set the reset packet counter of the prog track to 0 every time the track is turned on --- DCCACK.cpp | 1 + MotorDriver.cpp | 2 ++ MotorDriver.h | 4 ++++ TrackManager.cpp | 2 ++ 4 files changed, 9 insertions(+) diff --git a/DCCACK.cpp b/DCCACK.cpp index de66355b1..83ce359b2 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -76,6 +76,7 @@ void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_C callback(-2); // our prog track cant measure current return; } + progDriver->setResetCounterPointer(&(DCCWaveform::progTrack.sentResetsSincePacket)); ackManagerRejoin=TrackManager::isJoined(); if (ackManagerRejoin ) { diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 517e2ddcf..b26161e41 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -98,6 +98,8 @@ void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { IODevice::write(powerPin,HIGH); + if (resetsCounterP != NULL) + *resetsCounterP = 0; } else IODevice::write(powerPin,LOW); powerMode=mode; diff --git a/MotorDriver.h b/MotorDriver.h index 981b25928..d0dd9992f 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -73,8 +73,12 @@ class MotorDriver { inline byte getFaultPin() { return faultPin; } + inline void setResetCounterPointer(byte *bp) { // load resetPacketCounter pointer + resetsCounterP = bp; + } void checkPowerOverload(bool useProgLimit, byte trackno); private: + byte *resetsCounterP = NULL; // points to the resetPacketCounter if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { getFastPin(type, pin, 0, result); diff --git a/TrackManager.cpp b/TrackManager.cpp index 5618644c1..58f10f7c0 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -124,6 +124,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[t]->setPower(POWERMODE::OFF); trackMode[t]=TRACK_MODE_OFF; } + } else { + track[trackToSet]->setResetCounterPointer(NULL); // only the prog track has this pointer set } trackMode[trackToSet]=mode; trackDCAddr[trackToSet]=dcAddr; From 3e8649f9a1333369709ca5391f73b79cc999f216 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 2 May 2022 22:15:02 +0200 Subject: [PATCH 101/870] zero transmit not pending repeats when ack is found --- DCCWaveform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCWaveform.h b/DCCWaveform.h index 667e22080..cee39c306 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -48,7 +48,7 @@ class DCCWaveform { static void loop(); static DCCWaveform mainTrack; static DCCWaveform progTrack; - void clearRepeats() {pendingRepeats=0;} + inline void clearRepeats() { transmitRepeats=0; } void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile bool packetPending; volatile byte sentResetsSincePacket; From ac32cd552837bfa9d93a89add60d916f33cd5529 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 3 May 2022 08:38:35 +0200 Subject: [PATCH 102/870] guess value should be 0 not random bute in RAM --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index bf8a12731..b434428a0 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -380,7 +380,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) { // -- uses verify callback if (!stashCallback(stream, p, ringStream)) break; - DCC::verifyCVByte(p[0], p[1], callback_Vbyte); + DCC::verifyCVByte(p[0], 0, callback_Vbyte); return; } if (params == 3) From cb365579d8abbcd0990e201cb0a0e8c7c01ea2bd Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 5 May 2022 21:20:49 +0800 Subject: [PATCH 103/870] Minor edits. --- DCCTimerSAMD.cpp | 5 +++-- defines.h | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 159dd55b2..88603e626 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -119,8 +119,9 @@ void TCC2_Handler() { bool DCCTimer::isPWMPin(byte pin) { - //TODO: SAMD test this works! -// return digitalPinHasPWM(pin); + //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, + // there's no support yet for High Accuracy, so for now return false + // return digitalPinHasPWM(pin); return false; } diff --git a/defines.h b/defines.h index aba5daf32..19ebb4a05 100644 --- a/defines.h +++ b/defines.h @@ -77,15 +77,21 @@ // This defines the speed at which the Arduino will communicate with the ESP8266 module. // Currently only devices which can communicate at 115200 are supported. // +// TODO: PMA remove! Need 9600bps for Sparkfun shield with old firmware! +#if defined(ARDUINO_ARCH_SAMD) #define WIFI_SERIAL_LINK_SPEED 9600 +#else +#define WIFI_SERIAL_LINK_SPEED 115200 +#endif // TODO: PMA - figure out why enabling this causes the CS to crashe immediately after starting the motor driver -//#if __has_include ( "myAutomation.h") -// #if defined(BIG_RAM) || defined(DISABLE_EEPROM) -// #define EXRAIL_ACTIVE -// #else -// #define EXRAIL_WARNING -// #endif -//#endif +// on the SAMD platform - going to try to rebase to current TrackManager in case it's not my bug :-) +#if __has_include ( "myAutomation.h") + #if defined(BIG_RAM) || defined(DISABLE_EEPROM) + #define EXRAIL_ACTIVE + #else + #define EXRAIL_WARNING + #endif +#endif #endif From 4a56998553bc95e9678c1757d172cec8832f0dd2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 10 May 2022 23:37:24 +0200 Subject: [PATCH 104/870] inline setSignal ; bugfix HA switching code by doing clearPWM --- DCCTimer.h | 2 +- DCCTimerAVR.cpp | 4 ++++ DCCTimerESP.cpp | 2 ++ DCCTimerMEGAAVR.cpp | 4 ++++ DCCTimerTEENSY.cpp | 6 +++++- MotorDriver.cpp | 22 ---------------------- MotorDriver.h | 22 +++++++++++++++++++++- TrackManager.cpp | 10 ++++++++-- 8 files changed, 45 insertions(+), 27 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index fb8184b38..4ad507c56 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -60,7 +60,7 @@ class DCCTimer { static void getSimulatedMacAddress(byte mac[6]); static bool isPWMPin(byte pin); static void setPWM(byte pin, bool high); - + static void clearPWM(); // Update low ram level. Allow for extra bytes to be specified // by estimation or inspection, that may be used by other // called subroutines. Must be called with interrupts disabled. diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index ff0143b2d..b6a516186 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -83,6 +83,10 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { #endif } +void DCCTimer::clearPWM() { + TCCR1A= 0; +} + void DCCTimer::getSimulatedMacAddress(byte mac[6]) { for (byte i=0; i<6; i++) { mac[i]=boot_signature_byte_get(0x0E + i); diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index d4b4a2bba..d80b0fecc 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -47,6 +47,8 @@ bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { } void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { } +void IRAM_ATTR DCCTimer::clearPWM() { +} // Fake this as it should not be used void DCCTimer::getSimulatedMacAddress(byte mac[6]) { diff --git a/DCCTimerMEGAAVR.cpp b/DCCTimerMEGAAVR.cpp index 8b43340de..521e7f7b0 100644 --- a/DCCTimerMEGAAVR.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -90,6 +90,10 @@ extern char *__malloc_heap_start; // TODO what are the relevant pins? } +void DCCTimer::clearPWM() { + // Do nothing unless we implent HA +} + void DCCTimer::getSimulatedMacAddress(byte mac[6]) { memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number mac[0] &= 0xFE; diff --git a/DCCTimerTEENSY.cpp b/DCCTimerTEENSY.cpp index 99b147a00..a75fe3a6b 100644 --- a/DCCTimerTEENSY.cpp +++ b/DCCTimerTEENSY.cpp @@ -50,6 +50,10 @@ void DCCTimer::setPWM(byte pin, bool high) { (void) high; } +void DCCTimer::clearPWM() { + // Do nothing unless we implent HA +} + #if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1 void DCCTimer::getSimulatedMacAddress(byte mac[6]) { uint32_t m1 = HW_OCOTP_MAC1; @@ -119,4 +123,4 @@ static inline int freeMemory() { } #endif -#endif \ No newline at end of file +#endif diff --git a/MotorDriver.cpp b/MotorDriver.cpp index b26161e41..92d8c88bd 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -25,11 +25,6 @@ #include "DCCTimer.h" #include "DIAG.h" -#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH -#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW -#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) -#define isLOW(fastpin) (!isHIGH(fastpin)) - bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; @@ -119,23 +114,6 @@ void MotorDriver::setBrake(bool on) { else setLOW(fastBrakePin); } -void MotorDriver::setSignal( bool high) { - if (usePWM) { - DCCTimer::setPWM(signalPin,high); - } - else { - if (high) { - setHIGH(fastSignalPin); - if (dualSignal) setLOW(fastSignalPin2); - } - else { - setLOW(fastSignalPin); - if (dualSignal) setHIGH(fastSignalPin2); - } - } -} - - bool MotorDriver::canMeasureCurrent() { return currentPin!=UNUSED_PIN; } diff --git a/MotorDriver.h b/MotorDriver.h index d0dd9992f..61ddf5cae 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -23,6 +23,12 @@ #define MotorDriver_h #include "FSH.h" #include "IODevice.h" +#include "DCCTimer.h" + +#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH +#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW +#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) +#define isLOW(fastpin) (!isHIGH(fastpin)) // Virtualised Motor shield 1-track hardware Interface @@ -53,7 +59,21 @@ class MotorDriver { byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); virtual void setPower( POWERMODE mode); virtual POWERMODE getPower() { return powerMode;} - virtual void setSignal( bool high); + __attribute__((always_inline)) inline void setSignal( bool high) { + if (usePWM) { + DCCTimer::setPWM(signalPin,high); + } + else { + if (high) { + setHIGH(fastSignalPin); + if (dualSignal) setLOW(fastSignalPin2); + } + else { + setLOW(fastSignalPin); + if (dualSignal) setHIGH(fastSignalPin2); + } + } + }; virtual void setBrake( bool on); virtual void setDCSignal(byte speedByte); virtual int getCurrentRaw(); diff --git a/TrackManager.cpp b/TrackManager.cpp index 58f10f7c0..74b3a7d3e 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -22,6 +22,7 @@ #include "DCCWaveform.h" #include "DCC.h" #include "MotorDriver.h" +#include "DCCTimer.h" #include "DIAG.h" // Virtualised Motor shield multi-track hardware Interface #define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++) @@ -110,6 +111,7 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; + //DIAG(F("Track=%c"),trackToSet+'A'); // DC tracks require a motorDriver that can set brake! if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) && !track[trackToSet]->canBrake()) { @@ -120,7 +122,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr if (mode==TRACK_MODE_PROG) { // only allow 1 track to be prog FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_PROG) { + if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) { track[t]->setPower(POWERMODE::OFF); trackMode[t]=TRACK_MODE_OFF; } @@ -150,6 +152,10 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG) canDo &= track[t]->isPWMCapable(); + //DIAG(F("HAMode=%d"),canDo); + if (!canDo) { + DCCTimer::clearPWM(); + } MotorDriver::usePWM=canDo; @@ -157,7 +163,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[trackToSet]->setPower( (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? mainPowerGuess : POWERMODE::OFF); - + //DIAG(F("TrackMode=%d"),mode); return true; } From af0d381e454f0a371db29dcfc5bfc6ca8c417d93 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 10 May 2022 23:42:21 +0200 Subject: [PATCH 105/870] shadow PORTX (PoC) --- MotorDriver.cpp | 22 ++++++++++++++++++++-- TrackManager.cpp | 20 ++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 92d8c88bd..0f5b60ecf 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -27,7 +27,11 @@ bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; - + +volatile byte fakePORTA; +volatile byte fakePORTB; +volatile byte fakePORTC; + MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { powerPin=power_pin; @@ -36,7 +40,20 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 signalPin=signal_pin; getFastPin(F("SIG"),signalPin,fastSignalPin); pinMode(signalPin, OUTPUT); - + + if (fastSignalPin.inout == &PORTA) { + DIAG(F("Found PORTA pin %d"),signalPin); + fastSignalPin.inout = &fakePORTA; + } + if (fastSignalPin.inout == &PORTB) { + DIAG(F("Found PORTB pin %d"),signalPin); + fastSignalPin.inout = &fakePORTB; + } + if (fastSignalPin.inout == &PORTC) { + DIAG(F("Found PORTC pin %d"),signalPin); + fastSignalPin.inout = &fakePORTC; + } + signalPin2=signal_pin2; if (signalPin2!=UNUSED_PIN) { dualSignal=true; @@ -50,6 +67,7 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 invertBrake=brake_pin < 0; brakePin=invertBrake ? 0-brake_pin : brake_pin; getFastPin(F("BRAKE"),brakePin,fastBrakePin); + // if brake is used for railcom cutout we need to do PORTX register trick here as well pinMode(brakePin, OUTPUT); setBrake(true); // start with brake on in case we hace DC stuff going on } diff --git a/TrackManager.cpp b/TrackManager.cpp index 74b3a7d3e..93a92e2cf 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -85,18 +85,34 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { track[t]->setPower(POWERMODE::OFF); } } +extern byte fakePORTA; +extern byte fakePORTB; +extern byte fakePORTC; void TrackManager::setDCCSignal( bool on) { - APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); + fakePORTA=PORTA; + fakePORTB=PORTB; + fakePORTC=PORTC; + APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); + PORTA=fakePORTA; + PORTB=fakePORTB; + PORTC=fakePORTC; } void TrackManager::setCutout( bool on) { (void) on; + // TODO Cutout needs fake ports as well // TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on)); } void TrackManager::setPROGSignal( bool on) { - APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); + fakePORTA=PORTA; + fakePORTB=PORTB; + fakePORTC=PORTC; + APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); + PORTA=fakePORTA; + PORTB=fakePORTB; + PORTC=fakePORTC; } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { From 6d382fa0f454096f79420ea9436ea7292301f65d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 12 May 2022 20:56:23 +0200 Subject: [PATCH 106/870] TackManager: Make OFF=zero and when DCC then HA must be off --- TrackManager.cpp | 15 ++++++++++++--- TrackManager.h | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 74b3a7d3e..39ec185bc 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -149,9 +149,18 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // re-evaluate HighAccuracy mode // We can only do this is all main and prog tracks agree bool canDo=true; - FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_MAIN ||trackMode[t]==TRACK_MODE_PROG) - canDo &= track[t]->isPWMCapable(); + FOR_EACH_TRACK(t) { + // DC tracks must not have the DCC PWM switched on + // so we globally turn it off if one of the PWM + // capable tracks is now DC or DCX. + if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) { + if (track[t]->isPWMCapable()) { + canDo=false; + break; + } + } else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) + canDo &= track[t]->isPWMCapable(); + } //DIAG(F("HAMode=%d"),canDo); if (!canDo) { DCCTimer::clearPWM(); diff --git a/TrackManager.h b/TrackManager.h index 8b12f8610..fcbda1732 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -23,7 +23,7 @@ #include "MotorDriver.h" // Virtualised Motor shield multi-track hardware Interface -enum TRACK_MODE : byte {TRACK_MODE_MAIN, TRACK_MODE_PROG, TRACK_MODE_OFF, +enum TRACK_MODE : byte {TRACK_MODE_OFF, TRACK_MODE_MAIN, TRACK_MODE_PROG, TRACK_MODE_DC, TRACK_MODE_DCX}; // These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. From f66f5785f52493e0a14ba156744de598c8efcdb2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 12 May 2022 20:59:31 +0200 Subject: [PATCH 107/870] reorder statements in addTrack --- TrackManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 39ec185bc..8d10e7e2b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -78,11 +78,11 @@ void TrackManager::Setup(const FSH * shieldname, } void TrackManager::addTrack(byte t, MotorDriver* driver) { - track[t]=driver; trackMode[t]=TRACK_MODE_OFF; + track[t]=driver; if (driver) { - lastTrack=t; track[t]->setPower(POWERMODE::OFF); + lastTrack=t; } } From 0268304d413522b3aafa17778aa3f3fef79faaa7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 12 May 2022 21:09:43 +0200 Subject: [PATCH 108/870] fix type warning --- MotorDriver.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 61ddf5cae..283039c0b 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -93,12 +93,12 @@ class MotorDriver { inline byte getFaultPin() { return faultPin; } - inline void setResetCounterPointer(byte *bp) { // load resetPacketCounter pointer + inline void setResetCounterPointer(volatile byte *bp) { // load resetPacketCounter pointer resetsCounterP = bp; } void checkPowerOverload(bool useProgLimit, byte trackno); private: - byte *resetsCounterP = NULL; // points to the resetPacketCounter if this is a prog track + volatile byte *resetsCounterP = NULL; // points to the resetPacketCounter if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { getFastPin(type, pin, 0, result); From d7a17b10b4ccfa70c3a0dbb51fe7edc6ae435461 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 13 May 2022 00:05:25 +0200 Subject: [PATCH 109/870] use ugly macros to make PORTX code portable --- MotorDriver.cpp | 6 +++--- MotorDriver.h | 25 +++++++++++++++++++++++++ TrackManager.cpp | 28 +++++++++++++++------------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 0f5b60ecf..30dd1beaf 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -41,15 +41,15 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 getFastPin(F("SIG"),signalPin,fastSignalPin); pinMode(signalPin, OUTPUT); - if (fastSignalPin.inout == &PORTA) { + if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) { DIAG(F("Found PORTA pin %d"),signalPin); fastSignalPin.inout = &fakePORTA; } - if (fastSignalPin.inout == &PORTB) { + if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) { DIAG(F("Found PORTB pin %d"),signalPin); fastSignalPin.inout = &fakePORTB; } - if (fastSignalPin.inout == &PORTC) { + if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) { DIAG(F("Found PORTC pin %d"),signalPin); fastSignalPin.inout = &fakePORTC; } diff --git a/MotorDriver.h b/MotorDriver.h index 61ddf5cae..db229d5fa 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -30,6 +30,31 @@ #define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) #define isLOW(fastpin) (!isHIGH(fastpin)) +#define TOKENPASTE(x, y) x ## y +#define TOKENPASTE2(x, y) TOKENPASTE(x, y) + +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) +#define HAVE_PORTA(X) X +#define HAVE_PORTB(X) X +#define HAVE_PORTC(X) X +#endif +#if defined(ARDUINO_AVR_UNO) +#define HAVE_PORTB(X) X +#endif + +// if macros not defined as pass-through we define +// them here as someting that is valid as a +// statement and evaluates to false. +#ifndef HAVE_PORTA +#define HAVE_PORTA(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0 +#endif +#ifndef HAVE_PORTB +#define HAVE_PORTB(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0 +#endif +#ifndef HAVE_PORTC +#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0 +#endif + // Virtualised Motor shield 1-track hardware Interface #ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h diff --git a/TrackManager.cpp b/TrackManager.cpp index 93a92e2cf..0c5117b2a 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -84,19 +84,21 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { lastTrack=t; track[t]->setPower(POWERMODE::OFF); } -} +} + +// defined in Motordriver.cpp extern byte fakePORTA; extern byte fakePORTB; extern byte fakePORTC; void TrackManager::setDCCSignal( bool on) { - fakePORTA=PORTA; - fakePORTB=PORTB; - fakePORTC=PORTC; + HAVE_PORTA(fakePORTA=PORTA); + HAVE_PORTB(fakePORTB=PORTB); + HAVE_PORTC(fakePORTC=PORTC); APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); - PORTA=fakePORTA; - PORTB=fakePORTB; - PORTC=fakePORTC; + HAVE_PORTA(PORTA=fakePORTA); + HAVE_PORTB(PORTB=fakePORTB); + HAVE_PORTC(PORTC=fakePORTC); } void TrackManager::setCutout( bool on) { @@ -106,13 +108,13 @@ void TrackManager::setCutout( bool on) { } void TrackManager::setPROGSignal( bool on) { - fakePORTA=PORTA; - fakePORTB=PORTB; - fakePORTC=PORTC; + HAVE_PORTA(fakePORTA=PORTA); + HAVE_PORTB(fakePORTB=PORTB); + HAVE_PORTC(fakePORTC=PORTC); APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); - PORTA=fakePORTA; - PORTB=fakePORTB; - PORTC=fakePORTC; + HAVE_PORTA(PORTA=fakePORTA); + HAVE_PORTB(PORTB=fakePORTB); + HAVE_PORTC(PORTC=fakePORTC); } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { From f0e8419fea207d19a078a32878c0f893ac621e68 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 13 May 2022 01:16:40 +0200 Subject: [PATCH 110/870] tag it --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6af3d5710..4c8f500f8 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "a26d988" +#define GITHUB_SHA "TM-PORTX-20220513" From f8a19de9fb0160ae5ee1dc908b3c6d1fb2a89d20 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 13 May 2022 01:22:00 +0200 Subject: [PATCH 111/870] tag it --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6af3d5710..19414996f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "a26d988" +#define GITHUB_SHA "TM-20220513" From d2fa44eec706f71cf894efb8a95698da281a2a88 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 24 Mar 2022 11:56:06 +0000 Subject: [PATCH 112/870] EXRAIL VIRTUAL_TURNOUT --- defines.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/defines.h b/defines.h index 19ebb4a05..c24d6af45 100644 --- a/defines.h +++ b/defines.h @@ -77,15 +77,8 @@ // This defines the speed at which the Arduino will communicate with the ESP8266 module. // Currently only devices which can communicate at 115200 are supported. // -// TODO: PMA remove! Need 9600bps for Sparkfun shield with old firmware! -#if defined(ARDUINO_ARCH_SAMD) -#define WIFI_SERIAL_LINK_SPEED 9600 -#else #define WIFI_SERIAL_LINK_SPEED 115200 -#endif -// TODO: PMA - figure out why enabling this causes the CS to crashe immediately after starting the motor driver -// on the SAMD platform - going to try to rebase to current TrackManager in case it's not my bug :-) #if __has_include ( "myAutomation.h") #if defined(BIG_RAM) || defined(DISABLE_EEPROM) #define EXRAIL_ACTIVE From 3c01bd9012003ef8f9e21b56cc8c282df18f32af Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 24 Mar 2022 13:56:01 +0000 Subject: [PATCH 113/870] Cleanup version.h --- version.h | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/version.h b/version.h index cb08ff3ae..8bfc201b2 100644 --- a/version.h +++ b/version.h @@ -24,29 +24,19 @@ // EXRAIL POWERON // 4.0.0 Major functional and non-functional changes. // Engine Driver "DriveAway" feature enhancement -// 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station -// New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability. -// EX-Rail Function commands for creating Automation, Route & Sequence Scripts -// EX-RAIL “ROSTER” Engines Id & Function key layout on Engine Driver or WiThrottle -// EX-RAIL DCC++EX Commands to Control EX-RAIL via JMRI Send pane and IDE Serial monitors -// New JMRI feature enhancements; -// Reads DCC++EX EEPROM & automatically uploades any Signals, DCC Turnouts, Servo Turnouts, Vpin Turnouts , & Output pane -// Turnout class revised to expand turnout capabilities, new commands added. -// Provides for multiple additional DCC++EX WiFi connections as accessory controllers or CS for a programming track when Motor Shields are added -// Supports Multiple Command Station connections and individual tracking of Send DCC++ Command panes and DCC++ Traffic Monitor panes -// New HAL added for I/O (digital and analogue inputs and outputs, servos etc) -// Automatically detects & connects to supported devices included in your config.h file -// 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). -// Added diagnostic command to show configured devices -// New Processor Support added -// Compiles on Nano Every and Teensy -// Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms). -// Can disable EEPROM code +// JMRI feature enhancement. Provides for multiple additional DCC++EX wifi +// connections as accessory controllers or CS for a programming track when +// motor shield is added +// 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). +// Added diagnostic command to show configured devices +// 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. // Output class now allows ID > 255. // Configuration options to globally flip polarity of DCC Accessory states when driven from command and command. From a1a2c9ce5b3b7392e7215a01f5cba72bcb8f7bb3 Mon Sep 17 00:00:00 2001 From: Kcsmith0708 Date: Thu, 24 Mar 2022 11:34:11 -0400 Subject: [PATCH 114/870] Update version.h (#223) Rewrite & Updated the 4.0.0 Section --- version.h | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/version.h b/version.h index 8bfc201b2..cb08ff3ae 100644 --- a/version.h +++ b/version.h @@ -24,19 +24,29 @@ // EXRAIL POWERON // 4.0.0 Major functional and non-functional changes. // Engine Driver "DriveAway" feature enhancement -// JMRI feature enhancement. Provides for multiple additional DCC++EX wifi -// connections as accessory controllers or CS for a programming track when -// motor shield is added -// 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). -// Added diagnostic command to show configured devices -// Native non-blocking I2C drivers for AVR and Nano architectures (fallback -// to blocking Wire library for other platforms). +// 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station +// New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability. +// EX-Rail Function commands for creating Automation, Route & Sequence Scripts +// EX-RAIL “ROSTER” Engines Id & Function key layout on Engine Driver or WiThrottle +// EX-RAIL DCC++EX Commands to Control EX-RAIL via JMRI Send pane and IDE Serial monitors +// New JMRI feature enhancements; +// Reads DCC++EX EEPROM & automatically uploades any Signals, DCC Turnouts, Servo Turnouts, Vpin Turnouts , & Output pane +// Turnout class revised to expand turnout capabilities, new commands added. +// Provides for multiple additional DCC++EX WiFi connections as accessory controllers or CS for a programming track when Motor Shields are added +// Supports Multiple Command Station connections and individual tracking of Send DCC++ Command panes and DCC++ Traffic Monitor panes +// New HAL added for I/O (digital and analogue inputs and outputs, servos etc) +// Automatically detects & connects to supported devices included in your config.h file +// 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). +// Added diagnostic command to show configured devices +// New Processor Support added +// Compiles on Nano Every and Teensy +// Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms). +// Can disable EEPROM code // EEPROM layout change - deletes EEPROM contents on first start following upgrade. // Output class now allows ID > 255. // Configuration options to globally flip polarity of DCC Accessory states when driven from command and command. From 6135272c329100248135d96d9d96a11936a1756f Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 17 May 2022 18:06:08 +0800 Subject: [PATCH 115/870] SAMD Support Initial Patches --- DCC.cpp | 4 ++ DCCEXParser.cpp | 3 + DCCTimer.h | 4 ++ DCCTimerSAMD.cpp | 170 ++++++++++++++++++++++++++++++++++++++++++++++ EEStore.h | 2 +- MotorDriver.cpp | 7 +- MotorDriver.h | 3 +- MotorDrivers.h | 8 +++ SerialManager.cpp | 6 ++ defines.h | 3 +- platformio.ini | 15 +++- 11 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 DCCTimerSAMD.cpp diff --git a/DCC.cpp b/DCC.cpp index 8af4ccdf9..b3b1595be 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -62,7 +62,11 @@ byte DCC::globalSpeedsteps=128; void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; +#if defined(ARDUINO_ARCH_SAMD) + StringFormatter::send(SerialUSB,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); +#else StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); +#endif #ifndef DISABLE_EEPROM // Load stuff from EEprom diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b434428a0..92511aaa8 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Herb Morton @@ -881,7 +882,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms delay(50); // wait for the prescaller time to expire #else +#if defined(ARDUINO_ARCH_ESP) ESP.restart(); +#endif #endif break; // and if we didnt restart } diff --git a/DCCTimer.h b/DCCTimer.h index 4ad507c56..913762da3 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Mike S * © 2021 Harald Barth * © 2021 Fred Decker @@ -121,6 +122,9 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { #elif defined(ARDUINO_ARCH_ESP32) #define ARDUINO_TYPE "ESP32" #undef HAS_AVR_WDT +#elif defined(ARDUINO_ARCH_SAMD) +#define ARDUINO_TYPE "SAMD21" +#undef HAS_AVR_WDT #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h #endif diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp new file mode 100644 index 000000000..b03962b07 --- /dev/null +++ b/DCCTimerSAMD.cpp @@ -0,0 +1,170 @@ +/* + * © 2022 Paul M Antoine + * © 2021 Mike S + * © 2021 Harald Barth + * © 2021 Fred Decker + * © 2021 Chris Harlow + * © 2021 David Cutting + * All rights reserved. + * + * This file is part of Asbelos DCC 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 . + */ + +// ATTENTION: this file only compiles on a SAMD21 based board +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef ARDUINO_ARCH_SAMD + +#include "FSH.h" //PMA temp debug +#include "DIAG.h" //PMA temp debug +#include "DCCTimer.h" + +INTERRUPT_CALLBACK interruptHandler=0; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + + // Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, + // and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now + // enables 10-bit mode, although 12-bit is possible + ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 + ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible + + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 + + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization + + // Timer setup - setup clock sources first + REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1 + GCLK_GENDIV_ID(4); // Apply to GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK + GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source + GCLK_GENCTRL_ID(4); // Select GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock + 4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4 + GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 + while (GCLK->STATUS.bit.SYNCBUSY); + + // Assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway + // for "normal accuracy" DCC waveform generation. For high accuracy we're going to need + // to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and output + // pins for each TCC are only available on certain pins + TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform + while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync + + // Set the frequency + TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val); + TCC0->PER.reg = CLOCK_CYCLES * 2; + while (TCC0->SYNCBUSY.bit.PER); + + // Start the timer + TCC0->CTRLA.bit.ENABLE = 1; + while (TCC0->SYNCBUSY.bit.ENABLE); + + // Set the interrupt condition, priority and enable it in the NVIC + TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow + NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority + NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt + interrupts(); +} + +// Timer IRQ handlers replace the dummy handlers (in cortex_handlers) +// copied from rf24 branch +void TCC0_Handler() { + if(TCC0->INTFLAG.bit.OVF) { + TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + +void TCC1_Handler() { + if(TCC1->INTFLAG.bit.OVF) { + TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + +void TCC2_Handler() { + if(TCC2->INTFLAG.bit.OVF) { + TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + + +bool DCCTimer::isPWMPin(byte pin) { + //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, + // there's no support yet for High Accuracy, so for now return false + // return digitalPinHasPWM(pin); + return false; +} + +void DCCTimer::setPWM(byte pin, bool high) { + // TODO: High Accuracy mode is not supported as yet, and may never need to be + (void) pin; + (void) high; +} + +void DCCTimer::clearPWM() { + return; +} + +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C; + volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040; +// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044; +// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048; + + volatile uint32_t m1 = *serno1; + volatile uint32_t m2 = *serno2; + mac[0] = m1 >> 8; + mac[1] = m1 >> 0; + mac[2] = m2 >> 24; + mac[3] = m2 >> 16; + mac[4] = m2 >> 8; + mac[5] = m2 >> 0; +} + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = freeMemory(); + interrupts(); + return retval; +} + +extern "C" char* sbrk(int incr); + +int DCCTimer::freeMemory() { + char top; + return (int)(&top - reinterpret_cast(sbrk(0))); +} + +#endif \ No newline at end of file diff --git a/EEStore.h b/EEStore.h index f0d9022af..360cc9c33 100644 --- a/EEStore.h +++ b/EEStore.h @@ -26,7 +26,7 @@ #include -#if defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_ARCH_SAMC) #include extern ExternalEEPROM EEPROM; #else diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 92d8c88bd..b0132534d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Mike S * © 2021 Fred Decker * © 2020-2022 Harald Barth @@ -172,8 +173,12 @@ int MotorDriver::mA2raw( unsigned int mA) { void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { // DIAG(F("MotorDriver %S Pin=%d,"),type,pin); - (void) type; // avoid compiler warning if diag not used above. + (void) type; // avoid compiler warning if diag not used above. +#if defined(ARDUINO_ARCH_SAMD) + PortGroup *port = digitalPinToPort(pin); +#else uint8_t port = digitalPinToPort(pin); +#endif if (input) result.inout = portInputRegister(port); else diff --git a/MotorDriver.h b/MotorDriver.h index 283039c0b..086cdc6f5 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Mike S * © 2021 Fred Decker * © 2020 Chris Harlow @@ -36,7 +37,7 @@ #define UNUSED_PIN 127 // inside int8_t #endif -#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) struct FASTPIN { volatile uint32_t *inout; uint32_t maskHIGH; diff --git a/MotorDrivers.h b/MotorDrivers.h index b2b798dc3..4877af533 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Fred Decker * © 2020-2022 Harald Barth * (c) 2020 Chris Harlow. All rights reserved. @@ -50,6 +51,13 @@ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 2000, UNUSED_PIN) +// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3 for 3v3 compatibility!!) +// senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, +// and for 12-bit samples it's more like 0.488, but we probably need to tweak both these +#define SAMD_STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 2000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) + // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \ diff --git a/SerialManager.cpp b/SerialManager.cpp index 871722760..0d1c3c377 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Chris Harlow * © 2022 Harald Barth * All rights reserved. @@ -33,8 +34,13 @@ SerialManager::SerialManager(Stream * myserial) { void SerialManager::init() { while (!Serial && millis() < 5000); // wait max 5s for Serial to start +#if defined(ARDUINO_ARCH_SAMD) + SerialUSB.begin(115200); + new SerialManager(&SerialUSB); +#else Serial.begin(115200); new SerialManager(&Serial); +#endif #ifdef SERIAL3_COMMANDS Serial3.begin(115200); new SerialManager(&Serial3); diff --git a/defines.h b/defines.h index 9ce154d85..fe291e8c8 100644 --- a/defines.h +++ b/defines.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Fred Decker @@ -40,7 +41,7 @@ // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) #define BIG_RAM #endif #if ENABLE_WIFI diff --git a/platformio.ini b/platformio.ini index 8bba2973e..a8b1e8b79 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,22 +15,35 @@ default_envs = mega328 unowifiR2 nano + samd21-dev-usb src_dir = . include_dir = . [env] build_flags = -Wall -Wextra -[env:samd21] +[env:samd21-dev-usb] platform = atmelsam board = sparkfun_samd21_dev_usb framework = arduino +upload_protocol = sam-ba +lib_deps = + ${env.lib_deps} +monitor_speed = 115200 +monitor_flags = --echo +build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM + +[env:samc21-firebox] +platform = atmelsam +board = firebox +framework = arduino upload_protocol = atmel-ice lib_deps = ${env.lib_deps} SparkFun External EEPROM Arduino Library monitor_speed = 115200 monitor_flags = --echo +build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM [env:mega2560-debug] platform = atmelavr From dd58e2c462b0b321c9001a247aea889d97fb672d Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 17 May 2022 20:04:19 +0800 Subject: [PATCH 116/870] Fix ESP32 define --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 92511aaa8..7191b470f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -882,7 +882,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms delay(50); // wait for the prescaller time to expire #else -#if defined(ARDUINO_ARCH_ESP) +#if defined(ARDUINO_ARCH_ESP32) ESP.restart(); #endif #endif From 6d2a9e3b36e08264dfb3ffc0acf575b2ecad7c56 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 18 May 2022 09:40:53 +0200 Subject: [PATCH 117/870] add EXT as a new track mode --- MotorDriver.h | 6 ++++++ TrackManager.cpp | 26 ++++++++++++++++++++++---- TrackManager.h | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 6c390588c..a7fe96c3a 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -99,6 +99,12 @@ class MotorDriver { } } }; + inline void enableSignal(bool on) { + if (on) + pinMode(signalPin, OUTPUT); + else + pinMode(signalPin, INPUT); + }; virtual void setBrake( bool on); virtual void setDCSignal(byte speedByte); virtual int getCurrentRaw(); diff --git a/TrackManager.cpp b/TrackManager.cpp index ccbed6f13..87d64ce0a 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -37,6 +37,7 @@ const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_OFF = 22479; const int16_t HASH_KEYWORD_DC = 2183; const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity +const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. MotorDriver * TrackManager::track[MAX_TRACKS]; @@ -161,9 +162,14 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } else { // DCC tracks need to have the brake set off or they will not work. - track[trackToSet]->setBrake(false); + track[trackToSet]->setBrake(false); } - + + // EXT is a special case where the signal pin is + // turned off. So unless that is set, the signal + // pin should be turned on + track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT); + // re-evaluate HighAccuracy mode // We can only do this is all main and prog tracks agree bool canDo=true; @@ -225,6 +231,9 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) case TRACK_MODE_OFF: StringFormatter::send(stream,F("OFF")); break; + case TRACK_MODE_EXT: + StringFormatter::send(stream,F("EXT")); + break; case TRACK_MODE_DC: StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]); break; @@ -252,7 +261,10 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF> return setTrackMode(p[0],TRACK_MODE_OFF); - + + if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> + return setTrackMode(p[0],TRACK_MODE_EXT); + if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab> return setTrackMode(p[0],TRACK_MODE_DC,p[2]); @@ -291,7 +303,8 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) { case TRACK_MODE_MAIN: if (setProg) break; // toggle brake before turning power on - resets overcurrent error - // on the Pololu board if brake is wired to ^D2. + // on the Pololu board if brake is wired to ^D2. + // XXX see if we can make this conditional driver->setBrake(true); driver->setBrake(false); // DCC runs with brake off driver->setPower(mode); @@ -309,6 +322,11 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) { driver->setBrake(false); driver->setPower(mode); break; + case TRACK_MODE_EXT: + driver->setBrake(true); + driver->setBrake(false); + driver->setPower(mode); + break; case TRACK_MODE_OFF: break; } diff --git a/TrackManager.h b/TrackManager.h index fcbda1732..8185e9704 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -24,7 +24,7 @@ // Virtualised Motor shield multi-track hardware Interface enum TRACK_MODE : byte {TRACK_MODE_OFF, TRACK_MODE_MAIN, TRACK_MODE_PROG, - TRACK_MODE_DC, TRACK_MODE_DCX}; + TRACK_MODE_DC, TRACK_MODE_DCX, TRACK_MODE_EXT}; // These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0; From 79ef114c0d78bd487f3a7a4c1dfdb1c15fec1830 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 21 May 2022 09:51:38 +0200 Subject: [PATCH 118/870] protect from setting unused pin --- MotorDriver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 30dd1beaf..701816ade 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -160,6 +160,8 @@ int MotorDriver::getCurrentRaw() { } void MotorDriver::setDCSignal(byte speedcode) { + if (brakePin == UNUSED_PIN) + return; // spedcoode is a dcc speed /direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 From 867e3b393063827486aab7feea4c67dd60cd4183 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 21 May 2022 10:19:25 +0200 Subject: [PATCH 119/870] Reset track signal when leaving PWM and use port registers in DC mode as well --- TrackManager.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 87d64ce0a..823c9e1d8 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -119,11 +119,17 @@ void TrackManager::setPROGSignal( bool on) { } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { - FOR_EACH_TRACK(t) { - if (trackDCAddr[t]!=cab) continue; - if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); - else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); - } + HAVE_PORTA(fakePORTA=PORTA); + HAVE_PORTB(fakePORTB=PORTB); + HAVE_PORTC(fakePORTC=PORTC); + FOR_EACH_TRACK(t) { + if (trackDCAddr[t]!=cab) continue; + if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); + else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); + } + HAVE_PORTA(PORTA=fakePORTA); + HAVE_PORTB(PORTB=fakePORTB); + HAVE_PORTC(PORTC=fakePORTC); } @@ -161,8 +167,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } else { - // DCC tracks need to have the brake set off or they will not work. - track[trackToSet]->setBrake(false); + // DCC tracks need to have set the PWM to zero or they will not work. + // 128 is speed=0 and dir=0 + track[trackToSet]->setDCSignal(128); } // EXT is a special case where the signal pin is From 17fb921678bcffa97605a05e1dd547122832680a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 22 May 2022 22:43:06 +0200 Subject: [PATCH 120/870] Explain better and change naming but same functionality --- DCC.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 8af4ccdf9..6b011e55f 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -229,23 +229,28 @@ uint32_t DCC::getFunctionMap(int cab) { return (reg<0)?0:speedTable[reg].functions; } -void DCC::setAccessory(int address, byte number, bool activate) { +void DCC::setAccessory(int address, byte port, bool gate) { + // An accessory has an address, 4 ports and 2 gates (coils) each. That's how + // the initial decoders were orgnized and that influenced how the DCC + // standard was made. #ifdef DIAG_IO - DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate); + DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, port, gate); #endif // use masks to detect wrong values and do nothing if(address != (address & 511)) return; - if(number != (number & 3)) + if(port != (port & 3)) return; byte b[2]; - b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address - b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate + // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address + // second byte is of the form 1AAACPPG, where C should be 1, PP the ports and G the gate + b[0] = address % 64 + 128; + b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8; - DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times + DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat the packet three times #if defined(EXRAIL_ACTIVE) - RMFT2::activateEvent(address<<2|number,activate); + RMFT2::activateEvent(address<<2|port,gate); #endif } From d3b72dc4fc7fc30f976668b405bc7094ec6893ec Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 22 May 2022 23:05:09 +0200 Subject: [PATCH 121/870] Send onoff packets from setAccessory --- DCC.cpp | 22 ++++++++++++++++------ DCC.h | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 6b011e55f..8e4114c05 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -8,7 +8,7 @@ * © 2020-2021 Chris Harlow * All rights reserved. * - * This file is part of Asbelos DCC API + * This file is part of DCC-EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -229,7 +229,12 @@ uint32_t DCC::getFunctionMap(int cab) { return (reg<0)?0:speedTable[reg].functions; } -void DCC::setAccessory(int address, byte port, bool gate) { +void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) { + // onoff is tristate: + // 0 => send off packet + // 1 => send on packet + // >1 => send both on and off packets. + // An accessory has an address, 4 ports and 2 gates (coils) each. That's how // the initial decoders were orgnized and that influenced how the DCC // standard was made. @@ -244,14 +249,19 @@ void DCC::setAccessory(int address, byte port, bool gate) { byte b[2]; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address - // second byte is of the form 1AAACPPG, where C should be 1, PP the ports and G the gate + // second byte is of the form 1AAACPPG, where C is 1 for on, PP the ports 0 to 3 and G the gate (coil). b[0] = address % 64 + 128; b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8; - - DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat the packet three times + if (onoff != 0) { + DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times #if defined(EXRAIL_ACTIVE) - RMFT2::activateEvent(address<<2|port,gate); + RMFT2::activateEvent(address<<2|port,gate); #endif + } + if (onoff != 1) { + b[1] &= ~0x08; // set C to 0 + DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times + } } // diff --git a/DCC.h b/DCC.h index 0e4dce11b..38ea980a2 100644 --- a/DCC.h +++ b/DCC.h @@ -66,7 +66,7 @@ class DCC static int getFn(int cab, int16_t functionNumber); static uint32_t getFunctionMap(int cab); static void updateGroupflags(byte &flags, int16_t functionNumber); - static void setAccessory(int aAdd, byte aNum, bool activate); + static void setAccessory(int address, byte port, bool gate, byte onoff = 2); static bool writeTextPacket(byte *b, int nBytes); // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 From c7b38170c1432e8edc96dd7b202ad1848b09d0e3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 22 May 2022 23:39:46 +0200 Subject: [PATCH 122/870] Parse ONOFF with 4 param command --- DCCEXParser.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b434428a0..f1a78d6e1 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -272,20 +272,29 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) return; break; - case 'a': // ACCESSORY or + case 'a': // ACCESSORY or { int address; byte subaddress; byte activep; + byte onoff; if (params==2) { // address=(p[0] - 1) / 4 + 1; subaddress=(p[0] - 1) % 4; - activep=1; + activep=1; + onoff=2; // send both } else if (params==3) { // address=p[0]; subaddress=p[1]; - activep=2; + activep=2; + onoff=2; // send both + } + else if (params==4) { // + address=p[0]; + subaddress=p[1]; + activep=2; + onoff=p[3]; } else break; // invalid no of parameters @@ -293,12 +302,13 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream) ((address & 0x01FF) != address) // invalid address (limit 9 bits ) || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) || ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 + || ((onoff & 0x01) != onoff) // invalid onoff 0|1 ) break; // Honour the configuration option (config.h) which allows the command to be reversed #ifdef DCC_ACCESSORY_COMMAND_REVERSE - DCC::setAccessory(address, subaddress,p[activep]==0); + DCC::setAccessory(address, subaddress,p[activep]==0,onoff); #else - DCC::setAccessory(address, subaddress,p[activep]==1); + DCC::setAccessory(address, subaddress,p[activep]==1,onoff); #endif } return; From 55196c2e7d1fef8903952c1ec302d26f97425566 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 23 May 2022 00:04:35 +0200 Subject: [PATCH 123/870] tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4c8f500f8..7d69b33be 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220513" +#define GITHUB_SHA "TM-PORTX-20220523" From 55561188e144ceaf129443b3042aae5f6b8f2055 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 24 May 2022 08:07:33 +0200 Subject: [PATCH 124/870] reset speed and loosen brake --- GITHUB_SHA.h | 2 +- TrackManager.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7d69b33be..fd9c8a094 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220523" +#define GITHUB_SHA "TM-PORTX-20220524" diff --git a/TrackManager.cpp b/TrackManager.cpp index 823c9e1d8..b1e76f82f 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -168,8 +168,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } else { // DCC tracks need to have set the PWM to zero or they will not work. - // 128 is speed=0 and dir=0 + // 128 is speed=0 and dir=0 and then loosen brake. track[trackToSet]->setDCSignal(128); + track[trackToSet]->setBrake(false); } // EXT is a special case where the signal pin is @@ -201,7 +202,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // Normal running tracks are set to the global power state track[trackToSet]->setPower( - (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? + (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ? mainPowerGuess : POWERMODE::OFF); //DIAG(F("TrackMode=%d"),mode); return true; From cfcd61174dcc059fb9f144d7c9c6034b813beebb Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 24 May 2022 23:33:46 +0200 Subject: [PATCH 125/870] test to remove port code --- TrackManager.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index b1e76f82f..aaf26f9f7 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -119,17 +119,11 @@ void TrackManager::setPROGSignal( bool on) { } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { - HAVE_PORTA(fakePORTA=PORTA); - HAVE_PORTB(fakePORTB=PORTB); - HAVE_PORTC(fakePORTC=PORTC); FOR_EACH_TRACK(t) { if (trackDCAddr[t]!=cab) continue; if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); } - HAVE_PORTA(PORTA=fakePORTA); - HAVE_PORTB(PORTB=fakePORTB); - HAVE_PORTC(PORTC=fakePORTC); } From 1c78792ddad8cfbd0e1780a637a9370ea4b1a213 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 25 May 2022 07:41:47 +0200 Subject: [PATCH 126/870] Revert "test to remove port code" This reverts commit cfcd61174dcc059fb9f144d7c9c6034b813beebb. --- TrackManager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TrackManager.cpp b/TrackManager.cpp index aaf26f9f7..b1e76f82f 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -119,11 +119,17 @@ void TrackManager::setPROGSignal( bool on) { } void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { + HAVE_PORTA(fakePORTA=PORTA); + HAVE_PORTB(fakePORTB=PORTB); + HAVE_PORTC(fakePORTC=PORTC); FOR_EACH_TRACK(t) { if (trackDCAddr[t]!=cab) continue; if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); } + HAVE_PORTA(PORTA=fakePORTA); + HAVE_PORTB(PORTB=fakePORTB); + HAVE_PORTC(PORTC=fakePORTC); } From 06e7ad5c5333cc084e0ee08e71086484d0d06c30 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 25 May 2022 09:28:28 +0200 Subject: [PATCH 127/870] prevent usage of pins for DC than can not do PWM --- GITHUB_SHA.h | 2 +- MotorDriver.h | 4 ++-- TrackManager.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index fd9c8a094..4e73c0faa 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220524" +#define GITHUB_SHA "TM-PORTX-20220525" diff --git a/MotorDriver.h b/MotorDriver.h index a7fe96c3a..4c7d6ff22 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -111,8 +111,8 @@ class MotorDriver { virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); virtual int mA2raw( unsigned int mA); - inline bool canBrake() { - return brakePin!=UNUSED_PIN; + inline bool brakeCanPWM() { + return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin))); } inline int getRawCurrentTripValue() { return rawCurrentTripValue; diff --git a/TrackManager.cpp b/TrackManager.cpp index b1e76f82f..472b263bc 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -139,8 +139,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr //DIAG(F("Track=%c"),trackToSet+'A'); // DC tracks require a motorDriver that can set brake! if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) - && !track[trackToSet]->canBrake()) { - DIAG(F("No brake:no DC")); + && !track[trackToSet]->brakeCanPWM()) { + DIAG(F("Brake pin can't PWM: No DC")); return false; } From c2d7e7169a2bbc95db2d6d23262a77c360c6a3c5 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 3 Jun 2022 17:04:32 +0800 Subject: [PATCH 128/870] Starting I2C Native Driver --- I2CManager_SAMD.h | 188 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 I2CManager_SAMD.h diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h new file mode 100644 index 000000000..aeabd6ca5 --- /dev/null +++ b/I2CManager_SAMD.h @@ -0,0 +1,188 @@ +/* + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 I2CMANAGER_SAMD_H +#define I2CMANAGER_SAMD_H + +#include +#include "I2CManager.h" + +//#include +//#include + +#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO) +// PMA - IRQ handler, based on SERCOM3 being used for I2C, as per Arduino Zero & Sparkfun SAMD21 +// TODO: test +void SERCOM3_Handler() { + I2CManagerClass::handleInterrupt(); +} +#endif + +/*************************************************************************** + * Set I2C clock speed register. + ***************************************************************************/ +void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { + 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; +} + +/*************************************************************************** + * Initialise I2C registers. + ***************************************************************************/ +void I2CManagerClass::I2C_init() +{ + // PMA - broadly we do the following + initialise the clock + initialise the NVIC + software reset the I2C for the sercom + set master mode + do we need smart mode and quick command?? + configure interrupt handlers + enable interrupts + set default baud rate + set SDA/SCL pins as outputs and enable pullups +} + +/*************************************************************************** + * Initiate a start bit for transmission. + ***************************************************************************/ +void I2CManagerClass::I2C_sendStart() { + bytesToSend = currentRequest->writeLen; + bytesToReceive = currentRequest->readLen; + // We may have initiated a stop bit before this without waiting for it. + // Wait for stop bit to be sent before sending start. + while (TWCR & (1<writeBuffer + (txCount++)); + else + TWDR = currentRequest->writeBuffer[txCount++]; + bytesToSend--; + TWCR = (1< 0) { + currentRequest->readBuffer[rxCount++] = TWDR; + bytesToReceive--; + } + /* fallthrough */ + case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received + if (bytesToReceive <= 1) { + TWCR = (1< 0) { + currentRequest->readBuffer[rxCount++] = TWDR; + bytesToReceive--; + } + TWCR = (1<i2cAddress << 1) | 1; // SLA+R + else + TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W + TWCR = (1< Date: Fri, 3 Jun 2022 17:15:46 +0800 Subject: [PATCH 129/870] Various SAMC/SAMD defs --- DCC.cpp | 5 ++++- EEStore.cpp | 4 ++-- defines.h | 5 ----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 05fb00a32..f59d8805d 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -62,8 +62,11 @@ byte DCC::globalSpeedsteps=128; void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; +#if defined(ARDUINO_ARCH_SAMD) + StringFormatter::send(SerialUSB,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); +#else StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); - +#endif #ifndef DISABLE_EEPROM // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused diff --git a/EEStore.cpp b/EEStore.cpp index a441f003a..89577aadb 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -31,12 +31,12 @@ #include "Sensors.h" #include "Turnouts.h" -#if defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_ARCH_SAMC) ExternalEEPROM EEPROM; #endif void EEStore::init() { -#if defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_ARCH_SAMC) EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three // A pins grounded (0b1010000 = 0x50) #endif diff --git a/defines.h b/defines.h index 19ebb4a05..f48811c83 100644 --- a/defines.h +++ b/defines.h @@ -77,12 +77,7 @@ // This defines the speed at which the Arduino will communicate with the ESP8266 module. // Currently only devices which can communicate at 115200 are supported. // -// TODO: PMA remove! Need 9600bps for Sparkfun shield with old firmware! -#if defined(ARDUINO_ARCH_SAMD) -#define WIFI_SERIAL_LINK_SPEED 9600 -#else #define WIFI_SERIAL_LINK_SPEED 115200 -#endif // TODO: PMA - figure out why enabling this causes the CS to crashe immediately after starting the motor driver // on the SAMD platform - going to try to rebase to current TrackManager in case it's not my bug :-) From 016bc37b533c377756fb9af53483f12a3bf8b8ba Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 5 Jun 2022 23:07:03 +0200 Subject: [PATCH 130/870] clean up getThrottleSpeed functions --- DCC.cpp | 15 ++++++++++++++- DCC.h | 3 ++- GITHUB_SHA.h | 2 +- TrackManager.cpp | 15 ++++----------- WiThrottle.cpp | 5 ++++- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 8e4114c05..31ff0beaf 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -137,12 +137,25 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) { DCCWaveform::mainTrack.schedulePacket(b, nB, 0); } -uint8_t DCC::getThrottleSpeed(int cab) { +// returns speed steps 0 to 127 (1 == emergency stop) +// or -1 on "loco not found" +int8_t DCC::getThrottleSpeed(int cab) { int reg=lookupSpeedTable(cab); if (reg<0) return -1; return speedTable[reg].speedCode & 0x7F; } +// returns speed code byte +// or 128 (speed 0, dir forward) on "loco not found". +uint8_t DCC::getThrottleSpeedByte(int cab) { + int reg=lookupSpeedTable(cab); + if (reg<0) + return 128; + return speedTable[reg].speedCode; +} + +// returns direction on loco +// or true/forward on "loco not found" bool DCC::getThrottleDirection(int cab) { int reg=lookupSpeedTable(cab); if (reg<0) return true; diff --git a/DCC.h b/DCC.h index 38ea980a2..b45c45e43 100644 --- a/DCC.h +++ b/DCC.h @@ -56,7 +56,8 @@ class DCC // Public DCC API functions static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); - static uint8_t getThrottleSpeed(int cab); + static int8_t getThrottleSpeed(int cab); + static uint8_t getThrottleSpeedByte(int cab); static bool getThrottleDirection(int cab); static void writeCVByteMain(int cab, int cv, byte bValue); static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4e73c0faa..bc1186112 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220525" +#define GITHUB_SHA "TM-PORTX-20220605" diff --git a/TrackManager.cpp b/TrackManager.cpp index 472b263bc..87ea73567 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -209,17 +209,10 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } void TrackManager::applyDCSpeed(byte t) { - - int16_t speed1=DCC::getThrottleSpeed(trackDCAddr[t]); - byte speedByte; - if (speed1<0) speedByte=0; - else { - speedByte=speed1; - bool direction=DCC::getThrottleDirection(trackDCAddr[t]); - if (trackMode[t]==TRACK_MODE_DCX) direction=!direction; - if (direction) speedByte|=0x80; - } - track[t]->setDCSignal(speedByte); + uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]); + if (trackMode[t]==TRACK_MODE_DCX) + speedByte = (speedByte & 0xF7) | ~(speedByte & 0x80); // Reverse highest bit + track[t]->setDCSignal(speedByte); } bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index a0b1fe387..5a835ceba 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -421,7 +421,10 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, bool forward=aval[1]!='0'; LOOPLOCOS(throttleChar, cab) { mostRecentCab=myLocos[loco].cab; - DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward); + int8_t speed = DCC::getThrottleSpeed(myLocos[loco].cab); + if (speed < 0) //can not find any speed for this cab + speed = 0; + DCC::setThrottle(myLocos[loco].cab, speed, forward); // setThrottle will cause a broadcast so notification will be sent } } From 2b2012ef1d7308f8d71ba4b152ada49b6666e831 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 6 Jun 2022 17:37:23 +0100 Subject: [PATCH 131/870] float memory saver --- MotorDriver.cpp | 19 +++++++++++++------ MotorDriver.h | 11 +++++++++-- StringFormatter.cpp | 2 +- version.h | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 92d8c88bd..b72cc670b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -67,16 +67,23 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 pinMode(faultPin, INPUT); } - senseFactor=sense_factor; + // This conversion performed at compile time so the remainder of the code never needs + // float calculations or libraray code. + senseFactorInternal=sense_factor * senseScale; tripMilliamps=trip_milliamps; - rawCurrentTripValue=(int)(trip_milliamps / sense_factor); + rawCurrentTripValue=mA2raw(trip_milliamps); if (currentPin==UNUSED_PIN) DIAG(F("MotorDriver ** WARNING ** No current or short detection")); - else + else { DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"), currentPin-A0, senseOffset,rawCurrentTripValue); + // self testing diagnostic for the non-float converters... may be removed when happy + // DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"), + // senseFactorInternal, raw2mA(1000),mA2raw(1000)); + } + // prepare values for current detection sampleDelay = 0; lastSampleTaken = millis(); @@ -164,10 +171,10 @@ int MotorDriver::getCurrentRawInInterrupt() { } unsigned int MotorDriver::raw2mA( int raw) { - return (unsigned int)(raw * senseFactor); + return (int32_t)raw * senseFactorInternal / senseScale; } -int MotorDriver::mA2raw( unsigned int mA) { - return (int)(mA / senseFactor); +unsigned int MotorDriver::mA2raw( unsigned int mA) { + return (int32_t)mA * senseScale / senseFactorInternal; } void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { diff --git a/MotorDriver.h b/MotorDriver.h index 283039c0b..1e4081ec1 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -79,7 +79,7 @@ class MotorDriver { virtual int getCurrentRaw(); virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); - virtual int mA2raw( unsigned int mA); + virtual unsigned int mA2raw( unsigned int mA); inline bool canBrake() { return brakePin!=UNUSED_PIN; } @@ -108,7 +108,14 @@ class MotorDriver { FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; bool dualSignal; // true to use signalPin2 bool invertBrake; // brake pin passed as negative means pin is inverted - float senseFactor; + + // Raw to milliamp conversion factors avoiding float data types. + // Milliamps=rawADCreading * sensefactorInternal / senseScale + // + // senseScale is chosen as 256 to give enough scale for 2 decimal place + // raw->mA conversion with an ultra fast optimised integer multiplication + int senseFactorInternal; // set to senseFactor * senseScale + static const int senseScale=256; int senseOffset; unsigned int tripMilliamps; int rawCurrentTripValue; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 73a910ff0..485d8fd08 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -104,7 +104,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; case 'x': stream->print(va_arg(args, int), HEX); break; - case 'f': stream->print(va_arg(args, double), 2); break; + //case 'f': stream->print(va_arg(args, double), 2); break; //format width prefix case '-': formatLeft=true; diff --git a/version.h b/version.h index e3ff37a82..5ab7e445e 100644 --- a/version.h +++ b/version.h @@ -6,6 +6,7 @@ #define VERSION "4.0.3" // 4.0.3 Track Manager additions: +// Float eliminated saving >1.5kb PROGMEM and speed. // SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF) // New DC track function and DCX reverse polarity function // TrackManager DCC & DC up to 8 Districts Architecture From b24f6b27c6131231b81d7c7ac2fdcf8598351e4d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 6 Jun 2022 23:14:35 +0200 Subject: [PATCH 132/870] protect setSignal() changes in setDCSignal from being changed back during interrupt vis setDCCSignal --- MotorDriver.cpp | 36 ++++++++++++++++++++++++++++++++---- MotorDriver.h | 6 ++++-- TrackManager.cpp | 20 ++++++++++++-------- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 701816ade..a77c2d245 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -41,16 +41,20 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 getFastPin(F("SIG"),signalPin,fastSignalPin); pinMode(signalPin, OUTPUT); + fastSignalPin.shadowinout = NULL; if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) { DIAG(F("Found PORTA pin %d"),signalPin); + fastSignalPin.shadowinout = fastSignalPin.inout; fastSignalPin.inout = &fakePORTA; } if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) { DIAG(F("Found PORTB pin %d"),signalPin); + fastSignalPin.shadowinout = fastSignalPin.inout; fastSignalPin.inout = &fakePORTB; } if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) { DIAG(F("Found PORTC pin %d"),signalPin); + fastSignalPin.shadowinout = fastSignalPin.inout; fastSignalPin.inout = &fakePORTC; } @@ -162,15 +166,39 @@ int MotorDriver::getCurrentRaw() { void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) return; - // spedcoode is a dcc speed /direction - byte tSpeed=speedcode & 0x7F; - // DCC Speed with 0,1 stop and speed steps 2 to 127 + // spedcoode is a dcc speed & direction + byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 + byte tDir=speedcode & 0x80; byte brake; if (tSpeed <= 1) brake = 255; else if (tSpeed >= 127) brake = 0; else brake = 2 * (128-tSpeed); analogWrite(brakePin,brake); - setSignal(speedcode & 0x80); + // as the port registers can be shadowed to get syncronized DCC signals + // we need to take care of that and we have to turn off interrupts during + // that time as otherwise setDCCSignal() which is called from interrupt + // contect can undo whatever we do here. + if (fastSignalPin.shadowinout != NULL) { + if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { + noInterrupts(); + fakePORTA=PORTA; + setSignal(tDir); + PORTA=fakePORTA; + interrupts(); + } else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) { + noInterrupts(); + fakePORTB=PORTB; + setSignal(tDir); + PORTB=fakePORTB; + interrupts(); + } else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) { + noInterrupts(); + fakePORTC=PORTC; + setSignal(tDir); + PORTC=fakePORTC; + interrupts(); + } + } } int MotorDriver::getCurrentRawInInterrupt() { diff --git a/MotorDriver.h b/MotorDriver.h index 4c7d6ff22..7f0b16948 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -65,13 +65,15 @@ struct FASTPIN { volatile uint32_t *inout; uint32_t maskHIGH; - uint32_t maskLOW; + uint32_t maskLOW; + volatile uint32_t *shadowinout; }; #else struct FASTPIN { volatile uint8_t *inout; uint8_t maskHIGH; - uint8_t maskLOW; + uint8_t maskLOW; + volatile uint8_t *shadowinout; }; #endif diff --git a/TrackManager.cpp b/TrackManager.cpp index 87ea73567..4727dc853 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -87,11 +87,15 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { } } +// The port registers that are shadowing +// the real port registers. These are // defined in Motordriver.cpp extern byte fakePORTA; extern byte fakePORTB; extern byte fakePORTC; +// setDCCSignal(), called from interrupt context +// does assume ports are shadowed if they can be void TrackManager::setDCCSignal( bool on) { HAVE_PORTA(fakePORTA=PORTA); HAVE_PORTB(fakePORTB=PORTB); @@ -108,6 +112,8 @@ void TrackManager::setCutout( bool on) { // TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on)); } +// setPROGSignal(), called from interrupt context +// does assume ports are shadowed if they can be void TrackManager::setPROGSignal( bool on) { HAVE_PORTA(fakePORTA=PORTA); HAVE_PORTB(fakePORTB=PORTB); @@ -118,18 +124,15 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTC(PORTC=fakePORTC); } +// setDCSignal(), called from normal context +// MotorDriver::setDCSignal handles shadowed IO port changes. +// with interrupts turned off around the critical section void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { - HAVE_PORTA(fakePORTA=PORTA); - HAVE_PORTB(fakePORTB=PORTB); - HAVE_PORTC(fakePORTC=PORTC); FOR_EACH_TRACK(t) { if (trackDCAddr[t]!=cab) continue; if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); } - HAVE_PORTA(PORTA=fakePORTA); - HAVE_PORTB(PORTB=fakePORTB); - HAVE_PORTC(PORTC=fakePORTC); } @@ -193,10 +196,11 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) canDo &= track[t]->isPWMCapable(); } - //DIAG(F("HAMode=%d"),canDo); if (!canDo) { DCCTimer::clearPWM(); } + //if (MotorDriver::usePWM != canDo) + // DIAG(F("HA mode changed from %d to %d"), MotorDriver::usePWM, canDo); MotorDriver::usePWM=canDo; @@ -211,7 +215,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr void TrackManager::applyDCSpeed(byte t) { uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]); if (trackMode[t]==TRACK_MODE_DCX) - speedByte = (speedByte & 0xF7) | ~(speedByte & 0x80); // Reverse highest bit + speedByte = speedByte ^ 128; // reverse direction bit track[t]->setDCSignal(speedByte); } From d37e303bdc25595eb37c92c090cf00ccafa6be7f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 6 Jun 2022 23:34:40 +0200 Subject: [PATCH 133/870] tag version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index bc1186112..bc83b7fe9 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220605" +#define GITHUB_SHA "TM-PORTX-20220606" From 0ab96d28c4509df405ae6c8cf0b7cc87c83bd598 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 6 Jun 2022 23:45:28 +0200 Subject: [PATCH 134/870] make compile on Uno --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index bc83b7fe9..4ab389bfb 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220606" +#define GITHUB_SHA "TM-PORTX-20220606-1" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a77c2d245..7030ca74e 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -181,21 +181,21 @@ void MotorDriver::setDCSignal(byte speedcode) { if (fastSignalPin.shadowinout != NULL) { if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { noInterrupts(); - fakePORTA=PORTA; + HAVE_PORTA(fakePORTA=PORTA); setSignal(tDir); - PORTA=fakePORTA; + HAVE_PORTA(PORTA=fakePORTA); interrupts(); } else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) { noInterrupts(); - fakePORTB=PORTB; + HAVE_PORTB(fakePORTB=PORTB); setSignal(tDir); - PORTB=fakePORTB; + HAVE_PORTB(PORTB=fakePORTB); interrupts(); } else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) { noInterrupts(); - fakePORTC=PORTC; + HAVE_PORTC(fakePORTC=PORTC); setSignal(tDir); - PORTC=fakePORTC; + HAVE_PORTC(PORTC=fakePORTC); interrupts(); } } From 62e471606d8727188ab20a319c5e2cfc44479878 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 7 Jun 2022 00:18:48 +0200 Subject: [PATCH 135/870] remember the base case --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4ab389bfb..fc9fb4478 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220606-1" +#define GITHUB_SHA "TM-PORTX-20220607" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 7030ca74e..a5488baf1 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -198,6 +198,8 @@ void MotorDriver::setDCSignal(byte speedcode) { HAVE_PORTC(PORTC=fakePORTC); interrupts(); } + } else { + setSignal(tDir); } } From 16fafccf152f40e19c04eb37d588807a46d8e658 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 10 Jun 2022 12:22:28 +0100 Subject: [PATCH 136/870] Command Distributor Improve and split multi-language responses. Remove dependency on RingStream. --- CommandDistributor.cpp | 110 ++++++++++++++++++++++++----------------- CommandDistributor.h | 24 ++++++--- SerialManager.cpp | 9 ++-- SerialManager.h | 6 +-- StringBuffer.cpp | 45 +++++++++++++++++ StringBuffer.h | 38 ++++++++++++++ version.h | 1 + 7 files changed, 173 insertions(+), 60 deletions(-) create mode 100644 StringBuffer.cpp create mode 100644 StringBuffer.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 85df03bc8..9c3dba433 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -30,16 +30,29 @@ #include "DCC.h" #include "TrackManager.h" -#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON) -// This section of CommandDistributor is simply not relevant on a uno or similar -const byte NO_CLIENT=255; - -RingStream * CommandDistributor::ring=0; -byte CommandDistributor::ringClient=NO_CLIENT; -CommandDistributor::clientType CommandDistributor::clients[8]={ - NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; -RingStream * CommandDistributor::broadcastBufferWriter=new RingStream(100); +#ifdef BIG_RAM + // use a buffer to allow broadcast + #define BUFFER broadcastBufferWriter + #define FLUSH broadcastBufferWriter->flush(); + #define SHOVE(type) broadcastToClients(type); + StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer(); +#else + // on a UNO/NANO write direct to Serial and ignore flush/shove + #define BUFFER &Serial + #define FLUSH + #define SHOVE(type) +#endif + +#ifdef CD_HANDLE_RING + // wifi or ethernet ring streams with multiple client types + RingStream * CommandDistributor::ring=0; + byte CommandDistributor::ringClient=NO_CLIENT; + CommandDistributor::clientType CommandDistributor::clients[8]={ + NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; + +// Parse is called by Withrottle or Ethernet interface to determine which +// protocol the client is using and call the appropriate part of dcc++Ex void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { ring=stream; ringClient=stream->peekTargetMark(); @@ -56,15 +69,15 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream void CommandDistributor::forget(byte clientId) { clients[clientId]=NONE_TYPE; } +#endif - -void CommandDistributor::broadcast(bool includeWithrottleClients) { - broadcastBufferWriter->write((byte)'\0'); +// This will not be called on a uno +void CommandDistributor::broadcastToClients(clientType type) { /* Boadcast to Serials */ - SerialManager::broadcast(broadcastBufferWriter); + if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString()); -#if defined(WIFI_ON) | defined(ETHERNET_ON) +#ifdef CD_HANDLE_RING // If we are broadcasting from a wifi/eth process we need to complete its output // before merging broadcasts in the ring, then reinstate it in case // the process continues to output to its client. @@ -72,49 +85,46 @@ void CommandDistributor::broadcast(bool includeWithrottleClients) { /* loop through ring clients */ for (byte clientId=0; clientIdmark(clientId); - broadcastBufferWriter->printBuffer(ring); - ring->commit(); + if (clients[clientId]==type) { + ring->mark(clientId); + ring->print(broadcastBufferWriter->getString()); + ring->commit(); + } } if (ringClient!=NO_CLIENT) ring->mark(ringClient); #endif - broadcastBufferWriter->flush(); -} -#else -// For a UNO/NANO we can broadcast direct to just one Serial instead of the ring -// Redirect ring output ditrect to Serial -#define broadcastBufferWriter &Serial -// and ignore the internal broadcast call. -void CommandDistributor::broadcast(bool includeWithrottleClients) { - (void)includeWithrottleClients; } -#endif +// Public broadcast functions below void CommandDistributor::broadcastSensor(int16_t id, bool on ) { - StringFormatter::send(broadcastBufferWriter,F("<%c %d>\n"), on?'Q':'q', id); - broadcast(false); + FLUSH + StringFormatter::send(BUFFER,F("<%c %d>\n"), on?'Q':'q', id); + SHOVE(COMMAND_TYPE) } void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed; // The string below contains serial and Withrottle protocols which should // be safe for both types. - StringFormatter::send(broadcastBufferWriter,F("\n"),id, !isClosed); -#if defined(WIFI_ON) | defined(ETHERNET_ON) - StringFormatter::send(broadcastBufferWriter,F("PTA%c%d\n"), isClosed?'2':'4', id); + FLUSH + StringFormatter::send(BUFFER,F("\n"),id, !isClosed); + SHOVE(COMMAND_TYPE) + +#ifdef CD_HANDLE_RING + FLUSH + StringFormatter::send(BUFFER,F("PTA%c%d\n"), isClosed?'2':'4', id); + SHOVE(WITHROTTLE_TYPE) #endif - broadcast(true); } void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; - StringFormatter::send(broadcastBufferWriter,F("\n"), + FLUSH + StringFormatter::send(BUFFER,F("\n"), sp->loco,slot,sp->speedCode,sp->functions); - broadcast(false); -#if defined(WIFI_ON) | defined(ETHERNET_ON) + SHOVE(COMMAND_TYPE) +#ifdef CD_HANDLE_RING WiThrottle::markForBroadcast(sp->loco); #endif } @@ -130,14 +140,24 @@ void CommandDistributor::broadcastPower() { else if (main) reason=F(" MAIN"); else if (prog) reason=F(" PROG"); else state='0'; - - StringFormatter::send(broadcastBufferWriter, - F("\nPPA%c\n"),state,reason, main?'1':'0'); - LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); - broadcast(true); + FLUSH + StringFormatter::send(BUFFER,F("

\n"),state,reason); + SHOVE(COMMAND_TYPE) +#ifdef CD_HANDLE_RING + FLUSH + StringFormatter::send(BUFFER,F("PPA%c\n"), main?'1':'0'); + SHOVE(WITHROTTLE_TYPE) +#endif + LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); } void CommandDistributor::broadcastText(const FSH * msg) { - StringFormatter::send(broadcastBufferWriter,F("%S"),msg); - broadcast(false); + FLUSH + StringFormatter::send(BUFFER,F("%S"),msg); + SHOVE(COMMAND_TYPE) +#ifdef CD_HANDLE_RING + FLUSH + StringFormatter::send(BUFFER,F("Hm%S\n"), msg); + SHOVE(WITHROTTLE_TYPE) +#endif } diff --git a/CommandDistributor.h b/CommandDistributor.h index a9af3bbdd..7da3c8f2a 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -23,6 +23,13 @@ #define CommandDistributor_h #include "DCCEXParser.h" #include "RingStream.h" +#include "StringBuffer.h" +#include "defines.h" + +#if WIFI_ON | ETHERNET_ON + // Command Distributor must handle a RingStream of clients + #define CD_HANDLE_RING +#endif class CommandDistributor { @@ -35,14 +42,15 @@ public : static void broadcastText(const FSH * msg); static void forget(byte clientId); private: - static void broadcast(bool includeWithrottleClients); - static RingStream * ring; - static RingStream * broadcastBufferWriter; - static byte ringClient; - - // each bit in broadcastlist = 1<next) s->broadcast2(ring); +void SerialManager::broadcast(char * stringBuffer) { + for (SerialManager * s=first;s;s=s->next) s->broadcast2(stringBuffer); } -void SerialManager::broadcast2(RingStream * ring) { - ring->printBuffer(serial); +void SerialManager::broadcast2(char * stringBuffer) { + serial->print(stringBuffer); } void SerialManager::loop() { diff --git a/SerialManager.h b/SerialManager.h index fb58cbac9..ad1eaf307 100644 --- a/SerialManager.h +++ b/SerialManager.h @@ -23,7 +23,7 @@ #include "Arduino.h" #include "defines.h" -#include "RingStream.h" + #ifndef COMMAND_BUFFER_SIZE #define COMMAND_BUFFER_SIZE 100 @@ -33,13 +33,13 @@ class SerialManager { public: static void init(); static void loop(); - static void broadcast(RingStream * ring); + static void broadcast(char * stringBuffer); private: static SerialManager * first; SerialManager(Stream * myserial); void loop2(); - void broadcast2(RingStream * ring); + void broadcast2(char * stringBuffer); Stream * serial; SerialManager * next; byte bufferLength; diff --git a/StringBuffer.cpp b/StringBuffer.cpp new file mode 100644 index 000000000..dd4e47712 --- /dev/null +++ b/StringBuffer.cpp @@ -0,0 +1,45 @@ +/* + * © 2022 Chris Harlow + * All rights reserved. + * + * This file is part of DCC-EX CommandStation-EX + * + * 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 . + */ + +#include "StringBuffer.h" +#include "DIAG.h" + +StringBuffer::StringBuffer() { + flush(); +}; + +char * StringBuffer::getString() { + return _buffer; +} + +void StringBuffer::flush() { + _pos_write=0; + _buffer[0]='\0'; +} + +size_t StringBuffer::write(uint8_t b) { + if (_pos_write>=buffer_max) return 0; + _buffer[_pos_write] = b; + ++_pos_write; + _buffer[_pos_write]='\0'; + return 1; +} + + diff --git a/StringBuffer.h b/StringBuffer.h new file mode 100644 index 000000000..fe227fb8c --- /dev/null +++ b/StringBuffer.h @@ -0,0 +1,38 @@ + /* + * © 2022 Chris Harlow + * All rights reserved. + * + * This file is part of DCC++EX + * + * 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 StringBuffer_h +#define StringBuffer_h +#include + +class StringBuffer : public Print { + public: + StringBuffer(); + // Override Print default + virtual size_t write(uint8_t b); + void flush(); + char * getString(); + private: + static const int buffer_max=64; // enough for long text msgs to throttles + int16_t _pos_write; + char _buffer[buffer_max+1]; +}; + +#endif \ No newline at end of file diff --git a/version.h b/version.h index 5ab7e445e..98b431726 100644 --- a/version.h +++ b/version.h @@ -6,6 +6,7 @@ #define VERSION "4.0.3" // 4.0.3 Track Manager additions: +// Broadcast improvements to separate <> and Withrottle responses // Float eliminated saving >1.5kb PROGMEM and speed. // SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF) // New DC track function and DCX reverse polarity function From 40dfda47c7783566ec1f760d1b6687e324decfde Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Jun 2022 19:57:45 +0200 Subject: [PATCH 137/870] make usePWM per track (trackPWM) 2nd half --- GITHUB_SHA.h | 2 +- MotorDriver.h | 6 ++++-- TrackManager.cpp | 22 +++++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index fc9fb4478..8f67bc818 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220607" +#define GITHUB_SHA "TM-PORTX-20220607-1" diff --git a/MotorDriver.h b/MotorDriver.h index 7f0b16948..93a337b5a 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -2,6 +2,7 @@ * © 2021 Mike S * © 2021 Fred Decker * © 2020 Chris Harlow + * © 2022 Harald Barth * All rights reserved. * * This file is part of Asbelos DCC API @@ -87,7 +88,7 @@ class MotorDriver { virtual void setPower( POWERMODE mode); virtual POWERMODE getPower() { return powerMode;} __attribute__((always_inline)) inline void setSignal( bool high) { - if (usePWM) { + if (trackPWM) { DCCTimer::setPWM(signalPin,high); } else { @@ -121,7 +122,8 @@ class MotorDriver { } bool isPWMCapable(); bool canMeasureCurrent(); - static bool usePWM; + bool trackPWM; + static bool usePWM; // TODO: Remove static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs inline byte getFaultPin() { return faultPin; diff --git a/TrackManager.cpp b/TrackManager.cpp index 4727dc853..505a006dd 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -190,14 +190,26 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // capable tracks is now DC or DCX. if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) { if (track[t]->isPWMCapable()) { - canDo=false; - break; + canDo=false; // this track is capable but can not run PWM + break; // in this mode, so abort and prevent globally below + } else { + track[t]->trackPWM=false; // this track sure can not run with PWM + //DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A'); } - } else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) - canDo &= track[t]->isPWMCapable(); + } else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) { + track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here + //DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM); + canDo &= track[t]->trackPWM; + } } if (!canDo) { - DCCTimer::clearPWM(); + // if we discover that HA mode was globally impossible + // we must adjust the trackPWM capabilities + FOR_EACH_TRACK(t) { + track[t]->trackPWM=false; + //DIAG(F("Track %c trackPWM 0 (global override)"), t+'A'); + } + DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track } //if (MotorDriver::usePWM != canDo) // DIAG(F("HA mode changed from %d to %d"), MotorDriver::usePWM, canDo); From 4297ed557220f9948e1c9d353cbc9ca105dd29a3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Jun 2022 20:09:21 +0200 Subject: [PATCH 138/870] remove global usePWM flag --- MotorDriver.cpp | 1 - MotorDriver.h | 3 +-- TrackManager.cpp | 6 ------ 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a5488baf1..a679582da 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -25,7 +25,6 @@ #include "DCCTimer.h" #include "DIAG.h" -bool MotorDriver::usePWM=false; bool MotorDriver::commonFaultPin=false; volatile byte fakePORTA; diff --git a/MotorDriver.h b/MotorDriver.h index 93a337b5a..13545e312 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -122,8 +122,7 @@ class MotorDriver { } bool isPWMCapable(); bool canMeasureCurrent(); - bool trackPWM; - static bool usePWM; // TODO: Remove + bool trackPWM; // this track uses PWM timer to generate the DCC waveform static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs inline byte getFaultPin() { return faultPin; diff --git a/TrackManager.cpp b/TrackManager.cpp index 505a006dd..7265b955b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -73,8 +73,6 @@ void TrackManager::Setup(const FSH * shieldname, // TODO Fault pin config for odd motor boards (example pololu) // MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) // && (mainDriver->getFaultPin() != UNUSED_PIN)); - DIAG(F("Signal pin config: %S accuracy waveform"), - MotorDriver::usePWM ? F("high") : F("normal") ); DCC::begin(shieldname); } @@ -211,10 +209,6 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track } - //if (MotorDriver::usePWM != canDo) - // DIAG(F("HA mode changed from %d to %d"), MotorDriver::usePWM, canDo); - MotorDriver::usePWM=canDo; - // Normal running tracks are set to the global power state track[trackToSet]->setPower( From 808aa9aba9f20ec3c9114083c94e89b2be5d8ffa Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Jun 2022 20:35:52 +0200 Subject: [PATCH 139/870] show high accuracy status with track mode --- GITHUB_SHA.h | 2 +- TrackManager.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8f67bc818..6ab40dbeb 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220607-1" +#define GITHUB_SHA "TM-PORTX-20220611" diff --git a/TrackManager.cpp b/TrackManager.cpp index 7265b955b..2cf7fcb31 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -235,9 +235,13 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) switch(trackMode[t]) { case TRACK_MODE_MAIN: StringFormatter::send(stream,F("MAIN")); + if (track[t]->trackPWM) + StringFormatter::send(stream,F("+")); break; case TRACK_MODE_PROG: StringFormatter::send(stream,F("PROG")); + if (track[t]->trackPWM) + StringFormatter::send(stream,F("+")); break; case TRACK_MODE_OFF: StringFormatter::send(stream,F("OFF")); From 2385d0809c38018fe09a9957feaa50de9f2f8caf Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Jun 2022 20:38:44 +0200 Subject: [PATCH 140/870] copyright adjustments --- MotorDriver.h | 2 +- TrackManager.cpp | 1 + TrackManager.h | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 13545e312..fd8d24bc4 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -5,7 +5,7 @@ * © 2022 Harald Barth * All rights reserved. * - * This file is part of Asbelos DCC API + * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/TrackManager.cpp b/TrackManager.cpp index 2cf7fcb31..5b48cc474 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -1,5 +1,6 @@ /* * © 2022 Chris Harlow + * © 2022 Harald Barth * All rights reserved. * * This file is part of DCC++EX diff --git a/TrackManager.h b/TrackManager.h index 8185e9704..9184fd029 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -1,8 +1,9 @@ /* * © 2022 Chris Harlow + * © 2022 Harald Barth * All rights reserved. * - * This file is part of Asbelos DCC API + * This file is part of CommandStation-EX * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 744713769c830e08aa271bb03105eebb2d404a38 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 11 Jun 2022 22:23:33 +0200 Subject: [PATCH 141/870] fix merge error --- MotorDriver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MotorDriver.h b/MotorDriver.h index a66b3aafc..fe262f1fd 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -113,9 +113,9 @@ class MotorDriver { virtual int getCurrentRaw(); virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); + virtual unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin))); - virtual unsigned int mA2raw( unsigned int mA); } inline int getRawCurrentTripValue() { return rawCurrentTripValue; From 4833eaac65b02d4b90ce292afa15ac3f56af4608 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 13 Jun 2022 23:15:35 +0200 Subject: [PATCH 142/870] move all arch dependent port register declarations to MotorDriver.h --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 6 +++--- MotorDriver.h | 24 +++++++++++++----------- TrackManager.cpp | 7 ------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 20a16e5b7..06deeac5c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220611-1" +#define GITHUB_SHA "TM-PORTX-20220613" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 930a99336..6e1da57c7 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -27,9 +27,9 @@ bool MotorDriver::commonFaultPin=false; -volatile byte fakePORTA; -volatile byte fakePORTB; -volatile byte fakePORTC; +volatile portreg_t fakePORTA; +volatile portreg_t fakePORTB; +volatile portreg_t fakePORTC; MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { diff --git a/MotorDriver.h b/MotorDriver.h index fe262f1fd..27ccff71d 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -63,20 +63,22 @@ #endif #if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) -struct FASTPIN { - volatile uint32_t *inout; - uint32_t maskHIGH; - uint32_t maskLOW; - volatile uint32_t *shadowinout; -}; +typedef uint32_t portreg_t; #else +typedef uint8_t portreg_t; +#endif struct FASTPIN { - volatile uint8_t *inout; - uint8_t maskHIGH; - uint8_t maskLOW; - volatile uint8_t *shadowinout; + volatile portreg_t *inout; + portreg_t maskHIGH; + portreg_t maskLOW; + volatile portreg_t *shadowinout; }; -#endif +// The port registers that are shadowing +// the real port registers. These are +// defined in Motordriver.cpp +extern volatile portreg_t fakePORTA; +extern volatile portreg_t fakePORTB; +extern volatile portreg_t fakePORTC; enum class POWERMODE : byte { OFF, ON, OVERLOAD }; diff --git a/TrackManager.cpp b/TrackManager.cpp index 5b48cc474..3995dbfba 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -86,13 +86,6 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { } } -// The port registers that are shadowing -// the real port registers. These are -// defined in Motordriver.cpp -extern byte fakePORTA; -extern byte fakePORTB; -extern byte fakePORTC; - // setDCCSignal(), called from interrupt context // does assume ports are shadowed if they can be void TrackManager::setDCCSignal( bool on) { From 9a98d10a8626500d8e46c3b225adca779b6195bd Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 13 Jun 2022 23:18:10 +0200 Subject: [PATCH 143/870] rename fakePORT* to shadowPORT* --- MotorDriver.cpp | 24 ++++++++++++------------ MotorDriver.h | 6 +++--- TrackManager.cpp | 24 ++++++++++++------------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 6e1da57c7..9aadbe684 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -27,9 +27,9 @@ bool MotorDriver::commonFaultPin=false; -volatile portreg_t fakePORTA; -volatile portreg_t fakePORTB; -volatile portreg_t fakePORTC; +volatile portreg_t shadowPORTA; +volatile portreg_t shadowPORTB; +volatile portreg_t shadowPORTC; MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { @@ -44,17 +44,17 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) { DIAG(F("Found PORTA pin %d"),signalPin); fastSignalPin.shadowinout = fastSignalPin.inout; - fastSignalPin.inout = &fakePORTA; + fastSignalPin.inout = &shadowPORTA; } if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) { DIAG(F("Found PORTB pin %d"),signalPin); fastSignalPin.shadowinout = fastSignalPin.inout; - fastSignalPin.inout = &fakePORTB; + fastSignalPin.inout = &shadowPORTB; } if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) { DIAG(F("Found PORTC pin %d"),signalPin); fastSignalPin.shadowinout = fastSignalPin.inout; - fastSignalPin.inout = &fakePORTC; + fastSignalPin.inout = &shadowPORTC; } signalPin2=signal_pin2; @@ -187,21 +187,21 @@ void MotorDriver::setDCSignal(byte speedcode) { if (fastSignalPin.shadowinout != NULL) { if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { noInterrupts(); - HAVE_PORTA(fakePORTA=PORTA); + HAVE_PORTA(shadowPORTA=PORTA); setSignal(tDir); - HAVE_PORTA(PORTA=fakePORTA); + HAVE_PORTA(PORTA=shadowPORTA); interrupts(); } else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) { noInterrupts(); - HAVE_PORTB(fakePORTB=PORTB); + HAVE_PORTB(shadowPORTB=PORTB); setSignal(tDir); - HAVE_PORTB(PORTB=fakePORTB); + HAVE_PORTB(PORTB=shadowPORTB); interrupts(); } else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) { noInterrupts(); - HAVE_PORTC(fakePORTC=PORTC); + HAVE_PORTC(shadowPORTC=PORTC); setSignal(tDir); - HAVE_PORTC(PORTC=fakePORTC); + HAVE_PORTC(PORTC=shadowPORTC); interrupts(); } } else { diff --git a/MotorDriver.h b/MotorDriver.h index 27ccff71d..b42723fa1 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -76,9 +76,9 @@ struct FASTPIN { // The port registers that are shadowing // the real port registers. These are // defined in Motordriver.cpp -extern volatile portreg_t fakePORTA; -extern volatile portreg_t fakePORTB; -extern volatile portreg_t fakePORTC; +extern volatile portreg_t shadowPORTA; +extern volatile portreg_t shadowPORTB; +extern volatile portreg_t shadowPORTC; enum class POWERMODE : byte { OFF, ON, OVERLOAD }; diff --git a/TrackManager.cpp b/TrackManager.cpp index 3995dbfba..acfb3f960 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -89,13 +89,13 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { // setDCCSignal(), called from interrupt context // does assume ports are shadowed if they can be void TrackManager::setDCCSignal( bool on) { - HAVE_PORTA(fakePORTA=PORTA); - HAVE_PORTB(fakePORTB=PORTB); - HAVE_PORTC(fakePORTC=PORTC); + HAVE_PORTA(shadowPORTA=PORTA); + HAVE_PORTB(shadowPORTB=PORTB); + HAVE_PORTC(shadowPORTC=PORTC); APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); - HAVE_PORTA(PORTA=fakePORTA); - HAVE_PORTB(PORTB=fakePORTB); - HAVE_PORTC(PORTC=fakePORTC); + HAVE_PORTA(PORTA=shadowPORTA); + HAVE_PORTB(PORTB=shadowPORTB); + HAVE_PORTC(PORTC=shadowPORTC); } void TrackManager::setCutout( bool on) { @@ -107,13 +107,13 @@ void TrackManager::setCutout( bool on) { // setPROGSignal(), called from interrupt context // does assume ports are shadowed if they can be void TrackManager::setPROGSignal( bool on) { - HAVE_PORTA(fakePORTA=PORTA); - HAVE_PORTB(fakePORTB=PORTB); - HAVE_PORTC(fakePORTC=PORTC); + HAVE_PORTA(shadowPORTA=PORTA); + HAVE_PORTB(shadowPORTB=PORTB); + HAVE_PORTC(shadowPORTC=PORTC); APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); - HAVE_PORTA(PORTA=fakePORTA); - HAVE_PORTB(PORTB=fakePORTB); - HAVE_PORTC(PORTC=fakePORTC); + HAVE_PORTA(PORTA=shadowPORTA); + HAVE_PORTB(PORTB=shadowPORTB); + HAVE_PORTC(PORTC=shadowPORTC); } // setDCSignal(), called from normal context From e11d2d08d16a3c6952cebc85130aeb5fa9651e82 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Jun 2022 15:15:42 +0100 Subject: [PATCH 144/870] HAL catch pin overlaps --- IODevice.cpp | 12 ++++++++++++ IODevice.h | 5 ++++- IO_AnalogueInputs.h | 2 +- IO_DCCAccessory.cpp | 4 ++-- IO_DFPlayer.h | 2 +- IO_ExampleSerial.cpp | 2 +- IO_HCSR04.h | 3 ++- IO_MCP23008.h | 2 +- IO_MCP23017.h | 2 +- IO_PCA9685.cpp | 2 +- IO_PCF8574.h | 2 +- IO_VL53L0X.h | 2 +- 12 files changed, 28 insertions(+), 12 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index f13404ce1..de6b021f4 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -274,6 +274,18 @@ IODevice *IODevice::findDevice(VPIN vpin) { } return NULL; } + +// Private helper function to check for vpin overlap. Run during setup only. +// returns true if pins DONT overlap with existing device +bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins) { + for (VPIN testPin=firstPin; testPin< (firstPin+nPins); testPin++) + if (findDevice(testPin)) { + DIAG(F("WARNING HAL Pin %d overlap, re-definition of pins %d to %d ignored."), + testPin, firstPin, firstPin+nPins); + return false; + } + return true; +} //================================================================================================================== // Static data diff --git a/IODevice.h b/IODevice.h index 193b86b97..396fc46bd 100644 --- a/IODevice.h +++ b/IODevice.h @@ -228,6 +228,9 @@ class IODevice { // pin low if an input changes state. int16_t _gpioInterruptPin = -1; + // Method to check if pins will overlap before creating new device. + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1); + // Static support function for subclass creation static void addDevice(IODevice *newDevice); @@ -239,7 +242,7 @@ class IODevice { bool owns(VPIN vpin); // Method to find device handling Vpin static IODevice *findDevice(VPIN vpin); - + IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 85c224af3..ade497b11 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -69,7 +69,7 @@ class ADS111x: public IODevice { addDevice(this); } static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { - new ADS111x(firstVpin, nPins, i2cAddress); + if (checkNoOverlap(firstVpin,nPins)) new ADS111x(firstVpin, nPins, i2cAddress); } private: void _begin() { diff --git a/IO_DCCAccessory.cpp b/IO_DCCAccessory.cpp index 6e739cd78..214a2f9e8 100644 --- a/IO_DCCAccessory.cpp +++ b/IO_DCCAccessory.cpp @@ -26,8 +26,8 @@ #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); +void DCCAccessoryDecoder::create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress) { + if (checkNoOverlap(firstVpin,nPins)) new DCCAccessoryDecoder(firstVpin, nPins, DCCAddress, DCCSubaddress); } // Constructors diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index bdf062614..44d4b5ce4 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -78,7 +78,7 @@ class DFPlayer : public IODevice { } static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) { - new DFPlayer(firstVpin, nPins, serial); + if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial); } protected: diff --git a/IO_ExampleSerial.cpp b/IO_ExampleSerial.cpp index efcc3bf35..12476dbae 100644 --- a/IO_ExampleSerial.cpp +++ b/IO_ExampleSerial.cpp @@ -36,7 +36,7 @@ IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *se // Static create method for one module. void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { - new IO_ExampleSerial(firstVpin, nPins, serial, baud); + if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud); } // Device-specific initialisation diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 9bbd2f806..f41399179 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -86,7 +86,8 @@ class HCSR04 : public IODevice { // Static create function provides alternative way to create object static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { - new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); + if (checkNoOverlap(vpin) && checkNoOverlap(trigPin) && checkNoOverlap(echoPin)) + new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); } protected: diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 18ff12f89..806212836 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -25,7 +25,7 @@ class MCP23008 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); + if (checkNoOverlap(firstVpin, nPins)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); } // Constructor diff --git a/IO_MCP23017.h b/IO_MCP23017.h index 930b051f1..4ad84cbac 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -31,7 +31,7 @@ class MCP23017 : public GPIOBase { public: static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { - new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); + if (checkNoOverlap(vpin, nPins)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); } // Constructor diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 55065b7f9..ae6d3dd66 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -39,7 +39,7 @@ static void writeRegister(byte address, byte reg, byte value); // Create device driver instance. void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - new PCA9685(firstVpin, nPins, I2CAddress); + if (checkNoOverlap(firstVpin, nPins)) new PCA9685(firstVpin, nPins, I2CAddress); } // Configure a port on the PCA9685. diff --git a/IO_PCF8574.h b/IO_PCF8574.h index 58c465492..5a9dc050b 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -43,7 +43,7 @@ class PCF8574 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); + if (checkNoOverlap(firstVpin, nPins)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); } PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index bcdbc4950..71f81e06c 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -139,7 +139,7 @@ class VL53L0X : public IODevice { 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); + if (checkNoOverlap(firstVpin, nPins)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); } protected: From ef937dcacff630a70755767690b68238b0aaae6b Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Jun 2022 15:23:27 +0100 Subject: [PATCH 145/870] Privatize HAL constructors Forces caller to go via create function which includes overlap checks before class is instantiated. --- .gitignore | 1 + IODevice.h | 14 +++++++------- IO_AnalogueInputs.h | 8 ++++---- IO_DFPlayer.h | 13 +++++++------ IO_ExampleSerial.h | 2 +- IO_HCSR04.h | 18 +++++++++--------- IO_MCP23008.h | 2 +- IO_MCP23017.h | 5 ++--- IO_PCF8574.h | 2 +- IO_VL53L0X.h | 13 +++++++------ 10 files changed, 40 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 5e5d3557d..1f24578b6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ myAutomation.h myFilter.cpp myLayout.h .vscode/extensions.json +.vscode/extensions.json diff --git a/IODevice.h b/IODevice.h index 396fc46bd..a688ba327 100644 --- a/IODevice.h +++ b/IODevice.h @@ -260,8 +260,6 @@ class IODevice { class PCA9685 : public IODevice { public: static void create(VPIN vpin, int nPins, uint8_t I2CAddress); - // Constructor - PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress); enum ProfileType : uint8_t { Instant = 0, // Moves immediately between positions (if duration not specified) UseDuration = 0, // Use specified duration @@ -273,6 +271,8 @@ class PCA9685 : public IODevice { }; private: + // Constructor + PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress); // Device-specific initialisation void _begin() override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; @@ -320,10 +320,10 @@ class PCA9685 : public IODevice { class DCCAccessoryDecoder: public IODevice { public: static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress); - // Constructor - DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress); private: + // Constructor + DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress); // Device-specific write function. void _begin() override; void _write(VPIN vpin, int value) override; @@ -343,13 +343,13 @@ class ArduinoPins: public IODevice { addDevice(new ArduinoPins(firstVpin, nPins)); } - // Constructor - ArduinoPins(VPIN firstVpin, int nPins); - static void fastWriteDigital(uint8_t pin, uint8_t value); static bool fastReadDigital(uint8_t pin); private: + // Constructor + ArduinoPins(VPIN firstVpin, int nPins); + // Device-specific pin configuration bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; // Device-specific write function. diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index ade497b11..5849501b0 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -59,6 +59,10 @@ **********************************************************************************************/ class ADS111x: public IODevice { public: + static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(firstVpin,nPins)) new ADS111x(firstVpin, nPins, i2cAddress); + } +private: ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = min(nPins,4); @@ -68,10 +72,6 @@ class ADS111x: public IODevice { _value[i] = -1; addDevice(this); } - static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { - if (checkNoOverlap(firstVpin,nPins)) new ADS111x(firstVpin, nPins, i2cAddress); - } -private: void _begin() { // Initialise ADS device if (I2CManager.exists(_i2cAddress)) { diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 44d4b5ce4..575668bfd 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -69,6 +69,12 @@ class DFPlayer : public IODevice { unsigned long _commandSendTime; // Allows timeout processing public: + + static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) { + if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial); + } + +protected: // Constructor DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) : IODevice(firstVpin, nPins), @@ -77,12 +83,7 @@ class DFPlayer : public IODevice { addDevice(this); } - static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) { - if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial); - } - -protected: - void _begin() override { + void _begin() override { _serial->begin(9600); _deviceState = DEVSTATE_INITIALISING; diff --git a/IO_ExampleSerial.h b/IO_ExampleSerial.h index 582a51ca2..9b20399ca 100644 --- a/IO_ExampleSerial.h +++ b/IO_ExampleSerial.h @@ -36,10 +36,10 @@ class IO_ExampleSerial : public IODevice { public: - IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); protected: + IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); void _begin() override; void _loop(unsigned long currentMicros) override; void _write(VPIN vpin, int value) override; diff --git a/IO_HCSR04.h b/IO_HCSR04.h index f41399179..5b9b1691c 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -73,6 +73,14 @@ class HCSR04 : public IODevice { const uint16_t factor = 58; // ms/cm public: + + // Static create function provides alternative way to create object + static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { + if (checkNoOverlap(vpin) && checkNoOverlap(trigPin) && checkNoOverlap(echoPin)) + new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); + } + +protected: // Constructor perfroms static initialisation of the device object HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { _firstVpin = vpin; @@ -83,15 +91,7 @@ class HCSR04 : public IODevice { _offThreshold = offThreshold; addDevice(this); } - - // Static create function provides alternative way to create object - static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { - if (checkNoOverlap(vpin) && checkNoOverlap(trigPin) && checkNoOverlap(echoPin)) - new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); - } - -protected: - // _begin function called to perform dynamic initialisation of the device + // _begin function called to perform dynamic initialisation of the device void _begin() override { pinMode(_trigPin, OUTPUT); pinMode(_echoPin, INPUT); diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 806212836..916ebbefd 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -28,6 +28,7 @@ class MCP23008 : public GPIOBase { if (checkNoOverlap(firstVpin, nPins)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); } +private: // Constructor MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) { @@ -37,7 +38,6 @@ class MCP23008 : public GPIOBase { outputBuffer[0] = REG_GPIO; } -private: void _writeGpioPort() override { I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState); } diff --git a/IO_MCP23017.h b/IO_MCP23017.h index 4ad84cbac..e6ff5be07 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -33,7 +33,8 @@ class MCP23017 : public GPIOBase { static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { if (checkNoOverlap(vpin, nPins)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); } - + +private: // Constructor MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin) @@ -42,8 +43,6 @@ class MCP23017 : public GPIOBase { outputBuffer, sizeof(outputBuffer)); outputBuffer[0] = REG_GPIOA; } - -private: void _writeGpioPort() override { I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8); } diff --git a/IO_PCF8574.h b/IO_PCF8574.h index 5a9dc050b..d3f98c89e 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -46,13 +46,13 @@ class PCF8574 : public GPIOBase { if (checkNoOverlap(firstVpin, nPins)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); } +private: PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); } -private: // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. void _writeGpioPort() override { I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 71f81e06c..47204d5d5 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -127,7 +127,13 @@ class VL53L0X : public IODevice { }; const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29; -public: + + public: + static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + if (checkNoOverlap(firstVpin, nPins)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); + } + +protected: VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { _firstVpin = firstVpin; _nPins = min(nPins, 3); @@ -138,11 +144,6 @@ class VL53L0X : public IODevice { _value = 0; addDevice(this); } - static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { - if (checkNoOverlap(firstVpin, nPins)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); - } - -protected: void _begin() override { if (_xshutPin == VPIN_NONE) { // Check if device is already responding on the nominated address. From 10a0cfcccb9a6c12670695d1d7df1c841338f00c Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Jun 2022 15:28:13 +0100 Subject: [PATCH 146/870] change halSetup order --- IODevice.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index de6b021f4..d320c095a 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -32,7 +32,6 @@ // Link to halSetup function. If not defined, the function reference will be NULL. extern __attribute__((weak)) void halSetup(); -extern __attribute__((weak)) void mySetup(); // Deprecated function name, output warning if it's declared //================================================================================================================== // Static methods @@ -47,7 +46,16 @@ extern __attribute__((weak)) void mySetup(); // Deprecated function name, outpu // Create any standard device instances that may be required, such as the Arduino pins // and PCA9685. void IODevice::begin() { - // Initialise the IO subsystem + // Call user's halSetup() function (if defined in the build in myHal.cpp). + // The contents will depend on the user's system hardware configuration. + // The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs. + + // This is done first so that the following defaults will detect an overlap and not + // create something that conflicts with the users vpin definitions. + if (halSetup) + halSetup(); + + // Initialise the IO subsystem defaults ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access // Predefine two PCA9685 modules 0x40-0x41 // Allocates 32 pins 100-131 @@ -63,16 +71,6 @@ void IODevice::begin() { dev->_begin(); } _initPhase = false; - - // Check for presence of deprecated mySetup() function, and output warning. - if (mySetup) - DIAG(F("WARNING: mySetup() function should be renamed to halSetup()")); - - // Call user's halSetup() function (if defined in the build in myHal.cpp). - // The contents will depend on the user's system hardware configuration. - // The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs. - if (halSetup) - halSetup(); } // Overarching static loop() method for the IODevice subsystem. Works through the From 08eaa8ddb799fcfcc9970cecdb7b51a849c1ad7f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Jun 2022 17:21:11 +0100 Subject: [PATCH 147/870] I2C overlap checks (working but messy) Needs disgnostic clean and promotion of i2c address to IODevice. --- IODevice.cpp | 38 +++++++++++++++++++++++++++++++------- IODevice.h | 11 ++++++++--- IO_AnalogueInputs.h | 2 +- IO_GPIOBase.h | 6 +++++- IO_MCP23008.h | 2 +- IO_MCP23017.h | 2 +- IO_PCA9685.cpp | 2 +- IO_PCF8574.h | 2 +- IO_VL53L0X.h | 2 +- 9 files changed, 50 insertions(+), 17 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index d320c095a..c6af1377a 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -61,10 +61,14 @@ void IODevice::begin() { // Allocates 32 pins 100-131 PCA9685::create(100, 16, 0x40); PCA9685::create(116, 16, 0x41); + PCA9685::create(132, 16, 0x41); // should fail + PCA9685::create(118, 4, 0x42); // should fail + // Predefine two MCP23017 module 0x20/0x21 // Allocates 32 pins 164-195 MCP23017::create(164, 16, 0x20); MCP23017::create(180, 16, 0x21); +MCP23017::create(196, 16, 0x40); // should fail // Call the begin() methods of each configured device in turn for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) { @@ -275,16 +279,36 @@ IODevice *IODevice::findDevice(VPIN vpin) { // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device -bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins) { - for (VPIN testPin=firstPin; testPin< (firstPin+nPins); testPin++) - if (findDevice(testPin)) { - DIAG(F("WARNING HAL Pin %d overlap, re-definition of pins %d to %d ignored."), - testPin, firstPin, firstPin+nPins); - return false; +bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) { + DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress); + VPIN lastPin=firstPin+nPins-1; + for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { + + // check for pin range overlaps (verbose but compiler will fix that) + VPIN firstDevPin=dev->_firstVpin; + VPIN lastDevPin=firstDevPin+dev->_nPins-1; + bool noOverlap= firstPin>lastDevPin || lastPin_matchI2CAddress(i2cAddress)) { + DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress); + return false; + } } - return true; + return true; // no overlaps... OK to go on with constructor } + bool IODevice::_matchI2CAddress(uint8_t i2cAddress) { + // Overridden for I2c devices. + (void) i2cAddress; + return false; + } + //================================================================================================================== // Static data //------------------------------------------------------------------------------------------------------------------ diff --git a/IODevice.h b/IODevice.h index a688ba327..77325c2e5 100644 --- a/IODevice.h +++ b/IODevice.h @@ -228,8 +228,11 @@ class IODevice { // pin low if an input changes state. int16_t _gpioInterruptPin = -1; + // non-i2c hal drivers return false, i2c drivers override this in IO_GPIOBase + virtual bool _matchI2CAddress(uint8_t i2cAddress); + // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1); + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); // Static support function for subclass creation static void addDevice(IODevice *newDevice); @@ -242,7 +245,7 @@ class IODevice { bool owns(VPIN vpin); // Method to find device handling Vpin static IODevice *findDevice(VPIN vpin); - + uint8_t _I2CAddress; IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; @@ -284,7 +287,9 @@ class PCA9685 : public IODevice { void updatePosition(uint8_t pin); void writeDevice(uint8_t pin, int value); void _display() override; - + virtual bool _matchI2CAddress(uint8_t i2caddress) override { + return i2caddress && i2caddress==_I2CAddress; + } uint8_t _I2CAddress; // 0x40-0x43 possible struct ServoData { diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 5849501b0..1af351d7d 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -60,7 +60,7 @@ class ADS111x: public IODevice { public: static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { - if (checkNoOverlap(firstVpin,nPins)) new ADS111x(firstVpin, nPins, i2cAddress); + if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress); } private: ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) { diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 3bc75afa8..d2a1339fb 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -45,9 +45,13 @@ class GPIOBase : public IODevice { int _read(VPIN vpin) override; void _display() override; void _loop(unsigned long currentMicros) override; + virtual bool _matchI2CAddress(uint8_t i2cAddress) override { + DIAG(F("MatchI2c %x %x"), i2cAddress, _I2CAddress); + return (i2cAddress && i2cAddress==_I2CAddress); + } // Data fields - uint8_t _I2CAddress; + uint8_t _I2CAddress; // Allocate enough space for all input pins T _portInputState; T _portOutputState; diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 916ebbefd..aa4679b96 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -25,7 +25,7 @@ class MCP23008 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); + if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); } private: diff --git a/IO_MCP23017.h b/IO_MCP23017.h index e6ff5be07..65769f600 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -31,7 +31,7 @@ class MCP23017 : public GPIOBase { public: static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(vpin, nPins)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); + if (checkNoOverlap(vpin, nPins, I2CAddress)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); } private: diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index ae6d3dd66..3d7c34785 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -39,7 +39,7 @@ static void writeRegister(byte address, byte reg, byte value); // Create device driver instance. void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - if (checkNoOverlap(firstVpin, nPins)) new PCA9685(firstVpin, nPins, I2CAddress); + if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685(firstVpin, nPins, I2CAddress); } // Configure a port on the PCA9685. diff --git a/IO_PCF8574.h b/IO_PCF8574.h index d3f98c89e..44eccadad 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -43,7 +43,7 @@ class PCF8574 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); + if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); } private: diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 47204d5d5..9d5134586 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -130,7 +130,7 @@ class VL53L0X : public IODevice { public: static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { - if (checkNoOverlap(firstVpin, nPins)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); } protected: From 6b7c2ccdf0d56966bddb531d6072b0133fce4958 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Jun 2022 17:35:29 +0100 Subject: [PATCH 148/870] I2C address checks cleaned up --- IODevice.cpp | 11 ++++------- IODevice.h | 11 ++++------- IO_GPIOBase.h | 6 +----- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index c6af1377a..f76bbb02d 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -61,14 +61,11 @@ void IODevice::begin() { // Allocates 32 pins 100-131 PCA9685::create(100, 16, 0x40); PCA9685::create(116, 16, 0x41); - PCA9685::create(132, 16, 0x41); // should fail - PCA9685::create(118, 4, 0x42); // should fail // Predefine two MCP23017 module 0x20/0x21 // Allocates 32 pins 164-195 MCP23017::create(164, 16, 0x20); MCP23017::create(180, 16, 0x21); -MCP23017::create(196, 16, 0x40); // should fail // Call the begin() methods of each configured device in turn for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) { @@ -280,7 +277,9 @@ IODevice *IODevice::findDevice(VPIN vpin) { // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) { +#ifdef DIAG_IO DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress); +#endif VPIN lastPin=firstPin+nPins-1; for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { @@ -303,10 +302,8 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) return true; // no overlaps... OK to go on with constructor } - bool IODevice::_matchI2CAddress(uint8_t i2cAddress) { - // Overridden for I2c devices. - (void) i2cAddress; - return false; +bool IODevice::_matchI2CAddress(uint8_t i2cAddress) { + return (i2cAddress && i2cAddress==_I2CAddress); } //================================================================================================================== diff --git a/IODevice.h b/IODevice.h index 77325c2e5..07a632aac 100644 --- a/IODevice.h +++ b/IODevice.h @@ -168,6 +168,7 @@ class IODevice { _firstVpin = firstVpin; _nPins = nPins; _nextEntryTime = 0; + _I2CAddress=0; } // Method to perform initialisation of the device (optionally implemented within device class) @@ -220,7 +221,7 @@ class IODevice { // Common object fields. VPIN _firstVpin; int _nPins; - + uint8_t _I2CAddress; // Flag whether the device supports callbacks. bool _hasCallback = false; @@ -229,7 +230,7 @@ class IODevice { int16_t _gpioInterruptPin = -1; // non-i2c hal drivers return false, i2c drivers override this in IO_GPIOBase - virtual bool _matchI2CAddress(uint8_t i2cAddress); + bool _matchI2CAddress(uint8_t i2cAddress); // Method to check if pins will overlap before creating new device. static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); @@ -245,7 +246,6 @@ class IODevice { bool owns(VPIN vpin); // Method to find device handling Vpin static IODevice *findDevice(VPIN vpin); - uint8_t _I2CAddress; IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; @@ -287,10 +287,7 @@ class PCA9685 : public IODevice { void updatePosition(uint8_t pin); void writeDevice(uint8_t pin, int value); void _display() override; - virtual bool _matchI2CAddress(uint8_t i2caddress) override { - return i2caddress && i2caddress==_I2CAddress; - } - uint8_t _I2CAddress; // 0x40-0x43 possible + struct ServoData { uint16_t activePosition : 12; // Config parameter diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index d2a1339fb..1a66b3deb 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -45,13 +45,9 @@ class GPIOBase : public IODevice { int _read(VPIN vpin) override; void _display() override; void _loop(unsigned long currentMicros) override; - virtual bool _matchI2CAddress(uint8_t i2cAddress) override { - DIAG(F("MatchI2c %x %x"), i2cAddress, _I2CAddress); - return (i2cAddress && i2cAddress==_I2CAddress); - } // Data fields - uint8_t _I2CAddress; + // Allocate enough space for all input pins T _portInputState; T _portOutputState; From 3496b9919747814825931b28d059b01b9d137be5 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 14 Jun 2022 17:50:57 +0100 Subject: [PATCH 149/870] HAL minor simplification --- IODevice.cpp | 5 +---- IODevice.h | 3 --- IO_HCSR04.h | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index f76bbb02d..3b6094bec 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -294,7 +294,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) } // Check for overlapping I2C address - if (dev->_matchI2CAddress(i2cAddress)) { + if (i2cAddress && dev->_I2CAddress==i2cAddress) { DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress); return false; } @@ -302,9 +302,6 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) return true; // no overlaps... OK to go on with constructor } -bool IODevice::_matchI2CAddress(uint8_t i2cAddress) { - return (i2cAddress && i2cAddress==_I2CAddress); - } //================================================================================================================== // Static data diff --git a/IODevice.h b/IODevice.h index 07a632aac..8b786c42a 100644 --- a/IODevice.h +++ b/IODevice.h @@ -228,9 +228,6 @@ class IODevice { // 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; - - // non-i2c hal drivers return false, i2c drivers override this in IO_GPIOBase - bool _matchI2CAddress(uint8_t i2cAddress); // Method to check if pins will overlap before creating new device. static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 5b9b1691c..d56dda389 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -76,7 +76,7 @@ class HCSR04 : public IODevice { // Static create function provides alternative way to create object static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { - if (checkNoOverlap(vpin) && checkNoOverlap(trigPin) && checkNoOverlap(echoPin)) + if (checkNoOverlap(vpin)) new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); } From 044b467085ebe60532ac9c91d4d1b0bcceacb6dc Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 15 Jun 2022 11:44:46 +0100 Subject: [PATCH 150/870] Implement HAL macro in exrail --- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 9 +++++++++ IODevice.cpp | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 80e82af8d..bdd6682fd 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -58,6 +58,7 @@ #undef FREE #undef FWD #undef GREEN +#undef HAL #undef IF #undef IFAMBER #undef IFCLOSED @@ -161,6 +162,7 @@ #define FREE(blockid) #define FWD(speed) #define GREEN(signal_id) +#define HAL(haltype,params...) #define IF(sensor_id) #define IFAMBER(signal_id) #define IFCLOSED(turnout_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index cff0753e5..e953936bf 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -61,6 +61,14 @@ #define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10; #include "myAutomation.h" +// Pass 1h Implements HAL macro by creating exrailHalSetup function +#include "EXRAIL2MacroReset.h" +#undef HAL +#define HAL(haltype,params...) haltype::create(params); +void exrailHalSetup() { + #include "myAutomation.h" +} + // Pass 2 create throttle route list #include "EXRAIL2MacroReset.h" #undef ROUTE @@ -234,6 +242,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define FREE(blockid) OPCODE_FREE,V(blockid), #define FWD(speed) OPCODE_FWD,V(speed), #define GREEN(signal_id) OPCODE_GREEN,V(signal_id), +#define HAL(haltype,params...) #define IF(sensor_id) OPCODE_IF,V(sensor_id), #define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id), #define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id), diff --git a/IODevice.cpp b/IODevice.cpp index 3b6094bec..b2921950b 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -32,6 +32,7 @@ // Link to halSetup function. If not defined, the function reference will be NULL. extern __attribute__((weak)) void halSetup(); +extern __attribute__((weak)) void exrailHalSetup(); //================================================================================================================== // Static methods @@ -55,6 +56,10 @@ void IODevice::begin() { if (halSetup) halSetup(); + // include any HAL devices defined in exrail. + if (exrailHalSetup) + exrailHalSetup(); + // Initialise the IO subsystem defaults ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access // Predefine two PCA9685 modules 0x40-0x41 From d0e71875e068ecb4190e89c15e01716ef6a5086d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 17 Jun 2022 11:48:37 +0100 Subject: [PATCH 151/870] UNTESTED ONRED/ONAMBER/ONGREEN with genericl code tidy for other ON handlers. --- EXRAIL2.cpp | 149 +++++++++++++++++--------------------------- EXRAIL2.h | 15 +++-- EXRAIL2MacroReset.h | 6 ++ EXRAILMacros.h | 3 + 4 files changed, 75 insertions(+), 98 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 243768b0b..a3c065da8 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -87,6 +87,9 @@ LookList * RMFT2::onThrowLookup=NULL; LookList * RMFT2::onCloseLookup=NULL; LookList * RMFT2::onActivateLookup=NULL; LookList * RMFT2::onDeactivateLookup=NULL; +LookList * RMFT2::onRedLookup=NULL; +LookList * RMFT2::onAmberLookup=NULL; +LookList * RMFT2::onGreenLookup=NULL; #define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter) #define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3)) @@ -116,56 +119,40 @@ int16_t LookList::find(int16_t value) { return -1; } -/* static */ void RMFT2::begin() { - DCCEXParser::setRMFTFilter(RMFT2::ComandFilter); - for (int f=0;fadd(GET_OPERAND(0),progCounter); } + return list; +} +/* static */ void RMFT2::begin() { + DCCEXParser::setRMFTFilter(RMFT2::ComandFilter); + for (int f=0;fadd(operand,progCounter); - break; - - case OPCODE_ONTHROW: - onThrowLookup->add(operand,progCounter); - break; - - case OPCODE_ONCLOSE: - onCloseLookup->add(operand,progCounter); - break; - - case OPCODE_ONACTIVATE: - onActivateLookup->add(operand,progCounter); - break; - - case OPCODE_ONDEACTIVATE: - onDeactivateLookup->add(operand,progCounter); - break; - + case OPCODE_AUTOSTART: // automatically create a task from here at startup. - new RMFT2(progCounter); + // but we will do one at 0 anyway by default. + if (progCounter>0) new RMFT2(progCounter); break; default: // Ignore @@ -249,9 +216,7 @@ int16_t LookList::find(int16_t value) { } SKIPOP; // include ENDROUTES opcode - DIAG(F("EXRAIL %db, fl=%d seq=%d, onT=%d, onC=%d"), - progCounter,MAX_FLAGS, - sequenceCount, onThrowCount, onCloseCount); + DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS); new RMFT2(0); // add the startup route } @@ -461,7 +426,7 @@ RMFT2::RMFT2(int progCtr) { invert=false; timeoutFlag=false; stackDepth=0; - onTurnoutId=-1; // Not handling an ONTHROW/ONCLOSE + onEventStartPosition=-1; // Not handling an ONxxx // chain into ring of RMFTs if (loopTask==NULL) { @@ -1013,6 +978,13 @@ int16_t RMFT2::getSignalSlot(VPIN id) { } /* static */ void RMFT2::doSignal(VPIN id,char rag) { if (diag) DIAG(F(" doSignal %d %x"),id,rag); + + // Schedule any event handler for this signal change. + // Thjis will work even without a signal definition. + if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id); + else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id); + else handleEvent(F("AMBER"), onAmberLookup,id); + int16_t sigslot=getSignalSlot(id); if (sigslot<0) return; @@ -1027,6 +999,7 @@ int16_t RMFT2::getSignalSlot(VPIN id) { VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3); if (diag) DIAG(F("signal %d %d %d %d"),sigid,redpin,amberpin,greenpin); + if (sigid & SERVO_SIGNAL_FLAG) { // A servo signal, the pin numbers are actually servo positions // Note, setting a signal to a zero position has no effect. @@ -1059,46 +1032,36 @@ int16_t RMFT2::getSignalSlot(VPIN id) { void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) { // Hunt for an ONTHROW/ONCLOSE for this turnout - int pc= (closed?onCloseLookup:onThrowLookup)->find(turnoutId); - if (pc<0) return; - - // Check we dont already have a task running this turnout - RMFT2 * task=loopTask; - while(task) { - if (task->onTurnoutId==turnoutId) { - DIAG(F("Recursive ONTHROW/ONCLOSE for Turnout %d"),turnoutId); - return; - } - task=task->next; - if (task==loopTask) break; - } - - task=new RMFT2(pc); // new task starts at this instruction - task->onTurnoutId=turnoutId; // flag for recursion detector + if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId); + else handleEvent(F("THROW"),onThrowLookup,turnoutId); } + void RMFT2::activateEvent(int16_t addr, bool activate) { // Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory - int pc= (activate?onActivateLookup:onDeactivateLookup)->find(addr); + if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr); + else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr); +} + +void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { + int pc= handlers->find(id); if (pc<0) return; - // Check we dont already have a task running this address + // Check we dont already have a task running this handler RMFT2 * task=loopTask; while(task) { - if (task->onActivateAddr==addr) { - DIAG(F("Recursive ON(DE)ACTIVATE for %d"),addr); + if (task->onEventStartPosition==pc) { + DIAG(F("Recursive ON%S(%d)"),reason, id); return; } task=task->next; if (task==loopTask) break; } - task->onActivateAddr=addr; // flag for recursion detector task=new RMFT2(pc); // new task starts at this instruction + task->onEventStartPosition=pc; // flag for recursion detector } void RMFT2::printMessage2(const FSH * msg) { DIAG(F("EXRAIL(%d) %S"),loco,msg); } - - diff --git a/EXRAIL2.h b/EXRAIL2.h index ba7facd37..6bbae23c4 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -53,6 +53,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE, OPCODE_ENDTASK,OPCODE_ENDEXRAIL, OPCODE_SET_TRACK, + OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -63,7 +64,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFTIMEOUT, OPCODE_IF,OPCODE_IFNOT, OPCODE_IFRANDOM,OPCODE_IFRESERVE, - OPCODE_IFCLOSED, OPCODE_IFTHROWN + OPCODE_IFCLOSED,OPCODE_IFTHROWN }; @@ -132,6 +133,9 @@ class LookList { static bool isSignal(VPIN id,char rag); static int16_t getSignalSlot(VPIN id); static void setTurnoutHiddenState(Turnout * t); + static LookList* LookListLoader(OPCODE op1, + OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); + static void handleEvent(const FSH* reason,LookList* handlers, int16_t id); static RMFT2 * loopTask; static RMFT2 * pausingTask; void delayMe(long millisecs); @@ -144,7 +148,6 @@ class LookList { void printMessage(uint16_t id); // Built by RMFTMacros.h void printMessage2(const FSH * msg); - static bool diag; static const FLASH byte RouteCode[]; static const FLASH int16_t SignalDefinitions[]; @@ -154,7 +157,10 @@ class LookList { static LookList * onCloseLookup; static LookList * onActivateLookup; static LookList * onDeactivateLookup; - + static LookList * onRedLookup; + static LookList * onAmberLookup; + static LookList * onGreenLookup; + // Local variables - exist for each instance/task RMFT2 *next; // loop chain @@ -172,8 +178,7 @@ class LookList { bool forward; bool invert; byte speedo; - int16_t onTurnoutId; - int16_t onActivateAddr; + int onEventStartPosition; byte stackDepth; int callStack[MAX_STACK_DEPTH]; }; diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index bdd6682fd..f5b76033e 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -79,9 +79,12 @@ #undef LCN #undef ONACTIVATE #undef ONACTIVATEL +#undef ONAMBER #undef ONDEACTIVATE #undef ONDEACTIVATEL #undef ONCLOSE +#undef ONGREEN +#undef ONRED #undef ONTHROW #undef PARSE #undef PAUSE @@ -183,9 +186,12 @@ #define LCN(msg) #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) +#define ONAMBER(signal_id) #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) #define ONCLOSE(turnout_id) +#define ONGREEN(signal_id) +#define ONRED(signal_id) #define ONTHROW(turnout_id) #define PAUSE #define PIN_TURNOUT(id,pin,description...) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index e953936bf..535422684 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -263,9 +263,12 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define LCN(msg) PRINT(msg) #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), +#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), +#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), +#define ONRED(signal_id) OPCODE_ONRED,V(signal_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define PAUSE OPCODE_PAUSE,0,0, #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), From 6104311ccbda0a9d7b1aa222194d27070a0de53f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 18 Jun 2022 12:31:54 +0100 Subject: [PATCH 152/870] Signal fixes and DCC_SIGNAL/VIRTUAL_SIGNAL --- EXRAIL2.cpp | 52 +++++++++++++++++++++++++++------------------ EXRAIL2.h | 9 ++++---- EXRAIL2MacroReset.h | 4 ++++ EXRAILMacros.h | 7 ++++++ 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index a3c065da8..fa8d5755e 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -170,6 +170,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { switch (opcode) { case OPCODE_AT: + case OPCODE_ATTIMEOUT2: case OPCODE_AFTER: case OPCODE_IF: case OPCODE_IFNOT: { @@ -348,13 +349,14 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { return true; } - // all other / commands take 1 parameter 0 to MAX_FLAGS-1 - if (paramCount!=2 || p[1]<0 || p[1]>=MAX_FLAGS) return false; + // all other / commands take 1 parameter + if (paramCount!=2 ) return false; switch (p[0]) { case HASH_KEYWORD_KILL: // Kill taskid|ALL { - RMFT2 * task=loopTask; + if ( p[1]<0 || p[1]>=MAX_FLAGS) return false; + RMFT2 * task=loopTask; while(task) { if (task->taskId==p[1]) { task->kill(F("KILL")); @@ -367,20 +369,16 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { return false; case HASH_KEYWORD_RESERVE: // force reserve a section - setFlag(p[1],SECTION_FLAG); - return true; + return setFlag(p[1],SECTION_FLAG); case HASH_KEYWORD_FREE: // force free a section - setFlag(p[1],0,SECTION_FLAG); - return true; + return setFlag(p[1],0,SECTION_FLAG); case HASH_KEYWORD_LATCH: - setFlag(p[1], LATCH_FLAG); - return true; + return setFlag(p[1], LATCH_FLAG); case HASH_KEYWORD_UNLATCH: - setFlag(p[1], 0, LATCH_FLAG); - return true; + return setFlag(p[1], 0, LATCH_FLAG); case HASH_KEYWORD_RED: doSignal(p[1],SIGNAL_RED); @@ -926,6 +924,10 @@ void RMFT2::loop2() { case OPCODE_ONTHROW: case OPCODE_ONACTIVATE: // Activate event catchers ignored here case OPCODE_ONDEACTIVATE: + case OPCODE_ONRED: + case OPCODE_ONAMBER: + case OPCODE_ONGREEN: + break; default: @@ -942,12 +944,13 @@ void RMFT2::delayMe(long delay) { delayStart=millis(); } -void RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { - if (FLAGOVERFLOW(id)) return; // Outside range limit +boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { + if (FLAGOVERFLOW(id)) return false; // Outside range limit byte f=flags[id]; f &= ~offMask; f |= onMask; flags[id]=f; + return true; } bool RMFT2::getFlag(VPIN id,byte mask) { @@ -961,9 +964,9 @@ void RMFT2::kill(const FSH * reason, int operand) { delete this; } -int16_t RMFT2::getSignalSlot(VPIN id) { +int16_t RMFT2::getSignalSlot(int16_t id) { for (int sigpos=0;;sigpos+=4) { - VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); + int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); if (sigid==0) { // end of signal list DIAG(F("EXRAIL Signal %d not defined"), id); return -1; @@ -976,7 +979,7 @@ int16_t RMFT2::getSignalSlot(VPIN id) { return sigpos/4; // relative slot in signals table } } -/* static */ void RMFT2::doSignal(VPIN id,char rag) { +/* static */ void RMFT2::doSignal(int16_t id,char rag) { if (diag) DIAG(F(" doSignal %d %x"),id,rag); // Schedule any event handler for this signal change. @@ -997,10 +1000,11 @@ int16_t RMFT2::getSignalSlot(VPIN id) { VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3); - if (diag) DIAG(F("signal %d %d %d %d"),sigid,redpin,amberpin,greenpin); + if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin); + VPIN sigtype=sigid & ~SIGNAL_ID_MASK; - if (sigid & SERVO_SIGNAL_FLAG) { + if (sigtype == SERVO_SIGNAL_FLAG) { // A servo signal, the pin numbers are actually servo positions // Note, setting a signal to a zero position has no effect. int16_t servopos= rag==SIGNAL_RED? redpin: (rag==SIGNAL_GREEN? greenpin : amberpin); @@ -1009,7 +1013,14 @@ int16_t RMFT2::getSignalSlot(VPIN id) { return; } - // LED or similar 3 pin signal + + if (sigtype== DCC_SIGNAL_FLAG) { + // redpin,amberpin are the DCC addr,subaddr + DCC::setAccessory(redpin,amberpin, rag!=SIGNAL_RED); + return; + } + + // LED or similar 3 pin signal, (all pins zero would be a virtual signal) // If amberpin is zero, synthesise amber from red+green const byte SIMAMBER=0x00; if (rag==SIGNAL_AMBER && (amberpin==0)) rag=SIMAMBER; // special case this func only @@ -1021,10 +1032,9 @@ int16_t RMFT2::getSignalSlot(VPIN id) { if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh); if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh); if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh); - return; } -/* static */ bool RMFT2::isSignal(VPIN id,char rag) { +/* static */ bool RMFT2::isSignal(int16_t id,char rag) { int16_t sigslot=getSignalSlot(id); if (sigslot<0) return false; return (flags[sigslot] & SIGNAL_MASK) == rag; diff --git a/EXRAIL2.h b/EXRAIL2.h index 6bbae23c4..323ae57b3 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -109,6 +109,7 @@ class LookList { static void activateEvent(int16_t addr, bool active); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; + static const int16_t DCC_SIGNAL_FLAG=0x1000; static const int16_t SIGNAL_ID_MASK=0x0FFF; // Throttle Info Access functions built by exrail macros @@ -126,12 +127,12 @@ class LookList { static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ; static void streamFlags(Print* stream); - static void setFlag(VPIN id,byte onMask, byte OffMask=0); + static bool setFlag(VPIN id,byte onMask, byte OffMask=0); static bool getFlag(VPIN id,byte mask); static int16_t progtrackLocoId; - static void doSignal(VPIN id,char rag); - static bool isSignal(VPIN id,char rag); - static int16_t getSignalSlot(VPIN id); + static void doSignal(int16_t id,char rag); + static bool isSignal(int16_t id,char rag); + static int16_t getSignalSlot(int16_t id); static void setTurnoutHiddenState(Turnout * t); static LookList* LookListLoader(OPCODE op1, OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index f5b76033e..fb0ea2398 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -37,6 +37,7 @@ #undef BROADCAST #undef CALL #undef CLOSE +#undef DCC_SIGNAL #undef DEACTIVATE #undef DEACTIVATEL #undef DELAY @@ -124,6 +125,7 @@ #undef TURNOUT #undef UNJOIN #undef UNLATCH +#undef VIRTUAL_SIGNAL #undef VIRTUAL_TURNOUT #undef WAITFOR #undef XFOFF @@ -144,6 +146,7 @@ #define BROADCAST(msg) #define CALL(route) #define CLOSE(id) +#define DCC_SIGNAL(id,add,subaddr) #define DEACTIVATE(addr,subaddr) #define DEACTIVATEL(addr) #define DELAY(mindelay) @@ -231,6 +234,7 @@ #define TURNOUT(id,addr,subaddr,description...) #define UNJOIN #define UNLATCH(sensor_id) +#define VIRTUAL_SIGNAL(id) #define VIRTUAL_TURNOUT(id,description...) #define WAITFOR(pin) #define XFOFF(cab,func) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 535422684..f2b5bcaba 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -193,6 +193,11 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin, #undef SERVO_SIGNAL #define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval, +#undef DCC_SIGNAL +#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0, +#undef VIRTUAL_SIGNAL +#define VIRTUAL_SIGNAL(id) id,0,0,0, + const FLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; @@ -226,6 +231,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)), #define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay), #define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L), +#define DCC_SIGNAL(id,add,subaddr) #define DONE OPCODE_ENDTASK,0,0, #define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin), #define ELSE OPCODE_ELSE,0,0, @@ -308,6 +314,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr), #define UNJOIN OPCODE_UNJOIN,0,0, #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), +#define VIRTUAL_SIGNAL(id) #define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0), #define WAITFOR(pin) OPCODE_WAITFOR,V(pin), #define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func), From 7d6c2c8afbb79c8950b42d56561522788d8b2299 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 18 Jun 2022 13:58:46 +0100 Subject: [PATCH 153/870] smaller random Saves over 300 bytes of progmem on a uno by omitting the random library --- EXRAIL2.cpp | 4 ++-- objdump.bat | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index fa8d5755e..30b1b6de1 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -706,7 +706,7 @@ void RMFT2::loop2() { break; case OPCODE_IFRANDOM: // do block on random percentage - skipIf=(int16_t)random(100)>=operand; + skipIf=(int16_t)(micros()%100) >= operand; break; case OPCODE_IFRESERVE: // do block if we successfully RERSERVE @@ -750,7 +750,7 @@ void RMFT2::loop2() { break; case OPCODE_RANDWAIT: - delayMe(random(operand)*100L); + delayMe((micros()%operand) *100L); break; case OPCODE_RED: diff --git a/objdump.bat b/objdump.bat index 94e19e841..26e323326 100644 --- a/objdump.bat +++ b/objdump.bat @@ -2,7 +2,7 @@ ECHO ON FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf - +set PATH="C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\";%PATH% avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt From a5cda1e350e5fe264787c31c6cb0b699c1b69497 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 27 Jun 2022 12:42:59 +0100 Subject: [PATCH 154/870] RingStream RAM saver --- RingStream.cpp | 86 +++++++++++++++++++++++++++++++++++++++++---- RingStream.h | 15 ++++++-- StringFormatter.cpp | 16 ++++++++- StringFormatter.h | 2 ++ 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index bf89e3301..a03bfbfeb 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -18,9 +18,17 @@ * along with CommandStation. If not, see . */ + +// NOTE: The use of a marker byte without an escape algorithm means +// RingStream is unsuitable for binary data. Should binary data need to be +// streamed it will be necessary to implementr an escape strategy to handle the +// marker char when embedded in data. + #include "RingStream.h" #include "DIAG.h" +const byte FLASH_INSERT_MARKER=0xff; + RingStream::RingStream( const uint16_t len) { _len=len; @@ -31,6 +39,7 @@ RingStream::RingStream( const uint16_t len) _overflow=false; _mark=0; _count=0; + _flashInsert=0; } size_t RingStream::write(uint8_t b) { @@ -46,8 +55,74 @@ size_t RingStream::write(uint8_t b) { return 1; } +// Ideally, I would prefer to override the Print:print(_FlashStringHelper) function +// but the library authors omitted to make this virtual. +// Therefore we obveride the only other simple function that has no side effects +// in order that StringFormatter can recognise a RingStream and call its +// printFlash() directly. +int RingStream::availableForWrite() { + return THIS_IS_A_RINGSTREAM; + } + +size_t RingStream::printFlash(const FSH * flashBuffer) { +// We are about to add a PROGMEM string to the buffer. +// To save RAM we can insert a marker and the +// progmem address into the buffer instead. +// The buffer reading code must recognise this marker and +// silently extract the progmem bytes. +// In addition, we must make the count correct as if the +// string had been embedded so that things like the wifi code +// can read the expected count before reading the buffer. + +// Establish the actual length of the progmem string. +char * flash=(char *)flashBuffer; +int16_t plength=strlen_P(flash); +if (plength==0) return 0; // just ignore empty string + +// Retain the buffer count as it will be modified by the marker+address insert +int prevCount=_count; +write(FLASH_INSERT_MARKER); // write the marker +uintptr_t iFlash=reinterpret_cast(flash); // expect size match with pointer + +// write address bytes LSB first (size depends on CPU) +for (byte f=0;f>=8; +} + +// correct the buffer count to reflect the flash length, not the marker/addr. +_count=prevCount+plength; +return plength; +} + int RingStream::read() { + if (_flashInsert) { + // we are reading out of a flash string + byte fb=GETFLASH(_flashInsert); + _flashInsert++; + if (fb) return fb; // we have a byte from the flash + // flash insert complete, clear and drop through to next buffer byte + _flashInsert=NULL; + } if ((_pos_read==_pos_write) && !_overflow) return -1; // empty + byte b=readRawByte(); + if (b!=FLASH_INSERT_MARKER) return b; + + // Detected a flash insert + // read address bytes LSB first (size depends on CPU) + uintptr_t iFlash=0; + for (byte f=0; f( iFlash); + // and try again... so will read the first byte of the insert. + return read(); +} + +byte RingStream::readRawByte() { byte b=_buffer[_pos_read]; _pos_read++; if (_pos_read==_len) _pos_read=0; @@ -55,9 +130,8 @@ int RingStream::read() { return b; } - int RingStream::count() { - return (read()<<8) | read(); + return (readRawByte()<<8) | readRawByte(); } int RingStream::freeSpace() { @@ -83,6 +157,7 @@ uint8_t RingStream::peekTargetMark() { } bool RingStream::commit() { + _flashInsert=NULL; // prepared for first read if (_overflow) { DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); // just throw it away @@ -108,8 +183,7 @@ void RingStream::flush() { _pos_write=0; _pos_read=0; _buffer[0]=0; + _flashInsert=NULL; // prepared for first read + } -void RingStream::printBuffer(Print * stream) { - _buffer[_pos_write]='\0'; - stream->print((char *)_buffer); -} + diff --git a/RingStream.h b/RingStream.h index 5821bc439..61c1737bd 100644 --- a/RingStream.h +++ b/RingStream.h @@ -21,22 +21,32 @@ */ #include +#include "FSH.h" class RingStream : public Print { public: RingStream( const uint16_t len); - + static const int THIS_IS_A_RINGSTREAM=77; virtual size_t write(uint8_t b); + + // This availableForWrite function is subverted from its original intention so that a caller + // can destinguish between a normal stream and a RingStream. + // The Arduino compiler does not support runtime dynamic cast to perform + // an instranceOf check. + // This is necessary since the Print functions are mostly not virtual so + // we cant override the print(__FlashStringHelper *) function. + virtual int availableForWrite() override; using Print::write; + size_t printFlash(const FSH * flashBuffer); int read(); int count(); int freeSpace(); void mark(uint8_t b); bool commit(); uint8_t peekTargetMark(); - void printBuffer(Print * streamer); void flush(); + byte readRawByte(); private: int _len; int _pos_write; @@ -45,6 +55,7 @@ class RingStream : public Print { int _mark; int _count; byte * _buffer; + char * _flashInsert; }; #endif diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 485d8fd08..ff5a96a4e 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -97,7 +97,21 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 's': stream->print(va_arg(args, char*)); break; case 'e': printEscapes(stream,va_arg(args, char*)); break; case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break; - case 'S': stream->print((const FSH*)va_arg(args, char*)); break; + case 'S': + { + const FSH* flash= (const FSH*)va_arg(args, char*); + +#if WIFI_ON | ETHERNET_ON + // RingStream has special logic to handle flash strings + // but is not implemented unless wifi or ethernet are enabled. + // The define prevents RingStream code being added unnecessariliy. + if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) + ((RingStream *)stream)->printFlash(flash); + else +#endif + stream->print(flash); + break; + } case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; diff --git a/StringFormatter.h b/StringFormatter.h index 50ce0f732..1e225b531 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -20,6 +20,8 @@ #define StringFormatter_h #include #include "FSH.h" +#include "RingStream.h" + #if defined(ARDUINO_ARCH_SAMD) // Some processors use a gcc compiler that renames va_list!!! #include From 959225c2528442ab81b4696b77324aea26e026ba Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sun, 3 Jul 2022 18:01:24 +0700 Subject: [PATCH 155/870] Added Arduino Zero USB platform support --- platformio.ini | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/platformio.ini b/platformio.ini index a8b1e8b79..740c8cc90 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,6 +16,7 @@ default_envs = unowifiR2 nano samd21-dev-usb + samd21-zero-usb src_dir = . include_dir = . @@ -33,6 +34,17 @@ monitor_speed = 115200 monitor_flags = --echo build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM +[env:samd21-zero-usb] +platform = atmelsam +board = zeroUSB +framework = arduino +upload_protocol = sam-ba +lib_deps = + ${env.lib_deps} +monitor_speed = 115200 +monitor_flags = --echo +build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM + [env:samc21-firebox] platform = atmelsam board = firebox From 695b776493d70202dc1564de8b9b822906bbd4a4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 4 Jul 2022 22:42:04 +0200 Subject: [PATCH 156/870] add Pololu TB9051FTG --- MotorDrivers.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MotorDrivers.h b/MotorDrivers.h index b2b798dc3..d2dc027c6 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -65,6 +65,13 @@ // new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12) // See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet. +// Pololu Dual TB9051FTG Motor Shield +// This is the shield without modifications which means +// no HA waveform and no RailCom on an Arduino Mega 2560 +#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \ + new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \ + new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12) + // Firebox Mk1 #define FIREBOX_MK1 F("FIREBOX_MK1"), \ new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ From 0406ca69cfeeeac0e07b32b6400d8ba5afcea5fe Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 4 Jul 2022 22:50:19 +0200 Subject: [PATCH 157/870] add comment Pololu TB9051FTG --- config.example.h | 1 + 1 file changed, 1 insertion(+) diff --git a/config.example.h b/config.example.h index 3dac20717..d6d60dcdd 100644 --- a/config.example.h +++ b/config.example.h @@ -37,6 +37,7 @@ The configuration file for DCC-EX Command Station // // STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel // POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) +// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver // FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) // FIREBOX_MK1 : The Firebox MK1 // FIREBOX_MK1S : The Firebox MK1S From 6687c6f46d59eb5989c237ba7cae5706df342c9e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 4 Jul 2022 23:15:02 +0200 Subject: [PATCH 158/870] when brake pin has inverted sense, honor that when running in DC mode as well --- MotorDriver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 9aadbe684..0c6cef175 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -179,6 +179,8 @@ void MotorDriver::setDCSignal(byte speedcode) { if (tSpeed <= 1) brake = 255; else if (tSpeed >= 127) brake = 0; else brake = 2 * (128-tSpeed); + if (invertBrake) + brake=255-brake; analogWrite(brakePin,brake); // as the port registers can be shadowed to get syncronized DCC signals // we need to take care of that and we have to turn off interrupts during From 44b21fd98739a74e0bdfe373396358f15d9d194e Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 5 Jul 2022 12:40:00 +0700 Subject: [PATCH 159/870] Added code to respond to --- DCCEXParser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 7191b470f..86408a96a 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -885,6 +885,12 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) #if defined(ARDUINO_ARCH_ESP32) ESP.restart(); #endif +#if defined(ARDUINO_ARCH_SAMD) + // Disable all interrupts and reset uC + __disable_irq(); + NVIC_SystemReset(); + while(true); +#endif #endif break; // and if we didnt restart } From 5cef1ac864d72e915b963f2163bf7feba582e00f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 5 Jul 2022 15:09:32 +0200 Subject: [PATCH 160/870] answer always with hearbeat number --- WiThrottle.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 5a835ceba..66463de7f 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -211,9 +211,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { } break; case 'N': // Heartbeat (2), only send if connection completed by 'HU' message - if (initSent) { - StringFormatter::send(stream, F("*%d\n"),HEARTBEAT_SECONDS); // return timeout value - } + StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : 1); // return timeout value break; case 'M': // multithrottle multithrottle(stream, cmd); From 14dc56936637b28e36534b4ccca7c352332699ce Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 6 Jul 2022 17:05:34 +0100 Subject: [PATCH 161/870] Moved CPU type detection to defines.h And fixed up BIG_RAM/HAS_ENOUGH_RAM issues. --- CommandDistributor.cpp | 4 ++-- CommandStation-EX.ino | 5 +++++ DCCTimer.h | 38 --------------------------------- defines.h | 48 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 49 insertions(+), 46 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 9c3dba433..1d558c748 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -31,14 +31,14 @@ #include "TrackManager.h" -#ifdef BIG_RAM +#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS) // use a buffer to allow broadcast #define BUFFER broadcastBufferWriter #define FLUSH broadcastBufferWriter->flush(); #define SHOVE(type) broadcastToClients(type); StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer(); #else - // on a UNO/NANO write direct to Serial and ignore flush/shove + // on a single USB connection config, write direct to Serial and ignore flush/shove #define BUFFER &Serial #define FLUSH #define SHOVE(type) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index cd23f0e71..a1ff73919 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -47,6 +47,11 @@ */ #include "DCCEX.h" + +#ifdef CPU_TYPE_ERROR +#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h +#endif + #ifdef WIFI_WARNING #warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED #endif diff --git a/DCCTimer.h b/DCCTimer.h index 4ad507c56..ebd22a817 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -86,42 +86,4 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { }; -//////////////////////////////////////////////////////////////////////////////// -// Create a cpu type we can share and -// gigure out if we have enough memory for advanced features -// so define HAS_ENOUGH_MEMORY until proved otherwise. -#define HAS_ENOUGH_MEMORY -#define HAS_AVR_WDT - -#if defined(ARDUINO_AVR_UNO) -#define ARDUINO_TYPE "UNO" -#undef HAS_ENOUGH_MEMORY -#elif defined(ARDUINO_AVR_NANO) -#define ARDUINO_TYPE "NANO" -#undef HAS_ENOUGH_MEMORY -#elif defined(ARDUINO_AVR_MEGA) -#define ARDUINO_TYPE "MEGA" -#elif defined(ARDUINO_AVR_MEGA2560) -#define ARDUINO_TYPE "MEGA" -#elif defined(ARDUINO_ARCH_MEGAAVR) -#define ARDUINO_TYPE "MEGAAVR" -#elif defined(ARDUINO_TEENSY32) -#define ARDUINO_TYPE "TEENSY32" -#elif defined(ARDUINO_TEENSY35) -#define ARDUINO_TYPE "TEENSY35" -#elif defined(ARDUINO_TEENSY36) -#define ARDUINO_TYPE "TEENSY36" -#elif defined(ARDUINO_TEENSY40) -#define ARDUINO_TYPE "TEENSY40" -#elif defined(ARDUINO_TEENSY41) -#define ARDUINO_TYPE "TEENSY41" -#elif defined(ARDUINO_ARCH_ESP8266) -#define ARDUINO_TYPE "ESP8266" -#undef HAS_AVR_WDT -#elif defined(ARDUINO_ARCH_ESP32) -#define ARDUINO_TYPE "ESP32" -#undef HAS_AVR_WDT -#else -#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h -#endif #endif diff --git a/defines.h b/defines.h index 9ce154d85..d34f963e9 100644 --- a/defines.h +++ b/defines.h @@ -35,16 +35,52 @@ #endif #endif +//////////////////////////////////////////////////////////////////////////////// +// Create a cpu type we can share and +// figure out if we have enough memory for advanced features +// so define HAS_ENOUGH_MEMORY until proved otherwise. +#define HAS_ENOUGH_MEMORY +#define HAS_AVR_WDT + +#if defined(ARDUINO_AVR_UNO) +#define ARDUINO_TYPE "UNO" +#undef HAS_ENOUGH_MEMORY +#elif defined(ARDUINO_AVR_NANO) +#define ARDUINO_TYPE "NANO" +#undef HAS_ENOUGH_MEMORY +#elif defined(ARDUINO_AVR_MEGA) +#define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_AVR_MEGA2560) +#define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_ARCH_MEGAAVR) +#define ARDUINO_TYPE "MEGAAVR" +#elif defined(ARDUINO_TEENSY32) +#define ARDUINO_TYPE "TEENSY32" +#elif defined(ARDUINO_TEENSY35) +#define ARDUINO_TYPE "TEENSY35" +#elif defined(ARDUINO_TEENSY36) +#define ARDUINO_TYPE "TEENSY36" +#elif defined(ARDUINO_TEENSY40) +#define ARDUINO_TYPE "TEENSY40" +#elif defined(ARDUINO_TEENSY41) +#define ARDUINO_TYPE "TEENSY41" +#elif defined(ARDUINO_ARCH_ESP8266) +#define ARDUINO_TYPE "ESP8266" +#undef HAS_AVR_WDT +#elif defined(ARDUINO_ARCH_ESP32) +#define ARDUINO_TYPE "ESP32" +#undef HAS_AVR_WDT +#else +#define CPU_TYPE_ERROR +#endif + //////////////////////////////////////////////////////////////////////////////// // // WIFI_ON: All prereqs for running with WIFI are met // Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - #define BIG_RAM -#endif #if ENABLE_WIFI - #if defined(BIG_RAM) + #if defined(HAS_ENOUGH_MEMORY) #define WIFI_ON true #ifndef WIFI_CHANNEL #define WIFI_CHANNEL 1 @@ -58,7 +94,7 @@ #endif #if ENABLE_ETHERNET - #if defined(BIG_RAM) + #if defined(HAS_ENOUGH_MEMORY) #define ETHERNET_ON true #else #define ETHERNET_WARNING @@ -80,7 +116,7 @@ #define WIFI_SERIAL_LINK_SPEED 115200 #if __has_include ( "myAutomation.h") - #if defined(BIG_RAM) || defined(DISABLE_EEPROM) + #if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) #define EXRAIL_ACTIVE #else #define EXRAIL_WARNING From ca3ed9562434421b80a9ce1d515aa9bc60a166aa Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 6 Jul 2022 20:35:58 +0200 Subject: [PATCH 162/870] fix broken 'a' command after broken commit c7b3817 --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 14b6bfe49..2cd6c4c0f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -316,7 +316,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) ((address & 0x01FF) != address) // invalid address (limit 9 bits ) || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) || ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 - || ((onoff & 0x01) != onoff) // invalid onoff 0|1 + || (onoff > 2) // invalid onoff 0|1|2 ) break; // Honour the configuration option (config.h) which allows the command to be reversed #ifdef DCC_ACCESSORY_COMMAND_REVERSE From 632e9335f35fdbc3973b2c95dcf36fec374c7adc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 6 Jul 2022 20:35:58 +0200 Subject: [PATCH 163/870] fix broken 'a' command after broken commit c7b3817 --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 14b6bfe49..2cd6c4c0f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -316,7 +316,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) ((address & 0x01FF) != address) // invalid address (limit 9 bits ) || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) || ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 - || ((onoff & 0x01) != onoff) // invalid onoff 0|1 + || (onoff > 2) // invalid onoff 0|1|2 ) break; // Honour the configuration option (config.h) which allows the command to be reversed #ifdef DCC_ACCESSORY_COMMAND_REVERSE From a7ea96b3924134081bd694b430c39c47c9de3d07 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 6 Jul 2022 21:12:10 +0200 Subject: [PATCH 164/870] improve parameter check for 'a' command --- DCCEXParser.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2cd6c4c0f..9862902df 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -308,16 +308,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) address=p[0]; subaddress=p[1]; activep=2; + if ((p[3] < 0) || (p[3] > 1)) // invalid onoff 0|1 + break; onoff=p[3]; } else break; // invalid no of parameters if ( - ((address & 0x01FF) != address) // invalid address (limit 9 bits ) - || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) - || ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 - || (onoff > 2) // invalid onoff 0|1|2 - ) break; + ((address & 0x01FF) != address) // invalid address (limit 9 bits) + || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits) + || (p[activep] > 1) || (p[activep] < 0) // invalid activate 0|1 + ) break; // Honour the configuration option (config.h) which allows the command to be reversed #ifdef DCC_ACCESSORY_COMMAND_REVERSE DCC::setAccessory(address, subaddress,p[activep]==0,onoff); From 76d6759d9863399a2fd7eb2d29a90a9d5c4f5c63 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 6 Jul 2022 21:12:10 +0200 Subject: [PATCH 165/870] improve parameter check for 'a' command --- DCCEXParser.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2cd6c4c0f..9862902df 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -308,16 +308,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) address=p[0]; subaddress=p[1]; activep=2; + if ((p[3] < 0) || (p[3] > 1)) // invalid onoff 0|1 + break; onoff=p[3]; } else break; // invalid no of parameters if ( - ((address & 0x01FF) != address) // invalid address (limit 9 bits ) - || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) - || ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 - || (onoff > 2) // invalid onoff 0|1|2 - ) break; + ((address & 0x01FF) != address) // invalid address (limit 9 bits) + || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits) + || (p[activep] > 1) || (p[activep] < 0) // invalid activate 0|1 + ) break; // Honour the configuration option (config.h) which allows the command to be reversed #ifdef DCC_ACCESSORY_COMMAND_REVERSE DCC::setAccessory(address, subaddress,p[activep]==0,onoff); From 108c5050adb0c8a9c53cd6477a28e6df527bc1d7 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 6 Jul 2022 23:46:00 +0100 Subject: [PATCH 166/870] Update defines.h Avoid Wifi on uniwifirev2 untile we have a proper wifinina interface. --- defines.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/defines.h b/defines.h index b613e9fac..5ec89cb7a 100644 --- a/defines.h +++ b/defines.h @@ -54,6 +54,7 @@ #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_ARCH_MEGAAVR) #define ARDUINO_TYPE "MEGAAVR" +#undef HAS_ENOUGH_MEMORY #elif defined(ARDUINO_TEENSY32) #define ARDUINO_TYPE "TEENSY32" #elif defined(ARDUINO_TEENSY35) @@ -73,6 +74,13 @@ #elif defined(ARDUINO_ARCH_SAMD) #define ARDUINO_TYPE "SAMD21" #undef HAS_AVR_WDT + +/* TODO when ready +#elif defined(ARDUINO_ARCH_RP2040) +#define ARDUINO_TYPE "RP2040" +#undef HAS_AVR_WDT +*/ + #else #define CPU_TYPE_ERROR #endif From ff46e283acfd23421712e8d45165e0758844ec46 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 7 Jul 2022 00:28:59 +0100 Subject: [PATCH 167/870] Reinstate platformio --- platformio.ini | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 7283f4214..bf70d7499 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,11 @@ [platformio] default_envs = - samd21 + mega2560 + uno + mega328 + unowifiR2 + nano src_dir = . include_dir = . @@ -128,6 +132,7 @@ platform = atmelavr board = nanoatmega328new board_upload.maximum_size = 32256 framework = arduino -lib_deps = ${env.lib_deps} +lib_deps = + ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo From aa3c3c2ee485b689c5cee5b1eb2fa0fe4b8c23bd Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 8 Jul 2022 10:34:27 +0100 Subject: [PATCH 168/870] SAMD fixups and workaround --- MotorDriver.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 758076b3b..d9d398265 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -64,14 +64,11 @@ #endif #if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) -struct FASTPIN { - volatile uint32_t *inout; - uint32_t maskHIGH; - uint32_t maskLOW; -}; +typedef uint32_t portreg_t; #else typedef uint8_t portreg_t; #endif + struct FASTPIN { volatile portreg_t *inout; portreg_t maskHIGH; @@ -122,7 +119,11 @@ class MotorDriver { virtual unsigned int raw2mA( int raw); virtual unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { +#ifdef digitalPinToTimer return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin))); +#else + return (brakePin<14 && brakePin >1); +#endif } inline int getRawCurrentTripValue() { return rawCurrentTripValue; From 090acdae44a0a7e99a08c15402a404996d95e447 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 8 Jul 2022 10:46:52 +0100 Subject: [PATCH 169/870] More SAMD cleanup --- DCCEXParser.cpp | 2 +- I2CManager.h | 8 ++++---- defines.h | 8 ++++++++ platformio.ini | 6 +++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 4846a5da6..d47dc574d 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -914,7 +914,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) // Disable all interrupts and reset uC __disable_irq(); NVIC_SystemReset(); - while(true); + while(true) {}; #endif #endif break; // and if we didnt restart diff --git a/I2CManager.h b/I2CManager.h index 67fd1ad79..49f3e8f2e 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -110,11 +110,11 @@ * */ -// Uncomment following line to enable Wire library instead of native I2C drivers -#define I2C_USE_WIRE +// Add following line to config.h to enable Wire library instead of native I2C drivers +//#define I2C_USE_WIRE -// Uncomment following line to disable the use of interrupts by the native I2C drivers. -#define I2C_NO_INTERRUPTS +// Add following line to config.h to disable the use of interrupts by the native I2C drivers. +//#define I2C_NO_INTERRUPTS // Default to use interrupts within the native I2C drivers. #ifndef I2C_NO_INTERRUPTS diff --git a/defines.h b/defines.h index a61566970..f61411e78 100644 --- a/defines.h +++ b/defines.h @@ -75,6 +75,14 @@ #elif defined(ARDUINO_ARCH_SAMD) #define ARDUINO_TYPE "SAMD21" #undef HAS_AVR_WDT +// SAMD support for I2C is awaiting development +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif + /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) diff --git a/platformio.ini b/platformio.ini index 740c8cc90..6b8796cea 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,7 @@ lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo -build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM +build_flags = -std=c++17 [env:samd21-zero-usb] platform = atmelsam @@ -43,7 +43,7 @@ lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo -build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM +build_flags = -std=c++17 [env:samc21-firebox] platform = atmelsam @@ -55,7 +55,7 @@ lib_deps = SparkFun External EEPROM Arduino Library monitor_speed = 115200 monitor_flags = --echo -build_flags = -std=c++17 -DI2C_USE_WIRE -DDISABLE_EEPROM +build_flags = -std=c++17 [env:mega2560-debug] platform = atmelavr From 9768083bfeadd98a856259e914d13ee6a440ed1f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 8 Jul 2022 15:01:40 +0100 Subject: [PATCH 170/870] DCCTimer::reset Moves CPU dependent reset code into correct place. --- DCCEXParser.cpp | 24 ++++-------------------- DCCTimer.h | 3 ++- DCCTimerAVR.cpp | 6 ++++++ DCCTimerESP.cpp | 4 ++++ DCCTimerMEGAAVR.cpp | 7 +++++++ DCCTimerSAMD.cpp | 6 ++++++ DCCTimerTEENSY.cpp | 5 +++++ defines.h | 5 ----- 8 files changed, 34 insertions(+), 26 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d47dc574d..d26428143 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -40,9 +40,7 @@ #include "TrackManager.h" #include "DCCTimer.h" #include "EXRAIL2.h" -#ifdef HAS_AVR_WDT -#include -#endif + // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. @@ -902,23 +900,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) return true; case HASH_KEYWORD_RESET: - { -#ifdef HAS_AVR_WDT - wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms - delay(50); // wait for the prescaller time to expire -#else -#if defined(ARDUINO_ARCH_ESP32) - ESP.restart(); -#endif -#if defined(ARDUINO_ARCH_SAMD) - // Disable all interrupts and reset uC - __disable_irq(); - NVIC_SystemReset(); - while(true) {}; -#endif -#endif - break; // and if we didnt restart - } + DCCTimer::reset(); + break; // and if we didnt restart + #ifndef DISABLE_EEPROM case HASH_KEYWORD_EEPROM: // diff --git a/DCCTimer.h b/DCCTimer.h index abb949139..caffcd8ee 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -78,7 +78,8 @@ static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { } static int getMinimumFreeMemory(); - + static void reset(); + private: static int freeMemory(); static volatile int minimum_free_memory; diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index b6a516186..f5a35620b 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -28,6 +28,7 @@ #ifdef ARDUINO_ARCH_AVR #include +#include #include "DCCTimer.h" INTERRUPT_CALLBACK interruptHandler=0; @@ -114,4 +115,9 @@ int DCCTimer::freeMemory() { return __brkval ? &top - __brkval : &top - __malloc_heap_start; } +void DCCTimer::reset() { + wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms + delay(50); // wait for the prescaller time to expire + +} #endif diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index d80b0fecc..0f83ef297 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -125,5 +125,9 @@ int DCCTimer::getMinimumFreeMemory() { int DCCTimer::freeMemory() { return ESP.getFreeHeap(); } + +void DCCTimmer:: reset() { + ESP.restart(); +} #endif diff --git a/DCCTimerMEGAAVR.cpp b/DCCTimerMEGAAVR.cpp index ce32ec509..be9248e2d 100644 --- a/DCCTimerMEGAAVR.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -118,4 +118,11 @@ int DCCTimer::freeMemory() { return __brkval ? &top - __brkval : &top - __malloc_heap_start; } +void DCCTimer::reset() { + CPU_CCP=0xD8; + WDT.CTRLA=0x4; + while(true){} +} + + #endif diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index b03962b07..e42d94c28 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -167,4 +167,10 @@ int DCCTimer::freeMemory() { return (int)(&top - reinterpret_cast(sbrk(0))); } +void DCCTimer::reset() { + __disable_irq(); + NVIC_SystemReset(); + while(true) {}; +} + #endif \ No newline at end of file diff --git a/DCCTimerTEENSY.cpp b/DCCTimerTEENSY.cpp index a75fe3a6b..dc682df52 100644 --- a/DCCTimerTEENSY.cpp +++ b/DCCTimerTEENSY.cpp @@ -123,4 +123,9 @@ static inline int freeMemory() { } #endif +void DCCTimer::reset() { + // found at https://forum.pjrc.com/threads/59935-Reboot-Teensy-programmatically + SCB_AIRCR = 0x05FA0004; +} + #endif diff --git a/defines.h b/defines.h index f61411e78..a51f41fd6 100644 --- a/defines.h +++ b/defines.h @@ -41,7 +41,6 @@ // figure out if we have enough memory for advanced features // so define HAS_ENOUGH_MEMORY until proved otherwise. #define HAS_ENOUGH_MEMORY -#define HAS_AVR_WDT #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" @@ -68,13 +67,10 @@ #define ARDUINO_TYPE "TEENSY41" #elif defined(ARDUINO_ARCH_ESP8266) #define ARDUINO_TYPE "ESP8266" -#undef HAS_AVR_WDT #elif defined(ARDUINO_ARCH_ESP32) #define ARDUINO_TYPE "ESP32" -#undef HAS_AVR_WDT #elif defined(ARDUINO_ARCH_SAMD) #define ARDUINO_TYPE "SAMD21" -#undef HAS_AVR_WDT // SAMD support for I2C is awaiting development #ifndef DISABLE_EEPROM #define DISABLE_EEPROM @@ -87,7 +83,6 @@ /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) #define ARDUINO_TYPE "RP2040" -#undef HAS_AVR_WDT */ #else From 3681f0e4456828ee948e9914ea8175d0f7744476 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 8 Jul 2022 15:52:46 +0100 Subject: [PATCH 171/870] Serial/SerialUSB cleanup --- DCC.cpp | 6 +----- DCCEXParser.cpp | 2 +- SerialManager.cpp | 13 +++++-------- StringFormatter.cpp | 24 +++++++----------------- StringFormatter.h | 7 ------- WifiInterface.cpp | 2 +- defines.h | 3 +++ 7 files changed, 18 insertions(+), 39 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 71a09c70f..f8ca7b14b 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -62,11 +62,7 @@ byte DCC::globalSpeedsteps=128; void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; -#if defined(ARDUINO_ARCH_SAMD) - StringFormatter::send(SerialUSB,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); -#else - StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); -#endif + StringFormatter::send(&USB_SERIAL,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); #ifndef DISABLE_EEPROM // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d26428143..d1630c247 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -183,7 +183,7 @@ void DCCEXParser::parse(const FSH * cmd) { int size=strlen_P((char *)cmd)+1; char buffer[size]; strcpy_P(buffer,(char *)cmd); - parse(&Serial,(byte *)buffer,NULL); + parse(&USB_SERIAL,(byte *)buffer,NULL); } // See documentation on DCC class for info on this section diff --git a/SerialManager.cpp b/SerialManager.cpp index 93dc1bb27..4b6bcf535 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -22,6 +22,7 @@ #include "SerialManager.h" #include "DCCEXParser.h" +#include "StringFormatter.h" SerialManager * SerialManager::first=NULL; @@ -34,14 +35,10 @@ SerialManager::SerialManager(Stream * myserial) { } void SerialManager::init() { -#if defined(ARDUINO_ARCH_SAMD) - SerialUSB.begin(115200); - new SerialManager(&SerialUSB); -#else - Serial.begin(115200); - while (!Serial && millis() < 5000); // wait max 5s for Serial to start - new SerialManager(&Serial); -#endif + USB_SERIAL.begin(115200); + while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start + new SerialManager(&USB_SERIAL); + #ifdef SERIAL3_COMMANDS Serial3.begin(115200); new SerialManager(&Serial3); diff --git a/StringFormatter.cpp b/StringFormatter.cpp index ff5a96a4e..ccb6ef3d6 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -18,15 +18,6 @@ */ #include "StringFormatter.h" #include - -#if defined(ARDUINO_ARCH_SAMD) - // Some processors use a gcc compiler that renames va_list!!! - #include - Print * StringFormatter::diagSerial= &SerialUSB; -#else - Print * StringFormatter::diagSerial=&Serial; -#endif - #include "LCDDisplay.h" bool Diag::ACK=false; @@ -38,22 +29,21 @@ bool Diag::LCN=false; void StringFormatter::diag( const FSH* input...) { - if (!diagSerial) return; - diagSerial->print(F("<* ")); + USB_SERIAL.print(F("<* ")); va_list args; va_start(args, input); - send2(diagSerial,input,args); - diagSerial->print(F(" *>\n")); + send2(&USB_SERIAL,input,args); + USB_SERIAL.print(F(" *>\n")); } void StringFormatter::lcd(byte row, const FSH* input...) { va_list args; // Issue the LCD as a diag first - send(diagSerial,F("<* LCD%d:"),row); + send(&USB_SERIAL,F("<* LCD%d:"),row); va_start(args, input); - send2(diagSerial,input,args); - send(diagSerial,F(" *>\n")); + send2(&USB_SERIAL,input,args); + send(&USB_SERIAL,F(" *>\n")); if (!LCDDisplay::lcdDisplay) return; LCDDisplay::lcdDisplay->setRow(row); @@ -164,7 +154,7 @@ void StringFormatter::printEscapes(Print * stream, const FSH * input) { } void StringFormatter::printEscape( char c) { - printEscape(diagSerial,c); + printEscape(&USB_SERIAL,c); } void StringFormatter::printEscape(Print * stream, char c) { diff --git a/StringFormatter.h b/StringFormatter.h index 1e225b531..47877158a 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -21,12 +21,6 @@ #include #include "FSH.h" #include "RingStream.h" - -#if defined(ARDUINO_ARCH_SAMD) - // Some processors use a gcc compiler that renames va_list!!! - #include -#endif - #include "LCDDisplay.h" class Diag { public: @@ -50,7 +44,6 @@ class StringFormatter static void printEscape(Print * serial, char c); // DIAG support - static Print * diagSerial; static void diag( const FSH* input...); static void lcd(byte row, const FSH* input...); static void printEscapes(char * input); diff --git a/WifiInterface.cpp b/WifiInterface.cpp index b7b059ed0..5f3b8e64b 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -381,7 +381,7 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, int ch = wifiStream->read(); if (echo) { if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE - else StringFormatter::diagSerial->print((char)ch); + else USB_SERIAL.print((char)ch); } if (ch != GETFLASH(locator)) locator = (char *)waitfor; if (ch == GETFLASH(locator)) { diff --git a/defines.h b/defines.h index a51f41fd6..d2aba22ea 100644 --- a/defines.h +++ b/defines.h @@ -41,6 +41,7 @@ // figure out if we have enough memory for advanced features // so define HAS_ENOUGH_MEMORY until proved otherwise. #define HAS_ENOUGH_MEMORY +#define USB_SERIAL Serial #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" @@ -71,6 +72,8 @@ #define ARDUINO_TYPE "ESP32" #elif defined(ARDUINO_ARCH_SAMD) #define ARDUINO_TYPE "SAMD21" +#undef USB_SERIAL +#define USB_SERIAL SerialUSB // SAMD support for I2C is awaiting development #ifndef DISABLE_EEPROM #define DISABLE_EEPROM From 68f0c6681d60945f9c774b39edbd8c8e391f21f8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 15 Jul 2022 15:45:25 +0200 Subject: [PATCH 172/870] Remember connection type determined at first connect --- CommandDistributor.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1d558c748..a0ebccdec 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -54,15 +54,30 @@ // Parse is called by Withrottle or Ethernet interface to determine which // protocol the client is using and call the appropriate part of dcc++Ex void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) { + if (Diag::WIFI && Diag::CMD) + DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer); ring=stream; ringClient=stream->peekTargetMark(); - if (buffer[0] == '<') { - clients[clientId]=COMMAND_TYPE; + + // First check if the client is not known + // yet and in that case determinine type + // NOTE: First character of transmission determines if this + // client is using the DCC++ protocol where all commands start + // with '<' + if (clients[clientId] == NONE_TYPE) { + if (buffer[0] == '<') + clients[clientId]=COMMAND_TYPE; + else + clients[clientId]=WITHROTTLE_TYPE; + } + + // When type is known, send the string + // to the right parser + if (clients[clientId] == COMMAND_TYPE) DCCEXParser::parse(stream, buffer, ring); - } else { - clients[clientId]=WITHROTTLE_TYPE; + else if (clients[clientId] == WITHROTTLE_TYPE) WiThrottle::getThrottle(clientId)->parse(ring, buffer); - } + ringClient=NO_CLIENT; } From ee639de5d67fcbbc7c36fbde9be7e4dc5f3a4326 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 15 Jul 2022 21:52:38 +0200 Subject: [PATCH 173/870] fix diag bug, update version strings --- CommandStation-EX.ino | 2 +- GITHUB_SHA.h | 2 +- version.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 4a7c5c6b9..547f56f37 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -71,7 +71,7 @@ void setup() SerialManager::init(); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); - DIAG(F("Platform: %s"), F(ARDUINO_TYPE)); // PMA - temporary + DIAG(F("Platform: %S"), F(ARDUINO_TYPE)); // PMA - temporary CONDITIONAL_LCD_START { // This block is still executed for DIAGS if LCD not in use diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 06deeac5c..e51d1fd21 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "TM-PORTX-20220613" +#define GITHUB_SHA "PORTX-HAL-20220715" diff --git a/version.h b/version.h index 825b2ce7c..90e5ce5dd 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.0 rc2" +#define VERSION "4.2.0 rc3" // 4.2.0 Track Manager additions: // Broadcast improvements to separate <> and Withrottle responses // Float eliminated saving >1.5kb PROGMEM and speed. From 0373f060fb3fb18510078602bc04150d14258379 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 26 Jul 2022 12:18:33 +0200 Subject: [PATCH 174/870] use pragma GCC push and pop correctly --- DCCWaveform.cpp | 7 ++++--- defines.h | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 02586273b..bc80809e8 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -92,7 +92,7 @@ void DCCWaveform::interruptHandler() { else DCCACK::checkAck(progTrack.sentResetsSincePacket); } -#pragma GCC push_options +#pragma GCC pop_options // An instance of this class handles the DCC transmissions for one track. (main or prog) // Interrupts are marshalled via the statics. @@ -115,7 +115,7 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { - +#pragma GCC push_options #pragma GCC optimize ("-O3") void DCCWaveform::interrupt2() { // calculate the next bit to be sent: @@ -172,6 +172,7 @@ void DCCWaveform::interrupt2() { } } } +#pragma GCC pop_options // Wait until there is no packet pending, then make this pending void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { @@ -190,4 +191,4 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea packetPending = true; sentResetsSincePacket=0; } -#endif \ No newline at end of file +#endif diff --git a/defines.h b/defines.h index d2aba22ea..23a7993a5 100644 --- a/defines.h +++ b/defines.h @@ -25,7 +25,6 @@ #ifndef DEFINES_H #define DEFINES_H - // defines.h relies on macros defined in config.h // but it may have already been included (for cosmetic convenence) by the .ino #ifndef MOTOR_SHIELD_TYPE From 6e8929c89eac051e7e29405193da7d70c67fa2b3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 26 Jul 2022 12:32:18 +0200 Subject: [PATCH 175/870] Force inline because we do not want to use stack space for this function even if compiler has other preferences --- DCCTimer.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index caffcd8ee..fa2b85448 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -71,11 +71,12 @@ class DCCTimer { // So even if all of the heap is freed, the reported minimum free // memory will not increase. // -static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) { - int spare = freeMemory()-extraBytes; - if (spare < 0) spare = 0; - if (spare < minimum_free_memory) minimum_free_memory = spare; -} + static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) + __attribute__((always_inline)) { + int spare = freeMemory()-extraBytes; + if (spare < 0) spare = 0; + if (spare < minimum_free_memory) minimum_free_memory = spare; + }; static int getMinimumFreeMemory(); static void reset(); From feebe67ecbaee84a7a7afd5a05d1686ef05b44d1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 10:40:45 +0200 Subject: [PATCH 176/870] fix compiler Werror --- EEStore.cpp | 4 ++-- I2CManager_Wire.h | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/EEStore.cpp b/EEStore.cpp index 3a13be705..24be0f8ef 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -43,7 +43,7 @@ void EEStore::init() { if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) { // if not, create blank eeStore structure (no // turnouts, no sensors) and save it back to EEPROM - strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)); + strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0); eeStore->data.nTurnouts = 0; eeStore->data.nSensors = 0; eeStore->data.nOutputs = 0; @@ -92,7 +92,7 @@ int EEStore::pointer() { return (eeAddress); } /////////////////////////////////////////////////////////////////////////////// void EEStore::dump(int num) { - byte b; + byte b = 0; DIAG(F("Addr 0x char")); for (int n = 0; n < num; n++) { EEPROM.get(n, b); diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index fb41f8601..19e682e12 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -98,22 +98,20 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea * returned in the I2CRB as for the asynchronous version. ***************************************************************************/ void I2CManagerClass::queueRequest(I2CRB *req) { - uint8_t status; switch (req->operation) { case OPERATION_READ: - status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); + req->status = read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); break; case OPERATION_SEND: - status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req); + req->status = write(req->i2cAddress, req->writeBuffer, req->writeLen, req); break; case OPERATION_SEND_P: - status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req); + req->status = write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req); break; case OPERATION_REQUEST: - status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req); + req->status = read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req); break; } - req->status = status; } /*************************************************************************** @@ -125,4 +123,4 @@ void I2CManagerClass::loop() {} void I2CManagerClass::checkForTimeout() {} -#endif \ No newline at end of file +#endif From a5d47e0c2ce1a59ca03b90d73d6651e370d52259 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 28 Nov 2021 17:07:21 +0100 Subject: [PATCH 177/870] make mDC a singleton static member of MotorDriverContainer --- DCC.cpp | 18 +++++++++++------- DCCWaveform.cpp | 7 +++++++ MotorDriver.cpp | 11 ++++++++++- MotorDriver.h | 3 +++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index fe0fbb44a..9f5d53e5a 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -28,7 +28,6 @@ #include "IODevice.h" #include "MotorDriver.h" -extern MotorDriverContainer mDC; // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -54,7 +53,8 @@ byte DCC::joinRelay=UNUSED_PIN; byte DCC::globalSpeedsteps=128; void DCC::begin() { - StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), mDC.getMotorShieldName(), F(GITHUB_SHA)); + StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), + MotorDriverContainer::mDC.getMotorShieldName(), F(GITHUB_SHA)); // Initialise HAL layer before reading EEprom. IODevice::begin(); @@ -63,13 +63,17 @@ void DCC::begin() { (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); - DCCWaveform::begin(mDC.mainTrack(),mDC.progTrack()); - DCCTrack::mainTrack.addDriver(mDC.mainTrack()); - DCCTrack::progTrack.addDriver(mDC.progTrack()); + DCCWaveform::begin(MotorDriverContainer::mDC.mainTrack(),MotorDriverContainer::mDC.progTrack()); + DCCTrack::mainTrack.addDriver(MotorDriverContainer::mDC.mainTrack()); + DCCTrack::progTrack.addDriver(MotorDriverContainer::mDC.progTrack()); MotorDriver *md; - mDC.add(2, md = new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); + MotorDriverContainer::mDC.add(2, md = new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); DCCTrack::mainTrack.addDriver(md); + /* + std::vector v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN); + for (const auto& d: v) DCCTrack::mainTrack.addDriver(d); + */ } void DCC::setJoinRelayPin(byte joinRelayPin) { @@ -578,7 +582,7 @@ byte DCC::loopStatus=0; void DCC::loop() { DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks - mDC.loop(); + MotorDriverContainer::mDC.loop(); ackManagerLoop(); // maintain prog track ack manager issueReminders(); } diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 85bf40bd3..92b98ebde 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -27,9 +27,16 @@ #include "DIAG.h" #include "freeMemory.h" +// The two Waveforms which defines what happens when the +// interrupt driven DCC signal is generated. This is tied +// to the timer interrupts of the hardware. DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); + +// The two different DCC _kinds_ of signals we want to be able +// to genrate at the same time. When timer interupts are used, +// these need the respective waveform DCCTrack DCCTrack::mainTrack(&DCCWaveform::mainTrack); DCCTrack DCCTrack::progTrack(&DCCWaveform::progTrack); diff --git a/MotorDriver.cpp b/MotorDriver.cpp index cb8e55de8..70643e5d0 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -254,4 +254,13 @@ void MotorDriverContainer::loop() { if(i > 7) i=0; } -MotorDriverContainer mDC(MOTOR_SHIELD_TYPE); +std::vector MotorDriverContainer::getDriverType(driverType t) { + std::vector v; + for(byte i=0; i<8; i++) { + if (mD[i] && mD[i]->type() == t) + v.push_back(mD[i]); + } + return v; +} + +MotorDriverContainer MotorDriverContainer::mDC(MOTOR_SHIELD_TYPE); diff --git a/MotorDriver.h b/MotorDriver.h index 77f645abe..c84bdcade 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -19,6 +19,7 @@ #ifndef MotorDriver_h #define MotorDriver_h +#include #include "defines.h" #include "FSH.h" @@ -127,6 +128,7 @@ class MotorDriverContainer { MotorDriver *m5=NULL, MotorDriver *m6=NULL, MotorDriver *m7=NULL); + static MotorDriverContainer mDC; inline void add(byte n, MotorDriver *m) { if (n>8) return; mD[n] = m; @@ -136,6 +138,7 @@ class MotorDriverContainer { inline MotorDriver *mainTrack() { return mD[0]; }; //start fixed inline MotorDriver *progTrack() { return mD[1]; }; void loop(); + std::vector getDriverType(driverType t); private: MotorDriver *mD[8]; From 342b9798f0e8af26ad8db59769a9864c8ee752bd Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 24 Nov 2021 09:00:23 +0100 Subject: [PATCH 178/870] define GETFLASHP for pointers --- FSH.h | 2 ++ SSD1306Ascii.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/FSH.h b/FSH.h index 7961b7c21..30f3d791f 100644 --- a/FSH.h +++ b/FSH.h @@ -41,6 +41,7 @@ typedef char FSH; #define GETFLASH(addr) (*(const unsigned char *)(addr)) #define GETFLASHW(addr) (*(const unsigned short *)(addr)) +#define GETFLASHP(addr) (*(void * const *)(addr)) #define FLASH #define strlen_P strlen #define strcpy_P strcpy @@ -48,6 +49,7 @@ typedef char FSH; typedef __FlashStringHelper FSH; #define GETFLASH(addr) pgm_read_byte_near(addr) #define GETFLASHW(addr) pgm_read_word_near(addr) +#define GETFLASHP(addr) pgm_read_ptr_near(addr) #define FLASH PROGMEM #endif #endif diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 17fd5a224..348d64f12 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -137,7 +137,7 @@ void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) { m_i2cAddr = i2cAddr; m_col = 0; m_row = 0; - const uint8_t* table = (const uint8_t*)GETFLASHW(&dev->initcmds); + const uint8_t* table = (const uint8_t*)GETFLASHP(&dev->initcmds); uint8_t size = GETFLASH(&dev->initSize); m_displayWidth = GETFLASH(&dev->lcdWidth); m_displayHeight = GETFLASH(&dev->lcdHeight); From 6c940615f6e5d8b95aec68883134e2b817587b83 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 28 Nov 2021 23:36:47 +0100 Subject: [PATCH 179/870] make mDC a vector in the Container and bugfixes --- DCC.cpp | 18 +++++++++-------- DCCTrack.cpp | 2 +- DCCTrack.h | 5 ++++- DCCWaveform.cpp | 32 +++++++++++++++++++------------ MotorDriver.cpp | 51 +++++++++++++++++++++++++++++-------------------- MotorDriver.h | 41 +++++++++++++++++++++++++++++++-------- 6 files changed, 98 insertions(+), 51 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 9f5d53e5a..6c640fbc0 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -56,24 +56,26 @@ void DCC::begin() { StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), MotorDriverContainer::mDC.getMotorShieldName(), F(GITHUB_SHA)); + /* +NOT YES, PIN CONFLICTS // Initialise HAL layer before reading EEprom. IODevice::begin(); + */ + //MotorDriverContainer::mDC.add(new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); + MotorDriverContainer::mDC.diag(); DCCWaveform::begin(MotorDriverContainer::mDC.mainTrack(),MotorDriverContainer::mDC.progTrack()); - DCCTrack::mainTrack.addDriver(MotorDriverContainer::mDC.mainTrack()); - DCCTrack::progTrack.addDriver(MotorDriverContainer::mDC.progTrack()); - MotorDriver *md; - MotorDriverContainer::mDC.add(2, md = new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); - DCCTrack::mainTrack.addDriver(md); - /* - std::vector v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN); + // Add main and prog drivers to the main and prog packet sources (dcc-tracks). + std::vector v; + v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN|TIMER_MAIN); for (const auto& d: v) DCCTrack::mainTrack.addDriver(d); - */ + v = MotorDriverContainer::mDC.getDriverType(RMT_PROG|TIMER_PROG); + for (const auto& d: v) DCCTrack::progTrack.addDriver(d); } void DCC::setJoinRelayPin(byte joinRelayPin) { diff --git a/DCCTrack.cpp b/DCCTrack.cpp index e0896d0e9..f2f05a653 100644 --- a/DCCTrack.cpp +++ b/DCCTrack.cpp @@ -29,7 +29,7 @@ void DCCTrack::schedulePacket(dccPacket packet) { //DIAG(F("DCCTrack::schedulePacket RMT l=%d d=%x"),packet.length, packet.data[0]); driver->schedulePacket(packet); } - if (driver->type() == TIMERINTERRUPT && waveform && once) { + if (driver->type() & (TIMER_MAIN | TIMER_PROG) && waveform && once) { //DIAG(F("DCCTrack::schedulePacket WAVE l=%d d=%x"),packet.length, packet.data[0]); waveform->schedulePacket(packet); once=false; diff --git a/DCCTrack.h b/DCCTrack.h index 86ce98d45..5a8d3de09 100644 --- a/DCCTrack.h +++ b/DCCTrack.h @@ -3,13 +3,16 @@ #include #include "DCCPacket.h" #include "DCCWaveform.h" +#include "DIAG.h" class DCCTrack { public: DCCTrack(DCCWaveform *w); void schedulePacket(const byte buffer[], byte byteCount, byte repeats); void schedulePacket(dccPacket packet); - inline void addDriver(MotorDriver *m) { mD.push_back(m); }; + inline void addDriver(MotorDriver *m) { mD.push_back(m); + DIAG(F("Track: mDType=%d count=%d"),m->type(), mD.size()); + }; static DCCTrack mainTrack; static DCCTrack progTrack; private: diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 92b98ebde..22bd42b78 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -49,19 +49,27 @@ uint8_t DCCWaveform::trailingEdgeCounter=0; void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { - mainTrack.motorDriver=mainDriver; - progTrack.motorDriver=progDriver; - progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static - mainTrack.setPowerMode(POWERMODE::OFF); - progTrack.setPowerMode(POWERMODE::OFF); - // Fault pin config for odd motor boards (example pololu) - MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) - && (mainDriver->getFaultPin() != UNUSED_PIN)); - // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work - MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); - DIAG(F("Signal pin config: %S accuracy waveform"), + if(mainDriver) { + mainTrack.motorDriver=mainDriver; + mainTrack.setPowerMode(POWERMODE::OFF); + } + if(progDriver) { + progTrack.motorDriver=progDriver; + progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static + progTrack.setPowerMode(POWERMODE::OFF); + } + if(mainDriver && progDriver) { + // Fault pin config for odd motor boards (example pololu) + MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) + && (mainDriver->getFaultPin() != UNUSED_PIN)); + // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work + MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); + } + if(mainDriver || progDriver) { + DIAG(F("Signal pin config: %S accuracy waveform"), MotorDriver::usePWM ? F("high") : F("normal") ); - DCCTimer::begin(DCCWaveform::interruptHandler); + } + DCCTimer::begin(DCCWaveform::interruptHandler); } #ifdef SLOW_ANALOG_READ diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 70643e5d0..ee878ecaf 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -44,7 +44,7 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 rmtChannel = new RMTChannel(signalPin, 0, PREAMBLE_BITS_MAIN); #endif dualSignal=false; - } else if (dtype == TIMERINTERRUPT) { + } else if (dtype & (TIMER_MAIN | TIMER_PROG)) { signalPin=signal_pin; getFastPin(F("SIG"),signalPin,fastSignalPin); pinMode(signalPin, OUTPUT); @@ -212,8 +212,9 @@ bool MotorDriver::schedulePacket(dccPacket packet) { if(!rmtChannel) return true; // fake success if functionality is not there outQueue.push(packet); - if (outQueue.size() > 10) { - DIAG(F("Warning: outQueue > 10")); + uint16_t size = outQueue.size(); + if (size > 10) { + DIAG(F("Warning: outQueue %d > 10"),size); } return true; } @@ -232,33 +233,41 @@ MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName, MotorDriver *m5, MotorDriver *m6, MotorDriver *m7) { - mD[0]=m0; - mD[1]=m1; - mD[2]=m2; - mD[3]=m3; - mD[4]=m4; - mD[5]=m5; - mD[6]=m6; - mD[7]=m7; + // THIS AUTOMATIC DOES NOT WORK YET. TIMER_MAIN AND TIMER_PROG required in CONSTRUCTOR + // AND CAN NOT BE ADDED LATER + if (m0) { + if (m0->type() == TYPE_UNKNOWN) + m0->setType(TIMER_MAIN); + mD.push_back(m0); + } + if (m1) { + if (m1->type() == TYPE_UNKNOWN) + m1->setType(TIMER_PROG); + mD.push_back(m1); + } + if (m2) mD.push_back(m2); + if (m3) mD.push_back(m3); + if (m4) mD.push_back(m4); + if (m5) mD.push_back(m5); + if (m6) mD.push_back(m6); + if (m7) mD.push_back(m7); shieldName = (FSH *)motorShieldName; } void MotorDriverContainer::loop() { - static byte i = 0; - // loops over MotorDrivers which have loop tasks - if (mD[i]) - if (mD[i]->type() == RMT_MAIN || mD[i]->type() == RMT_PROG) - mD[i]->loop(); - i++; - if(i > 7) i=0; + if (mD.empty()) + return; + for(const auto& d: mD) + if (d->type() & (RMT_MAIN | RMT_PROG)) + d->loop(); } std::vector MotorDriverContainer::getDriverType(driverType t) { std::vector v; - for(byte i=0; i<8; i++) { - if (mD[i] && mD[i]->type() == t) - v.push_back(mD[i]); + for(const auto& d: mD){ + if (d->type() & t) + v.push_back(d); } return v; } diff --git a/MotorDriver.h b/MotorDriver.h index c84bdcade..0121e99b9 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -22,6 +22,7 @@ #include #include "defines.h" #include "FSH.h" +#include "DIAG.h" #if defined(ARDUINO_ARCH_ESP32) #include @@ -57,13 +58,20 @@ struct FASTPIN { #define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) #define isLOW(fastpin) (!isHIGH(fastpin)) -enum driverType { TIMERINTERRUPT, RMT_MAIN, RMT_PROG, DC_ENA, DC_BRAKE }; +typedef byte driverType; +const driverType TYPE_UNKNOWN=0; +const driverType TIMER_MAIN=1; +const driverType TIMER_PROG=2; +const driverType RMT_MAIN=4; +const driverType RMT_PROG=16; +const driverType DC_ENA=32; +const driverType DC_BRAKE=64; class MotorDriver { public: MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin, - driverType t=TIMERINTERRUPT); + driverType t=TYPE_UNKNOWN); void setPower( bool on); void setSignal( bool high); void setBrake( bool on); @@ -83,6 +91,7 @@ class MotorDriver { #if defined(ARDUINO_ARCH_ESP32) void loop(); inline driverType type() { return dtype; }; + inline void setType(driverType t) { dtype = t; }; bool schedulePacket(dccPacket packet); #endif @@ -129,19 +138,35 @@ class MotorDriverContainer { MotorDriver *m6=NULL, MotorDriver *m7=NULL); static MotorDriverContainer mDC; - inline void add(byte n, MotorDriver *m) { - if (n>8) return; - mD[n] = m; + inline void add(MotorDriver *m) { + mD.push_back(m); + DIAG(F("Container: mDType=%d count=%d"),m->type(), mD.size()); }; // void SetCapability(byte n, byte cap, char [] name); inline FSH *getMotorShieldName() { return shieldName; }; - inline MotorDriver *mainTrack() { return mD[0]; }; //start fixed - inline MotorDriver *progTrack() { return mD[1]; }; + inline void diag() { + if (mD.empty()) { + DIAG(F("Container empty")); + return; + } + for(const auto& d: mD) + DIAG(F("Container: mDType=%d count=%d"),d->type(), mD.size()); + }; + inline MotorDriver *mainTrack() { + std::vector v = getDriverType(TIMER_MAIN); + if(v.empty()) return NULL; + return v.front(); + }; + inline MotorDriver *progTrack() { + std::vector v = getDriverType(TIMER_PROG); + if(v.empty()) return NULL; + return v.front(); + }; void loop(); std::vector getDriverType(driverType t); private: - MotorDriver *mD[8]; + std::vectormD; FSH *shieldName; }; #endif From 237846f190c4ef21480120db950e297dfd0a28e2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 29 Nov 2021 00:14:24 +0100 Subject: [PATCH 180/870] clean up diag, multiple gpio pin test worked --- DCC.cpp | 7 +++---- DCCRMT.cpp | 9 +++++++++ DCCTrack.h | 4 ++-- GITHUB_SHA.h | 2 +- MotorDriver.h | 3 +-- config.example.h | 9 ++++++--- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 6c640fbc0..74397ef74 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -56,13 +56,12 @@ void DCC::begin() { StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), MotorDriverContainer::mDC.getMotorShieldName(), F(GITHUB_SHA)); - /* -NOT YES, PIN CONFLICTS + // BE AWARE, USES I2C PINS, MAY LEAD TO PIN CONFLICTS // Initialise HAL layer before reading EEprom. IODevice::begin(); - */ - //MotorDriverContainer::mDC.add(new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); + //example how to use add: + //MotorDriverContainer::mDC.add(new MotorDriver(16, 21, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN)); // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 95d0e9a7d..6a81c251d 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -23,6 +23,7 @@ #include "DIAG.h" #include "DCCRMT.h" #include "DCCWaveform.h" // for MAX_PACKET_SIZE +#include "soc/gpio_sig_map.h" #define DATA_LEN(X) ((X)*9+1) // Each byte has one bit extra and we have one EOF marker @@ -104,6 +105,14 @@ RMTChannel::RMTChannel(byte pin, byte ch, byte plen) { // 2 mem block of 64 RMT items should be enough ESP_ERROR_CHECK(rmt_config(&config)); + /* + // test: config another gpio pin + gpio_num_t gpioNum = (gpio_num_t)(pin-1); + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO); + gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT); + gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0); + */ + // NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED)); diff --git a/DCCTrack.h b/DCCTrack.h index 5a8d3de09..6c22ed96f 100644 --- a/DCCTrack.h +++ b/DCCTrack.h @@ -10,8 +10,8 @@ class DCCTrack { DCCTrack(DCCWaveform *w); void schedulePacket(const byte buffer[], byte byteCount, byte repeats); void schedulePacket(dccPacket packet); - inline void addDriver(MotorDriver *m) { mD.push_back(m); - DIAG(F("Track: mDType=%d count=%d"),m->type(), mD.size()); + inline void addDriver(MotorDriver *m) { + mD.push_back(m); }; static DCCTrack mainTrack; static DCCTrack progTrack; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index fba9fe35f..3b6ebdf4f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "ESP32-motordriver-2021122-23:21" +#define GITHUB_SHA "ESP32-motordriver-2021129-00:12" diff --git a/MotorDriver.h b/MotorDriver.h index 0121e99b9..da9e0d928 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -140,7 +140,6 @@ class MotorDriverContainer { static MotorDriverContainer mDC; inline void add(MotorDriver *m) { mD.push_back(m); - DIAG(F("Container: mDType=%d count=%d"),m->type(), mD.size()); }; // void SetCapability(byte n, byte cap, char [] name); inline FSH *getMotorShieldName() { return shieldName; }; @@ -150,7 +149,7 @@ class MotorDriverContainer { return; } for(const auto& d: mD) - DIAG(F("Container: mDType=%d count=%d"),d->type(), mD.size()); + DIAG(F("Container: mDType=%d"),d->type()); }; inline MotorDriver *mainTrack() { std::vector v = getDriverType(TIMER_MAIN); diff --git a/config.example.h b/config.example.h index 845258964..5904ad27d 100644 --- a/config.example.h +++ b/config.example.h @@ -45,9 +45,12 @@ The configuration file for DCC-EX Command Station // https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/ // 4 high at boot -#define ESP8266_MOTOR_SHIELD F("ESP8266"), \ - new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\ - new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, A0 , 2.99, 2000, UNUSED_PIN) +// BUG: WE STILL NEED AT LEAST ONE TIMER_* motor shield defined, otherwise the packet scheduling does not work +// BUG: WE DO NOT HAVE ANY RMT_PROG CAPABILITY yet. +#define ESP32_MOTOR_SHIELD F("ESP32"), \ + NULL /*new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 36, 2.00, 2000, UNUSED_PIN, TIMER_MAIN)*/, \ + new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 39, 2.00, 2000, UNUSED_PIN, TIMER_PROG), \ + new MotorDriver(16, 23, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN) // ESP32 ADC1 only supported GPIO pins 32 to 39, for example // ADC1 CH4 = GPIO32, ADC1 CH5 = GPIO33, ADC1 CH0 = GPIO36 From 83300387d25de3a5624c03220e50c18822098eb8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 15:57:35 +0200 Subject: [PATCH 181/870] working pin assignment in config.example.h --- config.example.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config.example.h b/config.example.h index 5904ad27d..0cf036ae8 100644 --- a/config.example.h +++ b/config.example.h @@ -62,8 +62,9 @@ The configuration file for DCC-EX Command Station // Adjust conversion factor according to your voltage divider. // #define ESP32_MOTOR_SHIELD F("ESP32"), \ - new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN),\ - new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.00, 2000, UNUSED_PIN) + NULL /* new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN) */ , \ + new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 39, 2.00, 2000, UNUSED_PIN, TIMER_PROG), \ + new MotorDriver(16, 23, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN) #define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD From 37f44709f98f20fd0949a2d46ead0e063fb176d7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 4 Dec 2021 20:07:34 +0100 Subject: [PATCH 182/870] RMT prog track channel start --- DCCPacket.h | 4 ++++ DCCRMT.cpp | 19 ++++++++++++------- DCCRMT.h | 8 +++++++- MotorDriver.cpp | 2 +- MotorDriver.h | 4 ---- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/DCCPacket.h b/DCCPacket.h index 261cff33a..9ac480a4f 100644 --- a/DCCPacket.h +++ b/DCCPacket.h @@ -2,6 +2,10 @@ const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. +// Number of preamble bits (moved here so MotorDriver and Waveform know) +const int PREAMBLE_BITS_MAIN = 16; +const int PREAMBLE_BITS_PROG = 22; + class dccPacket { public: byte data[MAX_PACKET_SIZE+1]; // space for checksum if needed diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 6a81c251d..e26dd1867 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -62,7 +62,7 @@ void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { tt->RMTinterrupt(); } -RMTChannel::RMTChannel(byte pin, byte ch, byte plen) { +RMTChannel::RMTChannel(byte pin, byte ch, byte plen, bool isMain) { // preamble preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker @@ -79,12 +79,17 @@ RMTChannel::RMTChannel(byte pin, byte ch, byte plen) { // idle idleLen = 28; idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t)); - for (byte n=0; n<8; n++) // 0 to 7 - setDCCBit1(idle + n); - for (byte n=8; n<18; n++) // 8, 9 to 16, 17 - setDCCBit0(idle + n); - for (byte n=18; n<26; n++) // 18 to 25 - setDCCBit1(idle + n); + if (isMain) { + for (byte n=0; n<8; n++) // 0 to 7 + setDCCBit1(idle + n); + for (byte n=8; n<18; n++) // 8, 9 to 16, 17 + setDCCBit0(idle + n); + for (byte n=18; n<26; n++) // 18 to 25 + setDCCBit1(idle + n); + } else { + for (byte n=0; n<26; n++) // all zero + setDCCBit0(idle + n); + } setDCCBit1(idle + 26); // end bit setEOT(idle + 27); // EOT marker diff --git a/DCCRMT.h b/DCCRMT.h index 8d8a93c07..94234e7dc 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -32,7 +32,13 @@ class RMTChannel { public: - RMTChannel(byte pin, byte ch, byte plen); + inline RMTChannel(byte pin, bool isMain) { + if (isMain) + RMTChannel(pin, 0, PREAMBLE_BITS_MAIN, 1); + else + RMTChannel(pin, 2, PREAMBLE_BITS_PROG, 0); + }; + RMTChannel(byte pin, byte ch, byte plen, bool isProg); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); bool RMTfillData(dccPacket packet); diff --git a/MotorDriver.cpp b/MotorDriver.cpp index ee878ecaf..5f3d68a70 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -41,7 +41,7 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 if (dtype == RMT_MAIN) { signalPin=signal_pin; #if defined(ARDUINO_ARCH_ESP32) - rmtChannel = new RMTChannel(signalPin, 0, PREAMBLE_BITS_MAIN); + rmtChannel = new RMTChannel(signalPin, true); // true: isMain #endif dualSignal=false; } else if (dtype & (TIMER_MAIN | TIMER_PROG)) { diff --git a/MotorDriver.h b/MotorDriver.h index da9e0d928..65f97f2ae 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -29,10 +29,6 @@ #include "DCCRMT.h" #endif -// Number of preamble bits (moved here so MotorDriver and Waveform know) -const int PREAMBLE_BITS_MAIN = 16; -const int PREAMBLE_BITS_PROG = 22; - #ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h #define UNUSED_PIN 127 // inside int8_t #endif From 67e8c04314a51cd04ed1499d92c1c07cb0cbd88e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 21:11:51 +0200 Subject: [PATCH 183/870] in principle schedules packets --- DCC.cpp | 8 +++++++- DCCRMT.cpp | 25 ++++++++++++++++++------- DCCRMT.h | 10 ++-------- MotorDriver.cpp | 24 +++++++++++++++++++----- MotorDriver.h | 6 +++++- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 74397ef74..bc9160f11 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -71,7 +71,13 @@ void DCC::begin() { // Add main and prog drivers to the main and prog packet sources (dcc-tracks). std::vector v; - v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN|TIMER_MAIN); + + v = MotorDriverContainer::mDC.getDriverType(RMT_MAIN); + + v.front()->setChannel(new RMTChannel(v.front()->getSignalPin(), true)); + + + for (const auto& d: v) DCCTrack::mainTrack.addDriver(d); v = MotorDriverContainer::mDC.getDriverType(RMT_PROG|TIMER_PROG); for (const auto& d: v) DCCTrack::progTrack.addDriver(d); diff --git a/DCCRMT.cpp b/DCCRMT.cpp index e26dd1867..1ac1aa439 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -62,8 +62,17 @@ void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { tt->RMTinterrupt(); } -RMTChannel::RMTChannel(byte pin, byte ch, byte plen, bool isMain) { - +RMTChannel::RMTChannel(byte pin, bool isMain) { + byte ch; + byte plen; + if (isMain) { + ch = 0; + plen = PREAMBLE_BITS_MAIN; + } else { + ch = 2; + plen = PREAMBLE_BITS_PROG; + } + // preamble preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t)); @@ -146,17 +155,19 @@ void RMTChannel::RMTprefill() { const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; //bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) { -bool RMTChannel::RMTfillData(dccPacket packet) { +int RMTChannel::RMTfillData(dccPacket packet) { // dataReady: Signals to then interrupt routine. It is set when // we have data in the channel buffer which can be copied out // to the HW. dataRepeat on the other hand signals back to // the caller of this function if the data has been sent enough // times (0 to 3 means 1 to 4 times in total). - if (dataReady == true || dataRepeat > 0) // we have still old work to do - return false; + if (dataReady == true) + return 1000; + if (dataRepeat > 0) // we have still old work to do + return dataRepeat; if (DATA_LEN(packet.length) > maxDataLen) { // this would overun our allocated memory for data DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), packet.length, maxDataLen); - return false; // something very broken, can not convert packet + return -1; // something very broken, can not convert packet } byte *buffer = packet.data; @@ -177,7 +188,7 @@ bool RMTChannel::RMTfillData(dccPacket packet) { dataLen = bitcounter; dataReady = true; dataRepeat = packet.repeat+1; // repeatCount of 0 means send once - return true; + return 0; } void IRAM_ATTR RMTChannel::RMTinterrupt() { diff --git a/DCCRMT.h b/DCCRMT.h index 94234e7dc..6e752191e 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -32,16 +32,10 @@ class RMTChannel { public: - inline RMTChannel(byte pin, bool isMain) { - if (isMain) - RMTChannel(pin, 0, PREAMBLE_BITS_MAIN, 1); - else - RMTChannel(pin, 2, PREAMBLE_BITS_PROG, 0); - }; - RMTChannel(byte pin, byte ch, byte plen, bool isProg); + RMTChannel(byte pin, bool isMain); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); - bool RMTfillData(dccPacket packet); + int RMTfillData(dccPacket packet); //bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); static RMTChannel mainRMTChannel; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 5f3d68a70..5b6d6c3dc 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -38,11 +38,14 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8 getFastPin(F("POWER"),powerPin,fastPowerPin); pinMode(powerPin, OUTPUT); - if (dtype == RMT_MAIN) { + if (dtype & (RMT_MAIN | RMT_PROG)) { signalPin=signal_pin; + /* #if defined(ARDUINO_ARCH_ESP32) - rmtChannel = new RMTChannel(signalPin, true); // true: isMain + //rmtChannel = new RMTChannel(signalPin, 0, PREAMBLE_BITS_MAIN, true); // true: isMain + rmtChannel = new RMTChannel(signalPin, dtype == RMT_MAIN); // true: isMain #endif + */ dualSignal=false; } else if (dtype & (TIMER_MAIN | TIMER_PROG)) { signalPin=signal_pin; @@ -209,7 +212,10 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res } bool MotorDriver::schedulePacket(dccPacket packet) { - if(!rmtChannel) return true; // fake success if functionality is not there + if(!rmtChannel) { + DIAG(F("no rmt Channel")); + return true; // fake success if functionality is not there + } outQueue.push(packet); uint16_t size = outQueue.size(); @@ -220,8 +226,16 @@ bool MotorDriver::schedulePacket(dccPacket packet) { } void MotorDriver::loop() { - if (rmtChannel && !outQueue.empty() && rmtChannel->RMTfillData(outQueue.front())) - outQueue.pop(); + int r; + if (rmtChannel && !outQueue.empty()) { + r = rmtChannel->RMTfillData(outQueue.front()); + if (r == 0) { + DIAG(F("r=OK")); + outQueue.pop(); + } + else + DIAG(F("r=%d"), r); + } } MotorDriverContainer::MotorDriverContainer(const FSH * motorShieldName, diff --git a/MotorDriver.h b/MotorDriver.h index 65f97f2ae..95d00fe90 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -83,12 +83,16 @@ class MotorDriver { static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs inline byte getFaultPin() { return faultPin; - } + }; + inline byte getSignalPin() { + return signalPin; + }; #if defined(ARDUINO_ARCH_ESP32) void loop(); inline driverType type() { return dtype; }; inline void setType(driverType t) { dtype = t; }; bool schedulePacket(dccPacket packet); + inline void setChannel(RMTChannel * r) { rmtChannel = r; }; #endif private: From 7ce74cfdf81ee3e9b1230cd120f2e8204cf51360 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 21:33:25 +0200 Subject: [PATCH 184/870] pin assignment for uno form factor board --- config.example.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.example.h b/config.example.h index 0cf036ae8..c1c0c2d4a 100644 --- a/config.example.h +++ b/config.example.h @@ -63,8 +63,8 @@ The configuration file for DCC-EX Command Station // #define ESP32_MOTOR_SHIELD F("ESP32"), \ NULL /* new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN) */ , \ - new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 39, 2.00, 2000, UNUSED_PIN, TIMER_PROG), \ - new MotorDriver(16, 23, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.00, 2000, UNUSED_PIN, RMT_MAIN) + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, UNUSED_PIN, 39, 2.00, 2000, UNUSED_PIN, TIMER_PROG), \ + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, UNUSED_PIN, 36, 2.00, 2000, UNUSED_PIN, RMT_MAIN) #define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD From ec12baa0ca50d6244962af9f79b4ea9bddf1ed54 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 21:50:21 +0200 Subject: [PATCH 185/870] motor driver def for standard shield on uno form factor --- MotorDrivers.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MotorDrivers.h b/MotorDrivers.h index 3cbefefc2..f51807f04 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -54,6 +54,10 @@ #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) +#elif defined(ARDUINO_ARCH_ESP32) +#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, UNUSED_PIN, 36/*A4*/, 2.00, 2000, UNUSED_PIN), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, UNUSED_PIN, 39/*A5*/, 2.00, 2000, UNUSED_PIN) #else #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ From bfa81b801e1516388a8c718cc1ae8322e4e74c5f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 21:54:28 +0200 Subject: [PATCH 186/870] fix compiler Werror --- EEStore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EEStore.cpp b/EEStore.cpp index 89577aadb..4978f3546 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -49,7 +49,7 @@ void EEStore::init() { if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) { // if not, create blank eeStore structure (no // turnouts, no sensors) and save it back to EEPROM - strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)); + strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0); eeStore->data.nTurnouts = 0; eeStore->data.nSensors = 0; eeStore->data.nOutputs = 0; @@ -98,7 +98,7 @@ int EEStore::pointer() { return (eeAddress); } /////////////////////////////////////////////////////////////////////////////// void EEStore::dump(int num) { - byte b; + byte b = 0; DIAG(F("Addr 0x char")); for (int n = 0; n < num; n++) { EEPROM.get(n, b); From 43164136185a55edd2ff5f06997913d59d6b988c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 23:02:26 +0200 Subject: [PATCH 187/870] compiles (but no waveform) on ESP32 --- DCCTimerESP.cpp | 4 +++- DCCWaveform.cpp | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index 0f83ef297..9780a4d50 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -101,6 +101,8 @@ bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) { } void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) { } +void IRAM_ATTR DCCTimer::clearPWM() { +} // Fake this as it should not be used void DCCTimer::getSimulatedMacAddress(byte mac[6]) { @@ -126,7 +128,7 @@ int DCCTimer::freeMemory() { return ESP.getFreeHeap(); } -void DCCTimmer:: reset() { +void DCCTimer::reset() { ESP.restart(); } #endif diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index bc80809e8..3708da121 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -192,3 +192,20 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea sentResetsSincePacket=0; } #endif + +#ifdef ARDUINO_ARCH_ESP32 +#include "DCCWaveform.h" + +DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); +DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); + +DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) { +} + +void DCCWaveform::begin() { +} +void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { +} +void DCCWaveform::loop() { +} +#endif From fb513b64f93b2a7b7fec144ab1592204501a3707 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 30 Jul 2022 23:08:42 +0200 Subject: [PATCH 188/870] (c) and version tag --- DCCWaveform.cpp | 2 +- EEStore.cpp | 2 +- GITHUB_SHA.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 3708da121..300150e3e 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -2,7 +2,7 @@ * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Fred Decker - * © 2020-2021 Harald Barth + * © 2020-2022 Harald Barth * © 2020-2021 Chris Harlow * All rights reserved. * diff --git a/EEStore.cpp b/EEStore.cpp index 4978f3546..57def0f88 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -1,7 +1,7 @@ /* * © 2021 Neil McKechnie * © 2021 Fred Decker - * © 2020-2021 Harald Barth + * © 2020-2022 Harald Barth * © 2020-2021 Chris Harlow * © 2013-2016 Gregg E. Berman * All rights reserved. diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e51d1fd21..064160a76 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220715" +#define GITHUB_SHA "PORTX-HAL-20220730" From 06647ae7e45efbdbfcdc773f1a470bc22d5a3103 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 00:18:35 +0200 Subject: [PATCH 189/870] make RMT channel compile without dccpacket class --- DCCRMT.cpp | 14 ++++++-------- DCCRMT.h | 5 ++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 1ac1aa439..d9a78442a 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -154,8 +154,8 @@ void RMTChannel::RMTprefill() { const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; -//bool RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) { -int RMTChannel::RMTfillData(dccPacket packet) { +int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) { + //int RMTChannel::RMTfillData(dccPacket packet) { // dataReady: Signals to then interrupt routine. It is set when // we have data in the channel buffer which can be copied out // to the HW. dataRepeat on the other hand signals back to @@ -165,16 +165,14 @@ int RMTChannel::RMTfillData(dccPacket packet) { return 1000; if (dataRepeat > 0) // we have still old work to do return dataRepeat; - if (DATA_LEN(packet.length) > maxDataLen) { // this would overun our allocated memory for data - DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), packet.length, maxDataLen); + if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data + DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen); return -1; // something very broken, can not convert packet } - byte *buffer = packet.data; - // convert bytes to RMT stream of "bits" byte bitcounter = 0; - for(byte n=0; n #if defined(ARDUINO_ARCH_ESP32) -#include "DCCPacket.h" #include "driver/rmt.h" #include "soc/rmt_reg.h" #include "soc/rmt_struct.h" @@ -35,8 +34,8 @@ class RMTChannel { RMTChannel(byte pin, bool isMain); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); - int RMTfillData(dccPacket packet); - //bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); + //int RMTfillData(dccPacket packet); + int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); static RMTChannel mainRMTChannel; static RMTChannel progRMTChannel; From d29219f858cc6b9e544c05d6967abedeca4ef9f0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 00:23:19 +0200 Subject: [PATCH 190/870] ifdef whole file --- WifiESP32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 48d40d67d..9d2af6d7c 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -17,9 +17,9 @@ along with CommandStation. If not, see . */ +#if defined(ARDUINO_ARCH_ESP32) #include #include "defines.h" -#if defined(ARDUINO_ARCH_ESP32) #include #include "WifiESP32.h" #include "DIAG.h" From 26fc11d1a6c8a464fb29e517d0f148c5798b78e6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 00:50:59 +0200 Subject: [PATCH 191/870] add RinStream::peek() --- RingStream.cpp | 13 +++++++------ RingStream.h | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index a03bfbfeb..4a75c1a31 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -95,21 +95,22 @@ _count=prevCount+plength; return plength; } -int RingStream::read() { +int RingStream::read(byte advance) { if (_flashInsert) { // we are reading out of a flash string byte fb=GETFLASH(_flashInsert); - _flashInsert++; + _flashInsert+=advance; if (fb) return fb; // we have a byte from the flash // flash insert complete, clear and drop through to next buffer byte _flashInsert=NULL; } if ((_pos_read==_pos_write) && !_overflow) return -1; // empty - byte b=readRawByte(); + byte b=readRawByte(advance); if (b!=FLASH_INSERT_MARKER) return b; // Detected a flash insert // read address bytes LSB first (size depends on CPU) + if (advance == 0) readRawByte(); // read past it anyway uintptr_t iFlash=0; for (byte f=0; f( iFlash); // and try again... so will read the first byte of the insert. - return read(); + return read(advance); } -byte RingStream::readRawByte() { +byte RingStream::readRawByte(byte advance) { byte b=_buffer[_pos_read]; - _pos_read++; + _pos_read+=advance; if (_pos_read==_len) _pos_read=0; _overflow=false; return b; diff --git a/RingStream.h b/RingStream.h index 61c1737bd..2178dea7f 100644 --- a/RingStream.h +++ b/RingStream.h @@ -39,15 +39,17 @@ class RingStream : public Print { virtual int availableForWrite() override; using Print::write; size_t printFlash(const FSH * flashBuffer); - int read(); + inline int read() { return read(1); }; + inline int peek() { return read(0); }; int count(); int freeSpace(); void mark(uint8_t b); bool commit(); uint8_t peekTargetMark(); void flush(); - byte readRawByte(); private: + int read(byte advance); + byte readRawByte(byte advance=1); int _len; int _pos_write; int _pos_read; From 3aef54c0fe06bf2c688f2900806199dede1ed575 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 00:53:26 +0200 Subject: [PATCH 192/870] add RinStream::info() --- RingStream.cpp | 4 ++++ RingStream.h | 1 + 2 files changed, 5 insertions(+) diff --git a/RingStream.cpp b/RingStream.cpp index 4a75c1a31..51a77d222 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -157,6 +157,10 @@ uint8_t RingStream::peekTargetMark() { return _buffer[_mark]; } +void RingStream::info() { + DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark); +} + bool RingStream::commit() { _flashInsert=NULL; // prepared for first read if (_overflow) { diff --git a/RingStream.h b/RingStream.h index 2178dea7f..45b1c4e8f 100644 --- a/RingStream.h +++ b/RingStream.h @@ -47,6 +47,7 @@ class RingStream : public Print { bool commit(); uint8_t peekTargetMark(); void flush(); + void info(); private: int read(byte advance); byte readRawByte(byte advance=1); From bdd87e73996e7809ba74a669d5d1ea9c658a3f98 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 08:47:58 +0200 Subject: [PATCH 193/870] wifi 2nd try kludge --- WifiESP32.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 9d2af6d7c..4599c6420 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -21,6 +21,7 @@ #include #include "defines.h" #include +#include "esp_wifi.h" #include "WifiESP32.h" #include "DIAG.h" #include "RingStream.h" @@ -110,6 +111,21 @@ bool WifiESP::setup(const char *SSid, wifiUp = true; } else { DIAG(F("Could not connect to Wifi SSID %s"),SSid); + DIAG(F("Forcing one more Wifi restart")); + esp_wifi_start(); + esp_wifi_connect(); + tries=40; + while (WiFi.status() != WL_CONNECTED && tries) { + Serial.print('.'); + tries--; + delay(500); + } + if (WiFi.status() == WL_CONNECTED) { + DIAG(F("Wifi STA IP 2nd try %s"),WiFi.localIP().toString().c_str()); + wifiUp = true; + } else { + DIAG(F("Fail 2nd try")); + } } } if (!haveSSID) { From 3f0b3ccaf7818de1a71ce7131b48d733007f3853 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 08:49:12 +0200 Subject: [PATCH 194/870] ESP32 wifi added to startup and loop --- CommandStation-EX.ino | 12 +++++++++++- DCCEX.h | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 547f56f37..c3cb4784d 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -82,9 +82,14 @@ void setup() // Responsibility 2: Start all the communications before the DCC engine // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi // Start Ethernet if it exists +#ifndef ARDUINO_ARCH_ESP32 #if WIFI_ON WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL); #endif // WIFI_ON +#else + // ESP32 needs wifi on always + WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL); +#endif // ARDUINO_ARCH_ESP32 #if ETHERNET_ON EthernetInterface::setup(); @@ -132,9 +137,14 @@ void loop() SerialManager::loop(); // Responsibility 3: Optionally handle any incoming WiFi traffic +#ifndef ARDUINO_ARCH_ESP32 #if WIFI_ON WifiInterface::loop(); -#endif +#endif //WIFI_ON +#else + // ESP32 needs wifi on always + WifiESP::loop(); +#endif //ARDUINO_ARCH_ESP32 #if ETHERNET_ON EthernetInterface::loop(); #endif diff --git a/DCCEX.h b/DCCEX.h index c3f55407b..f2eee515d 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -32,7 +32,11 @@ #include "DCCEXParser.h" #include "SerialManager.h" #include "version.h" +#ifndef ARDUINO_ARCH_ESP32 #include "WifiInterface.h" +#else +#include "WifiESP32.h" +#endif #if ETHERNET_ON == true #include "EthernetInterface.h" #endif From 2ad0d7ab76808b74cf515ff3a14c1e7155d1f639 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 09:20:34 +0200 Subject: [PATCH 195/870] ESP32 wifi should not sleep, otherwise auth errors --- WifiESP32.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 4599c6420..a48a590c4 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -99,6 +99,7 @@ bool WifiESP::setup(const char *SSid, if (haveSSID && havePassword) { WiFi.mode(WIFI_STA); + WiFi.setSleep(false); WiFi.setAutoReconnect(true); WiFi.begin(SSid, password); while (WiFi.status() != WL_CONNECTED && tries) { From 8916d1415f09ca273204a70d47d20ec6383ad5f5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 09:23:02 +0200 Subject: [PATCH 196/870] Revert "add RinStream::peek()" This reverts commit 26fc11d1a6c8a464fb29e517d0f148c5798b78e6. --- RingStream.cpp | 13 ++++++------- RingStream.h | 6 ++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/RingStream.cpp b/RingStream.cpp index 51a77d222..319727542 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -95,22 +95,21 @@ _count=prevCount+plength; return plength; } -int RingStream::read(byte advance) { +int RingStream::read() { if (_flashInsert) { // we are reading out of a flash string byte fb=GETFLASH(_flashInsert); - _flashInsert+=advance; + _flashInsert++; if (fb) return fb; // we have a byte from the flash // flash insert complete, clear and drop through to next buffer byte _flashInsert=NULL; } if ((_pos_read==_pos_write) && !_overflow) return -1; // empty - byte b=readRawByte(advance); + byte b=readRawByte(); if (b!=FLASH_INSERT_MARKER) return b; // Detected a flash insert // read address bytes LSB first (size depends on CPU) - if (advance == 0) readRawByte(); // read past it anyway uintptr_t iFlash=0; for (byte f=0; f( iFlash); // and try again... so will read the first byte of the insert. - return read(advance); + return read(); } -byte RingStream::readRawByte(byte advance) { +byte RingStream::readRawByte() { byte b=_buffer[_pos_read]; - _pos_read+=advance; + _pos_read++; if (_pos_read==_len) _pos_read=0; _overflow=false; return b; diff --git a/RingStream.h b/RingStream.h index 45b1c4e8f..a3bb7d021 100644 --- a/RingStream.h +++ b/RingStream.h @@ -39,8 +39,7 @@ class RingStream : public Print { virtual int availableForWrite() override; using Print::write; size_t printFlash(const FSH * flashBuffer); - inline int read() { return read(1); }; - inline int peek() { return read(0); }; + int read(); int count(); int freeSpace(); void mark(uint8_t b); @@ -48,9 +47,8 @@ class RingStream : public Print { uint8_t peekTargetMark(); void flush(); void info(); + byte readRawByte(); private: - int read(byte advance); - byte readRawByte(byte advance=1); int _len; int _pos_write; int _pos_read; From ddc0c5ac3c20c1344cecec624ea650055c920db1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 09:41:46 +0200 Subject: [PATCH 197/870] new ringstream peek inline --- RingStream.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RingStream.h b/RingStream.h index a3bb7d021..8b183aa89 100644 --- a/RingStream.h +++ b/RingStream.h @@ -48,6 +48,9 @@ class RingStream : public Print { void flush(); void info(); byte readRawByte(); + inline byte peek() { + return _buffer[_pos_read]; + }; private: int _len; int _pos_write; From 024c8fc1991a4f5e51fadf35c20e92ba1e9c0631 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 10:41:27 +0200 Subject: [PATCH 198/870] repair peek --- RingStream.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RingStream.h b/RingStream.h index 8b183aa89..cfc82ebe1 100644 --- a/RingStream.h +++ b/RingStream.h @@ -48,7 +48,8 @@ class RingStream : public Print { void flush(); void info(); byte readRawByte(); - inline byte peek() { + inline int peek() { + if ((_pos_read==_pos_write) && !_overflow) return -1; // empty return _buffer[_pos_read]; }; private: From eeb70293e090fb773e841b2ccbba2d738de255ba Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 10:42:03 +0200 Subject: [PATCH 199/870] disable ringstream read from flash --- StringFormatter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index ccb6ef3d6..6cdd5cdd4 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -91,6 +91,9 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { { const FSH* flash= (const FSH*)va_arg(args, char*); +#ifndef ARDUINO_ARCH_ESP32 + // On ESP32 the reading flashstring from rinstream code + // crashes, so don't use the flashstream hack on ESP32 #if WIFI_ON | ETHERNET_ON // RingStream has special logic to handle flash strings // but is not implemented unless wifi or ethernet are enabled. @@ -98,6 +101,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) ((RingStream *)stream)->printFlash(flash); else +#endif #endif stream->print(flash); break; From 9d4e7903d5d1493024baf38635c0a49270afd957 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 10:46:42 +0200 Subject: [PATCH 200/870] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 064160a76..e91d7e023 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220730" +#define GITHUB_SHA "PORTX-HAL-20220731" From 90d6ff43c51ee9114f932ff7015a5ad835db3a98 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 13:31:58 +0200 Subject: [PATCH 201/870] on ESP32 wifiloop is an own task --- CommandStation-EX.ino | 3 --- 1 file changed, 3 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index c3cb4784d..2c520c0e5 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -141,9 +141,6 @@ void loop() #if WIFI_ON WifiInterface::loop(); #endif //WIFI_ON -#else - // ESP32 needs wifi on always - WifiESP::loop(); #endif //ARDUINO_ARCH_ESP32 #if ETHERNET_ON EthernetInterface::loop(); From ed1b451b85337d67d5999bc0334a8cb818796305 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 13:35:25 +0200 Subject: [PATCH 202/870] ESP32 Wifi "good to have" commands --- WifiESP32.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index a48a590c4..fc7a276c9 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -91,6 +91,11 @@ bool WifiESP::setup(const char *SSid, // enableCoreWDT(1); // disableCoreWDT(0); + // clean start + WiFi.disconnect(true); + //WiFi.useStaticBuffers(true); + //WiFi.setTxPower(WIFI_POWER_8_5dBm); + const char *yourNetwork = "Your network "; if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0) haveSSID = false; From 7a123e7e1798d2ac2fbe976766e8b138d878ebd7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 31 Jul 2022 23:07:19 +0200 Subject: [PATCH 203/870] make service start to be outside the DONT_TOUCH_WIFI_CONF area --- WifiInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 5f3b8e64b..4d93eda48 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -276,6 +276,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, checkForOK(2000, true); } } +#endif //DONT_TOUCH_WIFI_CONF StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1) checkForOK(1000, true); // ignore result in case it already was off @@ -291,7 +292,6 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port if (!checkForOK(1000, true)) return WIFI_DISCONNECTED; -#endif //DONT_TOUCH_WIFI_CONF StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED; From e5ce76e70334325f3aa6217f8fd65409e6cc7e54 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 1 Aug 2022 22:56:56 +0200 Subject: [PATCH 204/870] idle waveform through RMT --- DCCRMT.cpp | 2 +- DCCRMT.h | 5 +---- DCCWaveform.cpp | 21 ++++++++++++++++++++- DCCWaveform.h | 9 ++++++++- MotorDriver.h | 1 + TrackManager.cpp | 9 +++++++++ TrackManager.h | 6 ++++++ 7 files changed, 46 insertions(+), 7 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index d9a78442a..0d819b9d1 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -1,5 +1,5 @@ /* - * © 2021, Harald Barth. + * © 2021-2022, Harald Barth. * * This file is part of DCC-EX * diff --git a/DCCRMT.h b/DCCRMT.h index e1834236b..6747e74dd 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -1,5 +1,5 @@ /* - * © 2021, Harald Barth. + * © 2021-2022, Harald Barth. * * This file is part of DCC-EX * @@ -37,9 +37,6 @@ class RMTChannel { //int RMTfillData(dccPacket packet); int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); - static RMTChannel mainRMTChannel; - static RMTChannel progRMTChannel; - private: rmt_channel_t channel; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 300150e3e..ff0cd19b4 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -198,12 +198,31 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); +RMTChannel *DCCWaveform::rmtMainChannel = NULL; +RMTChannel *DCCWaveform::rmtProgChannel = NULL; DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) { + isMainTrack = isMain; + packetPending = false; + requiredPreambles = preambleBits; } - void DCCWaveform::begin() { + for(const auto& md: TrackManager::getMainDrivers()) { + if(rmtMainChannel) { + /* rmtMainChannel->addPin(md->getSignalPin); // add pin to existing main channel */ + } else { + DIAG(F("new MAIN channel %d"), md->getSignalPin()); + rmtMainChannel = new RMTChannel(md->getSignalPin(), true); /* create new main channel */ + } + } + if (rmtProgChannel) { + /* exchange prog channel - not supported yet */ + } else { + DIAG(F("new PROGchannel %d"), TrackManager::getProgDriver()->getSignalPin()); + rmtProgChannel = new RMTChannel(TrackManager::getProgDriver()->getSignalPin(), false); + } } + void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) { } void DCCWaveform::loop() { diff --git a/DCCWaveform.h b/DCCWaveform.h index cee39c306..d530b7071 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -25,6 +25,10 @@ #define DCCWaveform_h #include "MotorDriver.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "DCCRMT.h" +#include "TrackManager.h" +#endif @@ -71,6 +75,9 @@ class DCCWaveform { byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum byte pendingLength; byte pendingRepeats; - +#ifdef ARDUINO_ARCH_ESP32 + static RMTChannel *rmtMainChannel; + static RMTChannel *rmtProgChannel; +#endif }; #endif diff --git a/MotorDriver.h b/MotorDriver.h index d9d398265..d673044af 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -112,6 +112,7 @@ class MotorDriver { else pinMode(signalPin, INPUT); }; + inline byte getSignalPin() { return signalPin; }; virtual void setBrake( bool on); virtual void setDCSignal(byte speedByte); virtual int getCurrentRaw(); diff --git a/TrackManager.cpp b/TrackManager.cpp index acfb3f960..064dfc8a6 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -303,6 +303,15 @@ MotorDriver * TrackManager::getProgDriver() { return NULL; } +#ifdef ARDUINO_ARCH_ESP32 +std::vectorTrackManager::getMainDrivers() { + std::vector v; + FOR_EACH_TRACK(t) + if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]); + return v; +} +#endif + void TrackManager::setPower2(bool setProg,POWERMODE mode) { if (!setProg) mainPowerGuess=mode; FOR_EACH_TRACK(t) { diff --git a/TrackManager.h b/TrackManager.h index 9184fd029..b21022143 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -18,6 +18,9 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif #ifndef TrackManager_h #define TrackManager_h #include "FSH.h" @@ -55,6 +58,9 @@ class TrackManager { static void setPROGSignal( bool on); static void setDCSignal(int16_t cab, byte speedbyte); static MotorDriver * getProgDriver(); +#ifdef ARDUINO_ARCH_ESP32 + static std::vectorgetMainDrivers(); +#endif static void setPower2(bool progTrack,POWERMODE mode); static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);} static void setMainPower(POWERMODE mode) {setPower2(false,mode);} From 33c9155f6ebcb3dba63bbafd2acc84d4186fba64 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 01:22:36 +0200 Subject: [PATCH 205/870] Make own interrupt routine selection array as RMT api only can have one interrupt routine with one argument --- DCCRMT.cpp | 21 +++++++++++++-------- DCCRMT.h | 6 +++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 0d819b9d1..d29b74b5e 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -57,9 +57,15 @@ void setEOT(rmt_item32_t* item) { item->val = 0; } +// This is an array that contains the this pointers +// to all uses channel objects. This is used to determine +// which of the channels was triggering the ISR as there +// is only ONE common ISR routine for all channels. +RMTChannel *channelHandle[8] = { 0 }; + void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { - RMTChannel *tt = (RMTChannel *)t; - tt->RMTinterrupt(); + RMTChannel *tt = channelHandle[channel]; + if (tt) tt->RMTinterrupt(); } RMTChannel::RMTChannel(byte pin, bool isMain) { @@ -133,18 +139,17 @@ RMTChannel::RMTChannel(byte pin, bool isMain) { DIAG(F("Register interrupt on core %d"), xPortGetCoreID()); ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true)); - rmt_register_tx_end_callback(interrupt, this); + channelHandle[channel] = this; // used by interrupt + rmt_register_tx_end_callback(interrupt, 0); rmt_set_tx_intr_en(channel, true); - DIAG(F("Starting channel %d signal generator"), config.channel); + DIAG(F("Starting channel %d signal generator for %s"), config.channel, isMainTrack ? "MAIN" : "PROG"); // send one bit to kickstart the signal, remaining data will come from the // packet queue. We intentionally do not wait for the RMT TX complete here. //rmt_write_items(channel, preamble, preambleLen, false); RMTprefill(); - preambleNext = true; dataReady = false; - RMTinterrupt(); } void RMTChannel::RMTprefill() { @@ -161,10 +166,10 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun // to the HW. dataRepeat on the other hand signals back to // the caller of this function if the data has been sent enough // times (0 to 3 means 1 to 4 times in total). - if (dataReady == true) - return 1000; if (dataRepeat > 0) // we have still old work to do return dataRepeat; + if (dataReady == true) // the packet is not copied out yet + return 1000; if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen); return -1; // something very broken, can not convert packet diff --git a/DCCRMT.h b/DCCRMT.h index 6747e74dd..268c99e08 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -36,6 +36,11 @@ class RMTChannel { void RMTprefill(); //int RMTfillData(dccPacket packet); int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); + inline bool busy() { + if (dataRepeat > 0) // we have still old work to do + return true; + return dataReady; + }; private: @@ -50,7 +55,6 @@ class RMTChannel { byte dataLen; byte maxDataLen; // flags - volatile bool preambleNext = true; // alternate between preamble and content volatile bool dataReady = false; // do we have real data available or send idle volatile byte dataRepeat = 0; }; From b09dba12139e9e33a2211a54c8fb2861ae22dd15 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 01:24:01 +0200 Subject: [PATCH 206/870] Make packetPending private and new access routine. Implement schedulePacket without packetPending variable for ESP32 --- DCC.cpp | 2 +- DCCWaveform.cpp | 32 +++++++++++++++++++++++++++++++- DCCWaveform.h | 6 ++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index f8ca7b14b..0993f4cb7 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -587,7 +587,7 @@ void DCC::loop() { void DCC::issueReminders() { // if the main track transmitter still has a pending packet, skip this time around. - if ( DCCWaveform::mainTrack.packetPending) return; + if ( DCCWaveform::mainTrack.getPacketPending()) return; // This loop searches for a loco in the speed table starting at nextLoco and cycling back around for (int reg=0;reg MAX_PACKET_SIZE) return; // allow for chksum + + byte checksum = 0; + for (byte b = 0; b < byteCount; b++) { + checksum ^= buffer[b]; + pendingPacket[b] = buffer[b]; + } + // buffer is MAX_PACKET_SIZE but pendingPacket is one bigger + pendingPacket[byteCount] = checksum; + pendingLength = byteCount + 1; + pendingRepeats = repeats; + sentResetsSincePacket=0; + { + int ret; + do { + if(isMainTrack) + ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats); + else + ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats); + } while(ret > 0); + } +} + +bool DCCWaveform::getPacketPending() { + if(isMainTrack) + return rmtMainChannel->busy(); + else + return rmtProgChannel->busy(); } void DCCWaveform::loop() { } diff --git a/DCCWaveform.h b/DCCWaveform.h index d530b7071..2a55189a1 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -54,11 +54,13 @@ class DCCWaveform { static DCCWaveform progTrack; inline void clearRepeats() { transmitRepeats=0; } void schedulePacket(const byte buffer[], byte byteCount, byte repeats); - volatile bool packetPending; volatile byte sentResetsSincePacket; + bool getPacketPending(); private: - +#ifndef ARDUINO_ARCH_ESP32 + volatile bool packetPending; +#endif static void interruptHandler(); void interrupt2(); From 2c24bbee17f5e7e53ad43b27c44bebe71e8df72c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 01:26:02 +0200 Subject: [PATCH 207/870] typo in DIAG --- DCCRMT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index d29b74b5e..c6ffb60c0 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -143,7 +143,7 @@ RMTChannel::RMTChannel(byte pin, bool isMain) { rmt_register_tx_end_callback(interrupt, 0); rmt_set_tx_intr_en(channel, true); - DIAG(F("Starting channel %d signal generator for %s"), config.channel, isMainTrack ? "MAIN" : "PROG"); + DIAG(F("Starting channel %d signal generator for %s"), config.channel, isMain ? "MAIN" : "PROG"); // send one bit to kickstart the signal, remaining data will come from the // packet queue. We intentionally do not wait for the RMT TX complete here. From 167c5db1fe716d964b623f23a1aa21328b44322d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 01:30:26 +0200 Subject: [PATCH 208/870] version which does MAIN on ESP32 --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e91d7e023..1c53f089a 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220731" +#define GITHUB_SHA "PORTX-HAL-20220801" From 07600274f13d5f788c8a2ad81ba44b17735486e1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 12:42:20 +0200 Subject: [PATCH 209/870] count packets sent by RMT HW --- DCCRMT.cpp | 1 + DCCRMT.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index c6ffb60c0..0bad881f2 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -197,6 +197,7 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun void IRAM_ATTR RMTChannel::RMTinterrupt() { //no rmt_tx_start(channel,true) as we run in loop mode //preamble is always loaded at beginning of buffer + packetCounter++; if (dataReady) { // if we have new data, fill while preamble is running rmt_fill_tx_items(channel, data, dataLen, preambleLen-1); dataReady = false; diff --git a/DCCRMT.h b/DCCRMT.h index 268c99e08..45d3eef8e 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -41,6 +41,7 @@ class RMTChannel { return true; return dataReady; }; + inline uint32_t packetCount() { return packetCounter; }; private: @@ -54,6 +55,7 @@ class RMTChannel { rmt_item32_t *data; byte dataLen; byte maxDataLen; + uint32_t packetCounter = 0; // flags volatile bool dataReady = false; // do we have real data available or send idle volatile byte dataRepeat = 0; From 6b2cd226e2d0a376e4193545cdd3af55ee40b168 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 15:13:29 +0200 Subject: [PATCH 210/870] provide methods to access sentResetsSincePacket --- DCCWaveform.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/DCCWaveform.h b/DCCWaveform.h index 2a55189a1..cad786184 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -53,6 +53,21 @@ class DCCWaveform { static DCCWaveform mainTrack; static DCCWaveform progTrack; inline void clearRepeats() { transmitRepeats=0; } +#ifndef ARDUINO_ARCH_ESP32 + inline void clearResets() { sentResetsSincePacket=0; } + inline byte getResets() { return sentResetsSincePacket; } +#else + inline void clearResets() { resetPacketBase = isMainTrack ? + rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); }; + inline byte getResets() { + uint32_t packetcount = isMainTrack ? + rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); + uint32_t count = packetcount - resetPacketBase; + if ((count & 255) == 0) // no high bits set + return count; + return 255; + }; +#endif void schedulePacket(const byte buffer[], byte byteCount, byte repeats); volatile byte sentResetsSincePacket; bool getPacketPending(); @@ -60,6 +75,8 @@ class DCCWaveform { private: #ifndef ARDUINO_ARCH_ESP32 volatile bool packetPending; +#else + volatile uint32_t resetPacketBase; #endif static void interruptHandler(); void interrupt2(); From 863f2f4a85d889ad1d220a6c6c9dce3315862ce2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 15:36:51 +0200 Subject: [PATCH 211/870] eliminate resetsCounterP/setResetCounterPointer and replace with isProgTrack/makeProgTrack method --- DCCACK.cpp | 1 - MotorDriver.cpp | 5 +++-- MotorDriver.h | 6 +++--- TrackManager.cpp | 16 +++++++++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index 83ce359b2..de66355b1 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -76,7 +76,6 @@ void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_C callback(-2); // our prog track cant measure current return; } - progDriver->setResetCounterPointer(&(DCCWaveform::progTrack.sentResetsSincePacket)); ackManagerRejoin=TrackManager::isJoined(); if (ackManagerRejoin ) { diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 42fef166a..0d6423bea 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -23,6 +23,7 @@ */ #include #include "MotorDriver.h" +#include "DCCWaveform.h" #include "DCCTimer.h" #include "DIAG.h" @@ -122,8 +123,8 @@ void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { IODevice::write(powerPin,HIGH); - if (resetsCounterP != NULL) - *resetsCounterP = 0; + if (isProgTrack) + DCCWaveform::progTrack.clearResets(); } else IODevice::write(powerPin,LOW); powerMode=mode; diff --git a/MotorDriver.h b/MotorDriver.h index d673044af..7e71687e5 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -136,12 +136,12 @@ class MotorDriver { inline byte getFaultPin() { return faultPin; } - inline void setResetCounterPointer(volatile byte *bp) { // load resetPacketCounter pointer - resetsCounterP = bp; + inline void makeProgTrack(bool on) { // let this output know it's a prog track. + isProgTrack = on; } void checkPowerOverload(bool useProgLimit, byte trackno); private: - volatile byte *resetsCounterP = NULL; // points to the resetPacketCounter if this is a prog track + bool isProgTrack = false; // tells us if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { getFastPin(type, pin, 0, result); diff --git a/TrackManager.cpp b/TrackManager.cpp index 064dfc8a6..0aac3f7b8 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -140,14 +140,16 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } if (mode==TRACK_MODE_PROG) { - // only allow 1 track to be prog - FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) { - track[t]->setPower(POWERMODE::OFF); - trackMode[t]=TRACK_MODE_OFF; - } + // only allow 1 track to be prog + FOR_EACH_TRACK(t) + if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) { + track[t]->setPower(POWERMODE::OFF); + trackMode[t]=TRACK_MODE_OFF; + track[t]->makeProgTrack(false); // revoke prog track special handling + } + track[trackToSet]->makeProgTrack(true); // set for prog track special handling } else { - track[trackToSet]->setResetCounterPointer(NULL); // only the prog track has this pointer set + track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type } trackMode[trackToSet]=mode; trackDCAddr[trackToSet]=dcAddr; From c7cf8246a7c78ad0f9b85b9b2c0c6451446ce5be Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 15:57:45 +0200 Subject: [PATCH 212/870] make sentResetsSincePacket private to the class and replace all accesses with with methods --- DCCACK.cpp | 6 +++--- DCCWaveform.cpp | 10 +++++----- DCCWaveform.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index de66355b1..d15938397 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -81,7 +81,7 @@ void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_C if (ackManagerRejoin ) { // Change from JOIN must zero resets packet. TrackManager::setJoin(false); - DCCWaveform::progTrack.sentResetsSincePacket = 0; + DCCWaveform::progTrack.clearResets(); } autoPowerOff=false; @@ -93,7 +93,7 @@ void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_C /* TODO !!! in MotorDriver surely! if (MotorDriver::commonFaultPin) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON); - DCCWaveform::progTrack.sentResetsSincePacket = 0; + DCCWaveform::progTrack.clearResets(); **/ } @@ -117,7 +117,7 @@ const byte RESET_MIN=8; // tuning of reset counter before sending message // checkRessets return true if the caller should yield back to loop and try later. bool DCCACK::checkResets(uint8_t numResets) { - return DCCWaveform::progTrack.sentResetsSincePacket < numResets; + return DCCWaveform::progTrack.getResets() < numResets; } // Operations applicable to PROG track ONLY. // (yes I know I could have subclassed the main track but...) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 7cb59daf6..e39a61caf 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -89,7 +89,7 @@ void DCCWaveform::interruptHandler() { // WAVE_PENDING means we dont yet know what the next bit is if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); - else DCCACK::checkAck(progTrack.sentResetsSincePacket); + else DCCACK::checkAck(progTrack.getResets()); } #pragma GCC pop_options @@ -160,14 +160,14 @@ void DCCWaveform::interrupt2() { transmitLength = pendingLength; transmitRepeats = pendingRepeats; packetPending = false; - sentResetsSincePacket=0; + clearResets(); } else { // Fortunately reset and idle packets are the same length memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket)); transmitLength = sizeof(idlePacket); transmitRepeats = 0; - if (sentResetsSincePacket<250) sentResetsSincePacket++; + if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!) } } } @@ -189,7 +189,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea pendingLength = byteCount + 1; pendingRepeats = repeats; packetPending = true; - sentResetsSincePacket=0; + clearResets(); } bool DCCWaveform::getPacketPending() { return packetPending; @@ -237,7 +237,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea pendingPacket[byteCount] = checksum; pendingLength = byteCount + 1; pendingRepeats = repeats; - sentResetsSincePacket=0; + clearResets(); { int ret; do { diff --git a/DCCWaveform.h b/DCCWaveform.h index cad786184..3ba313aa4 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -69,12 +69,12 @@ class DCCWaveform { }; #endif void schedulePacket(const byte buffer[], byte byteCount, byte repeats); - volatile byte sentResetsSincePacket; bool getPacketPending(); private: #ifndef ARDUINO_ARCH_ESP32 volatile bool packetPending; + volatile byte sentResetsSincePacket; #else volatile uint32_t resetPacketBase; #endif From 89905f8ed7c89a51c4609bd17171b2815af12a09 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 16:32:21 +0200 Subject: [PATCH 213/870] check ack in loop and update version --- DCCWaveform.cpp | 1 + GITHUB_SHA.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index e39a61caf..1204dc995 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -256,5 +256,6 @@ bool DCCWaveform::getPacketPending() { return rmtProgChannel->busy(); } void DCCWaveform::loop() { + DCCACK::checkAck(progTrack.getResets()); } #endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 1c53f089a..1aea59bd3 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220801" +#define GITHUB_SHA "PORTX-HAL-20220802" From 37ea688eaba9097ff62e98c40fae4d8869ce472f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 20:58:05 +0200 Subject: [PATCH 214/870] do compare with 255 right --- DCCWaveform.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCWaveform.h b/DCCWaveform.h index 3ba313aa4..5ff9805e7 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -63,9 +63,9 @@ class DCCWaveform { uint32_t packetcount = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); uint32_t count = packetcount - resetPacketBase; - if ((count & 255) == 0) // no high bits set - return count; - return 255; + if (count > 255) // cap to 255 + return 255; + return count; }; #endif void schedulePacket(const byte buffer[], byte byteCount, byte repeats); From df767aaa36d0e015f0a81b521296ef0cd1aacad6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 21:44:29 +0200 Subject: [PATCH 215/870] go back to idle/reset packet if nothing to do --- DCCRMT.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 0bad881f2..cc5427482 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -198,12 +198,17 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() { //no rmt_tx_start(channel,true) as we run in loop mode //preamble is always loaded at beginning of buffer packetCounter++; + if (!dataReady && dataRepeat == 0) { // we did run empty + rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1); + return; // nothing to do about that + } + + // take care of incoming data if (dataReady) { // if we have new data, fill while preamble is running rmt_fill_tx_items(channel, data, dataLen, preambleLen-1); dataReady = false; } if (dataRepeat > 0) // if a repeat count was specified, work on that dataRepeat--; - return; } #endif //ESP32 From 6167a949b666b4571ba82c27910ae8dcb81e32c8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 21:46:00 +0200 Subject: [PATCH 216/870] approx conversion factor for 12bit ADC of ESP32 --- MotorDrivers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index f51807f04..2f51b4a0e 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -56,8 +56,8 @@ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) #elif defined(ARDUINO_ARCH_ESP32) #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, UNUSED_PIN, 36/*A4*/, 2.00, 2000, UNUSED_PIN), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, UNUSED_PIN, 39/*A5*/, 2.00, 2000, UNUSED_PIN) + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, UNUSED_PIN, 36/*A4*/, 0.57, 2000, UNUSED_PIN), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, UNUSED_PIN, 39/*A5*/, 0.57, 2000, UNUSED_PIN) #else #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ From 33327d14c9d61fcd4b75821637cd0e8f45a8a6ce Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 21:49:16 +0200 Subject: [PATCH 217/870] Current reading for ACK and overload on ESP32 --- DCCWaveform.cpp | 3 ++- MotorDriver.cpp | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 1204dc995..6d1406f67 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -198,6 +198,7 @@ bool DCCWaveform::getPacketPending() { #ifdef ARDUINO_ARCH_ESP32 #include "DCCWaveform.h" +#include "DCCACK.h" DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); @@ -255,7 +256,7 @@ bool DCCWaveform::getPacketPending() { else return rmtProgChannel->busy(); } -void DCCWaveform::loop() { +void IRAM_ATTR DCCWaveform::loop() { DCCACK::checkAck(progTrack.getResets()); } #endif diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 0d6423bea..0239d1c2d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -26,6 +26,24 @@ #include "DCCWaveform.h" #include "DCCTimer.h" #include "DIAG.h" +#if defined(ARDUINO_ARCH_ESP32) +#include +#include +#include +#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28) + +int IRAM_ATTR local_adc1_get_raw(int channel) { + uint16_t adc_value; + SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected + while (SENS.sar_slave_addr1.meas_status != 0); + SENS.sar_meas_start1.meas1_start_sar = 0; + SENS.sar_meas_start1.meas1_start_sar = 1; + while (SENS.sar_meas_start1.meas1_done_sar == 0); + adc_value = SENS.sar_meas_start1.meas1_data_sar; + return adc_value; +} + +#endif bool MotorDriver::commonFaultPin=false; @@ -80,8 +98,16 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 currentPin=current_pin; if (currentPin!=UNUSED_PIN) { +#ifdef ARDUINO_ARCH_ESP32 + pinMode(currentPin, ANALOG); + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11); + senseOffset = adc1_get_raw(pinToADC1Channel(currentPin)); + DIAG(F("senseOffset c=%d"), senseOffset); +#else pinMode(currentPin, INPUT); senseOffset=analogRead(currentPin); // value of sensor at zero current +#endif } faultPin=fault_pin; @@ -161,9 +187,13 @@ int MotorDriver::getCurrentRaw() { // This function should NOT be called in an interruot so we // dont need to fart about saving and restoring CPU specific // interrupt registers. +#ifdef ARDUINO_ARCH_ESP32 + current = local_adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset; +#else noInterrupts(); current = analogRead(currentPin)-senseOffset; interrupts(); +#endif if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) return (current == 0 ? -1 : -current); @@ -218,9 +248,12 @@ int MotorDriver::getCurrentRawInInterrupt() { // IMPORTANT: This function must be called in Interrupt() time within the 56uS timer // The default analogRead takes ~100uS which is catastrphic // so DCCTimer has set the sample time to be much faster. - if (currentPin==UNUSED_PIN) return 0; +#ifdef ARDUINO_ARCH_ESP32 //On ESP we do all in loop() instead of in interrupt + return getCurrentRaw(); +#else return analogRead(currentPin)-senseOffset; +#endif } unsigned int MotorDriver::raw2mA( int raw) { From ee279c9a03efd4f2b83078cbf509805f9ca108f7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 Aug 2022 21:56:06 +0200 Subject: [PATCH 218/870] version update --- GITHUB_SHA.h | 2 +- version.h | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 1aea59bd3..717e95c02 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220802" +#define GITHUB_SHA "PORTX-HAL-20220802-1" diff --git a/version.h b/version.h index 90e5ce5dd..a5d336f59 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,11 @@ #include "StringFormatter.h" -#define VERSION "4.2.0 rc3" +#define VERSION "4.2.1 rc1" +// 4.2.1 ESP32 alpha +// Ready for alpha test on ESP32. Track switching with <=> untested +// Send DCC signal on MAIN +// Detects ACK on PROG // 4.2.0 Track Manager additions: // Broadcast improvements to separate <> and Withrottle responses // Float eliminated saving >1.5kb PROGMEM and speed. From 172dbfd4448b65bc615b1c6a9f82b2fdd9c2ce60 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 3 Aug 2022 08:57:34 +0200 Subject: [PATCH 219/870] make whole files ESP32 only --- CommandStation-EX.ino | 1 - DCCRMT.cpp | 3 +-- DCCRMT.h | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 2c520c0e5..57a178bb6 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -23,7 +23,6 @@ #include "config.example.h" #endif - /* * © 2021 Neil McKechnie * © 2020-2021 Chris Harlow, Harald Barth, David Cutting, diff --git a/DCCRMT.cpp b/DCCRMT.cpp index cc5427482..8fe427020 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -17,9 +17,8 @@ * along with CommandStation. If not, see . */ -#include "config.h" -#include "defines.h" #if defined(ARDUINO_ARCH_ESP32) +#include "defines.h" #include "DIAG.h" #include "DCCRMT.h" #include "DCCWaveform.h" // for MAX_PACKET_SIZE diff --git a/DCCRMT.h b/DCCRMT.h index 45d3eef8e..bae4bb907 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -17,9 +17,9 @@ * along with CommandStation. If not, see . */ +#if defined(ARDUINO_ARCH_ESP32) #pragma once #include -#if defined(ARDUINO_ARCH_ESP32) #include "driver/rmt.h" #include "soc/rmt_reg.h" #include "soc/rmt_struct.h" From fad504bc7f441d5fa87ba5e0e99c94d9a8494261 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 3 Aug 2022 09:31:03 +0200 Subject: [PATCH 220/870] extra catch for buggy compiler/preprocessor --- CommandStation-EX.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 57a178bb6..0cbf05669 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -18,6 +18,9 @@ #if __has_include ( "config.h") #include "config.h" + #ifndef MOTOR_SHIELD_TYPE + #error Your config.h must include a MOTOR_SHIELD_TYPE definition. If you see this warning in spite not having a config.h, you have a buggy preprocessor and must copy config.example.h to config.h + #endif #else #warning config.h not found. Using defaults from config.example.h #include "config.example.h" From ad4a9d88b422064ade1f2967cca58b853382e039 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 3 Aug 2022 16:21:58 +0800 Subject: [PATCH 221/870] ESP32 platformio initial build info --- platformio.ini | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6b8796cea..0a38cf8c6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,6 +17,7 @@ default_envs = nano samd21-dev-usb samd21-zero-usb + ESP32 src_dir = . include_dir = . @@ -28,22 +29,20 @@ platform = atmelsam board = sparkfun_samd21_dev_usb framework = arduino upload_protocol = sam-ba -lib_deps = - ${env.lib_deps} +lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo -build_flags = -std=c++17 +build_flags = -std=c++17 [env:samd21-zero-usb] platform = atmelsam board = zeroUSB framework = arduino upload_protocol = sam-ba -lib_deps = - ${env.lib_deps} +lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo -build_flags = -std=c++17 +build_flags = -std=c++17 [env:samc21-firebox] platform = atmelsam @@ -55,7 +54,7 @@ lib_deps = SparkFun External EEPROM Arduino Library monitor_speed = 115200 monitor_flags = --echo -build_flags = -std=c++17 +build_flags = -std=c++17 [env:mega2560-debug] platform = atmelavr @@ -156,7 +155,13 @@ platform = atmelavr board = nanoatmega328new board_upload.maximum_size = 32256 framework = arduino -lib_deps = - ${env.lib_deps} +lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_flags = --echo + +[env:ESP32] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 \ No newline at end of file From 87fd1b887ef80badbf4744402f2485ecf0112db0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 4 Aug 2022 01:19:11 +0200 Subject: [PATCH 222/870] ESP32 RMT pin add function --- DCCRMT.cpp | 11 +++++++++++ DCCRMT.h | 1 + 2 files changed, 12 insertions(+) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 8fe427020..dcf3ac771 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -210,4 +210,15 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() { if (dataRepeat > 0) // if a repeat count was specified, work on that dataRepeat--; } + +bool RMTChannel::addPin(byte pin) { + gpio_num_t gpioNum = (gpio_num_t)(pin); + esp_err_t err; + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO); + err = gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT); + if (err != ESP_OK) return false; + gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, 0, 0); + if (err != ESP_OK) return false; + return true; +} #endif //ESP32 diff --git a/DCCRMT.h b/DCCRMT.h index bae4bb907..ff790cd6a 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -32,6 +32,7 @@ class RMTChannel { public: RMTChannel(byte pin, bool isMain); + bool addPin(byte pin); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); //int RMTfillData(dccPacket packet); From 7bad16dc59bf032f6ed9f646749cc8120425e899 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 4 Aug 2022 01:20:33 +0200 Subject: [PATCH 223/870] ESP32 DCCWaveform::begin() bugfix for prog --- DCCWaveform.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 6d1406f67..ac7ecb1e1 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -212,17 +212,22 @@ DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) { void DCCWaveform::begin() { for(const auto& md: TrackManager::getMainDrivers()) { if(rmtMainChannel) { - /* rmtMainChannel->addPin(md->getSignalPin); // add pin to existing main channel */ + DIAG(F("added pin %d to MAIN channel"), md->getSignalPin()); + rmtMainChannel->addPin(md->getSignalPin()); // add pin to existing main channel } else { - DIAG(F("new MAIN channel %d"), md->getSignalPin()); + DIAG(F("new MAIN channel with pin %d"), md->getSignalPin()); rmtMainChannel = new RMTChannel(md->getSignalPin(), true); /* create new main channel */ } } - if (rmtProgChannel) { - /* exchange prog channel - not supported yet */ - } else { - DIAG(F("new PROGchannel %d"), TrackManager::getProgDriver()->getSignalPin()); - rmtProgChannel = new RMTChannel(TrackManager::getProgDriver()->getSignalPin(), false); + MotorDriver *md = TrackManager::getProgDriver(); + if (md) { + if (rmtProgChannel) { + DIAG(F("added pin %d to PROG channel"), md->getSignalPin()); + rmtProgChannel->addPin(md->getSignalPin()); // add pin to existing prog channel + } else { + DIAG(F("new PROGchannel with pin %d"), TrackManager::getProgDriver()->getSignalPin()); + rmtProgChannel = new RMTChannel(TrackManager::getProgDriver()->getSignalPin(), false); + } } } From 96f042897a120ddf3f136cab8d0320153ee756a4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 4 Aug 2022 01:21:28 +0200 Subject: [PATCH 224/870] Adopt setTrackMode to RMT channel pins --- DCC.cpp | 3 ++- TrackManager.cpp | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 0993f4cb7..3d2d0563d 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -68,8 +68,9 @@ void DCC::begin(const FSH * motorShieldName) { (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); #endif - +#ifndef ARDUINO_ARCH_ESP32 /* On ESP32 started in TrackManager::setTrackMode() */ DCCWaveform::begin(); +#endif } diff --git a/TrackManager.cpp b/TrackManager.cpp index 0aac3f7b8..ba3f8c019 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -127,7 +127,6 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { } } - bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; @@ -139,6 +138,11 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr return false; } +#ifdef ARDUINO_ARCH_ESP32 + // remove pin from MUX matrix and turn it off + DIAG(F("Track=%c remove pin %d"),trackToSet+'A', track[trackToSet]->getSignalPin()); + gpio_reset_pin((gpio_num_t)track[trackToSet]->getSignalPin()); +#endif if (mode==TRACK_MODE_PROG) { // only allow 1 track to be prog FOR_EACH_TRACK(t) @@ -175,6 +179,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // pin should be turned on track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT); +#ifndef ARDUINO_ARCH_ESP32 // re-evaluate HighAccuracy mode // We can only do this is all main and prog tracks agree bool canDo=true; @@ -205,6 +210,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track } +#else + DCCWaveform::begin(); +#endif // Normal running tracks are set to the global power state track[trackToSet]->setPower( From 0301b787124e41e3eef31444119fa6120fd1dd17 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 4 Aug 2022 01:22:07 +0200 Subject: [PATCH 225/870] version update --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 717e95c02..6080bfb57 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220802-1" +#define GITHUB_SHA "PORTX-HAL-20220804" From f57fd245a1e4df13fc5bd8a1d0930706deccecb2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 4 Aug 2022 04:15:28 +0200 Subject: [PATCH 226/870] temp fix for counting packets code --- DCCACK.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DCCACK.cpp b/DCCACK.cpp index d15938397..82deb7f75 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -113,7 +113,11 @@ void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) { Setup(0, 0, program, callback); } +#ifdef ARDUINO_ARCH_ESP32 +const byte RESET_MIN=12; // Ugly fix until counting code is right +#else const byte RESET_MIN=8; // tuning of reset counter before sending message +#endif // checkRessets return true if the caller should yield back to loop and try later. bool DCCACK::checkResets(uint8_t numResets) { From 67b14ec57d91c5df6cc9e337ac91326e90f0cccc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 4 Aug 2022 09:50:20 +0200 Subject: [PATCH 227/870] take into account that the reset packets are sent first #repeat packets into the future --- DCCACK.cpp | 4 ---- DCCWaveform.cpp | 3 ++- DCCWaveform.h | 15 ++++++++++----- GITHUB_SHA.h | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index 82deb7f75..d15938397 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -113,11 +113,7 @@ void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) { Setup(0, 0, program, callback); } -#ifdef ARDUINO_ARCH_ESP32 -const byte RESET_MIN=12; // Ugly fix until counting code is right -#else const byte RESET_MIN=8; // tuning of reset counter before sending message -#endif // checkRessets return true if the caller should yield back to loop and try later. bool DCCACK::checkResets(uint8_t numResets) { diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index ac7ecb1e1..7140e91c5 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -243,7 +243,8 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea pendingPacket[byteCount] = checksum; pendingLength = byteCount + 1; pendingRepeats = repeats; - clearResets(); + // The resets will be zero not only now but as well repeats packets into the future + clearResets(repeats+1); { int ret; do { diff --git a/DCCWaveform.h b/DCCWaveform.h index 5ff9805e7..108a63d76 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -57,15 +57,20 @@ class DCCWaveform { inline void clearResets() { sentResetsSincePacket=0; } inline byte getResets() { return sentResetsSincePacket; } #else - inline void clearResets() { resetPacketBase = isMainTrack ? - rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); }; + // extrafudge is added when we know that the resets will first come extrafudge packets in the future + inline void clearResets(byte extrafudge=0) { + resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); + resetPacketBase += extrafudge; + }; inline byte getResets() { uint32_t packetcount = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); - uint32_t count = packetcount - resetPacketBase; - if (count > 255) // cap to 255 + uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic. + if (count > UINT32_MAX/2) // we are in the extrafudge area + return 0; + if (count > 255) // cap to 255 return 255; - return count; + return count; // all special cases handled above }; #endif void schedulePacket(const byte buffer[], byte byteCount, byte repeats); diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6080bfb57..c8e93ff78 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220804" +#define GITHUB_SHA "PORTX-HAL-20220804-1" From 6286f5fedfbba5410bdb17f9082ae1f4a2f94431 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 5 Aug 2022 12:25:32 +0200 Subject: [PATCH 228/870] init trackPWM to false so that we do not get false positives later --- MotorDriver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MotorDriver.h b/MotorDriver.h index 7e71687e5..9e32ea02b 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -131,7 +131,7 @@ class MotorDriver { } bool isPWMCapable(); bool canMeasureCurrent(); - bool trackPWM; // this track uses PWM timer to generate the DCC waveform + bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs inline byte getFaultPin() { return faultPin; From 704fabd1a4f086f32e730edb6dec34050844de54 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 5 Aug 2022 15:15:42 +0200 Subject: [PATCH 229/870] check M command against max DCC packet size --- DCCEXParser.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d1630c247..e9047a630 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -354,7 +354,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'P': // WRITE TRANSPARENT DCC PACKET PROG

// NOTE: this command was parsed in HEX instead of decimal params--; // drop REG - if (params<1) break; + if (params<1) break; + if (params > MAX_PACKET_SIZE) break; { byte packet[params]; for (int i=0;i Date: Fri, 5 Aug 2022 15:16:43 +0200 Subject: [PATCH 230/870] size send buffer for RMT channel including DCC checksum byte --- DCCRMT.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index dcf3ac771..cb28b1034 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -24,7 +24,9 @@ #include "DCCWaveform.h" // for MAX_PACKET_SIZE #include "soc/gpio_sig_map.h" -#define DATA_LEN(X) ((X)*9+1) // Each byte has one bit extra and we have one EOF marker +// Number of bits resulting out of X bytes of DCC payload data +// Each byte has one bit extra and at the end we have one EOF marker +#define DATA_LEN(X) ((X)*9+1) #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0) #error wrong IDF version @@ -108,7 +110,7 @@ RMTChannel::RMTChannel(byte pin, bool isMain) { setEOT(idle + 27); // EOT marker // data: max packet size today is 5 + checksum - maxDataLen = DATA_LEN(MAX_PACKET_SIZE); + maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t)); rmt_config_t config; From 608c7547fbdabd13e5f58a34ca733356b701dfc8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 5 Aug 2022 16:14:51 +0200 Subject: [PATCH 231/870] ESP32 Wifi AP mode no sleep --- WifiESP32.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index fc7a276c9..770557e66 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -146,6 +146,7 @@ bool WifiESP::setup(const char *SSid, strPass.concat(strMac); WiFi.mode(WIFI_AP); + WiFi.setSleep(false); if (WiFi.softAP(strSSID.c_str(), havePassword ? password : strPass.c_str(), channel, false, 8)) { From 01f129e25fd6f77bfee3f2f4205d015abed1c670 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 5 Aug 2022 18:49:35 +0200 Subject: [PATCH 232/870] version update --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c8e93ff78..f0d56e097 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220804-1" +#define GITHUB_SHA "PORTX-HAL-20220805" From 42ac954475351e2cb7cab13e596c97bd9cc785d9 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 5 Aug 2022 18:14:22 +0100 Subject: [PATCH 233/870] Forget withrottle client on disconnect --- CommandDistributor.cpp | 1 + WiThrottle.cpp | 8 ++++++++ WiThrottle.h | 1 + 3 files changed, 10 insertions(+) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index a0ebccdec..f8dc25913 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -82,6 +82,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream } void CommandDistributor::forget(byte clientId) { + if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); clients[clientId]=NONE_TYPE; } #endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 66463de7f..e2e7d959d 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -69,6 +69,14 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) { return new WiThrottle( wifiClient); } +void WiThrottle::forget( byte clientId) { + for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) + if (wt->clientid==clientId) { + delete wt; + break; + } +} + bool WiThrottle::isThrottleInUse(int cab) { for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) if (wt->areYouUsingThrottle(cab)) return true; diff --git a/WiThrottle.h b/WiThrottle.h index 54ca703ab..253787d54 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -37,6 +37,7 @@ class WiThrottle { void parse(RingStream * stream, byte * cmd); static WiThrottle* getThrottle( int wifiClient); static void markForBroadcast(int cab); + static void forget(byte clientId); private: WiThrottle( int wifiClientId); From 5f1a2631583689d237c0718093a910eb39831777 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 5 Aug 2022 19:54:01 +0200 Subject: [PATCH 234/870] do not make delete client conditional --- WifiESP32.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 770557e66..3be3827c2 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -193,15 +193,17 @@ void WifiESP::loop() { // really no good way to check for LISTEN especially in AP mode? if (APmode || WiFi.status() == WL_CONNECTED) { - if (server->hasClient()) { - // loop over all clients and remove inactive - for (clientId=0; clientIdhasClient()) { WiFiClient client; while (client = server->available()) { clients.push_back(client); From 012d427c6e381c5386fb7eebfdaa133688d4b230 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Aug 2022 00:34:35 +0200 Subject: [PATCH 235/870] loop over WiThrottle so that broadcast commands get into outbound ring and get sent --- EthernetInterface.cpp | 3 +++ WifiESP32.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index f691f45c6..48f9002a8 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -26,6 +26,7 @@ #include "EthernetInterface.h" #include "DIAG.h" #include "CommandDistributor.h" +#include "WiThrottle.h" #include "DCCTimer.h" EthernetInterface * EthernetInterface::singleton=NULL; @@ -178,6 +179,8 @@ void EthernetInterface::loop() if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket); } } + + WiThrottle::loop(outboundRing); // handle at most 1 outbound transmission int socketOut=outboundRing->read(); diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 3be3827c2..22a02ca8c 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -26,6 +26,7 @@ #include "DIAG.h" #include "RingStream.h" #include "CommandDistributor.h" +#include "WiThrottle.h" /* #include "soc/rtc_wdt.h" #include "esp_task_wdt.h" @@ -228,6 +229,8 @@ void WifiESP::loop() { } } // all clients + WiThrottle::loop(outboundRing); + // something to write out? clientId=outboundRing->peek(); if (clientId >= 0) { From 42c35a11e14fb8d0ed198d32661c1c8ea3f3de96 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Aug 2022 00:35:54 +0200 Subject: [PATCH 236/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f0d56e097..9ea001a59 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220805" +#define GITHUB_SHA "PORTX-HAL-20220806" From 17bdd2d724c63cf83ef56d3152751dce411dfd91 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 6 Aug 2022 16:53:14 +0800 Subject: [PATCH 237/870] Teensy build support, STM32F411RE first beta --- DCCTimer.h | 4 ++ DCCTimerSTM32.cpp | 118 +++++++++++++++++++++++++++++++++++++++++++++ DCCTimerTEENSY.cpp | 16 +++++- FSH.h | 8 +++ IO_MCP23008.h | 2 +- IO_PCF8574.h | 2 +- MotorDriver.cpp | 2 + MotorDriver.h | 16 +++++- defines.h | 64 ++++++++++++++++++++++-- platformio.ini | 57 +++++++++++++++++++++- 10 files changed, 280 insertions(+), 9 deletions(-) create mode 100644 DCCTimerSTM32.cpp diff --git a/DCCTimer.h b/DCCTimer.h index fa2b85448..34b2d9187 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -85,7 +85,11 @@ class DCCTimer { static int freeMemory(); static volatile int minimum_free_memory; static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle +#if defined(ARDUINO_ARCH_STM32) // TODO: PMA temporary hack - assumes 100Mhz F_CPU as STM32 can change frequency + static const long CLOCK_CYCLES=(100000000L / 1000000 * DCC_SIGNAL_TIME) >>1; +#else static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1; +#endif }; diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp new file mode 100644 index 000000000..4ca20c025 --- /dev/null +++ b/DCCTimerSTM32.cpp @@ -0,0 +1,118 @@ +/* + * © 2022 Paul M Antoine + * © 2021 Mike S + * © 2021 Harald Barth + * © 2021 Fred Decker + * © 2021 Chris Harlow + * © 2021 David Cutting + * All rights reserved. + * + * This file is part of Asbelos DCC 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 . + */ + +// ATTENTION: this file only compiles on a STM32 based boards +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef ARDUINO_ARCH_STM32 + +#include "FSH.h" //PMA temp debug +#include "DIAG.h" //PMA temp debug +#include "DCCTimer.h" + +// STM32 doesn't have Serial1 defined by default +HardwareSerial Serial1(PA10, PA15); // Rx=PA10, Tx=PA9 + +INTERRUPT_CALLBACK interruptHandler=0; +// Let's use STM32's timer #1 until disabused of this notion +HardwareTimer timer(TIM1); + +// Timer IRQ handler +void Timer1_Handler() { + interruptHandler(); +} + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + + timer.pause(); + timer.setPrescaleFactor(1); +// timer.setOverflow(CLOCK_CYCLES * 2); + timer.setOverflow(DCC_SIGNAL_TIME * 2, MICROSEC_FORMAT); + timer.attachInterrupt(Timer1_Handler); + timer.refresh(); + timer.resume(); + + interrupts(); +} + +bool DCCTimer::isPWMPin(byte pin) { + //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, + // there's no support yet for High Accuracy, so for now return false + // return digitalPinHasPWM(pin); + return false; +} + +void DCCTimer::setPWM(byte pin, bool high) { + // TODO: High Accuracy mode is not supported as yet, and may never need to be + (void) pin; + (void) high; +} + +void DCCTimer::clearPWM() { + return; +} + +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C; + volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040; +// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044; +// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048; + + volatile uint32_t m1 = *serno1; + volatile uint32_t m2 = *serno2; + mac[0] = m1 >> 8; + mac[1] = m1 >> 0; + mac[2] = m2 >> 24; + mac[3] = m2 >> 16; + mac[4] = m2 >> 8; + mac[5] = m2 >> 0; +} + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = freeMemory(); + interrupts(); + return retval; +} + +extern "C" char* sbrk(int incr); + +int DCCTimer::freeMemory() { + char top; + return (int)(&top - reinterpret_cast(sbrk(0))); +} + +void DCCTimer::reset() { + __disable_irq(); + NVIC_SystemReset(); + while(true) {}; +} + +#endif \ No newline at end of file diff --git a/DCCTimerTEENSY.cpp b/DCCTimerTEENSY.cpp index dc682df52..a29f000a3 100644 --- a/DCCTimerTEENSY.cpp +++ b/DCCTimerTEENSY.cpp @@ -88,8 +88,20 @@ void DCCTimer::getSimulatedMacAddress(byte mac[6]) { } #endif +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = freeMemory(); + interrupts(); + return retval; +} + +extern "C" char* sbrk(int incr); + #if !defined(__IMXRT1062__) -static inline int freeMemory() { +int DCCTimer::freeMemory() { char top; return &top - reinterpret_cast(sbrk(0)); } @@ -110,7 +122,7 @@ static inline int freeMemory() { #endif #endif -static inline int freeMemory() { +int DCCTimer::freeMemory() { extern unsigned long _ebss; extern unsigned long _sdata; extern unsigned long _estack; diff --git a/FSH.h b/FSH.h index c901fb931..f13e597fd 100644 --- a/FSH.h +++ b/FSH.h @@ -47,6 +47,14 @@ typedef char FSH; #define FLASH #define strlen_P strlen #define strcpy_P strcpy +#elif defined(ARDUINO_ARCH_STM32) +typedef __FlashStringHelper FSH; +#define GETFLASH(addr) pgm_read_byte_near(addr) +#define GETFLASHW(addr) pgm_read_word_near(addr) +#ifdef FLASH + #undef FLASH +#endif +#define FLASH PROGMEM #else typedef __FlashStringHelper FSH; #define GETFLASH(addr) pgm_read_byte_near(addr) diff --git a/IO_MCP23008.h b/IO_MCP23008.h index aa4679b96..a411117f5 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -31,7 +31,7 @@ class MCP23008 : public GPIOBase { private: // Constructor MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) { + : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); diff --git a/IO_PCF8574.h b/IO_PCF8574.h index 44eccadad..f8629842e 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -48,7 +48,7 @@ class PCF8574 : public GPIOBase { private: PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, 8), I2CAddress, interruptPin) + : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); } diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 0239d1c2d..620d996d3 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -268,6 +268,8 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res (void) type; // avoid compiler warning if diag not used above. #if defined(ARDUINO_ARCH_SAMD) PortGroup *port = digitalPinToPort(pin); +#elif defined(ARDUINO_ARCH_STM32) + GPIO_TypeDef *port = digitalPinToPort(pin); #else uint8_t port = digitalPinToPort(pin); #endif diff --git a/MotorDriver.h b/MotorDriver.h index 9e32ea02b..9e2ff4f9d 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -43,6 +43,20 @@ #if defined(ARDUINO_AVR_UNO) #define HAVE_PORTB(X) X #endif +#if defined(ARDUINO_ARCH_SAMD) +#define PORTA REG_PORT_OUT0 +#define HAVE_PORTA(X) X +#define PORTB REG_PORT_OUT1 +#define HAVE_PORTB(X) X +#endif +#if defined(ARDUINO_ARCH_STM32) +#define PORTA GPIOA->ODR +#define HAVE_PORTA(X) X +#define PORTB GPIOB->ODR +#define HAVE_PORTB(X) X +#define PORTC GPIOC->ODR +#define HAVE_PORTC(X) X +#endif // if macros not defined as pass-through we define // them here as someting that is valid as a @@ -63,7 +77,7 @@ #define UNUSED_PIN 127 // inside int8_t #endif -#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) +#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) typedef uint32_t portreg_t; #else typedef uint8_t portreg_t; diff --git a/defines.h b/defines.h index 23a7993a5..fa8dd170e 100644 --- a/defines.h +++ b/defines.h @@ -40,6 +40,7 @@ // figure out if we have enough memory for advanced features // so define HAS_ENOUGH_MEMORY until proved otherwise. #define HAS_ENOUGH_MEMORY +#undef USB_SERIAL // Teensy has this defined by default... #define USB_SERIAL Serial #if defined(ARDUINO_AVR_UNO) @@ -55,16 +56,62 @@ #elif defined(ARDUINO_ARCH_MEGAAVR) #define ARDUINO_TYPE "MEGAAVR" #undef HAS_ENOUGH_MEMORY -#elif defined(ARDUINO_TEENSY32) -#define ARDUINO_TYPE "TEENSY32" +#elif defined(ARDUINO_TEENSY31) +#define ARDUINO_TYPE "TEENSY3132" +#undef USB_SERIAL +#define USB_SERIAL SerialUSB +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +// Teensy support for native I2C is awaiting development +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif #elif defined(ARDUINO_TEENSY35) #define ARDUINO_TYPE "TEENSY35" +#undef USB_SERIAL +#define USB_SERIAL SerialUSB +// Teensy support for I2C is awaiting development +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +// Teensy support for native I2C is awaiting development +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif #elif defined(ARDUINO_TEENSY36) #define ARDUINO_TYPE "TEENSY36" +#undef USB_SERIAL +#define USB_SERIAL SerialUSB +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +// Teensy support for native I2C is awaiting development +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif #elif defined(ARDUINO_TEENSY40) #define ARDUINO_TYPE "TEENSY40" +#undef USB_SERIAL +#define USB_SERIAL SerialUSB +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +// Teensy support for native I2C is awaiting development +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif #elif defined(ARDUINO_TEENSY41) #define ARDUINO_TYPE "TEENSY41" +#undef USB_SERIAL +#define USB_SERIAL SerialUSB +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +// Teensy support for native I2C is awaiting development +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif #elif defined(ARDUINO_ARCH_ESP8266) #define ARDUINO_TYPE "ESP8266" #elif defined(ARDUINO_ARCH_ESP32) @@ -73,10 +120,21 @@ #define ARDUINO_TYPE "SAMD21" #undef USB_SERIAL #define USB_SERIAL SerialUSB -// SAMD support for I2C is awaiting development +// STM32 no EEPROM by default +#ifndef DISABLE_EEPROM + #define DISABLE_EEPROM +#endif +// SAMD support for native I2C is awaiting development +#ifndef I2C_NO_INTERRUPTS + #define I2C_NO_INTERRUPTS +#endif +#elif defined(ARDUINO_ARCH_STM32) +#define ARDUINO_TYPE "STM32" +// STM32 no EEPROM by default #ifndef DISABLE_EEPROM #define DISABLE_EEPROM #endif +// STM32 support for native I2C is awaiting development #ifndef I2C_NO_INTERRUPTS #define I2C_NO_INTERRUPTS #endif diff --git a/platformio.ini b/platformio.ini index 0a38cf8c6..43a4092f9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,6 +18,12 @@ default_envs = samd21-dev-usb samd21-zero-usb ESP32 + Nucleo-STM32F411RE + Teensy3.2 + Teensy3.5 + Teensy3.6 + Teensy4.0 + Teensy4.1 src_dir = . include_dir = . @@ -164,4 +170,53 @@ platform = espressif32 board = esp32dev framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 \ No newline at end of file +build_flags = -std=c++17 + +[env:Nucleo-STM32F411RE] +platform = ststm32 +board = nucleo_f411re +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +monitor_speed = 115200 +monitor_echo = yes + +[env:Teensy3.2] +platform = teensy +board = teensy31 +framework = arduino +build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy3.5] +platform = teensy +board = teensy35 +framework = arduino +build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy3.6] +platform = teensy +board = teensy36 +framework = arduino +build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy4.0] +platform = teensy +board = teensy40 +framework = arduino +build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy4.1] +platform = teensy +board = teensy41 +framework = arduino +build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = \ No newline at end of file From e7d8d320bdbb7cb933ce853124d6c5466f4d1fd7 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 6 Aug 2022 17:51:13 +0800 Subject: [PATCH 238/870] SAMD21 I2C native interrupt capable driver --- I2CManager.cpp | 7 +- I2CManager_NonBlocking.h | 43 +++++- I2CManager_SAMD.h | 273 +++++++++++++++++++++++---------------- defines.h | 6 +- 4 files changed, 213 insertions(+), 116 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index a3ea61195..a951a8772 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -1,5 +1,7 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2022 Paul M Antoine + * © 2021, Neil McKechnie + * All rights reserved. * * This file is part of CommandStation-EX * @@ -30,6 +32,9 @@ #elif defined(ARDUINO_ARCH_MEGAAVR) #include "I2CManager_NonBlocking.h" #include "I2CManager_Mega4809.h" // NanoEvery/UnoWifi +#elif defined(ARDUINO_ARCH_SAMD) +#include "I2CManager_NonBlocking.h" +#include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later #else #define I2C_USE_WIRE #include "I2CManager_Wire.h" // Other platforms diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 112e51e48..fbcb98ae3 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -1,5 +1,7 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2022 Paul M Antoine + * © 2021, Neil McKechnie + * All rights reserved. * * This file is part of CommandStation-EX * @@ -23,7 +25,46 @@ #include #include "I2CManager.h" #if defined(I2C_USE_INTERRUPTS) +// atomic.h isn't available on SAMD, and likely others too... +#if defined(__AVR__) #include +#elif defined(__arm__) +// Helper assembly language functions +static __inline__ uint8_t my_iSeiRetVal(void) +{ + __asm__ __volatile__ ("cpsie i" ::); + return 1; +} + +static __inline__ uint8_t my_iCliRetVal(void) +{ + __asm__ __volatile__ ("cpsid i" ::); + return 1; +} + +static __inline__ void my_iRestore(const uint32_t *__s) +{ + uint32_t res = *__s; + __asm__ __volatile__ ("MSR primask, %0" : : "r" (res) ); +} + +static __inline__ uint32_t my_iGetIReg( void ) +{ + uint32_t reg; + __asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) ); + return reg; +} +// Macros for atomic isolation +#define MY_ATOMIC_RESTORESTATE uint32_t _sa_saved \ + __attribute__((__cleanup__(my_iRestore))) = my_iGetIReg() + +#define ATOMIC() \ +for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ + _done; _done = 0 ) + +#define ATOMIC_BLOCK(x) ATOMIC() +#define ATOMIC_RESTORESTATE +#endif #else #define ATOMIC_BLOCK(x) #define ATOMIC_RESTORESTATE diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index aeabd6ca5..4dd27901a 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -1,5 +1,7 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2022 Paul M Antoine + * © 2021, Neil McKechnie + * All rights reserved. * * This file is part of CommandStation-EX * @@ -25,31 +27,61 @@ //#include //#include +#include +/*************************************************************************** + * Interrupt handler. + * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero + * compatible variants such as the Sparkfun SAMD21 Dev Breakout etc. + * Later we may wish to allow use of an alternate I2C bus, or more than one I2C + * bus on the SAMD architecture + ***************************************************************************/ #if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO) -// PMA - IRQ handler, based on SERCOM3 being used for I2C, as per Arduino Zero & Sparkfun SAMD21 -// TODO: test void SERCOM3_Handler() { I2CManagerClass::handleInterrupt(); } #endif +// Assume SERCOM3 for now - default I2C bus on Arduino Zero and variants of same +Sercom *s = SERCOM3; + /*************************************************************************** * Set I2C clock speed register. ***************************************************************************/ -void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { - 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; +void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { + + // Calculate a rise time appropriate to the requested bus speed + int t_rise; + if (i2cClockSpeed < 200000L) { + i2cClockSpeed = 100000L; + t_rise = 1000; + } else if (i2cClockSpeed < 800000L) { + i2cClockSpeed = 400000L; + t_rise = 300; + } else if (i2cClockSpeed < 1200000L) { + i2cClockSpeed = 1000000L; + t_rise = 120; + } else { + i2cClockSpeed = 100000L; + t_rise = 1000; } - // Set slowest speed ~= 500 bits/sec - TWBR = 255; - TWSR |= 0x03; + + // Disable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 0 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + + // Calculate baudrate - using a rise time appropriate for the speed + s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + + // Enable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 1 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + + // Setting bus idle mode and wait for sync + s->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + + return; } /*************************************************************************** @@ -57,16 +89,58 @@ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { ***************************************************************************/ void I2CManagerClass::I2C_init() { - // PMA - broadly we do the following - initialise the clock - initialise the NVIC - software reset the I2C for the sercom - set master mode - do we need smart mode and quick command?? - configure interrupt handlers - enable interrupts - set default baud rate - set SDA/SCL pins as outputs and enable pullups + //Setting clock + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM3_CORE) | // Generic Clock 0 (SERCOM3) + GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source + GCLK_CLKCTRL_CLKEN ; + + /* Wait for peripheral clock synchronization */ + while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY ); + + // Software reset the SERCOM + s->I2CM.CTRLA.bit.SWRST = 1; + + //Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0 + while(s->I2CM.CTRLA.bit.SWRST || s->I2CM.SYNCBUSY.bit.SWRST); + + // Set master mode and enable SCL Clock Stretch mode (stretch after ACK bit) + s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* | + SERCOM_I2CM_CTRLA_SCLSM*/ ; + + // Enable Smart mode and Quick Command + s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN | SERCOM_I2CM_CTRLB_QCEN; + +#if defined(I2C_USE_INTERRUPTS) + // Setting NVIC + NVIC_EnableIRQ(SERCOM3_IRQn); + NVIC_SetPriority (SERCOM3_IRQn, 0); /* set Priority */ + + // Enable all interrupts + s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR; +#endif + + // Calculate baudrate and set default rate for now + s->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * I2C_FREQ) - 7 / (2 * 1000); + + // Enable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 1 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + + // Setting bus idle mode and wait for sync + s->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + + // Set SDA/SCL pins as outputs and enable pullups, at present we assume these are + // the default ones for SERCOM3 (see assumption above) + pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType); + pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType); + + // Enable the SCL and SDA pins on the sercom: includes increased driver strength, + // pull-up resistors and pin multiplexer + PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg = + PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; + PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg = + PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; } /*************************************************************************** @@ -75,28 +149,36 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + // We may have initiated a stop bit before this without waiting for it. // Wait for stop bit to be sent before sending start. - while (TWCR & (1<I2CM.STATUS.bit.BUSSTATE == 0x2); + + // If anything to send, initiate write. Otherwise initiate read. + if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) + { + // Send start and address with read/write flag or'd in + s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + } + else { + // Wait while the I2C bus is BUSY + while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + } } /*************************************************************************** * Initiate a stop bit for transmission (does not interrupt) ***************************************************************************/ void I2CManagerClass::I2C_sendStop() { - TWDR = 0xff; // Default condition = SDA released - TWCR = (1<I2CM.CTRLB.bit.CMD = 3; // Stop condition } /*************************************************************************** * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - // disable TWI I2C_sendStop(); - while (TWCR & (1<writeBuffer + (txCount++)); - else - TWDR = currentRequest->writeBuffer[txCount++]; - bytesToSend--; - TWCR = (1< 0) { - currentRequest->readBuffer[rxCount++] = TWDR; - bytesToReceive--; - } - /* fallthrough */ - case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received - if (bytesToReceive <= 1) { - TWCR = (1< 0) { - currentRequest->readBuffer[rxCount++] = TWDR; - bytesToReceive--; - } - TWCR = (1<i2cAddress << 1) | 1; // SLA+R - else - TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W - TWCR = (1<I2CM.STATUS.bit.ARBLOST) { + // Arbitration lost, restart + I2C_sendStart(); // Reinitiate request + } else if (s->I2CM.STATUS.bit.BUSERR) { + // Bus error + state = I2C_STATUS_BUS_ERROR; + } else if (s->I2CM.INTFLAG.bit.MB) { + // Master write completed + if (s->I2CM.STATUS.bit.RXNACK) { + // Nacked, send stop. + I2C_sendStop(); state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; - break; - case TWI_ARB_LOST: // Arbitration lost - // Restart transaction from start. - I2C_sendStart(); - break; - case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition - default: - TWDR = 0xff; // Default condition = SDA released - TWCR = (1<operation == OPERATION_SEND_P) + s->I2CM.DATA.bit.DATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); + else + s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + bytesToSend--; + } else if (bytesToReceive) { + // Last sent byte acked and no more to send. Send repeated start, address and read bit. + s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + } else { + // No more data to send/receive. Initiate a STOP condition. + I2C_sendStop(); + state = I2C_STATUS_OK; // Done + } + } else if (s->I2CM.INTFLAG.bit.SB) { + // Master read completed without errors + if (bytesToReceive) { + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive--; + } else { + // Buffer full, issue nack/stop + s->I2CM.CTRLB.bit.ACKACT = 1; + I2C_sendStop(); + state = I2C_STATUS_OK; + } + if (bytesToReceive) { + // PMA - I think Smart Mode means we have nothing to do... + // More bytes to receive, issue ack and start another read + } + else + { + // Transaction finished, issue NACK and STOP. + s->I2CM.CTRLB.bit.ACKACT = 1; + I2C_sendStop(); + state = I2C_STATUS_OK; + } } } -#if defined(I2C_USE_INTERRUPTS) -ISR(TWI_vect) { - I2CManagerClass::handleInterrupt(); -} -#endif - -#endif /* I2CMANAGER_AVR_H */ +#endif /* I2CMANAGER_SAMD_H */ diff --git a/defines.h b/defines.h index fa8dd170e..ba96b4edf 100644 --- a/defines.h +++ b/defines.h @@ -120,14 +120,10 @@ #define ARDUINO_TYPE "SAMD21" #undef USB_SERIAL #define USB_SERIAL SerialUSB -// STM32 no EEPROM by default +// SAMD no EEPROM by default #ifndef DISABLE_EEPROM #define DISABLE_EEPROM #endif -// SAMD support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS -#endif #elif defined(ARDUINO_ARCH_STM32) #define ARDUINO_TYPE "STM32" // STM32 no EEPROM by default From af75297a2324e240032c327540b5cbd17e7d0e28 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 6 Aug 2022 18:06:00 +0800 Subject: [PATCH 239/870] I2CManager support for 1Mhz+ I2C speeds --- I2CManager.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/I2CManager.h b/I2CManager.h index 49f3e8f2e..e99b4f30c 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -230,7 +230,11 @@ class I2CManagerClass { private: bool _beginCompleted = false; bool _clockSpeedFixed = false; +#if defined(__arm__) + uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32 +#else uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. +#endif // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); From 803db81c8f2d20d91be0b84b930fe0048b86ca0c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Aug 2022 19:07:35 +0200 Subject: [PATCH 240/870] try to recover from ringbuffer read problem --- WifiESP32.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 22a02ca8c..917785e2f 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -236,6 +236,17 @@ void WifiESP::loop() { if (clientId >= 0) { if ((unsigned int)clientId > clients.size()) { // something is wrong with the ringbuffer position + // or client has disconnected + outboundRing->info(); + // try to recover by reading out to nowhere + int count=outboundRing->count(); + for(int i=0;iread(); + if (c < 0) { + DIAG(F("Ringread fail at %d"),i); + break; + } + } outboundRing->info(); } else { // we have data to send in outboundRing From 64a6412ce2401c792d5bae09aa01058fa5bac19d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Aug 2022 19:08:26 +0200 Subject: [PATCH 241/870] hearbeat 1s too agressive --- WiThrottle.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index e2e7d959d..4a9c59c3f 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -219,7 +219,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { } break; case 'N': // Heartbeat (2), only send if connection completed by 'HU' message - StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : 1); // return timeout value + StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARBEAT_SECONDS/2); // return timeout value break; case 'M': // multithrottle multithrottle(stream, cmd); @@ -241,8 +241,8 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { #endif - // set heartbeat to 1 second because we need to sync the metadata - StringFormatter::send(stream,F("*1\n")); + // set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!) + StringFormatter::send(stream,F("*%d\n"), HEARBEAT_SECONDS/2); initSent = true; } break; From 76137ff24c1ba5b67ba73ea06bce96d9545c1cf9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 6 Aug 2022 19:23:52 +0200 Subject: [PATCH 242/870] send some answer to loco enquire even if no locos are found --- WiThrottle.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 4a9c59c3f..1581b0cae 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -219,7 +219,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { } break; case 'N': // Heartbeat (2), only send if connection completed by 'HU' message - StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARBEAT_SECONDS/2); // return timeout value + StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value break; case 'M': // multithrottle multithrottle(stream, cmd); @@ -242,7 +242,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { // set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!) - StringFormatter::send(stream,F("*%d\n"), HEARBEAT_SECONDS/2); + StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2); initSent = true; } break; @@ -417,9 +417,13 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, case 'q': if (aval[1]=='V' || aval[1]=='R' ) { //qV or qR // just flag the loco for broadcast and it will happen. - LOOPLOCOS(throttleChar, cab) { + bool foundone = false; + LOOPLOCOS(throttleChar, cab) { + foundone = true; myLocos[loco].broadcastPending=true; - } + } + if (!foundone) + StringFormatter::send(stream,F("HMCS loco list empty\n")); } break; case 'R': From c115c441e41f2a797eceab0dadaf6cfab03c17d0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Aug 2022 01:24:41 +0200 Subject: [PATCH 243/870] ESP32 introduce NetworkClient class to hold state associated to WiFiClient --- GITHUB_SHA.h | 2 +- WifiESP32.cpp | 103 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 9ea001a59..16bd1760f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220806" +#define GITHUB_SHA "PORTX-HAL-20220807" diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 917785e2f..3f9f7e5c7 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -67,7 +67,25 @@ void disableCoreWDT(byte core){ } */ -static std::vector clients; // a list to hold all clients +class NetworkClient { +public: + NetworkClient(WiFiClient c) { + wifi = c; + }; + bool ok() { + return (inUse && wifi.connected()); + }; + bool recycle(WiFiClient c) { + if (inUse == true) return false; + wifi = c; + inUse = true; + return true; + }; + WiFiClient wifi; + bool inUse = true; +}; + +static std::vector clients; // a list to hold all clients static WiFiServer *server = NULL; static RingStream *outboundRing = new RingStream(2048); static bool APmode = false; @@ -197,29 +215,40 @@ void WifiESP::loop() { // loop over all clients and remove inactive for (clientId=0; clientIdhasClient()) { WiFiClient client; while (client = server->available()) { - clients.push_back(client); - DIAG(F("New client %s"), client.remoteIP().toString().c_str()); + for (clientId=0; clientId=clients.size()) { + NetworkClient nc(client); + clients.push_back(nc); + DIAG(F("New client %d, %s"), clientId, client.remoteIP().toString().c_str()); + } } } // loop over all connected clients for (clientId=0; clientId 0) { + if ((len = clients[clientId].wifi.available()) > 0) { // read data from client byte cmd[len+1]; for(int i=0; imark(clientId); @@ -238,35 +267,41 @@ void WifiESP::loop() { // something is wrong with the ringbuffer position // or client has disconnected outboundRing->info(); - // try to recover by reading out to nowhere - int count=outboundRing->count(); - for(int i=0;iread(); - if (c < 0) { - DIAG(F("Ringread fail at %d"),i); - break; + if ((unsigned int)clientId < 8) { + // try to recover by reading out to nowhere + int count=outboundRing->count(); + for(int i=0;iread(); + if (c < 0) { + DIAG(F("Ringread fail at %d"),i); + break; + } } + outboundRing->info(); } - outboundRing->info(); + DIAG(F("Ring beyond rescue")); } else { - // we have data to send in outboundRing - if(clients[clientId].connected()) { - outboundRing->read(); // read over peek() - int count=outboundRing->count(); - { - char buffer[count+1]; - for(int i=0;iread(); - if (c >= 0) - buffer[i] = (char)c; - else { - DIAG(F("Ringread fail at %d"),i); - break; - } + // We have data to send in outboundRing + // and we have a valid clientId. + // First read it out to buffer + // and then look if it can be sent because + // we can not leave it in the ring for ever + outboundRing->read(); // read over peek() + int count=outboundRing->count(); + { + char buffer[count+1]; + for(int i=0;iread(); + if (c >= 0) + buffer[i] = (char)c; + else { + DIAG(F("Ringread fail at %d"),i); + break; } - buffer[count]=0; - clients[clientId].write(buffer,count); } + buffer[count]=0; + if(clients[clientId].ok()) + clients[clientId].wifi.write(buffer,count); } } } From caca265529d51d3cd68e7957c589b552da8ef943 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Aug 2022 20:05:40 +0200 Subject: [PATCH 244/870] alternate implementation of DCC::issueReminders() --- DCC.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/DCC.cpp b/DCC.cpp index 3d2d0563d..852430800 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -591,6 +591,7 @@ void DCC::issueReminders() { if ( DCCWaveform::mainTrack.getPacketPending()) return; // This loop searches for a loco in the speed table starting at nextLoco and cycling back around + /* for (int reg=0;reg=MAX_LOCOS) slot-=MAX_LOCOS; @@ -601,6 +602,17 @@ void DCC::issueReminders() { return; } } + */ + for (int reg=nextLoco;reg 0) { + // have found the next loco to remind + // issueReminder will return true if this loco is completed (ie speed and functions) + if (issueReminder(slot)) + nextLoco=(slot+1)%MAX_LOCOS; + return; + } + } } bool DCC::issueReminder(int reg) { From 5182bb171dad5f05ddfad26c9cf4e32962b21dc9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Aug 2022 20:11:31 +0200 Subject: [PATCH 245/870] ESP32 do not recycle client handles for now --- WifiESP32.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 3f9f7e5c7..c1383d78f 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -76,7 +76,14 @@ class NetworkClient { return (inUse && wifi.connected()); }; bool recycle(WiFiClient c) { + if (inUse == true) return false; + + // return false here until we have + // implemented a LRU timer + // if (LRU too recent) return false; + return false; + wifi = c; inUse = true; return true; @@ -111,9 +118,9 @@ bool WifiESP::setup(const char *SSid, // disableCoreWDT(0); // clean start + WiFi.mode(WIFI_STA); WiFi.disconnect(true); - //WiFi.useStaticBuffers(true); - //WiFi.setTxPower(WIFI_POWER_8_5dBm); + //WiFi.useStaticBuffers(true); // does not help either const char *yourNetwork = "Your network "; if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0) From 186fd8adeea1a5eaadf87ce2a1af055991ca8877 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Aug 2022 20:12:42 +0200 Subject: [PATCH 246/870] implement Withrottle client handles to remember clients over reconnects --- CommandDistributor.cpp | 2 +- WiThrottle.cpp | 57 +++++++++++++++++++++++++++++++++++++++++- WiThrottle.h | 5 +++- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index f8dc25913..12a78f376 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -82,7 +82,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream } void CommandDistributor::forget(byte clientId) { - if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); + // keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); clients[clientId]=NONE_TYPE; } #endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 1581b0cae..32968a489 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -63,6 +63,58 @@ WiThrottle * WiThrottle::firstThrottle=NULL; +static uint8_t xstrcmp(const char *s1, const char *s2) { + while(*s1 != '\0' && *s2 != '\0') { + if (*s1 != *s2) return 1; + s1++; + s2++; + } + if (*s1 == '\0' && *s2 == '\0') + return 0; + return 1; +} + +void WiThrottle::findUniqThrottle(int id, char *u) { + WiThrottle *wtmyid = NULL; + WiThrottle *wtmyuniq = NULL; + u[16] = '\0'; + for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ + //DIAG(F("looking at %d as %s"),wt->clientid, wt->uniq); + if (wtmyid == NULL && wt->clientid == id) + wtmyid = wt; + if (wtmyuniq == NULL && xstrcmp(u, wt->uniq) == 0) + wtmyuniq = wt; + } + if (wtmyid == NULL) { // should not happen + DIAG(F("Did not find my own wiThrottle handle")); + return; + } + if (wtmyid == wtmyuniq) { // all well, just return; + return; + } + if (wtmyuniq == NULL) { // register uniq in the found id + strncpy(wtmyid->uniq, u, 16); + wtmyid->uniq[16] = '\0'; + if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq); + return; + } + // do the copy (all other options above) + for(int n=0; n < MAX_MY_LOCO; n++) + wtmyid->myLocos[n] = wtmyuniq->myLocos[n]; + wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable; + wtmyid->heartBeat = wtmyuniq->heartBeat; + wtmyid->initSent = wtmyuniq->initSent; + wtmyid->exRailSent = wtmyuniq->exRailSent; + wtmyid->mostRecentCab = wtmyuniq->mostRecentCab; + wtmyid->turnoutListHash = wtmyuniq->turnoutListHash; + wtmyid->lastPowerState = wtmyuniq->lastPowerState; + strncpy(wtmyid->uniq, u, 16); + wtmyid->uniq[16] = '\0'; + if (Diag::WITHROTTLE) + DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq); + forget(wtmyuniq->clientid); // do not use wtmyid after this +} + WiThrottle* WiThrottle::getThrottle( int wifiClient) { for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) if (wt->clientid==wifiClient) return wt; @@ -105,6 +157,7 @@ WiThrottle::WiThrottle( int wificlientid) { } WiThrottle::~WiThrottle() { + if (Diag::WITHROTTLE) DIAG(F("Deleting WiThrottle client %d"),this->clientid); if (firstThrottle== this) { firstThrottle=this->nextThrottle; return; @@ -226,6 +279,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { break; case 'H': // send initial connection info after receiving "HU" message if (cmd[1] == 'U') { + WiThrottle::findUniqThrottle(clientid, (char *)cmd+2); StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n")); StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); @@ -489,7 +543,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) { DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop } } - delete this; + //haba no, not necessary the only throttle and it may come back + //delete this; return; } diff --git a/WiThrottle.h b/WiThrottle.h index 253787d54..3fa973a14 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -38,7 +38,8 @@ class WiThrottle { static WiThrottle* getThrottle( int wifiClient); static void markForBroadcast(int cab); static void forget(byte clientId); - + static void findUniqThrottle(int id, char *u); + private: WiThrottle( int wifiClientId); ~WiThrottle(); @@ -55,6 +56,7 @@ class WiThrottle { bool areYouUsingThrottle(int cab); WiThrottle* nextThrottle; int clientid; + char uniq[17] = ""; MYLOCO myLocos[MAX_MY_LOCO]; bool heartBeatEnable; @@ -64,6 +66,7 @@ class WiThrottle { uint16_t mostRecentCab; int turnoutListHash; // used to check for changes to turnout list bool lastPowerState; // last power state sent to this client + int DCCToWiTSpeed(int DCCSpeed); int WiTToDCCSpeed(int WiTSpeed); void multithrottle(RingStream * stream, byte * cmd); From 966b9594efb53a6a1ffd888df3d0e475c8589817 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 Aug 2022 20:14:26 +0200 Subject: [PATCH 247/870] version (milestone marker) --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 16bd1760f..c9cbede59 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220807" +#define GITHUB_SHA "PORTX-HAL-20220807-1" From f56e3bec9e8f850aa20c8dd70eb7df09904a2a25 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 8 Aug 2022 13:00:53 +0200 Subject: [PATCH 248/870] eliminate wrong ringread due to peek (eliminated peek as well) - new version --- GITHUB_SHA.h | 2 +- WifiESP32.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c9cbede59..924cebb2a 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220807-1" +#define GITHUB_SHA "PORTX-HAL-20220808" diff --git a/WifiESP32.cpp b/WifiESP32.cpp index c1383d78f..12820dc72 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -268,7 +268,7 @@ void WifiESP::loop() { WiThrottle::loop(outboundRing); // something to write out? - clientId=outboundRing->peek(); + clientId=outboundRing->read(); if (clientId >= 0) { if ((unsigned int)clientId > clients.size()) { // something is wrong with the ringbuffer position @@ -280,20 +280,20 @@ void WifiESP::loop() { for(int i=0;iread(); if (c < 0) { - DIAG(F("Ringread fail at %d"),i); + DIAG(F("Ringread fail in discarding data for client %d at pos %d"),clientId, i); break; } } outboundRing->info(); + } else { + DIAG(F("No clientId where expected: Ring beyond rescue")); } - DIAG(F("Ring beyond rescue")); } else { // We have data to send in outboundRing // and we have a valid clientId. // First read it out to buffer // and then look if it can be sent because // we can not leave it in the ring for ever - outboundRing->read(); // read over peek() int count=outboundRing->count(); { char buffer[count+1]; From c9612984e865b61c69b72fbc621e08d33631e4b9 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 9 Aug 2022 12:28:15 +0800 Subject: [PATCH 249/870] STM32F411 fix to DCC period --- DCCTimerSTM32.cpp | 2 +- platformio.ini | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 4ca20c025..229baec5b 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -51,7 +51,7 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { timer.pause(); timer.setPrescaleFactor(1); // timer.setOverflow(CLOCK_CYCLES * 2); - timer.setOverflow(DCC_SIGNAL_TIME * 2, MICROSEC_FORMAT); + timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); timer.attachInterrupt(Timer1_Handler); timer.refresh(); timer.resume(); diff --git a/platformio.ini b/platformio.ini index 43a4092f9..1a6356d92 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,7 +37,7 @@ framework = arduino upload_protocol = sam-ba lib_deps = ${env.lib_deps} monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = -std=c++17 [env:samd21-zero-usb] @@ -47,7 +47,7 @@ framework = arduino upload_protocol = sam-ba lib_deps = ${env.lib_deps} monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = -std=c++17 [env:samc21-firebox] @@ -59,7 +59,7 @@ lib_deps = ${env.lib_deps} SparkFun External EEPROM Arduino Library monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = -std=c++17 [env:mega2560-debug] @@ -71,7 +71,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = -DDIAG_IO -DDIAG_LOOPTIMES [env:mega2560-no-HAL] @@ -83,7 +83,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = -DIO_NO_HAL [env:mega2560-I2C-wire] @@ -95,7 +95,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = -DI2C_USE_WIRE [env:mega2560] @@ -107,7 +107,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes [env:mega328] platform = atmelavr @@ -118,7 +118,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes [env:unowifiR2] platform = atmelmegaavr @@ -129,7 +129,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200" [env:nanoevery] @@ -141,7 +141,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes upload_speed = 19200 build_flags = -DDIAG_IO @@ -154,7 +154,7 @@ lib_deps = arduino-libraries/Ethernet SPI monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes [env:nano] platform = atmelavr @@ -163,7 +163,7 @@ board_upload.maximum_size = 32256 framework = arduino lib_deps = ${env.lib_deps} monitor_speed = 115200 -monitor_flags = --echo +monitor_echo = yes [env:ESP32] platform = espressif32 From db032d567df1bc6e8e56cb642ff201cab1e1e91a Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 9 Aug 2022 14:57:33 +0800 Subject: [PATCH 250/870] Update TEENSY copyright --- DCCTimerTEENSY.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DCCTimerTEENSY.cpp b/DCCTimerTEENSY.cpp index a29f000a3..a8f97f11f 100644 --- a/DCCTimerTEENSY.cpp +++ b/DCCTimerTEENSY.cpp @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Mike S * © 2021 Harald Barth * © 2021 Fred Decker From 84e44df47ceac5f5562ae6dc851744a6c7c71ad8 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 9 Aug 2022 15:03:22 +0800 Subject: [PATCH 251/870] Copyright addition --- FSH.h | 1 + I2CManager.h | 1 + IO_MCP23008.h | 1 + IO_PCF8574.h | 1 + 4 files changed, 4 insertions(+) diff --git a/FSH.h b/FSH.h index f13e597fd..850311ef4 100644 --- a/FSH.h +++ b/FSH.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021 Neil McKechnie * © 2021 Harald Barth * © 2021 Fred Decker diff --git a/I2CManager.h b/I2CManager.h index e99b4f30c..7df08033c 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX diff --git a/IO_MCP23008.h b/IO_MCP23008.h index a411117f5..bf4d521a1 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API diff --git a/IO_PCF8574.h b/IO_PCF8574.h index f8629842e..beeeb7ce9 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M Antoine * © 2021, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API From 76c56081815c6fb63a2d1e0af37af4aa01359b95 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 9 Aug 2022 13:12:04 +0200 Subject: [PATCH 252/870] Protect port registers from change during interrupt code in differnet way --- MotorDriver.cpp | 54 +++++++++++++++++++++++------------------------- MotorDriver.h | 11 ++++++++-- TrackManager.cpp | 4 ++-- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 620d996d3..be5e304bb 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -148,11 +148,17 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { + noInterrupts(); IODevice::write(powerPin,HIGH); + interrupts(); if (isProgTrack) DCCWaveform::progTrack.clearResets(); } - else IODevice::write(powerPin,LOW); + else { + noInterrupts(); + IODevice::write(powerPin,LOW); + interrupts(); + } powerMode=mode; } @@ -164,10 +170,14 @@ void MotorDriver::setPower(POWERMODE mode) { // (HIGH == release brake) and setBrake does // compensate for that. // -void MotorDriver::setBrake(bool on) { +void MotorDriver::setBrake(bool on, bool interruptContext) { if (brakePin == UNUSED_PIN) return; - if (on ^ invertBrake) setHIGH(fastBrakePin); - else setLOW(fastBrakePin); + if (!interruptContext) {noInterrupts();} + if (on ^ invertBrake) + setHIGH(fastBrakePin); + else + setLOW(fastBrakePin); + if (!interruptContext) {interrupts();} } bool MotorDriver::canMeasureCurrent() { @@ -214,30 +224,18 @@ void MotorDriver::setDCSignal(byte speedcode) { if (invertBrake) brake=255-brake; analogWrite(brakePin,brake); - // as the port registers can be shadowed to get syncronized DCC signals - // we need to take care of that and we have to turn off interrupts during - // that time as otherwise setDCCSignal() which is called from interrupt - // contect can undo whatever we do here. - if (fastSignalPin.shadowinout != NULL) { - if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { - noInterrupts(); - HAVE_PORTA(shadowPORTA=PORTA); - setSignal(tDir); - HAVE_PORTA(PORTA=shadowPORTA); - interrupts(); - } else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) { - noInterrupts(); - HAVE_PORTB(shadowPORTB=PORTB); - setSignal(tDir); - HAVE_PORTB(PORTB=shadowPORTB); - interrupts(); - } else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) { - noInterrupts(); - HAVE_PORTC(shadowPORTC=PORTC); - setSignal(tDir); - HAVE_PORTC(PORTC=shadowPORTC); - interrupts(); - } + if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { + HAVE_PORTA(shadowPORTA=PORTA); + setSignal(tDir); + HAVE_PORTA(PORTA=shadowPORTA); + } else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) { + HAVE_PORTB(shadowPORTB=PORTB); + setSignal(tDir); + HAVE_PORTB(PORTB=shadowPORTB); + } else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) { + HAVE_PORTC(shadowPORTC=PORTC); + setSignal(tDir); + HAVE_PORTC(PORTC=shadowPORTC); } else { setSignal(tDir); } diff --git a/MotorDriver.h b/MotorDriver.h index 9e2ff4f9d..2138a4058 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -105,7 +105,14 @@ class MotorDriver { byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); virtual void setPower( POWERMODE mode); virtual POWERMODE getPower() { return powerMode;} - __attribute__((always_inline)) inline void setSignal( bool high) { + // as the port registers can be shadowed to get syncronized DCC signals + // we need to take care of that and we have to turn off interrupts if + // we setSignal() or setBrake() or setPower() during that time as + // otherwise the call from interrupt context can undo whatever we do + // from outside interrupt + virtual void setBrake( bool on, bool interruptContext=false); + __attribute__((always_inline)) inline void setSignal( bool high, bool interruptContext=false) { + if (!interruptContext) {noInterrupts();} if (trackPWM) { DCCTimer::setPWM(signalPin,high); } @@ -119,6 +126,7 @@ class MotorDriver { if (dualSignal) setHIGH(fastSignalPin2); } } + if (!interruptContext) {interrupts();} }; inline void enableSignal(bool on) { if (on) @@ -127,7 +135,6 @@ class MotorDriver { pinMode(signalPin, INPUT); }; inline byte getSignalPin() { return signalPin; }; - virtual void setBrake( bool on); virtual void setDCSignal(byte speedByte); virtual int getCurrentRaw(); virtual int getCurrentRawInInterrupt(); diff --git a/TrackManager.cpp b/TrackManager.cpp index ba3f8c019..6c4da7b7c 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -92,7 +92,7 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTA(shadowPORTA=PORTA); HAVE_PORTB(shadowPORTB=PORTB); HAVE_PORTC(shadowPORTC=PORTC); - APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); + APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on, true)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); HAVE_PORTC(PORTC=shadowPORTC); @@ -110,7 +110,7 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTA(shadowPORTA=PORTA); HAVE_PORTB(shadowPORTB=PORTB); HAVE_PORTC(shadowPORTC=PORTC); - APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); + APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on, true)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); HAVE_PORTC(PORTC=shadowPORTC); From ecda69ba323d5a2b5a02dce13f6435147129e259 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 9 Aug 2022 15:25:29 +0200 Subject: [PATCH 253/870] make sending loop ringbuffer to ESP32 Wifi more simple --- WifiESP32.cpp | 58 +++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 12820dc72..a91372ffd 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -270,45 +270,29 @@ void WifiESP::loop() { // something to write out? clientId=outboundRing->read(); if (clientId >= 0) { - if ((unsigned int)clientId > clients.size()) { - // something is wrong with the ringbuffer position - // or client has disconnected - outboundRing->info(); - if ((unsigned int)clientId < 8) { - // try to recover by reading out to nowhere - int count=outboundRing->count(); - for(int i=0;iread(); - if (c < 0) { - DIAG(F("Ringread fail in discarding data for client %d at pos %d"),clientId, i); - break; - } + // We have data to send in outboundRing + // and we have a valid clientId. + // First read it out to buffer + // and then look if it can be sent because + // we can not leave it in the ring for ever + int count=outboundRing->count(); + { + char buffer[count+1]; // one extra for '\0' + for(int i=0;iread(); + if (c >= 0) // Panic check, should never be false + buffer[i] = (char)c; + else { + DIAG(F("Ringread fail at %d"),i); + break; } - outboundRing->info(); - } else { - DIAG(F("No clientId where expected: Ring beyond rescue")); } - } else { - // We have data to send in outboundRing - // and we have a valid clientId. - // First read it out to buffer - // and then look if it can be sent because - // we can not leave it in the ring for ever - int count=outboundRing->count(); - { - char buffer[count+1]; - for(int i=0;iread(); - if (c >= 0) - buffer[i] = (char)c; - else { - DIAG(F("Ringread fail at %d"),i); - break; - } - } - buffer[count]=0; - if(clients[clientId].ok()) - clients[clientId].wifi.write(buffer,count); + // buffer filled, end with '\0' so we can use it as C string + buffer[count]='\0'; + if((unsigned int)clientId <= clients.size() && clients[clientId].ok()) { + clients[clientId].wifi.write(buffer,count); + } else { + DIAG(F("Unsent(%d): %s"), clientId, buffer); } } } From e1fd6e9414e9b9d0ede15d9f8692fcd7ad36eaf2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 9 Aug 2022 16:26:48 +0200 Subject: [PATCH 254/870] disable ESP32 code that is not used --- DCCTimerESP.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index 9780a4d50..442c7baf9 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -87,6 +87,8 @@ INTERRUPT_CALLBACK interruptHandler=0; portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + // This should not be called on ESP32 so disable it + return; interruptHandler = callback; hw_timer_t *timer = NULL; timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2 From c1993fba8741d9ace4f47364ed22729a2562fbc5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 10 Aug 2022 00:14:28 +0200 Subject: [PATCH 255/870] make DC mode portable to ESP32 --- MotorDriver.h | 13 ++++++++++++- MotorDrivers.h | 4 ++-- TrackManager.cpp | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 2138a4058..7a155ae67 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -136,16 +136,27 @@ class MotorDriver { }; inline byte getSignalPin() { return signalPin; }; virtual void setDCSignal(byte speedByte); + inline void detachDCSignal() { +#ifndef ARDUINO_ARCH_ESP32 + setDCSignal(128); +#else + ledcDetachPin(brakePin); +#endif + }; virtual int getCurrentRaw(); virtual int getCurrentRawInInterrupt(); virtual unsigned int raw2mA( int raw); virtual unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { +#ifdef ARDUINO_ARCH_ESP32 + return true; +#else #ifdef digitalPinToTimer return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin))); #else return (brakePin<14 && brakePin >1); -#endif +#endif //digitalPinToTimer +#endif //ESP32 } inline int getRawCurrentTripValue() { return rawCurrentTripValue; diff --git a/MotorDrivers.h b/MotorDrivers.h index 2f51b4a0e..3e3464b10 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -56,8 +56,8 @@ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) #elif defined(ARDUINO_ARCH_ESP32) #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, UNUSED_PIN, 36/*A4*/, 0.57, 2000, UNUSED_PIN), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, UNUSED_PIN, 39/*A5*/, 0.57, 2000, UNUSED_PIN) + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.57, 2000, UNUSED_PIN), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.57, 2000, UNUSED_PIN) #else #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ diff --git a/TrackManager.cpp b/TrackManager.cpp index 6c4da7b7c..772ddc5d1 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -170,7 +170,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr else { // DCC tracks need to have set the PWM to zero or they will not work. // 128 is speed=0 and dir=0 and then loosen brake. - track[trackToSet]->setDCSignal(128); + track[trackToSet]->detachDCSignal(); track[trackToSet]->setBrake(false); } From a985356f0cdc99d0d8d1705bf5dad092b0ee274b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 10 Aug 2022 00:51:17 +0200 Subject: [PATCH 256/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 924cebb2a..e7183e38b 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220808" +#define GITHUB_SHA "PORTX-HAL-20220809" From 915de96e80ae44aad9e65d1311d8ed6f94ce32b2 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 10 Aug 2022 10:45:33 +0800 Subject: [PATCH 257/870] DC mode portability for ARM (SAMD21/STM32) --- MotorDriver.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 7a155ae67..03b50410e 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -137,10 +137,12 @@ class MotorDriver { inline byte getSignalPin() { return signalPin; }; virtual void setDCSignal(byte speedByte); inline void detachDCSignal() { -#ifndef ARDUINO_ARCH_ESP32 - setDCSignal(128); -#else +#if defined(__arm__) + pinMode(brakePin, OUTPUT); +#elif defined(ARDUINO_ARCH_ESP32) ledcDetachPin(brakePin); +#else + setDCSignal(128); #endif }; virtual int getCurrentRaw(); @@ -148,7 +150,8 @@ class MotorDriver { virtual unsigned int raw2mA( int raw); virtual unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__) + // TODO: on ARM we can use digitalPinHasPWM, and may wish/need to return true; #else #ifdef digitalPinToTimer @@ -156,7 +159,7 @@ class MotorDriver { #else return (brakePin<14 && brakePin >1); #endif //digitalPinToTimer -#endif //ESP32 +#endif //ESP32/ARM } inline int getRawCurrentTripValue() { return rawCurrentTripValue; From da8b189b43738ca7b1f33286a4004260222bce72 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 13 Aug 2022 01:52:44 +0200 Subject: [PATCH 258/870] protect from NULL deref if running without prog track --- DCCWaveform.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DCCWaveform.h b/DCCWaveform.h index 108a63d76..1dad1b2c6 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -59,10 +59,12 @@ class DCCWaveform { #else // extrafudge is added when we know that the resets will first come extrafudge packets in the future inline void clearResets(byte extrafudge=0) { + if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return; resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); resetPacketBase += extrafudge; }; inline byte getResets() { + if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return 0; uint32_t packetcount = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic. From 059fd1b193d95e8960fd63a29ce03a2600385506 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 13 Aug 2022 09:12:53 +0200 Subject: [PATCH 259/870] Send inverted signal (DCC and DC mode) on ESP32 if signalPin2 defined in MotorDriver --- DCCRMT.cpp | 14 ++++++++++---- DCCRMT.h | 6 ++++-- DCCWaveform.cpp | 18 ++++++++++-------- MotorDriver.h | 12 +++++++++++- TrackManager.cpp | 11 +++++++++-- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index cb28b1034..ac97d0d23 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -69,7 +69,7 @@ void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { if (tt) tt->RMTinterrupt(); } -RMTChannel::RMTChannel(byte pin, bool isMain) { +RMTChannel::RMTChannel(pinpair pins, bool isMain) { byte ch; byte plen; if (isMain) { @@ -119,13 +119,14 @@ RMTChannel::RMTChannel(byte pin, bool isMain) { config.rmt_mode = RMT_MODE_TX; config.channel = channel = (rmt_channel_t)ch; config.clk_div = RMT_CLOCK_DIVIDER; - config.gpio_num = (gpio_num_t)pin; + config.gpio_num = (gpio_num_t)pins.pin; config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion) // number of bits needed is 22preamble + start + // 11*9 + extrazero + EOT = 124 // 2 mem block of 64 RMT items should be enough ESP_ERROR_CHECK(rmt_config(&config)); + addPin(pins.invpin, true); /* // test: config another gpio pin gpio_num_t gpioNum = (gpio_num_t)(pin-1); @@ -213,14 +214,19 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() { dataRepeat--; } -bool RMTChannel::addPin(byte pin) { +bool RMTChannel::addPin(byte pin, bool inverted) { + if (pin == UNUSED_PIN) + return true; gpio_num_t gpioNum = (gpio_num_t)(pin); esp_err_t err; PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO); err = gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT); if (err != ESP_OK) return false; - gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, 0, 0); + gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, inverted, 0); if (err != ESP_OK) return false; return true; } +bool RMTChannel::addPin(pinpair pins) { + return addPin(pins.pin) && addPin(pins.invpin, true); +} #endif //ESP32 diff --git a/DCCRMT.h b/DCCRMT.h index ff790cd6a..33257a040 100644 --- a/DCCRMT.h +++ b/DCCRMT.h @@ -23,6 +23,7 @@ #include "driver/rmt.h" #include "soc/rmt_reg.h" #include "soc/rmt_struct.h" +#include "MotorDriver.h" // for class pinpair // make calculations easy and set up for microseconds #define RMT_CLOCK_DIVIDER 80 @@ -31,8 +32,9 @@ class RMTChannel { public: - RMTChannel(byte pin, bool isMain); - bool addPin(byte pin); + RMTChannel(pinpair pins, bool isMain); + bool addPin(byte pin, bool inverted=0); + bool addPin(pinpair pins); void IRAM_ATTR RMTinterrupt(); void RMTprefill(); //int RMTfillData(dccPacket packet); diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 7140e91c5..5499b047a 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -211,22 +211,24 @@ DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) { } void DCCWaveform::begin() { for(const auto& md: TrackManager::getMainDrivers()) { + pinpair p = md->getSignalPin(); if(rmtMainChannel) { - DIAG(F("added pin %d to MAIN channel"), md->getSignalPin()); - rmtMainChannel->addPin(md->getSignalPin()); // add pin to existing main channel + DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin); + rmtMainChannel->addPin(p); // add pin to existing main channel } else { - DIAG(F("new MAIN channel with pin %d"), md->getSignalPin()); - rmtMainChannel = new RMTChannel(md->getSignalPin(), true); /* create new main channel */ + DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin); + rmtMainChannel = new RMTChannel(p, true); /* create new main channel */ } } MotorDriver *md = TrackManager::getProgDriver(); if (md) { + pinpair p = md->getSignalPin(); if (rmtProgChannel) { - DIAG(F("added pin %d to PROG channel"), md->getSignalPin()); - rmtProgChannel->addPin(md->getSignalPin()); // add pin to existing prog channel + DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin); + rmtProgChannel->addPin(p); // add pin to existing prog channel } else { - DIAG(F("new PROGchannel with pin %d"), TrackManager::getProgDriver()->getSignalPin()); - rmtProgChannel = new RMTChannel(TrackManager::getProgDriver()->getSignalPin(), false); + DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin); + rmtProgChannel = new RMTChannel(p, false); } } } diff --git a/MotorDriver.h b/MotorDriver.h index 7a155ae67..9e628ebd4 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -77,6 +77,16 @@ #define UNUSED_PIN 127 // inside int8_t #endif +class pinpair { +public: + pinpair(byte p1, byte p2) { + pin = p1; + invpin = p2; + }; + byte pin = UNUSED_PIN; + byte invpin = UNUSED_PIN; +}; + #if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) typedef uint32_t portreg_t; #else @@ -134,7 +144,7 @@ class MotorDriver { else pinMode(signalPin, INPUT); }; - inline byte getSignalPin() { return signalPin; }; + inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); }; virtual void setDCSignal(byte speedByte); inline void detachDCSignal() { #ifndef ARDUINO_ARCH_ESP32 diff --git a/TrackManager.cpp b/TrackManager.cpp index 772ddc5d1..c082c959d 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -140,8 +140,15 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr #ifdef ARDUINO_ARCH_ESP32 // remove pin from MUX matrix and turn it off - DIAG(F("Track=%c remove pin %d"),trackToSet+'A', track[trackToSet]->getSignalPin()); - gpio_reset_pin((gpio_num_t)track[trackToSet]->getSignalPin()); + pinpair p = track[trackToSet]->getSignalPin(); + DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); + gpio_reset_pin((gpio_num_t)p.pin); + pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input + if (p.invpin != UNUSED_PIN) { + DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); + gpio_reset_pin((gpio_num_t)p.invpin); + pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input + } #endif if (mode==TRACK_MODE_PROG) { // only allow 1 track to be prog From 905b2c0148fabfc9cbe8e93a6beb6fbf6b12d538 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 13 Aug 2022 17:36:53 +0200 Subject: [PATCH 260/870] protect from NULL pointer deref in ESP32 code when no MAIN rmt cannel is present --- DCCWaveform.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 5499b047a..ea3a970f9 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -250,19 +250,27 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea { int ret; do { - if(isMainTrack) - ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats); - else - ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats); + if(isMainTrack) { + if (rmtMainChannel != NULL) + ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats); + } else { + if (rmtProgChannel != NULL) + ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats); + } } while(ret > 0); } } bool DCCWaveform::getPacketPending() { - if(isMainTrack) + if(isMainTrack) { + if (rmtMainChannel == NULL) + return true; return rmtMainChannel->busy(); - else + } else { + if (rmtProgChannel == NULL) + return true; return rmtProgChannel->busy(); + } } void IRAM_ATTR DCCWaveform::loop() { DCCACK::checkAck(progTrack.getResets()); From c49d11573c15afc476796b968d37b11d6f43a701 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 13 Aug 2022 18:38:51 +0200 Subject: [PATCH 261/870] less verbose diag, version stamp --- DCCRMT.cpp | 4 ++-- DCCWaveform.cpp | 8 ++++---- GITHUB_SHA.h | 2 +- TrackManager.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index ac97d0d23..4c5fe4ef7 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -138,14 +138,14 @@ RMTChannel::RMTChannel(pinpair pins, bool isMain) { // NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED)); - DIAG(F("Register interrupt on core %d"), xPortGetCoreID()); + // DIAG(F("Register interrupt on core %d"), xPortGetCoreID()); ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true)); channelHandle[channel] = this; // used by interrupt rmt_register_tx_end_callback(interrupt, 0); rmt_set_tx_intr_en(channel, true); - DIAG(F("Starting channel %d signal generator for %s"), config.channel, isMain ? "MAIN" : "PROG"); + DIAG(F("Channel %d DCC signal for %s start"), config.channel, isMain ? "MAIN" : "PROG"); // send one bit to kickstart the signal, remaining data will come from the // packet queue. We intentionally do not wait for the RMT TX complete here. diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index ea3a970f9..28835795b 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -213,10 +213,10 @@ void DCCWaveform::begin() { for(const auto& md: TrackManager::getMainDrivers()) { pinpair p = md->getSignalPin(); if(rmtMainChannel) { - DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin); + // DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin); rmtMainChannel->addPin(p); // add pin to existing main channel } else { - DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin); + // DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin); rmtMainChannel = new RMTChannel(p, true); /* create new main channel */ } } @@ -224,10 +224,10 @@ void DCCWaveform::begin() { if (md) { pinpair p = md->getSignalPin(); if (rmtProgChannel) { - DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin); + // DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin); rmtProgChannel->addPin(p); // add pin to existing prog channel } else { - DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin); + // DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin); rmtProgChannel = new RMTChannel(p, false); } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e7183e38b..56000ec2e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220809" +#define GITHUB_SHA "PORTX-HAL-20220813" diff --git a/TrackManager.cpp b/TrackManager.cpp index c082c959d..23a833d64 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -141,7 +141,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr #ifdef ARDUINO_ARCH_ESP32 // remove pin from MUX matrix and turn it off pinpair p = track[trackToSet]->getSignalPin(); - DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); + // DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); gpio_reset_pin((gpio_num_t)p.pin); pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input if (p.invpin != UNUSED_PIN) { From d333a265f488c6fd3c6761cc598482c50d79a222 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 14 Aug 2022 06:48:03 +1000 Subject: [PATCH 262/870] Add EX-Turntable to PORTX_HAL ready to test --- EXRAIL2MacroReset.h | 2 + EXRAILMacros.h | 1 + IODevice.h | 33 ++++++++++++ IO_EXTurntable.h | 121 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 IO_EXTurntable.h diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index fb0ea2398..0211e2216 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -78,6 +78,7 @@ #undef LATCH #undef LCD #undef LCN +#undef MOVETT #undef ONACTIVATE #undef ONACTIVATEL #undef ONAMBER @@ -187,6 +188,7 @@ #define LATCH(sensor_id) #define LCD(row,msg) #define LCN(msg) +#define MOVETT(id,steps,activity) #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) #define ONAMBER(signal_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index f2b5bcaba..d5424c6fa 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -267,6 +267,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) +#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), diff --git a/IODevice.h b/IODevice.h index 8b786c42a..2a3437cf1 100644 --- a/IODevice.h +++ b/IODevice.h @@ -364,6 +364,39 @@ class ArduinoPins: public IODevice { uint8_t *_pinInUse; }; +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for EX-Turntable. + */ + +class EXTurntable : public IODevice { +public: + static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress); + // Constructor + EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress); + enum ActivityNumber : uint8_t { + Turn = 0, // Rotate turntable, maintain phase + Turn_PInvert = 1, // Rotate turntable, invert phase + Home = 2, // Initiate homing + Calibrate = 3, // Initiate calibration sequence + LED_On = 4, // Turn LED on + LED_Slow = 5, // Set LED to a slow blink + LED_Fast = 6, // Set LED to a fast blink + LED_Off = 7, // Turn LED off + Acc_On = 8, // Turn accessory pin on + Acc_Off = 9, // Turn accessory pin off + }; + +private: + // Device-specific write function. + void _begin() override; + void _loop(unsigned long currentMicros) override; + int _read(VPIN vpin) override; + void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override; + void _display() override; + uint8_t _stepperStatus; +}; + ///////////////////////////////////////////////////////////////////////////////////////////////////// #include "IO_MCP23008.h" diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h new file mode 100644 index 000000000..ea3dcb0c7 --- /dev/null +++ b/IO_EXTurntable.h @@ -0,0 +1,121 @@ +/* + * © 2021, Peter Cole. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C. +* +* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic. +* +* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands: +* in the serial console +* MOVETT(vpin, steps, activity) in EX-RAIL +* Refer to the documentation for further information including the valid activities. +*/ + +#ifndef IO_EXTurntable_h +#define IO_EXTurntable_h + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + new EXTurntable(firstVpin, nPins, I2CAddress); +} + +// Constructor +EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + _firstVpin = firstVpin; + _nPins = nPins; + _I2CAddress = I2CAddress; + addDevice(this); +} + +// Initialisation of TurntableEX +void EXTurntable::_begin() { + I2CManager.begin(); + I2CManager.setClock(1000000); + if (I2CManager.exists(_I2CAddress)) { +#ifdef DIAG_IO + _display(); +#endif + } else { + _deviceState = DEVSTATE_FAILED; + } +} + +// Processing loop to obtain status of stepper +// 0 = finished moving and in correct position +// 1 = still moving +void EXTurntable::_loop(unsigned long currentMicros) { + uint8_t readBuffer[1]; + I2CManager.read(_I2CAddress, readBuffer, 1); + _stepperStatus = readBuffer[0]; + // DIAG(F("Turntable-EX returned status: %d"), _stepperStatus); + delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly +} + +// Read returns status as obtained in our loop. +// Return false if our status value is invalid. +int EXTurntable::_read(VPIN vpin) { + if (_deviceState == DEVSTATE_FAILED) return 0; + // DIAG(F("_read status: %d"), _stepperStatus); + if (_stepperStatus > 1) { + return false; + } else { + return _stepperStatus; + } +} + +// writeAnalogue to send the steps and activity to Turntable-EX. +// Sends 3 bytes containing the MSB and LSB of the step count, and activity. +// value contains the steps, bit shifted to MSB + LSB. +// activity contains the activity flag as per this list: +// +// Turn = 0, // Rotate turntable, maintain phase +// Turn_PInvert = 1, // Rotate turntable, invert phase +// Home = 2, // Initiate homing +// Calibrate = 3, // Initiate calibration sequence +// LED_On = 4, // Turn LED on +// LED_Slow = 5, // Set LED to a slow blink +// LED_Fast = 6, // Set LED to a fast blink +// LED_Off = 7, // Turn LED off +// Acc_On = 8, // Turn accessory pin on +// Acc_Off = 9 // Turn accessory pin off +void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) { + if (_deviceState == DEVSTATE_FAILED) return; + uint8_t stepsMSB = value >> 8; + uint8_t stepsLSB = value & 0xFF; +#ifdef DIAG_IO + DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), + vpin, value, activity, duration); + DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), + _I2CAddress, stepsMSB, stepsLSB, activity); +#endif + _stepperStatus = 1; // Tell the device driver Turntable-EX is busy + I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity); +} + +// Display Turnetable-EX device driver info. +void EXTurntable::_display() { + DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); +} + +#endif From 0a6d023373226ba813597d710e1e98473536a6e9 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 14 Aug 2022 06:58:20 +1000 Subject: [PATCH 263/870] Add ready to test --- DCCEXParser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e9047a630..e986406dd 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -67,6 +67,7 @@ const int16_t HASH_KEYWORD_RETRY = 25704; const int16_t HASH_KEYWORD_SPEED28 = -17064; const int16_t HASH_KEYWORD_SPEED128 = 25816; const int16_t HASH_KEYWORD_SERVO=27709; +const int16_t HASH_KEYWORD_TT=2688; const int16_t HASH_KEYWORD_VPIN=-415; const int16_t HASH_KEYWORD_A='A'; const int16_t HASH_KEYWORD_C='C'; @@ -938,6 +939,10 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) break; #endif + case HASH_KEYWORD_TT: // + IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0); + break; + default: // invalid/unknown break; } From 9f27759a9cb6e1d3e4ac32d92ceabd1d63420b7d Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 14 Aug 2022 07:11:38 +1000 Subject: [PATCH 264/870] Add myEX-Turntable.example.h --- myEX-Turntable.example.h | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 myEX-Turntable.example.h diff --git a/myEX-Turntable.example.h b/myEX-Turntable.example.h new file mode 100644 index 000000000..cbc1e5429 --- /dev/null +++ b/myEX-Turntable.example.h @@ -0,0 +1,101 @@ +/************************************************************************************************** + * This is an example automation file to control EX-Turntable using recommended techniques. + ************************************************************************************************** + * INSTRUCTIONS + ************************************************************************************************** + * To use this example file as the starting point for your layout, there are two options: + * + * 1. If you don't have an existing "myAutomation.h" file, simply rename "myEX-Turntable.example.h" to + * "myAutomation.h". + * 2. If you have an existing "myAutomation.h" file, rename "myEX-Turntable.example.h" to "myEX-Turntable.h", + * and then include it by adding the line below at the end of your existing "myAutomation.h", on a + * line of its own: + * + * #include "myEX-Turntable.h" + * + * Note that there are further instructions in the documentation at https://dcc-ex.com/. + *************************************************************************************************/ + +/************************************************************************************************** + * The MOVETT() command below will automatically move your turntable to the defined step position on + * start up. + * + * If you do not wish this to occur, simply comment the line out. + * + * NOTE: If you are including this file at the end of an existing "myAutomation.h" file, you will likely + * need to move this line to the beginning of your existing "myAutomation.h" file in order for it to + * be effective. + *************************************************************************************************/ +MOVETT(600, 114, Turn) +DONE + +// For Conductor level users who wish to just use EX-Turntable, you don't need to understand this +// and can move to defining the turntable positions below. You must, however, ensure this remains +// before any position definitions or you will get compile errors when uploading. +// +// Definition of the EX_TURNTABLE macro to correctly create the ROUTEs required for each position. +// This includes RESERVE()/FREE() to protect any automation activities. +// +#define EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc) \ + ROUTE(route_id, desc) \ + RESERVE(reserve_id) \ + MOVETT(vpin, steps, activity) \ + WAITFOR(vpin) \ + FREE(reserve_id) \ + DONE + +/************************************************************************************************** + * TURNTABLE POSITION DEFINITIONS + *************************************************************************************************/ +// EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc) +// +// route_id = A unique number for each defined route, the route is what appears in throttles +// reserve_id = A unique reservation number (0 - 255) to ensure nothing interferes with automation +// vpin = The Vpin defined for the Turntable-EX device driver, default is 600 +// steps = The target step position +// activity = The activity performed for this ROUTE (Note do not enclose in quotes "") +// desc = Description that will appear in throttles (Must use quotes "") +// +EX_TURNTABLE(TTRoute1, Turntable, 600, 114, Turn, "Position 1") +EX_TURNTABLE(TTRoute2, Turntable, 600, 227, Turn, "Position 2") +EX_TURNTABLE(TTRoute3, Turntable, 600, 341, Turn, "Position 3") +EX_TURNTABLE(TTRoute4, Turntable, 600, 2159, Turn, "Position 4") +EX_TURNTABLE(TTRoute5, Turntable, 600, 2273, Turn, "Position 5") +EX_TURNTABLE(TTRoute6, Turntable, 600, 2386, Turn, "Position 6") +EX_TURNTABLE(TTRoute7, Turntable, 600, 0, Home, "Home turntable") + +// Pre-defined aliases to ensure unique IDs are used. +// Turntable reserve ID, valid is 0 - 255 +ALIAS(Turntable, 255) + +// Turntable ROUTE ID reservations, using for uniqueness: +ALIAS(TTRoute1) +ALIAS(TTRoute2) +ALIAS(TTRoute3) +ALIAS(TTRoute4) +ALIAS(TTRoute5) +ALIAS(TTRoute6) +ALIAS(TTRoute7) +ALIAS(TTRoute8) +ALIAS(TTRoute9) +ALIAS(TTRoute10) +ALIAS(TTRoute11) +ALIAS(TTRoute12) +ALIAS(TTRoute13) +ALIAS(TTRoute14) +ALIAS(TTRoute15) +ALIAS(TTRoute16) +ALIAS(TTRoute17) +ALIAS(TTRoute18) +ALIAS(TTRoute19) +ALIAS(TTRoute20) +ALIAS(TTRoute21) +ALIAS(TTRoute22) +ALIAS(TTRoute23) +ALIAS(TTRoute24) +ALIAS(TTRoute25) +ALIAS(TTRoute26) +ALIAS(TTRoute27) +ALIAS(TTRoute28) +ALIAS(TTRoute29) +ALIAS(TTRoute30) From f0c267283558e4e40ba0d4ece2dc64fb98f22aba Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 15 Aug 2022 13:32:13 +0800 Subject: [PATCH 265/870] Corrected comment in DCCTimer for STM32 --- DCCTimerSTM32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 229baec5b..d055d59cf 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -33,7 +33,7 @@ #include "DCCTimer.h" // STM32 doesn't have Serial1 defined by default -HardwareSerial Serial1(PA10, PA15); // Rx=PA10, Tx=PA9 +HardwareSerial Serial1(PA10, PA15); // Rx=PA10, Tx=PA15 INTERRUPT_CALLBACK interruptHandler=0; // Let's use STM32's timer #1 until disabused of this notion From 60d91eef9d5c46518333490161561051d655d1d0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 15 Aug 2022 10:49:56 +0200 Subject: [PATCH 266/870] lots of diag --- CommandDistributor.cpp | 13 ++++++++++--- RingStream.cpp | 9 ++++++++- WiThrottle.cpp | 4 ++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 12a78f376..94be666fe 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -97,17 +97,24 @@ void CommandDistributor::broadcastToClients(clientType type) { // If we are broadcasting from a wifi/eth process we need to complete its output // before merging broadcasts in the ring, then reinstate it in case // the process continues to output to its client. - if (ringClient!=NO_CLIENT) ring->commit(); - + if (ringClient!=NO_CLIENT) { + DIAG(F("CD precommit client %d"), ringClient); + ring->commit(); + } /* loop through ring clients */ for (byte clientId=0; clientIdmark(clientId); ring->print(broadcastBufferWriter->getString()); + DIAG(F("CD commit client %d"), clientId); ring->commit(); } } - if (ringClient!=NO_CLIENT) ring->mark(ringClient); + if (ringClient!=NO_CLIENT) { + DIAG(F("CD postmark client %d"), ringClient); + ring->mark(ringClient); + } #endif } diff --git a/RingStream.cpp b/RingStream.cpp index 319727542..4c7176edc 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -143,6 +143,7 @@ int RingStream::freeSpace() { // mark start of message with client id (0...9) void RingStream::mark(uint8_t b) { + DIAG(F("RS mark client %d at %d"), b, _pos_write); _mark=_pos_write; write(b); // client id write((uint8_t)0); // count MSB placemarker @@ -170,7 +171,8 @@ bool RingStream::commit() { return false; // commit failed } if (_count==0) { - // ignore empty response + DIAG(F("RS commit count=0 rewind back to %d"), _mark); + // ignore empty response _pos_write=_mark; return true; // true=commit ok } @@ -181,6 +183,11 @@ bool RingStream::commit() { _mark++; if (_mark==_len) _mark=0; _buffer[_mark]=lowByte(_count); + { char s[_count+2]; + strncpy(s, (const char*)&(_buffer[_mark+1]), _count-2); + s[_count-1]=0; + DIAG(F("RS commit count=%d %s"), _count, s); + } return true; // commit worked } void RingStream::flush() { diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 32968a489..61765a4d2 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -610,10 +610,12 @@ byte WiThrottle::stashClient; char WiThrottle::stashThrottleChar; void WiThrottle::getLocoCallback(int16_t locoid) { + DIAG(F("LocoCallback mark client %d"), stashClient); stashStream->mark(stashClient); if (locoid<=0) { StringFormatter::send(stashStream,F("HMNo loco found on prog track\n")); + DIAG(F("LocoCallback commit (noloco)")); stashStream->commit(); // done here, commit and return return; } @@ -624,6 +626,7 @@ void WiThrottle::getLocoCallback(int16_t locoid) { locoid = locoid ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr if (locoid <= HIGHEST_SHORT_ADDR ) { // out of range for long addr StringFormatter::send(stashStream,F("HMLong addr %d <= %d unsupported\n"), locoid, HIGHEST_SHORT_ADDR); + DIAG(F("LocoCallback commit (error)")); stashStream->commit(); // done here, commit and return return; } @@ -637,6 +640,7 @@ void WiThrottle::getLocoCallback(int16_t locoid) { stashInstance->multithrottle(stashStream, (byte *)addcmd); TrackManager::setMainPower(POWERMODE::ON); TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away + DIAG(F("LocoCallback commit success")); stashStream->commit(); CommandDistributor::broadcastPower(); From ff28dbd561422975ec667e693dabcf29d674a0aa Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 15 Aug 2022 10:50:55 +0200 Subject: [PATCH 267/870] no need to estop repeatedly --- WiThrottle.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 61765a4d2..e6b018fee 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -541,6 +541,7 @@ void WiThrottle::checkHeartbeat(RingStream * stream) { if (myLocos[loco].throttle!='\0') { if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab); DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop + heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop. } } //haba no, not necessary the only throttle and it may come back From 162e1f9d3e65d4273eb783fa202d28edfe4d4d26 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 16 Aug 2022 09:38:40 +0200 Subject: [PATCH 268/870] move ringClient into RingStream --- CommandDistributor.cpp | 21 ++++++++++++--------- CommandDistributor.h | 4 ++-- RingStream.cpp | 17 ++++++++++------- RingStream.h | 2 ++ WifiESP32.cpp | 4 +++- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 94be666fe..2068dc69e 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -47,7 +47,7 @@ #ifdef CD_HANDLE_RING // wifi or ethernet ring streams with multiple client types RingStream * CommandDistributor::ring=0; - byte CommandDistributor::ringClient=NO_CLIENT; +// byte CommandDistributor::ringClient=NO_CLIENT; CommandDistributor::clientType CommandDistributor::clients[8]={ NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; @@ -57,7 +57,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream if (Diag::WIFI && Diag::CMD) DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer); ring=stream; - ringClient=stream->peekTargetMark(); + // ringClient=stream->peekTargetMark(); // First check if the client is not known // yet and in that case determinine type @@ -78,7 +78,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream else if (clients[clientId] == WITHROTTLE_TYPE) WiThrottle::getThrottle(clientId)->parse(ring, buffer); - ringClient=NO_CLIENT; + // ringClient=NO_CLIENT; } void CommandDistributor::forget(byte clientId) { @@ -90,6 +90,8 @@ void CommandDistributor::forget(byte clientId) { // This will not be called on a uno void CommandDistributor::broadcastToClients(clientType type) { + byte rememberClient; + /* Boadcast to Serials */ if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString()); @@ -97,8 +99,9 @@ void CommandDistributor::broadcastToClients(clientType type) { // If we are broadcasting from a wifi/eth process we need to complete its output // before merging broadcasts in the ring, then reinstate it in case // the process continues to output to its client. - if (ringClient!=NO_CLIENT) { - DIAG(F("CD precommit client %d"), ringClient); + if (ring) { + if ((rememberClient = ring->peekTargetMark()) != NO_CLIENT) { + DIAG(F("CD precommit client %d"), rememberClient); ring->commit(); } /* loop through ring clients */ @@ -111,11 +114,11 @@ void CommandDistributor::broadcastToClients(clientType type) { ring->commit(); } } - if (ringClient!=NO_CLIENT) { - DIAG(F("CD postmark client %d"), ringClient); - ring->mark(ringClient); + if (ring->peekTargetMark()!=NO_CLIENT) { + DIAG(F("CD postmark client %d"), rememberClient); + ring->mark(rememberClient); + } } - #endif } diff --git a/CommandDistributor.h b/CommandDistributor.h index 7da3c8f2a..c5b6ad712 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -47,8 +47,8 @@ public : static StringBuffer * broadcastBufferWriter; #ifdef CD_HANDLE_RING static RingStream * ring; - static const byte NO_CLIENT=255; - static byte ringClient; + static const byte NO_CLIENT=255; //XXX remove later + // static byte ringClient; static clientType clients[8]; #endif }; diff --git a/RingStream.cpp b/RingStream.cpp index 4c7176edc..fcf6ed740 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -143,7 +143,8 @@ int RingStream::freeSpace() { // mark start of message with client id (0...9) void RingStream::mark(uint8_t b) { - DIAG(F("RS mark client %d at %d"), b, _pos_write); + DIAG(F("RS mark client %d at %d core %d"), b, _pos_write, xPortGetCoreID()); + _ringClient = b; _mark=_pos_write; write(b); // client id write((uint8_t)0); // count MSB placemarker @@ -154,7 +155,7 @@ void RingStream::mark(uint8_t b) { // peekTargetMark is used by the parser stash routines to know which client // to send a callback response to some time later. uint8_t RingStream::peekTargetMark() { - return _buffer[_mark]; + return _ringClient; } void RingStream::info() { @@ -171,9 +172,10 @@ bool RingStream::commit() { return false; // commit failed } if (_count==0) { - DIAG(F("RS commit count=0 rewind back to %d"), _mark); + DIAG(F("RS commit count=0 rewind back to %d core %d"), _mark, xPortGetCoreID()); // ignore empty response _pos_write=_mark; + _ringClient = NO_CLIENT; //XXX make else clause later return true; // true=commit ok } // Go back to the _mark and inject the count 1 byte later @@ -184,10 +186,11 @@ bool RingStream::commit() { if (_mark==_len) _mark=0; _buffer[_mark]=lowByte(_count); { char s[_count+2]; - strncpy(s, (const char*)&(_buffer[_mark+1]), _count-2); - s[_count-1]=0; - DIAG(F("RS commit count=%d %s"), _count, s); + strncpy(s, (const char*)&(_buffer[_mark+1]), _count); + s[_count]=0; + DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); } + _ringClient = NO_CLIENT; return true; // commit worked } void RingStream::flush() { @@ -195,6 +198,6 @@ void RingStream::flush() { _pos_read=0; _buffer[0]=0; _flashInsert=NULL; // prepared for first read - + _ringClient = NO_CLIENT; } diff --git a/RingStream.h b/RingStream.h index cfc82ebe1..a3aad4960 100644 --- a/RingStream.h +++ b/RingStream.h @@ -61,6 +61,8 @@ class RingStream : public Print { int _count; byte * _buffer; char * _flashInsert; + static const byte NO_CLIENT=255; // must be same as in CommandDistributor + byte _ringClient = NO_CLIENT; }; #endif diff --git a/WifiESP32.cpp b/WifiESP32.cpp index a91372ffd..164374fbc 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -95,6 +95,7 @@ class NetworkClient { static std::vector clients; // a list to hold all clients static WiFiServer *server = NULL; static RingStream *outboundRing = new RingStream(2048); +//static RingStream *eventRing = new RingStream(2048); static bool APmode = false; void wifiLoop(void *){ @@ -260,7 +261,8 @@ void WifiESP::loop() { cmd[len]=0; outboundRing->mark(clientId); CommandDistributor::parse(clientId,cmd,outboundRing); - outboundRing->commit(); + if (outboundRing->peekTargetMark()!=255) //XXX fix 255 later + outboundRing->commit(); } } } // all clients From b9c1e779aec249f2a3b6c87f2ce45a8fbe08645d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 16 Aug 2022 12:25:49 +0200 Subject: [PATCH 269/870] move WiFiESP::loop() to core 1 --- CommandDistributor.cpp | 35 ++++++++++++++++------------------- CommandDistributor.h | 2 -- CommandStation-EX.ino | 4 ++++ RingStream.cpp | 8 ++++---- RingStream.h | 2 +- WiThrottle.cpp | 8 ++++---- WifiESP32.cpp | 11 ++++++++--- 7 files changed, 37 insertions(+), 33 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 2068dc69e..1dbcc6ee2 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -47,7 +47,6 @@ #ifdef CD_HANDLE_RING // wifi or ethernet ring streams with multiple client types RingStream * CommandDistributor::ring=0; -// byte CommandDistributor::ringClient=NO_CLIENT; CommandDistributor::clientType CommandDistributor::clients[8]={ NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE}; @@ -57,7 +56,6 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream if (Diag::WIFI && Diag::CMD) DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer); ring=stream; - // ringClient=stream->peekTargetMark(); // First check if the client is not known // yet and in that case determinine type @@ -78,7 +76,6 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream else if (clients[clientId] == WITHROTTLE_TYPE) WiThrottle::getThrottle(clientId)->parse(ring, buffer); - // ringClient=NO_CLIENT; } void CommandDistributor::forget(byte clientId) { @@ -100,24 +97,24 @@ void CommandDistributor::broadcastToClients(clientType type) { // before merging broadcasts in the ring, then reinstate it in case // the process continues to output to its client. if (ring) { - if ((rememberClient = ring->peekTargetMark()) != NO_CLIENT) { - DIAG(F("CD precommit client %d"), rememberClient); - ring->commit(); - } - /* loop through ring clients */ - for (byte clientId=0; clientIdmark(clientId); - ring->print(broadcastBufferWriter->getString()); - DIAG(F("CD commit client %d"), clientId); + if ((rememberClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) { + //DIAG(F("CD precommit client %d"), rememberClient); ring->commit(); } - } - if (ring->peekTargetMark()!=NO_CLIENT) { - DIAG(F("CD postmark client %d"), rememberClient); - ring->mark(rememberClient); - } + /* loop through ring clients */ + for (byte clientId=0; clientIdmark(clientId); + ring->print(broadcastBufferWriter->getString()); + //DIAG(F("CD commit client %d"), clientId); + ring->commit(); + } + } + if (ring->peekTargetMark()!=RingStream::NO_CLIENT) { + //DIAG(F("CD postmark client %d"), rememberClient); + ring->mark(rememberClient); + } } #endif } diff --git a/CommandDistributor.h b/CommandDistributor.h index c5b6ad712..3c3ec7bae 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -47,8 +47,6 @@ public : static StringBuffer * broadcastBufferWriter; #ifdef CD_HANDLE_RING static RingStream * ring; - static const byte NO_CLIENT=255; //XXX remove later - // static byte ringClient; static clientType clients[8]; #endif }; diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 0cbf05669..7dca07173 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -143,6 +143,10 @@ void loop() #if WIFI_ON WifiInterface::loop(); #endif //WIFI_ON +#else //ARDUINO_ARCH_ESP32 +#ifndef WIFI_TASK_ON_CORE0 + WifiESP::loop(); +#endif #endif //ARDUINO_ARCH_ESP32 #if ETHERNET_ON EthernetInterface::loop(); diff --git a/RingStream.cpp b/RingStream.cpp index fcf6ed740..e639f5d95 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -143,7 +143,7 @@ int RingStream::freeSpace() { // mark start of message with client id (0...9) void RingStream::mark(uint8_t b) { - DIAG(F("RS mark client %d at %d core %d"), b, _pos_write, xPortGetCoreID()); + //DIAG(F("RS mark client %d at %d core %d"), b, _pos_write, xPortGetCoreID()); _ringClient = b; _mark=_pos_write; write(b); // client id @@ -165,14 +165,14 @@ void RingStream::info() { bool RingStream::commit() { _flashInsert=NULL; // prepared for first read if (_overflow) { - DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); + //DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count); // just throw it away _pos_write=_mark; _overflow=false; return false; // commit failed } if (_count==0) { - DIAG(F("RS commit count=0 rewind back to %d core %d"), _mark, xPortGetCoreID()); + //DIAG(F("RS commit count=0 rewind back to %d core %d"), _mark, xPortGetCoreID()); // ignore empty response _pos_write=_mark; _ringClient = NO_CLIENT; //XXX make else clause later @@ -188,7 +188,7 @@ bool RingStream::commit() { { char s[_count+2]; strncpy(s, (const char*)&(_buffer[_mark+1]), _count); s[_count]=0; - DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); + //DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); } _ringClient = NO_CLIENT; return true; // commit worked diff --git a/RingStream.h b/RingStream.h index a3aad4960..4f51a656e 100644 --- a/RingStream.h +++ b/RingStream.h @@ -52,6 +52,7 @@ class RingStream : public Print { if ((_pos_read==_pos_write) && !_overflow) return -1; // empty return _buffer[_pos_read]; }; + static const byte NO_CLIENT=255; private: int _len; int _pos_write; @@ -61,7 +62,6 @@ class RingStream : public Print { int _count; byte * _buffer; char * _flashInsert; - static const byte NO_CLIENT=255; // must be same as in CommandDistributor byte _ringClient = NO_CLIENT; }; diff --git a/WiThrottle.cpp b/WiThrottle.cpp index e6b018fee..46ee81f4a 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -611,12 +611,12 @@ byte WiThrottle::stashClient; char WiThrottle::stashThrottleChar; void WiThrottle::getLocoCallback(int16_t locoid) { - DIAG(F("LocoCallback mark client %d"), stashClient); + //DIAG(F("LocoCallback mark client %d"), stashClient); stashStream->mark(stashClient); if (locoid<=0) { StringFormatter::send(stashStream,F("HMNo loco found on prog track\n")); - DIAG(F("LocoCallback commit (noloco)")); + //DIAG(F("LocoCallback commit (noloco)")); stashStream->commit(); // done here, commit and return return; } @@ -627,7 +627,7 @@ void WiThrottle::getLocoCallback(int16_t locoid) { locoid = locoid ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr if (locoid <= HIGHEST_SHORT_ADDR ) { // out of range for long addr StringFormatter::send(stashStream,F("HMLong addr %d <= %d unsupported\n"), locoid, HIGHEST_SHORT_ADDR); - DIAG(F("LocoCallback commit (error)")); + //DIAG(F("LocoCallback commit (error)")); stashStream->commit(); // done here, commit and return return; } @@ -641,7 +641,7 @@ void WiThrottle::getLocoCallback(int16_t locoid) { stashInstance->multithrottle(stashStream, (byte *)addcmd); TrackManager::setMainPower(POWERMODE::ON); TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away - DIAG(F("LocoCallback commit success")); + //DIAG(F("LocoCallback commit success")); stashStream->commit(); CommandDistributor::broadcastPower(); diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 164374fbc..21b94eaee 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -95,14 +95,15 @@ class NetworkClient { static std::vector clients; // a list to hold all clients static WiFiServer *server = NULL; static RingStream *outboundRing = new RingStream(2048); -//static RingStream *eventRing = new RingStream(2048); static bool APmode = false; +#ifdef WIFI_TASK_ON_CORE0 void wifiLoop(void *){ for(;;){ WifiESP::loop(); } } +#endif bool WifiESP::setup(const char *SSid, const char *password, @@ -196,6 +197,7 @@ bool WifiESP::setup(const char *SSid, server->begin(); // server started here +#ifdef WIFI_TASK_ON_CORE0 //start loop task if (pdPASS != xTaskCreatePinnedToCore( wifiLoop, /* Task function. */ @@ -211,7 +213,10 @@ bool WifiESP::setup(const char *SSid, // report server started after wifiLoop creation // when everything looks good - DIAG(F("Server up port %d"),port); + DIAG(F("Server starting (core 0) port %d"),port); +#else + DIAG(F("Server will be started on port %d"),port); +#endif return true; } @@ -261,7 +266,7 @@ void WifiESP::loop() { cmd[len]=0; outboundRing->mark(clientId); CommandDistributor::parse(clientId,cmd,outboundRing); - if (outboundRing->peekTargetMark()!=255) //XXX fix 255 later + if (outboundRing->peekTargetMark()!=RingStream::NO_CLIENT) outboundRing->commit(); } } From 5d415366d89fdf53403bd4442c1715433b6f4205 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 16 Aug 2022 12:43:38 +0200 Subject: [PATCH 270/870] bigger ringstream on ESP32 --- WifiESP32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 21b94eaee..95de764d1 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -94,7 +94,7 @@ class NetworkClient { static std::vector clients; // a list to hold all clients static WiFiServer *server = NULL; -static RingStream *outboundRing = new RingStream(2048); +static RingStream *outboundRing = new RingStream(10240); static bool APmode = false; #ifdef WIFI_TASK_ON_CORE0 From 9633e77c0a33a9a48c819623a961d73454b91dbd Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 16 Aug 2022 19:22:19 +0200 Subject: [PATCH 271/870] move mark/commit into CommandDistributor::parse() --- CommandDistributor.cpp | 21 ++++++++++++++++++--- EthernetInterface.cpp | 2 -- GITHUB_SHA.h | 2 +- WifiESP32.cpp | 3 --- WifiInboundHandler.cpp | 6 ------ version.h | 2 +- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1dbcc6ee2..c4538cb21 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -69,13 +69,28 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream clients[clientId]=WITHROTTLE_TYPE; } + // mark buffer that is sent to parser + ring->mark(clientId); + // When type is known, send the string // to the right parser - if (clients[clientId] == COMMAND_TYPE) + if (clients[clientId] == COMMAND_TYPE) { DCCEXParser::parse(stream, buffer, ring); - else if (clients[clientId] == WITHROTTLE_TYPE) + } else if (clients[clientId] == WITHROTTLE_TYPE) { WiThrottle::getThrottle(clientId)->parse(ring, buffer); + } + if (ring->peekTargetMark()!=RingStream::NO_CLIENT) { + // The commit call will either write the length bytes + // OR rollback to the mark because the reply is empty + // or the command generated more output than fits in + // the buffer + if (!ring->commit()) { + DIAG(F("OUTBOUND FULL processing cmd:%s"),buffer); + } + } else { + DIAG(F("CD parse: was alredy committed")); //XXX Could have been committed by broadcastClient?! + } } void CommandDistributor::forget(byte clientId) { @@ -111,7 +126,7 @@ void CommandDistributor::broadcastToClients(clientType type) { ring->commit(); } } - if (ring->peekTargetMark()!=RingStream::NO_CLIENT) { + if (ring->peekTargetMark() == RingStream::NO_CLIENT) { //DIAG(F("CD postmark client %d"), rememberClient); ring->mark(rememberClient); } diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index 48f9002a8..f8c6146b4 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -163,9 +163,7 @@ void EthernetInterface::loop() buffer[count] = '\0'; // terminate the string properly if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer); // execute with data going directly back - outboundRing->mark(socket); CommandDistributor::parse(socket,buffer,outboundRing); - outboundRing->commit(); return; // limit the amount of processing that takes place within 1 loop() cycle. } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 56000ec2e..42079cb0d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220813" +#define GITHUB_SHA "PORTX-HAL-20220816" diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 95de764d1..f00d6f428 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -264,10 +264,7 @@ void WifiESP::loop() { cmd[i]=clients[clientId].wifi.read(); } cmd[len]=0; - outboundRing->mark(clientId); CommandDistributor::parse(clientId,cmd,outboundRing); - if (outboundRing->peekTargetMark()!=RingStream::NO_CLIENT) - outboundRing->commit(); } } } // all clients diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index a50940926..2a8ec28f0 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -84,13 +84,7 @@ void WifiInboundHandler::loop1() { cmd[count]=0; if (Diag::WIFI) DIAG(F("%e"),cmd); - outboundRing->mark(clientId); // remember start of outbound data CommandDistributor::parse(clientId,cmd,outboundRing); - // The commit call will either write the lenbgth bytes - // OR rollback to the mark because the reply is empty or commend generated more than fits the buffer - if (!outboundRing->commit()) { - DIAG(F("OUTBOUND FULL processing cmd:%s"),cmd); - } return; } } diff --git a/version.h b/version.h index a5d336f59..6e743e189 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.1 rc1" +#define VERSION "4.2.2 rc1" // 4.2.1 ESP32 alpha // Ready for alpha test on ESP32. Track switching with <=> untested // Send DCC signal on MAIN From cadb82ab6b5cd784ea2ed4246ba89151d82a0325 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 16 Aug 2022 22:32:27 +0200 Subject: [PATCH 272/870] remove currently unused virtual functions --- MotorDriver.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index a2e1f73e3..1f97cfea6 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -113,14 +113,14 @@ class MotorDriver { MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); - virtual void setPower( POWERMODE mode); - virtual POWERMODE getPower() { return powerMode;} + void setPower( POWERMODE mode); + POWERMODE getPower() { return powerMode;} // as the port registers can be shadowed to get syncronized DCC signals // we need to take care of that and we have to turn off interrupts if // we setSignal() or setBrake() or setPower() during that time as // otherwise the call from interrupt context can undo whatever we do // from outside interrupt - virtual void setBrake( bool on, bool interruptContext=false); + void setBrake( bool on, bool interruptContext=false); __attribute__((always_inline)) inline void setSignal( bool high, bool interruptContext=false) { if (!interruptContext) {noInterrupts();} if (trackPWM) { @@ -145,7 +145,7 @@ class MotorDriver { pinMode(signalPin, INPUT); }; inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); }; - virtual void setDCSignal(byte speedByte); + void setDCSignal(byte speedByte); inline void detachDCSignal() { #if defined(__arm__) pinMode(brakePin, OUTPUT); @@ -155,10 +155,10 @@ class MotorDriver { setDCSignal(128); #endif }; - virtual int getCurrentRaw(); - virtual int getCurrentRawInInterrupt(); - virtual unsigned int raw2mA( int raw); - virtual unsigned int mA2raw( unsigned int mA); + int getCurrentRaw(); + int getCurrentRawInInterrupt(); + unsigned int raw2mA( int raw); + unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { #if defined(ARDUINO_ARCH_ESP32) || defined(__arm__) // TODO: on ARM we can use digitalPinHasPWM, and may wish/need to From d3dbeaa66613fa260b6751e5bd4cd6a3b62b297a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 17 Aug 2022 00:28:15 +0200 Subject: [PATCH 273/870] bugfix: Wrong condition for ring->commit() --- CommandDistributor.cpp | 4 +++- GITHUB_SHA.h | 2 +- RingStream.cpp | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index c4538cb21..9132d8aa0 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -126,7 +126,9 @@ void CommandDistributor::broadcastToClients(clientType type) { ring->commit(); } } - if (ring->peekTargetMark() == RingStream::NO_CLIENT) { + // at this point ring is committed (NO_CLIENT) either from + // 4 or 13 lines above. + if (rememberClient != RingStream::NO_CLIENT) { //DIAG(F("CD postmark client %d"), rememberClient); ring->mark(rememberClient); } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 42079cb0d..17197b86b 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220816" +#define GITHUB_SHA "PORTX-HAL-20220817" diff --git a/RingStream.cpp b/RingStream.cpp index e639f5d95..9db6230ec 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -107,7 +107,7 @@ int RingStream::read() { if ((_pos_read==_pos_write) && !_overflow) return -1; // empty byte b=readRawByte(); if (b!=FLASH_INSERT_MARKER) return b; - +#ifndef ARDUINO_ARCH_ESP32 // Detected a flash insert // read address bytes LSB first (size depends on CPU) uintptr_t iFlash=0; @@ -119,7 +119,11 @@ int RingStream::read() { } _flashInsert=reinterpret_cast( iFlash); // and try again... so will read the first byte of the insert. - return read(); + return read(); +#else + DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read); + return '\0'; +#endif } byte RingStream::readRawByte() { From e36e867ec21cc605fc5acd9343142307847d1247 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 17 Aug 2022 02:11:51 +0200 Subject: [PATCH 274/870] ESP32: implement JOIN --- DCCACK.cpp | 22 ++++++++++++---------- DCCWaveform.cpp | 8 ++++---- GITHUB_SHA.h | 2 +- TrackManager.cpp | 23 +++++++++++++++++++++-- TrackManager.h | 3 +++ 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index d15938397..fa3d3480b 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -67,23 +67,25 @@ CALLBACK_STATE DCCACK::callbackState=READY; ACK_CALLBACK DCCACK::ackManagerCallback; void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) { + ackManagerRejoin=TrackManager::isJoined(); + if (ackManagerRejoin) { + // Change from JOIN must zero resets packet. + TrackManager::setJoin(false); + DCCWaveform::progTrack.clearResets(); + } + progDriver=TrackManager::getProgDriver(); if (progDriver==NULL) { - callback(-3); // we dont have a prog track! - return; - } + TrackManager::setJoin(ackManagerRejoin); + callback(-3); // we dont have a prog track! + return; + } if (!progDriver->canMeasureCurrent()) { + TrackManager::setJoin(ackManagerRejoin); callback(-2); // our prog track cant measure current return; } - ackManagerRejoin=TrackManager::isJoined(); - if (ackManagerRejoin ) { - // Change from JOIN must zero resets packet. - TrackManager::setJoin(false); - DCCWaveform::progTrack.clearResets(); - } - autoPowerOff=false; if (progDriver->getPower() == POWERMODE::OFF) { autoPowerOff=true; // power off afterwards diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 28835795b..d7dc26a08 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -213,10 +213,10 @@ void DCCWaveform::begin() { for(const auto& md: TrackManager::getMainDrivers()) { pinpair p = md->getSignalPin(); if(rmtMainChannel) { - // DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin); + //DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin); rmtMainChannel->addPin(p); // add pin to existing main channel } else { - // DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin); + //DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin); rmtMainChannel = new RMTChannel(p, true); /* create new main channel */ } } @@ -224,10 +224,10 @@ void DCCWaveform::begin() { if (md) { pinpair p = md->getSignalPin(); if (rmtProgChannel) { - // DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin); + //DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin); rmtProgChannel->addPin(p); // add pin to existing prog channel } else { - // DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin); + //DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin); rmtProgChannel = new RMTChannel(p, false); } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 17197b86b..78b65c06d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220817" +#define GITHUB_SHA "PORTX-HAL-20220817-1" diff --git a/TrackManager.cpp b/TrackManager.cpp index 23a833d64..dd9ad64e5 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -50,6 +50,9 @@ byte TrackManager::lastTrack=0; bool TrackManager::progTrackSyncMain=false; bool TrackManager::progTrackBoosted=false; int16_t TrackManager::joinRelay=UNUSED_PIN; +#ifdef ARDUINO_ARCH_ESP32 +byte TrackManager::tempProgTrack=MAX_TRACKS+1; +#endif // The setup call is done this way so that the tracks can be in a list @@ -141,11 +144,11 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr #ifdef ARDUINO_ARCH_ESP32 // remove pin from MUX matrix and turn it off pinpair p = track[trackToSet]->getSignalPin(); - // DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); + //DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin); gpio_reset_pin((gpio_num_t)p.pin); pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input if (p.invpin != UNUSED_PIN) { - DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); + //DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin); gpio_reset_pin((gpio_num_t)p.invpin); pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input } @@ -384,6 +387,22 @@ void TrackManager::setJoinRelayPin(byte joinRelayPin) { } void TrackManager::setJoin(bool joined) { +#ifdef ARDUINO_ARCH_ESP32 + if (joined) { + FOR_EACH_TRACK(t) { + if (trackMode[t]==TRACK_MODE_PROG) { + tempProgTrack = t; + setTrackMode(t, TRACK_MODE_MAIN); + break; + } + } + } else { + if (tempProgTrack != MAX_TRACKS+1) { + setTrackMode(tempProgTrack, TRACK_MODE_PROG); + tempProgTrack = MAX_TRACKS+1; + } + } +#endif progTrackSyncMain=joined; if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW); } diff --git a/TrackManager.h b/TrackManager.h index b21022143..45325f966 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -90,6 +90,9 @@ class TrackManager { static MotorDriver* track[MAX_TRACKS]; static TRACK_MODE trackMode[MAX_TRACKS]; static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX +#ifdef ARDUINO_ARCH_ESP32 + static byte tempProgTrack; // holds the prog track number during join +#endif }; #endif From 77cca8f6eeaaa03f10486c827ce90312ca310749 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 17 Aug 2022 14:29:02 +0800 Subject: [PATCH 275/870] STM32 switch to Timer11 for DCC interrupts --- DCCTimerSTM32.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index d055d59cf..640599fc1 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -37,22 +37,25 @@ HardwareSerial Serial1(PA10, PA15); // Rx=PA10, Tx=PA15 INTERRUPT_CALLBACK interruptHandler=0; // Let's use STM32's timer #1 until disabused of this notion -HardwareTimer timer(TIM1); +HardwareTimer timer(TIM11); // Timer IRQ handler -void Timer1_Handler() { +void Timer11_Handler() { interruptHandler(); } + + void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; noInterrupts(); + // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); timer.pause(); timer.setPrescaleFactor(1); // timer.setOverflow(CLOCK_CYCLES * 2); timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); - timer.attachInterrupt(Timer1_Handler); + timer.attachInterrupt(Timer11_Handler); timer.refresh(); timer.resume(); From 04188926b4943c443dad9f80b43fd5eda7a2f543 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 17 Aug 2022 09:19:47 +0200 Subject: [PATCH 276/870] Improve messages for power overload --- MotorDriver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index be5e304bb..616c02b24 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -303,9 +303,9 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { } // Write this after the fact as we want to turn on as fast as possible // because we don't know which output actually triggered the fault pin - DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %d"), trackno); + DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); } else { - DIAG(F("TRACK %d FAULT PIN ACTIVE - OVERLOAD"), trackno); + DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A'); if (lastCurrent < tripValue) { lastCurrent = tripValue; // exaggerate } @@ -323,7 +323,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned int maxmA=raw2mA(tripValue); power_good_counter=0; sampleDelay = power_sample_overload_wait; - DIAG(F("TRACK %d POWER OVERLOAD current=%d max=%d offtime=%d"), trackno, mA, maxmA, sampleDelay); + DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay); if (power_sample_overload_wait >= 10000) power_sample_overload_wait = 10000; else @@ -335,7 +335,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { setPower(POWERMODE::ON); sampleDelay = POWER_SAMPLE_ON_WAIT; // Debug code.... - DIAG(F("TRACK %d POWER RESET delay=%d"), trackno, sampleDelay); + DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay); break; default: sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. From c2c51e32c346cfb3cc064ed5faec3c19956ca93b Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 17 Aug 2022 15:28:52 +0800 Subject: [PATCH 277/870] Update STM32 timer 11 commentary --- DCCTimerSTM32.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 640599fc1..3f66a5fff 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -36,7 +36,9 @@ HardwareSerial Serial1(PA10, PA15); // Rx=PA10, Tx=PA15 INTERRUPT_CALLBACK interruptHandler=0; -// Let's use STM32's timer #1 until disabused of this notion +// Let's use STM32's timer #11 until disabused of this notion +// Timer #11 is used for "servo" library, but as DCC-EX is not using +// this libary, we should be free and clear. HardwareTimer timer(TIM11); // Timer IRQ handler @@ -44,8 +46,6 @@ void Timer11_Handler() { interruptHandler(); } - - void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; noInterrupts(); From 5eb04f77a887961d78f7b6ab19087120c134ab35 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 19 Aug 2022 14:33:04 +0200 Subject: [PATCH 278/870] Replace FLUSH-SHOVE with broadcastReply() --- CommandDistributor.cpp | 62 ++++++++++++++++-------------------------- CommandDistributor.h | 18 ++++++------ 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 9132d8aa0..9ccdda393 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -32,16 +32,19 @@ #if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS) - // use a buffer to allow broadcast - #define BUFFER broadcastBufferWriter - #define FLUSH broadcastBufferWriter->flush(); - #define SHOVE(type) broadcastToClients(type); - StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer(); +// use a buffer to allow broadcast +StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer(); +template void CommandDistributor::broadcastReply(clientType type, Targs... msg){ + broadcastBufferWriter->flush(); + StringFormatter::send(broadcastBufferWriter, msg...); + broadcastToClients(type); +} #else - // on a single USB connection config, write direct to Serial and ignore flush/shove - #define BUFFER &Serial - #define FLUSH - #define SHOVE(type) +// on a single USB connection config, write direct to Serial and ignore flush/shove +template void CommandDistributor::broadcastReply(clientType type, Targs... msg){ + (void)type; //shut up compiler warning + StringFormatter::send(&Serial, msg...); +} #endif #ifdef CD_HANDLE_RING @@ -103,8 +106,9 @@ void CommandDistributor::forget(byte clientId) { void CommandDistributor::broadcastToClients(clientType type) { byte rememberClient; + (void)rememberClient; // shut up compiler warning - /* Boadcast to Serials */ + // Broadcast to Serials if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString()); #ifdef CD_HANDLE_RING @@ -116,7 +120,7 @@ void CommandDistributor::broadcastToClients(clientType type) { //DIAG(F("CD precommit client %d"), rememberClient); ring->commit(); } - /* loop through ring clients */ + // loop through ring clients for (byte clientId=0; clientId\n"), on?'Q':'q', id); - SHOVE(COMMAND_TYPE) + broadcastReply(COMMAND_TYPE, F("<%c %d>\n"), on?'Q':'q', id); } void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { // For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed; // The string below contains serial and Withrottle protocols which should // be safe for both types. - FLUSH - StringFormatter::send(BUFFER,F("\n"),id, !isClosed); - SHOVE(COMMAND_TYPE) - -#ifdef CD_HANDLE_RING - FLUSH - StringFormatter::send(BUFFER,F("PTA%c%d\n"), isClosed?'2':'4', id); - SHOVE(WITHROTTLE_TYPE) + broadcastReply(COMMAND_TYPE, F("\n"),id, !isClosed); +#ifdef CD_HANDLE_RING + broadcastReply(WITHROTTLE_TYPE, F("PTA%c%d\n"), isClosed?'2':'4', id); #endif } void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; - FLUSH - StringFormatter::send(BUFFER,F("\n"), - sp->loco,slot,sp->speedCode,sp->functions); - SHOVE(COMMAND_TYPE) + broadcastReply(COMMAND_TYPE, F("\n"), sp->loco,slot,sp->speedCode,sp->functions); #ifdef CD_HANDLE_RING WiThrottle::markForBroadcast(sp->loco); #endif @@ -180,24 +174,16 @@ void CommandDistributor::broadcastPower() { else if (main) reason=F(" MAIN"); else if (prog) reason=F(" PROG"); else state='0'; - FLUSH - StringFormatter::send(BUFFER,F("

\n"),state,reason); - SHOVE(COMMAND_TYPE) + broadcastReply(COMMAND_TYPE, F("

\n"),state,reason); #ifdef CD_HANDLE_RING - FLUSH - StringFormatter::send(BUFFER,F("PPA%c\n"), main?'1':'0'); - SHOVE(WITHROTTLE_TYPE) + broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0'); #endif LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); } void CommandDistributor::broadcastText(const FSH * msg) { - FLUSH - StringFormatter::send(BUFFER,F("%S"),msg); - SHOVE(COMMAND_TYPE) + broadcastReply(COMMAND_TYPE, F("\n"),msg); #ifdef CD_HANDLE_RING - FLUSH - StringFormatter::send(BUFFER,F("Hm%S\n"), msg); - SHOVE(WITHROTTLE_TYPE) + broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg); #endif } diff --git a/CommandDistributor.h b/CommandDistributor.h index 3c3ec7bae..633ff77df 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -32,15 +32,6 @@ #endif class CommandDistributor { - -public : - static void parse(byte clientId,byte* buffer, RingStream * ring); - static void broadcastLoco(byte slot); - static void broadcastSensor(int16_t id, bool value); - static void broadcastTurnout(int16_t id, bool isClosed); - static void broadcastPower(); - static void broadcastText(const FSH * msg); - static void forget(byte clientId); private: enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; static void broadcastToClients(clientType type); @@ -49,6 +40,15 @@ public : static RingStream * ring; static clientType clients[8]; #endif +public : + static void parse(byte clientId,byte* buffer, RingStream * ring); + static void broadcastLoco(byte slot); + static void broadcastSensor(int16_t id, bool value); + static void broadcastTurnout(int16_t id, bool isClosed); + static void broadcastPower(); + static void broadcastText(const FSH * msg); + template static void broadcastReply(clientType type, Targs... msg); + static void forget(byte clientId); }; #endif From 4fb53572f511f832a7e6a7afa3ba91093959f9ae Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 Aug 2022 11:45:29 +0200 Subject: [PATCH 279/870] Diag outgoing messages on ESP32 --- WifiESP32.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index f00d6f428..2226a0138 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -294,6 +294,8 @@ void WifiESP::loop() { // buffer filled, end with '\0' so we can use it as C string buffer[count]='\0'; if((unsigned int)clientId <= clients.size() && clients[clientId].ok()) { + if (Diag::CMD || Diag::WITHROTTLE) + DIAG(F("SEND %d:%s"), clientId, buffer); clients[clientId].wifi.write(buffer,count); } else { DIAG(F("Unsent(%d): %s"), clientId, buffer); From 58bac3dc51dbf0f8a7fb050749c52c56c9ff0c3f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 Aug 2022 17:35:07 +0200 Subject: [PATCH 280/870] revert adding space to power reply because of RtDriver app --- CommandDistributor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 9ccdda393..8c79652ee 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -174,7 +174,7 @@ void CommandDistributor::broadcastPower() { else if (main) reason=F(" MAIN"); else if (prog) reason=F(" PROG"); else state='0'; - broadcastReply(COMMAND_TYPE, F("

\n"),state,reason); + broadcastReply(COMMAND_TYPE, F("\n"),state,reason); #ifdef CD_HANDLE_RING broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0'); #endif From a9ce9101e68b99be6913d23a15ad5994c435de0e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 Aug 2022 18:15:18 +0200 Subject: [PATCH 281/870] return success/fail from and command handling (setFn, parsef) --- DCC.cpp | 10 +++++--- DCC.h | 2 +- DCCEXParser.cpp | 68 +++++++++++++++++++++++-------------------------- DCCEXParser.h | 2 +- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 852430800..7759516bc 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -163,8 +163,9 @@ bool DCC::getThrottleDirection(int cab) { } // Set function to value on or off -void DCC::setFn( int cab, int16_t functionNumber, bool on) { - if (cab<=0 ) return; +bool DCC::setFn( int cab, int16_t functionNumber, bool on) { + if (cab<=0 ) return false; + if (functionNumber < 0) return false; if (functionNumber>28) { //non reminding advanced binary bit set @@ -183,11 +184,11 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) { b[nB++] = functionNumber >>7 ; // high order bits } DCCWaveform::mainTrack.schedulePacket(b, nB, 4); - return; + return true; } int reg = lookupSpeedTable(cab); - if (reg<0) return; + if (reg<0) return false; // Take care of functions: // Set state of function @@ -202,6 +203,7 @@ void DCC::setFn( int cab, int16_t functionNumber, bool on) { updateGroupflags(speedTable[reg].groupFlags, functionNumber); CommandDistributor::broadcastLoco(reg); } + return true; } // Flip function state diff --git a/DCC.h b/DCC.h index b45c45e43..46b3b8360 100644 --- a/DCC.h +++ b/DCC.h @@ -62,7 +62,7 @@ class DCC static void writeCVByteMain(int cab, int cv, byte bValue); static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); static void setFunction(int cab, byte fByte, byte eByte); - static void setFn(int cab, int16_t functionNumber, bool on); + static bool setFn(int cab, int16_t functionNumber, bool on); static void changeFn(int cab, int16_t functionNumber); static int getFn(int cab, int16_t functionNumber); static uint32_t getFunctionMap(int cab); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e986406dd..21a9d3d80 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -546,8 +546,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) if(params!=3) break; if (Diag::CMD) DIAG(F("Setting loco %d F%d %S"), p[0], p[1], p[2] ? F("ON") : F("OFF")); - DCC::setFn(p[0], p[1], p[2] == 1); - return; + if (DCC::setFn(p[0], p[1], p[2] == 1)) return; + break; #if WIFI_ON case '+': // Complex Wifi interface command (not usual parse) @@ -689,43 +689,39 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[]) //=================================== bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[]) { - // JMRI sends this info in DCC message format but it's not exactly - // convenient for other processing - if (params == 2) - { - byte instructionField = p[1] & 0xE0; // 1110 0000 - if (instructionField == 0x80) // 1000 0000 Function group 1 - { - // Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0 - byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01); - funcmap(p[0], normalized, 0, 4); - } - else if (instructionField == 0xA0) // 1010 0000 Function group 2 - { - if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12 - funcmap(p[0], p[1], 5, 8); - else - funcmap(p[0], p[1], 9, 12); - } - } - if (params == 3) - { - if (p[1] == 222) - funcmap(p[0], p[2], 13, 20); - else if (p[1] == 223) - funcmap(p[0], p[2], 21, 28); - } - (void)stream; // NO RESPONSE - return true; + // JMRI sends this info in DCC message format but it's not exactly + // convenient for other processing + if (params == 2) { + byte instructionField = p[1] & 0xE0; // 1110 0000 + if (instructionField == 0x80) { // 1000 0000 Function group 1 + // Shuffle bits from order F0 F4 F3 F2 F1 to F4 F3 F2 F1 F0 + byte normalized = (p[1] << 1 & 0x1e) | (p[1] >> 4 & 0x01); + return (funcmap(p[0], normalized, 0, 4)); + } else if (instructionField == 0xA0) { // 1010 0000 Function group 2 + if (p[1] & 0x10) // 0001 0000 Bit selects F5toF8 / F9toF12 + return (funcmap(p[0], p[1], 5, 8)); + else + return (funcmap(p[0], p[1], 9, 12)); + } + } + if (params == 3) { + if (p[1] == 222) { + return (funcmap(p[0], p[2], 13, 20)); + } else if (p[1] == 223) { + return (funcmap(p[0], p[2], 21, 28)); + } + } + (void)stream; // NO RESPONSE + return false; } -void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop) +bool DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop) { - for (int16_t i = fstart; i <= fstop; i++) - { - DCC::setFn(cab, i, value & 1); - value >>= 1; - } + for (int16_t i = fstart; i <= fstop; i++) { + if (! DCC::setFn(cab, i, value & 1)) return false; + value >>= 1; + } + return true; } //=================================== diff --git a/DCCEXParser.h b/DCCEXParser.h index 797eab014..bb05ebf18 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -71,7 +71,7 @@ struct DCCEXParser static FILTER_CALLBACK filterCallback; static FILTER_CALLBACK filterRMFTCallback; static AT_COMMAND_CALLBACK atCommandCallback; - static void funcmap(int16_t cab, byte value, byte fstart, byte fstop); + static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop); static void sendFlashList(Print * stream,const int16_t flashList[]); }; From 3c706926c5a498919ac26431cb157ba712690d97 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Aug 2022 08:46:47 +0200 Subject: [PATCH 282/870] more explanations to motor driver definitions and more conservative max current limit --- MotorDrivers.h | 77 +++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index 3e3464b10..3d930ee88 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -39,38 +39,53 @@ #define UNUSED_PIN 127 // inside int8_t #endif +// The MotorDriver definition is: +// // MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, // float senseFactor, unsigned int tripMilliamps, byte faultPin); // -// If the brakePin is negative that means the sense +// power_pin: Turns the board on/off. Often called ENABLE or PWM on the shield +// signal_pin: Where the DCC signal goes in. Often called DIR on the shield +// signal_pin2: Inverse of signal_pin. A few shields need this as well, can be replace by hardware inverter +// brake_pin: When tuned on, brake is set - output shortened (*) +// current_pin: Current sense voltage pin from shield to ADC +// senseFactor: Relation between volts on current_pin and actual output current +// tripMilliamps: Short circuit trip limit in milliampere, max 32767 (32.767A) +// faultPin: Some shields have a pin to to report a fault condition to the uCPU. High when fault occurs +// +// (*) If the brake_pin is negative that means the sense // of the brake pin on the motor bridge is inverted // (HIGH == release brake) -// -// Arduino standard Motor Shield + +// Arduino STANDARD Motor Shield, used on different architectures: + #if defined(ARDUINO_ARCH_SAMD) -// PMA - Setup for SAMD21 Sparkfun DEV board -// senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, -// and for 12-bit samples it's more like 0.488, but we probably need to tweak both these +// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3 +// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using +// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need +// to tweak both these #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 1500, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 1500, UNUSED_PIN) +#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD + #elif defined(ARDUINO_ARCH_ESP32) +// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the +// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the +// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that +// they are not useable at the same time as WiFi (what a bummer). The numbers below are the +// actual GPIO numbers. In comments the numbers the pins have on an Uno. #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.57, 2000, UNUSED_PIN), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.57, 2000, UNUSED_PIN) + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN) + #else +// STANDARD shield on any Arduino Uno or Mega compatible with the original specification. #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 2000, UNUSED_PIN) + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN) #endif -// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3 for 3v3 compatibility!!) -// senseFactor for 3.3v systems is 1.95 as calculated when using 10-bit A/D samples, -// and for 12-bit samples it's more like 0.488, but we probably need to tweak both these -#define SAMD_STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 2000, UNUSED_PIN) - // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \ @@ -87,9 +102,13 @@ // See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet. // Pololu Dual TB9051FTG Motor Shield -// This is the shield without modifications which means -// no HA waveform and no RailCom on an Arduino Mega 2560 -#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \ +// This is the shield without modifications. Unfortunately the TB9051FTG driver chip on +// the shield makes short delays when direction is switched. That means that the chip +// can NOT provide a standard conformant DCC signal independent how hard we try. If your +// Decoders tolerate that signal, use it by all mean but it is not recommended. Without +// modifications it uses the following pins below which means no HA waveform and no +// RailCom on an Arduino Mega 2560 but the DCC signal is broken anyway. +#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \ new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \ new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12) @@ -105,17 +124,17 @@ // FunduMoto Motor Shield #define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ - new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) + new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN) // IBT_2 Motor Board for Main and Arduino Motor Shield for Prog #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) + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, 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) + new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN) // Makeblock ORION UNO like sized board with integrated motor driver // This is like an Uno with H-bridge and RJ12 contacts instead of pin rows. @@ -132,7 +151,7 @@ // to an NANO EVERY board. You have to make the connectons from the shield to the board // as in this example or adjust the values yourself. #define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \ - new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN),\ - new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) + new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\ + new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN) #endif From 43b7b5d797a33f2b356a91c7dce9c0b825a94397 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Aug 2022 08:47:37 +0200 Subject: [PATCH 283/870] adjust max current limit to ADC capability --- MotorDriver.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 616c02b24..9fdb26f36 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -26,10 +26,14 @@ #include "DCCWaveform.h" #include "DCCTimer.h" #include "DIAG.h" +#define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC + #if defined(ARDUINO_ARCH_ESP32) #include #include #include +#undef ADC_INPUT_MAX_VALUE +#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC #define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28) int IRAM_ATTR local_adc1_get_raw(int channel) { @@ -103,7 +107,6 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11); senseOffset = adc1_get_raw(pinToADC1Channel(currentPin)); - DIAG(F("senseOffset c=%d"), senseOffset); #else pinMode(currentPin, INPUT); senseOffset=analogRead(currentPin); // value of sensor at zero current @@ -121,11 +124,23 @@ MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8 senseFactorInternal=sense_factor * senseScale; tripMilliamps=trip_milliamps; rawCurrentTripValue=mA2raw(trip_milliamps); - + + if (rawCurrentTripValue + senseOffset > ADC_INPUT_MAX_VALUE) { + // This would mean that the values obtained from the ADC never + // can reach the trip value. So independent of the current, the + // short circuit protection would never trip. So we adjust the + // trip value so that it is tiggered when the ADC reports it's + // maximum value instead. + + // DIAG(F("Changing short detection value from %d to %d mA"), + // raw2mA(rawCurrentTripValue), raw2mA(ADC_INPUT_MAX_VALUE-senseOffset)); + rawCurrentTripValue=ADC_INPUT_MAX_VALUE-senseOffset; + } + if (currentPin==UNUSED_PIN) - DIAG(F("MotorDriver ** WARNING ** No current or short detection")); + DIAG(F("** WARNING ** No current or short detection")); else { - DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"), + DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"), currentPin-A0, senseOffset,rawCurrentTripValue); // self testing diagnostic for the non-float converters... may be removed when happy From 827e4fef86752486ce7256ba0cf8de65a849c358 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 Aug 2022 08:48:33 +0200 Subject: [PATCH 284/870] be consistent about that tripValue is already over limit --- MotorDriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 9fdb26f36..8901fba59 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -313,7 +313,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { lastCurrent = -lastCurrent; setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again if (commonFaultPin) { - if (lastCurrent <= tripValue) { + if (lastCurrent < tripValue) { setPower(POWERMODE::ON); // maybe other track } // Write this after the fact as we want to turn on as fast as possible From 86215b28ae337d93802ce7ea52def25de15bdb4b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 23 Aug 2022 07:56:56 +0200 Subject: [PATCH 285/870] Bugfix: Preserve direction all times when switching between DCC and DC --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 9 +++++++++ MotorDriver.h | 4 +--- TrackManager.cpp | 29 ++++++++++++++++------------- version.h | 7 ++++++- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 78b65c06d..c5dc4550c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220817-1" +#define GITHUB_SHA "PORTX-HAL-20220823" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 8901fba59..3bbf6d343 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -239,20 +239,29 @@ void MotorDriver::setDCSignal(byte speedcode) { if (invertBrake) brake=255-brake; analogWrite(brakePin,brake); + //DIAG(F("DCSignal %d"), speedcode); if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { + noInterrupts(); HAVE_PORTA(shadowPORTA=PORTA); setSignal(tDir); HAVE_PORTA(PORTA=shadowPORTA); + interrupts(); } else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) { + noInterrupts(); HAVE_PORTB(shadowPORTB=PORTB); setSignal(tDir); HAVE_PORTB(PORTB=shadowPORTB); + interrupts(); } else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) { + noInterrupts(); HAVE_PORTC(shadowPORTC=PORTC); setSignal(tDir); HAVE_PORTC(PORTC=shadowPORTC); + interrupts(); } else { + noInterrupts(); setSignal(tDir); + interrupts(); } } diff --git a/MotorDriver.h b/MotorDriver.h index 1f97cfea6..960983247 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -121,8 +121,7 @@ class MotorDriver { // otherwise the call from interrupt context can undo whatever we do // from outside interrupt void setBrake( bool on, bool interruptContext=false); - __attribute__((always_inline)) inline void setSignal( bool high, bool interruptContext=false) { - if (!interruptContext) {noInterrupts();} + __attribute__((always_inline)) inline void setSignal( bool high) { if (trackPWM) { DCCTimer::setPWM(signalPin,high); } @@ -136,7 +135,6 @@ class MotorDriver { if (dualSignal) setHIGH(fastSignalPin2); } } - if (!interruptContext) {interrupts();} }; inline void enableSignal(bool on) { if (on) diff --git a/TrackManager.cpp b/TrackManager.cpp index dd9ad64e5..9a3952ca0 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -95,7 +95,7 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTA(shadowPORTA=PORTA); HAVE_PORTB(shadowPORTB=PORTB); HAVE_PORTC(shadowPORTC=PORTC); - APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on, true)); + APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); HAVE_PORTC(PORTC=shadowPORTC); @@ -113,7 +113,7 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTA(shadowPORTA=PORTA); HAVE_PORTB(shadowPORTB=PORTB); HAVE_PORTC(shadowPORTC=PORTC); - APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on, true)); + APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); HAVE_PORTC(PORTC=shadowPORTC); @@ -169,17 +169,11 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr trackDCAddr[trackToSet]=dcAddr; // When a track is switched, we must clear any side effects of its previous - // state, otherwise trains run away or just dont move. - if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) { - // DC tracks need to be given speed of the throttle for that cab address - // otherwise will not match other tracks on same cab. - // This also needs to allow for inverted DCX - applyDCSpeed(trackToSet); - - } - else { + // state, otherwise trains run away or just dont move. + + // This can be done BEFORE the PWM-Timer evaluation (methinks) + if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) { // DCC tracks need to have set the PWM to zero or they will not work. - // 128 is speed=0 and dir=0 and then loosen brake. track[trackToSet]->detachDCSignal(); track[trackToSet]->setBrake(false); } @@ -221,9 +215,18 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track } #else + // For ESP32 we just reinitialize the DCC Waveform DCCWaveform::begin(); #endif - + + // This block must be AFTER the PWM-Timer modifications + if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) { + // DC tracks need to be given speed of the throttle for that cab address + // otherwise will not match other tracks on same cab. + // This also needs to allow for inverted DCX + applyDCSpeed(trackToSet); + } + // Normal running tracks are set to the global power state track[trackToSet]->setPower( (mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ? diff --git a/version.h b/version.h index 6e743e189..ac1f5ea7f 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,12 @@ #include "StringFormatter.h" -#define VERSION "4.2.2 rc1" +#define VERSION "4.2.3 rc1" +// 4.2.3 Bugfix direction when togging between MAIN and DC +// Bugfix return fail when F/f argument out of range +// More error checking for out of bounds motor driver current trip limit +// 4.2.2 ESP32 beta +// JOIN/UMJOIN on ESP32 // 4.2.1 ESP32 alpha // Ready for alpha test on ESP32. Track switching with <=> untested // Send DCC signal on MAIN From 2f9c8faa77675f8c855565f6d8743dadc2b93bb9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 28 Aug 2022 19:44:07 +0200 Subject: [PATCH 286/870] ESP32: More WiFi diag --- WifiESP32.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 2226a0138..98b42ed02 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -122,7 +122,10 @@ bool WifiESP::setup(const char *SSid, // clean start WiFi.mode(WIFI_STA); WiFi.disconnect(true); - //WiFi.useStaticBuffers(true); // does not help either + // differnet settings that did not improve for haba + // WiFi.useStaticBuffers(true); + // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); + // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SECURITY); const char *yourNetwork = "Your network "; if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0) @@ -158,7 +161,8 @@ bool WifiESP::setup(const char *SSid, DIAG(F("Wifi STA IP 2nd try %s"),WiFi.localIP().toString().c_str()); wifiUp = true; } else { - DIAG(F("Fail 2nd try")); + DIAG(F("Wifi STA mode FAIL. Will revert to AP mode")); + haveSSID=false; } } } @@ -220,11 +224,22 @@ bool WifiESP::setup(const char *SSid, return true; } +const char *wlerror[] = { + "WL_IDLE_STATUS", + "WL_NO_SSID_AVAIL", + "WL_SCAN_COMPLETED", + "WL_CONNECTED", + "WL_CONNECT_FAILED", + "WL_CONNECTION_LOST", + "WL_DISCONNECTED" +}; + void WifiESP::loop() { int clientId; //tmp loop var // really no good way to check for LISTEN especially in AP mode? - if (APmode || WiFi.status() == WL_CONNECTED) { + wl_status_t wlStatus; + if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) { // loop over all clients and remove inactive for (clientId=0; clientId Date: Sun, 28 Aug 2022 22:54:26 +0200 Subject: [PATCH 287/870] ESP32: Experimental BT support, enable with #define SERIAL_BT_COMMANDS --- GITHUB_SHA.h | 2 +- SerialManager.cpp | 23 +++++++++++++++++++++++ WifiESP32.cpp | 33 +++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c5dc4550c..777795ed9 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220823" +#define GITHUB_SHA "PORTX-HAL-20220828" diff --git a/SerialManager.cpp b/SerialManager.cpp index 4b6bcf535..9e14ea329 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -24,6 +24,18 @@ #include "DCCEXParser.h" #include "StringFormatter.h" +#ifdef ARDUINO_ARCH_ESP32 +#ifdef SERIAL_BT_COMMANDS +#include +//#include +#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED) +#error No Bluetooth library available +#endif //ENABLED +BluetoothSerial SerialBT; +//BleSerial SerialBT; +#endif //COMMANDS +#endif //ESP32 + SerialManager * SerialManager::first=NULL; SerialManager::SerialManager(Stream * myserial) { @@ -51,6 +63,17 @@ void SerialManager::init() { Serial1.begin(115200); new SerialManager(&Serial1); #endif +#ifdef SERIAL_BT_COMMANDS + { + uint64_t chipid = ESP.getEfuseMac(); + char idstr[16] = {0}; + snprintf(idstr, 15, "DCCEX-%08X", + __builtin_bswap32((uint32_t)(chipid>>16))); + SerialBT.begin(idstr); + new SerialManager(&SerialBT); + delay(1000); + } +#endif } void SerialManager::broadcast(char * stringBuffer) { diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 98b42ed02..7754e114f 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -115,6 +115,10 @@ bool WifiESP::setup(const char *SSid, bool wifiUp = false; uint8_t tries = 40; + //#ifdef SERIAL_BT_COMMANDS + //return false; + //#endif + // tests // enableCoreWDT(1); // disableCoreWDT(0); @@ -135,7 +139,11 @@ bool WifiESP::setup(const char *SSid, if (haveSSID && havePassword) { WiFi.mode(WIFI_STA); +#ifdef SERIAL_BT_COMMANDS + WiFi.setSleep(true); +#else WiFi.setSleep(false); +#endif WiFi.setAutoReconnect(true); WiFi.begin(SSid, password); while (WiFi.status() != WL_CONNECTED && tries) { @@ -178,7 +186,11 @@ bool WifiESP::setup(const char *SSid, strPass.concat(strMac); WiFi.mode(WIFI_AP); +#ifdef SERIAL_BT_COMMANDS + WiFi.setSleep(true); +#else WiFi.setSleep(false); +#endif if (WiFi.softAP(strSSID.c_str(), havePassword ? password : strPass.c_str(), channel, false, 8)) { @@ -319,14 +331,19 @@ void WifiESP::loop() { } } else if (!APmode) { // in STA mode but not connected any more // kick it again - DIAG(F("Wifi aborted with error %s. Kicking Wifi!"), wlStatus <= 6 ? wlerror[wlStatus] : "UNKNOWN"); - esp_wifi_start(); - esp_wifi_connect(); - uint8_t tries=40; - while (WiFi.status() != WL_CONNECTED && tries) { - Serial.print('.'); - tries--; - delay(500); + if (wlStatus <= 6) { + DIAG(F("Wifi aborted with error %s. Kicking Wifi!"), wlerror[wlStatus]); + esp_wifi_start(); + esp_wifi_connect(); + uint8_t tries=40; + while (WiFi.status() != WL_CONNECTED && tries) { + Serial.print('.'); + tries--; + delay(500); + } + } else { + // all well, probably + //DIAG(F("Running BT")); } } From 08427abe70f5fe3fe61149d773b122be5821daa9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 30 Aug 2022 09:31:09 +0200 Subject: [PATCH 288/870] Workarounds for bugs and functionality not in the Arduino ESP32 package --- ESP32-fixes.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ ESP32-fixes.h | 24 ++++++++++++++++++++ MotorDriver.cpp | 8 +++++++ 3 files changed, 91 insertions(+) create mode 100644 ESP32-fixes.cpp create mode 100644 ESP32-fixes.h diff --git a/ESP32-fixes.cpp b/ESP32-fixes.cpp new file mode 100644 index 000000000..a21a1df44 --- /dev/null +++ b/ESP32-fixes.cpp @@ -0,0 +1,59 @@ +/* + * © 2022 Harald Barth + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 . + */ +#include +#include "ESP32-fixes.h" + +#include "esp32-hal.h" +#include "soc/soc_caps.h" + + +#ifdef SOC_LEDC_SUPPORT_HS_MODE +#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1) +#else +#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM) +#endif + +static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 }; +static int cnt_channel = LEDC_CHANNELS; + +void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) { + if (pin < SOC_GPIO_PIN_COUNT) { + if (pin_to_channel[pin] != 0) { + ledcSetup(pin_to_channel[pin], frequency, 8); + } + } +} + +void DCCEXanalogWrite(uint8_t pin, int value) { + if (pin < SOC_GPIO_PIN_COUNT) { + if (pin_to_channel[pin] == 0) { + if (!cnt_channel) { + log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS); + return; + } + pin_to_channel[pin] = --cnt_channel; + ledcAttachPin(pin, cnt_channel); + ledcSetup(cnt_channel, 1000, 8); + } else { + ledcAttachPin(pin, pin_to_channel[pin]); + } + ledcWrite(pin_to_channel[pin], value); + } +} diff --git a/ESP32-fixes.h b/ESP32-fixes.h new file mode 100644 index 000000000..64edaa323 --- /dev/null +++ b/ESP32-fixes.h @@ -0,0 +1,24 @@ +/* + * © 2022 Harald Barth + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 . + */ +#pragma once +#include +void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency); +void DCCEXanalogWrite(uint8_t pin, int value); + diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 3bbf6d343..a7a794a69 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -29,6 +29,7 @@ #define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC #if defined(ARDUINO_ARCH_ESP32) +#include "ESP32-fixes.h" #include #include #include @@ -229,6 +230,9 @@ int MotorDriver::getCurrentRaw() { void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) return; +#if defined(ARDUINO_ARCH_ESP32) + DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup +#endif // spedcoode is a dcc speed & direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 byte tDir=speedcode & 0x80; @@ -238,7 +242,11 @@ void MotorDriver::setDCSignal(byte speedcode) { else brake = 2 * (128-tSpeed); if (invertBrake) brake=255-brake; +#if defined(ARDUINO_ARCH_ESP32) + DCCEXanalogWrite(brakePin,brake); +#else analogWrite(brakePin,brake); +#endif //DIAG(F("DCSignal %d"), speedcode); if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) { noInterrupts(); From cec6d6dbe769b3b0491d9435764d0b3bee958540 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 30 Aug 2022 09:44:59 +0200 Subject: [PATCH 289/870] need to ifdef that --- ESP32-fixes.cpp | 2 ++ ESP32-fixes.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ESP32-fixes.cpp b/ESP32-fixes.cpp index a21a1df44..e3c350cb3 100644 --- a/ESP32-fixes.cpp +++ b/ESP32-fixes.cpp @@ -17,6 +17,7 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#ifdef ARDUINO_ARCH_ESP32 #include #include "ESP32-fixes.h" @@ -57,3 +58,4 @@ void DCCEXanalogWrite(uint8_t pin, int value) { ledcWrite(pin_to_channel[pin], value); } } +#endif diff --git a/ESP32-fixes.h b/ESP32-fixes.h index 64edaa323..0fd4a7f0a 100644 --- a/ESP32-fixes.h +++ b/ESP32-fixes.h @@ -17,8 +17,10 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#ifdef ARDUINO_ARCH_ESP32 #pragma once #include void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency); void DCCEXanalogWrite(uint8_t pin, int value); +#endif From 7d1d6bf1e1b83b75e21e638ef8b7f5bac6bbef7d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 30 Aug 2022 22:12:10 +0200 Subject: [PATCH 290/870] make inverted power pin possible, lower DC frequency on Mega and for Uno if inverted def is used --- MotorDriver.cpp | 22 ++++++++++++++++++---- MotorDriver.h | 3 ++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a7a794a69..f421b3b7b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -56,10 +56,17 @@ volatile portreg_t shadowPORTA; volatile portreg_t shadowPORTB; volatile portreg_t shadowPORTC; -MotorDriver::MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, +MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { powerPin=power_pin; - IODevice::write(powerPin,LOW);// set to OUTPUT and off + invertPower=power_pin < 0; + if (invertPower) { + powerPin = 0-power_pin; + IODevice::write(powerPin,HIGH);// set to OUTPUT and off + } else { + powerPin = power_pin; + IODevice::write(powerPin,LOW);// set to OUTPUT and off + } signalPin=signal_pin; getFastPin(F("SIG"),signalPin,fastSignalPin); @@ -165,14 +172,14 @@ void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { noInterrupts(); - IODevice::write(powerPin,HIGH); + IODevice::write(powerPin,invertPower ? LOW : HIGH); interrupts(); if (isProgTrack) DCCWaveform::progTrack.clearResets(); } else { noInterrupts(); - IODevice::write(powerPin,LOW); + IODevice::write(powerPin,invertPower ? HIGH : LOW); interrupts(); } powerMode=mode; @@ -232,6 +239,13 @@ void MotorDriver::setDCSignal(byte speedcode) { return; #if defined(ARDUINO_ARCH_ESP32) DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup +#endif +#if defined(ARDUINO_AVR_UNO) + TCCR2B = TCCR2B & B11111000 | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz +#endif +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + TCCR2B = TCCR2B & B11111000 | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz + TCCR4B = TCCR4B & B11111000 | B00000100; // same for timer 4 but maxcount and thus divisor differs #endif // spedcoode is a dcc speed & direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 diff --git a/MotorDriver.h b/MotorDriver.h index 960983247..5afc9e3b6 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -111,7 +111,7 @@ enum class POWERMODE : byte { OFF, ON, OVERLOAD }; class MotorDriver { public: - MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, + MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); void setPower( POWERMODE mode); POWERMODE getPower() { return powerMode;} @@ -194,6 +194,7 @@ class MotorDriver { FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; bool dualSignal; // true to use signalPin2 bool invertBrake; // brake pin passed as negative means pin is inverted + bool invertPower; // power pin passed as negative means pin is inverted // Raw to milliamp conversion factors avoiding float data types. // Milliamps=rawADCreading * sensefactorInternal / senseScale From a91152be95a8a97482747b47d980617ed55d0f0b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 30 Aug 2022 22:17:07 +0200 Subject: [PATCH 291/870] example of motor shield that does brake on pwn and that reversed which equals out --- MotorDrivers.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MotorDrivers.h b/MotorDrivers.h index 3d930ee88..5e4245058 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -84,6 +84,9 @@ #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN) +#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \ + new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN) #endif // Pololu Motor Shield From c6866aba861348487657f4f01f58e2b69f3de729 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 30 Aug 2022 22:45:10 +0200 Subject: [PATCH 292/870] Fundumoto board does not have brake --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 6 +++--- MotorDrivers.h | 2 +- version.h | 4 +++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 777795ed9..85abd5495 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220828" +#define GITHUB_SHA "PORTX-HAL-20220830" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index f421b3b7b..70881114c 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -241,11 +241,11 @@ void MotorDriver::setDCSignal(byte speedcode) { DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup #endif #if defined(ARDUINO_AVR_UNO) - TCCR2B = TCCR2B & B11111000 | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz + TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz #endif #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - TCCR2B = TCCR2B & B11111000 | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz - TCCR4B = TCCR4B & B11111000 | B00000100; // same for timer 4 but maxcount and thus divisor differs + TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz + TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs #endif // spedcoode is a dcc speed & direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 diff --git a/MotorDrivers.h b/MotorDrivers.h index 5e4245058..3d3d297d7 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -127,7 +127,7 @@ // FunduMoto Motor Shield #define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \ - new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(10, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN) // IBT_2 Motor Board for Main and Arduino Motor Shield for Prog diff --git a/version.h b/version.h index ac1f5ea7f..b4e46900c 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.3 rc1" +#define VERSION "4.2.4 rc1" +// 4.2.4 ESP32 experimental BT support +// More DC configurations possible and lower frequency // 4.2.3 Bugfix direction when togging between MAIN and DC // Bugfix return fail when F/f argument out of range // More error checking for out of bounds motor driver current trip limit From 7c4640ad91c2214943245b3ced9b4d83fcb7c72d Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 3 Sep 2022 13:06:02 +0800 Subject: [PATCH 293/870] STM32 Serial1 defs for F411RE and F446ZE --- DCCTimerSTM32.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 3f66a5fff..2cacaffde 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -32,8 +32,17 @@ #include "DIAG.h" //PMA temp debug #include "DCCTimer.h" -// STM32 doesn't have Serial1 defined by default -HardwareSerial Serial1(PA10, PA15); // Rx=PA10, Tx=PA15 +#define STM32F411RE // PMA - ideally this ought to be derived from within the STM32 support somehow + +#if defined(STM32F411RE) +// STM32F411RE doesn't have Serial1 defined by default +HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE +#elif defined(STM32F446ZE) +// STM32F446ZE doesn't have Serial1 defined by default +HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE +#else +#warning Serial1 not defined +#endif INTERRUPT_CALLBACK interruptHandler=0; // Let's use STM32's timer #11 until disabused of this notion From ad6c1384c919a2a590f6b3c24f9c3b8eb0e88c0f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 5 Sep 2022 09:02:35 +0200 Subject: [PATCH 294/870] EXRAIL bugfix, protect RANDWAIT from division by zero. Triggered by DELAYRANDOM(X, X+99) or less diff --- EXRAIL2.cpp | 2 +- GITHUB_SHA.h | 2 +- version.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 30b1b6de1..fb6c9ac7e 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -750,7 +750,7 @@ void RMFT2::loop2() { break; case OPCODE_RANDWAIT: - delayMe((micros()%operand) *100L); + delayMe(operand==0 ? 0 : (micros()%operand) *100L); break; case OPCODE_RED: diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 85abd5495..cf63b2e93 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220830" +#define GITHUB_SHA "PORTX-HAL-20220905" diff --git a/version.h b/version.h index b4e46900c..9a6bd5f48 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.4 rc1" +#define VERSION "4.2.4 rc2" // 4.2.4 ESP32 experimental BT support // More DC configurations possible and lower frequency // 4.2.3 Bugfix direction when togging between MAIN and DC From 02ed7828c14106a47b9e6d6765b08984cc3ccb6d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 13 Sep 2022 22:42:38 +0200 Subject: [PATCH 295/870] EX-RAIL bugfix: Could not read long loco addrs --- EXRAIL2.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index fb6c9ac7e..75336fda5 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -518,7 +518,15 @@ bool RMFT2::skipIfBlock() { /* static */ void RMFT2::readLocoCallback(int16_t cv) { - progtrackLocoId=cv; + if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr + progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr + if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr + DIAG(F("Long addr %d <= %d unsupported\n"), progtrackLocoId, HIGHEST_SHORT_ADDR); + progtrackLocoId = -1; + } + } else { + progtrackLocoId=cv; + } } void RMFT2::loop() { From 27a458a85053f19a6cb80644b6e66dfa576ea2d3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 14 Sep 2022 12:27:04 +0200 Subject: [PATCH 296/870] handle decoders that do not ack better --- DCC.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 7759516bc..23348425d 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -498,8 +498,8 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = { V0,WACK,NAKFAIL, SETCV, (ackOp)1, SETBYTEL, // low byte of word - WB,WACK, // some decoders don't ACK writes - VB,WACK,ITCB, + WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok + VB,WACK,ITC1, // Some decoders do not ack and need verify CALLFAIL }; @@ -516,14 +516,18 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = { V1,WACK,NAKFAIL, // Store high byte of address in cv 17 SETCV, (ackOp)17, - SETBYTEH, // high byte of word - WB,WACK, - VB,WACK,NAKFAIL, + SETBYTEH, // high byte of word + WB,WACK, // do write + ITSKIP, // if ACK, jump to SKIPTARGET + VB,WACK, // try verify instead + ITSKIP, // if ACK, jump to SKIPTARGET + CALLFAIL, // if still here, fail + SKIPTARGET, // store SETCV, (ackOp)18, SETBYTEL, // low byte of word - WB,WACK, - VB,WACK,ITC1, // callback(1) means Ok + WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok + VB,WACK,ITC1, // Some decoders do not ack and need verify CALLFAIL }; From 2da28ad2db24f6896a92974803b27c0bfefde189 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Sep 2022 22:23:18 +0200 Subject: [PATCH 297/870] version --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index fc73a4b00..4a6504649 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,7 @@ #include "StringFormatter.h" -#define VERSION "4.1.1 rc4" +#define VERSION "4.1.1" // 4.1.1 Bugfix: preserve turnout format // Bugfix: parse multiple commands in one buffer string correct // Bugfix: command signal status in Exrail From 431dc2bcc63d20becf82eb1cc2844b2eb9a9f2e1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Sep 2022 22:30:19 +0200 Subject: [PATCH 298/870] version 4.2.4 rc3 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index cf63b2e93..1beebb184 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220905" +#define GITHUB_SHA "PORTX-HAL-20220918" diff --git a/version.h b/version.h index 9a6bd5f48..3f8c118b6 100644 --- a/version.h +++ b/version.h @@ -4,9 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.4 rc2" +#define VERSION "4.2.4 rc3" // 4.2.4 ESP32 experimental BT support // More DC configurations possible and lower frequency +// Handle decoders that do not ack at write better // 4.2.3 Bugfix direction when togging between MAIN and DC // Bugfix return fail when F/f argument out of range // More error checking for out of bounds motor driver current trip limit From 88f16140f81255efb1f7708f6b35cab8c9d4cb8e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Sep 2022 23:18:40 +0200 Subject: [PATCH 299/870] document Bluetooth on ESP32 (SERIAL_BT_COMMANDS) --- SerialManager.cpp | 1 + config.example.h | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/SerialManager.cpp b/SerialManager.cpp index 9e14ea329..372cc852e 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -65,6 +65,7 @@ void SerialManager::init() { #endif #ifdef SERIAL_BT_COMMANDS { + //SerialBT.setPin("6666"); // choose other pin uint64_t chipid = ESP.getEfuseMac(); char idstr[16] = {0}; snprintf(idstr, 15, "DCCEX-%08X", diff --git a/config.example.h b/config.example.h index d6d60dcdd..7909f0a92 100644 --- a/config.example.h +++ b/config.example.h @@ -196,5 +196,24 @@ The configuration file for DCC-EX Command Station //#define SERIAL1_COMMANDS //#define SERIAL2_COMMANDS //#define SERIAL3_COMMANDS +// +// BLUETOOTH SERIAL ON ESP32 +// On ESP32 you have the possibility to use the builtin BT serial to connect to +// the CS. +// +// The CS shows up as a pairable BT Clasic device. Name is "DCCEX-hexnumber". +// BT is as an additional serial port, debug messages are still sent over USB, +// not BT serial. +// +// If you enable this there are some implications: +// 1. WiFi will sleep more (as WiFi and BT share the radio. So WiFi performance +// may suffer +// 2. The app will be bigger that 1.2MB, so the default partition scheme will not +// work any more. You need to choose a partition scheme with 2MB (or bigger). +// For example "NO OTA (2MB APP, 2MB SPIFFS)" in the Arduino IDE. +// 3. There is no securuity (PIN) implemented. Everyone in radio range can pair +// with your CS. +// +//#define SERIAL_BT_COMMANDS ///////////////////////////////////////////////////////////////////////////////////// From 0cf58a996d9a89be8c0d8a702b60bd48164a364c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Sep 2022 00:08:12 +0200 Subject: [PATCH 300/870] remove redunant diag --- CommandStation-EX.ino | 1 - 1 file changed, 1 deletion(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 7dca07173..f003ef626 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -73,7 +73,6 @@ void setup() SerialManager::init(); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); - DIAG(F("Platform: %S"), F(ARDUINO_TYPE)); // PMA - temporary CONDITIONAL_LCD_START { // This block is still executed for DIAGS if LCD not in use From 3da44be86f44c144bdd687769ee0a3005c5c9052 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Sep 2022 00:08:29 +0200 Subject: [PATCH 301/870] version 4.2.4 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 3f8c118b6..7851c4353 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.4 rc3" +#define VERSION "4.2.4" // 4.2.4 ESP32 experimental BT support // More DC configurations possible and lower frequency // Handle decoders that do not ack at write better From 052f5807f0e502478d2a491528a6f4c6c30887cf Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 24 Sep 2022 23:32:24 +0200 Subject: [PATCH 302/870] installer shell script --- installer.sh | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 installer.sh diff --git a/installer.sh b/installer.sh new file mode 100755 index 000000000..70a7858a6 --- /dev/null +++ b/installer.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +# +# © 2022 Harald Barth +# +# This file is part of CommandStation-EX +# +# 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 . +# +# +# Usage: mkdir DIRNAME ; cd DIRNAME ; ../installer.sh +# or from install directory ./installer.sh +# + +DCCEXGITURL="https://github.com/DCC-EX/CommandStation-EX" +ACLIINSTALL="https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh" +ACLI="./bin/arduino-cli" + +function need () { + type -p $1 > /dev/null && return + sudo apt-get install $1 + type -p $1 > /dev/null && return + echo "Could not install $1, abort" + exit 255 +} + + +need git +if test -d .git ; then + : assume we are right here + git pull +else + git clone "$DCCEXGITURL" + cd `basename "$DCCEXGITURL"` || exit 255 +fi +if test -f config.h ; then + : all well +else + # need to do this config better + cp -p config.example.h config.h +fi +need curl +if test -x "$ACLI" ; then + : all well +else + curl "$ACLIINSTALL" > acliinstall.sh + chmod +x acliinstall.sh + ./acliinstall.sh +fi + +$ACLI core update-index || exit 255 + +# Board discovery +BOARDS=/tmp/boards.$$ +$ACLI board list | grep serial > $BOARDS +if test x`< $BOARDS wc -l` = 'x1' ; then + LINE=`cat $BOARDS` +else + # ask user + echo "What board to use? (give line number)" + cat -n $BOARDS + echo -n "> " + LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $BOARDS` +fi +rm $BOARDS +PORT=`echo $LINE | cut -d" " -f1` +echo Will use port: $PORT + +# FQBN discovery +FQBN=`echo $LINE | egrep 'arduino:avr:[a-z][a-z]*' | sed 's/.*\(arduino:avr:[a-z][a-z]*\) .*/\1/1'` +if test x$FQBN = x ; then + # ask user + cat > /tmp/fqbn.$$ < " + FQBN=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' /tmp/fqbn.$$` +fi +rm /tmp/fqbn.$$ +echo FQBN is $FQBN + +# Install phase +$ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package +$ACLI board attach -p $PORT --fqbn $FQBN $PWD +$ACLI compile --fqbn $FQBN $PWD +$ACLI upload -v -t -p $PORT $PWD From c47e9b79ca50842427a1041e19475104e4921b01 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Oct 2022 00:43:46 +0200 Subject: [PATCH 303/870] do analogRead without need for noInterrupt - first test --- DCCTimerAVR.cpp | 11 +++++++++-- DCCWaveform.cpp | 2 +- MotorDriver.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++---- MotorDriver.h | 4 ++++ TrackManager.cpp | 34 +++++++++++++++++++++++++++++++++ TrackManager.h | 7 ++++--- 6 files changed, 97 insertions(+), 10 deletions(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index f5a35620b..c82afe5c4 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -44,8 +44,15 @@ INTERRUPT_CALLBACK interruptHandler=0; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; - noInterrupts(); - ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time + noInterrupts(); + // ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time + // Set up ADC for free running mode + ADMUX=(1< 3) + DIAG(F("Current sample old %d"), millis() - sampleCurrentTimestamp); + //interrupts(); #endif if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) @@ -233,6 +237,42 @@ int MotorDriver::getCurrentRaw() { return current; } +/* + * This should only be called in interrupt context + * Copies current value from HW to cached value in + * Motordriver. + */ +#pragma GCC push_options +#pragma GCC optimize ("-O3") +bool MotorDriver::sampleCurrentFromHW() { +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + const byte mask = 7; +#else + const byte mask = 31; +#endif + byte low, high; + //if (!bit_is_set(ADCSRA, ADIF)) + if (bit_is_set(ADCSRA, ADSC)) + return false; + // if ((ADMUX & mask) != (currentPin - A0)) + // return false; + low = ADCL; //must read low before high + high = ADCH; + bitSet(ADCSRA, ADIF); + sampleCurrent = (high << 8) | low; + sampleCurrentTimestamp = millis(); + return true; +} +void MotorDriver::startCurrentFromHW() { +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + const byte mask = 7; +#else + const byte mask = 31; +#endif + ADMUX=(1<sampleCurrentFromHW()) { + return; // no result, continue to wait + } + // found value, advance at least one track + waiting = false; + tr++; + if (tr >= MAX_TRACKS) tr = 0; + } + if (!waiting) { + // look for a valid track to sample or until we are around + while (count) { + if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) { + track[tr]->startCurrentFromHW(); + waiting = true; + break; + } + tr++; + if (tr >= MAX_TRACKS) tr = 0; + count--; + } + if (count == 0) { + DIAG(F("WRONG")); + } + } +} // The setup call is done this way so that the tracks can be in a list // from the config... the tracks default to NULL in the declaration diff --git a/TrackManager.h b/TrackManager.h index 45325f966..90a93455b 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -27,8 +27,9 @@ #include "MotorDriver.h" // Virtualised Motor shield multi-track hardware Interface -enum TRACK_MODE : byte {TRACK_MODE_OFF, TRACK_MODE_MAIN, TRACK_MODE_PROG, - TRACK_MODE_DC, TRACK_MODE_DCX, TRACK_MODE_EXT}; +// use powers of two so we can do logical and/or on the track modes in if clauses. +enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, + TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32}; // These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0; @@ -75,10 +76,10 @@ class TrackManager { static void setJoin(bool join); static bool isJoined() { return progTrackSyncMain;} static void setJoinRelayPin(byte joinRelayPin); + static void sampleCurrent(); static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackBoosted; // true when prog track is not current limited - private: static void addTrack(byte t, MotorDriver* driver); From 24a74754829662dfd6fd018888641330b3df158d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Oct 2022 00:44:46 +0200 Subject: [PATCH 304/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 1beebb184..a08ac6677 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-20220918" +#define GITHUB_SHA "PORTX-HAL-202210020044" From 24e5e648b8d369425135c39a1318dd61403fc430 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Oct 2022 13:40:46 +0200 Subject: [PATCH 305/870] improve current sense sampling on AVR --- DCCWaveform.cpp | 5 +++-- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 2 +- TrackManager.cpp | 23 +++++++++++++++-------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 30108136d..12ab87bb1 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -80,12 +80,13 @@ void DCCWaveform::interruptHandler() { // Set the signal state for both tracks TrackManager::setDCCSignal(sigMain); TrackManager::setPROGSignal(sigProg); - + + TrackManager::sampleCurrent(); + // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; progTrack.state=stateTransform[progTrack.state]; - TrackManager::sampleCurrent(); // WAVE_PENDING means we dont yet know what the next bit is if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a08ac6677..5696f3c85 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210020044" +#define GITHUB_SHA "PORTX-HAL-202210021139Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index dc700622f..c99e63ada 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -226,7 +226,7 @@ int MotorDriver::getCurrentRaw() { // noInterrupts(); //current = analogRead(currentPin)-senseOffset; current = sampleCurrent-senseOffset; - DIAG(F("%d %d"), current, sampleCurrentTimestamp); + //DIAG(F("%d %d"), current, sampleCurrentTimestamp); if ((millis() - sampleCurrentTimestamp) > 3) DIAG(F("Current sample old %d"), millis() - sampleCurrentTimestamp); //interrupts(); diff --git a/TrackManager.cpp b/TrackManager.cpp index adce218fb..e596d8a9d 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -59,32 +59,39 @@ byte TrackManager::tempProgTrack=MAX_TRACKS+1; */ void TrackManager::sampleCurrent() { static byte tr = 0; + byte trAtStart = tr; static bool waiting = false; - byte count = MAX_TRACKS-1; if (waiting) { if (! track[tr]->sampleCurrentFromHW()) { return; // no result, continue to wait } // found value, advance at least one track + // for scope debug track[1]->setBrake(0); waiting = false; tr++; - if (tr >= MAX_TRACKS) tr = 0; + if (tr > lastTrack) tr = 0; + if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) { + return; // We could continue but for prog track we + // rather do it in next interrupt beacuse + // that gives us well defined sampling point. + // For other tracks we care less unless we + // have only few (max 2) tracks. + } } if (!waiting) { // look for a valid track to sample or until we are around - while (count) { + while (true) { if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) { track[tr]->startCurrentFromHW(); + // for scope debug track[1]->setBrake(1); waiting = true; break; } tr++; - if (tr >= MAX_TRACKS) tr = 0; - count--; - } - if (count == 0) { - DIAG(F("WRONG")); + if (tr > lastTrack) tr = 0; + if (tr == trAtStart) // we are through and nothing found to do + return; } } } From 5e616a9eb2202c9230de540a3eaf6d44b77eea2e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Oct 2022 22:53:35 +0200 Subject: [PATCH 306/870] make compile for other arch but AVR --- DCCTimerAVR.cpp | 8 +++----- DCCWaveform.cpp | 2 ++ GITHUB_SHA.h | 2 +- MotorDriver.cpp | 42 ++++++++++++++---------------------------- MotorDriver.h | 17 +++++++++++++---- TrackManager.cpp | 2 ++ defines.h | 7 ++++++- 7 files changed, 41 insertions(+), 39 deletions(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index c82afe5c4..dfdbeeeb4 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -1,6 +1,6 @@ /* * © 2021 Mike S - * © 2021 Harald Barth + * © 2021-2022 Harald Barth * © 2021 Fred Decker * © 2021 Chris Harlow * © 2021 David Cutting @@ -48,10 +48,8 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time // Set up ADC for free running mode ADMUX=(1< 3) DIAG(F("Current sample old %d"), millis() - sampleCurrentTimestamp); - //interrupts(); -#endif +#else + if (!fromISR) noInterrupts(); + current = analogRead(currentPin)-senseOffset; + if (!fromISR) interrupts(); +#endif //ANALOG_READ_INTERRUPT +#endif //ARDUINO_ARCH_ESP32 if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) return (current == 0 ? -1 : -current); return current; } + +#ifdef ANALOG_READ_INTERRUPT /* * This should only be called in interrupt context * Copies current value from HW to cached value in @@ -245,11 +249,6 @@ int MotorDriver::getCurrentRaw() { #pragma GCC push_options #pragma GCC optimize ("-O3") bool MotorDriver::sampleCurrentFromHW() { -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - const byte mask = 7; -#else - const byte mask = 31; -#endif byte low, high; //if (!bit_is_set(ADCSRA, ADIF)) if (bit_is_set(ADCSRA, ADSC)) @@ -273,6 +272,7 @@ void MotorDriver::startCurrentFromHW() { bitSet(ADCSRA,ADSC); // start conversion } #pragma GCC pop_options +#endif //ANALOG_READ_INTERRUPT void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) @@ -327,20 +327,6 @@ void MotorDriver::setDCSignal(byte speedcode) { } } -int MotorDriver::getCurrentRawInInterrupt() { - - // IMPORTANT: This function must be called in Interrupt() time within the 56uS timer - // The default analogRead takes ~100uS which is catastrphic - // so DCCTimer has set the sample time to be much faster. - if (currentPin==UNUSED_PIN) return 0; -#ifdef ARDUINO_ARCH_ESP32 //On ESP we do all in loop() instead of in interrupt - return getCurrentRaw(); -#else - //return analogRead(currentPin)-senseOffset; - return getCurrentRaw(); -#endif -} - unsigned int MotorDriver::raw2mA( int raw) { return (int32_t)raw * senseFactorInternal / senseScale; } diff --git a/MotorDriver.h b/MotorDriver.h index 91106e902..9164b0a56 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -153,8 +153,12 @@ class MotorDriver { setDCSignal(128); #endif }; - int getCurrentRaw(); - int getCurrentRawInInterrupt(); + int getCurrentRaw() { + return getCurrentRaw(false); + } + int getCurrentRawInInterrupt() { + return getCurrentRaw(true); + }; unsigned int raw2mA( int raw); unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { @@ -183,9 +187,12 @@ class MotorDriver { isProgTrack = on; } void checkPowerOverload(bool useProgLimit, byte trackno); +#ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); +#endif private: + int getCurrentRaw(bool fromISR); bool isProgTrack = false; // tells us if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { @@ -214,8 +221,10 @@ class MotorDriver { unsigned int sampleDelay; int progTripValue; int lastCurrent; - unsigned long sampleCurrentTimestamp; - uint16_t sampleCurrent; +#ifdef ANALOG_READ_INTERRUPT + volatile unsigned long sampleCurrentTimestamp; + volatile uint16_t sampleCurrent; +#endif int maxmA; int tripmA; diff --git a/TrackManager.cpp b/TrackManager.cpp index e596d8a9d..cbb672a5b 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -54,6 +54,7 @@ int16_t TrackManager::joinRelay=UNUSED_PIN; byte TrackManager::tempProgTrack=MAX_TRACKS+1; #endif +#ifdef ANALOG_READ_INTERRUPT /* * sampleCurrent() runs from Interrupt */ @@ -95,6 +96,7 @@ void TrackManager::sampleCurrent() { } } } +#endif // The setup call is done this way so that the tracks can be in a list // from the config... the tracks default to NULL in the declaration diff --git a/defines.h b/defines.h index ba96b4edf..bc9941306 100644 --- a/defines.h +++ b/defines.h @@ -3,7 +3,7 @@ * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Fred Decker - * © 2020-2021 Harald Barth + * © 2020-2022 Harald Barth * © 2020-2021 Chris Harlow * * This file is part of CommandStation-EX @@ -43,6 +43,11 @@ #undef USB_SERIAL // Teensy has this defined by default... #define USB_SERIAL Serial +// All AVRs must read analog values from the DCC timer interrupt +#ifdef ARDUINO_ARCH_AVR +#define ANALOG_READ_INTERRUPT +#endif + #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" #undef HAS_ENOUGH_MEMORY From b7295c492358b7a124c70af0a1fb730d046b38b8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Oct 2022 00:32:48 +0200 Subject: [PATCH 307/870] add class Adc instead of motordriver specific analog pin read --- DCCTimer.h | 11 ++++++ DCCTimerAVR.cpp | 101 ++++++++++++++++++++++++++++++++++++++++++++---- DCCWaveform.cpp | 4 +- MotorDriver.cpp | 12 ++++-- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 34b2d9187..7864d70cc 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -93,4 +93,15 @@ class DCCTimer { }; +class Adc { +public: + static void reg(uint8_t pin); + static int read(uint8_t pin); +private: + static void scan(); + static void begin(); + static uint16_t usedpins; + static int *analogvals; + friend class DCCWaveform; + }; #endif diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index dfdbeeeb4..8dbe00b8a 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -26,7 +26,6 @@ // Please refer to DCCTimer.h for general comments about how this class works // This is to avoid repetition and duplication. #ifdef ARDUINO_ARCH_AVR - #include #include #include "DCCTimer.h" @@ -45,12 +44,6 @@ INTERRUPT_CALLBACK interruptHandler=0; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; noInterrupts(); - // ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time - // Set up ADC for free running mode - ADMUX=(1< NUM_ADC_INPUTS) + return; + if (analogvals == NULL) + analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + usedpins |= (1<setBrake(0); + waiting = false; + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + if (!waiting) { + if (usedpins == 0) // otherwise we would loop forever + return; + // look for a valid track to sample or until we are around + while (true) { + if (mask & usedpins) { + // start new ADC aquire on id + ADMUX=(1<setBrake(1); + waiting = true; + return; + } + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + } +} +#pragma GCC pop_options + +void Adc::begin() { + noInterrupts(); + // ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time + // Set up ADC for free running mode + ADMUX=(1< */ LOW}; void DCCWaveform::begin() { + Adc::begin(); DCCTimer::begin(DCCWaveform::interruptHandler); } @@ -82,7 +83,8 @@ void DCCWaveform::interruptHandler() { TrackManager::setPROGSignal(sigProg); #ifdef ANALOG_READ_INTERRUPT - TrackManager::sampleCurrent(); + //TrackManager::sampleCurrent(); + Adc::scan(); #endif // Move on in the state engine diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 1a645ece2..4edf05932 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -117,6 +117,9 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i senseOffset = adc1_get_raw(pinToADC1Channel(currentPin)); #else pinMode(currentPin, INPUT); + Adc::reg(currentPin); + // run analogRead as Adc::read() does not have any values before + // the waveform has been started. senseOffset=analogRead(currentPin); // value of sensor at zero current #endif } @@ -209,7 +212,7 @@ bool MotorDriver::canMeasureCurrent() { /* * Return the current reading as pin reading 0 to 1023. If the fault * pin is activated return a negative current to show active fault pin. - * As there is no -0, create a little and return -1 in that case. + * As there is no -0, cheat a little and return -1 in that case. * * senseOffset handles the case where a shield returns values above or below * a central value depending on direction. @@ -224,9 +227,10 @@ int MotorDriver::getCurrentRaw(bool fromISR) { current = local_adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset; #else #ifdef ANALOG_READ_INTERRUPT - current = sampleCurrent-senseOffset; - if ((millis() - sampleCurrentTimestamp) > 3) - DIAG(F("Current sample old %d"), millis() - sampleCurrentTimestamp); + current = Adc::read(currentPin)-senseOffset; + //current = sampleCurrent-senseOffset; + //if ((millis() - sampleCurrentTimestamp) > 3) + // DIAG(F("Current sample old %d"), millis() - sampleCurrentTimestamp); #else if (!fromISR) noInterrupts(); current = analogRead(currentPin)-senseOffset; From 367d2dfe209effc28e53949297746778b8202dd0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Oct 2022 00:35:58 +0200 Subject: [PATCH 308/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index aeaf1b8ed..3f4b1b144 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210022048Z" +#define GITHUB_SHA "PORTX-HAL-202210032235Z" From ee568fcd11ccff6fc715be9399b43be9da61692f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Oct 2022 21:55:13 +0200 Subject: [PATCH 309/870] make the Adc class functions the normal code path --- DCCACK.cpp | 2 +- DCCTimer.h | 8 +++++-- DCCTimerAVR.cpp | 13 ++++++++--- DCCTimerESP.cpp | 40 +++++++++++++++++++++++++++++++- DCCTimerSAMD.cpp | 59 ++++++++++++++++++++++++++++++++---------------- DCCWaveform.cpp | 4 +--- MotorDriver.cpp | 46 ++----------------------------------- MotorDriver.h | 8 +------ defines.h | 5 ---- 9 files changed, 100 insertions(+), 85 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index fa3d3480b..8c5e8c71e 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -425,7 +425,7 @@ void DCCACK::checkAck(byte sentResetsSincePacket) { return; } - int current=progDriver->getCurrentRawInInterrupt(); + int current=progDriver->getCurrentRaw(true); // true means "from interrupt" numAckSamples++; if (current > ackMaxCurrent) ackMaxCurrent=current; // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) diff --git a/DCCTimer.h b/DCCTimer.h index 7864d70cc..a9baf0e2b 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -95,8 +95,12 @@ class DCCTimer { class Adc { public: - static void reg(uint8_t pin); - static int read(uint8_t pin); + // On architectures that use the analog read during DCC waveform + // with specially configured ADC, for example AVR, init must be + // called PRIOR to the start of the waveform. It returns the + // current value so that an offset can be initialized. + static int init(uint8_t pin); + static int read(uint8_t pin, bool fromISR); private: static void scan(); static void begin(); diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 8dbe00b8a..74611e153 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -129,19 +129,26 @@ int * Adc::analogvals = NULL; /* * Register a new pin to be scanned + * Returns current reading of pin and + * stores that as well */ -void Adc::reg(uint8_t pin) { +int Adc::init(uint8_t pin) { uint8_t id = pin - A0; if (id > NUM_ADC_INPUTS) - return; + return -1023; + pinMode(pin, INPUT); + int value = analogRead(pin); if (analogvals == NULL) analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogvals[id] = value; usedpins |= (1< +#include +#include +#undef ADC_INPUT_MAX_VALUE +#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC +#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28) + +int IRAM_ATTR local_adc1_get_raw(int channel) { + uint16_t adc_value; + SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected + while (SENS.sar_slave_addr1.meas_status != 0); + SENS.sar_meas_start1.meas1_start_sar = 0; + SENS.sar_meas_start1.meas1_start_sar = 1; + while (SENS.sar_meas_start1.meas1_done_sar == 0); + adc_value = SENS.sar_meas_start1.meas1_data_sar; + return adc_value; +} #include "DCCTimer.h" INTERRUPT_CALLBACK interruptHandler=0; @@ -133,5 +150,26 @@ int DCCTimer::freeMemory() { void DCCTimer::reset() { ESP.restart(); } -#endif +int Adc::init(uint8_t pin) { + pinMode(pin, ANALOG); + adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11); + return local_adc1_get_raw(pinToADC1Channel(pin)); +} +/* + * Read function Adc::read(pin) to get value instead of analogRead(pin) + */ +int Adc::read(uint8_t pin, bool fromISR) { + return local_adc1_get_raw(pinToADC1Channel(pin)); +} +/* + * Scan function that is called from interrupt + */ +void Adc::scan() { +} + +void Adc::begin() { + adc1_config_width(ADC_WIDTH_BIT_12); +} + +#endif //ESP32 diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index e42d94c28..ddd9a4816 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -37,24 +37,6 @@ INTERRUPT_CALLBACK interruptHandler=0; void DCCTimer::begin(INTERRUPT_CALLBACK callback) { interruptHandler=callback; noInterrupts(); - - // Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, - // and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now - // enables 10-bit mode, although 12-bit is possible - ADC->CTRLA.bit.ENABLE = 0; // disable ADC - while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - - ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits - ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 - ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible - - ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time - ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 - ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 - - ADC->CTRLA.bit.ENABLE = 1; // enable ADC - while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization - // Timer setup - setup clock sources first REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1 GCLK_GENDIV_ID(4); // Apply to GCLK4 @@ -173,4 +155,43 @@ void DCCTimer::reset() { while(true) {}; } -#endif \ No newline at end of file +int Adc::init(uint8_t pin) { + return analogRead(pin); +} +/* + * Read function Adc::read(pin) to get value instead of analogRead(pin) + */ +int Adc::read(uint8_t pin, bool fromISR) { + int current; + if (!fromISR) noInterrupts(); + current = analogRead(pin); + if (!fromISR) interrupts(); + return current; +} +/* + * Scan function that is called from interrupt + */ +void Adc::scan() { +} + +void Adc::begin() { + noInterrupts(); + // Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, + // and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now + // enables 10-bit mode, although 12-bit is possible + ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 + ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible + + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 + + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization + interrupts(); +} +#endif diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 36612463a..4e142a78c 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -82,10 +82,8 @@ void DCCWaveform::interruptHandler() { TrackManager::setDCCSignal(sigMain); TrackManager::setPROGSignal(sigProg); -#ifdef ANALOG_READ_INTERRUPT - //TrackManager::sampleCurrent(); + // Refresh the values in the Adc object buffering the values of the ADC HW Adc::scan(); -#endif // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 4edf05932..666013f52 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -30,24 +30,6 @@ #if defined(ARDUINO_ARCH_ESP32) #include "ESP32-fixes.h" -#include -#include -#include -#undef ADC_INPUT_MAX_VALUE -#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC -#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28) - -int IRAM_ATTR local_adc1_get_raw(int channel) { - uint16_t adc_value; - SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected - while (SENS.sar_slave_addr1.meas_status != 0); - SENS.sar_meas_start1.meas1_start_sar = 0; - SENS.sar_meas_start1.meas1_start_sar = 1; - while (SENS.sar_meas_start1.meas1_done_sar == 0); - adc_value = SENS.sar_meas_start1.meas1_data_sar; - return adc_value; -} - #endif bool MotorDriver::commonFaultPin=false; @@ -110,18 +92,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i currentPin=current_pin; if (currentPin!=UNUSED_PIN) { -#ifdef ARDUINO_ARCH_ESP32 - pinMode(currentPin, ANALOG); - adc1_config_width(ADC_WIDTH_BIT_12); - adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11); - senseOffset = adc1_get_raw(pinToADC1Channel(currentPin)); -#else - pinMode(currentPin, INPUT); - Adc::reg(currentPin); - // run analogRead as Adc::read() does not have any values before - // the waveform has been started. - senseOffset=analogRead(currentPin); // value of sensor at zero current -#endif + senseOffset = Adc::init(currentPin); } faultPin=fault_pin; @@ -223,20 +194,7 @@ int MotorDriver::getCurrentRaw(bool fromISR) { (void)fromISR; if (currentPin==UNUSED_PIN) return 0; int current; -#ifdef ARDUINO_ARCH_ESP32 - current = local_adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset; -#else -#ifdef ANALOG_READ_INTERRUPT - current = Adc::read(currentPin)-senseOffset; - //current = sampleCurrent-senseOffset; - //if ((millis() - sampleCurrentTimestamp) > 3) - // DIAG(F("Current sample old %d"), millis() - sampleCurrentTimestamp); -#else - if (!fromISR) noInterrupts(); - current = analogRead(currentPin)-senseOffset; - if (!fromISR) interrupts(); -#endif //ANALOG_READ_INTERRUPT -#endif //ARDUINO_ARCH_ESP32 + current = Adc::read(currentPin, fromISR)-senseOffset; if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) return (current == 0 ? -1 : -current); diff --git a/MotorDriver.h b/MotorDriver.h index 9164b0a56..51f7654ac 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -153,12 +153,7 @@ class MotorDriver { setDCSignal(128); #endif }; - int getCurrentRaw() { - return getCurrentRaw(false); - } - int getCurrentRawInInterrupt() { - return getCurrentRaw(true); - }; + int getCurrentRaw(bool fromISR=false); unsigned int raw2mA( int raw); unsigned int mA2raw( unsigned int mA); inline bool brakeCanPWM() { @@ -192,7 +187,6 @@ class MotorDriver { void startCurrentFromHW(); #endif private: - int getCurrentRaw(bool fromISR); bool isProgTrack = false; // tells us if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { diff --git a/defines.h b/defines.h index bc9941306..3d54a6f42 100644 --- a/defines.h +++ b/defines.h @@ -43,11 +43,6 @@ #undef USB_SERIAL // Teensy has this defined by default... #define USB_SERIAL Serial -// All AVRs must read analog values from the DCC timer interrupt -#ifdef ARDUINO_ARCH_AVR -#define ANALOG_READ_INTERRUPT -#endif - #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" #undef HAS_ENOUGH_MEMORY From 3e214ab77a66fdfa8cbc09d5f587e6a62aec5149 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Oct 2022 21:56:58 +0200 Subject: [PATCH 310/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 3f4b1b144..241f0e4eb 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210032235Z" +#define GITHUB_SHA "PORTX-HAL-202210041956Z" From e0bf978f2b211af5222544d464ebe3365823488a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Oct 2022 22:19:51 +0200 Subject: [PATCH 311/870] Change Adc to ADCee because of SAMD conflict --- DCCTimer.h | 4 ++-- DCCTimerAVR.cpp | 14 +++++++------- DCCTimerESP.cpp | 10 +++++----- DCCTimerSAMD.cpp | 12 ++++++------ DCCWaveform.cpp | 6 +++--- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 4 ++-- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index a9baf0e2b..a50c6695e 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,7 +1,7 @@ /* * © 2022 Paul M Antoine * © 2021 Mike S - * © 2021 Harald Barth + * © 2021-2022 Harald Barth * © 2021 Fred Decker * All rights reserved. * @@ -93,7 +93,7 @@ class DCCTimer { }; -class Adc { +class ADCee { public: // On architectures that use the analog read during DCC waveform // with specially configured ADC, for example AVR, init must be diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 74611e153..6759ac9b3 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -124,15 +124,15 @@ void DCCTimer::reset() { #else #define NUM_ADC_INPUTS 15 #endif -uint16_t Adc::usedpins = 0; -int * Adc::analogvals = NULL; +uint16_t ADCee::usedpins = 0; +int * ADCee::analogvals = NULL; /* * Register a new pin to be scanned * Returns current reading of pin and * stores that as well */ -int Adc::init(uint8_t pin) { +int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; if (id > NUM_ADC_INPUTS) return -1023; @@ -145,9 +145,9 @@ int Adc::init(uint8_t pin) { return value; } /* - * Read function Adc::read(pin) to get value instead of analogRead(pin) + * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ -int Adc::read(uint8_t pin, bool fromISR) { +int ADCee::read(uint8_t pin, bool fromISR) { (void)fromISR; // AVR does ignore this arg uint8_t id = pin - A0; if ((usedpins & (1< */ LOW}; void DCCWaveform::begin() { - Adc::begin(); + ADCee::begin(); DCCTimer::begin(DCCWaveform::interruptHandler); } @@ -82,8 +82,8 @@ void DCCWaveform::interruptHandler() { TrackManager::setDCCSignal(sigMain); TrackManager::setPROGSignal(sigProg); - // Refresh the values in the Adc object buffering the values of the ADC HW - Adc::scan(); + // Refresh the values in the ADCee object buffering the values of the ADC HW + ADCee::scan(); // Move on in the state engine mainTrack.state=stateTransform[mainTrack.state]; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 241f0e4eb..c8cba40de 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210041956Z" +#define GITHUB_SHA "PORTX-HAL-202210042018Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 666013f52..cef38faa3 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -92,7 +92,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i currentPin=current_pin; if (currentPin!=UNUSED_PIN) { - senseOffset = Adc::init(currentPin); + senseOffset = ADCee::init(currentPin); } faultPin=fault_pin; @@ -194,7 +194,7 @@ int MotorDriver::getCurrentRaw(bool fromISR) { (void)fromISR; if (currentPin==UNUSED_PIN) return 0; int current; - current = Adc::read(currentPin, fromISR)-senseOffset; + current = ADCee::read(currentPin, fromISR)-senseOffset; if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) return (current == 0 ? -1 : -current); From 08c114fd2224a840cc79c2ed24fc8c8094123384 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 5 Oct 2022 22:27:27 +0200 Subject: [PATCH 312/870] Fixed minor typo in ESP32 code which broke everything --- DCCTimerESP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index f48d5d57f..2b10f18de 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -152,8 +152,9 @@ void DCCTimer::reset() { } int ADCee::init(uint8_t pin) { pinMode(pin, ANALOG); + adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11); - return local_adc1_get_raw(pinToADC1Channel(pin)); + return adc1_get_raw(pinToADC1Channel(pin)); } /* * Read function ADCee::read(pin) to get value instead of analogRead(pin) @@ -168,7 +169,6 @@ void ADCee::scan() { } void ADCee::begin() { - adc1_config_width(ADC_WIDTH_BIT_12); } #endif //ESP32 From 452ffc57256f3cb8ce9db1b07203466f3820d25d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 5 Oct 2022 23:14:07 +0200 Subject: [PATCH 313/870] changed IODevice code to use ADCee --- DCCTimer.h | 2 +- EXRAIL2.cpp | 9 +++++++++ GITHUB_SHA.h | 2 +- IODevice.cpp | 52 ++++++++++++++++++++++++++++++---------------------- IODevice.h | 8 +++++++- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index a50c6695e..d1ba7008e 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -100,7 +100,7 @@ class ADCee { // called PRIOR to the start of the waveform. It returns the // current value so that an offset can be initialized. static int init(uint8_t pin); - static int read(uint8_t pin, bool fromISR); + static int read(uint8_t pin, bool fromISR=false); private: static void scan(); static void begin(); diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 75336fda5..9095de814 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -180,6 +180,15 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { break; } + case OPCODE_ATGTE: + case OPCODE_ATLT: + case OPCODE_IFGTE: + case OPCODE_IFLT: + case OPCODE_DRIVE: { + IODevice::configureAnalogIn((VPIN)operand); + break; + } + case OPCODE_TURNOUT: { VPIN id=operand; int addr=GET_OPERAND(1); diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c8cba40de..ccc9d873b 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210042018Z" +#define GITHUB_SHA "PORTX-HAL-202210052105Z" diff --git a/IODevice.cpp b/IODevice.cpp index b2921950b..812d7ed74 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -25,6 +25,7 @@ #include "DIAG.h" #include "FSH.h" #include "IO_MCP23017.h" +#include "DCCTimer.h" #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR) #define USE_FAST_IO @@ -195,7 +196,17 @@ int IODevice::readAnalogue(VPIN vpin) { #ifdef DIAG_IO DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin); #endif - return false; + return -1023; +} +int IODevice::configureAnalogIn(VPIN vpin) { + for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { + if (dev->owns(vpin)) + return dev->_configureAnalogIn(vpin); + } +#ifdef DIAG_IO + DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin); +#endif + return -1023; } // Write value to virtual pin(s). If multiple devices are allocated the same pin @@ -361,11 +372,10 @@ int IODevice::read(VPIN vpin) { 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; + return ADCee::read(vpin); +} +int IODevice::configureAnalogIn(VPIN vpin) { + return ADCee::init(vpin); } void IODevice::loop() {} void IODevice::DumpAll() { @@ -467,7 +477,18 @@ int ArduinoPins::_read(VPIN vpin) { // Device-specific readAnalogue function (analogue input) int ArduinoPins::_readAnalogue(VPIN vpin) { - int pin = vpin; + if (vpin > 255) return -1023; + uint8_t pin = vpin; + int value = ADCee::read(pin); + + #ifdef DIAG_IO + DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value); + #endif + return value; +} +int ArduinoPins::_configureAnalogIn(VPIN vpin) { + if (vpin > 255) return -1023; + uint8_t pin = vpin; uint8_t mask = 1 << ((pin-_firstVpin) % 8); uint8_t index = (pin-_firstVpin) / 8; if (_pinModes[index] & mask) { @@ -479,22 +500,9 @@ int ArduinoPins::_readAnalogue(VPIN vpin) { 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(); - + int value = ADCee::init(pin); #ifdef DIAG_IO - DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value); + DIAG(F("configureAnalogIn Pin:%d Value:%d"), pin, value); #endif return value; } diff --git a/IODevice.h b/IODevice.h index 2a3437cf1..ce472671a 100644 --- a/IODevice.h +++ b/IODevice.h @@ -143,6 +143,7 @@ class IODevice { // read invokes the IODevice instance's _readAnalogue method. static int readAnalogue(VPIN vpin); + static int configureAnalogIn(VPIN vpin); // loop invokes the IODevice instance's _loop method. static void loop(); @@ -201,6 +202,10 @@ class IODevice { (void)vpin; return 0; }; + virtual int _configureAnalogIn(VPIN vpin) { + (void)vpin; + return 0; + }; // Method to perform updates on an ongoing basis (optionally implemented within device class) virtual void _loop(unsigned long currentMicros) { @@ -356,6 +361,7 @@ class ArduinoPins: public IODevice { // Device-specific read functions. int _read(VPIN vpin) override; int _readAnalogue(VPIN vpin) override; + int _configureAnalogIn(VPIN vpin) override; void _display() override; @@ -403,4 +409,4 @@ class EXTurntable : public IODevice { #include "IO_MCP23017.h" #include "IO_PCF8574.h" -#endif // iodevice_h \ No newline at end of file +#endif // iodevice_h From c36234df733c4904fa736df926f0b50ffd0b2ea6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 5 Oct 2022 23:43:09 +0200 Subject: [PATCH 314/870] Dont forget ESP32 has 12 bits ADC --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index ccc9d873b..879212377 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210052105Z" +#define GITHUB_SHA "PORTX-HAL-202210052142Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index cef38faa3..0fbc14029 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -26,10 +26,12 @@ #include "DCCWaveform.h" #include "DCCTimer.h" #include "DIAG.h" -#define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC #if defined(ARDUINO_ARCH_ESP32) #include "ESP32-fixes.h" +#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC (should be moved to ADCee as well) +#else +#define ADC_INPUT_MAX_VALUE 1023 // 10 bit ADC #endif bool MotorDriver::commonFaultPin=false; @@ -290,9 +292,11 @@ void MotorDriver::setDCSignal(byte speedcode) { } unsigned int MotorDriver::raw2mA( int raw) { + //DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale); return (int32_t)raw * senseFactorInternal / senseScale; } unsigned int MotorDriver::mA2raw( unsigned int mA) { + //DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal); return (int32_t)mA * senseScale / senseFactorInternal; } From 208905e7b90b1f36456de446f24284ab9f2aba45 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 10 Oct 2022 07:59:54 +0200 Subject: [PATCH 315/870] explain in comment --- DCCTimer.h | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index d1ba7008e..5de01a126 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -93,19 +93,36 @@ class DCCTimer { }; +// Class ADCee implements caching of the ADC value for platforms which +// have a too slow ADC read to wait for. On these platforms the ADC is +// scanned continiously in the background from an ISR. On such +// architectures that use the analog read during DCC waveform with +// specially configured ADC, for example AVR, init must be called +// PRIOR to the start of the waveform. It returns the current value so +// that an offset can be initialized. class ADCee { public: - // On architectures that use the analog read during DCC waveform - // with specially configured ADC, for example AVR, init must be - // called PRIOR to the start of the waveform. It returns the - // current value so that an offset can be initialized. + // init does add the pin to the list of scanned pins (if this + // platform's implementation scans pins) and returns the first + // read value. It is called before the regular scan is started. static int init(uint8_t pin); + // read does read the pin value from the scanned cache or directly + // if this is a platform that does not scan. fromISR is a hint if + // it was called from ISR because for some implementations that + // makes a difference. static int read(uint8_t pin, bool fromISR=false); private: + // On platforms that scan, it is called from waveform ISR + // only on a regular basis. static void scan(); + // begin is called for any setup that must be done before + // scan can be called. static void begin(); + // bit array of used pins (max 16) static uint16_t usedpins; + // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; + // friend so that we can call scan() and begin() friend class DCCWaveform; }; #endif From 4d205be0079e3eda80c582f8afd041b20293b798 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 12 Oct 2022 16:46:37 +0800 Subject: [PATCH 316/870] SAMD21 ADCee class full implementation --- DCCTimer.h | 2 +- DCCTimerSAMD.cpp | 91 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 5de01a126..4733dbecf 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,5 +1,5 @@ /* - * © 2022 Paul M Antoine + * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021-2022 Harald Barth * © 2021 Fred Decker diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 671dab243..df11b1eae 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -1,5 +1,5 @@ /* - * © 2022 Paul M Antoine + * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021-2022 Harald Barth * © 2021 Fred Decker @@ -28,9 +28,8 @@ // This is to avoid repetition and duplication. #ifdef ARDUINO_ARCH_SAMD -#include "FSH.h" //PMA temp debug -#include "DIAG.h" //PMA temp debug #include "DCCTimer.h" +#include INTERRUPT_CALLBACK interruptHandler=0; @@ -155,36 +154,100 @@ void DCCTimer::reset() { while(true) {}; } +#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS + +uint16_t ADCee::usedpins = 0; +int * ADCee::analogvals = NULL; + int ADCee::init(uint8_t pin) { - return analogRead(pin); + uint id = pin - A0; + + if (id > NUM_ADC_INPUTS) + return -1023; + pinMode(pin, INPUT); + int value = analogRead(pin); + if (analogvals == NULL) + analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogvals[id] = value; + usedpins |= (1<INTFLAG.bit.RESRDY == 0) + return; // no result, continue to wait + // found value + analogvals[id] = ADC->RESULT.reg; + // Clear the Data Ready flag + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + // advance at least one track + // for scope debug TrackManager::track[1]->setBrake(0); + waiting = false; + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + if (!waiting) { + if (usedpins == 0) // otherwise we would loop forever + return; + // look for a valid track to sample or until we are around + while (true) { + if (mask & usedpins) { + // start new ADC aquire on id + ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id].ulADCChannelNumber; // Selection for the positive ADC input + // Start conversion + ADC->SWTRIG.bit.START = 1; + // for scope debug TrackManager::track[1]->setBrake(1); + waiting = true; + return; + } + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + } } +#pragma GCC pop_options void ADCee::begin() { noInterrupts(); // Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, - // and we need sub-100uS. This code sets it to a read speed of around 21uS, and for now - // enables 10-bit mode, although 12-bit is possible + // and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables + // 12-bit mode ADC->CTRLA.bit.ENABLE = 0; // disable ADC while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits - ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 - ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible + ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV16 | // divide Clock by 16 + ADC_CTRLB_RESSEL_12BIT; // Result is 12 bits, 10 bits possible ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 From eb766aa27f7f9d14c012c7cdde8a7932ed8dcdbd Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 12 Oct 2022 17:12:00 +0800 Subject: [PATCH 317/870] Fix to SAMD21 ADCee pin init --- DCCTimerSAMD.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index df11b1eae..4c0427690 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -164,12 +164,16 @@ int ADCee::init(uint8_t pin) { if (id > NUM_ADC_INPUTS) return -1023; - pinMode(pin, INPUT); +// pinMode(pin, INPUT); int value = analogRead(pin); if (analogvals == NULL) analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); analogvals[id] = value; usedpins |= (1<INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input return value; } From 65364212ca55a5acfdeed85984518cd7b23452a3 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 12 Oct 2022 17:30:20 +0800 Subject: [PATCH 318/870] SAMD21 fix analogresolution in ADCee::init --- DCCTimerSAMD.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 4c0427690..32e977fe8 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -164,7 +164,7 @@ int ADCee::init(uint8_t pin) { if (id > NUM_ADC_INPUTS) return -1023; -// pinMode(pin, INPUT); + analogReadResolution(12); // Consistent with settings in ADCee::begin below int value = analogRead(pin); if (analogvals == NULL) analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); From cf89fe2a72826c3a4dd4d058712e19fb61944613 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 12 Oct 2022 23:45:10 +0200 Subject: [PATCH 319/870] make max ADC value a per platform ADC function --- DCCTimer.h | 2 ++ DCCTimerAVR.cpp | 3 +++ DCCTimerESP.cpp | 3 +++ DCCTimerSAMD.cpp | 3 +++ MotorDriver.cpp | 9 +++------ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 4733dbecf..ed87e1fd1 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -111,6 +111,8 @@ class ADCee { // it was called from ISR because for some implementations that // makes a difference. static int read(uint8_t pin, bool fromISR=false); + // returns possible max value that the ADC can return + static int16_t ADCmax(); private: // On platforms that scan, it is called from waveform ISR // only on a regular basis. diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 6759ac9b3..f29d1f3ff 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -144,6 +144,9 @@ int ADCee::init(uint8_t pin) { usedpins |= (1< ADC_INPUT_MAX_VALUE) { + if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) { // This would mean that the values obtained from the ADC never // can reach the trip value. So independent of the current, the // short circuit protection would never trip. So we adjust the @@ -117,8 +114,8 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i // maximum value instead. // DIAG(F("Changing short detection value from %d to %d mA"), - // raw2mA(rawCurrentTripValue), raw2mA(ADC_INPUT_MAX_VALUE-senseOffset)); - rawCurrentTripValue=ADC_INPUT_MAX_VALUE-senseOffset; + // raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset)); + rawCurrentTripValue=ADCee::ADCmax()-senseOffset; } if (currentPin==UNUSED_PIN) From 578cbd08e54d69b820dd3df5c4562d7f5aea4d09 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 13 Oct 2022 09:12:51 +0800 Subject: [PATCH 320/870] SAMD21 senseFactor fix for 12-bit --- MotorDrivers.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index 3d3d297d7..7f217cef9 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -1,5 +1,5 @@ /* - * © 2022 Paul M Antoine + * © 2022 Paul M. Antoine * © 2021 Fred Decker * © 2020-2022 Harald Barth * (c) 2020 Chris Harlow. All rights reserved. @@ -65,8 +65,8 @@ // 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need // to tweak both these #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 1.95, 1500, UNUSED_PIN), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.95, 1500, UNUSED_PIN) + new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN) #define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD #elif defined(ARDUINO_ARCH_ESP32) From f39a9d151088ac61fed6233200485930ed4d702b Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 14 Oct 2022 18:55:12 +0800 Subject: [PATCH 321/870] SAMD21 ADCee fixes --- DCCTimerSAMD.cpp | 71 ++++++++++++++++++++++++++++++++---------------- MotorDrivers.h | 3 +- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 802c7e77d..481f6232b 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -30,6 +30,8 @@ #include "DCCTimer.h" #include +#include +#include INTERRUPT_CALLBACK interruptHandler=0; @@ -161,20 +163,46 @@ int * ADCee::analogvals = NULL; int ADCee::init(uint8_t pin) { uint id = pin - A0; + int value = 0; if (id > NUM_ADC_INPUTS) return -1023; - analogReadResolution(12); // Consistent with settings in ADCee::begin below - int value = analogRead(pin); + + // Dummy read using Arduino library + analogReadResolution(12); + value = analogRead(pin); + + // Reconfigure ADC + ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16 + ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0 + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + // Permanently configure SAMD IO MUX for that pin + pinPeripheral(pin, PIO_ANALOG); + ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input + + // Start conversion + ADC->SWTRIG.bit.START = 1; + + // Wait for the conversion to be ready + while (ADC->INTFLAG.bit.RESRDY == 0); // Waiting for conversion to complete + + // Read the value + value = ADC->RESULT.reg; + if (analogvals == NULL) analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); analogvals[id] = value; usedpins |= (1<INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input - return value; } int16_t ADCee::ADCmax() { @@ -207,8 +235,6 @@ void ADCee::scan() { return; // no result, continue to wait // found value analogvals[id] = ADC->RESULT.reg; - // Clear the Data Ready flag - ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; // advance at least one track // for scope debug TrackManager::track[1]->setBrake(0); waiting = false; @@ -226,8 +252,8 @@ void ADCee::scan() { while (true) { if (mask & usedpins) { // start new ADC aquire on id - ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id].ulADCChannelNumber; // Selection for the positive ADC input - // Start conversion + ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id + A0].ulADCChannelNumber; // Selection for the positive ADC input + // Start conversion ADC->SWTRIG.bit.START = 1; // for scope debug TrackManager::track[1]->setBrake(1); waiting = true; @@ -249,19 +275,18 @@ void ADCee::begin() { // Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, // and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables // 12-bit mode - ADC->CTRLA.bit.ENABLE = 0; // disable ADC - while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - - ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits - ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV16 | // divide Clock by 16 - ADC_CTRLB_RESSEL_12BIT; // Result is 12 bits, 10 bits possible - - ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time - ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 - ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 - - ADC->CTRLA.bit.ENABLE = 1; // enable ADC - while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization + // Reconfigure ADC + ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16 + ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0 + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization interrupts(); } #endif diff --git a/MotorDrivers.h b/MotorDrivers.h index 7f217cef9..ae556ca6f 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -59,7 +59,7 @@ // Arduino STANDARD Motor Shield, used on different architectures: -#if defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) // Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3 // for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using // 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need @@ -68,6 +68,7 @@ new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN) #define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD +#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD #elif defined(ARDUINO_ARCH_ESP32) // STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the From 81b5b25430d14f3899623973f137835babb59cbc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 14 Oct 2022 14:20:32 +0200 Subject: [PATCH 322/870] disable EEPROM on ESP32 --- config.example.h | 3 +++ defines.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/config.example.h b/config.example.h index 7909f0a92..8bed4ef7a 100644 --- a/config.example.h +++ b/config.example.h @@ -143,6 +143,9 @@ The configuration file for DCC-EX Command Station // and want to use the EX-RAIL automation. Otherwise you do not have enough RAM // to do that. Of course, then none of the EEPROM related commands work. // +// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled, +// at least until it works. +// // #define DISABLE_EEPROM ///////////////////////////////////////////////////////////////////////////////////// diff --git a/defines.h b/defines.h index 3d54a6f42..c4d38a3c6 100644 --- a/defines.h +++ b/defines.h @@ -114,8 +114,12 @@ #endif #elif defined(ARDUINO_ARCH_ESP8266) #define ARDUINO_TYPE "ESP8266" +#warning "ESP8266 platform untested, you are on your own" #elif defined(ARDUINO_ARCH_ESP32) #define ARDUINO_TYPE "ESP32" +#ifndef DISABLE_EEPROM +#define DISABLE_EEPROM +#endif #elif defined(ARDUINO_ARCH_SAMD) #define ARDUINO_TYPE "SAMD21" #undef USB_SERIAL From 1c73a3d7bff58bd7a8f4eb2ef691972099ed95f6 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sun, 16 Oct 2022 16:15:02 +0800 Subject: [PATCH 323/870] SAMD21 IRQ priority exploration --- DCCTimerSAMD.cpp | 6 +++--- I2CManager_SAMD.h | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 481f6232b..7f1016907 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -30,8 +30,6 @@ #include "DCCTimer.h" #include -#include -#include INTERRUPT_CALLBACK interruptHandler=0; @@ -71,7 +69,9 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // Set the interrupt condition, priority and enable it in the NVIC TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow - NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority + int USBprio = NVIC_GetPriority((IRQn_Type) USB_IRQn); // Fetch the USB priority + NVIC_SetPriority((IRQn_Type)TCC0_IRQn, USBprio); // Match the USB priority +// NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt interrupts(); } diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 4dd27901a..cd57507d6 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -113,7 +113,8 @@ void I2CManagerClass::I2C_init() #if defined(I2C_USE_INTERRUPTS) // Setting NVIC NVIC_EnableIRQ(SERCOM3_IRQn); - NVIC_SetPriority (SERCOM3_IRQn, 0); /* set Priority */ + NVIC_SetPriority (SERCOM3_IRQn, SERCOM_NVIC_PRIORITY); // Match default SERCOM priorities +// NVIC_SetPriority (SERCOM3_IRQn, 0); // Set highest priority // Enable all interrupts s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR; From 2a51fa6f5ddabd90dd7a28f2c2522be6015fb086 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 18 Oct 2022 22:18:11 +0200 Subject: [PATCH 324/870] Do not specify inline (because of linker) --- DCCTimerAVR.cpp | 2 +- GITHUB_SHA.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index f29d1f3ff..a8f898432 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -144,7 +144,7 @@ int ADCee::init(uint8_t pin) { usedpins |= (1< Date: Tue, 18 Oct 2022 22:20:38 +0200 Subject: [PATCH 325/870] remove possible RAM corruption and improve findUniqThrottle --- WiThrottle.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 46ee81f4a..d54c65018 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -77,28 +77,38 @@ static uint8_t xstrcmp(const char *s1, const char *s2) { void WiThrottle::findUniqThrottle(int id, char *u) { WiThrottle *wtmyid = NULL; WiThrottle *wtmyuniq = NULL; - u[16] = '\0'; + + // search 1, look for clientid match for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ - //DIAG(F("looking at %d as %s"),wt->clientid, wt->uniq); - if (wtmyid == NULL && wt->clientid == id) + if (wt->clientid == id) { wtmyid = wt; - if (wtmyuniq == NULL && xstrcmp(u, wt->uniq) == 0) + if (xstrcmp(u, wt->uniq) == 0) // should be most common case + return; + break; + } + } + // search 2, look for string match + for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ + if (xstrcmp(u, wt->uniq) == 0) { wtmyuniq = wt; + break; + } } + + // analyse result of the two for loops: if (wtmyid == NULL) { // should not happen DIAG(F("Did not find my own wiThrottle handle")); return; } - if (wtmyid == wtmyuniq) { // all well, just return; - return; - } + // wtmyuniq == wtmyid has already returned in for loop 1 if (wtmyuniq == NULL) { // register uniq in the found id strncpy(wtmyid->uniq, u, 16); wtmyid->uniq[16] = '\0'; if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq); return; } - // do the copy (all other options above) + // if we get here wtmyid and wtmyuniq point on objects but differnet ones + // so we need to do the copy (all other options covered above) for(int n=0; n < MAX_MY_LOCO; n++) wtmyid->myLocos[n] = wtmyuniq->myLocos[n]; wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable; From e8180603baae8e1a3bacb5a0d5465cbfc4b65500 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 18 Oct 2022 23:41:21 +0200 Subject: [PATCH 326/870] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index ec9acd721..3e0461368 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-202210182017Z" +#define GITHUB_SHA "PORTX-HAL-cursense2-202210182141Z" From 9afce0a7dfeceabe0dcfaf15f5c5c28e4caecc27 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 19 Oct 2022 11:10:37 +0200 Subject: [PATCH 327/870] change from xstrcmp to xstrncmp --- GITHUB_SHA.h | 2 +- WiThrottle.cpp | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 3e0461368..3e9fb5a7c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-cursense2-202210182141Z" +#define GITHUB_SHA "PORTX-HAL-cursense2-202210190909Z" diff --git a/WiThrottle.cpp b/WiThrottle.cpp index d54c65018..3df56dba9 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -63,15 +63,16 @@ WiThrottle * WiThrottle::firstThrottle=NULL; -static uint8_t xstrcmp(const char *s1, const char *s2) { - while(*s1 != '\0' && *s2 != '\0') { - if (*s1 != *s2) return 1; - s1++; - s2++; - } - if (*s1 == '\0' && *s2 == '\0') +static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) { + if (n == 0) return 0; - return 1; + do { + if (*s1 != *s2++) + return 1; + if (*s1++ == 0) + break; + } while (--n != 0); + return 0; } void WiThrottle::findUniqThrottle(int id, char *u) { @@ -81,15 +82,15 @@ void WiThrottle::findUniqThrottle(int id, char *u) { // search 1, look for clientid match for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ if (wt->clientid == id) { - wtmyid = wt; - if (xstrcmp(u, wt->uniq) == 0) // should be most common case + if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case return; + wtmyid = wt; break; } } // search 2, look for string match for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ - if (xstrcmp(u, wt->uniq) == 0) { + if (xstrncmp(u, wt->uniq, 16) == 0) { wtmyuniq = wt; break; } From 56ed6ab6dc12e805265463f77def6a9d0e9f1fae Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 20 Oct 2022 00:52:19 +0200 Subject: [PATCH 328/870] free memory value updated on ESP --- DCCRMT.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 4c5fe4ef7..631cc16b9 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -21,6 +21,7 @@ #include "defines.h" #include "DIAG.h" #include "DCCRMT.h" +#include "DCCTimer.h" #include "DCCWaveform.h" // for MAX_PACKET_SIZE #include "soc/gpio_sig_map.h" @@ -67,6 +68,8 @@ RMTChannel *channelHandle[8] = { 0 }; void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) { RMTChannel *tt = channelHandle[channel]; if (tt) tt->RMTinterrupt(); + if (channel == 0) + DCCTimer::updateMinimumFreeMemoryISR(0); } RMTChannel::RMTChannel(pinpair pins, bool isMain) { From f56a9a2c43efd79aefbc5bdd93e92cee423fa8ed Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 20 Oct 2022 00:53:05 +0200 Subject: [PATCH 329/870] compiler warning fixes and stupid cast for ESP toolchain --- IO_DFPlayer.h | 2 +- IO_HCSR04.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 575668bfd..c60d1168f 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -161,7 +161,7 @@ class DFPlayer : public IODevice { uint8_t pin = vpin - _firstVpin; // Validate parameter. - volume = min(30,volume); + volume = min((uint8_t)30,volume); if (pin == 0) { // Play track diff --git a/IO_HCSR04.h b/IO_HCSR04.h index d56dda389..e7b5622b1 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -138,7 +138,7 @@ class HCSR04 : public IODevice { // void read_HCSR04device() { // uint16 enough to time up to 65ms - uint16_t startTime, waitTime, currentTime, maxTime; + uint16_t startTime, waitTime = 0, currentTime, maxTime; // If receive pin is still set on from previous call, abort the read. if (ArduinoPins::fastReadDigital(_echoPin)) @@ -186,4 +186,4 @@ class HCSR04 : public IODevice { }; -#endif //IO_HCSR04_H \ No newline at end of file +#endif //IO_HCSR04_H From 604a69e0a894af0e2edfc308c25013d19cc56e0b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 20 Oct 2022 00:54:35 +0200 Subject: [PATCH 330/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 3e9fb5a7c..a025a4ec1 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-cursense2-202210190909Z" +#define GITHUB_SHA "PORTX-HAL-cursense2-202210192255Z" From 7388d14bab7f3eb05a6035cd818162008c53ddc5 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 20 Oct 2022 16:08:40 -0400 Subject: [PATCH 331/870] Update release_notes.md --- release_notes.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index e5b06ed3e..98cd5d19d 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,4 +1,4 @@ -The DCC-EX Team is pleased to release CommandStation-EX-v4.0.0 as a Production Release. Release v4.0.0 is a Major release that adds significant new product design, plus Automation features and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.2.0 to v3.2.0 rc13. +The DCC-EX Team is pleased to release CommandStation-EX-v4.1.1 as a Production Release. Release v4.0.0 is a Minor release though it does add significant new automation features to EX-RAIL in addition to some small changes and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** @@ -8,6 +8,39 @@ The DCC-EX Team is pleased to release CommandStation-EX-v4.0.0 as a Production R [CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz) +**EX-RAIL New Features** + + - ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX for more compatibility with non NMRA compliant decoders + - Automatically detect and run a myFilter add-on (no need to call setFilter) + - Add commands + - Add "if" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) + - New command to obtain current throttle settings + - New JA, JR, JT commands to obtain route, roster and turnout descriptions + - Add ability for HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) + - Add VIRTUAL_TURNOUT definition + - New PARSE <> commands in EXRAIL allows sending of command station commands from EX-RAIL + - New and KILLALL command to stop all tasks + - Add FORGET command. Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track + - Add Servo signals (SERVO_SIGNAL) + - Add High-On signal pins (SIGNALH) (Arduino normally handles active LOW signals. This allows for active HIGH) + - Add Wait for analog value (ATGTE, ATLT)... (At greater than or Equal and At less than a certain value) + +**EX-RAIL Updates** + + - EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi + - EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command) + + +**4.1.1 Bug Fixes** + +- Preserve the turnout format +- Parse multiple commands in one buffer string currectly +- Fix command signal status in EX-RAIL +- Read long loco addresses in EX-RAIL +- FIX negative route IDs in WIthrottle + +See the version.h file for notes about which of the 4.1.1 features were added/changed by point release. + **Known Issues** - **Wi-Fi** - Requires sending `` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup From 1901d9547eb8cea2438b486415c833c25c0297bb Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 20 Oct 2022 16:23:13 -0400 Subject: [PATCH 332/870] Update release_notes.md --- release_notes.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/release_notes.md b/release_notes.md index 98cd5d19d..7943f42b7 100644 --- a/release_notes.md +++ b/release_notes.md @@ -252,7 +252,7 @@ See the version.h file for notes about which of the 4.1.1 features were added/ch - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) -**CommandStation-EX Developers** +**EX-CommandStation Developers** - Chris Harlow - Bournemouth, UK (UKBloke) - Harald Barth - Stockholm, Sweden (Haba) @@ -267,7 +267,7 @@ See the version.h file for notes about which of the 4.1.1 features were added/ch - M Steve Todd -**exInstaller Software** +**EX-Installer Software** - Anthony W - Dayton, Ohio, USA (Dex, Dex++) @@ -280,12 +280,22 @@ See the version.h file for notes about which of the 4.1.1 features were added/ch - Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter) - Kevin Smith - Rochester Hills, Michigan USA (KC Smith) - Colin Grabham - Central NSW, Australia (Kebbin) +- Peter Cole - Brisbane, QLD, Australia (peteGSX) +- Peter Akers - Brisbane, QLD, Australia (flash62au) -**WebThrotle-EX** +**EX-WebThrottle** - Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk) - Mani Kumar - Bangalor, India (Mani /Mani Kumar) - Matt H - Somewhere in Europe + +**Hardware / Electronics** + +- Harald Barth - Stockholm, Sweden (Haba) +- Paul Antoine, Western Australia +- Neil McKechnie - Worcestershire, UK +- Fred Decker - Holly Springs NC, USA +- Herb Morton - Kingwood Texas, USA (Ash++) **Beta Testing / Release Management / Support** From dcab5a0e7268818f39d6330cac001c13606a3b6d Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 20 Oct 2022 16:25:10 -0400 Subject: [PATCH 333/870] Create release_notes_v4.1.1.md --- Release_Notes/release_notes_v4.1.1.md | 325 ++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 Release_Notes/release_notes_v4.1.1.md diff --git a/Release_Notes/release_notes_v4.1.1.md b/Release_Notes/release_notes_v4.1.1.md new file mode 100644 index 000000000..7943f42b7 --- /dev/null +++ b/Release_Notes/release_notes_v4.1.1.md @@ -0,0 +1,325 @@ +The DCC-EX Team is pleased to release CommandStation-EX-v4.1.1 as a Production Release. Release v4.0.0 is a Minor release though it does add significant new automation features to EX-RAIL in addition to some small changes and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. + +**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** + +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip) + + + +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz) + +**EX-RAIL New Features** + + - ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX for more compatibility with non NMRA compliant decoders + - Automatically detect and run a myFilter add-on (no need to call setFilter) + - Add commands + - Add "if" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) + - New command to obtain current throttle settings + - New JA, JR, JT commands to obtain route, roster and turnout descriptions + - Add ability for HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) + - Add VIRTUAL_TURNOUT definition + - New PARSE <> commands in EXRAIL allows sending of command station commands from EX-RAIL + - New and KILLALL command to stop all tasks + - Add FORGET command. Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track + - Add Servo signals (SERVO_SIGNAL) + - Add High-On signal pins (SIGNALH) (Arduino normally handles active LOW signals. This allows for active HIGH) + - Add Wait for analog value (ATGTE, ATLT)... (At greater than or Equal and At less than a certain value) + +**EX-RAIL Updates** + + - EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi + - EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command) + + +**4.1.1 Bug Fixes** + +- Preserve the turnout format +- Parse multiple commands in one buffer string currectly +- Fix command signal status in EX-RAIL +- Read long loco addresses in EX-RAIL +- FIX negative route IDs in WIthrottle + +See the version.h file for notes about which of the 4.1.1 features were added/changed by point release. + +**Known Issues** + +- **Wi-Fi** - Requires sending `` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup +- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry + +**All New Major DCC++EX 4.0.0 features** + +- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc). +- HAL Support for; + - MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules. + - PCA9685 PWM (servo & signal) control modules. + - Analogue inputs on Arduino pins and on ADS111x I2C modules. + - MP3 sound playback via DFPlayer module. + - HC-SR04 Ultrasonic range sensor module. + - VL53L0X Laser range sensor module (Time-Of-Flight). + - A new `` command to list the HAL devices attached to the command station + +**New Command Station Broadcast throttle logic** + +- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates + +**New ‘Discovered Servers’ on WiFi Throttles** + +- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster. + +**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language** + +- Use to control your entire layout or as a separate accessory/animation controller +- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts. +- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station. +- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps. + +**New EX-RAIL ‘Roster’ Feature** + +- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps. +- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station. +- The EX-RAIL ’ROSTER’ command allows all the engine numbers, names and function keys you’ve listed in your myAutomation.h file to automatically upload the Command Station's ‘Server Roster’ into your Engine Driver and WiThrottle Apps. + +**New JMRI 4.99.2 and above specific DCC++EX 4.0 features** + +- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI. + +- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time. + For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `` programming spur or siding track for acquiring an engine and ‘Drive Away’ onto the mainline (see the DriveAway feature for more information). + +**DCC++EX 4.0.0 additional product enhancements** + +- Additional Motor Shields and Motor Board {boosters) supported +- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals +- Additional diagnostic commands like ‘D ACK RETRY’ and ‘D EXRAIL ON’ events, ‘D HAL SHOW’ devices and ‘D SERVO’ positions, and ‘D RESET’ the command station while maintaining the serial connection with JMRI +- Automatic retry on failed ACK detection to give decoders another chance +- New EX-RAIL ’/’ slash command allows JMRI to directly communicate with many EX-RAIL scripts +- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps. +- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables +- Turnout user names display in Engine Driver & WiThrottles +- 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. +- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station +- Can define border between long and short addresses +- 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. + +**4.0.0 Bug Fixes** + +- Compiles on Nano Every +- Diagnostic display of ack pulses >32ms +- Current read from wrong ADC during interrupt +- AT(+) Command Pass Through +- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected +- One-off error in CIPSEND drop +- Common Fault Pin Error +- Uno Memory Utilization optimized + +#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release + +**Summary of the key new features added to CommandStation-EX V3.0.16** + +- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id + +**Summary of the key new features added to CommandStation-EX V3.0.15** + +- Send function commands just once instead of repeating them 4 times + +**Summary of the key new features added to CommandStation-EX V3.0.14** + +- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse +- Provide proper track power management when joining and unjoining tracks with <1 JOIN> + +**Summary of the key new features added to CommandStation-EX V3.0.13** + +- Fix for CAB Functions greater than 127 + +**Summary of the key new features added to CommandStation-EX V3.0.12** + +- Fixed clear screen issue for nanoEvery and nanoWifi + +**Summary of the key new features added to CommandStation-EX V3.0.11** + +- Reorganized files for support of 128 speed steps + +**Summary of the key new features added to CommandStation-EX V3.0.10** + +- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs +- No functional change just changes to avoid complier warnings for Teensy/nanoEvery + +**Summary of the key new features added to CommandStation-EX V3.0.9** + +- Rearranges serial newlines for the benefit of JMRI +- Major update for efficiencies in displays (LCD, OLED) +- Add I2C Support functions + +**Summary of the key new features added to CommandStation-EX V3.0.8** + +- Wraps <* *> around DIAGS for the benefit of JMRI + +**Summary of the key new features added to CommandStation-EX V3.0.7** + +- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps. +- Improved overload messages with raw values (relative to offset) + +**Summary of the key new features added to CommandStation-EX V3.0.6** + +- Prevent compiler warning about deprecated B constants +- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly +- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28 +- ESTOP all - New command to emergency stop all locos on the main track +- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages +- `` command to reboot Arduino +- Automatic sensor offset detect +- Improved startup msgs from Motor Drivers (accuracy and auto sense factors) +- Drop post-write verify - No need to double check CV writes. Writes are now even faster. +- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds. + +**Summary of the key new features added to CommandStation-EX V3.0.5** + +- Fix Fn Key startup with loco ID and fix state change for F16-28 +- Removed ethernet mac config and made it automatic +- Show wifi ip and port on lcd +- Auto load config.example.h with warning +- Dropped example .ino files +- Corrected .ino comments +- Add Pololu fault pin handling +- Waveform speed/simplicity improvements +- Improved pin speed in waveform +- Portability to nanoEvery and UnoWifiRev2 CPUs +- Analog read speed improvements +- Drop need for DIO2 library +- Improved current check code +- Linear command +- Removed need for ArduinoTimers files +- Removed option to choose different timer +- Added EX-RAIL hooks for automation in future version +- Fixed Turnout list +- Allow command keywords in mixed case +- Dropped unused memstream +- PWM pin accuracy if requirements met + +**Summary of the key new features added to CommandStation-EX V3.0.4** + +- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track +- WiFi Startup Fixes + +**Summary of the key new features added to CommandStation-EX V3.0.3** + +- Command to write loco address and clear consist +- Command will allow for consist address +- Startup commands implemented + +**Summary of the key new features added to CommandStation-EX V3.0.2:** + +- Create new output for current in mA for `` command - New current response outputs current in mA, overlimit current, and maximum board capable current +- Simultaneously update JMRI to handle new current meter + +**Summary of the key new features added to CommandStation-EX V3.0.1:** + +- Add back fix for jitter +- Add Turnouts, Outputs and Sensors to `` command output + +**CommandStation-EX V3.0.0:** + +**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:** + +- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains. +- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients. +- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently. +- **Add LCD/OLED support** - OLED supported on Mega only +- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second. +- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers +- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts. +- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs +- **New, simpler function command** - `` command allows setting functions based on their number, not based on a code as in `` +- **Function reminders** - Function reminders are sent in addition to speed reminders +- **Functions to F28** - All NMRA functions are now supported +- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24) +- **Diagnostic `` commands** - See documentation for a full list of new diagnostic commands +- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM +- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers +- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code +- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM +- **Fix EEPROM bugs** +- **Number of locos discovery command** - `<#>` command +- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega. +- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track. + +**Key Contributors** + +**Project Lead** + +- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) + +**EX-CommandStation Developers** + +- Chris Harlow - Bournemouth, UK (UKBloke) +- Harald Barth - Stockholm, Sweden (Haba) +- Neil McKechnie - Worcestershire, UK (NeilMck) +- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) +- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) +- M Steve Todd - Oregon, USA (MSteveTodd) +- Scott Catalano - Pennsylvania +- Gregor Baues - Île-de-France, France (grbba) + +**Engine Driver and JMRI Interface** + +- M Steve Todd + +**EX-Installer Software** + +- Anthony W - Dayton, Ohio, USA (Dex, Dex++) + +**Website and Documentation** + +- Mani Kumar - Bangalor, India (Mani / Mani Kumar) +- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) +- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) +- Roger Beschizza - Dorset, UK (Roger Beschizza) +- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter) +- Kevin Smith - Rochester Hills, Michigan USA (KC Smith) +- Colin Grabham - Central NSW, Australia (Kebbin) +- Peter Cole - Brisbane, QLD, Australia (peteGSX) +- Peter Akers - Brisbane, QLD, Australia (flash62au) + +**EX-WebThrottle** + +- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk) +- Mani Kumar - Bangalor, India (Mani /Mani Kumar) +- Matt H - Somewhere in Europe + +**Hardware / Electronics** + +- Harald Barth - Stockholm, Sweden (Haba) +- Paul Antoine, Western Australia +- Neil McKechnie - Worcestershire, UK +- Fred Decker - Holly Springs NC, USA +- Herb Morton - Kingwood Texas, USA (Ash++) + +**Beta Testing / Release Management / Support** + +- Larry Dribin - Release Management +- Kevin Smith - Rochester Hills, Michigan USA (KC Smith) +- Herb Morton - Kingwood Texas, USA (Ash++) +- Keith Ledbetter +- Brad Van der Elst +- Andrew Pye +- Mike Bowers +- Randy McKenzie +- Roberto Bravin +- Sam Brigden +- Alan Lautenslager +- Martin Bafver +- Mário André Silva +- Anthony Kochevar +- Gajanatha Kobbekaduwe +- Sumner Patterson +- Paul - Virginia, USA + +**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** + +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip) + + +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz) From e618b91900cfeed72ef1857d3c546577e068f1fd Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 21 Oct 2022 11:57:55 -0400 Subject: [PATCH 334/870] Update version.h --- version.h | 55 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/version.h b/version.h index 4a6504649..c2bb135b7 100644 --- a/version.h +++ b/version.h @@ -5,30 +5,55 @@ #define VERSION "4.1.1" // 4.1.1 Bugfix: preserve turnout format -// Bugfix: parse multiple commands in one buffer string correct +// Bugfix: parse multiple commands in one buffer string correctly (ex: ) // Bugfix: command signal status in Exrail // Bugfix: EX-RAIL read long loco addr +// Bugfix: Add space character after version string 4.1.1 for JMRI parsing. +// Improved display and loop time for signalsmake service start to be outside the DONT_TOUCH_WIFI_CONF area +// Improve WiFi startup by making service start to be outside the DONT_TOUCH_WIFI_CONF area // 4.1.0 ... // // 4.0.2 EXRAIL additions: -// ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX +// Code: struct TurnoutData to enable EEPROM in v 4.0 format +// Code: Fix weak reference to myFilter +// Updated CV read command . Equivalent to . Uses the verify callback. +// Allow WRITE CV on PROG commands -// command to obtain current throttle settings -// JA, JR, JT commands to obtain route, roster and turnout descriptions -// HIDDEN turnouts -// PARSE <> commands in EXRAIL -// VIRTUAL_TURNOUT -// and KILLALL command to stop all tasks. +// FIX negative route ids in WIthrottle problem by allowing them +// Add IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) +// Add commands +// Add command to obtain current throttle settings +// Add JA, JR, JT commands to obtain route, roster and turnout descriptions +// Add HIDDEN turnouts +// Add PARSE <> commands in EXRAIL +// Add VIRTUAL_TURNOUT +// Add and KILLALL command to stop all tasks +// Add Diagnostic messages when KILL is used // FORGET forgets the current loco in DCC reminder tables. -// Servo signals (SERVO_SIGNAL) -// High-On signal pins (SIGNALH) -// Wait for analog value (ATGTE, ATLT) +// Add Servo signals (SERVO_SIGNAL) +// Add High-On signal pins (SIGNALH) +// Add Wait for analog value (ATGTE, ATLT) +// Allow underscore in keywords ex: MY_KEYWORD +// Automatically assign a name with ALIAS(name) without having to define it first +// README.md: removed misleading "folder/subforlders" (#218) +// README.md: fix dead link to rewrite (#217) in notes/rewrite.md +// Incoming LCN turnout throw +// Broadcast jopin after DriveAway +// Corrections to I2C code: +// 1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact. +// 2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest(). +// Save memory on the Uno // 4.0.1 Small EXRAIL updates // EXRAIL BROADCAST("msg") -// EXRAIL POWERON +// EXRAIL POWERON (only turns on MAIN) +// Remove EXRAIL/ENDEXRAIL from myAutomation.example.h (#215) +// Use "startup sequence" to describe the initial instructions +// Add description of display scroll modes +// restructure GetLocoCallback() for better readability and put broadcastPower() at right place // 4.0.0 Major functional and non-functional changes. // Engine Driver "DriveAway" feature enhancement // 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station From 941e74beaf1a974f01e0b2176ddec9919fd11234 Mon Sep 17 00:00:00 2001 From: Kcsmith0708 Date: Fri, 21 Oct 2022 13:45:54 -0400 Subject: [PATCH 335/870] Realese Document Edit & Enhancements (#262) * Realese Document Edit & Enhancements Edited Intro and Rearranged EXRAIL Enhancements * Update release_notes_v4.1.1.md edited indents * Update release_notes_v4.1.1.md Fomating --- Release_Notes/release_notes_v4.1.1.md | 46 ++++++++++++++++----------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/Release_Notes/release_notes_v4.1.1.md b/Release_Notes/release_notes_v4.1.1.md index 7943f42b7..875243a92 100644 --- a/Release_Notes/release_notes_v4.1.1.md +++ b/Release_Notes/release_notes_v4.1.1.md @@ -1,4 +1,6 @@ -The DCC-EX Team is pleased to release CommandStation-EX-v4.1.1 as a Production Release. Release v4.0.0 is a Minor release though it does add significant new automation features to EX-RAIL in addition to some small changes and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. +The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public. +This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes. +The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** @@ -8,28 +10,36 @@ The DCC-EX Team is pleased to release CommandStation-EX-v4.1.1 as a Production R [CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz) -**EX-RAIL New Features** - - - ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX for more compatibility with non NMRA compliant decoders +**New Command Station & EX-RAIL Features** + - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders - Automatically detect and run a myFilter add-on (no need to call setFilter) - - Add commands - - Add "if" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) - - New command to obtain current throttle settings - - New JA, JR, JT commands to obtain route, roster and turnout descriptions - - Add ability for HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) - - Add VIRTUAL_TURNOUT definition - - New PARSE <> commands in EXRAIL allows sending of command station commands from EX-RAIL - - New and KILLALL command to stop all tasks - - Add FORGET command. Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track - - Add Servo signals (SERVO_SIGNAL) - - Add High-On signal pins (SIGNALH) (Arduino normally handles active LOW signals. This allows for active HIGH) - - Add Wait for analog value (ATGTE, ATLT)... (At greater than or Equal and At less than a certain value) - -**EX-RAIL Updates** + - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor + - commands + - and KILLALL command to stop all tasks + - command to obtain current throttle settings + + - New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications + + - New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts + - ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value) + - FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins + - FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track + - "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) + - PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL + - SERVO_SIGNAL Servo signals assigned to a specific servo turnout + - SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH) + - HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) + - VIRTUAL_TURNOUT definition + +**EX-RAIL Updates** - EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi - EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command) +** Other Enhancements** + - UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes. + - PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off. + - Position servo can use spare servo pin as a GPIO pin. **4.1.1 Bug Fixes** From 1a67930af4b7bd24393d6a93986b6e77cb2053a2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 22 Oct 2022 00:48:53 +0200 Subject: [PATCH 336/870] make tones like a Taurus --- MotorDriver.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 990424a03..c41420a9f 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -235,12 +235,18 @@ void MotorDriver::startCurrentFromHW() { #pragma GCC pop_options #endif //ANALOG_READ_INTERRUPT +#if defined(ARDUINO_ARCH_ESP32) +uint16_t taurustones[28] = { 165, 175, 196, 220, + 247, 262, 294, 330, + 249, 392, 440, 494, + 523, 587, 659, 698, + 494, 440, 392, 249, + 330, 284, 262, 247, + 220, 196, 175, 165 }; +#endif void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) return; -#if defined(ARDUINO_ARCH_ESP32) - DCCEXanalogWriteFrequency(brakePin, 100); // set DC PWM frequency to 100Hz XXX May move to setup -#endif #if defined(ARDUINO_AVR_UNO) TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz #endif @@ -252,6 +258,17 @@ void MotorDriver::setDCSignal(byte speedcode) { byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 byte tDir=speedcode & 0x80; byte brake; +#if defined(ARDUINO_ARCH_ESP32) + { + int f = 131; + if (tSpeed > 2) { + if (tSpeed <= 58) { + f = taurustones[ (tSpeed-2)/2 ] ; + } + } + DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup + } +#endif if (tSpeed <= 1) brake = 255; else if (tSpeed >= 127) brake = 0; else brake = 2 * (128-tSpeed); From d72474cd8f7b8132fd31347a4dcd527f2954d50e Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 21 Oct 2022 20:03:00 -0400 Subject: [PATCH 337/870] Update release_notes_v4.1.1.md --- Release_Notes/release_notes_v4.1.1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Release_Notes/release_notes_v4.1.1.md b/Release_Notes/release_notes_v4.1.1.md index 875243a92..03a9a45e5 100644 --- a/Release_Notes/release_notes_v4.1.1.md +++ b/Release_Notes/release_notes_v4.1.1.md @@ -4,11 +4,11 @@ The team continues improving the architecture of DCC++EX to make it more flexibl **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** -[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip) +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip) -[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz) +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) **New Command Station & EX-RAIL Features** - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders @@ -329,7 +329,7 @@ See the version.h file for notes about which of the 4.1.1 features were added/ch **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** -[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip) +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip) -[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz) +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) From 39a85903cec643814254d7b52f5d486490f87e46 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 21 Oct 2022 20:04:05 -0400 Subject: [PATCH 338/870] Update release_notes.md --- release_notes.md | 54 ++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/release_notes.md b/release_notes.md index 7943f42b7..03a9a45e5 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,35 +1,45 @@ -The DCC-EX Team is pleased to release CommandStation-EX-v4.1.1 as a Production Release. Release v4.0.0 is a Minor release though it does add significant new automation features to EX-RAIL in addition to some small changes and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. +The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public. +This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes. +The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** -[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip) +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip) -[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz) +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) -**EX-RAIL New Features** - - - ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX for more compatibility with non NMRA compliant decoders +**New Command Station & EX-RAIL Features** + - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders - Automatically detect and run a myFilter add-on (no need to call setFilter) - - Add commands - - Add "if" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) - - New command to obtain current throttle settings - - New JA, JR, JT commands to obtain route, roster and turnout descriptions - - Add ability for HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) - - Add VIRTUAL_TURNOUT definition - - New PARSE <> commands in EXRAIL allows sending of command station commands from EX-RAIL - - New and KILLALL command to stop all tasks - - Add FORGET command. Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track - - Add Servo signals (SERVO_SIGNAL) - - Add High-On signal pins (SIGNALH) (Arduino normally handles active LOW signals. This allows for active HIGH) - - Add Wait for analog value (ATGTE, ATLT)... (At greater than or Equal and At less than a certain value) - -**EX-RAIL Updates** + - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor + - commands + - and KILLALL command to stop all tasks + - command to obtain current throttle settings + + - New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications + + - New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts + - ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value) + - FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins + - FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track + - "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) + - PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL + - SERVO_SIGNAL Servo signals assigned to a specific servo turnout + - SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH) + - HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) + - VIRTUAL_TURNOUT definition + +**EX-RAIL Updates** - EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi - EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command) +** Other Enhancements** + - UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes. + - PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off. + - Position servo can use spare servo pin as a GPIO pin. **4.1.1 Bug Fixes** @@ -319,7 +329,7 @@ See the version.h file for notes about which of the 4.1.1 features were added/ch **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** -[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip) +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip) -[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz) +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) From d1518b8af02a6306fc2c46be581b3158ee6f515d Mon Sep 17 00:00:00 2001 From: Kcsmith0708 Date: Sat, 22 Oct 2022 18:00:21 -0400 Subject: [PATCH 339/870] Update Release_notes_v4.1.1.md (#264) * Update release_notes_v4.1.1.md Edited etc., commands and added KILLALL function to EXRAIL list * Update release_notes_v4.1.1.md added back in * Update release_notes_v4.1.1.md fixed < t cab> so it would display --- Release_Notes/release_notes_v4.1.1.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Release_Notes/release_notes_v4.1.1.md b/Release_Notes/release_notes_v4.1.1.md index 03a9a45e5..cd9531b52 100644 --- a/Release_Notes/release_notes_v4.1.1.md +++ b/Release_Notes/release_notes_v4.1.1.md @@ -14,11 +14,18 @@ The team continues improving the architecture of DCC++EX to make it more flexibl - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders - Automatically detect and run a myFilter add-on (no need to call setFilter) - - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor - - commands - - and KILLALL command to stop all tasks - - command to obtain current throttle settings - + - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor + - to turn a individual LED Signal On & Off + - " + - " + - command to stop all tasks, and Diagnostic messages when KILL is used + - < t cab> command to obtain current throttle setting + + - Allow WRITE CV on PROG + - Updated CV read command . Equivalent to . Uses the verify callback. + - Allow WRITE CV on PROG commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL - SERVO_SIGNAL Servo signals assigned to a specific servo turnout - SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH) From b7a010f904d6cf66e57eaebe0efd9bdd237d67f3 Mon Sep 17 00:00:00 2001 From: Kcsmith0708 Date: Sat, 22 Oct 2022 18:01:37 -0400 Subject: [PATCH 340/870] Verion.h 4.1.1 (#263) Edited & Reformatted verify then ready for release --- version.h | 69 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/version.h b/version.h index c2bb135b7..e9b61bfa0 100644 --- a/version.h +++ b/version.h @@ -6,55 +6,58 @@ #define VERSION "4.1.1" // 4.1.1 Bugfix: preserve turnout format // Bugfix: parse multiple commands in one buffer string correctly (ex: ) -// Bugfix: command signal status in Exrail +// Bugfix: command signal status of EX-RAIL tasks or threads // Bugfix: EX-RAIL read long loco addr // Bugfix: Add space character after version string 4.1.1 for JMRI parsing. -// Improved display and loop time for signalsmake service start to be outside the DONT_TOUCH_WIFI_CONF area +// Improved display and loop time for signals make service start to be outside the DONT_TOUCH_WIFI_CONF area // Improve WiFi startup by making service start to be outside the DONT_TOUCH_WIFI_CONF area // 4.1.0 ... -// -// 4.0.2 EXRAIL additions: -// Code: struct TurnoutData to enable EEPROM in v 4.0 format -// Code: Fix weak reference to myFilter +// UNO Progmem optimized to allow for small EXRAIL Automation scipts +// 4.0.2 Command Station and EX-RAIL Ehancements & Additions: +// New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications +// Change ACK defaults now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders +// New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor +// to turn a individual LED Signal On & Off +// " +// " +// command to stop all tasks, and Diagnostic messages when KILL is used +// command to obtain current throttle settings +// Allow WRITE CV on PROG // Updated CV read command . Equivalent to . Uses the verify callback. -// Allow WRITE CV on PROG commands -// Add command to obtain current throttle settings -// Add JA, JR, JT commands to obtain route, roster and turnout descriptions -// Add HIDDEN turnouts -// Add PARSE <> commands in EXRAIL -// Add VIRTUAL_TURNOUT -// Add and KILLALL command to stop all tasks -// Add Diagnostic messages when KILL is used -// FORGET forgets the current loco in DCC reminder tables. -// Add Servo signals (SERVO_SIGNAL) -// Add High-On signal pins (SIGNALH) -// Add Wait for analog value (ATGTE, ATLT) -// Allow underscore in keywords ex: MY_KEYWORD -// Automatically assign a name with ALIAS(name) without having to define it first -// README.md: removed misleading "folder/subforlders" (#218) -// README.md: fix dead link to rewrite (#217) in notes/rewrite.md +// Improved SIGNALs startup and diagnostics // Incoming LCN turnout throw +// Allow turnout ID of "0" +// Code: struct TurnoutData to enable EEPROM in v 4.0 format // Broadcast jopin after DriveAway // Corrections to I2C code: // 1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact. // 2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest(). -// Save memory on the Uno -// 4.0.1 Small EXRAIL updates +// New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) scripts +// Automatically assign a name with ALIAS(name) without having to define it first +// ALIAS now has the aility to use Low underscore in keywords e.g., MY_KEYWORD +// ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value) +// FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins +// FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track +// "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id) +// KILLALL command to stop all tasks, and Diagnostic messages when KILL is used +// PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL +// SERVO_SIGNAL Servo signals assigned to a specific servo turnout +// SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH) +// HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) +// VIRTUAL_TURNOUT definition +// README.md: removed misleading "folder/subforlders" (#218) +// README.md: fix dead link to rewrite (#217) in notes/rewrite.md +// 4.0.1 Additional EXRAIL updates // EXRAIL BROADCAST("msg") // EXRAIL POWERON (only turns on MAIN) -// Remove EXRAIL/ENDEXRAIL from myAutomation.example.h (#215) +// Remove optional EXRAIL/ENDEXRAIL from myAutomation.example.h (#215) // Use "startup sequence" to describe the initial instructions // Add description of display scroll modes -// restructure GetLocoCallback() for better readability and put broadcastPower() at right place -// 4.0.0 Major functional and non-functional changes. +// GetLocoCallback() restructured for better readability and put broadcastPower() at right place +// 4.0.0 Major Production Release with New Functional and non-functional changes. // Engine Driver "DriveAway" feature enhancement // 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station // New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability. From daf6799ac1ef1fbb07fe5ca836c12acd37ffed28 Mon Sep 17 00:00:00 2001 From: Fred Date: Sat, 22 Oct 2022 18:10:42 -0400 Subject: [PATCH 341/870] Update release_notes.md --- release_notes.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/release_notes.md b/release_notes.md index 03a9a45e5..cd9531b52 100644 --- a/release_notes.md +++ b/release_notes.md @@ -14,11 +14,18 @@ The team continues improving the architecture of DCC++EX to make it more flexibl - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders - Automatically detect and run a myFilter add-on (no need to call setFilter) - - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor - - commands - - and KILLALL command to stop all tasks - - command to obtain current throttle settings - + - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor + - to turn a individual LED Signal On & Off + - " + - " + - command to stop all tasks, and Diagnostic messages when KILL is used + - < t cab> command to obtain current throttle setting + + - Allow WRITE CV on PROG + - Updated CV read command . Equivalent to . Uses the verify callback. + - Allow WRITE CV on PROG commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL - SERVO_SIGNAL Servo signals assigned to a specific servo turnout - SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH) From 57aa1457e0d4ed0016f2cee1937a4564b2de9c80 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sun, 23 Oct 2022 17:49:39 +0800 Subject: [PATCH 342/870] GETFLASHW hack for SAMD/STM32 --- FSH.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FSH.h b/FSH.h index 850311ef4..ea54554a9 100644 --- a/FSH.h +++ b/FSH.h @@ -1,5 +1,5 @@ /* - * © 2022 Paul M Antoine + * © 2022 Paul M. Antoine * © 2021 Neil McKechnie * © 2021 Harald Barth * © 2021 Fred Decker @@ -48,10 +48,10 @@ typedef char FSH; #define FLASH #define strlen_P strlen #define strcpy_P strcpy -#elif defined(ARDUINO_ARCH_STM32) +#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) typedef __FlashStringHelper FSH; -#define GETFLASH(addr) pgm_read_byte_near(addr) -#define GETFLASHW(addr) pgm_read_word_near(addr) +#define GETFLASH(addr) pgm_read_byte(addr) +#define GETFLASHW(addr) (*(const unsigned int8_t *)(addr)) | ((*(const unsigned int8_t *)(addr+1)) << 8) #ifdef FLASH #undef FLASH #endif From f0d1909d9f7e487c30114e050f3f7f2ab7695189 Mon Sep 17 00:00:00 2001 From: Fred Date: Sun, 23 Oct 2022 08:21:36 -0400 Subject: [PATCH 343/870] Update release_notes.md --- release_notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index cd9531b52..184256551 100644 --- a/release_notes.md +++ b/release_notes.md @@ -11,7 +11,7 @@ The team continues improving the architecture of DCC++EX to make it more flexibl [CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) **New Command Station & EX-RAIL Features** - - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders + - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders - Automatically detect and run a myFilter add-on (no need to call setFilter) - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor From 4a3f3d0f3483fa116cb873764358034b26a1f9b0 Mon Sep 17 00:00:00 2001 From: Fred Date: Sun, 23 Oct 2022 08:22:00 -0400 Subject: [PATCH 344/870] Update release_notes_v4.1.1.md --- Release_Notes/release_notes_v4.1.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Release_Notes/release_notes_v4.1.1.md b/Release_Notes/release_notes_v4.1.1.md index cd9531b52..184256551 100644 --- a/Release_Notes/release_notes_v4.1.1.md +++ b/Release_Notes/release_notes_v4.1.1.md @@ -11,7 +11,7 @@ The team continues improving the architecture of DCC++EX to make it more flexibl [CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) **New Command Station & EX-RAIL Features** - - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders + - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders - Automatically detect and run a myFilter add-on (no need to call setFilter) - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor From 888165e98702c526f43692ce8eb4ad420f3d04c9 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 24 Oct 2022 08:33:08 +0800 Subject: [PATCH 345/870] Skeletal ADCee class for STM32 --- DCCTimerSTM32.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 2cacaffde..cab3bc71a 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -1,5 +1,5 @@ /* - * © 2022 Paul M Antoine + * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021 Harald Barth * © 2021 Fred Decker @@ -127,4 +127,31 @@ void DCCTimer::reset() { while(true) {}; } +int16_t ADCee::ADCmax() { + return 4095; +} + +int ADCee::init(uint8_t pin) { + return analogRead(pin); +} +/* + * Read function ADCee::read(pin) to get value instead of analogRead(pin) + */ +int ADCee::read(uint8_t pin, bool fromISR) { + int current; + if (!fromISR) noInterrupts(); + current = analogRead(pin); + if (!fromISR) interrupts(); + return current; +} +/* + * Scan function that is called from interrupt + */ +void ADCee::scan() { +} + +void ADCee::begin() { + noInterrupts(); + interrupts(); +} #endif \ No newline at end of file From 88c7e540fa59df201432903c5eb6ca5ce45bca99 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 24 Oct 2022 11:12:56 +0800 Subject: [PATCH 346/870] STM32 additional serial port support --- DCCTimerSTM32.cpp | 5 +++++ SerialManager.cpp | 14 +++++++++++++- config.example.h | 9 +++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index cab3bc71a..eb0d36523 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -37,6 +37,11 @@ #if defined(STM32F411RE) // STM32F411RE doesn't have Serial1 defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE +// Serial2 is defined to use USART2 by default, but is in fact used as the diag console +// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable +// for other DCC-EX uses like WiFi, DFPlayer, etc. +// Let's define Serial6 as an additional serial port (the only other option for the F411RE) +HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(STM32F446ZE) // STM32F446ZE doesn't have Serial1 defined by default HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE diff --git a/SerialManager.cpp b/SerialManager.cpp index 372cc852e..0ac567d88 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -1,5 +1,5 @@ /* - * © 2022 Paul M Antoine + * © 2022 Paul M. Antoine * © 2021 Chris Harlow * © 2022 Harald Barth * All rights reserved. @@ -51,6 +51,18 @@ void SerialManager::init() { while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start new SerialManager(&USB_SERIAL); +#ifdef SERIAL6_COMMANDS + Serial6.begin(115200); + new SerialManager(&Serial6); +#endif +#ifdef SERIAL5_COMMANDS + Serial5.begin(115200); + new SerialManager(&Serial5); +#endif +#ifdef SERIAL4_COMMANDS + Serial4.begin(115200); + new SerialManager(&Serial4); +#endif #ifdef SERIAL3_COMMANDS Serial3.begin(115200); new SerialManager(&Serial3); diff --git a/config.example.h b/config.example.h index 8bed4ef7a..aae18a92f 100644 --- a/config.example.h +++ b/config.example.h @@ -1,4 +1,5 @@ /* + * © 2022 Paul M. Antoine * © 2021 Neil McKechnie * © 2020-2021 Harald Barth * © 2020-2021 Fred Decker @@ -191,14 +192,18 @@ The configuration file for DCC-EX Command Station // HANDLING MULTIPLE SERIAL THROTTLES // The command station always operates with the default Serial port. // Diagnostics are only emitted on the default serial port and not broadcast. -// Other serial throttles may be added to the Serial1, Serial2, Serial3 ports -// which may or may not exist on your CPU. (Mega has all 3) +// Other serial throttles may be added to the Serial1, Serial2, Serial3, Serial4, +// Serial5, and Serial6 ports which may or may not exist on your CPU. (Mega has 3, +// SAMD/SAMC and STM32 have up to 6.) // To monitor a throttle on one or more serial ports, uncomment the defines below. // NOTE: do not define here the WiFi shield serial port or your wifi will not work. // //#define SERIAL1_COMMANDS //#define SERIAL2_COMMANDS //#define SERIAL3_COMMANDS +//#define SERIAL4_COMMANDS +//#define SERIAL5_COMMANDS +//#define SERIAL6_COMMANDS // // BLUETOOTH SERIAL ON ESP32 // On ESP32 you have the possibility to use the builtin BT serial to connect to From 90897ff2d1cb30cec0cecce30f1d39958cd27a7d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 25 Oct 2022 11:59:29 +0200 Subject: [PATCH 347/870] stacked motor shield example --- MotorDrivers.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/MotorDrivers.h b/MotorDrivers.h index ae556ca6f..a7d3bc5fc 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -158,4 +158,31 @@ new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\ new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN) +// This is an example how to stack two standard motor shields. The upper shield +// needs pins 3 8 9 11 12 13 A0 A1 disconnected from the lower shield and +// jumpered instead like this: 2-3 6-8 7-9 4-13 5-11 10-12 A0-A4 A1-A5 +// Pin assigment table: +// 2 Enable C jumpered +// 3 Enable A direct +// 4 Dir D jumpered +// 5 Enable D jumpered +// 6 Brake D jumpered +// 7 Brake C jumpered +// 8 Brake B direct +// 9 Brake A direct +// 10 Dir C jumpered +// 11 Enable B direct +// 12 Dir A direct +// 13 Dir B direct +// A0 Sense A direct +// A1 Sense B direct +// A4 Sense C jumpered +// A5 Sense D jumpered +// +#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver(10, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) +// #endif From 45504db1ad84adffc53f192ef97e79cb1c9add70 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 26 Oct 2022 18:59:39 +0200 Subject: [PATCH 348/870] stacked motor shield example typo fix --- MotorDrivers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index a7d3bc5fc..328d5e66f 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -183,6 +183,6 @@ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \ new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver(10, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) + new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) // #endif From 4bfd4b1a124aaa35ec4c18999497e761a0f20025 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 27 Oct 2022 09:34:13 +1000 Subject: [PATCH 349/870] Add templates and project workflow (#258) * Add templates and project workflow * Fixed template typos --- .github/ISSUE_TEMPLATE/bug_report.yml | 80 ++++++++++ .github/ISSUE_TEMPLATE/config.yml | 12 ++ .../ISSUE_TEMPLATE/documentation_update.yml | 31 ++++ .github/ISSUE_TEMPLATE/feature_request.yml | 37 +++++ .github/ISSUE_TEMPLATE/support_request.yml | 39 +++++ .github/ISSUE_TEMPLATE/to_do.yml | 24 +++ .github/workflows/new-items.yml | 143 ++++++++++++++++++ 7 files changed, 366 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation_update.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/support_request.yml create mode 100644 .github/ISSUE_TEMPLATE/to_do.yml create mode 100644 .github/workflows/new-items.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..457a075e7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,80 @@ +# Bug report GitHub issue form +# +# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. + +name: Bug Report +description: Submit a bug report +labels: + - Bug +title: "Bug Report: " +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to submit a bug report to the DCC-EX team! + + In order to help us to validate the bug and ascertain what's causing it, please provide as much information as possible in this form. + + - type: input + id: version + attributes: + label: Version + description: Please provide the version of EX-CommandStation in use. + validations: + required: true + + - type: textarea + id: description + attributes: + label: Bug description + description: Please provide a clear and concise description of what the symptoms of the bug are. + placeholder: | + When attempting to drive a locomotive on the main track, it runs forwards, backwards, spins around, jumps up and down, blows the horn, and then stops. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce the bug + description: Please provide the steps to reproduce the behaviour. + placeholder: | + 1. Turn on the CommandStation and track power. + 2. Connect Engine Driver to the CommandStation. + 3. Select locomotive with address 123. + 4. Throttle up to half speed. + validations: + required: true + + - type: textarea + id: expectation + attributes: + label: Expected behaviour + description: Please provide a clear and concise description of what you expected to happen. + placeholder: | + The locomotive should accelerate smoothly to half speed in a forward direction. + validations: + required: true + + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, upload any screenshots here. + + - type: textarea + id: hardware + attributes: + label: Hardware in use + description: Please provide details of hardware in use including microcontroller, motor shield, and any other relevant information. + placeholder: | + Elegoo Mega2560 + Arduino R3 motor shield + validations: + required: true + + - type: textarea + id: extra-context + attributes: + label: Additional context + description: Please provide any other relevant information that could help us resolve this issue, for example a customised config.h file. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..21c2243a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,12 @@ +# Configuration file for the template chooser +# +# This file needs to exist in the https://github.com/DCC-EX/.github repository in the ".github/ISSUE_TEMPLATE/" folder. + +blank_issues_enabled: false +contact_links: + - name: DCC-EX Discord server + url: https://discord.gg/y2sB4Fp + about: For the best support experience, join our Discord server + - name: DCC-EX Contact and Support page + url: https://dcc-ex.com/support/index.html + about: For other support options, refer to our Contact & Support page \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/documentation_update.yml b/.github/ISSUE_TEMPLATE/documentation_update.yml new file mode 100644 index 000000000..2fc94050f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_update.yml @@ -0,0 +1,31 @@ +# Documentation update GitHub issue form +# +# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. + +name: Documentation Update +description: Submit a request for documentation updates, or to report broken links or inaccuracies +title: "[Documentation Update]: " +labels: + - Needs Documentation +body: + - type: markdown + attributes: + value: | + Use this template to submit a request for updates to our documentation. + + This can be used for general documentation requests if information is missing or lacking, or to correct issues with our existing documentation such as broken links, or inaccurate information. + + - type: textarea + id: details + attributes: + label: Documentation details + description: Provide the details of what needs to be documented or corrected. + validations: + required: true + + - type: input + id: page + attributes: + label: Page with issues + description: If reporting broken links or inaccuracies, please provide the link to the page here. + placeholder: https://dcc-ex.com/index.html \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..c1fae168b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +# Feature Request GitHub issue form +# +# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. + +name: Feature Request +description: Suggest a new feature +title: "[Feature Request]: " +labels: + - Enhancement +body: + - type: markdown + attributes: + value: | + Use this template to suggest a new feature for EX-CommandStation. + + - type: textarea + id: description + attributes: + label: Problem/idea statement + description: Please provide the problem you're trying to solve, or share the idea you have. + placeholder: A clear and concise description of the problem you're trying to solve, or the idea you have. For example, I'm always frustrated when... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives or workarounds + description: Please provide any alternatives or workarounds you currently use. + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context, screenshots, or files related to the feature request here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/support_request.yml b/.github/ISSUE_TEMPLATE/support_request.yml new file mode 100644 index 000000000..c53d2b780 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support_request.yml @@ -0,0 +1,39 @@ +# Support Request GitHub issue form +# +# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. + +name: Support Request +description: Request support or assistance +title: "[Support Request]: " +labels: + - Support Request +body: + - type: markdown + attributes: + value: | + Use this template to request support or assistance with EX-CommandStation. + + - type: input + id: version + attributes: + label: Version + description: Please provide the version of the software in use. + validations: + required: true + + - type: textarea + id: description + attributes: + label: Issue description + description: Please describe the issue being encountered as accurately and detailed as possible. + validations: + required: true + + - type: textarea + id: hardware + attributes: + label: Hardware + description: If appropriate, please provide details of the hardware in use. + placeholder: | + Elegoo Mega2560 + Arduino Motor Shield R3 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/to_do.yml b/.github/ISSUE_TEMPLATE/to_do.yml new file mode 100644 index 000000000..2905b8ca8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/to_do.yml @@ -0,0 +1,24 @@ +# General To Do item GitHub issue form +# +# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder. + +name: To Do +description: Create a general To Do item +title: "[To Do]: " +labels: + - To Do +body: + - type: markdown + attributes: + value: | + Use this template to create an issue for a general task that needs to be done. + + This is handy for capturing ad-hoc items that don't necessarily require code to be written or updated. + + - type: textarea + id: description + attributes: + label: Task description + description: Provide the details of what needs to be done. + validations: + required: true \ No newline at end of file diff --git a/.github/workflows/new-items.yml b/.github/workflows/new-items.yml new file mode 100644 index 000000000..b5f0a4899 --- /dev/null +++ b/.github/workflows/new-items.yml @@ -0,0 +1,143 @@ +# This workflow is to be used for all repositories to integrate with the DCC++ EX Beta Project. +# It will add all issues and pull requests for a repository to the project, and put in the correct status. +# +# Ensure "REPO_LABEL" is updated with the correct label for the repo stream of work. +name: Add Issue or Pull Request to Project + +env: + REPO_LABEL: ${{ secrets.PROJECT_STREAM_LABEL }} + PROJECT_NUMBER: 7 + ORG: DCC-EX + +on: + issues: + types: + - opened + pull_request: + types: + - ready_for_review + - opened + - review_requested + +jobs: + add_to_project: + runs-on: ubuntu-latest + steps: + - name: Add labels + uses: andymckay/labeler@master + with: + add-labels: ${{ env.REPO_LABEL }} + + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 + with: + app_id: ${{ secrets.PROJECT_APP_ID }} + private_key: ${{ secrets. PROJECT_APP_KEY }} + + - name: Get project data + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + gh api graphql -f query=' + query($org: String!, $number: Int!) { + organization(login: $org){ + projectNext(number: $number) { + id + fields(first:20) { + nodes { + id + name + settings + } + } + } + } + }' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json + + echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV + echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV + echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV + echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV + echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV + + - name: Add issue to project + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ITEM_ID: ${{ github.event.issue.node_id }} + if: github.event_name == 'issues' + run: | + project_item_id="$( gh api graphql -f query=' + mutation($project:ID!, $item:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $item}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV + + - name: Add PR to project + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + ITEM_ID: ${{ github.event.pull_request.node_id }} + if: github.event_name == 'pull_request' + run: | + project_item_id="$( gh api graphql -f query=' + mutation($project:ID!, $item:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $item}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV + + - name: Set status - To Do + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Bug') || contains(github.event.*.labels.*.name, 'Support Request')) + run: | + gh api graphql -f query=' + mutation( + $project: ID! + $item: ID! + $status_field: ID! + $status_value: String! + ){ + set_status: updateProjectNextItemField(input: { + projectId: $project + itemId: $item + fieldId: $status_field + value: $status_value + }) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TO_DO_OPTION_ID }} --silent + + - name: Set status - Review + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Unit Tested') || contains(github.event.*.labels.*.name, 'Regression Tested') || contains(github.event.*.labels.*.name, 'Needs Review')) || github.event_name == 'pull_request' + run: | + gh api graphql -f query=' + mutation( + $project: ID! + $item: ID! + $status_field: ID! + $status_value: String! + ){ + set_status: updateProjectNextItemField(input: { + projectId: $project + itemId: $item + fieldId: $status_field + value: $status_value + }) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.NEEDS_REVIEW_OPTION_ID }} --silent From df6c511d1deb1b3f805fe8073064f7ff61339fe6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 28 Oct 2022 13:24:12 +0200 Subject: [PATCH 350/870] Fix for W5100 ethernet shield which does not report as the W5200 or W5500 --- EthernetInterface.cpp | 16 ++++++++-------- version.h | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index 7d8fed8f5..ddc71bef7 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -36,13 +36,13 @@ EthernetInterface * EthernetInterface::singleton=NULL; */ void EthernetInterface::setup() { - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - if (singleton!=NULL) - DIAG(F("Prog Error!")); - } else { - singleton=new EthernetInterface(); - } - DIAG(F("Ethernet shield %sfound"), singleton==NULL ? "not " : ""); + if (Ethernet.hardwareStatus() == EthernetNoHardware) + DIAG(F("Ethernet shield not detected or is a W5100")); + if (singleton!=NULL) + DIAG(F("Prog Error!")); + if (singleton=new EthernetInterface()) + return; + DIAG(F("Ethernet not initialized")); }; @@ -111,7 +111,7 @@ void EthernetInterface::loop() { * @return true when cable is connected, false otherwise */ bool EthernetInterface::checkLink() { - if (Ethernet.linkStatus() == LinkON) { + if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown //if we are not connected yet, setup a new server if(!connected) { DIAG(F("Ethernet cable connected")); diff --git a/version.h b/version.h index e9b61bfa0..591084df7 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.1.1" +#define VERSION "4.1.2" +// 4.2.1 Bugfix: Ethernet shield W5100 does not report HW or link level // 4.1.1 Bugfix: preserve turnout format // Bugfix: parse multiple commands in one buffer string correctly (ex: ) // Bugfix: command signal status of EX-RAIL tasks or threads From 5e50731a78a5fba7bd7c25a0f232f49f548102eb Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 10:28:54 -0400 Subject: [PATCH 351/870] Update version.h Fix version number in notes from 4.2.1 to 4.1.2 --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index 591084df7..d821db04b 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" #define VERSION "4.1.2" -// 4.2.1 Bugfix: Ethernet shield W5100 does not report HW or link level +// 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level // 4.1.1 Bugfix: preserve turnout format // Bugfix: parse multiple commands in one buffer string correctly (ex: ) // Bugfix: command signal status of EX-RAIL tasks or threads From 7b9f3ae08d1cee90c96ec1e1f5a24bda23344f0d Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 10:39:13 -0400 Subject: [PATCH 352/870] Update release_notes.md --- release_notes.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/release_notes.md b/release_notes.md index 184256551..025cda8a9 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,14 +1,14 @@ -The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public. +The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public. This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes. -The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. +The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2. **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** -[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip) +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip) -[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz) **New Command Station & EX-RAIL Features** - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders @@ -47,7 +47,10 @@ The team continues improving the architecture of DCC++EX to make it more flexibl ** Other Enhancements** - UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes. - PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off. - - Position servo can use spare servo pin as a GPIO pin. + - Position servo can use spare servo pin as a GPIO pin. + +**4.1.2 Bug Fixes** +- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips. **4.1.1 Bug Fixes** @@ -57,7 +60,7 @@ The team continues improving the architecture of DCC++EX to make it more flexibl - Read long loco addresses in EX-RAIL - FIX negative route IDs in WIthrottle -See the version.h file for notes about which of the 4.1.1 features were added/changed by point release. +See the version.h file for notes about which of the 4.1.2 features were added/changed by point release. **Known Issues** @@ -337,7 +340,7 @@ See the version.h file for notes about which of the 4.1.1 features were added/ch **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** -[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip) +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip) -[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz) +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz) From 0023ce33562d1c422c7a31d51630c6038dec1b23 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 10:48:40 -0400 Subject: [PATCH 353/870] Create release_notes_v4.1.2.md --- Release_Notes/release_notes_v4.1.2.md | 346 ++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 Release_Notes/release_notes_v4.1.2.md diff --git a/Release_Notes/release_notes_v4.1.2.md b/Release_Notes/release_notes_v4.1.2.md new file mode 100644 index 000000000..9bff53cbe --- /dev/null +++ b/Release_Notes/release_notes_v4.1.2.md @@ -0,0 +1,346 @@ +The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public. +This release is a Bugfix release which fixes support for Ethernet Shields based on the W5100 chip that broke with the release of v4.1.1. This chip does not report HW and link status the way the W5200 and W5500 do, so the check routine needed to be changed. +The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2. + +**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** + +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip) + + + +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz) + +**New Command Station & EX-RAIL Features** + - ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders + - Automatically detect and run a myFilter add-on (no need to call setFilter) + + - New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor + - to turn a individual LED Signal On & Off + - " + - " + - command to stop all tasks, and Diagnostic messages when KILL is used + - < t cab> command to obtain current throttle setting + + - Allow WRITE CV on PROG + - Updated CV read command . Equivalent to . Uses the verify callback. + - Allow WRITE CV on PROG commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL + - SERVO_SIGNAL Servo signals assigned to a specific servo turnout + - SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH) + - HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown) + - VIRTUAL_TURNOUT definition + +**EX-RAIL Updates** + - EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi + - EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command) + +** Other Enhancements** + - UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes. + - PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off. + - Position servo can use spare servo pin as a GPIO pin. + +**4.1.2 Bug Fixes** +- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips. + +**4.1.1 Bug Fixes** + +- Preserve the turnout format +- Parse multiple commands in one buffer string currectly +- Fix command signal status in EX-RAIL +- Read long loco addresses in EX-RAIL +- FIX negative route IDs in WIthrottle + +See the version.h file for notes about which of the 4.1.2 features were added/changed by point release. + +**Known Issues** + +- **Wi-Fi** - Requires sending `` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup +- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry + +**All New Major DCC++EX 4.0.0 features** + +- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc). +- HAL Support for; + - MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules. + - PCA9685 PWM (servo & signal) control modules. + - Analogue inputs on Arduino pins and on ADS111x I2C modules. + - MP3 sound playback via DFPlayer module. + - HC-SR04 Ultrasonic range sensor module. + - VL53L0X Laser range sensor module (Time-Of-Flight). + - A new `` command to list the HAL devices attached to the command station + +**New Command Station Broadcast throttle logic** + +- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates + +**New ‘Discovered Servers’ on WiFi Throttles** + +- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster. + +**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language** + +- Use to control your entire layout or as a separate accessory/animation controller +- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts. +- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station. +- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps. + +**New EX-RAIL ‘Roster’ Feature** + +- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps. +- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station. +- The EX-RAIL ’ROSTER’ command allows all the engine numbers, names and function keys you’ve listed in your myAutomation.h file to automatically upload the Command Station's ‘Server Roster’ into your Engine Driver and WiThrottle Apps. + +**New JMRI 4.99.2 and above specific DCC++EX 4.0 features** + +- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI. + +- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time. + For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `` programming spur or siding track for acquiring an engine and ‘Drive Away’ onto the mainline (see the DriveAway feature for more information). + +**DCC++EX 4.0.0 additional product enhancements** + +- Additional Motor Shields and Motor Board {boosters) supported +- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals +- Additional diagnostic commands like ‘D ACK RETRY’ and ‘D EXRAIL ON’ events, ‘D HAL SHOW’ devices and ‘D SERVO’ positions, and ‘D RESET’ the command station while maintaining the serial connection with JMRI +- Automatic retry on failed ACK detection to give decoders another chance +- New EX-RAIL ’/’ slash command allows JMRI to directly communicate with many EX-RAIL scripts +- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps. +- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables +- Turnout user names display in Engine Driver & WiThrottles +- 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. +- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station +- Can define border between long and short addresses +- 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. + +**4.0.0 Bug Fixes** + +- Compiles on Nano Every +- Diagnostic display of ack pulses >32ms +- Current read from wrong ADC during interrupt +- AT(+) Command Pass Through +- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected +- One-off error in CIPSEND drop +- Common Fault Pin Error +- Uno Memory Utilization optimized + +#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release + +**Summary of the key new features added to CommandStation-EX V3.0.16** + +- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id + +**Summary of the key new features added to CommandStation-EX V3.0.15** + +- Send function commands just once instead of repeating them 4 times + +**Summary of the key new features added to CommandStation-EX V3.0.14** + +- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse +- Provide proper track power management when joining and unjoining tracks with <1 JOIN> + +**Summary of the key new features added to CommandStation-EX V3.0.13** + +- Fix for CAB Functions greater than 127 + +**Summary of the key new features added to CommandStation-EX V3.0.12** + +- Fixed clear screen issue for nanoEvery and nanoWifi + +**Summary of the key new features added to CommandStation-EX V3.0.11** + +- Reorganized files for support of 128 speed steps + +**Summary of the key new features added to CommandStation-EX V3.0.10** + +- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs +- No functional change just changes to avoid complier warnings for Teensy/nanoEvery + +**Summary of the key new features added to CommandStation-EX V3.0.9** + +- Rearranges serial newlines for the benefit of JMRI +- Major update for efficiencies in displays (LCD, OLED) +- Add I2C Support functions + +**Summary of the key new features added to CommandStation-EX V3.0.8** + +- Wraps <* *> around DIAGS for the benefit of JMRI + +**Summary of the key new features added to CommandStation-EX V3.0.7** + +- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps. +- Improved overload messages with raw values (relative to offset) + +**Summary of the key new features added to CommandStation-EX V3.0.6** + +- Prevent compiler warning about deprecated B constants +- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly +- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28 +- ESTOP all - New command to emergency stop all locos on the main track +- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages +- `` command to reboot Arduino +- Automatic sensor offset detect +- Improved startup msgs from Motor Drivers (accuracy and auto sense factors) +- Drop post-write verify - No need to double check CV writes. Writes are now even faster. +- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds. + +**Summary of the key new features added to CommandStation-EX V3.0.5** + +- Fix Fn Key startup with loco ID and fix state change for F16-28 +- Removed ethernet mac config and made it automatic +- Show wifi ip and port on lcd +- Auto load config.example.h with warning +- Dropped example .ino files +- Corrected .ino comments +- Add Pololu fault pin handling +- Waveform speed/simplicity improvements +- Improved pin speed in waveform +- Portability to nanoEvery and UnoWifiRev2 CPUs +- Analog read speed improvements +- Drop need for DIO2 library +- Improved current check code +- Linear command +- Removed need for ArduinoTimers files +- Removed option to choose different timer +- Added EX-RAIL hooks for automation in future version +- Fixed Turnout list +- Allow command keywords in mixed case +- Dropped unused memstream +- PWM pin accuracy if requirements met + +**Summary of the key new features added to CommandStation-EX V3.0.4** + +- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track +- WiFi Startup Fixes + +**Summary of the key new features added to CommandStation-EX V3.0.3** + +- Command to write loco address and clear consist +- Command will allow for consist address +- Startup commands implemented + +**Summary of the key new features added to CommandStation-EX V3.0.2:** + +- Create new output for current in mA for `` command - New current response outputs current in mA, overlimit current, and maximum board capable current +- Simultaneously update JMRI to handle new current meter + +**Summary of the key new features added to CommandStation-EX V3.0.1:** + +- Add back fix for jitter +- Add Turnouts, Outputs and Sensors to `` command output + +**CommandStation-EX V3.0.0:** + +**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:** + +- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains. +- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients. +- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently. +- **Add LCD/OLED support** - OLED supported on Mega only +- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second. +- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers +- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts. +- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs +- **New, simpler function command** - `` command allows setting functions based on their number, not based on a code as in `` +- **Function reminders** - Function reminders are sent in addition to speed reminders +- **Functions to F28** - All NMRA functions are now supported +- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24) +- **Diagnostic `` commands** - See documentation for a full list of new diagnostic commands +- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM +- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers +- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code +- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM +- **Fix EEPROM bugs** +- **Number of locos discovery command** - `<#>` command +- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega. +- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track. + +**Key Contributors** + +**Project Lead** + +- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) + +**EX-CommandStation Developers** + +- Chris Harlow - Bournemouth, UK (UKBloke) +- Harald Barth - Stockholm, Sweden (Haba) +- Neil McKechnie - Worcestershire, UK (NeilMck) +- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) +- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) +- M Steve Todd - Oregon, USA (MSteveTodd) +- Scott Catalano - Pennsylvania +- Gregor Baues - Île-de-France, France (grbba) + +**Engine Driver and JMRI Interface** + +- M Steve Todd + +**EX-Installer Software** + +- Anthony W - Dayton, Ohio, USA (Dex, Dex++) + +**Website and Documentation** + +- Mani Kumar - Bangalor, India (Mani / Mani Kumar) +- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) +- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) +- Roger Beschizza - Dorset, UK (Roger Beschizza) +- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter) +- Kevin Smith - Rochester Hills, Michigan USA (KC Smith) +- Colin Grabham - Central NSW, Australia (Kebbin) +- Peter Cole - Brisbane, QLD, Australia (peteGSX) +- Peter Akers - Brisbane, QLD, Australia (flash62au) + +**EX-WebThrottle** + +- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk) +- Mani Kumar - Bangalor, India (Mani /Mani Kumar) +- Matt H - Somewhere in Europe + +**Hardware / Electronics** + +- Harald Barth - Stockholm, Sweden (Haba) +- Paul Antoine, Western Australia +- Neil McKechnie - Worcestershire, UK +- Fred Decker - Holly Springs NC, USA +- Herb Morton - Kingwood Texas, USA (Ash++) + +**Beta Testing / Release Management / Support** + +- Larry Dribin - Release Management +- Kevin Smith - Rochester Hills, Michigan USA (KC Smith) +- Herb Morton - Kingwood Texas, USA (Ash++) +- Keith Ledbetter +- Brad Van der Elst +- Andrew Pye +- Mike Bowers +- Randy McKenzie +- Roberto Bravin +- Sam Brigden +- Alan Lautenslager +- Martin Bafver +- Mário André Silva +- Anthony Kochevar +- Gajanatha Kobbekaduwe +- Sumner Patterson +- Paul - Virginia, USA + +**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** + +[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip) + + +[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz) From 1827a11f83b1fe9675705a11fc290b5d67dd65c3 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 10:49:32 -0400 Subject: [PATCH 354/870] Update release_notes_v4.1.1.md --- Release_Notes/release_notes_v4.1.1.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Release_Notes/release_notes_v4.1.1.md b/Release_Notes/release_notes_v4.1.1.md index 184256551..654c42d64 100644 --- a/Release_Notes/release_notes_v4.1.1.md +++ b/Release_Notes/release_notes_v4.1.1.md @@ -1,3 +1,6 @@ +Version 4.1.1 Release Notes +************************* + The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public. This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13. From 6f94cd71ab9540ee78e9e7650ef69ef025b9520b Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 10:50:35 -0400 Subject: [PATCH 355/870] Update release_notes_v4.1.2.md --- Release_Notes/release_notes_v4.1.2.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Release_Notes/release_notes_v4.1.2.md b/Release_Notes/release_notes_v4.1.2.md index 9bff53cbe..687d0c91c 100644 --- a/Release_Notes/release_notes_v4.1.2.md +++ b/Release_Notes/release_notes_v4.1.2.md @@ -1,3 +1,6 @@ +Version 4.1.2 Release Notes +************************* + The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public. This release is a Bugfix release which fixes support for Ethernet Shields based on the W5100 chip that broke with the release of v4.1.1. This chip does not report HW and link status the way the W5200 and W5500 do, so the check routine needed to be changed. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2. From aca9c9c941185a57bf1dd7d4a0c68ef199248083 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 10:52:12 -0400 Subject: [PATCH 356/870] Update release_notes.md --- release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/release_notes.md b/release_notes.md index 025cda8a9..f38e22e21 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,3 +1,6 @@ +Version 4.0 Release Notes +************************* + The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public. This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2. From 5b7801ca6c8f99e3f51218f038f18e22961ec4c0 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 28 Oct 2022 14:05:35 -0400 Subject: [PATCH 357/870] Update version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index d821db04b..537a44b6e 100644 --- a/version.h +++ b/version.h @@ -16,7 +16,7 @@ // UNO Progmem optimized to allow for small EXRAIL Automation scipts // 4.0.2 Command Station and EX-RAIL Ehancements & Additions: // New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications -// Change ACK defaults now set to LIMIT 50mA, MIN 2000uS, MAX 2000uS for more compatibility with non NMRA compliant decoders +// Change ACK defaults now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders // New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor // to turn a individual LED Signal On & Off // " From 07f1d6fc20dd9505a54ca3a9b3ba215afbb6a9b2 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Sun, 23 Oct 2022 08:01:59 +1000 Subject: [PATCH 358/870] Updated .gitignore (#261) --- .gitignore | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 1f24578b6..c8e40c222 100644 --- a/.gitignore +++ b/.gitignore @@ -8,13 +8,14 @@ Release/* .vscode/ config.h .vscode/* -mySetup.h +# mySetup.h mySetup.cpp myHal.cpp -myAutomation.h +# myAutomation.h myFilter.cpp -myAutomation.h -myFilter.cpp -myLayout.h +# myAutomation.h +# myLayout.h +my*.h +!my*.example.h .vscode/extensions.json .vscode/extensions.json From eed1237b9f18518b4565234db3896502738999ed Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 27 Oct 2022 15:18:18 +0100 Subject: [PATCH 359/870] FIX Driveaway! --- WiThrottle.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 3df56dba9..cfc4a7432 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -651,8 +651,9 @@ void WiThrottle::getLocoCallback(int16_t locoid) { itoa(locoid,addcmd+4,10); stashInstance->multithrottle(stashStream, (byte *)addcmd); TrackManager::setMainPower(POWERMODE::ON); + TrackManager::setProgPower(POWERMODE::ON); TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away - //DIAG(F("LocoCallback commit success")); + DIAG(F("LocoCallback commit success")); stashStream->commit(); CommandDistributor::broadcastPower(); From 5e2b416c30cb528685f0e0f8aeb5e64b25b0aa74 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 27 Oct 2022 18:27:23 +0100 Subject: [PATCH 360/870] roster list half error --- WiThrottle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index cfc4a7432..6c427b7f2 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -298,7 +298,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { #ifdef EXRAIL_ACTIVE StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); for (int16_t r=0;r Date: Mon, 31 Oct 2022 19:43:55 +0100 Subject: [PATCH 361/870] devel version string update --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a025a4ec1..c163cae5e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "PORTX-HAL-cursense2-202210192255Z" +#define GITHUB_SHA "devel-202210311845Z" From 863c839563931f6e95e9170486264206279dffd3 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 2 Nov 2022 13:46:16 +0800 Subject: [PATCH 362/870] Add Teensy ADCee class skeleton. --- DCCTimerTEENSY.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DCCTimerTEENSY.cpp b/DCCTimerTEENSY.cpp index a8f97f11f..0619e2116 100644 --- a/DCCTimerTEENSY.cpp +++ b/DCCTimerTEENSY.cpp @@ -141,4 +141,31 @@ void DCCTimer::reset() { SCB_AIRCR = 0x05FA0004; } +int16_t ADCee::ADCmax() { + return 4095; +} + +int ADCee::init(uint8_t pin) { + return analogRead(pin); +} +/* + * Read function ADCee::read(pin) to get value instead of analogRead(pin) + */ +int ADCee::read(uint8_t pin, bool fromISR) { + int current; + if (!fromISR) noInterrupts(); + current = analogRead(pin); + if (!fromISR) interrupts(); + return current; +} +/* + * Scan function that is called from interrupt + */ +void ADCee::scan() { +} + +void ADCee::begin() { + noInterrupts(); + interrupts(); +} #endif From f939ea0768f30a90db26f6bb51241e7be0257fe4 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 2 Nov 2022 13:55:10 +0800 Subject: [PATCH 363/870] Add MEGAAVR ADCeee skeleton. --- DCCTimerMEGAAVR.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DCCTimerMEGAAVR.cpp b/DCCTimerMEGAAVR.cpp index be9248e2d..2b2bdab9f 100644 --- a/DCCTimerMEGAAVR.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -1,4 +1,5 @@ /* + * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021 Harald Barth * © 2021 Fred Decker @@ -124,5 +125,31 @@ void DCCTimer::reset() { while(true){} } +int16_t ADCee::ADCmax() { + return 4095; +} +int ADCee::init(uint8_t pin) { + return analogRead(pin); +} +/* + * Read function ADCee::read(pin) to get value instead of analogRead(pin) + */ +int ADCee::read(uint8_t pin, bool fromISR) { + int current; + if (!fromISR) noInterrupts(); + current = analogRead(pin); + if (!fromISR) interrupts(); + return current; +} +/* + * Scan function that is called from interrupt + */ +void ADCee::scan() { +} + +void ADCee::begin() { + noInterrupts(); + interrupts(); +} #endif From b1d110ecbf262058268d0ddfd3a63dfc026797dd Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 3 Nov 2022 14:06:43 +1000 Subject: [PATCH 364/870] Fix project workflow --- .github/workflows/new-items.yml | 59 ++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/.github/workflows/new-items.yml b/.github/workflows/new-items.yml index b5f0a4899..492a0f17a 100644 --- a/.github/workflows/new-items.yml +++ b/.github/workflows/new-items.yml @@ -42,26 +42,35 @@ jobs: gh api graphql -f query=' query($org: String!, $number: Int!) { organization(login: $org){ - projectNext(number: $number) { + projectV2(number: $number) { id fields(first:20) { nodes { - id - name - settings + ... on ProjectV2Field { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } } } } } }' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV - echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV - echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV - echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV - echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV - + echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV + echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV + echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV + echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV + echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV + - name: Add issue to project env: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} @@ -70,12 +79,12 @@ jobs: run: | project_item_id="$( gh api graphql -f query=' mutation($project:ID!, $item:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $item}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { + item { id } } - }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')" echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV - name: Add PR to project @@ -86,12 +95,12 @@ jobs: run: | project_item_id="$( gh api graphql -f query=' mutation($project:ID!, $item:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $item}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { + item { id } } - }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectNextItem.projectNextItem.id')" + }' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')" echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV - name: Set status - To Do @@ -106,13 +115,15 @@ jobs: $status_field: ID! $status_value: String! ){ - set_status: updateProjectNextItemField(input: { + set_status: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $status_field - value: $status_value + value: { + singleSelectOptionId: $status_value + } }) { - projectNextItem { + projectV2Item { id } } @@ -130,13 +141,15 @@ jobs: $status_field: ID! $status_value: String! ){ - set_status: updateProjectNextItemField(input: { + set_status: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $status_field - value: $status_value + value: { + singleSelectOptionId: $status_value + } }) { - projectNextItem { + projectV2Item { id } } From 5376c9f410c71372a6ce6855aa8f6bfcc3e72b4f Mon Sep 17 00:00:00 2001 From: peteGSX Date: Fri, 4 Nov 2022 06:54:49 +1000 Subject: [PATCH 365/870] Update project workflow for forks --- .github/workflows/new-items.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/new-items.yml b/.github/workflows/new-items.yml index 492a0f17a..17187304b 100644 --- a/.github/workflows/new-items.yml +++ b/.github/workflows/new-items.yml @@ -13,7 +13,7 @@ on: issues: types: - opened - pull_request: + pull_request_target: types: - ready_for_review - opened From dd309a3705dde6de86b9d1ed46b757c7ea27e0ad Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Nov 2022 15:39:35 +0100 Subject: [PATCH 366/870] Ethernet init order --- EthernetInterface.cpp | 18 ++++++++++++++---- version.h | 3 ++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index ddc71bef7..fe780b7eb 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -36,11 +36,11 @@ EthernetInterface * EthernetInterface::singleton=NULL; */ void EthernetInterface::setup() { - if (Ethernet.hardwareStatus() == EthernetNoHardware) - DIAG(F("Ethernet shield not detected or is a W5100")); - if (singleton!=NULL) + if (singleton!=NULL) { DIAG(F("Prog Error!")); - if (singleton=new EthernetInterface()) + return; + } + if ((singleton=new EthernetInterface())) return; DIAG(F("Ethernet not initialized")); }; @@ -67,6 +67,16 @@ EthernetInterface::EthernetInterface() return; } #endif + if (Ethernet.hardwareStatus() == EthernetNoHardware) + DIAG(F("Ethernet shield not detected or is a W5100")); + + unsigned long startmilli = millis(); + while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection + if (Ethernet.linkStatus() == LinkON) + break; + DIAG(F("Ethernet waiting for link (1sec) ")); + delay(1000); + } } /** diff --git a/version.h b/version.h index d821db04b..4804870d6 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.1.2" +#define VERSION "4.1.3" +// 4.1.3 Bugfix: Ethernet init order // 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level // 4.1.1 Bugfix: preserve turnout format // Bugfix: parse multiple commands in one buffer string correctly (ex: ) From be2f3b0db74e6adc0758715a6096d8a3cc65b257 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Nov 2022 16:08:43 +0100 Subject: [PATCH 367/870] Ethernet restructure --- EthernetInterface.cpp | 98 +++++++++++++++++++++++++++++-------------- EthernetInterface.h | 11 ++--- GITHUB_SHA.h | 2 +- version.h | 1 + 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index f8c6146b4..a72f76c3a 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -1,6 +1,7 @@ /* + * © 2022 Bruno Sanches * © 2021 Fred Decker - * © 2020-2021 Harald Barth + * © 2020-2022 Harald Barth * © 2020-2021 Chris Harlow * © 2020 Gregor Baues * All rights reserved. @@ -36,8 +37,13 @@ EthernetInterface * EthernetInterface::singleton=NULL; */ void EthernetInterface::setup() { - singleton=new EthernetInterface(); - if (!singleton->connected) singleton=NULL; + if (singleton!=NULL) { + DIAG(F("Prog Error!")); + return; + } + if ((singleton=new EthernetInterface())) + return; + DIAG(F("Ethernet not initialized")); }; @@ -62,37 +68,34 @@ EthernetInterface::EthernetInterface() return; } #endif - DIAG(F("begin OK.")); - if (Ethernet.hardwareStatus() == EthernetNoHardware) { + if (Ethernet.hardwareStatus() == EthernetNoHardware) { DIAG(F("Ethernet shield not found")); return; } unsigned long startmilli = millis(); - while ((millis() - startmilli) < 5500) // Loop to give time to check for cable connection - { + while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection if (Ethernet.linkStatus() == LinkON) break; DIAG(F("Ethernet waiting for link (1sec) ")); delay(1000); } + // now we either do have link of we have a W5100 + // where we do not know if we have link. That's + // the reason to now run checkLink. + // CheckLinks sets up outboundRing if it does + // not exist yet as well. + checkLink(); +} - if (Ethernet.linkStatus() == LinkOFF) { - DIAG(F("Ethernet cable not connected")); - return; - } - - connected=true; - - IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address - - server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT - server->begin(); - - LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); - LCD(5,F("Port:%d"), IP_PORT); - - outboundRing=new RingStream(OUTBOUND_RING_SIZE); +/** + * @brief Cleanup any resources + * + * @return none + */ +EthernetInterface::~EthernetInterface() { + delete server; + delete outboundRing; } /** @@ -101,33 +104,66 @@ EthernetInterface::EthernetInterface() */ void EthernetInterface::loop() { - if (!singleton) return; + if (!singleton || (!singleton->checkLink())) + return; - switch (Ethernet.maintain()) - { + switch (Ethernet.maintain()) { case 1: //renewed fail DIAG(F("Ethernet Error: renewed fail")); singleton=NULL; return; - case 3: //rebind fail DIAG(F("Ethernet Error: rebind fail")); singleton=NULL; return; - default: //nothing happened break; } - singleton->loop2(); +} +/** + * @brief Checks ethernet link cable status and detects when it connects / disconnects + * + * @return true when cable is connected, false otherwise + */ +bool EthernetInterface::checkLink() { + if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown + //if we are not connected yet, setup a new server + if(!connected) { + DIAG(F("Ethernet cable connected")); + connected=true; + IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address + server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT + server->begin(); + LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); + LCD(5,F("Port:%d"), IP_PORT); + // only create a outboundRing it none exists, this may happen if the cable + // gets disconnected and connected again + if(!outboundRing) + outboundRing=new RingStream(OUTBOUND_RING_SIZE); + } + return true; + } else { // connected + DIAG(F("Ethernet cable disconnected")); + connected=false; + //clean up any client + for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) { + if(clients[socket].connected()) + clients[socket].stop(); + } + // tear down server + delete server; + server = nullptr; + LCD(4,F("IP: None")); + } + return false; } - void EthernetInterface::loop2() -{ +void EthernetInterface::loop2() { // get client from the server EthernetClient client = server->accept(); diff --git a/EthernetInterface.h b/EthernetInterface.h index e79996d79..ce4a2ef60 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -56,15 +56,16 @@ class EthernetInterface { static void loop(); private: - static EthernetInterface * singleton; - bool connected; - EthernetInterface(); - void loop2(); + static EthernetInterface * singleton; + bool connected; + EthernetInterface(); + ~EthernetInterface(); + void loop2(); + bool checkLink(); EthernetServer * server; EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv RingStream * outboundRing; - }; #endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c163cae5e..93b77aae7 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202210311845Z" +#define GITHUB_SHA "devel-202211041507Z" diff --git a/version.h b/version.h index 7851c4353..43e48e983 100644 --- a/version.h +++ b/version.h @@ -5,6 +5,7 @@ #define VERSION "4.2.4" +// Ethernet start improvement and link detection // 4.2.4 ESP32 experimental BT support // More DC configurations possible and lower frequency // Handle decoders that do not ack at write better From 7e16ec70884e91b72d83ecb6ab190d842a487171 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 5 Nov 2022 05:17:03 +1000 Subject: [PATCH 368/870] Fix support request issue template --- .github/ISSUE_TEMPLATE/support_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/support_request.yml b/.github/ISSUE_TEMPLATE/support_request.yml index c53d2b780..a8c76490c 100644 --- a/.github/ISSUE_TEMPLATE/support_request.yml +++ b/.github/ISSUE_TEMPLATE/support_request.yml @@ -35,5 +35,5 @@ body: label: Hardware description: If appropriate, please provide details of the hardware in use. placeholder: | - Elegoo Mega2560 - Arduino Motor Shield R3 \ No newline at end of file + Elegoo Mega2560 + Arduino Motor Shield R3 \ No newline at end of file From c0cb643cb56bd2282d282082315a4f3ca436a910 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Nov 2022 23:15:29 +0100 Subject: [PATCH 369/870] When sending all turnouts, keep it short --- DCCEXParser.cpp | 10 +--------- GITHUB_SHA.h | 2 +- Turnouts.h | 9 +++++++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 21a9d3d80..f947db964 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -730,15 +730,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) switch (params) { case 0: // list turnout definitions - { - bool gotOne = false; - for (Turnout *tt = Turnout::first(); tt != NULL; tt = tt->next()) - { - gotOne = true; - tt->print(stream); - } - return gotOne; // will if none found - } + return Turnout::printAll(stream); // will if none found case 1: // delete turnout if (!Turnout::remove(p[0])) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 93b77aae7..d9d37b0c1 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211041507Z" +#define GITHUB_SHA "devel-202211042214Z" diff --git a/Turnouts.h b/Turnouts.h index b18170971..7c40bdee8 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -171,9 +171,14 @@ class Turnout { // Save all turnout definitions static void store(); #endif - static void printAll(Print *stream) { + static bool printAll(Print *stream) { + bool gotOne=false; for (Turnout *tt = _firstTurnout; tt != 0; tt = tt->_nextTurnout) - if (!tt->isHidden()) StringFormatter::send(stream, F("\n"),tt->getId(), tt->isThrown()); + if (!tt->isHidden()) { + gotOne=true; + StringFormatter::send(stream, F("\n"),tt->getId(), tt->isThrown()); + } + return gotOne; } From a199de6d3e87f6cf4e1dd0954be6ac05ea0e0cb3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Nov 2022 23:43:26 +0100 Subject: [PATCH 370/870] Make return long config print --- DCCEXParser.cpp | 9 +++++++-- GITHUB_SHA.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index f947db964..ec207cd10 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -751,8 +751,13 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_T: state= false; break; - default: - return false; // Invalid parameter + default: // any other parameter prints the long status + Turnout *tt = Turnout::get(p[0]); + if (tt) { + tt->print(stream); + break; + } + return false; } if (!Turnout::setClosed(p[0], state)) return false; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index d9d37b0c1..f08a8f110 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211042214Z" +#define GITHUB_SHA "devel-202211042235Z" From 2b3ba514b0916a0e26b6c42155354d9ef3c40a0e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 5 Nov 2022 23:11:54 +0100 Subject: [PATCH 371/870] Use X as the questionmark sign in --- DCCEXParser.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index ec207cd10..8e7cd33f4 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -73,6 +73,7 @@ const int16_t HASH_KEYWORD_A='A'; const int16_t HASH_KEYWORD_C='C'; const int16_t HASH_KEYWORD_R='R'; const int16_t HASH_KEYWORD_T='T'; +const int16_t HASH_KEYWORD_X='X'; const int16_t HASH_KEYWORD_LCN = 15137; const int16_t HASH_KEYWORD_HAL = 10853; const int16_t HASH_KEYWORD_SHOW = -21309; @@ -751,17 +752,19 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_T: state= false; break; - default: // any other parameter prints the long status + case HASH_KEYWORD_X: + { Turnout *tt = Turnout::get(p[0]); if (tt) { tt->print(stream); - break; + return true; } return false; + } + default: // Invalid parameter + return false; } if (!Turnout::setClosed(p[0], state)) return false; - - return true; } From 4f19a60621e66a5fb09c26422312f64269d5e92c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 6 Nov 2022 21:30:32 +0100 Subject: [PATCH 372/870] number of ADC inputs was reversed --- DCCTimerAVR.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index a8f898432..3e45ae86f 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -120,9 +120,9 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 7 -#else #define NUM_ADC_INPUTS 15 +#else +#define NUM_ADC_INPUTS 7 #endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; From f1d445e056054df604b136190c20837f8d1b67c0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 6 Nov 2022 21:32:54 +0100 Subject: [PATCH 373/870] Do not abort ethernet startup on W5100 --- EthernetInterface.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index a72f76c3a..2668ece5d 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -60,17 +60,16 @@ EthernetInterface::EthernetInterface() connected=false; #ifdef IP_ADDRESS - Ethernet.begin(mac, IP_ADDRESS); + if (Ethernet.begin(mac, IP_ADDRESS) == 0) #else if (Ethernet.begin(mac) == 0) + #endif { DIAG(F("Ethernet.begin FAILED")); return; } - #endif if (Ethernet.hardwareStatus() == EthernetNoHardware) { - DIAG(F("Ethernet shield not found")); - return; + DIAG(F("Ethernet shield not found or W5100")); } unsigned long startmilli = millis(); From eb0861959ca2f5e4b7116d14bb09d6d6fcc702c1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 6 Nov 2022 21:33:40 +0100 Subject: [PATCH 374/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f08a8f110..4aee54574 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211042235Z" +#define GITHUB_SHA "devel-202211062033Z" From 7f3d5475413bb51969d4ba9388701b0c3c7abc9e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 7 Nov 2022 11:20:00 +0100 Subject: [PATCH 375/870] Initialize outboundRing properly to NULL --- EthernetInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EthernetInterface.h b/EthernetInterface.h index ce4a2ef60..e9b6c6077 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -65,7 +65,7 @@ class EthernetInterface { EthernetServer * server; EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv - RingStream * outboundRing; + RingStream * outboundRing = NULL; }; #endif From b061c0b347b08476f620d220efe75c35356ec02e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 7 Nov 2022 11:22:15 +0100 Subject: [PATCH 376/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4aee54574..e870d2560 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211062033Z" +#define GITHUB_SHA "devel-202211071020Z" From 280e61e1fc4d602b9562e23e9ed1560cb9ccd97e Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 7 Nov 2022 11:53:56 +0100 Subject: [PATCH 377/870] Make EthernetInterface code more robust --- EthernetInterface.cpp | 8 +++++++- EthernetInterface.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index 2668ece5d..8dd4d3171 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -163,6 +163,10 @@ bool EthernetInterface::checkLink() { } void EthernetInterface::loop2() { + if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it + if (Diag::ETHERNET) DIAG(F("No outboundRing")); + return; + } // get client from the server EthernetClient client = server->accept(); @@ -217,7 +221,9 @@ void EthernetInterface::loop2() { // handle at most 1 outbound transmission int socketOut=outboundRing->read(); - if (socketOut>=0) { + if (socketOut >= MAX_SOCK_NUM) { + DIAG(F("Ethernet outboundRing socket=%d error"), socketOut); + } else if (socketOut >= 0) { int count=outboundRing->count(); if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count); for(;count>0;count--) clients[socketOut].write(outboundRing->read()); diff --git a/EthernetInterface.h b/EthernetInterface.h index e9b6c6077..8078c3f75 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -62,7 +62,7 @@ class EthernetInterface { ~EthernetInterface(); void loop2(); bool checkLink(); - EthernetServer * server; + EthernetServer * server = NULL; EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv RingStream * outboundRing = NULL; From 65714ed1f2bb6da16bb5e095936dec2469b7fca5 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 8 Nov 2022 07:48:35 +1100 Subject: [PATCH 378/870] Rename Nucleo build target --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1a6356d92..39c8fce32 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,7 +18,7 @@ default_envs = samd21-dev-usb samd21-zero-usb ESP32 - Nucleo-STM32F411RE + Nucleo-F411RE Teensy3.2 Teensy3.5 Teensy3.6 @@ -172,7 +172,7 @@ framework = arduino lib_deps = ${env.lib_deps} build_flags = -std=c++17 -[env:Nucleo-STM32F411RE] +[env:Nucleo-F411RE] platform = ststm32 board = nucleo_f411re framework = arduino From bd8439c2f9f478a2abf052129ac4d6452d8821e1 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 8 Nov 2022 23:35:07 +0100 Subject: [PATCH 379/870] Fix: Roster indexing when sending to withrottle --- WiThrottle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 6c427b7f2..cfc4a7432 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -298,7 +298,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { #ifdef EXRAIL_ACTIVE StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); for (int16_t r=0;r Date: Wed, 9 Nov 2022 00:52:07 +0100 Subject: [PATCH 380/870] Make GETFLASHW code more universal --- EXRAIL2.cpp | 6 +++++- FSH.h | 18 ++++++++++++++---- GITHUB_SHA.h | 2 +- version.h | 4 +++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 9095de814..d4961c765 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -90,9 +90,13 @@ LookList * RMFT2::onDeactivateLookup=NULL; LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; - #define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter) +#ifdef ARDUINO_ARCH_AVR #define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3)) +#else +#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3)) +#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A)) +#endif #define SKIPOP progCounter+=3 diff --git a/FSH.h b/FSH.h index ea54554a9..38cc05272 100644 --- a/FSH.h +++ b/FSH.h @@ -37,7 +37,9 @@ * */ #include + #if defined(ARDUINO_ARCH_MEGAAVR) + #ifdef F #undef F #endif @@ -48,18 +50,26 @@ typedef char FSH; #define FLASH #define strlen_P strlen #define strcpy_P strcpy + #elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) + typedef __FlashStringHelper FSH; #define GETFLASH(addr) pgm_read_byte(addr) -#define GETFLASHW(addr) (*(const unsigned int8_t *)(addr)) | ((*(const unsigned int8_t *)(addr+1)) << 8) +// pgm_read_word is buggy if addr is odd but here +// we do only read well aligned addrs, the others are +// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp. +#define GETFLASHW(addr) pgm_read_word(addr) #ifdef FLASH #undef FLASH #endif #define FLASH PROGMEM -#else + +#else // AVR and AVR compat here + typedef __FlashStringHelper FSH; #define GETFLASH(addr) pgm_read_byte_near(addr) #define GETFLASHW(addr) pgm_read_word_near(addr) #define FLASH PROGMEM -#endif -#endif + +#endif // flash stuff +#endif // FSH diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e870d2560..6413eb55c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211071020Z" +#define GITHUB_SHA "devel-202211082310Z" diff --git a/version.h b/version.h index 43e48e983..f272e1d22 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.4" +#define VERSION "4.2.5" +// 4.2.5 Make GETFLASHW code more universal +// FIX: Withrottle roster index // Ethernet start improvement and link detection // 4.2.4 ESP32 experimental BT support // More DC configurations possible and lower frequency From f5b48619bf3d7ca735f2d56420f07d65fec3d123 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 16 Nov 2022 00:13:31 +0100 Subject: [PATCH 381/870] AVR Mega2560: Set timer reg ADCSRB correct --- DCCTimerAVR.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 3e45ae86f..5ff0e52fa 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -126,6 +126,7 @@ void DCCTimer::reset() { #endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; +bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -136,6 +137,8 @@ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; if (id > NUM_ADC_INPUTS) return -1023; + if (id > 7) + ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); if (analogvals == NULL) @@ -196,7 +199,15 @@ void ADCee::scan() { while (true) { if (mask & usedpins) { // start new ADC aquire on id - ADMUX=(1< 7) // if we use a high ADC pin + bitSet(ADCSRB, MUX5); // set MUX5 bit + else + bitClear(ADCSRB, MUX5); + } +#endif + ADMUX=(1<setBrake(1); waiting = true; From 6a3a891682c44f83f52ec60b4760b25533817e95 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 16 Nov 2022 00:14:11 +0100 Subject: [PATCH 382/870] break to va_end() --- StringFormatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 6cdd5cdd4..87c22e561 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -70,7 +70,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { char* flash=(char*)format; for(int i=0; ; ++i) { char c=GETFLASH(flash+i); - if (c=='\0') return; + if (c=='\0') break; // to va_end() if(c!='%') { stream->print(c); continue; } bool formatContinues=false; From 01e5d4933209f6a44a69e6a46d06165960092cd4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 16 Nov 2022 00:14:54 +0100 Subject: [PATCH 383/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6413eb55c..19880b609 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211082310Z" +#define GITHUB_SHA "devel-202211152314Z" From f4aa572df2352c6c260b820a4ff35a59292b42f3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 18 Nov 2022 20:19:53 +0100 Subject: [PATCH 384/870] Remove RAM thief --- GITHUB_SHA.h | 2 +- RingStream.cpp | 11 ++++++----- version.h | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 19880b609..12884d89f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211152314Z" +#define GITHUB_SHA "devel-202211181919Z" diff --git a/RingStream.cpp b/RingStream.cpp index 9db6230ec..7f107288b 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -189,11 +189,12 @@ bool RingStream::commit() { _mark++; if (_mark==_len) _mark=0; _buffer[_mark]=lowByte(_count); - { char s[_count+2]; - strncpy(s, (const char*)&(_buffer[_mark+1]), _count); - s[_count]=0; - //DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); - } + // Enable this for debugging only, it requires A LOT of RAM + //{ char s[_count+2]; + // strncpy(s, (const char*)&(_buffer[_mark+1]), _count); + // s[_count]=0; + // DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); + //} _ringClient = NO_CLIENT; return true; // commit worked } diff --git a/version.h b/version.h index f272e1d22..024484061 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.5" +#define VERSION "4.2.6" +// 4.2.6 FIX: Remove RAM thief +// FIX: ADC port 8-15 fix // 4.2.5 Make GETFLASHW code more universal // FIX: Withrottle roster index // Ethernet start improvement and link detection From 70203c373396203a9fef341890bc58cf86818afa Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 22 Nov 2022 17:15:13 +0000 Subject: [PATCH 385/870] Fix to IO_DFPlayer.h - device was ignoring commands The DFPlayer device does not like successive commands arriving to quickly after one another and may ignore a command. The driver has been modified to enforce a delay between commands by sending pad characters where necessary. --- IO_DFPlayer.h | 65 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index c60d1168f..d4684e7cd 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2022, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API * @@ -36,24 +36,31 @@ * In mySetup function within mySetup.cpp: * 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; + * Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card; + * Writing an analogue value 0 to the first pin (3500) will stop the file playing; + * Writing an analogue value 0-30 to the second pin (3501) will set the volume; + * Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g. + the first file will be played by setting pin 3500, the second by setting pin 3501 etc.; + * Writing a digital value of 0 to any pin will stop the player; * 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 - * SET(3501) -- starts playing the second file on the SD card + * SET(3500) -- starts playing the first file (file 1) on the SD card + * SET(3501) -- starts playing the second file (file 2) 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 - * 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 + * SERVO(3500,2,Instant) -- plays file 2 at current volume + * SERVO(3501,20,Instant) -- 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. * A 1k resistor in series with the module's RX terminal will alleviate this. + * + * Files on the SD card are numbered according to their order in the directory on the + * card (as listed by the DIR command in Windows). This may not match the order of the files + * as displayed by Windows File Manager, which sorts the file names. It is suggested that + * files be copied into an empty SDcard in the desired order, one at a time. */ #ifndef IO_DFPlayer_h @@ -68,6 +75,19 @@ class DFPlayer : public IODevice { uint8_t _inputIndex = 0; unsigned long _commandSendTime; // Allows timeout processing + // When two commands are sent in quick succession, the device sometimes + // fails to execute one. A delay is required between successive commands. + // This could be implemented by buffering commands and outputting them + // from the loop() function, but it would somewhat complicate the + // driver. A simpler solution is to output a number of NUL pad characters + // between successive command strings if there isn't sufficient elapsed time + // between them. At 9600 baud, each pad character takes approximately + // 1ms to complete. Experiments indicate that the minimum number of pads + // for reliable operation is 17. This gives 17.7ms between the end of one + // command and the beginning of the next, or 28ms between successive commands + // being completed. I've allowed 20 characters, which is almost 21ms. + const int numPadCharacters = 20; // Number of pad characters between commands + public: static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) { @@ -83,8 +103,10 @@ class DFPlayer : public IODevice { addDevice(this); } - void _begin() override { - _serial->begin(9600); + void _begin() override { + _serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit + // Flush any data in input queue + while (_serial->available()) _serial->read(); _deviceState = DEVSTATE_INITIALISING; // Send a query to the device to see if it responds @@ -94,10 +116,10 @@ class DFPlayer : public IODevice { 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" + // Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF" while (_serial->available()) { int c = _serial->read(); - if (c == 0x7E) + if (c == 0x7E && _inputIndex == 0) _inputIndex = 1; else if ((c==0xFF && _inputIndex==1) || (c==0x3D && _inputIndex==3) @@ -124,8 +146,8 @@ class DFPlayer : public IODevice { } 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) { + // Check if the initial prompt to device has timed out. Allow 5 seconds + if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) { DIAG(F("DFPlayer device not responding on serial port")); _deviceState = DEVSTATE_FAILED; } @@ -218,6 +240,7 @@ class DFPlayer : public IODevice { void sendPacket(uint8_t command, uint16_t arg = 0) { + unsigned long currentMillis = millis(); uint8_t out[] = { 0x7E, 0xFF, 06, @@ -231,7 +254,19 @@ class DFPlayer : public IODevice { setChecksum(out); + // Check how long since the last command was sent. + // Each character takes approx 1ms at 9600 baud + unsigned long minimumGap = numPadCharacters + sizeof(out); + if (currentMillis - _commandSendTime < minimumGap) { + // Output some pad characters to add an + // artificial delay between commands + for (int i=0; iwrite(0); + } + + // Now output the command _serial->write(out, sizeof(out)); + _commandSendTime = currentMillis; } uint16_t calcChecksum(uint8_t* packet) From 644cb29a3a55f32f6db58459dad81afc90822fa3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 22 Nov 2022 17:24:11 +0000 Subject: [PATCH 386/870] Performance improvements in function DCC::issueReminders Function issueReminders was taking around 1700us to complete. It has been refactored to optimise calculations and reduce the amount of the loco table that needs to be scanned each time. It now takes typically under 50us to execute. --- DCC.cpp | 44 +++++++++++++++++--------------------------- DCC.h | 3 ++- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 23348425d..375725aec 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -589,36 +589,24 @@ byte DCC::loopStatus=0; void DCC::loop() { TrackManager::loop(); // power overload checks + bitSet(DDRE, 4); + bitSet(PORTE, 4); issueReminders(); + bitClear(PORTE, 4); } void DCC::issueReminders() { // if the main track transmitter still has a pending packet, skip this time around. if ( DCCWaveform::mainTrack.getPacketPending()) return; - - // This loop searches for a loco in the speed table starting at nextLoco and cycling back around - /* - for (int reg=0;reg=MAX_LOCOS) slot-=MAX_LOCOS; - if (speedTable[slot].loco > 0) { - // have found the next loco to remind - // issueReminder will return true if this loco is completed (ie speed and functions) - if (issueReminder(slot)) nextLoco=slot+1; - return; - } - } - */ - for (int reg=nextLoco;reg 0) { - // have found the next loco to remind - // issueReminder will return true if this loco is completed (ie speed and functions) - if (issueReminder(slot)) - nextLoco=(slot+1)%MAX_LOCOS; - return; - } - } + // Move to next loco slot. If occupied, send a reminder. + int reg = lastLocoReminder+1; + if (reg > highestUsedReg) reg = 0; // Go to start of table + if (speedTable[reg].loco > 0) { + // have found loco to remind + if (issueReminder(reg)) + lastLocoReminder = reg; + } else + lastLocoReminder = reg; } bool DCC::issueReminder(int reg) { @@ -698,6 +686,7 @@ int DCC::lookupSpeedTable(int locoId, bool autoCreate) { speedTable[reg].groupFlags=0; speedTable[reg].functions=0; } + if (reg > highestUsedReg) highestUsedReg = reg; return reg; } @@ -705,7 +694,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { if (loco==0) { // broadcast stop/estop but dont change direction - for (int reg = 0; reg < MAX_LOCOS; reg++) { + for (int reg = 0; reg < highestUsedReg; reg++) { if (speedTable[reg].loco==0) continue; byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); if (speedTable[reg].speedCode != newspeed) { @@ -725,13 +714,14 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { } DCC::LOCO DCC::speedTable[MAX_LOCOS]; -int DCC::nextLoco = 0; +int DCC::lastLocoReminder = 0; +int DCC::highestUsedReg = 0; void DCC::displayCabList(Print * stream) { int used=0; - for (int reg = 0; reg < MAX_LOCOS; reg++) { + for (int reg = 0; reg <= highestUsedReg; reg++) { if (speedTable[reg].loco>0) { used ++; StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"), diff --git a/DCC.h b/DCC.h index 46b3b8360..15d5a4f7e 100644 --- a/DCC.h +++ b/DCC.h @@ -108,7 +108,8 @@ class DCC static void updateLocoReminder(int loco, byte speedCode); static void setFunctionInternal(int cab, byte fByte, byte eByte); static bool issueReminder(int reg); - static int nextLoco; + static int lastLocoReminder; + static int highestUsedReg; static FSH *shieldName; static byte globalSpeedsteps; From ce5d546b9b25088248220da466eb797db6abbbf4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 22 Nov 2022 17:26:11 +0000 Subject: [PATCH 387/870] Update DCC.cpp to remove diagnostic code. --- DCC.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 375725aec..abdee732a 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -589,10 +589,7 @@ byte DCC::loopStatus=0; void DCC::loop() { TrackManager::loop(); // power overload checks - bitSet(DDRE, 4); - bitSet(PORTE, 4); issueReminders(); - bitClear(PORTE, 4); } void DCC::issueReminders() { From aa02cd11e3624600e48a1a66dddec1172cded3f0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 23 Nov 2022 22:44:29 +0100 Subject: [PATCH 388/870] firebox disable, gcc flag for smaller binary --- platformio.ini | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/platformio.ini b/platformio.ini index 39c8fce32..bc1ef0ee2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,17 +50,18 @@ monitor_speed = 115200 monitor_echo = yes build_flags = -std=c++17 -[env:samc21-firebox] -platform = atmelsam -board = firebox -framework = arduino -upload_protocol = atmel-ice -lib_deps = - ${env.lib_deps} - SparkFun External EEPROM Arduino Library -monitor_speed = 115200 -monitor_echo = yes -build_flags = -std=c++17 +; Firebox disabled for now +; [env:samc21-firebox] +; platform = atmelsam +; board = firebox +; framework = arduino +; upload_protocol = atmel-ice +; lib_deps = +; ${env.lib_deps} +; SparkFun External EEPROM Arduino Library +;monitor_speed = 115200 +;monitor_echo = yes +;build_flags = -std=c++17 [env:mega2560-debug] platform = atmelavr @@ -108,6 +109,10 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes +; Example, but v12 does generate bigger binaries +; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux +; Should make binaries smaller +build_flags = -mcall-prologues [env:mega328] platform = atmelavr @@ -155,6 +160,8 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes +; Should make binaries smaller +build_flags = -mcall-prologues [env:nano] platform = atmelavr From d062de2eb8d5cc4f73b7861f0a312feebcb77d4f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 24 Nov 2022 20:24:15 +0100 Subject: [PATCH 389/870] better pseudo random --- EXRAIL2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index d4961c765..96cb447ca 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -727,7 +727,7 @@ void RMFT2::loop2() { break; case OPCODE_IFRANDOM: // do block on random percentage - skipIf=(int16_t)(micros()%100) >= operand; + skipIf=(uint8_t)micros() >= operand * 255/100; break; case OPCODE_IFRESERVE: // do block if we successfully RERSERVE From 984fd2fa0882684970fb918e88c0e3c481375d96 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 25 Nov 2022 20:44:16 +0100 Subject: [PATCH 390/870] select better bytes for the faked mac addr --- DCCTimerAVR.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 5ff0e52fa..9b16c4715 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -88,7 +88,10 @@ void DCCTimer::clearPWM() { void DCCTimer::getSimulatedMacAddress(byte mac[6]) { for (byte i=0; i<6; i++) { - mac[i]=boot_signature_byte_get(0x0E + i); + // take the fist 3 and last 3 of the serial. + // the first 5 of 8 are at 0x0E to 0x013 + // the last 3 of 8 are at 0x15 to 0x017 + mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0)); } mac[0] &= 0xFE; mac[0] |= 0x02; From 913f0a0c86a2a958ace4b3d412874a267e66e410 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sun, 27 Nov 2022 21:04:49 +0800 Subject: [PATCH 391/870] STM32F412ZG/F446ZE serial support update --- DCCTimerSTM32.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index eb0d36523..831c87e9b 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -28,13 +28,9 @@ // This is to avoid repetition and duplication. #ifdef ARDUINO_ARCH_STM32 -#include "FSH.h" //PMA temp debug -#include "DIAG.h" //PMA temp debug #include "DCCTimer.h" -#define STM32F411RE // PMA - ideally this ought to be derived from within the STM32 support somehow - -#if defined(STM32F411RE) +#if defined(ARDUINO_NUCLEO_F411RE) // STM32F411RE doesn't have Serial1 defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console @@ -42,9 +38,9 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F // for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the F411RE) HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE -#elif defined(STM32F446ZE) -// STM32F446ZE doesn't have Serial1 defined by default -HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE +#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F446ZE) +// Nucleo-144 boards don't have Serial1 defined by default +HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #else #warning Serial1 not defined #endif From a6ae1a48a2837942a0fb8b1b13109dc08bd98cc8 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 28 Nov 2022 10:44:41 +0800 Subject: [PATCH 392/870] Fixed STM32F4xx MAC address simulation --- DCCTimerSTM32.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 831c87e9b..b26aa4d0b 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -38,7 +38,7 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F // for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the F411RE) HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE -#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F446ZE) +#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #else @@ -90,10 +90,9 @@ void DCCTimer::clearPWM() { } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { - volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C; - volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040; -// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044; -// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048; + volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10; + volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14; + volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18; volatile uint32_t m1 = *serno1; volatile uint32_t m2 = *serno2; From d95096ded852429f04310c1546a57fd9f990b190 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 30 Nov 2022 10:11:27 +0800 Subject: [PATCH 393/870] Fixes STM32 compiler warning, and WIT/WIFI diags --- EXRAIL2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 96cb447ca..67cfce44a 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -965,7 +965,7 @@ void RMFT2::delayMe(long delay) { delayStart=millis(); } -boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { +bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) { if (FLAGOVERFLOW(id)) return false; // Outside range limit byte f=flags[id]; f &= ~offMask; From 45b36c48cbfd4fbeda4b85cb946b33d38fb672f9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 30 Nov 2022 13:16:04 +0100 Subject: [PATCH 394/870] be more strict about int vs char for the wifi diag --- WifiInterface.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 4d93eda48..347d7ef60 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -377,11 +377,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, char *locator = (char *)waitfor; DIAG(F("Wifi Check: [%E]"), waitfor); while ( millis() - startTime < timeout) { - while (wifiStream->available()) { - int ch = wifiStream->read(); + int nextchar; + while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) { + char ch = (char)nextchar; if (echo) { if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE - else USB_SERIAL.print((char)ch); + else USB_SERIAL.print(ch); } if (ch != GETFLASH(locator)) locator = (char *)waitfor; if (ch == GETFLASH(locator)) { From b671d70dfe87356feb65ec842efc7bc61fce3d22 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 4 Dec 2022 20:09:14 +0100 Subject: [PATCH 395/870] fix static IP addr --- EthernetInterface.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index 8dd4d3171..7c5d7685b 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -60,14 +60,14 @@ EthernetInterface::EthernetInterface() connected=false; #ifdef IP_ADDRESS - if (Ethernet.begin(mac, IP_ADDRESS) == 0) + Ethernet.begin(mac, IP_ADDRESS); #else if (Ethernet.begin(mac) == 0) - #endif { DIAG(F("Ethernet.begin FAILED")); return; } + #endif if (Ethernet.hardwareStatus() == EthernetNoHardware) { DIAG(F("Ethernet shield not found or W5100")); } @@ -135,7 +135,10 @@ bool EthernetInterface::checkLink() { if(!connected) { DIAG(F("Ethernet cable connected")); connected=true; - IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address + #ifdef IP_ADDRESS + setLocalIP(IP_ADDRESS); // for static IP, set it again + #endif + IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static) server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT server->begin(); LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); From cb1fc75077986ddffcff3379ea8d0b29ecb179da Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 4 Dec 2022 20:12:04 +0100 Subject: [PATCH 396/870] version --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 12884d89f..db58e067b 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202211181919Z" +#define GITHUB_SHA "devel-202212041810Z" diff --git a/version.h b/version.h index 024484061..580bfdf07 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.6" +#define VERSION "4.2.7pre1" +// 4.2.7 FIX: Static IP addr // 4.2.6 FIX: Remove RAM thief // FIX: ADC port 8-15 fix // 4.2.5 Make GETFLASHW code more universal From 13368c319a506d5bf193445ec0c0ac4d5c676294 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 5 Dec 2022 15:52:23 +0100 Subject: [PATCH 397/870] reuse WiThrottle list entries --- GITHUB_SHA.h | 2 +- WiThrottle.cpp | 2 +- version.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index db58e067b..d6c973888 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202212041810Z" +#define GITHUB_SHA "devel-202212051450Z" diff --git a/WiThrottle.cpp b/WiThrottle.cpp index cfc4a7432..019a515a9 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -378,7 +378,7 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ } //use first empty "slot" on this client's list, will be added to DCC registration list for (int loco=0;loco Date: Thu, 8 Dec 2022 14:21:01 +1000 Subject: [PATCH 398/870] Basic shell of device driver started --- IO_EXIOExpander.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++ myPinMap.example.h | 28 ++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 IO_EXIOExpander.h create mode 100644 myPinMap.example.h diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h new file mode 100644 index 000000000..c979176f2 --- /dev/null +++ b/IO_EXIOExpander.h @@ -0,0 +1,79 @@ +/* + * © 2021, Peter Cole. All rights reserved. + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EX-IOExpander.h device driver integrates with one or more EX-IOExpander devices. +* This device driver will configure the device and all I/O ports on startup, along with +* interacting with the device for all input/output duties. +*/ + +#ifndef IO_EX_IOEXPANDER_H +#define IO_EX_IOEXPANDER_H + +#include "IO_GPIOBase.h" +#include "FSH.h" + +#if __has_include ("myPinMap.h") + #include "myPinMap.h" +#else + #warning myPinMap.h not found. Using defaults from myPinMap.example.h + #include "myPinMap.example.h" +#endif + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for EX-IOExpander. + */ + +class EXIOExpander : public IODevice { +public: + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); + } + +private: + // Constructor + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + _firstVpin = firstVpin; + _nPins = nPins; + _i2cAddress = i2cAddress; + addDevice(this); + } + + void _begin() { + // Initialise EX-IOExander device + if (I2CManager.exists(_i2cAddress)) { +#ifdef DIAG_IO + _display(); +#endif + } else { + DIAG(F("EX-IOExpander device not found, I2C:%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + } + } + + void _display() override { + DIAG(F("EX-IOExpander I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, + _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + uint8_t _i2cAddress; +}; + +#endif \ No newline at end of file diff --git a/myPinMap.example.h b/myPinMap.example.h new file mode 100644 index 000000000..3afebc199 --- /dev/null +++ b/myPinMap.example.h @@ -0,0 +1,28 @@ +/* + * © 2022 Peter Cole. All rights reserved. + * + * This is the example configuration file for the EX-IOExpander pin map file. + * + * It is highly recommended to copy this to "myPinMap.h" and modify to suit your specific + * requirements. + * + * NOTE: Modifications to this file will be overwritten by future software updates. + */ +#ifndef MYPINMAP_H +#define MYPINMAP_H + +///////////////////////////////////////////////////////////////////////////////////// +// Define the number of I/O pins to be configured on the EX-IOExpander device +// +#define NUMBER_OF_DIGITAL_PINS 12 +#define NUMBER_OF_ANALOGUE_PINS 4 + +///////////////////////////////////////////////////////////////////////////////////// +// Define the pin map +// +// You must define the correct number of pins as per NUMBER_OF_PINS above +// +static uint8_t digitalPinMap[NUMBER_OF_DIGITAL_PINS] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; +static uint8_t analoguePinMap[NUMBER_OF_ANALOGUE_PINS] = {A0, A1, A2, A3}; + +#endif \ No newline at end of file From 2d27cb052d499fa23454239c567670f88bb9c98c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Fri, 9 Dec 2022 14:41:48 +1000 Subject: [PATCH 399/870] Add registers --- IO_EXIOExpander.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index c979176f2..67cbb3d7f 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -74,6 +74,15 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; + + enum { + REG_EXIOINIT = 0x00, // Flag to initialise setup procedure + REG_EXIODPIN = 0x01, // Flag we're sending digital pin assignments + REG_EXIOAPIN = 0x02, // Flag we're sending analogue pin assignments + REG_EXIORDY = 0x03, // Flag we have completed setup procedure, also for EX-IO to ACK setup + REG_EXIODDIR = 0x04, // Flag we're sending digital pin direction configuration + REG_EXIODPUP = 0x05, // Flag we're sending digital pin pullup configuration + }; }; #endif \ No newline at end of file From 06945bb114fcfe009de039c0fa5ee4215d347873 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 10 Dec 2022 08:23:46 +1000 Subject: [PATCH 400/870] Try to add pin map classes --- EX-IOExpanderPinMaps.h | 38 +++++++++++++++++++++++++++ IO_EXIOExpander.h | 54 ++++++++++++++++++++++++++++++++------- myEX-IOExpander.example.h | 23 +++++++++++++++++ myPinMap.example.h | 28 -------------------- 4 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 EX-IOExpanderPinMaps.h create mode 100644 myEX-IOExpander.example.h delete mode 100644 myPinMap.example.h diff --git a/EX-IOExpanderPinMaps.h b/EX-IOExpanderPinMaps.h new file mode 100644 index 000000000..b09dca395 --- /dev/null +++ b/EX-IOExpanderPinMaps.h @@ -0,0 +1,38 @@ +/* + * © 2022 Peter Cole + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* +* This file defines default pin maps for the various architectures supported +* by default by EX-IOExpander. +* +* Custom user defined pinmaps should be defined in myEX-IOExpander.h. +* +* Any modifications to this file will be overwritten by future software updates. +*/ + +#define DEFAULT_NANO_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) +#define DEFAULT_NANO_ANALOGUE_PINMAP new EXIOAnaloguePinMap(6,A0,A1,A2,A3,A6,A7) + +#define DEFAULT_UNO F("DEFAULT_UNO"), \ + new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) \ + new EXIOAnaloguePinMap(4,A0,A1,A2,A3) + +#define DEFAULT_MEGA F("DEFAULT_MEGA"), \ + new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) \ + new EXIOAnaloguePinMap(4,A0,A1,A2,A3) \ No newline at end of file diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index c979176f2..052ad7264 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -28,31 +28,65 @@ #include "IO_GPIOBase.h" #include "FSH.h" +#include "EX-IOExpanderPinMaps.h" -#if __has_include ("myPinMap.h") - #include "myPinMap.h" -#else - #warning myPinMap.h not found. Using defaults from myPinMap.example.h - #include "myPinMap.example.h" +// Include user defined pin maps in myEX-IOExpander if defined +#if __has_include ("myEX-IOExpander.h") + #include "myEX-IOExpander.h" #endif +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * EXIODigitalPinMap class for EX-IOExpander. + */ +class EXIODigitalPinMap { + public: + EXIODigitalPinMap(uint8_t numDigitalPins, uint8_t...); + EXIODigitalPinMap() = default; + + private: + EXIODigitalPinMap(uint8_t numDigitalPins, ...) { + _numDigitalPins = numDigitalPins; + uint8_t _digitalPinMap[_numDigitalPins]; + va_list _pinList; + va_start(_pinList, _numDigitalPins); + for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { + _digitalPinMap[pin] = va_arg(_pinList, int); + } + va_end(_pinList); + } + + uint8_t _numDigitalPins; +}; + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * EXIOAnaloguePinMap class for EX-IOExpander. + */ +class EXIOAnaloguePinMap { + public: + EXIOAnaloguePinMap(uint8_t numAnaloguePins, ...); + EXIOAnaloguePinMap() = default; +}; + ///////////////////////////////////////////////////////////////////////////////////////////////////// /* * IODevice subclass for EX-IOExpander. */ - class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress, EXIODigitalPinMap digitalPinMap, EXIOAnaloguePinMap analoguePinMap) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, digitalPinMap, analoguePinMap); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, EXIODigitalPinMap digitalPinMap, EXIOAnaloguePinMap analoguePinMap) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; + _digitalPinMap = digitalPinMap; + _analoguePinMap = analoguePinMap; addDevice(this); } @@ -74,6 +108,8 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; + EXIODigitalPinMap _digitalPinMap; + EXIOAnaloguePinMap _analoguePinMap; }; #endif \ No newline at end of file diff --git a/myEX-IOExpander.example.h b/myEX-IOExpander.example.h new file mode 100644 index 000000000..3402c926a --- /dev/null +++ b/myEX-IOExpander.example.h @@ -0,0 +1,23 @@ +/* + * © 2022 Peter Cole. All rights reserved. + * + * This is the example configuration file for a custom EX-IOExpander pin map file. + * + * It is highly recommended to copy this to "myEX-IOExpander.h" and modify to suit your specific + * requirements. + * + * If you are simply using a default definition for a defined microcontroller, then you don't + * need to use this file, and instead can use one of the existing definitions. + * + * Refer to https://dcc-ex.com for the full documentation. + * + * NOTE: Modifications to this file will be overwritten by future software updates. + */ +#ifndef MYEX_IOEXPANDER_H +#define MYEX_IOEXPANDER_H + +#define MY_CUSTOM_NANO F("MY_NANO_PINMAP"), \ + new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13) \ + new EXIOAnaloguePinMap(4,A0,A1,A2,A3) + +#endif \ No newline at end of file diff --git a/myPinMap.example.h b/myPinMap.example.h deleted file mode 100644 index 3afebc199..000000000 --- a/myPinMap.example.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * © 2022 Peter Cole. All rights reserved. - * - * This is the example configuration file for the EX-IOExpander pin map file. - * - * It is highly recommended to copy this to "myPinMap.h" and modify to suit your specific - * requirements. - * - * NOTE: Modifications to this file will be overwritten by future software updates. - */ -#ifndef MYPINMAP_H -#define MYPINMAP_H - -///////////////////////////////////////////////////////////////////////////////////// -// Define the number of I/O pins to be configured on the EX-IOExpander device -// -#define NUMBER_OF_DIGITAL_PINS 12 -#define NUMBER_OF_ANALOGUE_PINS 4 - -///////////////////////////////////////////////////////////////////////////////////// -// Define the pin map -// -// You must define the correct number of pins as per NUMBER_OF_PINS above -// -static uint8_t digitalPinMap[NUMBER_OF_DIGITAL_PINS] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; -static uint8_t analoguePinMap[NUMBER_OF_ANALOGUE_PINS] = {A0, A1, A2, A3}; - -#endif \ No newline at end of file From 91049560094987a6c8df2456ac1c6905a66817fa Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 10 Dec 2022 08:28:20 +1000 Subject: [PATCH 401/870] Fix default pin maps --- EX-IOExpanderPinMaps.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/EX-IOExpanderPinMaps.h b/EX-IOExpanderPinMaps.h index b09dca395..b4fed5543 100644 --- a/EX-IOExpanderPinMaps.h +++ b/EX-IOExpanderPinMaps.h @@ -29,10 +29,8 @@ #define DEFAULT_NANO_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) #define DEFAULT_NANO_ANALOGUE_PINMAP new EXIOAnaloguePinMap(6,A0,A1,A2,A3,A6,A7) -#define DEFAULT_UNO F("DEFAULT_UNO"), \ - new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) \ - new EXIOAnaloguePinMap(4,A0,A1,A2,A3) +#define DEFAULT_UNO_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) +#define DEFAULT_UNO_ANALOGUE_PINMAP new EXIOAnaloguePinMap(4,A0,A1,A2,A3) -#define DEFAULT_MEGA F("DEFAULT_MEGA"), \ - new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) \ - new EXIOAnaloguePinMap(4,A0,A1,A2,A3) \ No newline at end of file +#define DEFAULT_MEGA_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) +#define DEFAULT_MEGA_ANALOGUE_PINMAP new EXIOAnaloguePinMap(4,A0,A1,A2,A3) From 7bc043319704670a3abacc030327504bdef586a0 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 10 Dec 2022 08:32:15 +1000 Subject: [PATCH 402/870] Add myHal.cpp example to driver --- IO_EXIOExpander.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 352711fd0..2157b798d 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -21,6 +21,15 @@ * The IO_EX-IOExpander.h device driver integrates with one or more EX-IOExpander devices. * This device driver will configure the device and all I/O ports on startup, along with * interacting with the device for all input/output duties. +* +* To create EX-IOExpander devices, these are defined in myHal.cpp: +* +* #include "IO_EX-IOExpander.h" +* +* void halSetup() { +* // EXIOExpander::create(vpin, num_vpins, i2c_address, digital_pinmap, analogue_pinmap); +* EXIOExpander::create(800, 18, 0x90, DEFAULT_NANO_DIGITAL_PINMAP, DEFAULT_NANO_ANALOGUE_PINMAP); +} */ #ifndef IO_EX_IOEXPANDER_H From 1d5897d2d22973e50252e91065ec2479f608fbea Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 10 Dec 2022 19:14:32 +1000 Subject: [PATCH 403/870] A bit lost --- EX-IOExpanderPinMaps.h | 13 ++++++----- IO_EXIOExpander.h | 48 ++++----------------------------------- myEX-IOExpander.example.h | 5 ++-- 3 files changed, 14 insertions(+), 52 deletions(-) diff --git a/EX-IOExpanderPinMaps.h b/EX-IOExpanderPinMaps.h index b4fed5543..46d03a9d1 100644 --- a/EX-IOExpanderPinMaps.h +++ b/EX-IOExpanderPinMaps.h @@ -26,11 +26,12 @@ * Any modifications to this file will be overwritten by future software updates. */ -#define DEFAULT_NANO_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) -#define DEFAULT_NANO_ANALOGUE_PINMAP new EXIOAnaloguePinMap(6,A0,A1,A2,A3,A6,A7) +#define DEFAULT_NANO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 +#define DEFAULT_NANO_ANALOGUE_PINMAP A0,A1,A2,A3,A6,A7 -#define DEFAULT_UNO_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) -#define DEFAULT_UNO_ANALOGUE_PINMAP new EXIOAnaloguePinMap(4,A0,A1,A2,A3) +#define DEFAULT_UNO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 +#define DEFAULT_UNO_ANALOGUE_PINMAP A0,A1,A2,A3 -#define DEFAULT_MEGA_DIGITAL_PINMAP new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A6,A7) -#define DEFAULT_MEGA_ANALOGUE_PINMAP new EXIOAnaloguePinMap(4,A0,A1,A2,A3) +#define DEFAULT_MEGA_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,22,23,24,25,26,27,28,29, \ + 30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49 +#define DEFAULT_MEGA_ANALOGUE_PINMAP A0,A1,A2,A3,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15 diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 2157b798d..0521ac7e1 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -27,8 +27,8 @@ * #include "IO_EX-IOExpander.h" * * void halSetup() { -* // EXIOExpander::create(vpin, num_vpins, i2c_address, digital_pinmap, analogue_pinmap); -* EXIOExpander::create(800, 18, 0x90, DEFAULT_NANO_DIGITAL_PINMAP, DEFAULT_NANO_ANALOGUE_PINMAP); +* // EXIOExpander::create(vpin, num_vpins, i2c_address); +* EXIOExpander::create(800, 18, 0x90); } */ @@ -44,58 +44,22 @@ #include "myEX-IOExpander.h" #endif -///////////////////////////////////////////////////////////////////////////////////////////////////// -/* - * EXIODigitalPinMap class for EX-IOExpander. - */ -class EXIODigitalPinMap { - public: - EXIODigitalPinMap(uint8_t numDigitalPins, uint8_t...); - EXIODigitalPinMap() = default; - - private: - EXIODigitalPinMap(uint8_t numDigitalPins, ...) { - _numDigitalPins = numDigitalPins; - uint8_t _digitalPinMap[_numDigitalPins]; - va_list _pinList; - va_start(_pinList, _numDigitalPins); - for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { - _digitalPinMap[pin] = va_arg(_pinList, int); - } - va_end(_pinList); - } - - uint8_t _numDigitalPins; -}; - -///////////////////////////////////////////////////////////////////////////////////////////////////// -/* - * EXIOAnaloguePinMap class for EX-IOExpander. - */ -class EXIOAnaloguePinMap { - public: - EXIOAnaloguePinMap(uint8_t numAnaloguePins, ...); - EXIOAnaloguePinMap() = default; -}; - ///////////////////////////////////////////////////////////////////////////////////////////////////// /* * IODevice subclass for EX-IOExpander. */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, EXIODigitalPinMap digitalPinMap, EXIOAnaloguePinMap analoguePinMap) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, digitalPinMap, analoguePinMap); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, EXIODigitalPinMap digitalPinMap, EXIOAnaloguePinMap analoguePinMap) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _digitalPinMap = digitalPinMap; - _analoguePinMap = analoguePinMap; addDevice(this); } @@ -117,8 +81,6 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - EXIODigitalPinMap _digitalPinMap; - EXIOAnaloguePinMap _analoguePinMap; enum { REG_EXIOINIT = 0x00, // Flag to initialise setup procedure diff --git a/myEX-IOExpander.example.h b/myEX-IOExpander.example.h index 3402c926a..94e378e6f 100644 --- a/myEX-IOExpander.example.h +++ b/myEX-IOExpander.example.h @@ -16,8 +16,7 @@ #ifndef MYEX_IOEXPANDER_H #define MYEX_IOEXPANDER_H -#define MY_CUSTOM_NANO F("MY_NANO_PINMAP"), \ - new EXIODigitalPinMap(12,2,3,4,5,6,7,8,9,10,11,12,13) \ - new EXIOAnaloguePinMap(4,A0,A1,A2,A3) +#define MY_NANO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 +#define MY_NANO_ANALOGUE_PINMAP A0,A1,A2,A3 #endif \ No newline at end of file From cb9a8bb7a66a14fb67f707b9c1333936e7a9e438 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 11 Dec 2022 10:22:48 +1000 Subject: [PATCH 404/870] Getting somewhere --- EX-IOExpanderPins.h | 46 +++++++++++++++++++++++++++++++++++++++++++++ IO_EXIOExpander.h | 36 +++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 EX-IOExpanderPins.h diff --git a/EX-IOExpanderPins.h b/EX-IOExpanderPins.h new file mode 100644 index 000000000..fa328735b --- /dev/null +++ b/EX-IOExpanderPins.h @@ -0,0 +1,46 @@ +/* + * © 2022 Peter Cole + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* +* This file defines default pin maps for the various architectures supported +* by default by EX-IOExpander. +* +* Custom user defined pinmaps should be defined in myEX-IOExpander.h. +* +* Any modifications to this file will be overwritten by future software updates. +*/ + +// #define DEFAULT_NANO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 +// #define DEFAULT_NANO_ANALOGUE_PINMAP A0,A1,A2,A3,A6,A7 + +#define EXIO_UNO_DIGITAL_PINS 12 +#define EXIO_UNO_ANALOGUE_PINS 4 + +#define EXIO_NANO_DIGITAL_PINS 12 +#define EXIO_NANO_ANALOGUE_PINS 6 + +#define EXIO_MEGA_DIGITAL_PINS 46 +#define EXIO_MEGA_ANALOGUE_PINS 16 + +// #define EXIO_UNO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 +// #define EXIO_UNO_ANALOGUE_PINMAP A0,A1,A2,A3 + +// #define DEFAULT_MEGA_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,22,23,24,25,26,27,28,29, \ +// 30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49 +// #define DEFAULT_MEGA_ANALOGUE_PINMAP A0,A1,A2,A3,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15 diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 0521ac7e1..efd09cd63 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -37,7 +37,7 @@ #include "IO_GPIOBase.h" #include "FSH.h" -#include "EX-IOExpanderPinMaps.h" +#include "EX-IOExpanderPins.h" // Include user defined pin maps in myEX-IOExpander if defined #if __has_include ("myEX-IOExpander.h") @@ -50,16 +50,18 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress, uint8_t numDigitalPins, uint8_t numAnaloguePins) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint8_t numDigitalPins, uint8_t numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; + _numDigitalPins = numDigitalPins; + _numAnaloguePins = numAnaloguePins; addDevice(this); } @@ -69,18 +71,44 @@ class EXIOExpander : public IODevice { #ifdef DIAG_IO _display(); #endif + _setupDevice(); } else { DIAG(F("EX-IOExpander device not found, I2C:%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; } } + void _setupDevice() { + // Send digital and analogue pin counts + I2CManager.write(_i2cAddress, 3, REG_EXIOINIT, _numDigitalPins, _numAnaloguePins); + // Enable digital ports + _digitalPinBytes = (_numDigitalPins + 7) / 8; + uint8_t enableDigitalPins[_digitalPinBytes]; + for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { + int pinByte = ((pin + 7) / 8); + bitSet(enableDigitalPins[pinByte], (pin - (pinByte * 8))); + } + I2CManager.write(_i2cAddress, _digitalPinBytes + 1, REG_EXIODPIN, enableDigitalPins); + // Enable analogue ports + _analoguePinBytes = (_numAnaloguePins + 7) / 8; + uint8_t enableAnaloguePins[_analoguePinBytes]; + for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { + int pinByte = ((pin + 7) / 8); + bitSet(enableAnaloguePins[pinByte], (pin - (pinByte * 8))); + } + I2CManager.write(_i2cAddress, _analoguePinBytes + 1, REG_EXIOAPIN, enableAnaloguePins); + } + void _display() override { DIAG(F("EX-IOExpander I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; + uint8_t _numDigitalPins; + uint8_t _numAnaloguePins; + int _digitalPinBytes; + int _analoguePinBytes; enum { REG_EXIOINIT = 0x00, // Flag to initialise setup procedure From 9699a44081d8972776cd61ebf2b662d9c9dbf015 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 11 Dec 2022 10:25:29 +1000 Subject: [PATCH 405/870] Rename pin file --- EX-IOExpanderPinMaps.h | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 EX-IOExpanderPinMaps.h diff --git a/EX-IOExpanderPinMaps.h b/EX-IOExpanderPinMaps.h deleted file mode 100644 index 46d03a9d1..000000000 --- a/EX-IOExpanderPinMaps.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * © 2022 Peter Cole - * - * This file is part of EX-CommandStation - * - * This is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * It is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CommandStation. If not, see . - */ - -/* -* This file defines default pin maps for the various architectures supported -* by default by EX-IOExpander. -* -* Custom user defined pinmaps should be defined in myEX-IOExpander.h. -* -* Any modifications to this file will be overwritten by future software updates. -*/ - -#define DEFAULT_NANO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 -#define DEFAULT_NANO_ANALOGUE_PINMAP A0,A1,A2,A3,A6,A7 - -#define DEFAULT_UNO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 -#define DEFAULT_UNO_ANALOGUE_PINMAP A0,A1,A2,A3 - -#define DEFAULT_MEGA_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,22,23,24,25,26,27,28,29, \ - 30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49 -#define DEFAULT_MEGA_ANALOGUE_PINMAP A0,A1,A2,A3,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15 From 785b515f9ec3c340604883fa3c30fd9331b955b5 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 11 Dec 2022 19:44:42 +1000 Subject: [PATCH 406/870] Bug fixes, update registers --- EX-IOExpanderPins.h | 14 +------------- IO_EXIOExpander.h | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/EX-IOExpanderPins.h b/EX-IOExpanderPins.h index fa328735b..cbb726f0d 100644 --- a/EX-IOExpanderPins.h +++ b/EX-IOExpanderPins.h @@ -18,17 +18,12 @@ */ /* -* This file defines default pin maps for the various architectures supported +* This file defines default pin numbers for the various architectures supported * by default by EX-IOExpander. * -* Custom user defined pinmaps should be defined in myEX-IOExpander.h. -* * Any modifications to this file will be overwritten by future software updates. */ -// #define DEFAULT_NANO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 -// #define DEFAULT_NANO_ANALOGUE_PINMAP A0,A1,A2,A3,A6,A7 - #define EXIO_UNO_DIGITAL_PINS 12 #define EXIO_UNO_ANALOGUE_PINS 4 @@ -37,10 +32,3 @@ #define EXIO_MEGA_DIGITAL_PINS 46 #define EXIO_MEGA_ANALOGUE_PINS 16 - -// #define EXIO_UNO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 -// #define EXIO_UNO_ANALOGUE_PINMAP A0,A1,A2,A3 - -// #define DEFAULT_MEGA_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,22,23,24,25,26,27,28,29, \ -// 30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49 -// #define DEFAULT_MEGA_ANALOGUE_PINMAP A0,A1,A2,A3,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15 diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index efd09cd63..ca2bf108b 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -84,19 +84,25 @@ class EXIOExpander : public IODevice { // Enable digital ports _digitalPinBytes = (_numDigitalPins + 7) / 8; uint8_t enableDigitalPins[_digitalPinBytes]; + for (uint8_t byte = 0; byte < _digitalPinBytes; byte++) { + enableDigitalPins[byte] = 0; + } for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { - int pinByte = ((pin + 7) / 8); - bitSet(enableDigitalPins[pinByte], (pin - (pinByte * 8))); + int pinByte = pin / 8; + bitSet(enableDigitalPins[pinByte], pin - pinByte * 8); } - I2CManager.write(_i2cAddress, _digitalPinBytes + 1, REG_EXIODPIN, enableDigitalPins); + I2CManager.write(_i2cAddress, _digitalPinBytes + 1, REG_EXIODPIN, *enableDigitalPins); // Enable analogue ports _analoguePinBytes = (_numAnaloguePins + 7) / 8; uint8_t enableAnaloguePins[_analoguePinBytes]; + for (uint8_t byte = 0; byte < _analoguePinBytes; byte++) { + enableAnaloguePins[byte] = 0; + } for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { - int pinByte = ((pin + 7) / 8); - bitSet(enableAnaloguePins[pinByte], (pin - (pinByte * 8))); + int pinByte = pin / 8; + bitSet(enableAnaloguePins[pinByte], pin - pinByte * 8); } - I2CManager.write(_i2cAddress, _analoguePinBytes + 1, REG_EXIOAPIN, enableAnaloguePins); + I2CManager.write(_i2cAddress, _analoguePinBytes + 1, REG_EXIOAPIN, *enableAnaloguePins); } void _display() override { @@ -111,12 +117,12 @@ class EXIOExpander : public IODevice { int _analoguePinBytes; enum { - REG_EXIOINIT = 0x00, // Flag to initialise setup procedure - REG_EXIODPIN = 0x01, // Flag we're sending digital pin assignments - REG_EXIOAPIN = 0x02, // Flag we're sending analogue pin assignments - REG_EXIORDY = 0x03, // Flag we have completed setup procedure, also for EX-IO to ACK setup - REG_EXIODDIR = 0x04, // Flag we're sending digital pin direction configuration - REG_EXIODPUP = 0x05, // Flag we're sending digital pin pullup configuration + REG_EXIOINIT = 0xE0, // Flag to initialise setup procedure + REG_EXIODPIN = 0xE1, // Flag we're sending digital pin assignments + REG_EXIOAPIN = 0xE2, // Flag we're sending analogue pin assignments + REG_EXIORDY = 0xE3, // Flag we have completed setup procedure, also for EX-IO to ACK setup + REG_EXIODDIR = 0xE4, // Flag we're sending digital pin direction configuration + REG_EXIODPUP = 0xE5, // Flag we're sending digital pin pullup configuration }; }; From 3862f7250dfcf4bee139ed5e511c90bcc27bf564 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 12 Dec 2022 19:54:20 +1000 Subject: [PATCH 407/870] Fix bugs, learn I2CManager --- IO_EXIOExpander.h | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index ca2bf108b..1c2c5708a 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -35,7 +35,9 @@ #ifndef IO_EX_IOEXPANDER_H #define IO_EX_IOEXPANDER_H -#include "IO_GPIOBase.h" +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" #include "FSH.h" #include "EX-IOExpanderPins.h" @@ -82,27 +84,29 @@ class EXIOExpander : public IODevice { // Send digital and analogue pin counts I2CManager.write(_i2cAddress, 3, REG_EXIOINIT, _numDigitalPins, _numAnaloguePins); // Enable digital ports - _digitalPinBytes = (_numDigitalPins + 7) / 8; - uint8_t enableDigitalPins[_digitalPinBytes]; - for (uint8_t byte = 0; byte < _digitalPinBytes; byte++) { - enableDigitalPins[byte] = 0; + _digitalPinBytes = (_numDigitalPins + 7) / 8 + 1; + uint8_t _enableDigitalPins[_digitalPinBytes]; + _enableDigitalPins[0] = REG_EXIODPIN; + for (uint8_t byte = 1; byte < _digitalPinBytes; byte++) { + _enableDigitalPins[byte] = 0; } for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { int pinByte = pin / 8; - bitSet(enableDigitalPins[pinByte], pin - pinByte * 8); + bitSet(_enableDigitalPins[pinByte + 1], pin - pinByte * 8); } - I2CManager.write(_i2cAddress, _digitalPinBytes + 1, REG_EXIODPIN, *enableDigitalPins); + I2CManager.write(_i2cAddress, _enableDigitalPins, _digitalPinBytes, &_i2crb); // Enable analogue ports - _analoguePinBytes = (_numAnaloguePins + 7) / 8; - uint8_t enableAnaloguePins[_analoguePinBytes]; - for (uint8_t byte = 0; byte < _analoguePinBytes; byte++) { - enableAnaloguePins[byte] = 0; + _analoguePinBytes = (_numAnaloguePins + 7) / 8 + 1; + uint8_t _enableAnaloguePins[_analoguePinBytes]; + _enableAnaloguePins[0] = REG_EXIOAPIN; + for (uint8_t byte = 1; byte < _analoguePinBytes; byte++) { + _enableAnaloguePins[byte] = 0; } for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { int pinByte = pin / 8; - bitSet(enableAnaloguePins[pinByte], pin - pinByte * 8); + bitSet(_enableAnaloguePins[pinByte + 1], pin - pinByte * 8); } - I2CManager.write(_i2cAddress, _analoguePinBytes + 1, REG_EXIOAPIN, *enableAnaloguePins); + I2CManager.write(_i2cAddress, _enableAnaloguePins, _analoguePinBytes, &_i2crb); } void _display() override { @@ -115,6 +119,7 @@ class EXIOExpander : public IODevice { uint8_t _numAnaloguePins; int _digitalPinBytes; int _analoguePinBytes; + I2CRB _i2crb; enum { REG_EXIOINIT = 0xE0, // Flag to initialise setup procedure From 8ecb408da7de74885d86bd6e2170e2a681dfaf97 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 13 Dec 2022 19:51:41 +1000 Subject: [PATCH 408/870] Update I2C address, fix bug setting analogue pins --- IO_EXIOExpander.h | 92 +++++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 1c2c5708a..23317155e 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -82,31 +82,58 @@ class EXIOExpander : public IODevice { void _setupDevice() { // Send digital and analogue pin counts - I2CManager.write(_i2cAddress, 3, REG_EXIOINIT, _numDigitalPins, _numAnaloguePins); - // Enable digital ports - _digitalPinBytes = (_numDigitalPins + 7) / 8 + 1; - uint8_t _enableDigitalPins[_digitalPinBytes]; - _enableDigitalPins[0] = REG_EXIODPIN; - for (uint8_t byte = 1; byte < _digitalPinBytes; byte++) { - _enableDigitalPins[byte] = 0; - } - for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { - int pinByte = pin / 8; - bitSet(_enableDigitalPins[pinByte + 1], pin - pinByte * 8); - } - I2CManager.write(_i2cAddress, _enableDigitalPins, _digitalPinBytes, &_i2crb); - // Enable analogue ports - _analoguePinBytes = (_numAnaloguePins + 7) / 8 + 1; - uint8_t _enableAnaloguePins[_analoguePinBytes]; - _enableAnaloguePins[0] = REG_EXIOAPIN; - for (uint8_t byte = 1; byte < _analoguePinBytes; byte++) { - _enableAnaloguePins[byte] = 0; - } - for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { - int pinByte = pin / 8; - bitSet(_enableAnaloguePins[pinByte + 1], pin - pinByte * 8); + uint8_t _setupBuffer[3] = {EXIOINIT, _numDigitalPins, _numAnaloguePins}; + // I2CManager.write(_i2cAddress, 3, EXIOINIT, _numDigitalPins, _numAnaloguePins); + I2CManager.write(_i2cAddress, _setupBuffer, 3, &_i2crb); + _activity = EXIODPIN; + } + + void _loop(unsigned long currentMicros) override { + if (_i2crb.status == I2C_STATUS_PENDING) return; + if (_i2crb.status == I2C_STATUS_OK) { + switch(_activity) { + case EXIODPIN: + // Enable digital ports + _digitalPinBytes = (_numDigitalPins + 7) / 8 + 1; + // uint8_t _enableDigitalPins[_digitalPinBytes]; + // _enableDigitalPins[0] = EXIODPIN; + _digitalOutBuffer[0] = EXIODPIN; + for (uint8_t byte = 1; byte < _digitalPinBytes; byte++) { + // _enableDigitalPins[byte] = 0; + _digitalOutBuffer[byte] = 0; + } + for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { + int pinByte = pin / 8; + // bitSet(_enableDigitalPins[pinByte + 1], pin - pinByte * 8); + bitSet(_digitalOutBuffer[pinByte + 1], pin - pinByte * 8); + } + // I2CManager.write(_i2cAddress, _enableDigitalPins, _digitalPinBytes, &_i2crb); + I2CManager.write(_i2cAddress, _digitalOutBuffer, _digitalPinBytes, &_i2crb); + _activity = EXIOAPIN; + break; + case EXIOAPIN: + // Enable analogue ports + _analoguePinBytes = (_numAnaloguePins + 7) / 8 + 1; + // uint8_t _enableAnaloguePins[_analoguePinBytes]; + // _enableAnaloguePins[0] = EXIOAPIN; + _analogueOutBuffer[0] = EXIOAPIN; + for (uint8_t byte = 1; byte < _analoguePinBytes; byte++) { + // _enableAnaloguePins[byte] = 0; + _analogueOutBuffer[byte] = 0; + } + for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { + int pinByte = pin / 8; + // bitSet(_enableAnaloguePins[pinByte + 1], pin - pinByte * 8); + bitSet(_analogueOutBuffer[pinByte + 1], pin - pinByte * 8); + } + // I2CManager.write(_i2cAddress, _enableAnaloguePins, _analoguePinBytes, &_i2crb); + I2CManager.write(_i2cAddress, _analogueOutBuffer, _analoguePinBytes, &_i2crb); + _activity = EXIORDY; + break; + default: + break; + } } - I2CManager.write(_i2cAddress, _enableAnaloguePins, _analoguePinBytes, &_i2crb); } void _display() override { @@ -119,15 +146,20 @@ class EXIOExpander : public IODevice { uint8_t _numAnaloguePins; int _digitalPinBytes; int _analoguePinBytes; + uint8_t _digitalOutBuffer[EXIO_NANO_DIGITAL_PINS + 1]; + uint8_t _digitalInBufer[EXIO_NANO_DIGITAL_PINS]; + uint8_t _analogueOutBuffer[EXIO_NANO_ANALOGUE_PINS + 1]; + uint8_t _analogueInBuffer[EXIO_NANO_ANALOGUE_PINS]; + uint8_t _activity; I2CRB _i2crb; enum { - REG_EXIOINIT = 0xE0, // Flag to initialise setup procedure - REG_EXIODPIN = 0xE1, // Flag we're sending digital pin assignments - REG_EXIOAPIN = 0xE2, // Flag we're sending analogue pin assignments - REG_EXIORDY = 0xE3, // Flag we have completed setup procedure, also for EX-IO to ACK setup - REG_EXIODDIR = 0xE4, // Flag we're sending digital pin direction configuration - REG_EXIODPUP = 0xE5, // Flag we're sending digital pin pullup configuration + EXIOINIT = 0xE0, // Flag to initialise setup procedure + EXIODPIN = 0xE1, // Flag we're sending digital pin assignments + EXIOAPIN = 0xE2, // Flag we're sending analogue pin assignments + EXIORDY = 0xE3, // Flag we have completed setup procedure, also for EX-IO to ACK setup + EXIODDIR = 0xE4, // Flag we're sending digital pin direction configuration + EXIODPUP = 0xE5, // Flag we're sending digital pin pullup configuration }; }; From ad294ea17e0d5428fa786926850a0dae99330f29 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 13 Dec 2022 15:29:20 +0100 Subject: [PATCH 409/870] typo --- MotorDrivers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index 328d5e66f..c0660528e 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -182,7 +182,7 @@ #define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \ - new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN) + new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \ + new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN) // #endif From 75f1a8f43a9187f0d09a7ad6daa3a265074c5428 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Wed, 14 Dec 2022 07:49:09 +1000 Subject: [PATCH 410/870] More bugs to fix --- IO_EXIOExpander.h | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 23317155e..f3cbfbe76 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -27,8 +27,8 @@ * #include "IO_EX-IOExpander.h" * * void halSetup() { -* // EXIOExpander::create(vpin, num_vpins, i2c_address); -* EXIOExpander::create(800, 18, 0x90); +* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); +* EXIOExpander::create(800, 18, 0x90, 12, 6); } */ @@ -69,64 +69,56 @@ class EXIOExpander : public IODevice { void _begin() { // Initialise EX-IOExander device + uint8_t _check = I2CManager.checkAddress(_i2cAddress); + DIAG(F("I2C status check: %d"), _check); if (I2CManager.exists(_i2cAddress)) { + _activity = EXIOINIT; #ifdef DIAG_IO _display(); #endif - _setupDevice(); } else { - DIAG(F("EX-IOExpander device not found, I2C:%x"), _i2cAddress); + DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; } } - void _setupDevice() { - // Send digital and analogue pin counts - uint8_t _setupBuffer[3] = {EXIOINIT, _numDigitalPins, _numAnaloguePins}; - // I2CManager.write(_i2cAddress, 3, EXIOINIT, _numDigitalPins, _numAnaloguePins); - I2CManager.write(_i2cAddress, _setupBuffer, 3, &_i2crb); - _activity = EXIODPIN; - } - void _loop(unsigned long currentMicros) override { if (_i2crb.status == I2C_STATUS_PENDING) return; if (_i2crb.status == I2C_STATUS_OK) { switch(_activity) { + case EXIOINIT: + // Send digital and analogue pin counts + _setupBuffer[0] = EXIOINIT; + _setupBuffer[1] = _numDigitalPins; + _setupBuffer[2] = _numAnaloguePins; + I2CManager.write(_i2cAddress, _setupBuffer, 3, &_i2crb); + _activity = EXIODPIN; + break; case EXIODPIN: // Enable digital ports _digitalPinBytes = (_numDigitalPins + 7) / 8 + 1; - // uint8_t _enableDigitalPins[_digitalPinBytes]; - // _enableDigitalPins[0] = EXIODPIN; _digitalOutBuffer[0] = EXIODPIN; for (uint8_t byte = 1; byte < _digitalPinBytes; byte++) { - // _enableDigitalPins[byte] = 0; _digitalOutBuffer[byte] = 0; } for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { int pinByte = pin / 8; - // bitSet(_enableDigitalPins[pinByte + 1], pin - pinByte * 8); bitSet(_digitalOutBuffer[pinByte + 1], pin - pinByte * 8); } - // I2CManager.write(_i2cAddress, _enableDigitalPins, _digitalPinBytes, &_i2crb); I2CManager.write(_i2cAddress, _digitalOutBuffer, _digitalPinBytes, &_i2crb); _activity = EXIOAPIN; break; case EXIOAPIN: // Enable analogue ports _analoguePinBytes = (_numAnaloguePins + 7) / 8 + 1; - // uint8_t _enableAnaloguePins[_analoguePinBytes]; - // _enableAnaloguePins[0] = EXIOAPIN; _analogueOutBuffer[0] = EXIOAPIN; for (uint8_t byte = 1; byte < _analoguePinBytes; byte++) { - // _enableAnaloguePins[byte] = 0; _analogueOutBuffer[byte] = 0; } for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { int pinByte = pin / 8; - // bitSet(_enableAnaloguePins[pinByte + 1], pin - pinByte * 8); bitSet(_analogueOutBuffer[pinByte + 1], pin - pinByte * 8); } - // I2CManager.write(_i2cAddress, _enableAnaloguePins, _analoguePinBytes, &_i2crb); I2CManager.write(_i2cAddress, _analogueOutBuffer, _analoguePinBytes, &_i2crb); _activity = EXIORDY; break; @@ -146,6 +138,7 @@ class EXIOExpander : public IODevice { uint8_t _numAnaloguePins; int _digitalPinBytes; int _analoguePinBytes; + uint8_t _setupBuffer[3]; uint8_t _digitalOutBuffer[EXIO_NANO_DIGITAL_PINS + 1]; uint8_t _digitalInBufer[EXIO_NANO_DIGITAL_PINS]; uint8_t _analogueOutBuffer[EXIO_NANO_ANALOGUE_PINS + 1]; From 070daa37dc5bcc6fd5ba780c128623f34358fb15 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 15 Dec 2022 07:58:21 +1000 Subject: [PATCH 411/870] Move buffers to constructor --- IO_EXIOExpander.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f3cbfbe76..f936677eb 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -52,18 +52,22 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, uint8_t numDigitalPins, uint8_t numAnaloguePins) { + static void create(VPIN vpin, int nPins, uint8_t i2cAddress, byte numDigitalPins, byte numAnaloguePins) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint8_t numDigitalPins, uint8_t numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, byte numDigitalPins, byte numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; + _digitalOutBuffer = (byte *)calloc(_numDigitalPins + 1, 1); + _digitalInBuffer = (byte *)calloc(_numDigitalPins, 1); + _analogueOutBuffer = (byte *)calloc(_numAnaloguePins + 1, 1); + _analogueInBuffer = (byte *)calloc(_numAnaloguePins, 1); addDevice(this); } @@ -139,10 +143,10 @@ class EXIOExpander : public IODevice { int _digitalPinBytes; int _analoguePinBytes; uint8_t _setupBuffer[3]; - uint8_t _digitalOutBuffer[EXIO_NANO_DIGITAL_PINS + 1]; - uint8_t _digitalInBufer[EXIO_NANO_DIGITAL_PINS]; - uint8_t _analogueOutBuffer[EXIO_NANO_ANALOGUE_PINS + 1]; - uint8_t _analogueInBuffer[EXIO_NANO_ANALOGUE_PINS]; + byte * _digitalOutBuffer = NULL; + byte * _digitalInBuffer = NULL; + byte * _analogueOutBuffer = NULL; + byte * _analogueInBuffer = NULL; uint8_t _activity; I2CRB _i2crb; From a480a5a3d29d1b7ed1bb93f2bdab568a537d038e Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 15 Dec 2022 15:10:53 +1000 Subject: [PATCH 412/870] Add comments, remove unnecessary functions --- IO_EXIOExpander.h | 55 ++++++++++++++--------------------------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f936677eb..fee48847b 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -28,8 +28,16 @@ * * void halSetup() { * // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); -* EXIOExpander::create(800, 18, 0x90, 12, 6); -} +* EXIOExpander::create(800, 18, 0x65, EXIO_NANO_DIGITAL_PINS, EXIO_NANO_ANALOGUE_PINS); +* } +* +* Note when defining the number of digital and analogue pins, there is no way to sanity check +* this from the device driver, and it is up to the user to define the correct values here. +* +* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will +* populate the first part of the specified vpin range, with the analogue pins populating the +* last part of the vpin range. +* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7). */ #ifndef IO_EX_IOEXPANDER_H @@ -74,9 +82,8 @@ class EXIOExpander : public IODevice { void _begin() { // Initialise EX-IOExander device uint8_t _check = I2CManager.checkAddress(_i2cAddress); - DIAG(F("I2C status check: %d"), _check); if (I2CManager.exists(_i2cAddress)) { - _activity = EXIOINIT; + _activity = EXIOINIT; // First thing to do is configure EX-IOExpander device #ifdef DIAG_IO _display(); #endif @@ -87,43 +94,15 @@ class EXIOExpander : public IODevice { } void _loop(unsigned long currentMicros) override { - if (_i2crb.status == I2C_STATUS_PENDING) return; + if (_i2crb.status == I2C_STATUS_PENDING) return; // Do nothing if I2C isn't ready yet if (_i2crb.status == I2C_STATUS_OK) { switch(_activity) { case EXIOINIT: - // Send digital and analogue pin counts + // Send digital and analogue pin counts to configure EX-IOExpander _setupBuffer[0] = EXIOINIT; _setupBuffer[1] = _numDigitalPins; _setupBuffer[2] = _numAnaloguePins; I2CManager.write(_i2cAddress, _setupBuffer, 3, &_i2crb); - _activity = EXIODPIN; - break; - case EXIODPIN: - // Enable digital ports - _digitalPinBytes = (_numDigitalPins + 7) / 8 + 1; - _digitalOutBuffer[0] = EXIODPIN; - for (uint8_t byte = 1; byte < _digitalPinBytes; byte++) { - _digitalOutBuffer[byte] = 0; - } - for (uint8_t pin = 0; pin < _numDigitalPins; pin++) { - int pinByte = pin / 8; - bitSet(_digitalOutBuffer[pinByte + 1], pin - pinByte * 8); - } - I2CManager.write(_i2cAddress, _digitalOutBuffer, _digitalPinBytes, &_i2crb); - _activity = EXIOAPIN; - break; - case EXIOAPIN: - // Enable analogue ports - _analoguePinBytes = (_numAnaloguePins + 7) / 8 + 1; - _analogueOutBuffer[0] = EXIOAPIN; - for (uint8_t byte = 1; byte < _analoguePinBytes; byte++) { - _analogueOutBuffer[byte] = 0; - } - for (uint8_t pin = 0; pin < _numAnaloguePins; pin++) { - int pinByte = pin / 8; - bitSet(_analogueOutBuffer[pinByte + 1], pin - pinByte * 8); - } - I2CManager.write(_i2cAddress, _analogueOutBuffer, _analoguePinBytes, &_i2crb); _activity = EXIORDY; break; default: @@ -152,11 +131,9 @@ class EXIOExpander : public IODevice { enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure - EXIODPIN = 0xE1, // Flag we're sending digital pin assignments - EXIOAPIN = 0xE2, // Flag we're sending analogue pin assignments - EXIORDY = 0xE3, // Flag we have completed setup procedure, also for EX-IO to ACK setup - EXIODDIR = 0xE4, // Flag we're sending digital pin direction configuration - EXIODPUP = 0xE5, // Flag we're sending digital pin pullup configuration + EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup + EXIODDIR = 0xE2, // Flag we're sending digital pin direction configuration + EXIODPUP = 0xE3, // Flag we're sending digital pin pullup configuration }; }; From 1d61a8f3f99ff71a4dd6fbe5614cf33f4047f910 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 16 Dec 2022 13:14:48 +0000 Subject: [PATCH 413/870] HIGHMEM + WITHROTTLE EXRAIL HIGHMEM feature affects parser and withrottle. Ringstream and wifi fixes Withrottle connecting / reconnecting --- CommandDistributor.cpp | 2 +- DCCEXParser.cpp | 24 +-- EXRAIL2.cpp | 198 ++++++++++++++++++------ EXRAIL2.h | 21 ++- EXRAIL2MacroReset.h | 6 + EXRAILMacros.h | 55 +++++-- FSH.h | 63 ++++---- RingStream.cpp | 22 +-- RingStream.h | 2 +- StringBuffer.h | 2 +- StringFormatter.cpp | 9 +- WiThrottle.cpp | 334 ++++++++++++++++++----------------------- WiThrottle.h | 16 +- WifiInboundHandler.cpp | 4 +- WifiInboundHandler.h | 6 +- WifiInterface.cpp | 3 +- defines.h | 6 + platformio.ini | 12 +- version.h | 5 +- 19 files changed, 463 insertions(+), 327 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 8c79652ee..9f2baa337 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -97,7 +97,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream } void CommandDistributor::forget(byte clientId) { - // keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); + if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId); clients[clientId]=NONE_TYPE; } #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 8e7cd33f4..cbb152eb7 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -41,6 +41,14 @@ #include "DCCTimer.h" #include "EXRAIL2.h" +// This macro can't be created easily as a portable function because the +// flashlist requires a far pointer for high flash access. +#define SENDFLASHLIST(stream,flashList) \ + for (int16_t i=0;;i+=sizeof(flashList[0])) { \ + int16_t value=GETHIGHFLASHW(flashList,i); \ + if (value==0) break; \ + StringFormatter::send(stream,F(" %d"),value); \ + } // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. @@ -569,8 +577,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F(" #ifdef EXRAIL_ACTIVE - sendFlashList(stream,RMFT2::routeIdList); - sendFlashList(stream,RMFT2::automationIdList); + SENDFLASHLIST(stream,RMFT2::routeIdList) + SENDFLASHLIST(stream,RMFT2::automationIdList) #endif } else { // @@ -589,7 +597,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case HASH_KEYWORD_R: // returns rosters StringFormatter::send(stream, F("\n")); } -void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) { - for (int16_t i=0;;i++) { - int16_t value=GETFLASHW(flashList+i); - if (value==0) return; - StringFormatter::send(stream,F(" %d"),value); - } -} - bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[]) { diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 67cfce44a..e72e05707 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -41,6 +41,7 @@ */ #include +#include "defines.h" #include "EXRAIL2.h" #include "DCC.h" #include "DCCWaveform.h" @@ -90,15 +91,25 @@ LookList * RMFT2::onDeactivateLookup=NULL; LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; -#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter) -#ifdef ARDUINO_ARCH_AVR -#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3)) -#else -#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3)) -#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A)) -#endif + +#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 +// getOperand instance version, uses progCounter from instance. +uint16_t RMFT2::getOperand(byte n) { + return getOperand(progCounter,n); +} + +// getOperand static version, must be provided prog counter from loop etc. +uint16_t RMFT2::getOperand(int progCounter,byte n) { + int offset=progCounter+1+(n*3); + if (offset&1) { + byte lsb=GETHIGHFLASH(RouteCode,offset); + byte msb=GETHIGHFLASH(RouteCode,offset+1); + return msb<<8|lsb; + } + return GETHIGHFLASHW(RouteCode,offset); +} LookList::LookList(int16_t size) { m_size=size; @@ -139,12 +150,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { for (progCounter=0;; SKIPOP) { byte opcode=GET_OPCODE; if (opcode==OPCODE_ENDEXRAIL) break; - if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter); + if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter); } return list; } /* static */ void RMFT2::begin() { + + DIAG(F("EXRAIL RoutCode at =%P"),RouteCode); + + bool saved_diag=diag; + diag=true; DCCEXParser::setRMFTFilter(RMFT2::ComandFilter); for (int f=0;fsetHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01); } char RMFT2::getRouteType(int16_t id) { - for (int16_t i=0;;i++) { - int16_t rid= GETFLASHW(routeIdList+i); + for (int16_t i=0;;i+=2) { + int16_t rid= GETHIGHFLASHW(routeIdList,i); if (rid==id) return 'R'; if (rid==0) break; } - for (int16_t i=0;;i++) { - int16_t rid= GETFLASHW(automationIdList+i); + for (int16_t i=0;;i+=2) { + int16_t rid= GETHIGHFLASHW(automationIdList,i); if (rid==id) return 'A'; if (rid==0) break; } @@ -308,7 +328,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) { // do the signals // flags[n] represents the state of the nth signal in the table for (int sigslot=0;;sigslot++) { - VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4); + VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8); if (sigid==0) break; // end of signal list byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id StringFormatter::send(stream,F("\n%S[%d]"), @@ -555,7 +575,7 @@ void RMFT2::loop2() { if (delayTime!=0 && millis()-delayStart < delayTime) return; byte opcode = GET_OPCODE; - int16_t operand = GET_OPERAND(0); + int16_t operand = getOperand(0); // skipIf will get set to indicate a failing IF condition bool skipIf=false; @@ -621,13 +641,13 @@ void RMFT2::loop2() { case OPCODE_ATGTE: // wait for analog sensor>= value timeoutFlag=false; - if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break; + if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break; delayMe(50); return; case OPCODE_ATLT: // wait for analog sensor < value timeoutFlag=false; - if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break; + if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break; delayMe(50); return; @@ -638,7 +658,7 @@ void RMFT2::loop2() { case OPCODE_ATTIMEOUT2: if (readSensor(operand)) break; // success without timeout - if (millis()-timeoutStart > 100*GET_OPERAND(1)) { + if (millis()-timeoutStart > 100*getOperand(1)) { timeoutFlag=true; break; // and drop through } @@ -681,7 +701,7 @@ void RMFT2::loop2() { break; case OPCODE_POM: - if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1)); + if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1)); break; case OPCODE_POWEROFF: @@ -715,11 +735,11 @@ void RMFT2::loop2() { break; case OPCODE_IFGTE: // do next operand if sensor>= value - skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1)); + skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1)); break; case OPCODE_IFLT: // do next operand if sensor< value - skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1)); + skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1)); break; case OPCODE_IFNOT: // do next operand if sensor not set @@ -802,11 +822,11 @@ void RMFT2::loop2() { } case OPCODE_XFON: - DCC::setFn(operand,GET_OPERAND(1),true); + DCC::setFn(operand,getOperand(1),true); break; case OPCODE_XFOFF: - DCC::setFn(operand,GET_OPERAND(1),false); + DCC::setFn(operand,getOperand(1),false); break; case OPCODE_DCCACTIVATE: { @@ -898,7 +918,7 @@ void RMFT2::loop2() { case OPCODE_SENDLOCO: // cab, route { - int newPc=sequenceLookup->find(GET_OPERAND(1)); + int newPc=sequenceLookup->find(getOperand(1)); if (newPc<0) break; RMFT2* newtask=new RMFT2(newPc); // create new task newtask->loco=operand; @@ -916,7 +936,7 @@ void RMFT2::loop2() { case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration) - IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3)); + IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3)); break; case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin) @@ -986,8 +1006,8 @@ void RMFT2::kill(const FSH * reason, int operand) { } int16_t RMFT2::getSignalSlot(int16_t id) { - for (int sigpos=0;;sigpos+=4) { - int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); + for (int sigslot=0;;sigslot++) { + int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8); if (sigid==0) { // end of signal list DIAG(F("EXRAIL Signal %d not defined"), id); return -1; @@ -997,9 +1017,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) { // but for a servo signal it will also have SERVO_SIGNAL_FLAG set. if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking - return sigpos/4; // relative slot in signals table + return sigslot; // relative slot in signals table } } + /* static */ void RMFT2::doSignal(int16_t id,char rag) { if (diag) DIAG(F(" doSignal %d %x"),id,rag); @@ -1016,11 +1037,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) { setFlag(sigslot,rag,SIGNAL_MASK); // Correct signal definition found, get the rag values - int16_t sigpos=sigslot*4; - VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos); - VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1); - VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2); - VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3); + int16_t sigpos=sigslot*8; + VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos); + VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2); + VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4); + VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6); if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin); VPIN sigtype=sigid & ~SIGNAL_ID_MASK; @@ -1096,3 +1117,96 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { void RMFT2::printMessage2(const FSH * msg) { DIAG(F("EXRAIL(%d) %S"),loco,msg); } +static StringBuffer * buffer=NULL; +/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial +and handle the oddities like LCD, BROADCAST and PARSE */ +void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { + //DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id); + Print * stream=NULL; + // Find out where the string is going + switch (mode) { + case thrunge_print: + StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco); + stream=&Serial; + break; + + case thrunge_serial: stream=&Serial; break; + case thrunge_serial1: + #ifdef SERIAL1_COMMANDS + stream=&Serial1; + #endif + break; + case thrunge_serial2: + #ifdef SERIAL2_COMMANDS + stream=&Serial2; + #endif + break; + case thrunge_serial3: + #ifdef SERIAL3_COMMANDS + stream=&Serial3; + #endif + break; + case thrunge_serial4: + #ifdef SERIAL4_COMMANDS + stream=&Serial4; + #endif + break; + case thrunge_serial5: + #ifdef SERIAL5_COMMANDS + stream=&Serial5; + #endif + break; + case thrunge_serial6: + #ifdef SERIAL6_COMMANDS + stream=&Serial6; + #endif + break; + // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; + case thrunge_lcn: + #if defined(LCN_SERIAL) + stream=&LCN_SERIAL; + #endif + break; + case thrunge_parse: + case thrunge_broadcast: + case thrunge_lcd: + if (!buffer) buffer=new StringBuffer(); + buffer->flush(); + stream=buffer; + break; + } + if (!stream) return; + + #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + // if mega stream it out + for (;;strfar++) { + char c=pgm_read_byte_far(strfar); + if (c=='\0') break; + stream->write(c); + } + #else + // UNO/NANO CPUs dont have high memory + // 32 bit cpus dont care anyway + stream->print((FSH *)strfar); + #endif + + // and decide what to do next + switch (mode) { + case thrunge_print: + StringFormatter::send(&Serial,F(" *>\n")); + break; + // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; + case thrunge_parse: + DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL); + break; + case thrunge_broadcast: + // TODO CommandDistributor::broadcastText(buffer->getString()); + break; + case thrunge_lcd: + LCD(id,F("%s"),buffer->getString()); + break; + + default: break; + } +} + \ No newline at end of file diff --git a/EXRAIL2.h b/EXRAIL2.h index 323ae57b3..6e6d0cad3 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -67,6 +67,12 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFCLOSED,OPCODE_IFTHROWN }; +enum thrunger: byte { + thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, + thrunge_serial1, thrunge_serial2, thrunge_serial3, + thrunge_serial4, thrunge_serial5, thrunge_serial6, + thrunge_lcd, thrunge_lcn}; + // Flag bits for status of hardware and TPL @@ -111,12 +117,11 @@ class LookList { static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; static const int16_t SIGNAL_ID_MASK=0x0FFF; - // Throttle Info Access functions built by exrail macros static const byte rosterNameCount; - static const int16_t FLASH routeIdList[]; - static const int16_t FLASH automationIdList[]; - static const int16_t FLASH rosterIdList[]; + static const int16_t HIGHFLASH routeIdList[]; + static const int16_t HIGHFLASH automationIdList[]; + static const int16_t HIGHFLASH rosterIdList[]; static const FSH * getRouteDescription(int16_t id); static char getRouteType(int16_t id); static const FSH * getTurnoutDescription(int16_t id); @@ -137,6 +142,7 @@ class LookList { static LookList* LookListLoader(OPCODE op1, OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL); static void handleEvent(const FSH* reason,LookList* handlers, int16_t id); + static uint16_t getOperand(int progCounter,byte n); static RMFT2 * loopTask; static RMFT2 * pausingTask; void delayMe(long millisecs); @@ -148,10 +154,12 @@ class LookList { void kill(const FSH * reason=NULL,int operand=0); void printMessage(uint16_t id); // Built by RMFTMacros.h void printMessage2(const FSH * msg); + void thrungeString(uint32_t strfar, thrunger mode, byte id=0); + uint16_t getOperand(byte n); static bool diag; - static const FLASH byte RouteCode[]; - static const FLASH int16_t SignalDefinitions[]; + static const HIGHFLASH byte RouteCode[]; + static const HIGHFLASH int16_t SignalDefinitions[]; static byte flags[MAX_FLAGS]; static LookList * sequenceLookup; static LookList * onThrowLookup; @@ -161,7 +169,6 @@ class LookList { static LookList * onRedLookup; static LookList * onAmberLookup; static LookList * onGreenLookup; - // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 0211e2216..63fc6bd97 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -110,6 +110,9 @@ #undef SERIAL1 #undef SERIAL2 #undef SERIAL3 +#undef SERIAL4 +#undef SERIAL5 +#undef SERIAL6 #undef SERVO #undef SERVO2 #undef SERVO_TURNOUT @@ -220,6 +223,9 @@ #define SERIAL1(msg) #define SERIAL2(msg) #define SERIAL3(msg) +#define SERIAL4(msg) +#define SERIAL5(msg) +#define SERIAL6(msg) #define SERVO(id,position,profile) #define SERVO2(id,position,duration) #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index d5424c6fa..69ffed284 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -73,14 +73,14 @@ void exrailHalSetup() { #include "EXRAIL2MacroReset.h" #undef ROUTE #define ROUTE(id, description) id, -const int16_t FLASH RMFT2::routeIdList[]= { +const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" 0}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, -const int16_t FLASH RMFT2::automationIdList[]= { +const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" 0}; @@ -100,30 +100,54 @@ const FSH * RMFT2::getRouteDescription(int16_t id) { // Pass 4... Create Text sending functions #include "EXRAIL2MacroReset.h" const int StringMacroTracker1=__COUNTER__; +#define THRUNGE(msg,mode) \ + case (__COUNTER__ - StringMacroTracker1) : {\ + static const char HIGHFLASH thrunge[]=msg;\ + strfar=(uint32_t)GETFARPTR(thrunge);\ + tmode=mode;\ + break;\ + } #undef BROADCAST -#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break; +#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast) #undef PARSE -#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break; +#define PARSE(msg) THRUNGE(msg,thrunge_parse) #undef PRINT -#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break; +#define PRINT(msg) THRUNGE(msg,thrunge_print) #undef LCN -#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break; +#define LCN(msg) THRUNGE(msg,thrunge_lcn) #undef SERIAL -#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break; +#define SERIAL(msg) THRUNGE(msg,thrunge_serial) #undef SERIAL1 -#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break; +#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1) #undef SERIAL2 -#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break; +#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2) #undef SERIAL3 -#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break; +#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3) +#undef SERIAL4 +#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4) +#undef SERIAL5 +#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5) +#undef SERIAL6 +#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6) #undef LCD -#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break; +#define LCD(id,msg) \ + case (__COUNTER__ - StringMacroTracker1) : {\ + static const char HIGHFLASH thrunge[]=msg;\ + strfar=(uint32_t)GETFARPTR(thrunge);\ + tmode=thrunge_lcd; \ + lcdid=id;\ + break;\ + } void RMFT2::printMessage(uint16_t id) { + thrunger tmode; + uint32_t strfar=0; + byte lcdid=0; switch(id) { #include "myAutomation.h" default: break ; } + if (strfar) thrungeString(strfar,tmode,lcdid); } @@ -158,7 +182,7 @@ const byte RMFT2::rosterNameCount=0 #include "EXRAIL2MacroReset.h" #undef ROSTER #define ROSTER(cabid,name,funcmap...) cabid, -const int16_t FLASH RMFT2::rosterIdList[]={ +const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" 0}; @@ -198,7 +222,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #undef VIRTUAL_SIGNAL #define VIRTUAL_SIGNAL(id) id,0,0,0, -const FLASH int16_t RMFT2::SignalDefinitions[] = { +const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #include "myAutomation.h" 0,0,0,0 }; @@ -299,6 +323,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { #define SERIAL1(msg) PRINT(msg) #define SERIAL2(msg) PRINT(msg) #define SERIAL3(msg) PRINT(msg) +#define SERIAL4(msg) PRINT(msg) +#define SERIAL5(msg) PRINT(msg) +#define SERIAL6(msg) PRINT(msg) #define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0), #define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L), #define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos) @@ -323,7 +350,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = { // Build RouteCode const int StringMacroTracker2=__COUNTER__; -const FLASH byte RMFT2::RouteCode[] = { +const HIGHFLASH byte RMFT2::RouteCode[] = { #include "myAutomation.h" OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 }; diff --git a/FSH.h b/FSH.h index 38cc05272..f4bf47e23 100644 --- a/FSH.h +++ b/FSH.h @@ -34,42 +34,51 @@ * PROGMEM use FLASH instead * pgm_read_byte_near use GETFLASH instead. * pgm_read_word_near use GETFLASHW instead. + * + * Also: + * HIGHFLASH - PROGMEM forced to end of link so needs far pointers. + * GETHIGHFLASH,GETHIGHFLASHW to access them * */ #include +#ifdef ARDUINO_ARCH_AVR +// AVR devices have flash memory mapped differently +// progmem can be accessed by _near functions or _far +typedef __FlashStringHelper FSH; +#define FLASH PROGMEM +#define GETFLASH(addr) pgm_read_byte_near(addr) -#if defined(ARDUINO_ARCH_MEGAAVR) +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) +// AVR_MEGA memory deliberately placed at end of link may need _far functions +#define HIGHFLASH __attribute__((section(".fini2"))) +#define GETFARPTR(data) pgm_get_far_address(data) +#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset) +#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset) +#else +// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent +// as there is no progmem above 32kb anyway. +#define HIGHFLASH PROGMEM +#define GETFARPTR(data) ((uint32_t)(data)) +#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset)) +#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset)) +#endif + +#else +// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access #ifdef F #undef F #endif +#ifdef FLASH + #undef FLASH +#endif #define F(str) (str) typedef char FSH; -#define GETFLASH(addr) (*(const unsigned char *)(addr)) -#define GETFLASHW(addr) (*(const unsigned short *)(addr)) #define FLASH -#define strlen_P strlen -#define strcpy_P strcpy - -#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) - -typedef __FlashStringHelper FSH; -#define GETFLASH(addr) pgm_read_byte(addr) -// pgm_read_word is buggy if addr is odd but here -// we do only read well aligned addrs, the others are -// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp. -#define GETFLASHW(addr) pgm_read_word(addr) -#ifdef FLASH - #undef FLASH +#define HIGHFLASH +#define GETFARPTR(data) ((uint32_t)(data)) +#define GETFLASH(addr) (*(const byte *)(addr)) +#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) +#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset)) +#endif #endif -#define FLASH PROGMEM - -#else // AVR and AVR compat here - -typedef __FlashStringHelper FSH; -#define GETFLASH(addr) pgm_read_byte_near(addr) -#define GETFLASHW(addr) pgm_read_word_near(addr) -#define FLASH PROGMEM - -#endif // flash stuff -#endif // FSH diff --git a/RingStream.cpp b/RingStream.cpp index 7f107288b..9377a0ac5 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -65,6 +65,13 @@ int RingStream::availableForWrite() { } size_t RingStream::printFlash(const FSH * flashBuffer) { + // This function does not work on a 32 bit processor where the runtime + // sometimes misrepresents the pointer size in uintptr_t. + // In any case its not really necessary in a 32 bit processor because + // we have adequate ram. + if (sizeof(void*)>2) return print(flashBuffer); + + // We are about to add a PROGMEM string to the buffer. // To save RAM we can insert a marker and the // progmem address into the buffer instead. @@ -107,8 +114,11 @@ int RingStream::read() { if ((_pos_read==_pos_write) && !_overflow) return -1; // empty byte b=readRawByte(); if (b!=FLASH_INSERT_MARKER) return b; -#ifndef ARDUINO_ARCH_ESP32 // Detected a flash insert + if (sizeof(void*)>2) { + DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read); + return '?'; + } // read address bytes LSB first (size depends on CPU) uintptr_t iFlash=0; for (byte f=0; f( iFlash); // and try again... so will read the first byte of the insert. return read(); -#else - DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read); - return '\0'; -#endif } byte RingStream::readRawByte() { @@ -189,12 +195,6 @@ bool RingStream::commit() { _mark++; if (_mark==_len) _mark=0; _buffer[_mark]=lowByte(_count); - // Enable this for debugging only, it requires A LOT of RAM - //{ char s[_count+2]; - // strncpy(s, (const char*)&(_buffer[_mark+1]), _count); - // s[_count]=0; - // DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s); - //} _ringClient = NO_CLIENT; return true; // commit worked } diff --git a/RingStream.h b/RingStream.h index 4f51a656e..b477b77ae 100644 --- a/RingStream.h +++ b/RingStream.h @@ -27,7 +27,7 @@ class RingStream : public Print { public: RingStream( const uint16_t len); - static const int THIS_IS_A_RINGSTREAM=77; + static const int THIS_IS_A_RINGSTREAM=777; virtual size_t write(uint8_t b); // This availableForWrite function is subverted from its original intention so that a caller diff --git a/StringBuffer.h b/StringBuffer.h index fe227fb8c..ac429644f 100644 --- a/StringBuffer.h +++ b/StringBuffer.h @@ -32,7 +32,7 @@ class StringBuffer : public Print { private: static const int buffer_max=64; // enough for long text msgs to throttles int16_t _pos_write; - char _buffer[buffer_max+1]; + char _buffer[buffer_max+2]; }; #endif \ No newline at end of file diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 87c22e561..cc78714b1 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -91,9 +91,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { { const FSH* flash= (const FSH*)va_arg(args, char*); -#ifndef ARDUINO_ARCH_ESP32 - // On ESP32 the reading flashstring from rinstream code - // crashes, so don't use the flashstream hack on ESP32 #if WIFI_ON | ETHERNET_ON // RingStream has special logic to handle flash strings // but is not implemented unless wifi or ethernet are enabled. @@ -101,11 +98,11 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) ((RingStream *)stream)->printFlash(flash); else -#endif #endif stream->print(flash); break; } + case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break; case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; @@ -168,8 +165,8 @@ void StringFormatter::printEscape(Print * stream, char c) { case '\r': stream->print(F("\\r")); break; case '\0': stream->print(F("\\0")); return; case '\t': stream->print(F("\\t")); break; - case '\\': stream->print(F("\\")); break; - default: stream->print(c); + case '\\': stream->print(F("\\\\")); break; + default: stream->write(c); } } diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 019a515a9..920ebd677 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -63,69 +63,6 @@ WiThrottle * WiThrottle::firstThrottle=NULL; -static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) { - if (n == 0) - return 0; - do { - if (*s1 != *s2++) - return 1; - if (*s1++ == 0) - break; - } while (--n != 0); - return 0; -} - -void WiThrottle::findUniqThrottle(int id, char *u) { - WiThrottle *wtmyid = NULL; - WiThrottle *wtmyuniq = NULL; - - // search 1, look for clientid match - for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ - if (wt->clientid == id) { - if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case - return; - wtmyid = wt; - break; - } - } - // search 2, look for string match - for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){ - if (xstrncmp(u, wt->uniq, 16) == 0) { - wtmyuniq = wt; - break; - } - } - - // analyse result of the two for loops: - if (wtmyid == NULL) { // should not happen - DIAG(F("Did not find my own wiThrottle handle")); - return; - } - // wtmyuniq == wtmyid has already returned in for loop 1 - if (wtmyuniq == NULL) { // register uniq in the found id - strncpy(wtmyid->uniq, u, 16); - wtmyid->uniq[16] = '\0'; - if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq); - return; - } - // if we get here wtmyid and wtmyuniq point on objects but differnet ones - // so we need to do the copy (all other options covered above) - for(int n=0; n < MAX_MY_LOCO; n++) - wtmyid->myLocos[n] = wtmyuniq->myLocos[n]; - wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable; - wtmyid->heartBeat = wtmyuniq->heartBeat; - wtmyid->initSent = wtmyuniq->initSent; - wtmyid->exRailSent = wtmyuniq->exRailSent; - wtmyid->mostRecentCab = wtmyuniq->mostRecentCab; - wtmyid->turnoutListHash = wtmyuniq->turnoutListHash; - wtmyid->lastPowerState = wtmyuniq->lastPowerState; - strncpy(wtmyid->uniq, u, 16); - wtmyid->uniq[16] = '\0'; - if (Diag::WITHROTTLE) - DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq); - forget(wtmyuniq->clientid); // do not use wtmyid after this -} - WiThrottle* WiThrottle::getThrottle( int wifiClient) { for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) if (wt->clientid==wifiClient) return wt; @@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) { void WiThrottle::forget( byte clientId) { for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) if (wt->clientid==clientId) { + DIAG(F("Withrottle client %d dropped"),clientId); delete wt; break; } @@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) { nextThrottle=firstThrottle; firstThrottle= this; clientid=wificlientid; - initSent=false; // prevent sending heartbeats before connection completed heartBeatEnable=false; // until client turns it on - turnoutListHash = -1; // make sure turnout list is sent once - exRailSent=false; mostRecentCab=0; for (int loco=0;loconext()){ - if (tt->isHidden()) continue; - int id=tt->getId(); - const FSH * tdesc=NULL; - #ifdef EXRAIL_ACTIVE - tdesc=RMFT2::getTurnoutDescription(id); - #endif - char tchar=Turnout::isClosed(id)?'2':'4'; - if (tdesc==NULL) // turnout with no description - StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar); - else - StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar); - } - StringFormatter::send(stream,F("\n")); - turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison - } - - else if (!exRailSent) { - // Send EX-RAIL routes list if not already sent (but not at same time as turnouts above) - exRailSent=true; -#ifdef EXRAIL_ACTIVE - StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL")); - for (byte pass=0;pass<2;pass++) { - // first pass automations, second pass routes. - for (int ix=0;;ix++) { - int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix); - if (id==0) break; - const FSH * desc=RMFT2::getRouteDescription(id); - StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"), - pass?'A':'R',id,desc, pass?'4':'2'); - } - } - StringFormatter::send(stream,F("\n")); -#endif - // allow heartbeat to slow down once all metadata sent - StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS); } } @@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { } break; case 'N': // Heartbeat (2), only send if connection completed by 'HU' message - StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value + StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value break; case 'M': // multithrottle multithrottle(stream, cmd); break; case 'H': // send initial connection info after receiving "HU" message - if (cmd[1] == 'U') { - WiThrottle::findUniqThrottle(clientid, (char *)cmd+2); - StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n")); - StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); - StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); - StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON); -#ifdef EXRAIL_ACTIVE - StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); - for (int16_t r=0;rwrite('\n'); // end roster -#endif - - - // set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!) - StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2); - initSent = true; + if (cmd[1] == 'U') { + sendIntro(stream); } break; case 'Q': // @@ -317,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab); } } - if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid); + if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid); delete this; break; } @@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ } //use first empty "slot" on this client's list, will be added to DCC registration list for (int loco=0;loco\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco - int fkeys=29; - myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle - -#ifdef EXRAIL_ACTIVE - const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); - if (!functionNames) { - // no roster, use presets as above - } - else if (GETFLASH(functionNames)=='\0') { - // "" = Roster but no functions given - fkeys=0; - } - else { - // we have function names... - // scan names list emitting names, counting functions and - // flagging non-toggling things like horn. - myLocos[loco].functionToggles =0; - StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid); - fkeys=0; - bool firstchar=true; - for (int fx=0;;fx++) { - char c=GETFLASH(functionNames+fx); - if (c=='\0') { - fkeys++; - break; - } - if (c=='/') { - fkeys++; - StringFormatter::send(stream,F("]\\[")); - firstchar=true; - } - else if (firstchar && c=='*') { - myLocos[loco].functionToggles |= 1UL<write(c); - } - } - StringFormatter::send(stream,F("\n")); - } - -#endif - - for(int fKey=0; fKey=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey); - } - //speed and direction will be published at next broadcast cycle - StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 - return; + if (myLocos[loco].throttle=='\0') { + myLocos[loco].throttle=throttleChar; + myLocos[loco].cab=locoid; + myLocos[loco].functionMap=DCC::getFunctionMap(locoid); + myLocos[loco].broadcastPending=true; // means speed/dir will be sent later + mostRecentCab=locoid; + StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco + sendFunctions(stream,loco); + //speed and direction will be published at next broadcast cycle + StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128 + return; } } StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid); @@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) { // for each WiThrottle, check the heartbeat and broadcast needed for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle) wt->checkHeartbeat(stream); - - } void WiThrottle::checkHeartbeat(RingStream * stream) { @@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) { heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop. } } - //haba no, not necessary the only throttle and it may come back - //delete this; + // if it does come back, the throttle should re-acquire + delete this; return; } @@ -656,5 +493,120 @@ void WiThrottle::getLocoCallback(int16_t locoid) { DIAG(F("LocoCallback commit success")); stashStream->commit(); CommandDistributor::broadcastPower(); +} + +void WiThrottle::sendIntro(Print* stream) { + introSent=true; + StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n")); + StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); + StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n")); + StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON); + // set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!) + StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD); +} +void WiThrottle::sendTurnouts(Print* stream) { + turnoutsSent=true; + StringFormatter::send(stream,F("PTL")); + for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){ + if (tt->isHidden()) continue; + int id=tt->getId(); + const FSH * tdesc=NULL; + #ifdef EXRAIL_ACTIVE + tdesc=RMFT2::getTurnoutDescription(id); + #endif + char tchar=Turnout::isClosed(id)?'2':'4'; + if (tdesc==NULL) // turnout with no description + StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar); + else + StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar); + } + StringFormatter::send(stream,F("\n")); +} +void WiThrottle::sendRoster(Print* stream) { + rosterSent=true; + #ifdef EXRAIL_ACTIVE + StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); + for (int16_t r=0;r]\\["), myLocos[loco].throttle,LorS(locoid),locoid); + fkeys=0; + bool firstchar=true; + for (int fx=0;;fx++) { + char c=GETFLASH(functionNames+fx); + if (c=='\0') { + fkeys++; + break; + } + if (c=='/') { + fkeys++; + StringFormatter::send(stream,F("]\\[")); + firstchar=true; + } + else if (firstchar && c=='*') { + myLocos[loco].functionToggles |= 1UL<write(c); + } + } + StringFormatter::send(stream,F("\n")); + } + +#endif + + for(int fKey=0; fKey=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey); + } +} \ No newline at end of file diff --git a/WiThrottle.h b/WiThrottle.h index 3fa973a14..67569433d 100644 --- a/WiThrottle.h +++ b/WiThrottle.h @@ -45,7 +45,8 @@ class WiThrottle { ~WiThrottle(); static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client - static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport + static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport + static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs static WiThrottle* firstThrottle; static int getInt(byte * cmd); @@ -61,10 +62,12 @@ class WiThrottle { MYLOCO myLocos[MAX_MY_LOCO]; bool heartBeatEnable; unsigned long heartBeat; - bool initSent; // valid connection established - bool exRailSent; // valid connection established + bool introSent=false; + bool turnoutsSent=false; + bool rosterSent=false; + bool routesSent=false; + bool heartrateSent=false; uint16_t mostRecentCab; - int turnoutListHash; // used to check for changes to turnout list bool lastPowerState; // last power state sent to this client int DCCToWiTSpeed(int DCCSpeed); @@ -74,6 +77,11 @@ class WiThrottle { void accessory(RingStream *, byte* cmd); void checkHeartbeat(RingStream * stream); void markForBroadcast2(int cab); + void sendIntro(Print * stream); + void sendTurnouts(Print * stream); + void sendRoster(Print * stream); + void sendRoutes(Print * stream); + void sendFunctions(Print* stream, byte loco); // callback stuff to support prog track acquire static RingStream * stashStream; static WiThrottle * stashInstance; diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 2a8ec28f0..b570527ce 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() { } - if (pendingCipsend) { + if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) { if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize); StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize); pendingCipsend=false; @@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() { if (ch=='S') { // SEND OK probably loopState=SKIPTOEND; + lastCIPSEND=0; // no need to wait next time break; } if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND pendingCipsend=(clientPendingCIPSEND>=0); + if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND loopState=SKIPTOEND; break; } diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index d3410cd4c..08f6789c0 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -68,7 +68,9 @@ class WifiInboundHandler { Stream * wifiStream; static const int INBOUND_RING = 512; - static const int OUTBOUND_RING = 2048; + static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192; + + static const int CIPSENDgap=100; // millis() between retries of cipsend. RingStream * inboundRing; RingStream * outboundRing; @@ -79,5 +81,7 @@ class WifiInboundHandler { int clientPendingCIPSEND=-1; int currentReplySize; bool pendingCipsend; + uint32_t lastCIPSEND=0; // millis() of previous cipsend + }; #endif diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 347d7ef60..bdc8dad4f 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) { while (wifiStream->available()) stream->write(wifiStream->read()); if (stream->available()) { int cx=stream->read(); - // A newline followed by !!! is an exit + // A newline followed by ! is an exit if (cx=='\n' || cx=='\r') startOfLine=true; else if (startOfLine && cx=='!') break; else startOfLine=false; - stream->write(cx); wifiStream->write(cx); } } diff --git a/defines.h b/defines.h index c4d38a3c6..9ad58511f 100644 --- a/defines.h +++ b/defines.h @@ -149,6 +149,12 @@ #define CPU_TYPE_ERROR #endif +// replace board type if provided by compiler +#ifdef BOARD_NAME + #undef ARDUINO_TYPE + #define ARDUINO_TYPE BOARD_NAME +#endif + //////////////////////////////////////////////////////////////////////////////// // // WIFI_ON: All prereqs for running with WIFI are met diff --git a/platformio.ini b/platformio.ini index bc1ef0ee2..5fcda004c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -184,7 +184,7 @@ platform = ststm32 board = nucleo_f411re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 monitor_speed = 115200 monitor_echo = yes @@ -192,7 +192,7 @@ monitor_echo = yes platform = teensy board = teensy31 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -200,7 +200,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy35 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -208,7 +208,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy36 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -216,7 +216,7 @@ lib_ignore = NativeEthernet platform = teensy board = teensy40 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet @@ -224,6 +224,6 @@ lib_ignore = NativeEthernet platform = teensy board = teensy41 framework = arduino -build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2 +build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = \ No newline at end of file diff --git a/version.h b/version.h index 5d36187e4..64281c19e 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.7pre1" +#define VERSION "4.2.8pre1" +// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb) +// Withrottle connect/disconnect improvements +// Report BOARD_TYPE if provided by compiler // 4.2.7 FIX: Static IP addr // FIX: Reuse WiThrottle list entries // 4.2.6 FIX: Remove RAM thief From c8fea3a4a7b4e9e26bd0c81cec6fba5f0d76f14d Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 18 Dec 2022 09:43:11 +1000 Subject: [PATCH 414/870] Add version, analogue reads working --- IO_EXIOExpander.h | 30 ++++++++++++++++++++++++------ IO_EXIOExpander_version.h | 10 ++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 IO_EXIOExpander_version.h diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index fee48847b..94bd589ff 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -48,6 +48,7 @@ #include "DIAG.h" #include "FSH.h" #include "EX-IOExpanderPins.h" +#include "IO_EXIOExpander_version.h" // Include user defined pin maps in myEX-IOExpander if defined #if __has_include ("myEX-IOExpander.h") @@ -60,13 +61,13 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, byte numDigitalPins, byte numAnaloguePins) { + static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, byte numDigitalPins, byte numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; @@ -74,8 +75,8 @@ class EXIOExpander : public IODevice { _numAnaloguePins = numAnaloguePins; _digitalOutBuffer = (byte *)calloc(_numDigitalPins + 1, 1); _digitalInBuffer = (byte *)calloc(_numDigitalPins, 1); - _analogueOutBuffer = (byte *)calloc(_numAnaloguePins + 1, 1); - _analogueInBuffer = (byte *)calloc(_numAnaloguePins, 1); + _analogueValues = (uint16_t *)calloc(_numAnaloguePins, 1); + _currentAPin = _nPins - _numAnaloguePins; addDevice(this); } @@ -84,6 +85,7 @@ class EXIOExpander : public IODevice { uint8_t _check = I2CManager.checkAddress(_i2cAddress); if (I2CManager.exists(_i2cAddress)) { _activity = EXIOINIT; // First thing to do is configure EX-IOExpander device + DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); #ifdef DIAG_IO _display(); #endif @@ -105,10 +107,22 @@ class EXIOExpander : public IODevice { I2CManager.write(_i2cAddress, _setupBuffer, 3, &_i2crb); _activity = EXIORDY; break; + case EXIORDY: + _analogueOutBuffer[0] = EXIORDAN; + _analogueOutBuffer[1] = _currentAPin - _numDigitalPins; + I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); + _analogueValues[_currentAPin] = (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; + if (++_currentAPin >= _numDigitalPins + _numAnaloguePins) _currentAPin = _nPins - _numAnaloguePins; default: break; } } + // delayUntil(currentMicros + 2000000); // Delay 2 seconds while bug fixing/developing + } + + int _readAnalogue(VPIN vpin) override { + int pin = vpin - _firstVpin; + return _analogueValues[pin]; } void _display() override { @@ -124,8 +138,10 @@ class EXIOExpander : public IODevice { uint8_t _setupBuffer[3]; byte * _digitalOutBuffer = NULL; byte * _digitalInBuffer = NULL; - byte * _analogueOutBuffer = NULL; - byte * _analogueInBuffer = NULL; + byte _analogueInBuffer[2]; + byte _analogueOutBuffer[2]; + uint16_t * _analogueValues = NULL; + uint8_t _currentAPin; // Current analogue pin to read uint8_t _activity; I2CRB _i2crb; @@ -134,6 +150,8 @@ class EXIOExpander : public IODevice { EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup EXIODDIR = 0xE2, // Flag we're sending digital pin direction configuration EXIODPUP = 0xE3, // Flag we're sending digital pin pullup configuration + EXIOOP = 0xE4, // Flag to say we're operating normally + EXIORDAN = 0xE5, // Flag to read an analogue input }; }; diff --git a/IO_EXIOExpander_version.h b/IO_EXIOExpander_version.h new file mode 100644 index 000000000..6d2e553df --- /dev/null +++ b/IO_EXIOExpander_version.h @@ -0,0 +1,10 @@ +#ifndef IO_EXIOEXPANDER_VERSION_H +#define IO_EXIOEXPANDER_VERSION_H + +#include "StringFormatter.h" + + +#define EXIO_VERSION "0.0.1alpha" +// 0.0.1 Initial version for alpha testing + +#endif \ No newline at end of file From 943494385f2a259a46f8d19e13abcdd8ba48a3d7 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 18 Dec 2022 18:59:16 +1000 Subject: [PATCH 415/870] Add digital write --- IO_EXIOExpander.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 94bd589ff..90dd06dca 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -125,6 +125,14 @@ class EXIOExpander : public IODevice { return _analogueValues[pin]; } + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + _digitalWriteBuffer[0] = EXIOWRD; + _digitalWriteBuffer[1] = pin; + _digitalWriteBuffer[2] = value; + I2CManager.write(_i2cAddress, _digitalWriteBuffer, 3, &_i2crb); + } + void _display() override { DIAG(F("EX-IOExpander I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); @@ -140,6 +148,7 @@ class EXIOExpander : public IODevice { byte * _digitalInBuffer = NULL; byte _analogueInBuffer[2]; byte _analogueOutBuffer[2]; + byte _digitalWriteBuffer[3]; uint16_t * _analogueValues = NULL; uint8_t _currentAPin; // Current analogue pin to read uint8_t _activity; @@ -152,6 +161,7 @@ class EXIOExpander : public IODevice { EXIODPUP = 0xE3, // Flag we're sending digital pin pullup configuration EXIOOP = 0xE4, // Flag to say we're operating normally EXIORDAN = 0xE5, // Flag to read an analogue input + EXIOWRD = 0xE6, // Flag for digital write }; }; From 3973996344c7973dd66291d6cf5d3cd28990fb8c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 19 Dec 2022 14:24:49 +1000 Subject: [PATCH 416/870] Digital pin config done, digital read in progress --- IO_EXIOExpander.h | 53 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 90dd06dca..3641dfdff 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -73,10 +73,11 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; - _digitalOutBuffer = (byte *)calloc(_numDigitalPins + 1, 1); - _digitalInBuffer = (byte *)calloc(_numDigitalPins, 1); + // _digitalOutBuffer = (byte *)calloc(_numDigitalPins + 1, 1); + // _digitalInBuffer = (byte *)calloc(_numDigitalPins, 1); _analogueValues = (uint16_t *)calloc(_numAnaloguePins, 1); _currentAPin = _nPins - _numAnaloguePins; + int _dPinArrayLen = (_numDigitalPins + 7) / 8; addDevice(this); } @@ -120,17 +121,40 @@ class EXIOExpander : public IODevice { // delayUntil(currentMicros + 2000000); // Delay 2 seconds while bug fixing/developing } + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { + if (configType != CONFIGURE_INPUT) return false; + if (paramCount != 1) return false; + bool pullup = params[0]; + int pin = vpin - _firstVpin; + uint8_t mask = 1 << ((pin-_firstVpin) % 8); + DIAG(F("Configure vpin|pin %d|%d as input, pullup %d"), vpin, pin, pullup); + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + return true; + } + int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; return _analogueValues[pin]; } + int _read(VPIN vpin) override { + int pin = vpin - _firstVpin; + _digitalOutBuffer[0] = EXIORDD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading + int _value = I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + return _value; + } + void _write(VPIN vpin, int value) override { int pin = vpin - _firstVpin; - _digitalWriteBuffer[0] = EXIOWRD; - _digitalWriteBuffer[1] = pin; - _digitalWriteBuffer[2] = value; - I2CManager.write(_i2cAddress, _digitalWriteBuffer, 3, &_i2crb); + _digitalOutBuffer[0] = EXIOWRD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = value; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); } void _display() override { @@ -144,11 +168,12 @@ class EXIOExpander : public IODevice { int _digitalPinBytes; int _analoguePinBytes; uint8_t _setupBuffer[3]; - byte * _digitalOutBuffer = NULL; - byte * _digitalInBuffer = NULL; + // byte * _digitalOutBuffer = NULL; + // byte * _digitalInBuffer = NULL; byte _analogueInBuffer[2]; byte _analogueOutBuffer[2]; - byte _digitalWriteBuffer[3]; + byte _digitalOutBuffer[3]; + byte _digitalInBuffer[1]; uint16_t * _analogueValues = NULL; uint8_t _currentAPin; // Current analogue pin to read uint8_t _activity; @@ -157,11 +182,11 @@ class EXIOExpander : public IODevice { enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup - EXIODDIR = 0xE2, // Flag we're sending digital pin direction configuration - EXIODPUP = 0xE3, // Flag we're sending digital pin pullup configuration - EXIOOP = 0xE4, // Flag to say we're operating normally - EXIORDAN = 0xE5, // Flag to read an analogue input - EXIOWRD = 0xE6, // Flag for digital write + EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration + EXIOOP = 0xE3, // Flag to say we're operating normally + EXIORDAN = 0xE4, // Flag to read an analogue input + EXIOWRD = 0xE5, // Flag for digital write + EXIORDD = 0xE6, // Flag to read digital input }; }; From 25b325034514f2b8939005899169e7773b716d4c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Dec 2022 07:08:42 +1000 Subject: [PATCH 417/870] Digital read working --- IO_EXIOExpander.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 3641dfdff..692e30966 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -145,8 +145,8 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIORDD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading - int _value = I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); - return _value; + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + return _digitalInBuffer[0]; } void _write(VPIN vpin, int value) override { From 2ad08029a4763e595db59ecef041094ec7e1aebc Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Dec 2022 08:05:05 +1000 Subject: [PATCH 418/870] Remove excess DIAG output --- IO_EXIOExpander.h | 1 - 1 file changed, 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 692e30966..591680d26 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -127,7 +127,6 @@ class EXIOExpander : public IODevice { bool pullup = params[0]; int pin = vpin - _firstVpin; uint8_t mask = 1 << ((pin-_firstVpin) % 8); - DIAG(F("Configure vpin|pin %d|%d as input, pullup %d"), vpin, pin, pullup); _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; From 5170147e3e3c5e395177f8a4208d93583ab24a5e Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Dec 2022 19:41:32 +1000 Subject: [PATCH 419/870] Error checking pin config, code tidy --- IO_EXIOExpander.h | 46 +++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 591680d26..067caf84f 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -73,9 +73,6 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; - // _digitalOutBuffer = (byte *)calloc(_numDigitalPins + 1, 1); - // _digitalInBuffer = (byte *)calloc(_numDigitalPins, 1); - _analogueValues = (uint16_t *)calloc(_numAnaloguePins, 1); _currentAPin = _nPins - _numAnaloguePins; int _dPinArrayLen = (_numDigitalPins + 7) / 8; addDevice(this); @@ -87,6 +84,15 @@ class EXIOExpander : public IODevice { if (I2CManager.exists(_i2cAddress)) { _activity = EXIOINIT; // First thing to do is configure EX-IOExpander device DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); + _digitalOutBuffer[0] = EXIOINIT; + _digitalOutBuffer[1] = _numDigitalPins; + _digitalOutBuffer[2] = _numAnaloguePins; + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + if (_digitalInBuffer[0] != EXIORDY) { + DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + return; + } #ifdef DIAG_IO _display(); #endif @@ -96,31 +102,6 @@ class EXIOExpander : public IODevice { } } - void _loop(unsigned long currentMicros) override { - if (_i2crb.status == I2C_STATUS_PENDING) return; // Do nothing if I2C isn't ready yet - if (_i2crb.status == I2C_STATUS_OK) { - switch(_activity) { - case EXIOINIT: - // Send digital and analogue pin counts to configure EX-IOExpander - _setupBuffer[0] = EXIOINIT; - _setupBuffer[1] = _numDigitalPins; - _setupBuffer[2] = _numAnaloguePins; - I2CManager.write(_i2cAddress, _setupBuffer, 3, &_i2crb); - _activity = EXIORDY; - break; - case EXIORDY: - _analogueOutBuffer[0] = EXIORDAN; - _analogueOutBuffer[1] = _currentAPin - _numDigitalPins; - I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); - _analogueValues[_currentAPin] = (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; - if (++_currentAPin >= _numDigitalPins + _numAnaloguePins) _currentAPin = _nPins - _numAnaloguePins; - default: - break; - } - } - // delayUntil(currentMicros + 2000000); // Delay 2 seconds while bug fixing/developing - } - bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; @@ -136,7 +117,10 @@ class EXIOExpander : public IODevice { int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; - return _analogueValues[pin]; + _analogueOutBuffer[0] = EXIORDAN; + _analogueOutBuffer[1] = _currentAPin - _numDigitalPins; + I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); + return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; } int _read(VPIN vpin) override { @@ -166,14 +150,10 @@ class EXIOExpander : public IODevice { uint8_t _numAnaloguePins; int _digitalPinBytes; int _analoguePinBytes; - uint8_t _setupBuffer[3]; - // byte * _digitalOutBuffer = NULL; - // byte * _digitalInBuffer = NULL; byte _analogueInBuffer[2]; byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; byte _digitalInBuffer[1]; - uint16_t * _analogueValues = NULL; uint8_t _currentAPin; // Current analogue pin to read uint8_t _activity; I2CRB _i2crb; From 1c7103c21ea3916ae75e4efbdca73d04d760ac77 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Wed, 21 Dec 2022 08:37:23 +1000 Subject: [PATCH 420/870] Analogue read bugfix --- IO_EXIOExpander.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 067caf84f..fc5bc4bbf 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -73,7 +73,6 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; - _currentAPin = _nPins - _numAnaloguePins; int _dPinArrayLen = (_numDigitalPins + 7) / 8; addDevice(this); } @@ -83,7 +82,6 @@ class EXIOExpander : public IODevice { uint8_t _check = I2CManager.checkAddress(_i2cAddress); if (I2CManager.exists(_i2cAddress)) { _activity = EXIOINIT; // First thing to do is configure EX-IOExpander device - DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; @@ -118,7 +116,7 @@ class EXIOExpander : public IODevice { int _readAnalogue(VPIN vpin) override { int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIORDAN; - _analogueOutBuffer[1] = _currentAPin - _numDigitalPins; + _analogueOutBuffer[1] = pin; I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; } @@ -143,6 +141,10 @@ class EXIOExpander : public IODevice { void _display() override { DIAG(F("EX-IOExpander I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); + DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); + DIAG(F("EX-IOExpander x%x: Digital Vpins %d-%d, Analogue Vpins %d-%d"), + _i2cAddress, _firstVpin, _firstVpin + _numDigitalPins - 1, _firstVpin + _numDigitalPins, + _firstVpin + _nPins - 1); } uint8_t _i2cAddress; @@ -154,7 +156,6 @@ class EXIOExpander : public IODevice { byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; byte _digitalInBuffer[1]; - uint8_t _currentAPin; // Current analogue pin to read uint8_t _activity; I2CRB _i2crb; From c44fb0ac44a5ffa90db7b3a45bce29afdd9fe21b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 22 Dec 2022 07:22:04 +1000 Subject: [PATCH 421/870] Disable device driver version, add myHal example --- IO_EXIOExpander.h | 3 +-- IO_EXIOExpander_version.h | 10 ---------- myEX-IOExpander.example.h | 22 ---------------------- myHal.cpp_example.txt | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 34 deletions(-) delete mode 100644 IO_EXIOExpander_version.h delete mode 100644 myEX-IOExpander.example.h diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index fc5bc4bbf..6eb3492f1 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -48,7 +48,6 @@ #include "DIAG.h" #include "FSH.h" #include "EX-IOExpanderPins.h" -#include "IO_EXIOExpander_version.h" // Include user defined pin maps in myEX-IOExpander if defined #if __has_include ("myEX-IOExpander.h") @@ -141,7 +140,7 @@ class EXIOExpander : public IODevice { void _display() override { DIAG(F("EX-IOExpander I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); - DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); + // DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); DIAG(F("EX-IOExpander x%x: Digital Vpins %d-%d, Analogue Vpins %d-%d"), _i2cAddress, _firstVpin, _firstVpin + _numDigitalPins - 1, _firstVpin + _numDigitalPins, _firstVpin + _nPins - 1); diff --git a/IO_EXIOExpander_version.h b/IO_EXIOExpander_version.h deleted file mode 100644 index 6d2e553df..000000000 --- a/IO_EXIOExpander_version.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef IO_EXIOEXPANDER_VERSION_H -#define IO_EXIOEXPANDER_VERSION_H - -#include "StringFormatter.h" - - -#define EXIO_VERSION "0.0.1alpha" -// 0.0.1 Initial version for alpha testing - -#endif \ No newline at end of file diff --git a/myEX-IOExpander.example.h b/myEX-IOExpander.example.h deleted file mode 100644 index 94e378e6f..000000000 --- a/myEX-IOExpander.example.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * © 2022 Peter Cole. All rights reserved. - * - * This is the example configuration file for a custom EX-IOExpander pin map file. - * - * It is highly recommended to copy this to "myEX-IOExpander.h" and modify to suit your specific - * requirements. - * - * If you are simply using a default definition for a defined microcontroller, then you don't - * need to use this file, and instead can use one of the existing definitions. - * - * Refer to https://dcc-ex.com for the full documentation. - * - * NOTE: Modifications to this file will be overwritten by future software updates. - */ -#ifndef MYEX_IOEXPANDER_H -#define MYEX_IOEXPANDER_H - -#define MY_NANO_DIGITAL_PINMAP 2,3,4,5,6,7,8,9,10,11,12,13 -#define MY_NANO_ANALOGUE_PINMAP A0,A1,A2,A3 - -#endif \ No newline at end of file diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 32aa12e20..3febc82ca 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -20,6 +20,8 @@ #include "IO_HCSR04.h" // Ultrasonic range sensor #include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_DFPlayer.h" // MP3 sound player +//#include "IO_EXTurntable.h" // Turntable-EX turntable controller +// #include "IO_EXIOExpander.h" // EX-IOExpander device driver //========================================================================== @@ -160,6 +162,39 @@ void halSetup() { // DFPlayer::create(10000, 10, Serial1); + //======================================================================= + // The following directive defines an EX-Turntable turntable instance. + //======================================================================= + // EXTurntable::create(VPIN, Number of VPINs, I2C Address) + // + // The parameters are: + // VPIN=600 + // Number of VPINs=1 (Note there is no reason to change this) + // I2C address=0x60 + // + // Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default. + + //EXTurntable::create(600, 1, 0x60); + + + //======================================================================= + // The following directive defines an EX-IOExpander instance. + //======================================================================= + // EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count) + // + // The parameters are: + // VPIN=an available Vpin + // Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation) + // I2C address=an available I2C address (default 0x65) + // + // Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default. + // The first example is for an Arduino Nano with the default pin allocations. + // The second example is for an Arduino Uno using all pins as digital only. + + //EXIOExpander::create(800, 18, 0x65, EXIO_NANO_DIGITAL_PINS, EXIO_NANO_ANALOGUE_PINS); + //EXIOExpander::create(820, 16, 0x66, 16, 0); + + } #endif From 70845b49322b80bc532ee265c77a9cbbc18c9ea6 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 26 Dec 2022 06:44:15 +1000 Subject: [PATCH 422/870] Receive/display EXIO version --- IO_EXIOExpander.h | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 6eb3492f1..69c568dd0 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -84,12 +84,20 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; + // Send config, if EXIORDY returned, we're good, otherwise go offline I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); if (_digitalInBuffer[0] != EXIORDY) { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; return; } + // Attempt to get version, if we don't get it, we don't care, don't go offline + // Using digital buffers in reverse to save RAM + _digitalInBuffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _digitalOutBuffer, 3, _digitalInBuffer, 1, &_i2crb); + _majorVer = _digitalOutBuffer[0]; + _minorVer = _digitalOutBuffer[1]; + _patchVer = _digitalOutBuffer[2]; #ifdef DIAG_IO _display(); #endif @@ -138,12 +146,10 @@ class EXIOExpander : public IODevice { } void _display() override { - DIAG(F("EX-IOExpander I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, - _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); - // DIAG(F("EX-IOExpander x%x using driver version %S"), _i2cAddress, EXIO_VERSION); - DIAG(F("EX-IOExpander x%x: Digital Vpins %d-%d, Analogue Vpins %d-%d"), - _i2cAddress, _firstVpin, _firstVpin + _numDigitalPins - 1, _firstVpin + _numDigitalPins, - _firstVpin + _nPins - 1); + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: Digital Vpins %d-%d, Analogue Vpins %d-%d %S"), + _i2cAddress, _majorVer, _minorVer, _patchVer, _firstVpin, _firstVpin + _numDigitalPins - 1, + _firstVpin + _numDigitalPins, _firstVpin + _nPins - 1, + _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; @@ -155,6 +161,9 @@ class EXIOExpander : public IODevice { byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; byte _digitalInBuffer[1]; + uint8_t _majorVer = 0; + uint8_t _minorVer = 0; + uint8_t _patchVer = 0; uint8_t _activity; I2CRB _i2crb; @@ -162,7 +171,7 @@ class EXIOExpander : public IODevice { EXIOINIT = 0xE0, // Flag to initialise setup procedure EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration - EXIOOP = 0xE3, // Flag to say we're operating normally + EXIOVER = 0xE3, // Flag to get version EXIORDAN = 0xE4, // Flag to read an analogue input EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input From 71ce913712fff2877ca0756e0edc2c8feabc05b6 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 26 Dec 2022 07:36:12 +1000 Subject: [PATCH 423/870] Version bugfix --- IO_EXIOExpander.h | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 69c568dd0..cc3160cb3 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -92,12 +92,12 @@ class EXIOExpander : public IODevice { return; } // Attempt to get version, if we don't get it, we don't care, don't go offline - // Using digital buffers in reverse to save RAM + // Using digital in buffer in reverse to save RAM _digitalInBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _digitalOutBuffer, 3, _digitalInBuffer, 1, &_i2crb); - _majorVer = _digitalOutBuffer[0]; - _minorVer = _digitalOutBuffer[1]; - _patchVer = _digitalOutBuffer[2]; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1, &_i2crb); + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; #ifdef DIAG_IO _display(); #endif @@ -146,9 +146,18 @@ class EXIOExpander : public IODevice { } void _display() override { - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: Digital Vpins %d-%d, Analogue Vpins %d-%d %S"), - _i2cAddress, _majorVer, _minorVer, _patchVer, _firstVpin, _firstVpin + _numDigitalPins - 1, - _firstVpin + _numDigitalPins, _firstVpin + _nPins - 1, + int _firstAnalogue, _lastAnalogue; + if (_numAnaloguePins == 0) { + _firstAnalogue = 0; + _lastAnalogue = 0; + } else { + _firstAnalogue = _firstVpin + _numDigitalPins; + _lastAnalogue = _firstVpin + _nPins - 1; + } + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + _i2cAddress, _majorVer, _minorVer, _patchVer, + _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, + _numAnaloguePins, _firstAnalogue, _lastAnalogue, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } @@ -161,6 +170,7 @@ class EXIOExpander : public IODevice { byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; byte _digitalInBuffer[1]; + uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; From 0be25f6e7f27bccafe1c1f062e21df817e45a1ba Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 26 Dec 2022 10:41:15 +0000 Subject: [PATCH 424/870] Squashed commit of the following: commit e06668f042e4b4646f6eab298374fd8f062c7a7f Author: Asbelos Date: Mon Dec 26 10:09:34 2022 +0000 speedup commit 3e5d3b1caaac79757262ebb05d0d31ac0d2c9c7b Author: Asbelos Date: Sun Dec 25 22:11:56 2022 +0000 Rename commit 81099af42b9a4f977232595aaddcafa72e34158d Author: Asbelos Date: Sun Dec 25 21:35:38 2022 +0000 spelling and polling commit 9240e7c6bab74dfead59c5d12da29cf31518d98f Author: Asbelos Date: Sun Dec 25 20:52:07 2022 +0000 input working commit 6c1c681a26987a4cac6a49dd82f7e9974b58c9ad Author: Asbelos Date: Wed Dec 21 11:18:39 2022 +0000 input working 1 board, no kit map, output untested commit 5ce67fac972dc864cec4c930361a2ab9f12449c8 Author: Asbelos Date: Sun Dec 18 15:32:37 2022 +0000 Include IO_DNU08 automatically commit ac8d453d2c1877c5372b406dd10e6e03a2969e5f Author: Asbelos Date: Sun Dec 18 12:28:13 2022 +0000 BNOU8 HAL driver --- IODevice.h | 6 +- IO_duinoNodes.h | 172 ++++++++++++++++++++++++++++++++++++ Release_Notes/duinoNodes.md | 39 ++++++++ 3 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 IO_duinoNodes.h create mode 100644 Release_Notes/duinoNodes.md diff --git a/IODevice.h b/IODevice.h index ce472671a..f3ffb1771 100644 --- a/IODevice.h +++ b/IODevice.h @@ -161,6 +161,8 @@ class IODevice { // once the GPIO port concerned has been read. void setGPIOInterruptPin(int16_t pinNumber); + // Method to check if pins will overlap before creating new device. + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); protected: @@ -234,9 +236,6 @@ class IODevice { // pin low if an input changes state. int16_t _gpioInterruptPin = -1; - // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); - // Static support function for subclass creation static void addDevice(IODevice *newDevice); @@ -408,5 +407,6 @@ class EXTurntable : public IODevice { #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_duinoNodes.h" #endif // iodevice_h diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h new file mode 100644 index 000000000..ae7d40c57 --- /dev/null +++ b/IO_duinoNodes.h @@ -0,0 +1,172 @@ +/* + * © 2022, Chris Harlow. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +#ifndef IO_duinoNodes_h + #define IO_duinoNodes_h +#include +#include "defines.h" +#include "IODevice.h" + +#define PIN_MASK(bit) (0x80>>(bit%8)) +#define GET_BIT(x) (_pinValues[(x)/8] & PIN_MASK((x)) ) +#define SET_BIT(x) _pinValues[(x)/8] |= PIN_MASK((x)) +#define CLR_BIT(x) _pinValues[(x)/8] &= ~PIN_MASK((x)) +#define DIAG_IO + + + +class IO_duinoNodes : public IODevice { + +public: + IO_duinoNodes(VPIN firstVpin, int nPins, + byte clockPin, byte latchPin, byte dataPin, + const byte* pinmap) : + IODevice(firstVpin, nPins) { + + _latchPin=latchPin; + _clockPin=clockPin; + _dataPin=dataPin; + _pinMap=pinmap; + _nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits + _pinValues=(byte*) calloc(_nShiftBytes,1); + // Connect to HAL so my _write, _read and _loop will be called as required. + IODevice::addDevice(this); + } + +// Called by HAL to start handling this device + void _begin() override { + _deviceState = DEVSTATE_NORMAL; + pinMode(_latchPin,OUTPUT); + pinMode(_clockPin,OUTPUT); + pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT); + _display(); + } + +// loop called by HAL supervisor +void _loop(unsigned long currentMicros) override { + if (_pinMap) _loopInput(currentMicros); + else if (_xmitPending) _loopOutput(); +} + +void _loopInput(unsigned long currentMicros) { + + if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do + _prevMicros=currentMicros; + + //set latch to HIGH to freeze & store parallel data + ArduinoPins::fastWriteDigital(_latchPin, HIGH); + delayMicroseconds(1); + //set latch to LOW to enable the data to be transmitted serially + ArduinoPins::fastWriteDigital(_latchPin, LOW); + + // stream in the bitmap using mapping order provided at constructor + for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) { + byte newByte=0; + for (int xmitBit=0;xmitBit<8; xmitBit++) { + ArduinoPins::fastWriteDigital(_clockPin, LOW); + delayMicroseconds(1); + bool data = ArduinoPins::fastReadDigital(_dataPin); + byte map=_pinMap[xmitBit]; + if (data) newByte |= map; + else newByte &= ~map; + ArduinoPins::fastWriteDigital(_clockPin, HIGH); + delayMicroseconds(1); + } + _pinValues[xmitByte]=newByte; + // DIAG(F("DIN %x=%x"),xmitByte, newByte); + } + } + +void _loopOutput() { + // stream out the bitmap (highest pin first) + _xmitPending=false; + ArduinoPins::fastWriteDigital(_latchPin, LOW); + for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) { + ArduinoPins::fastWriteDigital(_dataPin,GET_BIT(xmitBit)); + ArduinoPins::fastWriteDigital(_clockPin,HIGH); + ArduinoPins::fastWriteDigital(_clockPin,LOW); + } + digitalWrite(_latchPin, HIGH); + } + + int _read(VPIN vpin) override { + int pin=vpin - _firstVpin; + bool b=GET_BIT(pin); + return b?1:0; + } + + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + bool oldval=GET_BIT(pin); + bool newval=value!=0; + if (newval==oldval) return; // no change + if (newval) SET_BIT(pin); + else CLR_BIT(pin); + _xmitPending=true; // shift register will be sent on next _loop() + } + + void _display() override { + DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"), + _pinMap?F("IN"):F("OUT"), + (int)_firstVpin, + (int)_firstVpin+_nPins-1, _nShiftBytes*8); + } + +private: + static const unsigned long POLL_MICROS=100000; // 10 / S + unsigned long _prevMicros; + int _nShiftBytes=0; + VPIN _latchPin,_clockPin,_dataPin; + byte* _pinValues; + bool _xmitPending; // Only relevant in output mode + const byte* _pinMap; // NULL in output mode +}; + +class IO_DNIN8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0,7,6,5,1,2,3,4 + static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } + +}; + +class IO_DNIN8K { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + // input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7 + static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap); + } +}; + +class IO_DNOU8 { +public: + static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin ) + { + if (IODevice::checkNoOverlap(firstVpin,nPins)) + new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL); + } + +}; +#endif \ No newline at end of file diff --git a/Release_Notes/duinoNodes.md b/Release_Notes/duinoNodes.md new file mode 100644 index 000000000..64a232340 --- /dev/null +++ b/Release_Notes/duinoNodes.md @@ -0,0 +1,39 @@ +Using Lew's Duino Gear boards: + +1. DNIN8 Input + This is a shift-register implementation of a digital input collector. + Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software + configuratuion correctly represents the number of boards connected otherwise the results will be meaningless. + + Use in myAnimation.h + + HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 400, 16, 40, 42, 44) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + + + + This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence. + Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second. + + Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes. + + This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable. + + The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards. + + + DNOU8 works the same way, + Use in myAnimation.h + + HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin) + e.g. + HAL(IO_DNIN8, 450, 16, 45, 47, 49) + + OR Use in myHal.cpp + IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) + +This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins. From b1bd28273d3e31834f41798f252c8f12650dbcf6 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 26 Dec 2022 11:06:42 +0000 Subject: [PATCH 425/870] duinoNodes support --- IO_duinoNodes.h | 5 +++-- version.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h index ae7d40c57..eac752ef1 100644 --- a/IO_duinoNodes.h +++ b/IO_duinoNodes.h @@ -1,7 +1,8 @@ /* * © 2022, Chris Harlow. All rights reserved. + * Based on original by: Robin Simonds, Beagle Bay Inc * - * This file is part of DCC++EX API + * 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 @@ -101,7 +102,7 @@ void _loopOutput() { ArduinoPins::fastWriteDigital(_clockPin,HIGH); ArduinoPins::fastWriteDigital(_clockPin,LOW); } - digitalWrite(_latchPin, HIGH); + ArduinoPins::fastWriteDigital(_latchPin, HIGH); } int _read(VPIN vpin) override { diff --git a/version.h b/version.h index 64281c19e..a17babdc7 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.8pre1" +#define VERSION "4.2.9pre1" +// 4.2.9 duinoNodes support // 4.2.8 HIGHMEM (EXRAIL support beyond 64kb) // Withrottle connect/disconnect improvements // Report BOARD_TYPE if provided by compiler From eea13969979ce7f1c8a8613c1fb0118bb283b04c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 27 Dec 2022 10:10:44 +1000 Subject: [PATCH 426/870] Remove EX-IO pin macros --- EX-IOExpanderPins.h | 34 ---------------------------------- IO_EXIOExpander.h | 1 - 2 files changed, 35 deletions(-) delete mode 100644 EX-IOExpanderPins.h diff --git a/EX-IOExpanderPins.h b/EX-IOExpanderPins.h deleted file mode 100644 index cbb726f0d..000000000 --- a/EX-IOExpanderPins.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * © 2022 Peter Cole - * - * This file is part of EX-CommandStation - * - * This is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * It is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CommandStation. If not, see . - */ - -/* -* This file defines default pin numbers for the various architectures supported -* by default by EX-IOExpander. -* -* Any modifications to this file will be overwritten by future software updates. -*/ - -#define EXIO_UNO_DIGITAL_PINS 12 -#define EXIO_UNO_ANALOGUE_PINS 4 - -#define EXIO_NANO_DIGITAL_PINS 12 -#define EXIO_NANO_ANALOGUE_PINS 6 - -#define EXIO_MEGA_DIGITAL_PINS 46 -#define EXIO_MEGA_ANALOGUE_PINS 16 diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index cc3160cb3..58c6a1dfb 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -47,7 +47,6 @@ #include "I2CManager.h" #include "DIAG.h" #include "FSH.h" -#include "EX-IOExpanderPins.h" // Include user defined pin maps in myEX-IOExpander if defined #if __has_include ("myEX-IOExpander.h") From 8f32ae712f1f2342aa06f0f0e820b9a425fff7e6 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 27 Dec 2022 10:13:08 +1000 Subject: [PATCH 427/870] Fix myHal example --- myHal.cpp_example.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 3febc82ca..c56a03d74 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -191,7 +191,7 @@ void halSetup() { // The first example is for an Arduino Nano with the default pin allocations. // The second example is for an Arduino Uno using all pins as digital only. - //EXIOExpander::create(800, 18, 0x65, EXIO_NANO_DIGITAL_PINS, EXIO_NANO_ANALOGUE_PINS); + //EXIOExpander::create(800, 18, 0x65, 12, 8); //EXIOExpander::create(820, 16, 0x66, 16, 0); From ffdf023de67d121005d291c1a144ae08c5324500 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 29 Dec 2022 05:10:37 +1000 Subject: [PATCH 428/870] Clean up --- IO_EXIOExpander.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 58c6a1dfb..ff6db8e14 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -18,21 +18,23 @@ */ /* -* The IO_EX-IOExpander.h device driver integrates with one or more EX-IOExpander devices. -* This device driver will configure the device and all I/O ports on startup, along with +* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices. +* This device driver will configure the device on startup, along with * interacting with the device for all input/output duties. * * To create EX-IOExpander devices, these are defined in myHal.cpp: * -* #include "IO_EX-IOExpander.h" +* #include "IO_EXIOExpander.h" * * void halSetup() { * // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); -* EXIOExpander::create(800, 18, 0x65, EXIO_NANO_DIGITAL_PINS, EXIO_NANO_ANALOGUE_PINS); +* EXIOExpander::create(800, 18, 0x65, 12, 8); * } * * Note when defining the number of digital and analogue pins, there is no way to sanity check * this from the device driver, and it is up to the user to define the correct values here. +* +* All pins available on the EX-IOExpander device must be accounted for. * * Vpins are allocated to digital pins first, and then analogue pins, so digital pins will * populate the first part of the specified vpin range, with the analogue pins populating the @@ -48,11 +50,6 @@ #include "DIAG.h" #include "FSH.h" -// Include user defined pin maps in myEX-IOExpander if defined -#if __has_include ("myEX-IOExpander.h") - #include "myEX-IOExpander.h" -#endif - ///////////////////////////////////////////////////////////////////////////////////////////////////// /* * IODevice subclass for EX-IOExpander. @@ -79,7 +76,6 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device uint8_t _check = I2CManager.checkAddress(_i2cAddress); if (I2CManager.exists(_i2cAddress)) { - _activity = EXIOINIT; // First thing to do is configure EX-IOExpander device _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; @@ -173,7 +169,6 @@ class EXIOExpander : public IODevice { uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; - uint8_t _activity; I2CRB _i2crb; enum { From 322cb3db54e88071a7230b2a6f3eda566eca3298 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 29 Dec 2022 08:44:08 +1000 Subject: [PATCH 429/870] Include driver in IODevice.h --- IODevice.h | 1 + IO_EXIOExpander.h | 4 +--- myHal.cpp_example.txt | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/IODevice.h b/IODevice.h index ce472671a..942fb6421 100644 --- a/IODevice.h +++ b/IODevice.h @@ -408,5 +408,6 @@ class EXTurntable : public IODevice { #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_EXIOExpander.h" #endif // iodevice_h diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index ff6db8e14..151a31922 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -23,8 +23,7 @@ * interacting with the device for all input/output duties. * * To create EX-IOExpander devices, these are defined in myHal.cpp: -* -* #include "IO_EXIOExpander.h" +* (Note the device driver is included by default) * * void halSetup() { * // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); @@ -45,7 +44,6 @@ #ifndef IO_EX_IOEXPANDER_H #define IO_EX_IOEXPANDER_H -#include "IODevice.h" #include "I2CManager.h" #include "DIAG.h" #include "FSH.h" diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index c56a03d74..c95aaed45 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -21,7 +21,6 @@ #include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_DFPlayer.h" // MP3 sound player //#include "IO_EXTurntable.h" // Turntable-EX turntable controller -// #include "IO_EXIOExpander.h" // EX-IOExpander device driver //========================================================================== From 94c8dafeb280304a2f3d32340c4e955585d69d2e Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 29 Dec 2022 10:38:04 +0000 Subject: [PATCH 430/870] renamed macros --- IO_duinoNodes.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h index eac752ef1..73de1a1d1 100644 --- a/IO_duinoNodes.h +++ b/IO_duinoNodes.h @@ -23,11 +23,10 @@ #include "defines.h" #include "IODevice.h" -#define PIN_MASK(bit) (0x80>>(bit%8)) -#define GET_BIT(x) (_pinValues[(x)/8] & PIN_MASK((x)) ) -#define SET_BIT(x) _pinValues[(x)/8] |= PIN_MASK((x)) -#define CLR_BIT(x) _pinValues[(x)/8] &= ~PIN_MASK((x)) -#define DIAG_IO +#define DN_PIN_MASK(bit) (0x80>>(bit%8)) +#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) ) +#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x)) +#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x)) @@ -98,7 +97,7 @@ void _loopOutput() { _xmitPending=false; ArduinoPins::fastWriteDigital(_latchPin, LOW); for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) { - ArduinoPins::fastWriteDigital(_dataPin,GET_BIT(xmitBit)); + ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit)); ArduinoPins::fastWriteDigital(_clockPin,HIGH); ArduinoPins::fastWriteDigital(_clockPin,LOW); } @@ -107,17 +106,17 @@ void _loopOutput() { int _read(VPIN vpin) override { int pin=vpin - _firstVpin; - bool b=GET_BIT(pin); + bool b=DN_GET_BIT(pin); return b?1:0; } void _write(VPIN vpin, int value) override { int pin = vpin - _firstVpin; - bool oldval=GET_BIT(pin); + bool oldval=DN_GET_BIT(pin); bool newval=value!=0; if (newval==oldval) return; // no change - if (newval) SET_BIT(pin); - else CLR_BIT(pin); + if (newval) DN_SET_BIT(pin); + else DN_CLR_BIT(pin); _xmitPending=true; // shift register will be sent on next _loop() } From ec4dfb8c1e6c9cd6db6bcc3b58866c787ffcb9fc Mon Sep 17 00:00:00 2001 From: peteGSX Date: Fri, 30 Dec 2022 09:46:42 +1000 Subject: [PATCH 431/870] New working rotary encoder branch --- EXRAIL2.cpp | 12 ++++ EXRAIL2.h | 6 +- EXRAIL2MacroReset.h | 4 ++ EXRAILMacros.h | 2 + IO_RotaryEncoder.h | 127 ++++++++++++++++++++++++++++++++++++++++++ myHal.cpp_example.txt | 13 +++++ 6 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 IO_RotaryEncoder.h diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index e72e05707..f44f9dcab 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -91,6 +91,7 @@ LookList * RMFT2::onDeactivateLookup=NULL; LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; +LookList * RMFT2::onChangeLookup=NULL; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -173,6 +174,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onRedLookup=LookListLoader(OPCODE_ONRED); onAmberLookup=LookListLoader(OPCODE_ONAMBER); onGreenLookup=LookListLoader(OPCODE_ONGREEN); + onChangeLookup=LookListLoader(OPCODE_ONCHANGE); // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups @@ -745,6 +747,10 @@ void RMFT2::loop2() { case OPCODE_IFNOT: // do next operand if sensor not set skipIf=readSensor(operand); break; + + case OPCODE_IFRE: // do next operand if rotary encoder != position + skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1)); + break; case OPCODE_IFRANDOM: // do block on random percentage skipIf=(uint8_t)micros() >= operand * 255/100; @@ -968,6 +974,7 @@ void RMFT2::loop2() { case OPCODE_ONRED: case OPCODE_ONAMBER: case OPCODE_ONGREEN: + case OPCODE_ONCHANGE: break; @@ -1094,6 +1101,11 @@ void RMFT2::activateEvent(int16_t addr, bool activate) { if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr); else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr); } + +void RMFT2::changeEvent(int16_t vpin, bool change) { + // Hunt for an ONCHANGE for this sensor + if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); +} void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); diff --git a/EXRAIL2.h b/EXRAIL2.h index 6e6d0cad3..2ea2ba198 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -54,6 +54,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_ENDTASK,OPCODE_ENDEXRAIL, OPCODE_SET_TRACK, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, + OPCODE_ONCHANGE, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -64,7 +65,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFTIMEOUT, OPCODE_IF,OPCODE_IFNOT, OPCODE_IFRANDOM,OPCODE_IFRESERVE, - OPCODE_IFCLOSED,OPCODE_IFTHROWN + OPCODE_IFCLOSED,OPCODE_IFTHROWN, + OPCODE_IFRE, }; enum thrunger: byte { @@ -113,6 +115,7 @@ class LookList { static void createNewTask(int route, uint16_t cab); static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); + static void changeEvent(int16_t id, bool change); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; @@ -169,6 +172,7 @@ class LookList { static LookList * onRedLookup; static LookList * onAmberLookup; static LookList * onGreenLookup; + static LookList * onChangeLookup; // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 63fc6bd97..32e28a28a 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -72,6 +72,7 @@ #undef IFRESERVE #undef IFTHROWN #undef IFTIMEOUT +#undef IFRE #undef INVERT_DIRECTION #undef JOIN #undef KILLALL @@ -88,6 +89,7 @@ #undef ONGREEN #undef ONRED #undef ONTHROW +#undef ONCHANGE #undef PARSE #undef PAUSE #undef PIN_TURNOUT @@ -185,6 +187,7 @@ #define IFTHROWN(turnout_id) #define IFRESERVE(block) #define IFTIMEOUT +#define IFRE(sensor_id,value) #define INVERT_DIRECTION #define JOIN #define KILLALL @@ -201,6 +204,7 @@ #define ONGREEN(signal_id) #define ONRED(signal_id) #define ONTHROW(turnout_id) +#define ONCHANGE(sensor_id) #define PAUSE #define PIN_TURNOUT(id,pin,description...) #define PRINT(msg) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 69ffed284..b5e78d988 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -285,6 +285,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define IFRESERVE(block) OPCODE_IFRESERVE,V(block), #define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id), #define IFTIMEOUT OPCODE_IFTIMEOUT,0,0, +#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value), #define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0, #define JOIN OPCODE_JOIN,0,0, #define KILLALL OPCODE_KILLALL,0,0, @@ -301,6 +302,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), #define ONRED(signal_id) OPCODE_ONRED,V(signal_id), #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), +#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define PAUSE OPCODE_PAUSE,0,0, #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h new file mode 100644 index 000000000..b4d538cfc --- /dev/null +++ b/IO_RotaryEncoder.h @@ -0,0 +1,127 @@ +/* + * © 2022, Peter Cole. All rights reserved. + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C. +* +* There is separate code required for the Arduino the rotary encoder is connected to, which is located here: +* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder +* +* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions +* can be tested in EX-RAIL with: +* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position +* IFRE(vpin, position) - test to see if specified rotary encoder position has been received +* +* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. +* +* Refer to the documentation for further information including the valid activities and examples. +*/ + +#ifndef IO_ROTARYENCODER_H +#define IO_ROTARYENCODER_H + +#include "EXRAIL2.h" +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +class RotaryEncoder : public IODevice { +public: + // Constructor + RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){ + _firstVpin = firstVpin; + _nPins = nPins; + _I2CAddress = I2CAddress; + addDevice(this); + } + static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress); + } + +private: + // Initiate the device + void _begin() { + I2CManager.begin(); + if (I2CManager.exists(_I2CAddress)) { + byte _getVersion[1] = {RE_VER}; + I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + _buffer[0] = RE_OP; + I2CManager.write(_I2CAddress, _buffer, 1); +#ifdef DIAG_IO + _display(); +#endif + } else { + _deviceState = DEVSTATE_FAILED; + } + } + + void _loop(unsigned long currentMicros) override { + I2CManager.read(_I2CAddress, _buffer, 1); + _position = _buffer[0]; + // This here needs to have a change check, ie. position is a different value. + #if defined(EXRAIL_ACTIVE) + if (_position != _previousPosition) { + _previousPosition = _position; + RMFT2::changeEvent(_firstVpin,1); + } else { + RMFT2::changeEvent(_firstVpin,0); + } + #endif + delayUntil(currentMicros + 100000); + } + + // Device specific read function + int _readAnalogue(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + return _position; + } + + void _write(VPIN vpin, int value) override { + if (vpin == _firstVpin + 1) { + byte _feedbackBuffer[2] = {RE_OP, value}; + I2CManager.write(_I2CAddress, _feedbackBuffer, 2); + } + } + + void _display() override { + DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer, + (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } + + uint8_t _I2CAddress; + int8_t _position; + int8_t _previousPosition = 0; + uint8_t _versionBuffer[3]; + uint8_t _buffer[1]; + uint8_t _majorVer = 0; + uint8_t _minorVer = 0; + uint8_t _patchVer = 0; + + enum { + RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device + RE_OP = 0xA1, // Flag for normal operation + }; + +}; + +#endif diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index c95aaed45..9752a8bc4 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -194,6 +194,19 @@ void halSetup() { //EXIOExpander::create(820, 16, 0x66, 16, 0); + //======================================================================= + // The following directive defines a rotary encoder instance. + //======================================================================= + // The parameters are: + // firstVpin = First available Vpin to allocate + // numPins= Number of Vpins to allocate, can be either 1 or 2 + // i2cAddress = Available I2C address (default 0x70) + + //RotaryEncoder::create(firstVpin, numPins, i2cAddress); + //RotaryEncoder::create(700, 1, 0x70); + //RotaryEncoder::create(701, 2, 0x71); + + } #endif From 3fccf6a484af7b3b19a14d84aa63dd62f396a430 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 3 Jan 2023 08:57:21 +1000 Subject: [PATCH 432/870] Fix EX-IOExpander myHal.cpp example --- myHal.cpp_example.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 9752a8bc4..5470f76d8 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -190,7 +190,7 @@ void halSetup() { // The first example is for an Arduino Nano with the default pin allocations. // The second example is for an Arduino Uno using all pins as digital only. - //EXIOExpander::create(800, 18, 0x65, 12, 8); + //EXIOExpander::create(800, 18, 0x65, 12, 6); //EXIOExpander::create(820, 16, 0x66, 16, 0); From 658fca260195abbd881094598b12c502ad720cb3 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 9 Jan 2023 16:24:29 +0800 Subject: [PATCH 433/870] Nucleo-F446RE Build target support --- DCCTimerSTM32.cpp | 9 ++++----- platformio.ini | 33 +++++++++++++-------------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index b26aa4d0b..1b96f70ff 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -30,13 +30,12 @@ #include "DCCTimer.h" -#if defined(ARDUINO_NUCLEO_F411RE) -// STM32F411RE doesn't have Serial1 defined by default +#if defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) +// Nucleo-64 boards don't have Serial1 defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console -// via the debugger on the Nucleo-64 STM32F411RE. It is therefore unavailable -// for other DCC-EX uses like WiFi, DFPlayer, etc. -// Let's define Serial6 as an additional serial port (the only other option for the F411RE) +// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. +// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default diff --git a/platformio.ini b/platformio.ini index 5fcda004c..46606b199 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,8 +24,8 @@ default_envs = Teensy3.6 Teensy4.0 Teensy4.1 -src_dir = . -include_dir = . +src_dir = /Users/paul/Projects/CommandStation-EX-devel +include_dir = /Users/paul/Projects/CommandStation-EX-devel [env] build_flags = -Wall -Wextra @@ -50,19 +50,6 @@ monitor_speed = 115200 monitor_echo = yes build_flags = -std=c++17 -; Firebox disabled for now -; [env:samc21-firebox] -; platform = atmelsam -; board = firebox -; framework = arduino -; upload_protocol = atmel-ice -; lib_deps = -; ${env.lib_deps} -; SparkFun External EEPROM Arduino Library -;monitor_speed = 115200 -;monitor_echo = yes -;build_flags = -std=c++17 - [env:mega2560-debug] platform = atmelavr board = megaatmega2560 @@ -109,9 +96,6 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -; Example, but v12 does generate bigger binaries -; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux -; Should make binaries smaller build_flags = -mcall-prologues [env:mega328] @@ -160,7 +144,6 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -; Should make binaries smaller build_flags = -mcall-prologues [env:nano] @@ -188,6 +171,15 @@ build_flags = -std=c++17 -Os -g2 monitor_speed = 115200 monitor_echo = yes +[env:Nucleo-F446RE] +platform = ststm32 +board = nucleo_f446re +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 -Os -g2 +monitor_speed = 115200 +monitor_echo = yes + [env:Teensy3.2] platform = teensy board = teensy31 @@ -226,4 +218,5 @@ board = teensy41 framework = arduino build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} -lib_ignore = \ No newline at end of file +lib_ignore = + From 402e16727c4b100f010f4d7f5fe59bf3433acf13 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 9 Jan 2023 16:47:29 +0800 Subject: [PATCH 434/870] Fix platformio for Nucleo-F446RE --- platformio.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 46606b199..c82d8b489 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,13 +19,14 @@ default_envs = samd21-zero-usb ESP32 Nucleo-F411RE + Nucleo-F446RE Teensy3.2 Teensy3.5 Teensy3.6 Teensy4.0 Teensy4.1 -src_dir = /Users/paul/Projects/CommandStation-EX-devel -include_dir = /Users/paul/Projects/CommandStation-EX-devel +src_dir = . +include_dir = . [env] build_flags = -Wall -Wextra From 6eff836473ac700b9c98ac24de7415a1b8e7d2c1 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 9 Jan 2023 17:18:50 +0800 Subject: [PATCH 435/870] Add -Wunused-variable build flag to Nucleo builds --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index c82d8b489..82167f225 100644 --- a/platformio.ini +++ b/platformio.ini @@ -168,7 +168,7 @@ platform = ststm32 board = nucleo_f411re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 +build_flags = -std=c++17 -Os -g2 -Wunused-variable monitor_speed = 115200 monitor_echo = yes @@ -177,7 +177,7 @@ platform = ststm32 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 +build_flags = -std=c++17 -Os -g2 -Wunused-variable monitor_speed = 115200 monitor_echo = yes From e01893bcf14891504755537b177582cfd2b2f3f5 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 9 Jan 2023 20:03:18 +1000 Subject: [PATCH 436/870] Comment out unused variables --- IO_EXIOExpander.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 151a31922..7e343529d 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -66,13 +66,13 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; - int _dPinArrayLen = (_numDigitalPins + 7) / 8; + // int _dPinArrayLen = (_numDigitalPins + 7) / 8; addDevice(this); } void _begin() { // Initialise EX-IOExander device - uint8_t _check = I2CManager.checkAddress(_i2cAddress); + // uint8_t _check = I2CManager.checkAddress(_i2cAddress); if (I2CManager.exists(_i2cAddress)) { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; @@ -105,7 +105,7 @@ class EXIOExpander : public IODevice { if (paramCount != 1) return false; bool pullup = params[0]; int pin = vpin - _firstVpin; - uint8_t mask = 1 << ((pin-_firstVpin) % 8); + // uint8_t mask = 1 << ((pin-_firstVpin) % 8); _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; From 9abcfb9e4f0591882e02992d358d3c52f9603f94 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 9 Jan 2023 20:08:36 +1000 Subject: [PATCH 437/870] Add begin delay to test --- IO_EXIOExpander.h | 1 + 1 file changed, 1 insertion(+) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 7e343529d..5bff3ecc1 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -73,6 +73,7 @@ class EXIOExpander : public IODevice { void _begin() { // Initialise EX-IOExander device // uint8_t _check = I2CManager.checkAddress(_i2cAddress); + delay(100); if (I2CManager.exists(_i2cAddress)) { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; From 5c120efa1615ceeca8fcff092dc8c726dadfcf42 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 10 Jan 2023 08:16:42 +1000 Subject: [PATCH 438/870] Add being --- IO_EXIOExpander.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 5bff3ecc1..b3260fa8e 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -73,7 +73,7 @@ class EXIOExpander : public IODevice { void _begin() { // Initialise EX-IOExander device // uint8_t _check = I2CManager.checkAddress(_i2cAddress); - delay(100); + I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; From e48a40fafb0ea0bee5dfa33f19f01a1be5a7b0a8 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 10 Jan 2023 13:07:54 +1000 Subject: [PATCH 439/870] Change to blocking I2CManager calls --- IO_EXIOExpander.h | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index b3260fa8e..7661efcb1 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -79,7 +79,8 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + // I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); if (_digitalInBuffer[0] != EXIORDY) { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -88,7 +89,8 @@ class EXIOExpander : public IODevice { // Attempt to get version, if we don't get it, we don't care, don't go offline // Using digital in buffer in reverse to save RAM _digitalInBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1, &_i2crb); + // I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1, &_i2crb); + I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -110,7 +112,8 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + // I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); return true; } @@ -118,7 +121,8 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIORDAN; _analogueOutBuffer[1] = pin; - I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); + // I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); + I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; } @@ -127,7 +131,8 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIORDD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading - I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + // I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); + I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); return _digitalInBuffer[0]; } @@ -136,7 +141,8 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + // I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); } void _display() override { @@ -168,7 +174,7 @@ class EXIOExpander : public IODevice { uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; - I2CRB _i2crb; + // I2CRB _i2crb; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From c26f53e1fa65dd280e5e5ff20b9528f3bd3f1ad3 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 10 Jan 2023 20:05:09 +1000 Subject: [PATCH 440/870] Device driver fixed --- IO_EXIOExpander.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 7661efcb1..b36654f59 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -66,20 +66,17 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; - // int _dPinArrayLen = (_numDigitalPins + 7) / 8; addDevice(this); } void _begin() { // Initialise EX-IOExander device - // uint8_t _check = I2CManager.checkAddress(_i2cAddress); I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; // Send config, if EXIORDY returned, we're good, otherwise go offline - // I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); if (_digitalInBuffer[0] != EXIORDY) { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); @@ -89,7 +86,6 @@ class EXIOExpander : public IODevice { // Attempt to get version, if we don't get it, we don't care, don't go offline // Using digital in buffer in reverse to save RAM _digitalInBuffer[0] = EXIOVER; - // I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1, &_i2crb); I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; @@ -108,11 +104,9 @@ class EXIOExpander : public IODevice { if (paramCount != 1) return false; bool pullup = params[0]; int pin = vpin - _firstVpin; - // uint8_t mask = 1 << ((pin-_firstVpin) % 8); _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - // I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); return true; } @@ -121,7 +115,6 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIORDAN; _analogueOutBuffer[1] = pin; - // I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2, &_i2crb); I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; } @@ -131,7 +124,6 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIORDD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading - // I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3, &_i2crb); I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); return _digitalInBuffer[0]; } @@ -141,7 +133,6 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - // I2CManager.write(_i2cAddress, _digitalOutBuffer, 3, &_i2crb); I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); } @@ -174,7 +165,6 @@ class EXIOExpander : public IODevice { uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; - // I2CRB _i2crb; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From ff7260b9bc7258a35df8b0ea4ed8f03a554f80cb Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 11 Jan 2023 17:36:11 +0000 Subject: [PATCH 441/870] Added code for FastClock Added code for both I2C fastclock and serial clocks --- CommandDistributor.cpp | 11 ++++ DCCEXParser.cpp | 22 ++++++- EXRAIL2.cpp | 12 +++- EXRAIL2.h | 4 ++ EXRAIL2MacroReset.h | 4 ++ EXRAILMacros.h | 10 +++ IO_EXFastclock.h | 134 +++++++++++++++++++++++++++++++++++++++++ myHal.cpp_example.txt | 15 ++++- 8 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 IO_EXFastclock.h diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 9f2baa337..961df387b 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -155,6 +155,17 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { #endif } +void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) { + // The JMRI clock command is of the form : PFT65871<;>4 + // The CS broadcast is of the form "\n"),time, rate); +#ifdef CD_HANDLE_RING + broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate); +#endif +} + void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; broadcastReply(COMMAND_TYPE, F("\n"), sp->loco,slot,sp->speedCode,sp->functions); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index cbb152eb7..486ef32c9 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -97,6 +97,8 @@ Print *DCCEXParser::stashStream = NULL; RingStream *DCCEXParser::stashRingStream = NULL; byte DCCEXParser::stashTarget=0; +int16_t lastclocktime = 0; + // This is a JMRI command parser. // It doesnt know how the string got here, nor how it gets back. // It knows nothing about hardware or tracks... it just parses strings and @@ -570,9 +572,27 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'J' : // throttle info access { - if ((params<1) | (params>2)) break; // + if ((params<1) | (params>3)) break; // int16_t id=(params==2)?p[1]:0; switch(p[0]) { + case HASH_KEYWORD_C: // sets time and speed + if (params==1) { // returns latest time + StringFormatter::send(stream, F("\n"), lastclocktime); + return; + } + if (p[1] != lastclocktime){ + if (Diag::CMD) { + DIAG(F("Clock Command Received")); + DIAG(F("Received Clock Time is: %d at rate: %d"), p[1], p[2]); + } + LCD(6,F("Clk Time:%d Sp %d"), p[1], p[2]); + //LCD(7,F("Clock Speed: %d"), p[2]); + RMFT2::clockEvent(p[1],1); + // Now tell everyone else what the time is. + CommandDistributor::broadcastClockTime(p[1], p[2]); + lastclocktime = p[1]; + } + return; case HASH_KEYWORD_A: // returns automations/routes StringFormatter::send(stream, F(" diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index f44f9dcab..f96954425 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -92,6 +92,7 @@ LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onChangeLookup=NULL; +LookList * RMFT2::onClockLookup=NULL; #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -175,6 +176,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onAmberLookup=LookListLoader(OPCODE_ONAMBER); onGreenLookup=LookListLoader(OPCODE_ONGREEN); onChangeLookup=LookListLoader(OPCODE_ONCHANGE); + onClockLookup=LookListLoader(OPCODE_ONTIME); // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups @@ -975,6 +977,7 @@ void RMFT2::loop2() { case OPCODE_ONAMBER: case OPCODE_ONGREEN: case OPCODE_ONCHANGE: + case OPCODE_ONTIME: break; @@ -1106,7 +1109,14 @@ void RMFT2::changeEvent(int16_t vpin, bool change) { // Hunt for an ONCHANGE for this sensor if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); } - + +void RMFT2::clockEvent(int16_t clocktime, bool change) { + // Hunt for an ONTIME for this time + if (Diag::CMD) + DIAG(F("Looking for clock event at : %d"), clocktime); + if (change) handleEvent(F("CHANGE"),onClockLookup,clocktime); +} + void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); if (pc<0) return; diff --git a/EXRAIL2.h b/EXRAIL2.h index 2ea2ba198..69fd382fa 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -55,6 +55,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_SET_TRACK, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, OPCODE_ONCHANGE, + OPCODE_ONCLOCKTIME, + OPCODE_ONTIME, // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -116,6 +118,7 @@ class LookList { static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); static void changeEvent(int16_t id, bool change); + static void clockEvent(int16_t clocktime, bool change); static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; @@ -173,6 +176,7 @@ class LookList { static LookList * onAmberLookup; static LookList * onGreenLookup; static LookList * onChangeLookup; + static LookList * onClockLookup; // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 32e28a28a..a5e6d9051 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -86,6 +86,8 @@ #undef ONDEACTIVATE #undef ONDEACTIVATEL #undef ONCLOSE +#undef ONTIME +#undef ONCLOCKTIME #undef ONGREEN #undef ONRED #undef ONTHROW @@ -198,6 +200,8 @@ #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) #define ONAMBER(signal_id) +#define ONTIME(value) +#define ONCLOCKTIME(hours,mins) #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) #define ONCLOSE(turnout_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index b5e78d988..7ef3acd89 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -55,6 +55,14 @@ // helper macro for turnout description as HIDDEN #define HIDDEN "\x01" +// helper macro to strip leading zeros off time inputs +// (10#mins)%100) +#define STRIP_ZERO(value) 10##value%100 + +// helper macro to strip leading zeros off time inputs +// (10#mins)%100) +#define STRIP_ZERO(value) 10##value%100 + // Pass 1 Implements aliases #include "EXRAIL2MacroReset.h" #undef ALIAS @@ -297,6 +305,8 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), +#define ONTIME(value) OPCODE_ONTIME,V(value), +#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)), #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h new file mode 100644 index 000000000..c9985f26d --- /dev/null +++ b/IO_EXFastclock.h @@ -0,0 +1,134 @@ +/* + * © 2022, Colin Murdoch. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . +*/ + +/* +* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data. +* +* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic. +* +* +*/ + +#ifndef IO_EXFastclock_h + #define IO_EXFastclock_h +#endif + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" +#include "EXRAIL2.h" +#include "CommandDistributor.h" + +bool FAST_CLOCK_EXISTS = true; + +class EXFastClock : public IODevice { +public: + // Constructor + EXFastClock(uint8_t I2CAddress){ + _I2CAddress = I2CAddress; + addDevice(this); + } + +static void EXFastClock::create(uint8_t _I2CAddress) { + + DIAG(F("Checking for Clock")); + // Start by assuming we will find the clock + // Check if specified I2C address is responding (blocking operation) + // Returns I2C_STATUS_OK (0) if OK, or error code. + uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress); + DIAG(F("Clock check result - %d"), _checkforclock); + if (_checkforclock == 0) { + FAST_CLOCK_EXISTS = true; + DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); + new EXFastClock(_I2CAddress); + } + else { + FAST_CLOCK_EXISTS = false; + DIAG(F("No Fast Clock found")); + LCD(6,F("CLOCK NOT FOUND")); + } + + } + +private: + //uint8_t _I2CAddress; + uint16_t _clocktime; + uint8_t _clockrate; + uint16_t _previousclocktime; + unsigned long _lastchecktime; + +// Initialisation of Fastclock +void _begin() override { + + if (FAST_CLOCK_EXISTS == true) { + I2CManager.begin(); + if (I2CManager.exists(_I2CAddress)) { + _deviceState = DEVSTATE_NORMAL; + #ifdef DIAG_IO + _display(); + #endif + } else { + _deviceState = DEVSTATE_FAILED; + LCD(6,F("CLOCK NOT FOUND")); + DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress); + } + } +} + +// Processing loop to obtain clock time + +void _loop(unsigned long currentMicros) override{ + + if (FAST_CLOCK_EXISTS==true) { + uint8_t readBuffer[3]; + byte a,b; + #if defined(EXRAIL_ACTIVE) + I2CManager.read(_I2CAddress, readBuffer, 3); + a = readBuffer[0]; + b = readBuffer[1]; + _clocktime = (a << 8) + b; + _clockrate = readBuffer[2]; + + if (_clocktime != _previousclocktime) { + _previousclocktime = _clocktime; + if (Diag::CMD) + DIAG(F("Received Clock Time is: %d at rate: %d"), _clocktime, _clockrate); + LCD(6,F(("Clk Time:%d Sp %d")), _clocktime, _clockrate); + RMFT2::clockEvent(_clocktime,1); + // Now tell everyone else what the time is. + CommandDistributor::broadcastClockTime(_clocktime, _clockrate); + + // As the maximum clock increment is 2 seconds delay a bit - say 1 sec. + delayUntil(currentMicros + 1000000); // Wait 1000ms before checking again, + + } + _lastchecktime = currentMicros; + + #endif + + + } + +} + +// Display EX-FastClock device driver info. +void _display() { + DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); +} +}; diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 5470f76d8..64adac1ae 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -21,7 +21,7 @@ #include "IO_VL53L0X.h" // Laser time-of-flight sensor #include "IO_DFPlayer.h" // MP3 sound player //#include "IO_EXTurntable.h" // Turntable-EX turntable controller - +//#include "IO_EXFastClock.h" // FastClock driver //========================================================================== // The function halSetup() is invoked from CS if it exists within the build. @@ -206,6 +206,19 @@ void halSetup() { //RotaryEncoder::create(700, 1, 0x70); //RotaryEncoder::create(701, 2, 0x71); + //======================================================================= + // The following directive defines an EX-FastClock instance. + //======================================================================= + // EXFastCLock::create(I2C Address) + // + // The parameters are: + // + // I2C address=0x55 (decimal 85) + // + // Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default. + + + // EXFastClock::create(0x55); } From 873d470f867f81a25cc9e85c6b5545beecaceeb5 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 11 Jan 2023 19:50:39 +0000 Subject: [PATCH 442/870] Supply missing function Supply missing function --- CommandDistributor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/CommandDistributor.h b/CommandDistributor.h index 633ff77df..c816b9449 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -45,6 +45,7 @@ public : static void broadcastLoco(byte slot); static void broadcastSensor(int16_t id, bool value); static void broadcastTurnout(int16_t id, bool isClosed); + static void broadcastClockTime(int16_t time, int8_t rate); static void broadcastPower(); static void broadcastText(const FSH * msg); template static void broadcastReply(clientType type, Targs... msg); From 22e20f90922272dd6d546873c7ac60515dfcc77f Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 12 Jan 2023 07:27:42 +1000 Subject: [PATCH 443/870] Logic added and working --- IO_EXIOExpander.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index b36654f59..d00e97c4c 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -90,6 +90,8 @@ class EXIOExpander : public IODevice { _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; + DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"), + _i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); #ifdef DIAG_IO _display(); #endif @@ -102,6 +104,10 @@ class EXIOExpander : public IODevice { bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; + if (vpin >= _firstVpin + _numDigitalPins) { + DIAG(F("Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); + return false; + } bool pullup = params[0]; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIODPUP; @@ -112,6 +118,10 @@ class EXIOExpander : public IODevice { } int _readAnalogue(VPIN vpin) override { + if (vpin < _firstVpin + _numDigitalPins) { + DIAG(F("Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); + return false; + } int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIORDAN; _analogueOutBuffer[1] = pin; @@ -120,6 +130,10 @@ class EXIOExpander : public IODevice { } int _read(VPIN vpin) override { + if (vpin >= _firstVpin + _numDigitalPins) { + DIAG(F("Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); + return false; + } int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIORDD; _digitalOutBuffer[1] = pin; @@ -129,6 +143,10 @@ class EXIOExpander : public IODevice { } void _write(VPIN vpin, int value) override { + if (vpin >= _firstVpin + _numDigitalPins) { + DIAG(F("Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); + return; + } int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; From a8646a2f329d31519733d32aec395c9446f59219 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 12 Jan 2023 07:33:50 +1000 Subject: [PATCH 444/870] Fix EX-Turntable diag message --- IO_EXTurntable.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index ea3dcb0c7..2dc9e6b92 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -47,7 +47,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) { addDevice(this); } -// Initialisation of TurntableEX +// Initialisation of EXTurntable void EXTurntable::_begin() { I2CManager.begin(); I2CManager.setClock(1000000); @@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ uint8_t stepsMSB = value >> 8; uint8_t stepsLSB = value & 0xFF; #ifdef DIAG_IO - DIAG(F("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), + DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), _I2CAddress, stepsMSB, stepsLSB, activity); @@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } From 9b36bdcf46e2c2ec5fd01cc9cc9f02080e40651b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 12 Jan 2023 08:10:41 +1000 Subject: [PATCH 445/870] Logic and diag message done --- IO_EXIOExpander.h | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d00e97c4c..1e20fac99 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -105,7 +105,7 @@ class EXIOExpander : public IODevice { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); + DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); return false; } bool pullup = params[0]; @@ -117,11 +117,16 @@ class EXIOExpander : public IODevice { return true; } - int _readAnalogue(VPIN vpin) override { + // We only use this to detect incorrect use of analogue pins + int _configureAnalogIn(VPIN vpin) override { if (vpin < _firstVpin + _numDigitalPins) { - DIAG(F("Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); - return false; + DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); } + return false; + } + + int _readAnalogue(VPIN vpin) override { + if (vpin < _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIORDAN; _analogueOutBuffer[1] = pin; @@ -130,10 +135,7 @@ class EXIOExpander : public IODevice { } int _read(VPIN vpin) override { - if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); - return false; - } + if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIORDD; _digitalOutBuffer[1] = pin; @@ -143,10 +145,7 @@ class EXIOExpander : public IODevice { } void _write(VPIN vpin, int value) override { - if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); - return; - } + if (vpin >= _firstVpin + _numDigitalPins) return; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; From 046e62a8b362bf288f0cea7c90dd1f14331673d8 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 13 Jan 2023 17:24:26 +0800 Subject: [PATCH 446/870] Minor fix to DCCTimerSTM32.cpp for F412ZG. --- DCCTimerSTM32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 1b96f70ff..f81f386f8 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -37,7 +37,7 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE -#elif defined(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #else From 1f433d0c178a33620cc38f87437c664861105de6 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 14 Jan 2023 12:43:05 +0800 Subject: [PATCH 447/870] Serial1 for STM32F446RE corrected. --- DCCTimerSTM32.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index f81f386f8..432100aff 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -30,13 +30,18 @@ #include "DCCTimer.h" -#if defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) +#if defined(ARDUINO_NUCLEO_F411RE) // Nucleo-64 boards don't have Serial1 defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE +#elif defined(ARDUINO_NUCLEO_F446RE) +// Nucleo-64 boards don't have Serial1 defined by default +HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 17 and 32 - F446RE +// Serial2 is defined to use USART2 by default, but is in fact used as the diag console +// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE From 1be382a6edbead09e3538fda86f938ecf6c29c5a Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 14 Jan 2023 12:45:21 +0800 Subject: [PATCH 448/870] Fixed comment re Serial1 for STM32F446RE --- DCCTimerSTM32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 432100aff..0976bcd2c 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -39,7 +39,7 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_NUCLEO_F446RE) // Nucleo-64 boards don't have Serial1 defined by default -HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 17 and 32 - F446RE +HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 17 and 33 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) From 79437bbf379ac0eb0fb80f4a54e47f7280cd6c7d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 17:10:45 +0000 Subject: [PATCH 449/870] Update MotorDriver.cpp Remove unnecessary and undesirable interrupt disable/enable when writing to HAL driver. --- MotorDriver.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c41420a9f..e87755bbf 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -144,16 +144,12 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { - noInterrupts(); IODevice::write(powerPin,invertPower ? LOW : HIGH); - interrupts(); if (isProgTrack) DCCWaveform::progTrack.clearResets(); } else { - noInterrupts(); IODevice::write(powerPin,invertPower ? HIGH : LOW); - interrupts(); } powerMode=mode; } From 3c5b7bbcfe3c32783a6812125b4bc4a29191ed04 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 17:15:30 +0000 Subject: [PATCH 450/870] HAL updates Remove redundant deferment of device _begin() calls (no longer necessary). Improve diagnostic loop measurement. --- IODevice.cpp | 44 +++++++++++++++++--------------------------- IODevice.h | 1 - 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 812d7ed74..9cf1221d9 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -48,12 +48,14 @@ extern __attribute__((weak)) void exrailHalSetup(); // Create any standard device instances that may be required, such as the Arduino pins // and PCA9685. void IODevice::begin() { + // Initialise the IO subsystem defaults + ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access + // Call user's halSetup() function (if defined in the build in myHal.cpp). // The contents will depend on the user's system hardware configuration. // The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs. - - // This is done first so that the following defaults will detect an overlap and not - // create something that conflicts with the users vpin definitions. + // This is done early so that the subsequent defaults will detect an overlap and not + // create something that conflicts with the user's vpin definitions. if (halSetup) halSetup(); @@ -61,8 +63,6 @@ void IODevice::begin() { if (exrailHalSetup) exrailHalSetup(); - // Initialise the IO subsystem defaults - ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access // Predefine two PCA9685 modules 0x40-0x41 // Allocates 32 pins 100-131 PCA9685::create(100, 16, 0x40); @@ -72,12 +72,6 @@ void IODevice::begin() { // Allocates 32 pins 164-195 MCP23017::create(164, 16, 0x20); MCP23017::create(180, 16, 0x21); - - // Call the begin() methods of each configured device in turn - for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) { - dev->_begin(); - } - _initPhase = false; } // Overarching static loop() method for the IODevice subsystem. Works through the @@ -109,18 +103,19 @@ void IODevice::loop() { // Report loop time if diags enabled #if defined(DIAG_LOOPTIMES) + unsigned long diagMicros = micros(); static unsigned long lastMicros = 0; - // Measure time since loop() method started. - unsigned long halElapsed = micros() - currentMicros; - // Measure time between loop() method entries. - unsigned long elapsed = currentMicros - lastMicros; + // Measure time since HAL's loop() method started. + unsigned long halElapsed = diagMicros - currentMicros; + // Measure time between loop() method entries (excluding this diagnostic). + unsigned long elapsed = diagMicros - 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 - // Ignore long loop counts while message is still outputting + // Ignore long loop counts while message is still outputting (~3 milliseconds) if (currentMicros - lastOutputTime > 3000UL) { if (elapsed > maxElapsed) maxElapsed = elapsed; if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed; @@ -128,14 +123,16 @@ void IODevice::loop() { total += elapsed; count++; } - if (currentMicros - lastOutputTime > interval) { + if (diagMicros - lastOutputTime > interval) { if (lastOutputTime > 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; + lastOutputTime = diagMicros; } - lastMicros = currentMicros; + // Read microsecond count after calculations, so they aren't + // included in the overall timings. + lastMicros = micros(); #endif } @@ -272,11 +269,7 @@ void IODevice::addDevice(IODevice *newDevice) { lastDevice->_nextDevice = newDevice; } newDevice->_nextDevice = 0; - - // If the IODevice::begin() method has already been called, initialise device here. If not, - // the device's _begin() method will be called by IODevice::begin(). - if (!_initPhase) - newDevice->_begin(); + newDevice->_begin(); } // Private helper function to locate a device by VPIN. Returns NULL if not found. @@ -332,9 +325,6 @@ IODevice *IODevice::_firstDevice = 0; // Reference to next device to be called on _loop() method. IODevice *IODevice::_nextLoopDevice = 0; -// Flag which is reset when IODevice::begin has been called. -bool IODevice::_initPhase = true; - //================================================================================================================== // Instance members diff --git a/IODevice.h b/IODevice.h index ce472671a..3b583ba3f 100644 --- a/IODevice.h +++ b/IODevice.h @@ -253,7 +253,6 @@ class IODevice { static IODevice *_firstDevice; static IODevice *_nextLoopDevice; - static bool _initPhase; }; From 6e69df2da8bbac6ef9e583a1f8b377d500beda4b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 18:18:57 +0000 Subject: [PATCH 451/870] Add I2C retries to Wire and to non-blocking I2CManager. --- I2CManager.h | 6 ++++++ I2CManager_NonBlocking.h | 27 ++++++++++++++++++--------- I2CManager_Wire.h | 37 +++++++++++++++++++++++-------------- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/I2CManager.h b/I2CManager.h index 7df08033c..f966d7798 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -111,6 +111,11 @@ * */ +// Maximum number of retries on an I2C operation +// A value of zero will disable retries. +// Maximum value is 254 (unsigned byte counter) +#define MAX_I2C_RETRIES 2 + // Add following line to config.h to enable Wire library instead of native I2C drivers //#define I2C_USE_WIRE @@ -265,6 +270,7 @@ class I2CManagerClass { static volatile unsigned long startTime; static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. + static uint8_t retryCounter; // Count of retries void startTransaction(); diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index fbcb98ae3..2793823e8 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -223,16 +223,15 @@ void I2CManagerClass::handleInterrupt() { // Update hardware state machine I2C_handleInterrupt(); - // Enable interrupts to minimise effect on other interrupt code - interrupts(); - // Check if current request has completed. If there's a current request // and state isn't active then state contains the completion status of the request. if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { - // Remove completed request from head of queue - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // Operation has completed. + if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES) { + // Status is OK, or has failed and retry count exceeded. + // Remove completed request from head of queue I2CRB * t = queueHead; - if (t == queueHead) { + if (t == currentRequest) { queueHead = t->nextRequest; if (!queueHead) queueTail = queueHead; t->nBytes = rxCount; @@ -241,12 +240,21 @@ void I2CManagerClass::handleInterrupt() { // I2C state machine is now free for next request currentRequest = NULL; state = I2C_STATE_FREE; - - // Start next request (if any) - I2CManager.startTransaction(); } + retryCounter = 0; + } else { + // Status is failed and retry permitted. + // Retry previous request. + state = I2C_STATE_FREE; } } + + if (state == I2C_STATE_FREE && queueHead != NULL) { + // Allow any pending interrupts before starting the next request. + interrupts(); + // Start next request + I2CManager.startTransaction(); + } } // Fields in I2CManager class specific to Non-blocking implementation. @@ -261,5 +269,6 @@ volatile uint8_t I2CManagerClass::bytesToSend; volatile uint8_t I2CManagerClass::bytesToReceive; volatile unsigned long I2CManagerClass::startTime; unsigned long I2CManagerClass::timeout = 0; +uint8_t I2CManagerClass::retryCounter = 0; #endif \ No newline at end of file diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 87152e759..aa0189f90 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -49,9 +49,14 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { - Wire.beginTransmission(address); - if (size > 0) Wire.write(buffer, size); - rb->status = Wire.endTransmission(); + uint8_t status = I2C_STATUS_OK; + uint8_t retryCount = 0; + do { + Wire.beginTransmission(address); + if (size > 0) Wire.write(buffer, size); + status = Wire.endTransmission(); + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + rb->status = status; return I2C_STATUS_OK; } @@ -75,17 +80,21 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea { uint8_t status = I2C_STATUS_OK; uint8_t nBytes = 0; - if (writeSize > 0) { - Wire.beginTransmission(address); - Wire.write(writeBuffer, writeSize); - status = Wire.endTransmission(false); // Don't free bus yet - } - if (status == I2C_STATUS_OK) { - Wire.requestFrom(address, (size_t)readSize); - while (Wire.available() && nBytes < readSize) - readBuffer[nBytes++] = Wire.read(); - if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; - } + uint8_t retryCount = 0; + do { + if (writeSize > 0) { + Wire.beginTransmission(address); + Wire.write(writeBuffer, writeSize); + status = Wire.endTransmission(false); // Don't free bus yet + } + if (status == I2C_STATUS_OK) { + Wire.requestFrom(address, (size_t)readSize); + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; + } + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + rb->nBytes = nBytes; rb->status = status; return I2C_STATUS_OK; From 538519dd9df13caa66dab93f85d992282f808a7e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 18:58:06 +0000 Subject: [PATCH 452/870] Add option to suppress I2C retries. I2CRB method suppressRetries() added to allow retries to be suppressed. --- I2CManager.cpp | 13 ++++++++++++- I2CManager.h | 3 +++ I2CManager_NonBlocking.h | 8 +++++--- I2CManager_Wire.h | 12 +++++++++--- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index a951a8772..d7be935e3 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -81,8 +81,13 @@ void I2CManagerClass::forceClock(uint32_t speed) { // Check if specified I2C address is responding (blocking operation) // Returns I2C_STATUS_OK (0) if OK, or error code. +// Suppress retries. If it doesn't respond first time it's out of the running. uint8_t I2CManagerClass::checkAddress(uint8_t address) { - return write(address, NULL, 0); + I2CRB rb; + rb.setWriteParams(address, NULL, 0); + rb.suppressRetries(true); + queueRequest(&rb); + return rb.wait(); } @@ -244,3 +249,9 @@ void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8 this->status = I2C_STATUS_OK; } +void I2CRB::suppressRetries(bool suppress) { + if (suppress) + this->operation |= OPERATION_NORETRY; + else + this->operation &= ~OPERATION_NORETRY; +} diff --git a/I2CManager.h b/I2CManager.h index f966d7798..bcb70924f 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -157,6 +157,8 @@ typedef enum : uint8_t OPERATION_REQUEST = 2, OPERATION_SEND = 3, OPERATION_SEND_P = 4, + OPERATION_NORETRY = 0x80, // OR with operation to suppress retries. + OPERATION_MASK = 0x7f, // mask for extracting the operation code } OperationEnum; @@ -178,6 +180,7 @@ class I2CRB { void setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen); void setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); void setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); + void suppressRetries(bool suppress); uint8_t writeLen; uint8_t readLen; diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 2793823e8..7769d17c1 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -105,7 +105,7 @@ void I2CManagerClass::startTransaction() { currentRequest = queueHead; rxCount = txCount = 0; // Copy key fields to static data for speed. - operation = currentRequest->operation; + operation = currentRequest->operation & OPERATION_MASK; // Start the I2C process going. I2C_sendStart(); startTime = micros(); @@ -227,8 +227,10 @@ void I2CManagerClass::handleInterrupt() { // and state isn't active then state contains the completion status of the request. if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { // Operation has completed. - if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES) { - // Status is OK, or has failed and retry count exceeded. + if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES + || currentRequest->operation & OPERATION_NORETRY) + { + // Status is OK, or has failed and retry count exceeded, or retries disabled. // Remove completed request from head of queue I2CRB * t = queueHead; if (t == currentRequest) { diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index aa0189f90..aea91aae6 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -51,11 +51,14 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t status = I2C_STATUS_OK; uint8_t retryCount = 0; + // If request fails, retry up to the defined limit, unless the NORETRY flag is set + // in the request block. do { Wire.beginTransmission(address); if (size > 0) Wire.write(buffer, size); status = Wire.endTransmission(); - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES + || rb->operation & OPERATION_NORETRY)); rb->status = status; return I2C_STATUS_OK; } @@ -81,6 +84,8 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea uint8_t status = I2C_STATUS_OK; uint8_t nBytes = 0; uint8_t retryCount = 0; + // If request fails, retry up to the defined limit, unless the NORETRY flag is set + // in the request block. do { if (writeSize > 0) { Wire.beginTransmission(address); @@ -93,7 +98,8 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea readBuffer[nBytes++] = Wire.read(); if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; } - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES)); + } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES + || rb->operation & OPERATION_NORETRY)); rb->nBytes = nBytes; rb->status = status; @@ -109,7 +115,7 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea * the non-blocking version. ***************************************************************************/ void I2CManagerClass::queueRequest(I2CRB *req) { - switch (req->operation) { + switch (req->operation & OPERATION_MASK) { case OPERATION_READ: read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req); break; From 7c25f2293998b18306007f608d8c04aaf4749c63 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 14 Jan 2023 23:50:33 +0000 Subject: [PATCH 453/870] Fix error reported in IO_DFPlayer.h when compiling for some platforms --- IO_DFPlayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index d4684e7cd..ad09ca591 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -261,7 +261,7 @@ class DFPlayer : public IODevice { // Output some pad characters to add an // artificial delay between commands for (int i=0; iwrite(0); + _serial->write((uint8_t)0); } // Now output the command From 8fac20a4518515a37c55d0d9542422445a2a9568 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Mon, 16 Jan 2023 18:16:25 +0000 Subject: [PATCH 454/870] Add #ifdef selections Add #ifdef selections linked to #define in config.exampe.h --- CommandDistributor.cpp | 2 ++ CommandDistributor.h | 2 ++ DCCEXParser.cpp | 9 +++++++++ EXRAIL2.cpp | 10 +++++++++- EXRAIL2.h | 6 ++++++ EXRAIL2MacroReset.h | 4 ++++ EXRAILMacros.h | 8 ++++---- IO_EXFastclock.h | 16 +++++++++------- config.example.h | 4 ++++ 9 files changed, 49 insertions(+), 12 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 961df387b..3fe1e96aa 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -155,6 +155,7 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { #endif } +#ifdef USEFASTCLOCK void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) { // The JMRI clock command is of the form : PFT65871<;>4 // The CS broadcast is of the form "%d\n"), time*60, rate); #endif } +#endif void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; diff --git a/CommandDistributor.h b/CommandDistributor.h index c816b9449..d51b2b656 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -45,7 +45,9 @@ public : static void broadcastLoco(byte slot); static void broadcastSensor(int16_t id, bool value); static void broadcastTurnout(int16_t id, bool isClosed); +#ifdef USEFASTCLOCK static void broadcastClockTime(int16_t time, int8_t rate); +#ifdef static void broadcastPower(); static void broadcastText(const FSH * msg); template static void broadcastReply(clientType type, Targs... msg); diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 486ef32c9..3898f952b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -97,7 +97,9 @@ Print *DCCEXParser::stashStream = NULL; RingStream *DCCEXParser::stashRingStream = NULL; byte DCCEXParser::stashTarget=0; +#ifdef USEFASTCLOCK int16_t lastclocktime = 0; +#endif // This is a JMRI command parser. // It doesnt know how the string got here, nor how it gets back. @@ -572,9 +574,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'J' : // throttle info access { +#ifdef USEFASTCLOCK if ((params<1) | (params>3)) break; // +#endif +#ifndef USEFASTCLOCK + if ((params<1) | (params>2)) break; // +#endif int16_t id=(params==2)?p[1]:0; switch(p[0]) { +#ifdef USEFASTCLOCK case HASH_KEYWORD_C: // sets time and speed if (params==1) { // returns latest time StringFormatter::send(stream, F("\n"), lastclocktime); @@ -593,6 +601,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) lastclocktime = p[1]; } return; +#endif case HASH_KEYWORD_A: // returns automations/routes StringFormatter::send(stream, F(" diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index f96954425..1b5b51e1c 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -92,7 +92,9 @@ LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onChangeLookup=NULL; +#ifdef USEFASTCLOCK LookList * RMFT2::onClockLookup=NULL; +#endif #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -176,7 +178,9 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onAmberLookup=LookListLoader(OPCODE_ONAMBER); onGreenLookup=LookListLoader(OPCODE_ONGREEN); onChangeLookup=LookListLoader(OPCODE_ONCHANGE); + #ifdef USEFASTCLOCK onClockLookup=LookListLoader(OPCODE_ONTIME); + #endif // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups @@ -977,7 +981,9 @@ void RMFT2::loop2() { case OPCODE_ONAMBER: case OPCODE_ONGREEN: case OPCODE_ONCHANGE: + #ifdef USEFASTCLOCK case OPCODE_ONTIME: + #endif break; @@ -1110,12 +1116,14 @@ void RMFT2::changeEvent(int16_t vpin, bool change) { if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); } +#ifdef USEFASTCLOCK void RMFT2::clockEvent(int16_t clocktime, bool change) { // Hunt for an ONTIME for this time if (Diag::CMD) DIAG(F("Looking for clock event at : %d"), clocktime); - if (change) handleEvent(F("CHANGE"),onClockLookup,clocktime); + if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime); } +#endif void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); diff --git a/EXRAIL2.h b/EXRAIL2.h index 69fd382fa..7f04458ca 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -55,8 +55,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_SET_TRACK, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, OPCODE_ONCHANGE, +#ifdef USEFASTCLOCK OPCODE_ONCLOCKTIME, OPCODE_ONTIME, +#endif // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -118,7 +120,9 @@ class LookList { static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); static void changeEvent(int16_t id, bool change); + #ifdef USEFASTCLOCK static void clockEvent(int16_t clocktime, bool change); + #endif static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; @@ -176,7 +180,9 @@ class LookList { static LookList * onAmberLookup; static LookList * onGreenLookup; static LookList * onChangeLookup; +#ifdef USEFASTCLOCK static LookList * onClockLookup; +#endif // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index a5e6d9051..409cdcd65 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -86,8 +86,10 @@ #undef ONDEACTIVATE #undef ONDEACTIVATEL #undef ONCLOSE +#ifdef USEFASTCLOCK #undef ONTIME #undef ONCLOCKTIME +#endif #undef ONGREEN #undef ONRED #undef ONTHROW @@ -200,8 +202,10 @@ #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) #define ONAMBER(signal_id) +#ifdef USEFASTCLOCK #define ONTIME(value) #define ONCLOCKTIME(hours,mins) +#endif #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) #define ONCLOSE(turnout_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 7ef3acd89..0d807d12a 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -55,13 +55,11 @@ // helper macro for turnout description as HIDDEN #define HIDDEN "\x01" +#ifdef USEFASTCLOCK // helper macro to strip leading zeros off time inputs // (10#mins)%100) #define STRIP_ZERO(value) 10##value%100 - -// helper macro to strip leading zeros off time inputs -// (10#mins)%100) -#define STRIP_ZERO(value) 10##value%100 +#endif // Pass 1 Implements aliases #include "EXRAIL2MacroReset.h" @@ -305,8 +303,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), +#ifdef USEFASTCLOCK #define ONTIME(value) OPCODE_ONTIME,V(value), #define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)), +#endif #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index c9985f26d..22bb94ebc 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -26,8 +26,8 @@ */ #ifndef IO_EXFastclock_h - #define IO_EXFastclock_h -#endif +#define IO_EXFastclock_h + #include "IODevice.h" #include "I2CManager.h" @@ -55,12 +55,12 @@ static void EXFastClock::create(uint8_t _I2CAddress) { DIAG(F("Clock check result - %d"), _checkforclock); if (_checkforclock == 0) { FAST_CLOCK_EXISTS = true; - DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); + //DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); new EXFastClock(_I2CAddress); } else { FAST_CLOCK_EXISTS = false; - DIAG(F("No Fast Clock found")); + //DIAG(F("No Fast Clock found")); LCD(6,F("CLOCK NOT FOUND")); } @@ -85,7 +85,7 @@ void _begin() override { #endif } else { _deviceState = DEVSTATE_FAILED; - LCD(6,F("CLOCK NOT FOUND")); + //LCD(6,F("CLOCK NOT FOUND")); DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress); } } @@ -107,8 +107,8 @@ void _loop(unsigned long currentMicros) override{ if (_clocktime != _previousclocktime) { _previousclocktime = _clocktime; - if (Diag::CMD) - DIAG(F("Received Clock Time is: %d at rate: %d"), _clocktime, _clockrate); + //if (Diag::CMD) + // DIAG(F("Received Clock Time is: %d at rate: %d"), _clocktime, _clockrate); LCD(6,F(("Clk Time:%d Sp %d")), _clocktime, _clockrate); RMFT2::clockEvent(_clocktime,1); // Now tell everyone else what the time is. @@ -132,3 +132,5 @@ void _display() { DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } }; + +#endif diff --git a/config.example.h b/config.example.h index aae18a92f..a2d97c5a1 100644 --- a/config.example.h +++ b/config.example.h @@ -224,4 +224,8 @@ The configuration file for DCC-EX Command Station // //#define SERIAL_BT_COMMANDS + +// FastClock Enabler +// To build the FastClock code into the CS please uncomment the line below +//#define USEFASTCLOCK ///////////////////////////////////////////////////////////////////////////////////// From abf62dfd85799334f08bb351a8ec4b316cce4382 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 16 Jan 2023 23:00:58 +0000 Subject: [PATCH 455/870] IO_VL53L0X driver: improve I2C error checking and reporting. --- IO_VL53L0X.h | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 9d5134586..040014e55 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -115,6 +115,7 @@ class VL53L0X : public IODevice { STATE_CHECKSTATUS = 5, STATE_GETRESULTS = 6, STATE_DECODERESULTS = 7, + STATE_FAILED = 8, }; // Register addresses @@ -190,11 +191,12 @@ class VL53L0X : public IODevice { // Set 2.8V mode write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); + _nextState = STATE_INITIATESCAN; } else { DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); _deviceState = DEVSTATE_FAILED; + _nextState = STATE_FAILED; } - _nextState = STATE_INITIATESCAN; break; case STATE_INITIATESCAN: // Not scanning, so initiate a scan @@ -207,13 +209,11 @@ class VL53L0X : public IODevice { 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; + reportError(status); + _nextState = STATE_FAILED; } else - _nextState = 2; + _nextState = STATE_GETRESULTS; delayUntil(currentMicros + 95000); // wait for 95 ms before checking. - _nextState = STATE_GETRESULTS; break; case STATE_GETRESULTS: // Ranging completed. Request results @@ -240,15 +240,29 @@ class VL53L0X : public IODevice { else if (_distance > _offThreshold) _value = false; } + // Completed. Restart scan on next loop entry. + _nextState = STATE_INITIATESCAN; + } else { + reportError(status); + _nextState = STATE_FAILED; } - // Completed. Restart scan on next loop entry. - _nextState = STATE_INITIATESCAN; + break; + case STATE_FAILED: + // Do nothing. + delayUntil(currentMicros+1000000UL); break; default: break; } } + // Function to report a failed I2C operation. Put the device off-line. + void reportError(uint8_t status) { + DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; + _value = 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; From ccf463b5075b6f05922dbd4c744b1be8b6ba02a5 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 16 Jan 2023 23:03:53 +0000 Subject: [PATCH 456/870] IODevice.cpp: Fix error in overlap checking. The checkNoOverlap() function didn't work correctly in the case where one device has nPins=0. All devices configured after that were rejected, even when no overlap was present. --- IODevice.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index 9cf1221d9..a51c84bfe 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -292,16 +292,17 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) VPIN lastPin=firstPin+nPins-1; for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { - // check for pin range overlaps (verbose but compiler will fix that) - VPIN firstDevPin=dev->_firstVpin; - VPIN lastDevPin=firstDevPin+dev->_nPins-1; - bool noOverlap= firstPin>lastDevPin || lastPin 0 && dev->_nPins > 0) { + // check for pin range overlaps (verbose but compiler will fix that) + VPIN firstDevPin=dev->_firstVpin; + VPIN lastDevPin=firstDevPin+dev->_nPins-1; + bool noOverlap= firstPin>lastDevPin || lastPin_I2CAddress==i2cAddress) { DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress); From b62c4da04da63b25b561abfe94756cd51d6977e7 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Tue, 17 Jan 2023 10:56:12 +0000 Subject: [PATCH 457/870] Update CommandDistributor.h Fixed #endif typo. --- CommandDistributor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommandDistributor.h b/CommandDistributor.h index d51b2b656..3df477c75 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -47,7 +47,7 @@ public : static void broadcastTurnout(int16_t id, bool isClosed); #ifdef USEFASTCLOCK static void broadcastClockTime(int16_t time, int8_t rate); -#ifdef +#endif static void broadcastPower(); static void broadcastText(const FSH * msg); template static void broadcastReply(clientType type, Targs... msg); From fb36bd138066ffafc75c0af01a160db19fbe6162 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 17 Jan 2023 20:15:30 +0800 Subject: [PATCH 458/870] Fix F446 Serial Pin Comment (Rx/Tx) --- DCCTimerSTM32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 0976bcd2c..0f60507ef 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -39,7 +39,7 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_NUCLEO_F446RE) // Nucleo-64 boards don't have Serial1 defined by default -HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 17 and 33 - F446RE +HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) From cd46d3c9e07a4641843a154046cc7482504932fe Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Sat, 21 Jan 2023 10:18:54 +0000 Subject: [PATCH 459/870] Remove #ifdef and merge calcs Remove #idfef statements and merge duplicate routines into CommandDistributor --- CommandDistributor.cpp | 40 +++++++++++++++++++++++++++++++-- CommandDistributor.h | 6 +++-- DCCEXParser.cpp | 29 +++++------------------- EXRAIL2.cpp | 9 +------- EXRAIL2.h | 6 ----- EXRAIL2MacroReset.h | 4 ---- EXRAILMacros.h | 4 ---- IO_EXFastclock.h | 50 ++++++++++++++++++------------------------ 8 files changed, 69 insertions(+), 79 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 3fe1e96aa..652d852b4 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -29,6 +29,11 @@ #include "DCCWaveform.h" #include "DCC.h" #include "TrackManager.h" +#include "StringFormatter.h" + +// variables to hold clock time +int16_t lastclocktime; +int8_t lastclockrate; #if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS) @@ -155,7 +160,6 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) { #endif } -#ifdef USEFASTCLOCK void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) { // The JMRI clock command is of the form : PFT65871<;>4 // The CS broadcast is of the form "%d\n"), time*60, rate); #endif } -#endif + +void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) { + // opt - case 1 save the latest time if changed + // case 2 broadcast the time when requested + // case 3 display latest time + switch (opt) + { + case 1: + if (clocktime != lastclocktime){ + if (Diag::CMD) { + DIAG(F("Clock Command Received")); + DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate); + } + LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate); + // look for an event for this time + RMFT2::clockEvent(clocktime,1); + // Now tell everyone else what the time is. + CommandDistributor::broadcastClockTime(clocktime, clockrate); + lastclocktime = clocktime; + lastclockrate = clockrate; + } + return; + + case 2: + CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate); + return; + } + +} + +int16_t CommandDistributor::retClockTime() { + return lastclocktime; +} void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; diff --git a/CommandDistributor.h b/CommandDistributor.h index 3df477c75..bbbc44c45 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -25,6 +25,7 @@ #include "RingStream.h" #include "StringBuffer.h" #include "defines.h" +#include "EXRAIL2.h" #if WIFI_ON | ETHERNET_ON // Command Distributor must handle a RingStream of clients @@ -45,13 +46,14 @@ public : static void broadcastLoco(byte slot); static void broadcastSensor(int16_t id, bool value); static void broadcastTurnout(int16_t id, bool isClosed); -#ifdef USEFASTCLOCK static void broadcastClockTime(int16_t time, int8_t rate); -#endif + static void setClockTime(int16_t time, int8_t rate, byte opt); + static int16_t retClockTime(); static void broadcastPower(); static void broadcastText(const FSH * msg); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); + }; #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 3898f952b..5c0cb84bc 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -97,10 +97,6 @@ Print *DCCEXParser::stashStream = NULL; RingStream *DCCEXParser::stashRingStream = NULL; byte DCCEXParser::stashTarget=0; -#ifdef USEFASTCLOCK -int16_t lastclocktime = 0; -#endif - // This is a JMRI command parser. // It doesnt know how the string got here, nor how it gets back. // It knows nothing about hardware or tracks... it just parses strings and @@ -574,34 +570,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'J' : // throttle info access { -#ifdef USEFASTCLOCK if ((params<1) | (params>3)) break; // -#endif -#ifndef USEFASTCLOCK - if ((params<1) | (params>2)) break; // -#endif + //if ((params<1) | (params>2)) break; // int16_t id=(params==2)?p[1]:0; switch(p[0]) { -#ifdef USEFASTCLOCK case HASH_KEYWORD_C: // sets time and speed if (params==1) { // returns latest time - StringFormatter::send(stream, F("\n"), lastclocktime); + int16_t x = CommandDistributor::retClockTime(); + StringFormatter::send(stream, F("\n"), x); return; } - if (p[1] != lastclocktime){ - if (Diag::CMD) { - DIAG(F("Clock Command Received")); - DIAG(F("Received Clock Time is: %d at rate: %d"), p[1], p[2]); - } - LCD(6,F("Clk Time:%d Sp %d"), p[1], p[2]); - //LCD(7,F("Clock Speed: %d"), p[2]); - RMFT2::clockEvent(p[1],1); - // Now tell everyone else what the time is. - CommandDistributor::broadcastClockTime(p[1], p[2]); - lastclocktime = p[1]; - } + CommandDistributor::setClockTime(p[1], p[2], 1); return; -#endif + case HASH_KEYWORD_A: // returns automations/routes StringFormatter::send(stream, F(" diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 1b5b51e1c..75ec63b7e 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -92,9 +92,7 @@ LookList * RMFT2::onRedLookup=NULL; LookList * RMFT2::onAmberLookup=NULL; LookList * RMFT2::onGreenLookup=NULL; LookList * RMFT2::onChangeLookup=NULL; -#ifdef USEFASTCLOCK LookList * RMFT2::onClockLookup=NULL; -#endif #define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter) #define SKIPOP progCounter+=3 @@ -178,9 +176,8 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { onAmberLookup=LookListLoader(OPCODE_ONAMBER); onGreenLookup=LookListLoader(OPCODE_ONGREEN); onChangeLookup=LookListLoader(OPCODE_ONCHANGE); - #ifdef USEFASTCLOCK onClockLookup=LookListLoader(OPCODE_ONTIME); - #endif + // Second pass startup, define any turnouts or servos, set signals red // add sequences onRoutines to the lookups @@ -981,9 +978,7 @@ void RMFT2::loop2() { case OPCODE_ONAMBER: case OPCODE_ONGREEN: case OPCODE_ONCHANGE: - #ifdef USEFASTCLOCK case OPCODE_ONTIME: - #endif break; @@ -1116,14 +1111,12 @@ void RMFT2::changeEvent(int16_t vpin, bool change) { if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin); } -#ifdef USEFASTCLOCK void RMFT2::clockEvent(int16_t clocktime, bool change) { // Hunt for an ONTIME for this time if (Diag::CMD) DIAG(F("Looking for clock event at : %d"), clocktime); if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime); } -#endif void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { int pc= handlers->find(id); diff --git a/EXRAIL2.h b/EXRAIL2.h index 7f04458ca..69fd382fa 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -55,10 +55,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_SET_TRACK, OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN, OPCODE_ONCHANGE, -#ifdef USEFASTCLOCK OPCODE_ONCLOCKTIME, OPCODE_ONTIME, -#endif // OPcodes below this point are skip-nesting IF operations // placed here so that they may be skipped as a group @@ -120,9 +118,7 @@ class LookList { static void turnoutEvent(int16_t id, bool closed); static void activateEvent(int16_t addr, bool active); static void changeEvent(int16_t id, bool change); - #ifdef USEFASTCLOCK static void clockEvent(int16_t clocktime, bool change); - #endif static const int16_t SERVO_SIGNAL_FLAG=0x4000; static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000; static const int16_t DCC_SIGNAL_FLAG=0x1000; @@ -180,9 +176,7 @@ class LookList { static LookList * onAmberLookup; static LookList * onGreenLookup; static LookList * onChangeLookup; -#ifdef USEFASTCLOCK static LookList * onClockLookup; -#endif // Local variables - exist for each instance/task RMFT2 *next; // loop chain diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 409cdcd65..a5e6d9051 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -86,10 +86,8 @@ #undef ONDEACTIVATE #undef ONDEACTIVATEL #undef ONCLOSE -#ifdef USEFASTCLOCK #undef ONTIME #undef ONCLOCKTIME -#endif #undef ONGREEN #undef ONRED #undef ONTHROW @@ -202,10 +200,8 @@ #define ONACTIVATE(addr,subaddr) #define ONACTIVATEL(linear) #define ONAMBER(signal_id) -#ifdef USEFASTCLOCK #define ONTIME(value) #define ONCLOCKTIME(hours,mins) -#endif #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) #define ONCLOSE(turnout_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 0d807d12a..2ffbc755f 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -55,11 +55,9 @@ // helper macro for turnout description as HIDDEN #define HIDDEN "\x01" -#ifdef USEFASTCLOCK // helper macro to strip leading zeros off time inputs // (10#mins)%100) #define STRIP_ZERO(value) 10##value%100 -#endif // Pass 1 Implements aliases #include "EXRAIL2MacroReset.h" @@ -303,10 +301,8 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3), #define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id), #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), -#ifdef USEFASTCLOCK #define ONTIME(value) OPCODE_ONTIME,V(value), #define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)), -#endif #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index 22bb94ebc..0e1bf76d7 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -45,7 +45,7 @@ class EXFastClock : public IODevice { addDevice(this); } -static void EXFastClock::create(uint8_t _I2CAddress) { +static void create(uint8_t _I2CAddress) { DIAG(F("Checking for Clock")); // Start by assuming we will find the clock @@ -53,6 +53,7 @@ static void EXFastClock::create(uint8_t _I2CAddress) { // Returns I2C_STATUS_OK (0) if OK, or error code. uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress); DIAG(F("Clock check result - %d"), _checkforclock); + // XXXX change thistosave2 bytes if (_checkforclock == 0) { FAST_CLOCK_EXISTS = true; //DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); @@ -67,11 +68,8 @@ static void EXFastClock::create(uint8_t _I2CAddress) { } private: - //uint8_t _I2CAddress; - uint16_t _clocktime; - uint8_t _clockrate; - uint16_t _previousclocktime; - unsigned long _lastchecktime; +uint8_t _I2CAddress; + // Initialisation of Fastclock void _begin() override { @@ -98,39 +96,33 @@ void _loop(unsigned long currentMicros) override{ if (FAST_CLOCK_EXISTS==true) { uint8_t readBuffer[3]; byte a,b; - #if defined(EXRAIL_ACTIVE) + #ifdef EXRAIL_ACTIVE I2CManager.read(_I2CAddress, readBuffer, 3); + // XXXX change this to save a few bytes a = readBuffer[0]; b = readBuffer[1]; - _clocktime = (a << 8) + b; - _clockrate = readBuffer[2]; - - if (_clocktime != _previousclocktime) { - _previousclocktime = _clocktime; - //if (Diag::CMD) - // DIAG(F("Received Clock Time is: %d at rate: %d"), _clocktime, _clockrate); - LCD(6,F(("Clk Time:%d Sp %d")), _clocktime, _clockrate); - RMFT2::clockEvent(_clocktime,1); - // Now tell everyone else what the time is. - CommandDistributor::broadcastClockTime(_clocktime, _clockrate); + //_clocktime = (a << 8) + b; + //_clockrate = readBuffer[2]; - // As the maximum clock increment is 2 seconds delay a bit - say 1 sec. - delayUntil(currentMicros + 1000000); // Wait 1000ms before checking again, + CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1); + //setClockTime(int16_t clocktime, int8_t clockrate, byte opt); + + // As the minimum clock increment is 2 seconds delay a bit - say 1 sec. + // Clock interval is 60/ clockspeed i.e 60/b seconds + delayUntil(currentMicros + ((60/b) * 1000000)); } - _lastchecktime = currentMicros; - + #endif - } - -} -// Display EX-FastClock device driver info. -void _display() { - DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); -} + + // Display EX-FastClock device driver info. + void _display() { + DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } + }; #endif From 286bdc3c4d817ed0a75c8021955b804ca15aa44b Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Sat, 21 Jan 2023 10:20:49 +0000 Subject: [PATCH 460/870] Create platformio.ini.original --- platformio.ini.original | 223 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 platformio.ini.original diff --git a/platformio.ini.original b/platformio.ini.original new file mode 100644 index 000000000..82167f225 --- /dev/null +++ b/platformio.ini.original @@ -0,0 +1,223 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = + mega2560 + uno + mega328 + unowifiR2 + nano + samd21-dev-usb + samd21-zero-usb + ESP32 + Nucleo-F411RE + Nucleo-F446RE + Teensy3.2 + Teensy3.5 + Teensy3.6 + Teensy4.0 + Teensy4.1 +src_dir = . +include_dir = . + +[env] +build_flags = -Wall -Wextra + +[env:samd21-dev-usb] +platform = atmelsam +board = sparkfun_samd21_dev_usb +framework = arduino +upload_protocol = sam-ba +lib_deps = ${env.lib_deps} +monitor_speed = 115200 +monitor_echo = yes +build_flags = -std=c++17 + +[env:samd21-zero-usb] +platform = atmelsam +board = zeroUSB +framework = arduino +upload_protocol = sam-ba +lib_deps = ${env.lib_deps} +monitor_speed = 115200 +monitor_echo = yes +build_flags = -std=c++17 + +[env:mega2560-debug] +platform = atmelavr +board = megaatmega2560 +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +build_flags = -DDIAG_IO -DDIAG_LOOPTIMES + +[env:mega2560-no-HAL] +platform = atmelavr +board = megaatmega2560 +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +build_flags = -DIO_NO_HAL + +[env:mega2560-I2C-wire] +platform = atmelavr +board = megaatmega2560 +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +build_flags = -DI2C_USE_WIRE + +[env:mega2560] +platform = atmelavr +board = megaatmega2560 +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +build_flags = -mcall-prologues + +[env:mega328] +platform = atmelavr +board = uno +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes + +[env:unowifiR2] +platform = atmelmegaavr +board = uno_wifi_rev2 +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200" + +[env:nanoevery] +platform = atmelmegaavr +board = nano_every +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +upload_speed = 19200 +build_flags = -DDIAG_IO + +[env:uno] +platform = atmelavr +board = uno +framework = arduino +lib_deps = + ${env.lib_deps} + arduino-libraries/Ethernet + SPI +monitor_speed = 115200 +monitor_echo = yes +build_flags = -mcall-prologues + +[env:nano] +platform = atmelavr +board = nanoatmega328new +board_upload.maximum_size = 32256 +framework = arduino +lib_deps = ${env.lib_deps} +monitor_speed = 115200 +monitor_echo = yes + +[env:ESP32] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 + +[env:Nucleo-F411RE] +platform = ststm32 +board = nucleo_f411re +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 -Os -g2 -Wunused-variable +monitor_speed = 115200 +monitor_echo = yes + +[env:Nucleo-F446RE] +platform = ststm32 +board = nucleo_f446re +framework = arduino +lib_deps = ${env.lib_deps} +build_flags = -std=c++17 -Os -g2 -Wunused-variable +monitor_speed = 115200 +monitor_echo = yes + +[env:Teensy3.2] +platform = teensy +board = teensy31 +framework = arduino +build_flags = -std=c++17 -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy3.5] +platform = teensy +board = teensy35 +framework = arduino +build_flags = -std=c++17 -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy3.6] +platform = teensy +board = teensy36 +framework = arduino +build_flags = -std=c++17 -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy4.0] +platform = teensy +board = teensy40 +framework = arduino +build_flags = -std=c++17 -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = NativeEthernet + +[env:Teensy4.1] +platform = teensy +board = teensy41 +framework = arduino +build_flags = -std=c++17 -Os -g2 +lib_deps = ${env.lib_deps} +lib_ignore = + From fb9170ab8b80564fba5fd915728dcb1ad7cd8ee4 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 22 Jan 2023 19:25:00 +1000 Subject: [PATCH 461/870] SIGNAL/SIGNALH operating correctly --- EXRAIL2.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index f44f9dcab..bf5b61afc 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1076,11 +1076,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) { // Manage invert (HIGH on) pins bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG; - + // set the three pins - if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh); - if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh); - if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh); + if (redpin) { + bool redval=(rag==SIGNAL_RED || rag==SIMAMBER); + if (!aHigh) redval=!redval; + // IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh); + IODevice::write(redpin,redval); + } + if (amberpin) { + // IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh); + bool amberval=(rag==SIGNAL_AMBER); + if (!aHigh) amberval=!amberval; + IODevice::write(amberpin,amberval); + } + if (greenpin) { + // IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh); + bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER); + if (!aHigh) greenval=!greenval; + IODevice::write(greenpin,greenval); + } } /* static */ bool RMFT2::isSignal(int16_t id,char rag) { From e079a9e395489fcfa440d58481c5246eff582769 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 22 Jan 2023 12:37:16 +0000 Subject: [PATCH 462/870] Update IO_VL53L0X.h Improve address changing logic. --- IO_VL53L0X.h | 64 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 040014e55..6a83970be 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -70,7 +70,8 @@ * 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. + * XSHUT terminal on the module. The digital output may be an Arduino pin or an + * I/O extender pin. * * Example: * In mySetup function within mySetup.cpp: @@ -101,21 +102,24 @@ class VL53L0X : public IODevice { uint16_t _offThreshold; VPIN _xshutPin; bool _value; - uint8_t _nextState = 0; + uint8_t _nextState = STATE_INIT; I2CRB _rb; uint8_t _inBuffer[12]; uint8_t _outBuffer[2]; + static bool _addressConfigInProgress; + // 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, - STATE_FAILED = 8, + STATE_RESTARTMODULE = 1, + STATE_CONFIGUREADDRESS = 2, + STATE_SKIP = 3, + STATE_CONFIGUREDEVICE = 4, + STATE_INITIATESCAN = 5, + STATE_CHECKSTATUS = 6, + STATE_GETRESULTS = 7, + STATE_DECODERESULTS = 8, + STATE_FAILED = 9, }; // Register addresses @@ -146,14 +150,13 @@ class VL53L0X : public IODevice { addDevice(this); } void _begin() override { - 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; - } + // If there's only one device, then the XSHUT pin need not be connected. However, + // the device will not respond on its default address if it has + // already been changed. Therefore, we skip the address configuration if the + // desired address is already responding on the I2C bus. + if (_xshutPin == VPIN_NONE && I2CManager.exists(_i2cAddress)) { + // Device already present on this address, so skip the address initialisation. + _nextState = STATE_CONFIGUREDEVICE; } } @@ -161,21 +164,32 @@ class VL53L0X : public IODevice { 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. + // On first entry to loop, reset this module by pulling XSHUT low. Each module + // will be addressed in turn, until all are in the reset state. + // If no XSHUT pin is configured, then only one device is supported. if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); - _nextState = STATE_CONFIGUREADDRESS; + _nextState = STATE_RESTARTMODULE; + delayUntil(currentMicros+1000); break; - case STATE_CONFIGUREADDRESS: - // On second entry, set XSHUT pin high to allow the module to restart. + case STATE_RESTARTMODULE: + // On second entry, set XSHUT pin high to allow this module to restart. // On the module, there is a diode in series with the XSHUT pin to // protect the low-voltage pin against +5V. + // Ensure this is done for only one module at a time by using a + // shared flag accessible to all device instances. + if (_addressConfigInProgress) return; + _addressConfigInProgress = true; + // Set XSHUT pin (if connected) if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); // Allow the module time to restart - delay(10); + delayUntil(currentMicros+10000); + _nextState = STATE_CONFIGUREADDRESS; + break; + case STATE_CONFIGUREADDRESS: // 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); + _addressConfigInProgress = false; _nextState = STATE_SKIP; break; case STATE_SKIP: @@ -311,4 +325,6 @@ class VL53L0X : public IODevice { } }; +bool VL53L0X::_addressConfigInProgress = false; + #endif // IO_VL53L0X_h From bfbc45674f67fda5af1ab42080fe51e287df018b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 22 Jan 2023 12:38:24 +0000 Subject: [PATCH 463/870] Update IO_AnalogueInputs.h Add I2C initialisation calls (previously missing). --- IO_AnalogueInputs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 1af351d7d..5f9d0e0b3 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -73,6 +73,11 @@ class ADS111x: public IODevice { addDevice(this); } void _begin() { + // Initialise I2C + I2CManager.begin(); + // ADS111x support high-speed I2C (4.3MHz) but that requires special + // processing. So stick to fast mode (400kHz maximum). + I2CManager.setClock(400000); // Initialise ADS device if (I2CManager.exists(_i2cAddress)) { _nextState = STATE_STARTSCAN; From 705617239fa69d7110077b9053d16d0e48fd25bf Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 22 Jan 2023 13:13:20 +0000 Subject: [PATCH 464/870] Sort out I2C timeout handling, and further I2C diagnostics. Timeout handling and recovery in loop() function now operative. Start-up check for I2C signals short to ground added. Initial I2C device probe speed up. Possible infinite loops in I2C AVR native driver during fault conditions removed. --- I2CManager.cpp | 66 +++++++++++++++++++++++----------------- I2CManager.h | 22 ++++++++------ I2CManager_AVR.h | 15 +++++---- I2CManager_NonBlocking.h | 35 +++++++++++++-------- I2CManager_Wire.h | 43 +++++++++++++++++++++----- 5 files changed, 114 insertions(+), 67 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index d7be935e3..eafdb7068 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -1,6 +1,6 @@ /* + * © 2023, Neil McKechnie * © 2022 Paul M Antoine - * © 2021, Neil McKechnie * All rights reserved. * * This file is part of CommandStation-EX @@ -43,12 +43,21 @@ // If not already initialised, initialise I2C void I2CManagerClass::begin(void) { - //setTimeout(25000); // 25 millisecond timeout if (!_beginCompleted) { _beginCompleted = true; _initialise(); - // Probe and list devices. + // Check for short-circuits on I2C + if (!digitalRead(SDA)) + DIAG(F("WARNING: Possible short-circuit on I2C SDA line")); + if (!digitalRead(SCL)) + DIAG(F("WARNING: Possible short-circuit on I2C SCL line")); + + // Probe and list devices. Use standard mode + // (clock speed 100kHz) for best device compatibility. + _setClock(100000); + unsigned long originalTimeout = timeout; + setTimeout(1000); // use 1ms timeout for probes bool found = false; for (byte addr=1; addr<127; addr++) { if (exists(addr)) { @@ -57,6 +66,8 @@ void I2CManagerClass::begin(void) { } } if (!found) DIAG(F("No I2C Devices found")); + _setClock(_clockSpeed); + setTimeout(originalTimeout); // set timeout back to original } } @@ -65,18 +76,17 @@ void I2CManagerClass::begin(void) { void I2CManagerClass::setClock(uint32_t speed) { if (speed < _clockSpeed && !_clockSpeedFixed) { _clockSpeed = speed; + DIAG(F("I2C clock speed set to %l Hz"), _clockSpeed); } _setClock(_clockSpeed); } -// Force clock speed to that specified. It can then only -// be overridden by calling Wire.setClock directly. +// Force clock speed to that specified. void I2CManagerClass::forceClock(uint32_t speed) { - if (!_clockSpeedFixed) { - _clockSpeed = speed; - _clockSpeedFixed = true; - _setClock(_clockSpeed); - } + _clockSpeed = speed; + _clockSpeedFixed = true; + _setClock(_clockSpeed); + DIAG(F("I2C clock speed forced to %l Hz"), _clockSpeed); } // Check if specified I2C address is responding (blocking operation) @@ -181,40 +191,40 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) { ***************************************************************************/ I2CManagerClass I2CManager = I2CManagerClass(); +// Default timeout 100ms on I2C request block completion. +// A full 32-byte transmission takes about 8ms at 100kHz, +// so this value allows lots of headroom. +// It can be modified by calling I2CManager.setTimeout() function. +// When retries are enabled, the timeout applies to each +// try, and failure from timeout does not get retried. +unsigned long I2CManagerClass::timeout = 100000UL; + ///////////////////////////////////////////////////////////////////////////// // Helper functions associated with I2C Request Block ///////////////////////////////////////////////////////////////////////////// /*************************************************************************** - * 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 (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. + * Block waiting for request to complete, and return completion status. + * Timeout monitoring is performed in the I2CManager.loop() function. ***************************************************************************/ uint8_t I2CRB::wait() { - unsigned long waitStart = millis(); - do { + while (status==I2C_STATUS_PENDING) { I2CManager.loop(); - // 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. - // TODO: Ideally we would like to cancel the request. - return status; - } - } while (status==I2C_STATUS_PENDING); + }; return status; } /*************************************************************************** * Check whether request is still in progress. + * Timeout monitoring is performed in the I2CManager.loop() function. ***************************************************************************/ bool I2CRB::isBusy() { - I2CManager.loop(); - return (status==I2C_STATUS_PENDING); + if (status==I2C_STATUS_PENDING) { + I2CManager.loop(); + return true; + } else + return false; } /*************************************************************************** diff --git a/I2CManager.h b/I2CManager.h index bcb70924f..677c5f988 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -1,6 +1,6 @@ /* + * © 2023, Neil McKechnie. All rights reserved. * © 2022 Paul M Antoine - * © 2021, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -29,7 +29,7 @@ * of the Wire class, but also has a native implementation for AVR * which supports non-blocking queued I/O requests. * - * Helps to avoid calling Wire.begin() multiple times (which is not) + * Helps to avoid calling Wire.begin() multiple times (which is not * entirely benign as it reinitialises). * * Also helps to avoid the Wire clock from being set, by another device @@ -76,6 +76,8 @@ * Timeout monitoring is possible, but requires that the following call is made * reasonably frequently in the program's loop() function: * I2CManager.loop(); + * So that the application doesn't need to do this explicitly, this call is performed + * from the I2CRB::isBusy() or I2CRB::wait() functions. * */ @@ -111,9 +113,11 @@ * */ -// Maximum number of retries on an I2C operation +// Maximum number of retries on an I2C operation. // A value of zero will disable retries. // Maximum value is 254 (unsigned byte counter) +// Note that timeout failures are not retried, but any timeout +// configured applies to each try separately. #define MAX_I2C_RETRIES 2 // Add following line to config.h to enable Wire library instead of native I2C drivers @@ -203,6 +207,8 @@ class I2CManagerClass { void setClock(uint32_t speed); // Force clock speed void forceClock(uint32_t speed); + // setTimeout sets the timout value for I2C transactions (milliseconds). + void setTimeout(unsigned long); // Check if specified I2C address is responding. uint8_t checkAddress(uint8_t address); inline bool exists(uint8_t address) { @@ -239,11 +245,14 @@ class I2CManagerClass { private: bool _beginCompleted = false; bool _clockSpeedFixed = false; + static uint8_t retryCounter; // Count of retries #if defined(__arm__) uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32 #else uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. #endif + static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. + // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); @@ -272,9 +281,6 @@ class I2CManagerClass { static volatile uint8_t operation; static volatile unsigned long startTime; - static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. - static uint8_t retryCounter; // Count of retries - void startTransaction(); // Low-level hardware manipulation functions. @@ -286,10 +292,6 @@ class I2CManagerClass { static void I2C_close(); public: - // setTimeout sets the timout value for I2C transactions. - // TODO: Get I2C timeout working before uncommenting the code below. - void setTimeout(unsigned long value) { (void)value; /* timeout = value; */ }; - // handleInterrupt needs to be public to be called from the ISR function! static void handleInterrupt(); #endif diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 6492e000e..267a9211d 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -96,9 +96,8 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; - // We may have initiated a stop bit before this without waiting for it. - // Wait for stop bit to be sent before sending start. - while (TWCR & (1<operation & OPERATION_MASK; // Start the I2C process going. I2C_sendStart(); - startTime = micros(); } } } @@ -167,21 +168,30 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r return I2C_STATUS_OK; } +/*************************************************************************** + * Set I2C timeout value in microseconds. The timeout applies to the entire + * I2CRB request, e.g. where a write+read is performed, the timer is not + * reset before the read. + ***************************************************************************/ +void I2CManagerClass::setTimeout(unsigned long value) { + timeout = value; +}; + /*************************************************************************** * checkForTimeout() function, called from isBusy() and wait() to cancel - * requests that are taking too long to complete. - * This function doesn't fully work as intended so is not currently called. - * Instead we check for an I2C hang-up and report an error from - * I2CRB::wait(), but we aren't able to recover from the hang-up. Such faults + * requests that are taking too long to complete. Such faults * may be caused by an I2C wire short for example. ***************************************************************************/ void I2CManagerClass::checkForTimeout() { - unsigned long currentMicros = micros(); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { I2CRB *t = queueHead; if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && timeout > 0) { // Check for timeout - if (currentMicros - startTime > timeout) { + unsigned long elapsed = micros() - startTime; + if (elapsed > timeout) { +#ifdef DIAG_IO + //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), t->i2cAddress, currentRequest); +#endif // Excessive time. Dequeue request queueHead = t->nextRequest; if (!queueHead) queueTail = NULL; @@ -192,7 +202,9 @@ void I2CManagerClass::checkForTimeout() { // Try close and init, not entirely satisfactory but sort of works... I2C_close(); // Shutdown and restart twi interface I2C_init(); + _setClock(_clockSpeed); state = I2C_STATE_FREE; +// I2C_sendStop(); // in case device is waiting for a stop condition // Initiate next queued request if any. startTransaction(); @@ -208,10 +220,8 @@ void I2CManagerClass::loop() { #if !defined(I2C_USE_INTERRUPTS) handleInterrupt(); #endif - // Timeout is now reported in I2CRB::wait(), not here. - // I've left the code, commented out, as a reminder to look at this again - // in the future. - //checkForTimeout(); + // Call function to monitor for stuch I2C operations. + checkForTimeout(); } /*************************************************************************** @@ -270,7 +280,6 @@ volatile uint8_t I2CManagerClass::operation; volatile uint8_t I2CManagerClass::bytesToSend; volatile uint8_t I2CManagerClass::bytesToReceive; volatile unsigned long I2CManagerClass::startTime; -unsigned long I2CManagerClass::timeout = 0; uint8_t I2CManagerClass::retryCounter = 0; #endif \ No newline at end of file diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index aea91aae6..974956529 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -30,11 +30,19 @@ #define I2C_USE_WIRE #endif +// Older versions of Wire don't have setWireTimeout function. AVR does. +#ifdef ARDUINO_ARCH_AVR +#define WIRE_HAS_TIMEOUT +#endif + /*************************************************************************** * Initialise I2C interface software ***************************************************************************/ void I2CManagerClass::_initialise() { Wire.begin(); +#if defined(WIRE_HAS_TIMEOUT) + Wire.setWireTimeout(timeout, true); +#endif } /*************************************************************************** @@ -45,6 +53,18 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { Wire.setClock(i2cClockSpeed); } +/*************************************************************************** + * Set I2C timeout value in microseconds. The timeout applies to each + * Wire call separately, i.e. in a write+read, the timer is reset before the + * read is started. + ***************************************************************************/ +void I2CManagerClass::setTimeout(unsigned long value) { + timeout = value; +#if defined(WIRE_HAS_TIMEOUT) + Wire.setWireTimeout(value, true); +#endif +} + /*************************************************************************** * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ @@ -93,10 +113,21 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea status = Wire.endTransmission(false); // Don't free bus yet } if (status == I2C_STATUS_OK) { +#ifdef WIRE_HAS_TIMEOUT + Wire.clearWireTimeoutFlag(); +#endif Wire.requestFrom(address, (size_t)readSize); - while (Wire.available() && nBytes < readSize) - readBuffer[nBytes++] = Wire.read(); - if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; +#ifdef WIRE_HAS_TIMEOUT + if (!Wire.getWireTimeoutFlag()) { +#endif + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; +#ifdef WIRE_HAS_TIMEOUT + } else { + status = I2C_STATUS_TIMEOUT; + } +#endif } } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); @@ -136,8 +167,4 @@ void I2CManagerClass::queueRequest(I2CRB *req) { ***************************************************************************/ void I2CManagerClass::loop() {} -// Loop function -void I2CManagerClass::checkForTimeout() {} - - #endif \ No newline at end of file From aaf25d542618d348882c3ea241e6968731cf2a2f Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 23 Jan 2023 04:53:39 +1000 Subject: [PATCH 465/870] Remove excess comments --- EXRAIL2.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index bf5b61afc..5f2bf8b8a 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1081,17 +1081,14 @@ int16_t RMFT2::getSignalSlot(int16_t id) { if (redpin) { bool redval=(rag==SIGNAL_RED || rag==SIMAMBER); if (!aHigh) redval=!redval; - // IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh); IODevice::write(redpin,redval); } if (amberpin) { - // IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh); bool amberval=(rag==SIGNAL_AMBER); if (!aHigh) amberval=!amberval; IODevice::write(amberpin,amberval); } if (greenpin) { - // IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh); bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER); if (!aHigh) greenval=!greenval; IODevice::write(greenpin,greenval); From 7f4e3d9cea3caced0218e4a743a6f4ea8dfbede5 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 23 Jan 2023 11:49:23 +1000 Subject: [PATCH 466/870] Digital inputs optimised --- IO_EXIOExpander.h | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 1e20fac99..9aa90112e 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -66,6 +66,8 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; + _digitalBytes = (numDigitalPins+7)/8; + _digitalPinStates=(byte*) calloc(_digitalBytes,1); addDevice(this); } @@ -125,6 +127,11 @@ class EXIOExpander : public IODevice { return false; } + void _loop(unsigned long currentMicros) override { + _commandBuffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalPinStates, _digitalBytes, _commandBuffer, 1); + } + int _readAnalogue(VPIN vpin) override { if (vpin < _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; @@ -137,11 +144,9 @@ class EXIOExpander : public IODevice { int _read(VPIN vpin) override { if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIORDD; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = 0x00; // Don't need to use this for reading - I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); - return _digitalInBuffer[0]; + uint8_t pinByte = pin / 8; + bool value = _digitalPinStates[pinByte] >> (pin - pinByte * 8); + return value; } void _write(VPIN vpin, int value) override { @@ -182,6 +187,9 @@ class EXIOExpander : public IODevice { uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; + byte* _digitalPinStates; + uint8_t _digitalBytes = 0; + byte _commandBuffer[1]; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From 878549d5384c21196a4e8a098b8a18f5d3b3162d Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 23 Jan 2023 16:26:07 +1000 Subject: [PATCH 467/870] Working on analogue inputs --- IO_EXIOExpander.h | 50 ++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 9aa90112e..77554b570 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -66,8 +66,10 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; - _digitalBytes = (numDigitalPins+7)/8; - _digitalPinStates=(byte*) calloc(_digitalBytes,1); + _digitalPinBytes = (numDigitalPins+7)/8; + _analoguePinBytes = (numAnaloguePins+7)/8; + _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); + _analogueInputStates=(byte*) calloc(_analoguePinBytes,2); addDevice(this); } @@ -79,16 +81,16 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); - if (_digitalInBuffer[0] != EXIORDY) { + I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); + if (_commandBuffer[0] != EXIORDY) { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; return; } // Attempt to get version, if we don't get it, we don't care, don't go offline // Using digital in buffer in reverse to save RAM - _digitalInBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _digitalInBuffer, 1); + _commandBuffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -123,29 +125,41 @@ class EXIOExpander : public IODevice { int _configureAnalogIn(VPIN vpin) override { if (vpin < _firstVpin + _numDigitalPins) { DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); + return false; } - return false; + int pin = vpin - _firstVpin; + _analogueOutBuffer[0] = EXIOENAN; + _analogueOutBuffer[1] = pin; + DIAG(F("Enable Vpin %d/pin %d"), vpin, pin); + I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + return true; } void _loop(unsigned long currentMicros) override { _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalPinStates, _digitalBytes, _commandBuffer, 1); + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); + _commandBuffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + delayUntil(currentMicros + 500000); } int _readAnalogue(VPIN vpin) override { if (vpin < _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; - _analogueOutBuffer[0] = EXIORDAN; - _analogueOutBuffer[1] = pin; - I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); - return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; + // _analogueOutBuffer[0] = EXIORDAN; + // _analogueOutBuffer[1] = pin; + // I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); + // return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; + uint8_t _pinLSBByte = (pin / 4) * 2; + uint8_t _pinMSBByte = _pinLSBByte + 1; + return (_pinMSBByte << 8) + _pinLSBByte; } int _read(VPIN vpin) override { if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; - bool value = _digitalPinStates[pinByte] >> (pin - pinByte * 8); + bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); return value; } @@ -177,18 +191,17 @@ class EXIOExpander : public IODevice { uint8_t _i2cAddress; uint8_t _numDigitalPins; uint8_t _numAnaloguePins; - int _digitalPinBytes; - int _analoguePinBytes; byte _analogueInBuffer[2]; byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; - byte _digitalInBuffer[1]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; - byte* _digitalPinStates; - uint8_t _digitalBytes = 0; + byte* _digitalInputStates; + byte* _analogueInputStates; + uint8_t _digitalPinBytes = 0; + uint8_t _analoguePinBytes = 0; byte _commandBuffer[1]; enum { @@ -199,6 +212,7 @@ class EXIOExpander : public IODevice { EXIORDAN = 0xE4, // Flag to read an analogue input EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input + EXIOENAN = 0xE7, // Flag eo enable an analogue pin }; }; From 459904e5ddc16361d43cd033d7e2e1725f8f7211 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 23 Jan 2023 20:12:28 +1000 Subject: [PATCH 468/870] More analogue inputs --- IO_EXIOExpander.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 77554b570..9fc6876bf 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -67,9 +67,9 @@ class EXIOExpander : public IODevice { _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; _digitalPinBytes = (numDigitalPins+7)/8; - _analoguePinBytes = (numAnaloguePins+7)/8; + _analoguePinBytes = numAnaloguePins * 2; _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); - _analogueInputStates=(byte*) calloc(_analoguePinBytes,2); + _analogueInputStates=(byte*) calloc(_analoguePinBytes,1); addDevice(this); } @@ -130,7 +130,6 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIOENAN; _analogueOutBuffer[1] = pin; - DIAG(F("Enable Vpin %d/pin %d"), vpin, pin); I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); return true; } @@ -140,7 +139,6 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); _commandBuffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); - delayUntil(currentMicros + 500000); } int _readAnalogue(VPIN vpin) override { @@ -150,9 +148,10 @@ class EXIOExpander : public IODevice { // _analogueOutBuffer[1] = pin; // I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); // return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; - uint8_t _pinLSBByte = (pin / 4) * 2; + uint8_t _pinLSBByte = pin * 2; uint8_t _pinMSBByte = _pinLSBByte + 1; - return (_pinMSBByte << 8) + _pinLSBByte; + // DIAG(F("Vpin %d LSB %d MSB %d"), vpin, _analogueInputStates[_pinLSBByte], _analogueInputStates[_pinMSBByte]); + return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } int _read(VPIN vpin) override { From 20b3e9064cc463e9e0ae313b2c3f7dcbb0559afa Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 23 Jan 2023 21:35:22 +1000 Subject: [PATCH 469/870] Analogue inputs functioning --- IO_EXIOExpander.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 9fc6876bf..54b186e5f 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -143,14 +143,13 @@ class EXIOExpander : public IODevice { int _readAnalogue(VPIN vpin) override { if (vpin < _firstVpin + _numDigitalPins) return false; - int pin = vpin - _firstVpin; + int pin = vpin - _firstVpin - _numDigitalPins; // _analogueOutBuffer[0] = EXIORDAN; // _analogueOutBuffer[1] = pin; // I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); // return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; uint8_t _pinLSBByte = pin * 2; uint8_t _pinMSBByte = _pinLSBByte + 1; - // DIAG(F("Vpin %d LSB %d MSB %d"), vpin, _analogueInputStates[_pinLSBByte], _analogueInputStates[_pinMSBByte]); return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } From 4acf46db548108479f1afea52764a86ddef3f712 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 24 Jan 2023 08:17:43 +1000 Subject: [PATCH 470/870] EX-IO reads optimised for speed --- IO_EXIOExpander.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 54b186e5f..cce101b33 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -144,10 +144,6 @@ class EXIOExpander : public IODevice { int _readAnalogue(VPIN vpin) override { if (vpin < _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin - _numDigitalPins; - // _analogueOutBuffer[0] = EXIORDAN; - // _analogueOutBuffer[1] = pin; - // I2CManager.read(_i2cAddress, _analogueInBuffer, 2, _analogueOutBuffer, 2); - // return (_analogueInBuffer[1] << 8) + _analogueInBuffer[0]; uint8_t _pinLSBByte = pin * 2; uint8_t _pinMSBByte = _pinLSBByte + 1; return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; @@ -189,7 +185,6 @@ class EXIOExpander : public IODevice { uint8_t _i2cAddress; uint8_t _numDigitalPins; uint8_t _numAnaloguePins; - byte _analogueInBuffer[2]; byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; From 682c47f7dd011afff651ed20060c83ecc28d8d01 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 23 Jan 2023 22:23:05 +0000 Subject: [PATCH 471/870] I2CManager_Mega4809.h - allow other I2C clock speeds. --- I2CManager_Mega4809.h | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index 0b8e8ca7c..c378a5df5 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -28,21 +28,21 @@ ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { uint16_t t_rise; - if (i2cClockSpeed < 200000) { - i2cClockSpeed = 100000; + if (i2cClockSpeed < 200000) t_rise = 1000; - } else if (i2cClockSpeed < 800000) { - i2cClockSpeed = 400000; + else if (i2cClockSpeed < 800000) t_rise = 300; - } else if (i2cClockSpeed < 1200000) { - i2cClockSpeed = 1000000; + else t_rise = 120; - } else { - i2cClockSpeed = 100000; - t_rise = 1000; - } + + if (t_rise == 120) + TWI0.CTRLA |= TWI_FMPEN_bm; + else + TWI0.CTRLA &= ~TWI_FMPEN_bm; + uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 * t_rise / 1000 - 10) / 2; + if (baud > 255) baud = 255; // ~30kHz TWI0.MBAUD = (uint8_t)baud; } @@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init() pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP); PORTMUX.TWISPIROUTEA |= TWI_MUX; + I2C_setClock(I2C_FREQ); #if defined(I2C_USE_INTERRUPTS) TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; #else TWI0.MCTRLA = TWI_ENABLE_bm; #endif - I2C_setClock(I2C_FREQ); TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; } @@ -70,6 +70,8 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + txCount = 0; + rxCount = 0; // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) @@ -89,7 +91,10 @@ void I2CManagerClass::I2C_sendStop() { * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - I2C_sendStop(); + + TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C + TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc; + delayMicroseconds(10); // Wait for things to stabilise (hopefully) } /*************************************************************************** @@ -114,11 +119,9 @@ void I2CManagerClass::I2C_handleInterrupt() { TWI0.MCTRLB = TWI_MCMD_STOP_gc; state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { - // Acked, so send next byte - if (currentRequest->operation == OPERATION_SEND_P) - TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); - else - TWI0.MDATA = currentRequest->writeBuffer[txCount++]; + // Acked, so send next byte (don't need to use GETFLASH) + txCount++; + TWI0.MDATA = *sendPointer++; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. @@ -131,13 +134,10 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors if (bytesToReceive) { - currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte + rxCount++; + *receivePointer++ = TWI0.MDATA; // Store received byte bytesToReceive--; - } else { - // Buffer full, issue nack/stop - TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; - } + } if (bytesToReceive) { // More bytes to receive, issue ack and start another read TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc; From 10c8915d330cccac3d6f483f2fc64f944189eff6 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 23 Jan 2023 22:28:43 +0000 Subject: [PATCH 472/870] Ensure correct functions are called for strcpy_P, strncmp_P, strlen_P etc. on non-AVR targets.. --- DCCEXParser.cpp | 4 ++-- FSH.h | 11 ++++++++++- RingStream.cpp | 2 +- WifiInterface.cpp | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index cbb152eb7..221d8b0ea 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -190,9 +190,9 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) // Parse an F() string void DCCEXParser::parse(const FSH * cmd) { DIAG(F("SETUP(\"%S\")"),cmd); - int size=strlen_P((char *)cmd)+1; + int size=STRLEN_P((char *)cmd)+1; char buffer[size]; - strcpy_P(buffer,(char *)cmd); + STRCPY_P(buffer,(char *)cmd); parse(&USB_SERIAL,(byte *)buffer,NULL); } diff --git a/FSH.h b/FSH.h index f4bf47e23..d03193544 100644 --- a/FSH.h +++ b/FSH.h @@ -47,7 +47,11 @@ typedef __FlashStringHelper FSH; #define FLASH PROGMEM #define GETFLASH(addr) pgm_read_byte_near(addr) - +#define STRCPY_P strcpy_P +#define STRCMP_P strcmp_P +#define STRNCPY_P strncpy_P +#define STRNCMP_P strncmp_P +#define STRLEN_P strlen_P #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) // AVR_MEGA memory deliberately placed at end of link may need _far functions @@ -80,5 +84,10 @@ typedef char FSH; #define GETFLASH(addr) (*(const byte *)(addr)) #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) #define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset)) +#define STRCPY_P strcpy +#define STRCMP_P strcmp +#define STRNCPY_P strncpy +#define STRNCMP_P strncmp +#define STRLEN_P strlen #endif #endif diff --git a/RingStream.cpp b/RingStream.cpp index 9377a0ac5..12dbaa120 100644 --- a/RingStream.cpp +++ b/RingStream.cpp @@ -83,7 +83,7 @@ size_t RingStream::printFlash(const FSH * flashBuffer) { // Establish the actual length of the progmem string. char * flash=(char *)flashBuffer; -int16_t plength=strlen_P(flash); +int16_t plength=STRLEN_P(flash); if (plength==0) return 0; // just ignore empty string // Retain the buffer count as it will be modified by the marker+address insert diff --git a/WifiInterface.cpp b/WifiInterface.cpp index bdc8dad4f..f69cc2ff7 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -184,8 +184,8 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, checkForOK(1000, true); // Not always OK, sometimes "no change" const char *yourNetwork = "Your network "; - if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) { - if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { + if (STRNCMP_P(yourNetwork, (const char*)SSid, 13) == 0 || STRNCMP_P("", (const char*)SSid, 13) == 0) { + if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) { // If the source code looks unconfigured, check if the // ESP8266 is preconfigured in station mode. // We check the first 13 chars of the SSid and the password @@ -258,7 +258,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, i=0; do { - if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { + if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) { // unconfigured StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"), oldCmd ? "" : "_CUR", macTail, macTail, channel); From e8e00f69d6d270de8bab24698008efa97d8600cf Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 23 Jan 2023 22:31:33 +0000 Subject: [PATCH 473/870] Non-blocking I2C - reset byte counters on timeout. --- I2CManager_NonBlocking.h | 1 + 1 file changed, 1 insertion(+) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index c55219a1a..fce158bd7 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -196,6 +196,7 @@ void I2CManagerClass::checkForTimeout() { queueHead = t->nextRequest; if (!queueHead) queueTail = NULL; currentRequest = NULL; + bytesToReceive = bytesToSend = 0; // Post request as timed out. t->status = I2C_STATUS_TIMEOUT; // Reset TWI interface so it is able to continue From a3d4255fee33e5a317c701bc468f7b725d5c232b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 24 Jan 2023 09:33:17 +0000 Subject: [PATCH 474/870] Revert "I2CManager_Mega4809.h - allow other I2C clock speeds." This reverts commit 682c47f7dd011afff651ed20060c83ecc28d8d01. --- I2CManager_Mega4809.h | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index c378a5df5..0b8e8ca7c 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -1,5 +1,5 @@ /* - * © 2023, Neil McKechnie. All rights reserved. + * © 2021, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -28,21 +28,21 @@ ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { uint16_t t_rise; - if (i2cClockSpeed < 200000) + if (i2cClockSpeed < 200000) { + i2cClockSpeed = 100000; t_rise = 1000; - else if (i2cClockSpeed < 800000) + } else if (i2cClockSpeed < 800000) { + i2cClockSpeed = 400000; t_rise = 300; - else + } else if (i2cClockSpeed < 1200000) { + i2cClockSpeed = 1000000; t_rise = 120; - - if (t_rise == 120) - TWI0.CTRLA |= TWI_FMPEN_bm; - else - TWI0.CTRLA &= ~TWI_FMPEN_bm; - + } else { + i2cClockSpeed = 100000; + t_rise = 1000; + } uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 * t_rise / 1000 - 10) / 2; - if (baud > 255) baud = 255; // ~30kHz TWI0.MBAUD = (uint8_t)baud; } @@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init() pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP); PORTMUX.TWISPIROUTEA |= TWI_MUX; - I2C_setClock(I2C_FREQ); #if defined(I2C_USE_INTERRUPTS) TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; #else TWI0.MCTRLA = TWI_ENABLE_bm; #endif + I2C_setClock(I2C_FREQ); TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; } @@ -70,8 +70,6 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; - txCount = 0; - rxCount = 0; // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) @@ -91,10 +89,7 @@ void I2CManagerClass::I2C_sendStop() { * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - - TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C - TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc; - delayMicroseconds(10); // Wait for things to stabilise (hopefully) + I2C_sendStop(); } /*************************************************************************** @@ -119,9 +114,11 @@ void I2CManagerClass::I2C_handleInterrupt() { TWI0.MCTRLB = TWI_MCMD_STOP_gc; state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { - // Acked, so send next byte (don't need to use GETFLASH) - txCount++; - TWI0.MDATA = *sendPointer++; + // Acked, so send next byte + if (currentRequest->operation == OPERATION_SEND_P) + TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); + else + TWI0.MDATA = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. @@ -134,10 +131,13 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors if (bytesToReceive) { - rxCount++; - *receivePointer++ = TWI0.MDATA; // Store received byte + currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte bytesToReceive--; - } + } else { + // Buffer full, issue nack/stop + TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; + state = I2C_STATUS_OK; + } if (bytesToReceive) { // More bytes to receive, issue ack and start another read TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc; From d0ce59b19f7a18cbf431dc35cec4ca55b73cda89 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 24 Jan 2023 09:35:10 +0000 Subject: [PATCH 475/870] I2CManager_Mega4809.h - allow other I2C clock speeds. --- I2CManager_Mega4809.h | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/I2CManager_Mega4809.h b/I2CManager_Mega4809.h index 0b8e8ca7c..41c70fe95 100644 --- a/I2CManager_Mega4809.h +++ b/I2CManager_Mega4809.h @@ -1,5 +1,5 @@ /* - * © 2021, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -28,21 +28,21 @@ ***************************************************************************/ void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { uint16_t t_rise; - if (i2cClockSpeed < 200000) { - i2cClockSpeed = 100000; + if (i2cClockSpeed < 200000) t_rise = 1000; - } else if (i2cClockSpeed < 800000) { - i2cClockSpeed = 400000; + else if (i2cClockSpeed < 800000) t_rise = 300; - } else if (i2cClockSpeed < 1200000) { - i2cClockSpeed = 1000000; + else t_rise = 120; - } else { - i2cClockSpeed = 100000; - t_rise = 1000; - } + + if (t_rise == 120) + TWI0.CTRLA |= TWI_FMPEN_bm; + else + TWI0.CTRLA &= ~TWI_FMPEN_bm; + uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000 * t_rise / 1000 - 10) / 2; + if (baud > 255) baud = 255; // ~30kHz TWI0.MBAUD = (uint8_t)baud; } @@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init() pinMode(PIN_WIRE_SDA, INPUT_PULLUP); pinMode(PIN_WIRE_SCL, INPUT_PULLUP); PORTMUX.TWISPIROUTEA |= TWI_MUX; + I2C_setClock(I2C_FREQ); #if defined(I2C_USE_INTERRUPTS) TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm; #else TWI0.MCTRLA = TWI_ENABLE_bm; #endif - I2C_setClock(I2C_FREQ); TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; } @@ -70,6 +70,8 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + txCount = 0; + rxCount = 0; // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) @@ -89,7 +91,10 @@ void I2CManagerClass::I2C_sendStop() { * Close I2C down ***************************************************************************/ void I2CManagerClass::I2C_close() { - I2C_sendStop(); + + TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C + TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc; + delayMicroseconds(10); // Wait for things to stabilise (hopefully) } /*************************************************************************** @@ -114,15 +119,12 @@ void I2CManagerClass::I2C_handleInterrupt() { TWI0.MCTRLB = TWI_MCMD_STOP_gc; state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { - // Acked, so send next byte - if (currentRequest->operation == OPERATION_SEND_P) - TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); - else - TWI0.MDATA = currentRequest->writeBuffer[txCount++]; + // Acked, so send next byte (don't need to use GETFLASH) + TWI0.MDATA = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { - // Last sent byte acked and no more to send. Send repeated start, address and read bit. - TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; + // Last sent byte acked and no more to send. Send repeated start, address and read bit. + TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition. TWI0.MCTRLB = TWI_MCMD_STOP_gc; @@ -133,11 +135,7 @@ void I2CManagerClass::I2C_handleInterrupt() { if (bytesToReceive) { currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte bytesToReceive--; - } else { - // Buffer full, issue nack/stop - TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; - } + } if (bytesToReceive) { // More bytes to receive, issue ack and start another read TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc; From 006c85e6ae60dc9c89502dc4a3cb1af8b82cf483 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Tue, 24 Jan 2023 12:21:28 +0000 Subject: [PATCH 476/870] Delete platformio.ini.original Delete file not required --- platformio.ini.original | 223 ---------------------------------------- 1 file changed, 223 deletions(-) delete mode 100644 platformio.ini.original diff --git a/platformio.ini.original b/platformio.ini.original deleted file mode 100644 index 82167f225..000000000 --- a/platformio.ini.original +++ /dev/null @@ -1,223 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[platformio] -default_envs = - mega2560 - uno - mega328 - unowifiR2 - nano - samd21-dev-usb - samd21-zero-usb - ESP32 - Nucleo-F411RE - Nucleo-F446RE - Teensy3.2 - Teensy3.5 - Teensy3.6 - Teensy4.0 - Teensy4.1 -src_dir = . -include_dir = . - -[env] -build_flags = -Wall -Wextra - -[env:samd21-dev-usb] -platform = atmelsam -board = sparkfun_samd21_dev_usb -framework = arduino -upload_protocol = sam-ba -lib_deps = ${env.lib_deps} -monitor_speed = 115200 -monitor_echo = yes -build_flags = -std=c++17 - -[env:samd21-zero-usb] -platform = atmelsam -board = zeroUSB -framework = arduino -upload_protocol = sam-ba -lib_deps = ${env.lib_deps} -monitor_speed = 115200 -monitor_echo = yes -build_flags = -std=c++17 - -[env:mega2560-debug] -platform = atmelavr -board = megaatmega2560 -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -build_flags = -DDIAG_IO -DDIAG_LOOPTIMES - -[env:mega2560-no-HAL] -platform = atmelavr -board = megaatmega2560 -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -build_flags = -DIO_NO_HAL - -[env:mega2560-I2C-wire] -platform = atmelavr -board = megaatmega2560 -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -build_flags = -DI2C_USE_WIRE - -[env:mega2560] -platform = atmelavr -board = megaatmega2560 -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -build_flags = -mcall-prologues - -[env:mega328] -platform = atmelavr -board = uno -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes - -[env:unowifiR2] -platform = atmelmegaavr -board = uno_wifi_rev2 -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200" - -[env:nanoevery] -platform = atmelmegaavr -board = nano_every -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -upload_speed = 19200 -build_flags = -DDIAG_IO - -[env:uno] -platform = atmelavr -board = uno -framework = arduino -lib_deps = - ${env.lib_deps} - arduino-libraries/Ethernet - SPI -monitor_speed = 115200 -monitor_echo = yes -build_flags = -mcall-prologues - -[env:nano] -platform = atmelavr -board = nanoatmega328new -board_upload.maximum_size = 32256 -framework = arduino -lib_deps = ${env.lib_deps} -monitor_speed = 115200 -monitor_echo = yes - -[env:ESP32] -platform = espressif32 -board = esp32dev -framework = arduino -lib_deps = ${env.lib_deps} -build_flags = -std=c++17 - -[env:Nucleo-F411RE] -platform = ststm32 -board = nucleo_f411re -framework = arduino -lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable -monitor_speed = 115200 -monitor_echo = yes - -[env:Nucleo-F446RE] -platform = ststm32 -board = nucleo_f446re -framework = arduino -lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable -monitor_speed = 115200 -monitor_echo = yes - -[env:Teensy3.2] -platform = teensy -board = teensy31 -framework = arduino -build_flags = -std=c++17 -Os -g2 -lib_deps = ${env.lib_deps} -lib_ignore = NativeEthernet - -[env:Teensy3.5] -platform = teensy -board = teensy35 -framework = arduino -build_flags = -std=c++17 -Os -g2 -lib_deps = ${env.lib_deps} -lib_ignore = NativeEthernet - -[env:Teensy3.6] -platform = teensy -board = teensy36 -framework = arduino -build_flags = -std=c++17 -Os -g2 -lib_deps = ${env.lib_deps} -lib_ignore = NativeEthernet - -[env:Teensy4.0] -platform = teensy -board = teensy40 -framework = arduino -build_flags = -std=c++17 -Os -g2 -lib_deps = ${env.lib_deps} -lib_ignore = NativeEthernet - -[env:Teensy4.1] -platform = teensy -board = teensy41 -framework = arduino -build_flags = -std=c++17 -Os -g2 -lib_deps = ${env.lib_deps} -lib_ignore = - From fcf16c1367d5397cc63861f0cbccebee390cdb4e Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 26 Jan 2023 18:53:25 +1000 Subject: [PATCH 477/870] Update version --- version.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index a17babdc7..0e142889c 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.9pre1" +#define VERSION "4.2.10pre1" +// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted +// IO_EXIOExpander.h input speed optimisation // 4.2.9 duinoNodes support // 4.2.8 HIGHMEM (EXRAIL support beyond 64kb) // Withrottle connect/disconnect improvements From 88b572a14838389fff1b07e6e9e43f3ab84093c3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 26 Jan 2023 16:55:58 +0100 Subject: [PATCH 478/870] Add EXRAIL IFLOCO function --- EXRAIL2.cpp | 5 ++++- EXRAIL2.h | 1 + EXRAIL2MacroReset.h | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 09566cba6..122bf4aab 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -747,6 +747,10 @@ void RMFT2::loop2() { skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1)); break; + case OPCODE_IFLOCO: // do if the loco is the active one + skipIf=loco!=operand; + break; + case OPCODE_IFNOT: // do next operand if sensor not set skipIf=readSensor(operand); break; @@ -1244,4 +1248,3 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { default: break; } } - \ No newline at end of file diff --git a/EXRAIL2.h b/EXRAIL2.h index 69fd382fa..7acb839f5 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -69,6 +69,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFRANDOM,OPCODE_IFRESERVE, OPCODE_IFCLOSED,OPCODE_IFTHROWN, OPCODE_IFRE, + OPCODE_IFLOCO }; enum thrunger: byte { diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index a5e6d9051..e59d1c909 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -65,6 +65,7 @@ #undef IFCLOSED #undef IFGREEN #undef IFGTE +#undef IFLOCO #undef IFLT #undef IFNOT #undef IFRANDOM @@ -182,6 +183,7 @@ #define IFCLOSED(turnout_id) #define IFGREEN(signal_id) #define IFGTE(sensor_id,value) +#define IFLOCO(loco_id) #define IFLT(sensor_id,value) #define IFNOT(sensor_id) #define IFRANDOM(percent) From 762742b4affa5d35c39e71530b75ab4554abfb4a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 27 Jan 2023 13:05:36 +0100 Subject: [PATCH 479/870] Add the macro def --- EXRAILMacros.h | 1 + 1 file changed, 1 insertion(+) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 2ffbc755f..25bb29973 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -282,6 +282,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id), #define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id), #define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value), +#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id), #define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value), #define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id), #define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent), From 2c0886bc2f5415e63fdb9f976e31b6cc8f548472 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 27 Jan 2023 17:03:39 +0100 Subject: [PATCH 480/870] version and copyright info --- EXRAIL2.cpp | 2 +- EXRAIL2.h | 1 + EXRAIL2MacroReset.h | 4 ++-- EXRAILMacros.h | 1 + GITHUB_SHA.h | 2 +- version.h | 3 ++- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 122bf4aab..94d748139 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1,6 +1,6 @@ /* * © 2021 Neil McKechnie - * © 2021-2022 Harald Barth + * © 2021-2023 Harald Barth * © 2020-2022 Chris Harlow * All rights reserved. * diff --git a/EXRAIL2.h b/EXRAIL2.h index 7acb839f5..12bca3be9 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -1,6 +1,7 @@ /* * © 2021 Neil McKechnie * © 2020-2022 Chris Harlow + * © 2023 Harald Barth * All rights reserved. * * This file is part of CommandStation-EX diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index e59d1c909..e5f3ba01d 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -1,6 +1,6 @@ /* - * © 2021-2022 Chris Harlow - * © 2020,2021 Chris Harlow. All rights reserved. + * © 2020-2022 Chris Harlow. All rights reserved. + * © 2023 Harald Barth * * This file is part of CommandStation-EX * diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 25bb29973..fc05e6b9a 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -1,6 +1,7 @@ /* * © 2021 Neil McKechnie * © 2020-2022 Chris Harlow + * © 2023 Harald Barth * All rights reserved. * * This file is part of CommandStation-EX diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index d6c973888..5d48ec711 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202212051450Z" +#define GITHUB_SHA "devel-202301271500Z" diff --git a/version.h b/version.h index 0e142889c..f55e502f2 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.10pre1" +#define VERSION "4.2.11pre1" +// 4.2.11 Exrail IFLOCO feature added // 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted // IO_EXIOExpander.h input speed optimisation // 4.2.9 duinoNodes support From 9e5d780c140e532c83d543088df212358e5f7bef Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 27 Jan 2023 18:42:26 +0100 Subject: [PATCH 481/870] Bugfix for issue #299 TurnoutDescription NULL --- DCCEXParser.cpp | 19 ++++++++++++------- GITHUB_SHA.h | 2 +- version.h | 3 ++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 5c0cb84bc..e55489553 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -626,14 +626,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else { // Turnout * t=Turnout::get(id); if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id); - else StringFormatter::send(stream, F(" %d %c \"%S\""), - id,t->isThrown()?'T':'C', + else { + const FSH *tdesc = NULL; #ifdef EXRAIL_ACTIVE - RMFT2::getTurnoutDescription(id) -#else - F("") -#endif - ); + tdesc = RMFT2::getTurnoutDescription(id); +#endif + if (tdesc == NULL) + StringFormatter::send(stream, F(" %d %c"), + id,t->isThrown()?'T':'C'); + else + StringFormatter::send(stream, F(" %d %c \"%S\""), + id,t->isThrown()?'T':'C', + tdesc); + } } StringFormatter::send(stream, F(">\n")); return; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 5d48ec711..ba13dba3e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202301271500Z" +#define GITHUB_SHA "devel-202301271742Z" diff --git a/version.h b/version.h index f55e502f2..4afa18688 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.11pre1" +#define VERSION "4.2.12pre1" +// 4.2.12 Bugfix for issue #299 TurnoutDescription NULL // 4.2.11 Exrail IFLOCO feature added // 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted // IO_EXIOExpander.h input speed optimisation From c91d66549c11212979eeea17e380dbd36bf982bd Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 27 Jan 2023 19:42:55 +0100 Subject: [PATCH 482/870] Remove warnings --- EXRAIL2.cpp | 2 +- IO_EXIOExpander.h | 3 ++- WiThrottle.cpp | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 94d748139..e925d9608 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -748,7 +748,7 @@ void RMFT2::loop2() { break; case OPCODE_IFLOCO: // do if the loco is the active one - skipIf=loco!=operand; + skipIf=loco!=(uint16_t)operand; // bad luck if someone enters negative loco numbers into EXRAIL break; case OPCODE_IFNOT: // do next operand if sensor not set diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index cce101b33..d54feacd8 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -135,6 +135,7 @@ class EXIOExpander : public IODevice { } void _loop(unsigned long currentMicros) override { + (void)currentMicros; // remove warning _commandBuffer[0] = EXIORDD; I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); _commandBuffer[0] = EXIORDAN; @@ -209,4 +210,4 @@ class EXIOExpander : public IODevice { }; }; -#endif \ No newline at end of file +#endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 920ebd677..69c5d87b1 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -525,7 +525,7 @@ void WiThrottle::sendTurnouts(Print* stream) { } void WiThrottle::sendRoster(Print* stream) { rosterSent=true; - #ifdef EXRAIL_ACTIVE +#ifdef EXRAIL_ACTIVE StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); for (int16_t r=0;r=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey); } -} \ No newline at end of file +} From 6cc66e26c127ac3beabbec48f0fec35c6386a8ac Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 28 Jan 2023 13:58:55 +0800 Subject: [PATCH 483/870] Initial STM32F4xx fast ADC read implementation --- CommandStation-EX.ino | 3 + DCCTimer.h | 14 +++-- DCCTimerSAMD.cpp | 19 +------ DCCTimerSTM32.cpp | 129 ++++++++++++++++++++++++++++++++++++++++-- DCCWaveform.cpp | 1 - 5 files changed, 136 insertions(+), 30 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index f003ef626..67a7e586b 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -99,6 +99,9 @@ void setup() // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); + // As the setup of a motor shield may require a read of the current sense input from the ADC, + // let's make sure to initialise the ADCee class! + ADCee::begin(); // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) // Standard supported devices have pre-configured macros but custome hardware installations require diff --git a/DCCTimer.h b/DCCTimer.h index ed87e1fd1..75be8cd97 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,5 +1,5 @@ /* - * © 2022 Paul M. Antoine + * © 2022-2023 Paul M. Antoine * © 2021 Mike S * © 2021-2022 Harald Barth * © 2021 Fred Decker @@ -102,9 +102,14 @@ class DCCTimer { // that an offset can be initialized. class ADCee { public: - // init does add the pin to the list of scanned pins (if this + // begin is called for any setup that must be done before + // **init** can be called. On some architectures this involves ADC + // initialisation and clock routing, sampling times etc. + static void begin(); + // init adds the pin to the list of scanned pins (if this // platform's implementation scans pins) and returns the first - // read value. It is called before the regular scan is started. + // read value (which is why it required begin to have been called first!) + // It must be called before the regular scan is started. static int init(uint8_t pin); // read does read the pin value from the scanned cache or directly // if this is a platform that does not scan. fromISR is a hint if @@ -117,9 +122,6 @@ class ADCee { // On platforms that scan, it is called from waveform ISR // only on a regular basis. static void scan(); - // begin is called for any setup that must be done before - // scan can be called. - static void begin(); // bit array of used pins (max 16) static uint16_t usedpins; // cached analog values (malloc:ed to actual number of ADC channels) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 7f1016907..463cc647e 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -168,23 +168,6 @@ int ADCee::init(uint8_t pin) { if (id > NUM_ADC_INPUTS) return -1023; - // Dummy read using Arduino library - analogReadResolution(12); - value = analogRead(pin); - - // Reconfigure ADC - ADC->CTRLA.bit.ENABLE = 0; // disable ADC - while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - - ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits - ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16 - ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible - ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time - ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 - ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0 - ADC->CTRLA.bit.ENABLE = 1; // enable ADC - while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - // Permanently configure SAMD IO MUX for that pin pinPeripheral(pin, PIO_ANALOG); ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input @@ -205,9 +188,11 @@ int ADCee::init(uint8_t pin) { return value; } + int16_t ADCee::ADCmax() { return 4095; } + /* * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 0f60507ef..b1a90ac03 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -131,31 +131,148 @@ void DCCTimer::reset() { while(true) {}; } +#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS + +// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs! +uint16_t ADCee::usedpins = 0; +int * ADCee::analogvals = NULL; +uint32_t * analogchans = NULL; +bool adc1configured = false; + int16_t ADCee::ADCmax() { return 4095; } int ADCee::init(uint8_t pin) { - return analogRead(pin); + uint id = pin - A0; + int value = 0; + PinName stmpin = digitalPin[analogInputPin[id]]; + uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32 + uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!) + GPIO_TypeDef * gpioBase; + + // Port config - find which port we're on and power it up + switch(stmgpio) { + case 0x00: + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA + gpioBase = GPIOA; + break; + case 0x01: + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB + gpioBase = GPIOB; + break; + case 0x02: + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC + gpioBase = GPIOC; + break; + } + + // Set pin mux mode to analog input + gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode + + // Set the sampling rate for that analog input + if (adcchan < 10) + ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us + else + ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us + + // Read the inital ADC value for this analog input + ADC1->SQR3 = adcchan; // 1st conversion in regular sequence + ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART + while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete + value = ADC1->DR; // Read value from register + + if (analogvals == NULL) + { + analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t)); + } + analogvals[id] = value; // Store sampled value + analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin + usedpins |= (1 << id); // This pin is now ready + + return value; } + /* * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ int ADCee::read(uint8_t pin, bool fromISR) { - int current; - if (!fromISR) noInterrupts(); - current = analogRead(pin); - if (!fromISR) interrupts(); - return current; + uint8_t id = pin - A0; + // Was this pin initialised yet? + if ((usedpins & (1<SR & (1 << 1))) + return; // no result, continue to wait + // found value + analogvals[id] = ADC1->DR; + // advance at least one track + // for scope debug TrackManager::track[1]->setBrake(0); + waiting = false; + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + if (!waiting) { + if (usedpins == 0) // otherwise we would loop forever + return; + // look for a valid track to sample or until we are around + while (true) { + if (mask & usedpins) { + // start new ADC aquire on id + ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence + ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART + // for scope debug TrackManager::track[1]->setBrake(1); + waiting = true; + return; + } + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + } } +#pragma GCC pop_options void ADCee::begin() { noInterrupts(); + //ADC1 config sequence + // TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family + RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8) + // Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms + ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8 + ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8) + ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00) + ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion + ADC1->CR2 &= ~(1 << 1); //Single conversion + ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0 + ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register + ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register + ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register + ADC1->CR2 |= (1 << 0); // Switch on ADC1 interrupts(); } #endif \ No newline at end of file diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index f807c34d0..e06564801 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -62,7 +62,6 @@ const bool signalTransform[]={ /* WAVE_PENDING (should not happen) -> */ LOW}; void DCCWaveform::begin() { - ADCee::begin(); DCCTimer::begin(DCCWaveform::interruptHandler); } From be33bafa6676f8522fffbb5669389d57264dd39c Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 28 Jan 2023 14:39:00 +0800 Subject: [PATCH 484/870] Fixed logic of ADC ready --- DCCTimerSTM32.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index b1a90ac03..df66c986a 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -96,7 +96,7 @@ void DCCTimer::clearPWM() { void DCCTimer::getSimulatedMacAddress(byte mac[6]) { volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10; volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14; - volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18; + // volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18; volatile uint32_t m1 = *serno1; volatile uint32_t m2 = *serno2; @@ -172,9 +172,9 @@ int ADCee::init(uint8_t pin) { // Set the sampling rate for that analog input if (adcchan < 10) - ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us + ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles else - ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us + ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles // Read the inital ADC value for this analog input ADC1->SQR3 = adcchan; // 1st conversion in regular sequence @@ -219,7 +219,7 @@ void ADCee::scan() { if (waiting) { // look if we have a result - if ((ADC1->SR & (1 << 1))) + if (!(ADC1->SR & (1 << 1))) return; // no result, continue to wait // found value analogvals[id] = ADC1->DR; From 2a7588b1b542e23837806348e4a808395c7d8e73 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 28 Jan 2023 19:07:59 +0100 Subject: [PATCH 485/870] jT answer should contain empty string --- DCCEXParser.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e55489553..4070049ad 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -632,12 +632,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) tdesc = RMFT2::getTurnoutDescription(id); #endif if (tdesc == NULL) - StringFormatter::send(stream, F(" %d %c"), - id,t->isThrown()?'T':'C'); - else - StringFormatter::send(stream, F(" %d %c \"%S\""), - id,t->isThrown()?'T':'C', - tdesc); + tdesc = F(""); + StringFormatter::send(stream, F(" %d %c \"%S\""), + id,t->isThrown()?'T':'C', + tdesc); } } StringFormatter::send(stream, F(">\n")); From 290d878063beab9479cdfe87e58178a8da0b4b36 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 28 Jan 2023 19:09:16 +0100 Subject: [PATCH 486/870] version --- GITHUB_SHA.h | 2 +- version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index ba13dba3e..6b17a12f7 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202301271742Z" +#define GITHUB_SHA "devel-202301281808Z" diff --git a/version.h b/version.h index 4afa18688..10f11f9cc 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.12pre1" +#define VERSION "4.2.12pre2" // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL // 4.2.11 Exrail IFLOCO feature added // 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted From a17c02444d4157b8ce9a657af43b1663a9ddcefc Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 487/870] Refactored, analogue tested --- IO_EXIOExpander.h | 97 ++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d54feacd8..724ea42d9 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -54,22 +54,18 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _numDigitalPins = numDigitalPins; - _numAnaloguePins = numAnaloguePins; - _digitalPinBytes = (numDigitalPins+7)/8; - _analoguePinBytes = numAnaloguePins * 2; + _digitalPinBytes = (nPins+7)/8; _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); - _analogueInputStates=(byte*) calloc(_analoguePinBytes,1); addDevice(this); } @@ -77,20 +73,26 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { - _digitalOutBuffer[0] = EXIOINIT; - _digitalOutBuffer[1] = _numDigitalPins; - _digitalOutBuffer[2] = _numAnaloguePins; - // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); - if (_commandBuffer[0] != EXIORDY) { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; return; } + // We now need to retrieve the analogue pin map + _command1Buffer[0] = EXIOINITA; + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline - // Using digital in buffer in reverse to save RAM - _commandBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); + _command1Buffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -105,13 +107,10 @@ class EXIOExpander : public IODevice { } } + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); - return false; - } bool pullup = params[0]; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIODPUP; @@ -121,37 +120,39 @@ class EXIOExpander : public IODevice { return true; } - // We only use this to detect incorrect use of analogue pins + // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); - return false; - } int pin = vpin - _firstVpin; - _analogueOutBuffer[0] = EXIOENAN; - _analogueOutBuffer[1] = pin; - I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + _command2Buffer[0] = EXIOENAN; + _command2Buffer[1] = pin; + I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } + // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning - _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); - _commandBuffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } + // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) return false; - int pin = vpin - _firstVpin - _numDigitalPins; - uint8_t _pinLSBByte = pin * 2; + int pin = vpin - _firstVpin; + uint8_t _pinLSBByte; + for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { + if (_analoguePinMap[aPin] == pin) { + _pinLSBByte = aPin * 2; + } + } uint8_t _pinMSBByte = _pinLSBByte + 1; return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } + // Obtain the correct digital input value int _read(VPIN vpin) override { - if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); @@ -159,7 +160,6 @@ class EXIOExpander : public IODevice { } void _write(VPIN vpin, int value) override { - if (vpin >= _firstVpin + _numDigitalPins) return; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; @@ -168,25 +168,14 @@ class EXIOExpander : public IODevice { } void _display() override { - int _firstAnalogue, _lastAnalogue; - if (_numAnaloguePins == 0) { - _firstAnalogue = 0; - _lastAnalogue = 0; - } else { - _firstAnalogue = _firstVpin + _numDigitalPins; - _lastAnalogue = _firstVpin + _nPins - 1; - } - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, - _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, - _numAnaloguePins, _firstAnalogue, _lastAnalogue, + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; - uint8_t _numDigitalPins; uint8_t _numAnaloguePins; - byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -196,7 +185,10 @@ class EXIOExpander : public IODevice { byte* _analogueInputStates; uint8_t _digitalPinBytes = 0; uint8_t _analoguePinBytes = 0; - byte _commandBuffer[1]; + byte _command1Buffer[1]; + byte _command2Buffer[2]; + byte _receive2Buffer[2]; + uint8_t* _analoguePinMap; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure @@ -207,6 +199,7 @@ class EXIOExpander : public IODevice { EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input EXIOENAN = 0xE7, // Flag eo enable an analogue pin + EXIOINITA = 0xE8, // Flag we're receiving analogue pin info }; }; From 212bf8d80e34c7b3e96e0925dc43911df3fc2212 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 29 Jan 2023 08:13:52 +0100 Subject: [PATCH 488/870] Broadcast power for again --- DCCEXParser.cpp | 1 + GITHUB_SHA.h | 2 +- version.h | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 4070049ad..ef11763cb 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -510,6 +510,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 's': // StringFormatter::send(stream, F("\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); + CommandDistributor::broadcastPower(); // is the only "get power status" command we have Turnout::printAll(stream); //send all Turnout states Output::printAll(stream); //send all Output states Sensor::printAll(stream); //send all Sensor states diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6b17a12f7..dc9ffe989 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202301281808Z" +#define GITHUB_SHA "devel-202301290713Z" diff --git a/version.h b/version.h index 10f11f9cc..0ad586f4d 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.12pre2" +#define VERSION "4.2.13" +// 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL // 4.2.11 Exrail IFLOCO feature added // 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted From d8a1bcaf34b6a66b015045e3e02ff3e62876e470 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 489/870] Refactored, analogue tested --- IO_EXIOExpander.h | 97 ++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d54feacd8..724ea42d9 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -54,22 +54,18 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _numDigitalPins = numDigitalPins; - _numAnaloguePins = numAnaloguePins; - _digitalPinBytes = (numDigitalPins+7)/8; - _analoguePinBytes = numAnaloguePins * 2; + _digitalPinBytes = (nPins+7)/8; _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); - _analogueInputStates=(byte*) calloc(_analoguePinBytes,1); addDevice(this); } @@ -77,20 +73,26 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { - _digitalOutBuffer[0] = EXIOINIT; - _digitalOutBuffer[1] = _numDigitalPins; - _digitalOutBuffer[2] = _numAnaloguePins; - // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); - if (_commandBuffer[0] != EXIORDY) { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; return; } + // We now need to retrieve the analogue pin map + _command1Buffer[0] = EXIOINITA; + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline - // Using digital in buffer in reverse to save RAM - _commandBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); + _command1Buffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -105,13 +107,10 @@ class EXIOExpander : public IODevice { } } + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); - return false; - } bool pullup = params[0]; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIODPUP; @@ -121,37 +120,39 @@ class EXIOExpander : public IODevice { return true; } - // We only use this to detect incorrect use of analogue pins + // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); - return false; - } int pin = vpin - _firstVpin; - _analogueOutBuffer[0] = EXIOENAN; - _analogueOutBuffer[1] = pin; - I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + _command2Buffer[0] = EXIOENAN; + _command2Buffer[1] = pin; + I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } + // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning - _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); - _commandBuffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } + // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) return false; - int pin = vpin - _firstVpin - _numDigitalPins; - uint8_t _pinLSBByte = pin * 2; + int pin = vpin - _firstVpin; + uint8_t _pinLSBByte; + for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { + if (_analoguePinMap[aPin] == pin) { + _pinLSBByte = aPin * 2; + } + } uint8_t _pinMSBByte = _pinLSBByte + 1; return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } + // Obtain the correct digital input value int _read(VPIN vpin) override { - if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); @@ -159,7 +160,6 @@ class EXIOExpander : public IODevice { } void _write(VPIN vpin, int value) override { - if (vpin >= _firstVpin + _numDigitalPins) return; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; @@ -168,25 +168,14 @@ class EXIOExpander : public IODevice { } void _display() override { - int _firstAnalogue, _lastAnalogue; - if (_numAnaloguePins == 0) { - _firstAnalogue = 0; - _lastAnalogue = 0; - } else { - _firstAnalogue = _firstVpin + _numDigitalPins; - _lastAnalogue = _firstVpin + _nPins - 1; - } - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, - _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, - _numAnaloguePins, _firstAnalogue, _lastAnalogue, + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; - uint8_t _numDigitalPins; uint8_t _numAnaloguePins; - byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -196,7 +185,10 @@ class EXIOExpander : public IODevice { byte* _analogueInputStates; uint8_t _digitalPinBytes = 0; uint8_t _analoguePinBytes = 0; - byte _commandBuffer[1]; + byte _command1Buffer[1]; + byte _command2Buffer[2]; + byte _receive2Buffer[2]; + uint8_t* _analoguePinMap; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure @@ -207,6 +199,7 @@ class EXIOExpander : public IODevice { EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input EXIOENAN = 0xE7, // Flag eo enable an analogue pin + EXIOINITA = 0xE8, // Flag we're receiving analogue pin info }; }; From 95945eab4c7aedc132bc6b6226ce8ceb447cac8b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 29 Jan 2023 08:50:19 +0100 Subject: [PATCH 490/870] version bump --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index dc9ffe989..919895a75 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202301290713Z" +#define GITHUB_SHA "devel-202301290750Z" diff --git a/version.h b/version.h index 0ad586f4d..67886459a 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.13" +#define VERSION "4.2.14" +// 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL // 4.2.11 Exrail IFLOCO feature added From 28caa9e8d3cc4c067bd92c75a8c3bb2da4cc84b6 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 19:26:33 +1000 Subject: [PATCH 491/870] Brief PWM start --- IO_EXIOExpander.h | 76 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 724ea42d9..f284bc17d 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -1,5 +1,5 @@ /* - * © 2021, Peter Cole. All rights reserved. + * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation * @@ -26,19 +26,14 @@ * (Note the device driver is included by default) * * void halSetup() { -* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); -* EXIOExpander::create(800, 18, 0x65, 12, 8); +* // EXIOExpander::create(vpin, num_vpins, i2c_address); +* EXIOExpander::create(800, 18, 0x65); * } * -* Note when defining the number of digital and analogue pins, there is no way to sanity check -* this from the device driver, and it is up to the user to define the correct values here. -* -* All pins available on the EX-IOExpander device must be accounted for. -* -* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will -* populate the first part of the specified vpin range, with the analogue pins populating the -* last part of the vpin range. -* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7). +* All pins on an EX-IOExpander device are allocated according to the pin map for the specific +* device in use. There is no way for the device driver to sanity check pins are used for the +* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used +* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output). */ #ifndef IO_EX_IOEXPANDER_H @@ -76,12 +71,15 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive3Buffer[1]; + _numPWMPins = _receive3Buffer[2]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + _servoData = (struct ServoData*) calloc(_numPWMPins, 14); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -109,15 +107,24 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { - if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - bool pullup = params[0]; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + if (configType == CONFIGURE_INPUT) { + bool pullup = params[0]; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } else if (configType == CONFIGURE_SERVO) { + DIAG(F("Configure servo at pin %d"), (int)pin); + for (int i = 0; i < paramCount; i++) { + DIAG(F("Param %d is %x"), (int)i, params[i]); + } + return true; + } else { + return false; + } } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -175,7 +182,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins; + uint8_t _numAnaloguePins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -187,8 +194,29 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; + uint8_t _numPWMPins = 0; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + } ServoData; // 14 bytes per element, i.e. per pin in use + + struct ServoData* _servoData; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const byte FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From 1f5eafbccac97e9ef5d1991417a1a2881b6bf7f9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 27 Jan 2023 18:42:26 +0100 Subject: [PATCH 492/870] Bugfix for issue #299 TurnoutDescription NULL --- DCCEXParser.cpp | 19 ++++++++++++------- version.h | 3 ++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b26260015..2cfaafd4b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -599,14 +599,19 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else { // Turnout * t=Turnout::get(id); if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id); - else StringFormatter::send(stream, F(" %d %c \"%S\""), - id,t->isThrown()?'T':'C', + else { + const FSH *tdesc = NULL; #ifdef EXRAIL_ACTIVE - RMFT2::getTurnoutDescription(id) -#else - F("") -#endif - ); + tdesc = RMFT2::getTurnoutDescription(id); +#endif + if (tdesc == NULL) + StringFormatter::send(stream, F(" %d %c"), + id,t->isThrown()?'T':'C'); + else + StringFormatter::send(stream, F(" %d %c \"%S\""), + id,t->isThrown()?'T':'C', + tdesc); + } } StringFormatter::send(stream, F(">\n")); return; diff --git a/version.h b/version.h index 028999c22..947b3631f 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.1.3" +#define VERSION "4.1.4" +// 4.1.4 Bugfix for issue #299 TurnoutDescription NULL // 4.1.3 Bugfix: Ethernet init order // 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level // 4.1.1 Bugfix: preserve turnout format From 7e4f9eb0e18d0936094f4e5aaf4140913e350e7f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 28 Jan 2023 19:07:59 +0100 Subject: [PATCH 493/870] jT answer should contain empty string --- DCCEXParser.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 2cfaafd4b..05b300585 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -605,12 +605,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) tdesc = RMFT2::getTurnoutDescription(id); #endif if (tdesc == NULL) - StringFormatter::send(stream, F(" %d %c"), - id,t->isThrown()?'T':'C'); - else - StringFormatter::send(stream, F(" %d %c \"%S\""), - id,t->isThrown()?'T':'C', - tdesc); + tdesc = F(""); + StringFormatter::send(stream, F(" %d %c \"%S\""), + id,t->isThrown()?'T':'C', + tdesc); } } StringFormatter::send(stream, F(">\n")); From 7f19a92d2a0ca04b68af756329d5781e95cf1424 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 494/870] Refactored, analogue tested --- IO_EXIOExpander.h | 97 ++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d54feacd8..724ea42d9 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -54,22 +54,18 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _numDigitalPins = numDigitalPins; - _numAnaloguePins = numAnaloguePins; - _digitalPinBytes = (numDigitalPins+7)/8; - _analoguePinBytes = numAnaloguePins * 2; + _digitalPinBytes = (nPins+7)/8; _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); - _analogueInputStates=(byte*) calloc(_analoguePinBytes,1); addDevice(this); } @@ -77,20 +73,26 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { - _digitalOutBuffer[0] = EXIOINIT; - _digitalOutBuffer[1] = _numDigitalPins; - _digitalOutBuffer[2] = _numAnaloguePins; - // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); - if (_commandBuffer[0] != EXIORDY) { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; return; } + // We now need to retrieve the analogue pin map + _command1Buffer[0] = EXIOINITA; + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline - // Using digital in buffer in reverse to save RAM - _commandBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); + _command1Buffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -105,13 +107,10 @@ class EXIOExpander : public IODevice { } } + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); - return false; - } bool pullup = params[0]; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIODPUP; @@ -121,37 +120,39 @@ class EXIOExpander : public IODevice { return true; } - // We only use this to detect incorrect use of analogue pins + // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); - return false; - } int pin = vpin - _firstVpin; - _analogueOutBuffer[0] = EXIOENAN; - _analogueOutBuffer[1] = pin; - I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + _command2Buffer[0] = EXIOENAN; + _command2Buffer[1] = pin; + I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } + // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning - _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); - _commandBuffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } + // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) return false; - int pin = vpin - _firstVpin - _numDigitalPins; - uint8_t _pinLSBByte = pin * 2; + int pin = vpin - _firstVpin; + uint8_t _pinLSBByte; + for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { + if (_analoguePinMap[aPin] == pin) { + _pinLSBByte = aPin * 2; + } + } uint8_t _pinMSBByte = _pinLSBByte + 1; return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } + // Obtain the correct digital input value int _read(VPIN vpin) override { - if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); @@ -159,7 +160,6 @@ class EXIOExpander : public IODevice { } void _write(VPIN vpin, int value) override { - if (vpin >= _firstVpin + _numDigitalPins) return; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; @@ -168,25 +168,14 @@ class EXIOExpander : public IODevice { } void _display() override { - int _firstAnalogue, _lastAnalogue; - if (_numAnaloguePins == 0) { - _firstAnalogue = 0; - _lastAnalogue = 0; - } else { - _firstAnalogue = _firstVpin + _numDigitalPins; - _lastAnalogue = _firstVpin + _nPins - 1; - } - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, - _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, - _numAnaloguePins, _firstAnalogue, _lastAnalogue, + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; - uint8_t _numDigitalPins; uint8_t _numAnaloguePins; - byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -196,7 +185,10 @@ class EXIOExpander : public IODevice { byte* _analogueInputStates; uint8_t _digitalPinBytes = 0; uint8_t _analoguePinBytes = 0; - byte _commandBuffer[1]; + byte _command1Buffer[1]; + byte _command2Buffer[2]; + byte _receive2Buffer[2]; + uint8_t* _analoguePinMap; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure @@ -207,6 +199,7 @@ class EXIOExpander : public IODevice { EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input EXIOENAN = 0xE7, // Flag eo enable an analogue pin + EXIOINITA = 0xE8, // Flag we're receiving analogue pin info }; }; From 1d27eb67e41b3a99b4438f5d34b11cf5c9ad420b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 19:26:33 +1000 Subject: [PATCH 495/870] Brief PWM start --- IO_EXIOExpander.h | 76 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 724ea42d9..f284bc17d 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -1,5 +1,5 @@ /* - * © 2021, Peter Cole. All rights reserved. + * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation * @@ -26,19 +26,14 @@ * (Note the device driver is included by default) * * void halSetup() { -* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); -* EXIOExpander::create(800, 18, 0x65, 12, 8); +* // EXIOExpander::create(vpin, num_vpins, i2c_address); +* EXIOExpander::create(800, 18, 0x65); * } * -* Note when defining the number of digital and analogue pins, there is no way to sanity check -* this from the device driver, and it is up to the user to define the correct values here. -* -* All pins available on the EX-IOExpander device must be accounted for. -* -* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will -* populate the first part of the specified vpin range, with the analogue pins populating the -* last part of the vpin range. -* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7). +* All pins on an EX-IOExpander device are allocated according to the pin map for the specific +* device in use. There is no way for the device driver to sanity check pins are used for the +* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used +* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output). */ #ifndef IO_EX_IOEXPANDER_H @@ -76,12 +71,15 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive3Buffer[1]; + _numPWMPins = _receive3Buffer[2]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + _servoData = (struct ServoData*) calloc(_numPWMPins, 14); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -109,15 +107,24 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { - if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - bool pullup = params[0]; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + if (configType == CONFIGURE_INPUT) { + bool pullup = params[0]; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } else if (configType == CONFIGURE_SERVO) { + DIAG(F("Configure servo at pin %d"), (int)pin); + for (int i = 0; i < paramCount; i++) { + DIAG(F("Param %d is %x"), (int)i, params[i]); + } + return true; + } else { + return false; + } } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -175,7 +182,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins; + uint8_t _numAnaloguePins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -187,8 +194,29 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; + uint8_t _numPWMPins = 0; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + } ServoData; // 14 bytes per element, i.e. per pin in use + + struct ServoData* _servoData; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const byte FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From 291a331f3ee49bdfef51342953f0e8d6cad0c6f4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 00:36:57 +0000 Subject: [PATCH 496/870] Fix read operations on I2CManager for SAMD --- I2CManager_SAMD.h | 49 +++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index cd57507d6..38ee99613 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -1,6 +1,6 @@ /* * © 2022 Paul M Antoine - * © 2021, Neil McKechnie + * © 2023, Neil McKechnie * All rights reserved. * * This file is part of CommandStation-EX @@ -107,8 +107,8 @@ void I2CManagerClass::I2C_init() s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* | SERCOM_I2CM_CTRLA_SCLSM*/ ; - // Enable Smart mode and Quick Command - s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN | SERCOM_I2CM_CTRLB_QCEN; + // Enable Smart mode (but not Quick Command) + s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; #if defined(I2C_USE_INTERRUPTS) // Setting NVIC @@ -141,7 +141,7 @@ void I2CManagerClass::I2C_init() PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg = PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg = - PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; + PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; } /*************************************************************************** @@ -152,18 +152,21 @@ void I2CManagerClass::I2C_sendStart() { bytesToReceive = currentRequest->readLen; // We may have initiated a stop bit before this without waiting for it. - // Wait for stop bit to be sent before sending start. - while (s->I2CM.STATUS.bit.BUSSTATE == 0x2); + // However, the state machine ensures that the start bit isn't sent + // until the stop bit is complete. + //while (s->I2CM.STATUS.bit.BUSSTATE == 0x2); // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { + // Wait while the I2C bus is BUSY + //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); // Send start and address with read/write flag or'd in s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { // Wait while the I2C bus is BUSY - while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; } } @@ -203,14 +206,11 @@ void I2CManagerClass::I2C_handleInterrupt() { state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { // Acked, so send next byte - if (currentRequest->operation == OPERATION_SEND_P) - s->I2CM.DATA.bit.DATA = GETFLASH(currentRequest->writeBuffer + (txCount++)); - else - s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition. I2C_sendStop(); @@ -218,25 +218,16 @@ void I2CManagerClass::I2C_handleInterrupt() { } } else if (s->I2CM.INTFLAG.bit.SB) { // Master read completed without errors - if (bytesToReceive) { + if (bytesToReceive == 1) { + s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte + I2C_sendStop(); // send stop + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive = 0; + state = I2C_STATUS_OK; // done + } else if (bytesToReceive) { + s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive--; - } else { - // Buffer full, issue nack/stop - s->I2CM.CTRLB.bit.ACKACT = 1; - I2C_sendStop(); - state = I2C_STATUS_OK; - } - if (bytesToReceive) { - // PMA - I think Smart Mode means we have nothing to do... - // More bytes to receive, issue ack and start another read - } - else - { - // Transaction finished, issue NACK and STOP. - s->I2CM.CTRLB.bit.ACKACT = 1; - I2C_sendStop(); - state = I2C_STATUS_OK; } } } From a18c06d021ce989ef3e957698488c5a9df40ad9a Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 31 Jan 2023 19:29:39 +1000 Subject: [PATCH 497/870] Cleaned up PWM start --- IO_EXIOExpander.h | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f284bc17d..34ece1b7f 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -78,7 +78,6 @@ class EXIOExpander : public IODevice { _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - _servoData = (struct ServoData*) calloc(_numPWMPins, 14); } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); @@ -198,26 +197,6 @@ class EXIOExpander : public IODevice { uint8_t* _analoguePinMap; uint8_t _numPWMPins = 0; - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - } ServoData; // 14 bytes per element, i.e. per pin in use - - struct ServoData* _servoData; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const byte FLASH _bounceProfile[30]; - - const unsigned int refreshInterval = 50; // refresh every 50ms - enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup From 1073e142e6fe40e503cf4292a6dc5e4e9884615d Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 31 Jan 2023 19:32:12 +1000 Subject: [PATCH 498/870] Add new drivers --- IODevice.h | 5 +- IO_PCA9685_basic.h | 149 ++++++++++++++++++++++++ IO_Servo.h | 277 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 IO_PCA9685_basic.h create mode 100644 IO_Servo.h diff --git a/IODevice.h b/IODevice.h index 72beb9e73..e7906c601 100644 --- a/IODevice.h +++ b/IODevice.h @@ -242,11 +242,12 @@ class IODevice { // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); + private: // Method to check whether the vpin corresponds to this device bool owns(VPIN vpin); - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; diff --git a/IO_PCA9685_basic.h b/IO_PCA9685_basic.h new file mode 100644 index 000000000..4f809aaaf --- /dev/null +++ b/IO_PCA9685_basic.h @@ -0,0 +1,149 @@ +/* + * © 2023, 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 . + */ + +/* + * This driver performs the basic interface between the HAL and an + * I2C-connected PCA9685 16-channel PWM module. When requested, it + * commands the device to set the PWM mark-to-period ratio accordingly. + * The call to IODevice::writeAnalogue(vpin, value) specifies the + * desired value in the range 0-4095 (0=0% and 4095=100%). + */ + +#ifndef PCA9685_BASIC_H +#define PCA9685_BASIC_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +/* + * IODevice subclass for PCA9685 16-channel PWM module. + */ + +class PCA9685_basic : public IODevice { +public: + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685_basic(firstVpin, nPins, I2CAddress); + } + +private: + + // structures for setting up non-blocking writes to servo controller + I2CRB requestBlock; + uint8_t outputBuffer[5]; + + // REGISTER ADDRESSES + const byte PCA9685_MODE1=0x00; // Mode Register + const byte PCA9685_FIRST_SERVO=0x06; /** low byte first servo register ON*/ + const byte PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ + // MODE1 bits + const byte MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ + const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ + const byte MODE1_RESTART=0x80; /**< Restart enabled */ + + const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); + const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed + + // Constructor + PCA9685_basic(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + _firstVpin = firstVpin; + _nPins = min(nPins, 16); + _I2CAddress = I2CAddress; + addDevice(this); + + // Initialise structure used for setting pulse rate + requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C + // In reality, other devices including the Arduino will limit + // the clock speed to a lower rate. + + // Initialise I/O module here. + if (I2CManager.exists(_I2CAddress)) { + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); + // In theory, we should wait 500us before sending any other commands to each device, to allow + // the PWM oscillator to get running. However, we don't do any specific wait, as there's + // plenty of other stuff to do before we will send a command. + #if defined(DIAG_IO) + _display(); + #endif + } else + _deviceState = DEVSTATE_FAILED; + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + writeDevice(pin, value); + } + + // Display details of this device. + void _display() override { + 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("")); + } + + // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value + // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. + void writeDevice(uint8_t pin, int value) { + #ifdef DIAG_IO + DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); + #endif + // Wait for previous request to complete + 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); + } + } + + // Internal helper function for this device + static void writeRegister(byte address, byte reg, byte value) { + I2CManager.write(address, 2, reg, value); + } + +}; + +#endif \ No newline at end of file diff --git a/IO_Servo.h b/IO_Servo.h new file mode 100644 index 000000000..bd475fbc9 --- /dev/null +++ b/IO_Servo.h @@ -0,0 +1,277 @@ +/* + * © 2023, 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_SERVO_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +class Servo : IODevice { + +public: + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin) { + if (checkNoOverlap(firstVpin, nPins)) new Servo(firstVpin, nPins, firstSlavePin); + } + +private: + VPIN _firstSlavePin; + IODevice *_slaveDevice = NULL; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + + + // Configure a port on the Servo. + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { + if (configType != CONFIGURE_SERVO) return false; + if (paramCount != 5) return false; + #ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); + #endif + + int8_t pin = vpin - _firstVpin; + VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + + if (state != -1) { + // Position servo to initial state + IODevice::writeAnalogue(slavePin, state ? s->activePosition : s->inactivePosition, 0, 0); + } + return true; + } + + // Constructor + Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin) { + _firstVpin = firstVpin; + _nPins = (nPins > 16) ? 16 : nPins; + _firstSlavePin = firstSlavePin; + + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) + _servoData[i] = NULL; + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + // Get reference to slave device to make accesses faster. + _slaveDevice = this->findDevice(_firstSlavePin); + // Check firstSlavePin is actually allocated to a device + if (!_slaveDevice) { + DIAG(F("Servo: Slave device not found on pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + // Check that the last slave pin is allocated to the same device. + if (_slaveDevice != this->findDevice(_firstSlavePin+_nPins-1)) { + DIAG(F("Servo: Slave device does not cover all pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + #if defined(DIAG_IO) + _display(); + #endif + } + + // Device-specific write function, invoked from IODevice::write(). + // For this function, the configured profile is used. + void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; + #ifdef DIAG_IO + DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + #endif + int pin = vpin - _firstVpin; + VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + if (value) value = 1; + + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // Profile is as follows: + // Bit 7: 0=Set PWM to 0% to power off servo motor when finished + // 1=Keep PWM pulses on (better when using PWM to drive an LED) + // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) + // 1 (Fast) Move servo in 0.5 seconds + // 2 (Medium) Move servo in 1.0 seconds + // 3 (Slow) Move servo in 2.0 seconds + // 4 (Bounce) Servo 'bounces' at extremes. + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + // _read returns true if the device is currently in executing an animation, + // changing the output over a period of time. + int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + int pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) + return false; // No structure means no animation! + else + return (s->stepNumber < s->numSteps); + } + + void _loop(unsigned long currentMicros) override { + if (_deviceState == DEVSTATE_FAILED) return; + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } + delayUntil(currentMicros + refreshInterval * 1000UL); + } + + // Private function to reposition servo + // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command + _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off PWM to prevent annoying servo buzz + _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } + } + + // Display details of this device. + void _display() override { + DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } +}; + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +const byte FLASH Servo::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + + +#endif \ No newline at end of file From 4d350040ba8298e284112692826f8d80c0ba58e1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 12:28:51 +0000 Subject: [PATCH 499/870] I2CManager_SAMD.h - avoid bus hangs on speed changes The speed change is deferred until the next transmission is about to start to avoid issues with the I2C module being disabled and enabled during a transmission. --- I2CManager_SAMD.h | 62 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 38ee99613..63efcd6dc 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -29,6 +29,9 @@ //#include #include +// Storage for new baud rate. Zero means no change pending +static uint32_t pendingBaudRate = 0; + /*************************************************************************** * Interrupt handler. * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero @@ -65,22 +68,37 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { i2cClockSpeed = 100000L; t_rise = 1000; } + // Calculate baudrate - using a rise time appropriate for the speed + pendingBaudRate = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); +} - // Disable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 0 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); +/*************************************************************************** + * Internal function to actually change the baud rate register, executed from + * interrupt code to avoid in-progress I2C transactions. + ***************************************************************************/ +static void checkForPendingClockSpeedChange() { + if (pendingBaudRate > 0) { + // Wait while the bus is busy + while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); - // Calculate baudrate - using a rise time appropriate for the speed - s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + // Disable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 0 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); - // Enable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 1 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Update baudrate + s->I2CM.BAUD.bit.BAUD = pendingBaudRate; - // Setting bus idle mode and wait for sync - s->I2CM.STATUS.bit.BUSSTATE = 1 ; - while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + // Enable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 1 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + + // Setting bus idle mode and wait for sync + s->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + // Clear pending rate now it's been implemented. + pendingBaudRate = 0; + } return; } @@ -148,25 +166,29 @@ void I2CManagerClass::I2C_init() * Initiate a start bit for transmission. ***************************************************************************/ void I2CManagerClass::I2C_sendStart() { + // Check if the clock is to be changed, if so do it now. It doesn't matter + // what else is going on over the I2C bus as the clock change only affects + // this master. + checkForPendingClockSpeedChange(); + + // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; - // We may have initiated a stop bit before this without waiting for it. - // However, the state machine ensures that the start bit isn't sent - // until the stop bit is complete. - //while (s->I2CM.STATUS.bit.BUSSTATE == 0x2); + // On a single-master I2C bus, the start bit won't be sent until the bus + // state goes to IDLE so we can request it without waiting. On a + // multi-master bus, the bus may be BUSY under control of another master, + // in which case we can avoid some arbitration failures by waiting until + // the bus state is IDLE. We don't do that here. // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { - // Wait while the I2C bus is BUSY - //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); - // Send start and address with read/write flag or'd in + // Send start and address with read flag (1) or'd in s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { - // Wait while the I2C bus is BUSY - //while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); + // Send start and address with write flag (0) or'd in s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; } } From bdffd368206347a1b59dd3e52aa221a6963ba07d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 15:24:38 +0000 Subject: [PATCH 500/870] IODevice.h - change visibility of findDevice to protected. To support nested drivers efficiently (i.e. to allow the higher driver to call another driver directly, without searching for a VPIN every time), the visibility of the IODevice::findDevice() function has been changed from private to protected. --- IODevice.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/IODevice.h b/IODevice.h index 77b440d7c..5155aaf70 100644 --- a/IODevice.h +++ b/IODevice.h @@ -93,6 +93,8 @@ class IODevice { CONFIGURE_INPUT = 1, CONFIGURE_SERVO = 2, CONFIGURE_OUTPUT = 3, + CONFIGURE_ANALOGOUTPUT = 4, + CONFIGURE_ANALOGINPUT = 5, } ConfigTypeEnum; typedef enum : uint8_t { @@ -174,9 +176,12 @@ class IODevice { _I2CAddress=0; } - // Method to perform initialisation of the device (optionally implemented within device class) + // Method to perform initialisation of the device (optionally implemented within device class) virtual void _begin() {} + // Method to check whether the vpin corresponds to this device + bool owns(VPIN vpin); + // Method to configure device (optionally implemented within device class) virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { (void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning. @@ -239,14 +244,13 @@ class IODevice { // Static support function for subclass creation static void addDevice(IODevice *newDevice); + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); + // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; private: - // Method to check whether the vpin corresponds to this device - bool owns(VPIN vpin); - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; From ba9b36305856b38a9628502f47940b374c8cdf08 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 31 Jan 2023 18:39:15 +0000 Subject: [PATCH 501/870] I2CManager_NonBlocking - Defer I2C speed changes for all drivers Following on from the change to I2CManager_SAMD.h, the capability of deferring a request to change the speed of the I2C has been removed from the SAMD driver and put into the common NonBlocking code, so that all native drivers benefit from it. --- I2CManager.h | 2 ++ I2CManager_NonBlocking.h | 17 ++++++++++--- I2CManager_SAMD.h | 55 +++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/I2CManager.h b/I2CManager.h index 677c5f988..0fcc6c6dc 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -281,6 +281,8 @@ class I2CManagerClass { static volatile uint8_t operation; static volatile unsigned long startTime; + volatile uint32_t pendingClockSpeed = 0; + void startTransaction(); // Low-level hardware manipulation functions. diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index fce158bd7..f5caefd7d 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -84,25 +84,36 @@ void I2CManagerClass::_initialise() queueHead = queueTail = NULL; state = I2C_STATE_FREE; I2C_init(); - I2C_setClock(_clockSpeed); + _setClock(_clockSpeed); } /*************************************************************************** * Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast) * on Arduino. Mega4809 supports 1000000 (Fast+) too. + * This function saves the desired clock speed and the startTransaction + * function acts on it before a new transaction, to avoid speed changes + * during an I2C transaction. ***************************************************************************/ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { - I2C_setClock(i2cClockSpeed); + pendingClockSpeed = i2cClockSpeed; } /*************************************************************************** * Helper function to start operations, if the I2C interface is free and * there is a queued request to be processed. + * If there's an I2C clock speed change pending, then implement it before + * starting the operation. ***************************************************************************/ -void I2CManagerClass::startTransaction() { +void I2CManagerClass::startTransaction() { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { state = I2C_STATE_ACTIVE; + // Check for pending clock speed change + if (pendingClockSpeed) { + // We're about to start a new I2C transaction, so set clock now. + I2C_setClock(pendingClockSpeed); + pendingClockSpeed = 0; + } startTime = micros(); currentRequest = queueHead; rxCount = txCount = 0; diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 63efcd6dc..affb6b3af 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -49,7 +49,9 @@ void SERCOM3_Handler() { Sercom *s = SERCOM3; /*************************************************************************** - * Set I2C clock speed register. + * Set I2C clock speed register. This should only be called outside of + * a transmission. The I2CManagerClass::_setClock() function ensures + * that it is only called at the beginning of an I2C transaction. ***************************************************************************/ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { @@ -68,38 +70,24 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { i2cClockSpeed = 100000L; t_rise = 1000; } - // Calculate baudrate - using a rise time appropriate for the speed - pendingBaudRate = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); -} - -/*************************************************************************** - * Internal function to actually change the baud rate register, executed from - * interrupt code to avoid in-progress I2C transactions. - ***************************************************************************/ -static void checkForPendingClockSpeedChange() { - if (pendingBaudRate > 0) { - // Wait while the bus is busy - while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); - // Disable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 0 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Wait while the bus is busy + while (s->I2CM.STATUS.bit.BUSSTATE != 0x1); - // Update baudrate - s->I2CM.BAUD.bit.BAUD = pendingBaudRate; + // Disable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 0 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); - // Enable the I2C master mode and wait for sync - s->I2CM.CTRLA.bit.ENABLE = 1 ; - while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Calculate baudrate - using a rise time appropriate for the speed + s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); - // Setting bus idle mode and wait for sync - s->I2CM.STATUS.bit.BUSSTATE = 1 ; - while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + // Enable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 1 ; + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); - // Clear pending rate now it's been implemented. - pendingBaudRate = 0; - } - return; + // Setting bus idle mode and wait for sync + s->I2CM.STATUS.bit.BUSSTATE = 1 ; + while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); } /*************************************************************************** @@ -166,10 +154,6 @@ void I2CManagerClass::I2C_init() * Initiate a start bit for transmission. ***************************************************************************/ void I2CManagerClass::I2C_sendStart() { - // Check if the clock is to be changed, if so do it now. It doesn't matter - // what else is going on over the I2C bus as the clock change only affects - // this master. - checkForPendingClockSpeedChange(); // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; @@ -205,6 +189,13 @@ void I2CManagerClass::I2C_sendStop() { ***************************************************************************/ void I2CManagerClass::I2C_close() { I2C_sendStop(); + // Disable the I2C master mode and wait for sync + s->I2CM.CTRLA.bit.ENABLE = 0 ; + // Wait for up to 500us only. + unsigned long startTime = micros(); + while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) { + if (micros() - startTime >= 500UL) break; + } } /*************************************************************************** From 052256e2ed803cae8d371bed5478b6d5ed568200 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 502/870] Refactored, analogue tested --- IO_EXIOExpander.h | 75 +++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f284bc17d..c96262345 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -71,15 +71,12 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); - if (_receive3Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive3Buffer[1]; - _numPWMPins = _receive3Buffer[2]; + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - _servoData = (struct ServoData*) calloc(_numPWMPins, 14); - } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -105,26 +102,17 @@ class EXIOExpander : public IODevice { } } + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; + bool pullup = params[0]; int pin = vpin - _firstVpin; - if (configType == CONFIGURE_INPUT) { - bool pullup = params[0]; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; - } else if (configType == CONFIGURE_SERVO) { - DIAG(F("Configure servo at pin %d"), (int)pin); - for (int i = 0; i < paramCount; i++) { - DIAG(F("Param %d is %x"), (int)i, params[i]); - } - return true; - } else { - return false; - } + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -133,9 +121,13 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; I2CManager.write(_i2cAddress, _command2Buffer, 2); + _command2Buffer[0] = EXIOENAN; + _command2Buffer[1] = pin; + I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } + // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning @@ -143,10 +135,22 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } + // Obtain the correct analogue input value // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { + int pin = vpin - _firstVpin; + uint8_t _pinLSBByte; + for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { + if (_analoguePinMap[aPin] == pin) { + _pinLSBByte = aPin * 2; + } + } int pin = vpin - _firstVpin; uint8_t _pinLSBByte; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { @@ -158,6 +162,7 @@ class EXIOExpander : public IODevice { return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } + // Obtain the correct digital input value // Obtain the correct digital input value int _read(VPIN vpin) override { int pin = vpin - _firstVpin; @@ -178,11 +183,12 @@ class EXIOExpander : public IODevice { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; - uint8_t _numAnaloguePins = 0; + uint8_t _numAnaloguePins; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -194,29 +200,8 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive3Buffer[3]; + byte _receive2Buffer[2]; uint8_t* _analoguePinMap; - uint8_t _numPWMPins = 0; - - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - } ServoData; // 14 bytes per element, i.e. per pin in use - - struct ServoData* _servoData; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const byte FLASH _bounceProfile[30]; - - const unsigned int refreshInterval = 50; // refresh every 50ms enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From bdc8aec9a638ee35d13bfc9e6b327ba189cdf503 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 503/870] Refactored, analogue tested --- IO_EXIOExpander.h | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index c96262345..5c10d8843 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -49,12 +49,15 @@ */ class EXIOExpander : public IODevice { public: + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; @@ -68,6 +71,16 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline @@ -85,9 +98,14 @@ class EXIOExpander : public IODevice { // We now need to retrieve the analogue pin map _command1Buffer[0] = EXIOINITA; I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); + // We now need to retrieve the analogue pin map + _command1Buffer[0] = EXIOINITA; + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline _command1Buffer[0] = EXIOVER; I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); + _command1Buffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -102,7 +120,6 @@ class EXIOExpander : public IODevice { } } - // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; @@ -115,19 +132,16 @@ class EXIOExpander : public IODevice { return true; } + // Analogue input pin configuration, used to enable on EX-IOExpander device // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { int pin = vpin - _firstVpin; _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; I2CManager.write(_i2cAddress, _command2Buffer, 2); - _command2Buffer[0] = EXIOENAN; - _command2Buffer[1] = pin; - I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } - // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning @@ -135,22 +149,10 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); - _command1Buffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } - // Obtain the correct analogue input value // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { - int pin = vpin - _firstVpin; - uint8_t _pinLSBByte; - for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { - if (_analoguePinMap[aPin] == pin) { - _pinLSBByte = aPin * 2; - } - } int pin = vpin - _firstVpin; uint8_t _pinLSBByte; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { @@ -162,7 +164,6 @@ class EXIOExpander : public IODevice { return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } - // Obtain the correct digital input value // Obtain the correct digital input value int _read(VPIN vpin) override { int pin = vpin - _firstVpin; @@ -180,10 +181,10 @@ class EXIOExpander : public IODevice { } void _display() override { + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, - (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } @@ -202,6 +203,10 @@ class EXIOExpander : public IODevice { byte _command2Buffer[2]; byte _receive2Buffer[2]; uint8_t* _analoguePinMap; + byte _command1Buffer[1]; + byte _command2Buffer[2]; + byte _receive2Buffer[2]; + uint8_t* _analoguePinMap; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From e76197faa97f2aa0d6a488823456a5fcaba5ad7b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 19:26:33 +1000 Subject: [PATCH 504/870] Brief PWM start --- IO_EXIOExpander.h | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 5c10d8843..5476a41eb 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -90,6 +90,18 @@ class EXIOExpander : public IODevice { _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive3Buffer[1]; + _numPWMPins = _receive3Buffer[2]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -123,13 +135,23 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; - bool pullup = params[0]; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + if (configType == CONFIGURE_INPUT) { + bool pullup = params[0]; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } else if (configType == CONFIGURE_SERVO) { + DIAG(F("Configure servo at pin %d"), (int)pin); + for (int i = 0; i < paramCount; i++) { + DIAG(F("Param %d is %x"), (int)i, params[i]); + } + return true; + } else { + return false; + } } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -189,7 +211,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins; + uint8_t _numAnaloguePins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -201,7 +223,7 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; byte _command1Buffer[1]; byte _command2Buffer[2]; From 84431d1841a472f892aa1125e28242f6cc7f781a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 1 Feb 2023 07:49:31 +1000 Subject: [PATCH 505/870] Fix mess after rebase and conflicts --- IO_EXIOExpander.h | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 5476a41eb..519d2eb59 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -49,15 +49,12 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; @@ -80,28 +77,6 @@ class EXIOExpander : public IODevice { _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { - _command2Buffer[0] = EXIOINIT; - _command2Buffer[1] = _nPins; - // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; - _analoguePinBytes = _numAnaloguePins * 2; - _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); - _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { - _command2Buffer[0] = EXIOINIT; - _command2Buffer[1] = _nPins; - // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); - if (_receive3Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive3Buffer[1]; - _numPWMPins = _receive3Buffer[2]; - _analoguePinBytes = _numAnaloguePins * 2; - _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); - _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -110,9 +85,6 @@ class EXIOExpander : public IODevice { // We now need to retrieve the analogue pin map _command1Buffer[0] = EXIOINITA; I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); - // We now need to retrieve the analogue pin map - _command1Buffer[0] = EXIOINITA; - I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline _command1Buffer[0] = EXIOVER; I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); @@ -143,12 +115,6 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[2] = pullup; I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); return true; - } else if (configType == CONFIGURE_SERVO) { - DIAG(F("Configure servo at pin %d"), (int)pin); - for (int i = 0; i < paramCount; i++) { - DIAG(F("Param %d is %x"), (int)i, params[i]); - } - return true; } else { return false; } @@ -203,7 +169,6 @@ class EXIOExpander : public IODevice { } void _display() override { - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, @@ -212,6 +177,7 @@ class EXIOExpander : public IODevice { uint8_t _i2cAddress; uint8_t _numAnaloguePins = 0; + uint8_t numDigitalPins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -223,10 +189,6 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive3Buffer[3]; - uint8_t* _analoguePinMap; - byte _command1Buffer[1]; - byte _command2Buffer[2]; byte _receive2Buffer[2]; uint8_t* _analoguePinMap; From a7366b42c13bbef2671c73d1a42dfa8a48ee10dc Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 31 Jan 2023 19:32:12 +1000 Subject: [PATCH 506/870] Add new drivers --- IODevice.h | 5 +- IO_PCA9685_basic.h | 149 ++++++++++++++++++++++++ IO_Servo.h | 277 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 IO_PCA9685_basic.h create mode 100644 IO_Servo.h diff --git a/IODevice.h b/IODevice.h index 72beb9e73..e7906c601 100644 --- a/IODevice.h +++ b/IODevice.h @@ -242,11 +242,12 @@ class IODevice { // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); + private: // Method to check whether the vpin corresponds to this device bool owns(VPIN vpin); - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; diff --git a/IO_PCA9685_basic.h b/IO_PCA9685_basic.h new file mode 100644 index 000000000..4f809aaaf --- /dev/null +++ b/IO_PCA9685_basic.h @@ -0,0 +1,149 @@ +/* + * © 2023, 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 . + */ + +/* + * This driver performs the basic interface between the HAL and an + * I2C-connected PCA9685 16-channel PWM module. When requested, it + * commands the device to set the PWM mark-to-period ratio accordingly. + * The call to IODevice::writeAnalogue(vpin, value) specifies the + * desired value in the range 0-4095 (0=0% and 4095=100%). + */ + +#ifndef PCA9685_BASIC_H +#define PCA9685_BASIC_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +/* + * IODevice subclass for PCA9685 16-channel PWM module. + */ + +class PCA9685_basic : public IODevice { +public: + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685_basic(firstVpin, nPins, I2CAddress); + } + +private: + + // structures for setting up non-blocking writes to servo controller + I2CRB requestBlock; + uint8_t outputBuffer[5]; + + // REGISTER ADDRESSES + const byte PCA9685_MODE1=0x00; // Mode Register + const byte PCA9685_FIRST_SERVO=0x06; /** low byte first servo register ON*/ + const byte PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ + // MODE1 bits + const byte MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ + const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ + const byte MODE1_RESTART=0x80; /**< Restart enabled */ + + const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); + const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed + + // Constructor + PCA9685_basic(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + _firstVpin = firstVpin; + _nPins = min(nPins, 16); + _I2CAddress = I2CAddress; + addDevice(this); + + // Initialise structure used for setting pulse rate + requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C + // In reality, other devices including the Arduino will limit + // the clock speed to a lower rate. + + // Initialise I/O module here. + if (I2CManager.exists(_I2CAddress)) { + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); + // In theory, we should wait 500us before sending any other commands to each device, to allow + // the PWM oscillator to get running. However, we don't do any specific wait, as there's + // plenty of other stuff to do before we will send a command. + #if defined(DIAG_IO) + _display(); + #endif + } else + _deviceState = DEVSTATE_FAILED; + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + writeDevice(pin, value); + } + + // Display details of this device. + void _display() override { + 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("")); + } + + // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value + // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. + void writeDevice(uint8_t pin, int value) { + #ifdef DIAG_IO + DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); + #endif + // Wait for previous request to complete + 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); + } + } + + // Internal helper function for this device + static void writeRegister(byte address, byte reg, byte value) { + I2CManager.write(address, 2, reg, value); + } + +}; + +#endif \ No newline at end of file diff --git a/IO_Servo.h b/IO_Servo.h new file mode 100644 index 000000000..bd475fbc9 --- /dev/null +++ b/IO_Servo.h @@ -0,0 +1,277 @@ +/* + * © 2023, 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_SERVO_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +class Servo : IODevice { + +public: + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin) { + if (checkNoOverlap(firstVpin, nPins)) new Servo(firstVpin, nPins, firstSlavePin); + } + +private: + VPIN _firstSlavePin; + IODevice *_slaveDevice = NULL; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + + + // Configure a port on the Servo. + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { + if (configType != CONFIGURE_SERVO) return false; + if (paramCount != 5) return false; + #ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); + #endif + + int8_t pin = vpin - _firstVpin; + VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + + if (state != -1) { + // Position servo to initial state + IODevice::writeAnalogue(slavePin, state ? s->activePosition : s->inactivePosition, 0, 0); + } + return true; + } + + // Constructor + Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin) { + _firstVpin = firstVpin; + _nPins = (nPins > 16) ? 16 : nPins; + _firstSlavePin = firstSlavePin; + + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) + _servoData[i] = NULL; + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + // Get reference to slave device to make accesses faster. + _slaveDevice = this->findDevice(_firstSlavePin); + // Check firstSlavePin is actually allocated to a device + if (!_slaveDevice) { + DIAG(F("Servo: Slave device not found on pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + // Check that the last slave pin is allocated to the same device. + if (_slaveDevice != this->findDevice(_firstSlavePin+_nPins-1)) { + DIAG(F("Servo: Slave device does not cover all pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + #if defined(DIAG_IO) + _display(); + #endif + } + + // Device-specific write function, invoked from IODevice::write(). + // For this function, the configured profile is used. + void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; + #ifdef DIAG_IO + DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + #endif + int pin = vpin - _firstVpin; + VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + if (value) value = 1; + + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // Profile is as follows: + // Bit 7: 0=Set PWM to 0% to power off servo motor when finished + // 1=Keep PWM pulses on (better when using PWM to drive an LED) + // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) + // 1 (Fast) Move servo in 0.5 seconds + // 2 (Medium) Move servo in 1.0 seconds + // 3 (Slow) Move servo in 2.0 seconds + // 4 (Bounce) Servo 'bounces' at extremes. + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + // _read returns true if the device is currently in executing an animation, + // changing the output over a period of time. + int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + int pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) + return false; // No structure means no animation! + else + return (s->stepNumber < s->numSteps); + } + + void _loop(unsigned long currentMicros) override { + if (_deviceState == DEVSTATE_FAILED) return; + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } + delayUntil(currentMicros + refreshInterval * 1000UL); + } + + // Private function to reposition servo + // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command + _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off PWM to prevent annoying servo buzz + _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } + } + + // Display details of this device. + void _display() override { + DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } +}; + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +const byte FLASH Servo::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + + +#endif \ No newline at end of file From 73e1dfc1929838bc3770fc94e0ec5550cd8f2e0d Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 1 Feb 2023 08:13:23 +1000 Subject: [PATCH 507/870] Remove duplicate comment --- IO_EXIOExpander.h | 1 - 1 file changed, 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 519d2eb59..ac66fd976 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -120,7 +120,6 @@ class EXIOExpander : public IODevice { } } - // Analogue input pin configuration, used to enable on EX-IOExpander device // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { int pin = vpin - _firstVpin; From 4e32c707b984f353c1039741e8964e14f11ab851 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:53:46 +1000 Subject: [PATCH 508/870] Brief start on PWM --- IO_EXIOExpander.h | 25 ++++++++++++++++--------- IO_Servo.h | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index ac66fd976..718cf1c97 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -59,8 +59,6 @@ class EXIOExpander : public IODevice { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _digitalPinBytes = (nPins+7)/8; - _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); addDevice(this); } @@ -70,10 +68,13 @@ class EXIOExpander : public IODevice { if (I2CManager.exists(_i2cAddress)) { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; - // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; + // Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOPINS) { + _numDigitalPins = _receive3Buffer[1]; + _numAnaloguePins = _receive3Buffer[2]; + _digitalPinBytes = (_numDigitalPins + 7)/8; + _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); @@ -167,6 +168,11 @@ class EXIOExpander : public IODevice { I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); } + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + int pin = vpin - _firstVpin; + DIAG(F("Write %d to pin %d, param 1 %d, param 2 %d"), value, pin, param1, param2); + } + void _display() override { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, @@ -175,8 +181,8 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; + uint8_t _numDigitalPins = 0; uint8_t _numAnaloguePins = 0; - uint8_t numDigitalPins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -188,7 +194,7 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; enum { @@ -200,7 +206,8 @@ class EXIOExpander : public IODevice { EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input EXIOENAN = 0xE7, // Flag eo enable an analogue pin - EXIOINITA = 0xE8, // Flag we're receiving analogue pin info + EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings + EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers }; }; diff --git a/IO_Servo.h b/IO_Servo.h index bd475fbc9..b1935b6f5 100644 --- a/IO_Servo.h +++ b/IO_Servo.h @@ -139,7 +139,7 @@ class Servo : IODevice { DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; - VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + // VPIN slavePin = vpin - _firstVpin + _firstSlavePin; if (value) value = 1; struct ServoData *s = _servoData[pin]; From ec83a345dcfa44b2781d6a6c2cd4c765df80300d Mon Sep 17 00:00:00 2001 From: peteGSX Date: Wed, 1 Feb 2023 19:46:08 +1000 Subject: [PATCH 509/870] Basic PWM working --- IO_EXIOExpander.h | 19 ++++++++++++++----- version.h | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 718cf1c97..46abc505c 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -66,10 +66,12 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { - _command2Buffer[0] = EXIOINIT; - _command2Buffer[1] = _nPins; + _command4Buffer[0] = EXIOINIT; + _command4Buffer[1] = _nPins; + _command4Buffer[2] = _firstVpin & 0xFF; + _command4Buffer[3] = _firstVpin >> 8; // Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4); if (_receive3Buffer[0] == EXIOPINS) { _numDigitalPins = _receive3Buffer[1]; _numAnaloguePins = _receive3Buffer[2]; @@ -170,7 +172,11 @@ class EXIOExpander : public IODevice { void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { int pin = vpin - _firstVpin; - DIAG(F("Write %d to pin %d, param 1 %d, param 2 %d"), value, pin, param1, param2); + _command4Buffer[0] = EXIOWRAN; + _command4Buffer[1] = pin; + _command4Buffer[2] = value & 0xFF; + _command4Buffer[3] = value >> 8; + I2CManager.write(_i2cAddress, _command4Buffer, 4); } void _display() override { @@ -194,6 +200,7 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; + byte _command4Buffer[4]; byte _receive3Buffer[3]; uint8_t* _analoguePinMap; @@ -205,9 +212,11 @@ class EXIOExpander : public IODevice { EXIORDAN = 0xE4, // Flag to read an analogue input EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input - EXIOENAN = 0xE7, // Flag eo enable an analogue pin + EXIOENAN = 0xE7, // Flag to enable an analogue pin EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers + EXIOWRAN = 0xEA, // Flag we're sending an analogue write (PWM) + EXIOERR = 0xEF, // Flag we've received an error }; }; diff --git a/version.h b/version.h index 67886459a..c37a1c44e 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.14" +#define VERSION "4.2.15" +// 4.2.15 Separate Servo from PCA9685 +// Add PWM support to EX-IOExpander // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From be88344407b51bdabec5971d3a2f7da09b576f66 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 2 Feb 2023 07:12:47 +0800 Subject: [PATCH 510/870] PCF8575 16-bit port device support added --- IODevice.h | 2 + IO_PCF8575.h | 106 ++++++++++++++++++++++++++++++++++++++++++ myHal.cpp_example.txt | 15 ++++++ 3 files changed, 123 insertions(+) create mode 100644 IO_PCF8575.h diff --git a/IODevice.h b/IODevice.h index 5155aaf70..99394b45d 100644 --- a/IODevice.h +++ b/IODevice.h @@ -1,4 +1,5 @@ /* + * © 2023, Paul Antoine, Discord user @ADUBOURG * © 2021, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API @@ -410,6 +411,7 @@ class EXTurntable : public IODevice { #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" +#include "IO_PCF8575.h" #include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" diff --git a/IO_PCF8575.h b/IO_PCF8575.h new file mode 100644 index 000000000..1b271ecc1 --- /dev/null +++ b/IO_PCF8575.h @@ -0,0 +1,106 @@ +/* + * © 2023, Paul Antoine, and Discord user @ADUBOURG + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of DCC++EX API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * The PCF8575 is a simple device; it only has one register. The device + * input/output mode and pullup are configured through this, and the + * output state is written and the input state read through it too. + * + * This is accomplished by having a weak resistor in series with the output, + * and a read-back of the other end of the resistor. As an output, the + * pin state is set to 1 or 0, and the output voltage goes to +5V or 0V + * (through the weak resistor). + * + * In order to use the pin as an input, the output is written as + * a '1' in order to pull up the resistor. Therefore the input will be + * 1 unless the pin is pulled down externally, in which case it will be 0. + * + * As a consequence of this approach, it is not possible to use the device for + * inputs without pullups. + */ + +#ifndef IO_PCF8575_H +#define IO_PCF8575_H + +#include "IO_GPIOBase.h" +#include "FSH.h" + +class PCF8575 : public GPIOBase { +public: + static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), I2CAddress, interruptPin); + } + +private: + PCF8575(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, I2CAddress, interruptPin) + { + requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); + } + + // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + void _writeGpioPort() override { + I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + } + + // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. + // Therefore, writing '1' in _writePortModes is enough to set the module to input mode + // and enable pull-up. + void _writePullups() override { } + + // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + void _writePortModes() override { + I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + } + + // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. + // When not in immediate mode, it initiates a request using the request block and returns. + // When the request completes, _processCompletion finishes the operation. + void _readGpioPort(bool immediate) override { + if (immediate) { + uint8_t buffer[2]; + I2CManager.read(_I2CAddress, buffer, 2); + _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + } else { + requestBlock.wait(); // Wait for preceding operation to complete + // Issue new request to read GPIO register + I2CManager.queueRequest(&requestBlock); + } + } + + // This function is invoked when an I/O operation on the requestBlock completes. + void _processCompletion(uint8_t status) override { + if (status == I2C_STATUS_OK) + _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + else + _portInputState = 0xffff; + } + + // Set up device ports + void _setupDevice() override { + _writePortModes(); + _writeGpioPort(); + _writePullups(); + } + + uint8_t inputBuffer[2]; +}; + +#endif \ No newline at end of file diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 5470f76d8..1c5e7011d 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -88,6 +88,21 @@ void halSetup() { //PCF8574::create(200, 8, 0x23, 40); + //======================================================================= + // The following directive defines a PCF8575 16-port I2C GPIO Extender module. + //======================================================================= + // The parameters are: + // First Vpin=200 + // Number of VPINs=16 (numbered 200-215) + // I2C address of module=0x23 + + //PCF8575::create(200, 16, 0x23); + + + // Alternative form using INT pin (see above) + + //PCF8575::create(200, 16, 0x23, 40); + //======================================================================= // The following directive defines an HCSR04 ultrasonic ranging module. //======================================================================= From 49713badb2a464725042b60709ffba3c477dc197 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 2 Feb 2023 12:21:35 +0000 Subject: [PATCH 511/870] Update I2CManager_NonBlocking.h Add code to try and recover from stuck bus following a timeout. --- I2CManager_NonBlocking.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index f5caefd7d..3a3371995 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -213,10 +213,21 @@ void I2CManagerClass::checkForTimeout() { // Reset TWI interface so it is able to continue // Try close and init, not entirely satisfactory but sort of works... I2C_close(); // Shutdown and restart twi interface + + // If SDA is stuck low, issue up to 9 clock pulses to attempt to free it. + pinMode(SCL, INPUT_PULLUP); + pinMode(SDA, INPUT_PULLUP); + for (int i=0; !digitalRead(SDA) && i<9; i++) { + digitalWrite(SCL, 0); + pinMode(SCL, OUTPUT); // Force clock low + delayMicroseconds(10); // ... for 5us + pinMode(SCL, INPUT_PULLUP); // ... then high + delayMicroseconds(10); // ... for 5us (100kHz Clock) + } + // Whether that's succeeded or not, now try reinitialising. I2C_init(); _setClock(_clockSpeed); state = I2C_STATE_FREE; -// I2C_sendStop(); // in case device is waiting for a stop condition // Initiate next queued request if any. startTransaction(); From 847ced2f497b003ac2564dde1b0f50e04b9f2f92 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 3 Feb 2023 12:46:38 +0000 Subject: [PATCH 512/870] Update IO_VL53L0X.h Improve comments; drive XSHUT pin through pullup resistor, not directly. --- IO_VL53L0X.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 6a83970be..cb3b6f7f5 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -42,14 +42,17 @@ * 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. + * all VL53L0X modules are turned off, the driver works through each module in turn, + * setting XSHUT to HIGH to turn that module on, then writing that module's desired I2C address. * In this way, many VL53L0X modules can be connected to the one I2C bus, each one - * using a distinct I2C address. + * using a distinct I2C address. The process is described in ST Microelectronics application + * note AN4846. * * 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. + * and the device may reset spontaneously or when handled and the device will stop responding + * on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor + * (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example) + * include a pull-up on the module, but others don't. * * The driver is configured as follows: * @@ -173,14 +176,17 @@ class VL53L0X : public IODevice { break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. - // On the module, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V. - // Ensure this is done for only one module at a time by using a + // On some modules, there is a diode in series with the XSHUT pin to + // protect the low-voltage pin against +5V, but we can provide additional + // protection by enabling the pull-up resistor on the microcontroller + // instead of driving the output directly. + // Ensure XSHUT is set for only one module at a time by using a // shared flag accessible to all device instances. if (_addressConfigInProgress) return; _addressConfigInProgress = true; - // Set XSHUT pin (if connected) - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); + // Set XSHUT pin (if connected). Because of supply voltage differences, + // drive the signal through the digital output's pull-up resistor. + if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, 1); // Allow the module time to restart delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; From 81559998ec3fc834af939b5ea59f30140def1faf Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 3 Feb 2023 12:55:25 +0000 Subject: [PATCH 513/870] Update IODevice base class to better support filter drivers Filter drivers provide extra functionality above a hardware driver. For example, a hardware driver for a PWM module may just set the PWM ratio, but a separate filter driver could animate motors or servos over time, calling the PWM driver to output the pulses. This would allow the animations to be easily implemented on a different type of PWM module. --- IODevice.cpp | 43 +++++++++++++++++++++++++++++------------- IODevice.h | 53 ++++++++++++++++++++++++++++------------------------ 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index a51c84bfe..ea4301fb3 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -255,20 +255,26 @@ void IODevice::setGPIOInterruptPin(int16_t pinNumber) { _gpioInterruptPin = pinNumber; } -// Private helper function to add a device to the chain of devices. -void IODevice::addDevice(IODevice *newDevice) { - // Link new object to the end of the chain. Thereby, the first devices to be declared/created - // will be located faster by findDevice than those which are created later. - // Ideally declare/create the digital IO pins first, then servos, then more esoteric devices. - IODevice *lastDevice; - if (_firstDevice == 0) +// Helper function to add a new device to the device chain. If +// slaveDevice is NULL then the device is added to the end of the chain. +// Otherwise, the chain is searched for slaveDevice and the new device linked +// in front of it (to support filter devices that share the same VPIN range +// as the devices they control). If slaveDevice isn't found, then the +// device is linked to the end of the chain. +void IODevice::addDevice(IODevice *newDevice, IODevice *slaveDevice /* = NULL */) { + if (slaveDevice == _firstDevice) { + newDevice->_nextDevice = _firstDevice; _firstDevice = newDevice; - else { - for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) - lastDevice = dev; - lastDevice->_nextDevice = newDevice; + } else { + for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { + if (dev->_nextDevice == slaveDevice || dev->_nextDevice == NULL) { + // Link new device between dev and slaveDevice (or at end of chain) + newDevice->_nextDevice = dev->_nextDevice; + dev->_nextDevice = newDevice; + break; + } + } } - newDevice->_nextDevice = 0; newDevice->_begin(); } @@ -283,6 +289,17 @@ IODevice *IODevice::findDevice(VPIN vpin) { return NULL; } +// Instance helper function for filter devices (layered over others). Looks for +// a device that is further down the chain than the current device. +IODevice *IODevice::findDeviceFollowing(VPIN vpin) { + for (IODevice *dev = _nextDevice; dev != 0; dev = dev->_nextDevice) { + VPIN firstVpin = dev->_firstVpin; + if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins) + return dev; + } + return NULL; +} + // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) { @@ -320,7 +337,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) // Chain of callback blocks (identifying registered callback functions for state changes) IONotifyCallback *IONotifyCallback::first = 0; -// Start of chain of devices. +// Start and end of chain of devices. IODevice *IODevice::_firstDevice = 0; // Reference to next device to be called on _loop() method. diff --git a/IODevice.h b/IODevice.h index 5155aaf70..3e86ca11c 100644 --- a/IODevice.h +++ b/IODevice.h @@ -163,30 +163,12 @@ class IODevice { // once the GPIO port concerned has been read. void setGPIOInterruptPin(int16_t pinNumber); - // Method to check if pins will overlap before creating new device. + // Method to check if pins will overlap before creating new device. static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); - -protected: - - // Constructor - IODevice(VPIN firstVpin=0, int nPins=0) { - _firstVpin = firstVpin; - _nPins = nPins; - _nextEntryTime = 0; - _I2CAddress=0; - } - - // Method to perform initialisation of the device (optionally implemented within device class) - virtual void _begin() {} - // Method to check whether the vpin corresponds to this device - bool owns(VPIN vpin); - - // Method to configure device (optionally implemented within device class) - virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { - (void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning. - return false; - }; + // Method used by IODevice filters to locate slave pins that may be overlayed by their own + // pin range. + IODevice *findDeviceFollowing(VPIN vpin); // Method to write new state (optionally implemented within device class) virtual void _write(VPIN vpin, int value) { @@ -194,7 +176,7 @@ class IODevice { }; // Method to write an 'analogue' value (optionally implemented within device class) - virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { + virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1=0, uint16_t param2=0) { (void)vpin; (void)value; (void) param1; (void)param2; }; @@ -209,6 +191,29 @@ class IODevice { (void)vpin; return 0; }; + +protected: + + // Constructor + IODevice(VPIN firstVpin=0, int nPins=0) { + _firstVpin = firstVpin; + _nPins = nPins; + _nextEntryTime = 0; + _I2CAddress=0; + } + + // Method to perform initialisation of the device (optionally implemented within device class) + virtual void _begin() {} + + // Method to check whether the vpin corresponds to this device + bool owns(VPIN vpin); + + // Method to configure device (optionally implemented within device class) + virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { + (void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning. + return false; + }; + virtual int _configureAnalogIn(VPIN vpin) { (void)vpin; return 0; @@ -242,7 +247,7 @@ class IODevice { int16_t _gpioInterruptPin = -1; // Static support function for subclass creation - static void addDevice(IODevice *newDevice); + static void addDevice(IODevice *newDevice, IODevice *slaveDevice = NULL); // Method to find device handling Vpin static IODevice *findDevice(VPIN vpin); From 27ddc7b30b00a0ef826f8f3801dbe8b8c02a2334 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 3 Feb 2023 12:56:19 +0000 Subject: [PATCH 514/870] IO_ExampleSerial - refactor and update comments To more directly reflect the bulk of HAL drivers, the .h/.cpp split has been removed and the class is fully defined in the .h file. --- IO_ExampleSerial.cpp | 129 ------------------------------------------- IO_ExampleSerial.h | 129 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 140 deletions(-) delete mode 100644 IO_ExampleSerial.cpp diff --git a/IO_ExampleSerial.cpp b/IO_ExampleSerial.cpp deleted file mode 100644 index 12476dbae..000000000 --- a/IO_ExampleSerial.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * © 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 . - */ - -#include -#include "IO_ExampleSerial.h" -#include "FSH.h" - -// Constructor -IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { - _firstVpin = firstVpin; - _nPins = nPins; - _pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t)); - _baud = baud; - - // Save reference to serial port driver - _serial = serial; - - addDevice(this); -} - -// Static create method for one module. -void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { - if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud); -} - -// Device-specific initialisation -void IO_ExampleSerial::_begin() { - _serial->begin(_baud); -#if defined(DIAG_IO) - _display(); -#endif - - // Send a few # characters to the output - for (uint8_t i=0; i<3; i++) - _serial->write('#'); -} - -// Device-specific write function. Write a string in the form "#Wm,n#" -// where m is the vpin number, and n is the value. -void IO_ExampleSerial::_write(VPIN vpin, int value) { - int pin = vpin -_firstVpin; - #ifdef DIAG_IO - DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value); - #endif - // Send a command string over the serial line - _serial->print('#'); - _serial->print('W'); - _serial->print(pin); - _serial->print(','); - _serial->print(value); - _serial->println('#'); - DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value); - } - -// Device-specific read function. -int IO_ExampleSerial::_read(VPIN vpin) { - - // Return a value for the specified vpin. - int result = _pinValues[vpin-_firstVpin]; - - return result; -} - -// Loop function to do background scanning of the input port. State -// machine parses the incoming command as it is received. Command -// is in the form "#Nm,n#" where m is the index and n is the value. -void IO_ExampleSerial::_loop(unsigned long currentMicros) { - (void)currentMicros; // Suppress compiler warnings - if (_serial->available()) { - // Input data available to read. Read a character. - char c = _serial->read(); - switch (_inputState) { - case 0: // Waiting for start of command - if (c == '#') // Start of command received. - _inputState = 1; - break; - case 1: // Expecting command character - if (c == 'N') { // 'Notify' character received - _inputState = 2; - _inputValue = _inputIndex = 0; - } else - _inputState = 0; // Unexpected char, reset - break; - case 2: // reading first parameter (index) - if (isdigit(c)) - _inputIndex = _inputIndex * 10 + (c-'0'); - else if (c==',') - _inputState = 3; - else - _inputState = 0; // Unexpected char, reset - break; - case 3: // reading reading second parameter (value) - if (isdigit(c)) - _inputValue = _inputValue * 10 - (c-'0'); - else if (c=='#') { // End of command - // Complete command received, do something with it. - DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue); - if (_inputIndex < _nPins) { // Store value - _pinValues[_inputIndex] = _inputValue; - } - _inputState = 0; // Done, start again. - } else - _inputState = 0; // Unexpected char, reset - break; - } - } -} - -void IO_ExampleSerial::_display() { - DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin, - (int)_firstVpin+_nPins-1); -} - diff --git a/IO_ExampleSerial.h b/IO_ExampleSerial.h index 9b20399ca..da421c58c 100644 --- a/IO_ExampleSerial.h +++ b/IO_ExampleSerial.h @@ -35,24 +35,131 @@ #include "IODevice.h" class IO_ExampleSerial : public IODevice { -public: - static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); - -protected: - IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud); - void _begin() override; - void _loop(unsigned long currentMicros) override; - void _write(VPIN vpin, int value) override; - int _read(VPIN vpin) override; - void _display() override; - private: + // Here we define the device-specific variables. HardwareSerial *_serial; uint8_t _inputState = 0; int _inputIndex = 0; int _inputValue = 0; uint16_t *_pinValues; // Pointer to block of memory containing pin values unsigned long _baud; + +public: + // Static function to handle "IO_ExampleSerial::create(...)" calls. + static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { + if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud); + } + +protected: + // Constructor. This should initialise variables etc. but not call other objects yet + // (e.g. Serial, I2CManager, and other parts of the CS functionality). + // defer those until the _begin() function. The 'addDevice' call is required unless + // the device is not to be added (e.g. because of incorrect parameters). + IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) { + _firstVpin = firstVpin; + _nPins = nPins; + _pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t)); + _baud = baud; + + // Save reference to serial port driver + _serial = serial; + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + _serial->begin(_baud); +#if defined(DIAG_IO) + _display(); +#endif + + // Send a few # characters to the output + for (uint8_t i=0; i<3; i++) + _serial->write('#'); + } + + // Device-specific write function. Write a string in the form "#Wm,n#" + // where m is the vpin number, and n is the value. + void _write(VPIN vpin, int value) { + int pin = vpin -_firstVpin; + #ifdef DIAG_IO + DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value); + #endif + // Send a command string over the serial line + _serial->print('#'); + _serial->print('W'); + _serial->print(pin); + _serial->print(','); + _serial->print(value); + _serial->println('#'); + DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value); + } + + // Device-specific read function. + int _read(VPIN vpin) { + + // Return a value for the specified vpin. + int result = _pinValues[vpin-_firstVpin]; + + return result; + } + + // Loop function to do background scanning of the input port. State + // machine parses the incoming command as it is received. Command + // is in the form "#Nm,n#" where m is the index and n is the value. + void _loop(unsigned long currentMicros) { + (void)currentMicros; // Suppress compiler warnings + if (_serial->available()) { + // Input data available to read. Read a character. + char c = _serial->read(); + switch (_inputState) { + case 0: // Waiting for start of command + if (c == '#') // Start of command received. + _inputState = 1; + break; + case 1: // Expecting command character + if (c == 'N') { // 'Notify' character received + _inputState = 2; + _inputValue = _inputIndex = 0; + } else + _inputState = 0; // Unexpected char, reset + break; + case 2: // reading first parameter (index) + if (isdigit(c)) + _inputIndex = _inputIndex * 10 + (c-'0'); + else if (c==',') + _inputState = 3; + else + _inputState = 0; // Unexpected char, reset + break; + case 3: // reading reading second parameter (value) + if (isdigit(c)) + _inputValue = _inputValue * 10 - (c-'0'); + else if (c=='#') { // End of command + // Complete command received, do something with it. + DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue); + if (_inputIndex >= 0 && _inputIndex < _nPins) { // Store value + _pinValues[_inputIndex] = _inputValue; + } + _inputState = 0; // Done, start again. + } else + _inputState = 0; // Unexpected char, reset + break; + } + } + } + + // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). + // Here we display the current values held for the pins. + void _display() { + DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin, + (int)_firstVpin+_nPins-1); + for (int i=0; i<_nPins; i++) + DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]); + } + + }; #endif // IO_EXAMPLESERIAL_H \ No newline at end of file From abe79b854e73a01a71d94019cb1c696f2abfebc5 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 4 Feb 2023 09:19:32 +1000 Subject: [PATCH 515/870] Fix digital read bug --- IO_EXIOExpander.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 46abc505c..59684cd7c 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -158,7 +158,7 @@ class EXIOExpander : public IODevice { int _read(VPIN vpin) override { int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; - bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); + bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); return value; } From bd62939713678e2f94c7228dd4a81807e5618667 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 20:55:14 +0000 Subject: [PATCH 516/870] Fix handling of hex numbers to avoid extending MSB (sign bit) Also, added format %X (unsigned long) to complement %x (unsigned int). --- StringFormatter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index cc78714b1..c1f20c48c 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -108,7 +108,8 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'b': stream->print(va_arg(args, int), BIN); break; case 'o': stream->print(va_arg(args, int), OCT); break; - case 'x': stream->print(va_arg(args, int), HEX); break; + case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break; + case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break; //case 'f': stream->print(va_arg(args, double), 2); break; //format width prefix case '-': From 2f46a8e083dbbe27540060e366df59f0f3291946 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 20:56:12 +0000 Subject: [PATCH 517/870] Add EX-RAIL 'ANOUT' function for general analogue outputs. --- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 1 + 2 files changed, 3 insertions(+) diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 32e28a28a..6c004ffe5 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -28,6 +28,7 @@ #undef AFTER #undef ALIAS #undef AMBER +#undef ANOUT #undef AT #undef ATGTE #undef ATLT @@ -143,6 +144,7 @@ #define AFTER(sensor_id) #define ALIAS(name,value...) #define AMBER(signal_id) +#define ANOUT(vpin,value,param1,param2) #define AT(sensor_id) #define ATGTE(sensor_id,value) #define ATLT(sensor_id,value) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index b5e78d988..304443c5b 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -241,6 +241,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id), #define ALIAS(name,value...) #define AMBER(signal_id) OPCODE_AMBER,V(signal_id), +#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2), #define AT(sensor_id) OPCODE_AT,V(sensor_id), #define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value), #define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value), From 6f5680fce01e03739a5b61980fcb680c27a4f885 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 20:59:19 +0000 Subject: [PATCH 518/870] DFPlayer: Avoid jumps in volume when switching song and reducing volume at the same time. --- IO_DFPlayer.h | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index ad09ca591..4439fbc5d 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -70,10 +70,12 @@ class DFPlayer : public IODevice { private: + const uint8_t MAXVOLUME=30; HardwareSerial *_serial; bool _playing = false; uint8_t _inputIndex = 0; unsigned long _commandSendTime; // Allows timeout processing + uint8_t _lastVolumeLevel = MAXVOLUME; // When two commands are sent in quick succession, the device sometimes // fails to execute one. A delay is required between successive commands. @@ -179,41 +181,45 @@ class DFPlayer : public IODevice { // 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. + // If starting a new file and setting volume, then avoid a short burst of loud noise by + // the following strategy: + // - If the volume is increasing, start playing the song before setting the volume, + // - If the volume is decreasing, decrease it and then start playing. + // void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override { uint8_t pin = vpin - _firstVpin; + #ifdef DIAG_IO + DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume); + #endif + // Validate parameter. - volume = min((uint8_t)30,volume); + if (volume > MAXVOLUME) volume = MAXVOLUME; if (pin == 0) { // Play track if (value > 0) { - #ifdef DIAG_IO - DIAG(F("DFPlayer: Play %d"), value); - #endif - 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 + if (volume != 0) { + if (volume <= _lastVolumeLevel) + sendPacket(0x06, volume); // Set volume before starting + sendPacket(0x03, value); // Play track + _playing = true; + if (volume > _lastVolumeLevel) + sendPacket(0x06, volume); // Set volume after starting + _lastVolumeLevel = volume; + } else { + // Volume not changed, just play + sendPacket(0x03, value); + _playing = true; } } 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); + sendPacket(0x06, value); + _lastVolumeLevel = volume; } } From a590245e93a20bc8a45e65b86b7b667ec2ce5909 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 21:01:02 +0000 Subject: [PATCH 519/870] I2CManager_SAMD.h: Remove unneeded declaration. --- I2CManager_SAMD.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index affb6b3af..08e34f2a1 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -29,9 +29,6 @@ //#include #include -// Storage for new baud rate. Zero means no change pending -static uint32_t pendingBaudRate = 0; - /*************************************************************************** * Interrupt handler. * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero From 13bd6ef9ebe47e8bbb0639f576c1ebccdf517a93 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 4 Feb 2023 23:57:22 +0000 Subject: [PATCH 520/870] HAL: Add support for Extended Addresses and I2C Multiplexer Change I2C addresses from uint8_t to I2CAddress, in preparation for MUX support. Currently, by default, I2CAddress is typedef'd to uint8_t. MUX support implemented for AVR and Wire versions. --- I2CManager.cpp | 53 +++++++++++--- I2CManager.h | 154 +++++++++++++++++++++++++++++++++++---- I2CManager_AVR.h | 55 ++++++++++++-- I2CManager_NonBlocking.h | 12 ++- I2CManager_Wire.h | 77 ++++++++++++++------ IODevice.h | 6 +- IO_GPIOBase.h | 20 ++--- IO_MCP23008.h | 8 +- IO_MCP23017.h | 8 +- IO_PCA9685.cpp | 14 ++-- IO_PCF8574.h | 8 +- IO_PCF8575.h | 8 +- IO_VL53L0X.h | 23 +++--- LiquidCrystal_I2C.cpp | 2 +- LiquidCrystal_I2C.h | 2 +- SSD1306Ascii.cpp | 69 +++++++++++------- SSD1306Ascii.h | 11 ++- 17 files changed, 390 insertions(+), 140 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index eafdb7068..b00fc8ae8 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -58,13 +58,46 @@ void I2CManagerClass::begin(void) { _setClock(100000); unsigned long originalTimeout = timeout; setTimeout(1000); // use 1ms timeout for probes + + #if defined(I2C_EXTENDED_ADDRESS) + // First switch off all multiplexer subbuses. + for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { + I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux + } + #endif + bool found = false; - for (byte addr=1; addr<127; addr++) { + for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { found = true; DIAG(F("I2C Device found at x%x"), addr); } } + +#if defined(I2C_EXTENDED_ADDRESS) + // Enumerate all I2C devices that are connected via multiplexer, + // i.e. respond when only one multiplexer has one subBus enabled + // and the device doesn't respond when the mux subBus is disabled. + for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { + uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; + if (exists(muxAddr)) { + for (uint8_t subBus=0; subBus<=7; subBus++) { + for (uint8_t addr=0x08; addr<0x78; addr++) { + if (exists({(I2CMux)muxNo, (I2CSubBus)subBus, addr}) + && !exists({(I2CMux)muxNo, SubBus_None, addr})) { + found = true; + DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}"), + muxNo, subBus, addr); + } + } + } + // Probe mux address again with SubBus_None to deselect all + // subBuses for that mux. Otherwise its devices will continue to + // respond when other muxes are being probed. + I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux + } + } +#endif if (!found) DIAG(F("No I2C Devices found")); _setClock(_clockSpeed); setTimeout(originalTimeout); // set timeout back to original @@ -92,7 +125,7 @@ void I2CManagerClass::forceClock(uint32_t speed) { // Check if specified I2C address is responding (blocking operation) // Returns I2C_STATUS_OK (0) if OK, or error code. // Suppress retries. If it doesn't respond first time it's out of the running. -uint8_t I2CManagerClass::checkAddress(uint8_t address) { +uint8_t I2CManagerClass::checkAddress(I2CAddress address) { I2CRB rb; rb.setWriteParams(address, NULL, 0); rb.suppressRetries(true); @@ -104,7 +137,7 @@ uint8_t I2CManagerClass::checkAddress(uint8_t address) { /*************************************************************************** * Write a transmission to I2C using a list of data (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) { +uint8_t I2CManagerClass::write(I2CAddress address, uint8_t nBytes, ...) { uint8_t buffer[nBytes]; va_list args; va_start(args, nBytes); @@ -117,7 +150,7 @@ uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) { /*************************************************************************** * Initiate a write to an I2C device (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) { +uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) { I2CRB req; uint8_t status = write(i2cAddress, writeBuffer, writeLen, &req); return finishRB(&req, status); @@ -126,7 +159,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[], /*************************************************************************** * Initiate a write from PROGMEM (flash) to an I2C device (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8_t dataLen) { +uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * data, uint8_t dataLen) { I2CRB req; uint8_t status = write_P(i2cAddress, data, dataLen, &req); return finishRB(&req, status); @@ -135,7 +168,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8 /*************************************************************************** * Initiate a write (optional) followed by a read from the I2C device (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, +uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen) { I2CRB req; @@ -146,7 +179,7 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r /*************************************************************************** * Overload of read() to allow command to be specified as a series of bytes (blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, +uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, uint8_t writeSize, ...) { va_list args; // Copy the series of bytes into an array. @@ -230,7 +263,7 @@ bool I2CRB::isBusy() { /*************************************************************************** * Helper functions to fill the I2CRequest structure with parameters. ***************************************************************************/ -void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen) { +void I2CRB::setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen) { this->i2cAddress = i2cAddress; this->writeLen = 0; this->readBuffer = readBuffer; @@ -239,7 +272,7 @@ void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readL this->status = I2C_STATUS_OK; } -void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, +void I2CRB::setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen) { this->i2cAddress = i2cAddress; this->writeBuffer = writeBuffer; @@ -250,7 +283,7 @@ void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t re this->status = I2C_STATUS_OK; } -void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) { +void I2CRB::setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) { this->i2cAddress = i2cAddress; this->writeBuffer = writeBuffer; this->writeLen = writeLen; diff --git a/I2CManager.h b/I2CManager.h index 0fcc6c6dc..4ecf15560 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -131,6 +131,124 @@ #define I2C_USE_INTERRUPTS #endif +// I2C Extended Address support I2C Multiplexers and allows various properties to be +// associated with an I2C address such as the MUX and SubBus. In the future, this +// may be extended to include multiple buses, and other features. +// Uncomment to enable extended address. +// +// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc, +// it should be cast to (int) to ensure that the address value is passed rather than +// the struct. + +//#define I2C_EXTENDED_ADDRESS + + +// Type to hold I2C address +#if defined(I2C_EXTENDED_ADDRESS) + +///////////////////////////////////////////////////////////////////////////////////// +// Extended I2C Address type to facilitate extended I2C addresses including +// I2C multiplexer support. +///////////////////////////////////////////////////////////////////////////////////// + +// Currently I2CAddress supports one I2C bus, with up to eight +// multipexers (MUX) attached. Each MUX can have up to eight sub-buses. +enum I2CMux : uint8_t { + I2CMux_0 = 0, + I2CMux_1 = 1, + I2CMux_2 = 2, + I2CMux_3 = 3, + I2CMux_4 = 4, + I2CMux_5 = 5, + I2CMux_6 = 6, + I2CMux_7 = 7, + I2CMux_None = 255, // Address doesn't need mux switching +}; + +enum I2CSubBus : uint8_t { + SubBus_0 = 0, // Enable individual sub-buses... + SubBus_1 = 1, + SubBus_2 = 2, + SubBus_3 = 3, + SubBus_4 = 4, + SubBus_5 = 5, + SubBus_6 = 6, + SubBus_7 = 7, + SubBus_None = 254, // Disable all sub-buses on selected mux + SubBus_All = 255, // Enable all sub-buses +}; + +// First MUX address (they range between 0x70-0x77). +#define I2C_MUX_BASE_ADDRESS 0x70 + +// Currently I2C address supports one I2C bus, with up to eight +// multiplexers (MUX) attached. Each MUX can have up to eight sub-buses. +// This structure could be extended in the future (if there is a need) +// to support 10-bit I2C addresses, different I2C clock speed for each +// sub-bus, multiple I2C buses, and other features not yet thought of. +struct I2CAddress { +private: + // Fields + I2CMux _muxNumber; + I2CSubBus _subBus; + uint8_t _deviceAddress; +public: + // Constructors + // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. + I2CAddress(I2CMux muxNumber, I2CSubBus subBus, uint8_t deviceAddress) { + _muxNumber = muxNumber; + _subBus = subBus; + _deviceAddress = deviceAddress; + } + + // Basic constructor + I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {} + + // For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70) + I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) : + I2CAddress(I2CMux_0, subBus, deviceAddress) {} + + // Conversion from uint8_t to I2CAddress + // For I2CAddress in form "0x23" + // (device assumed to be on the main I2C bus). + I2CAddress(const uint8_t deviceAddress) : + I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {} + + // For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector) + I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : + I2CAddress(muxNumber, subBus, 0x00) {} + + // Conversion operator from I2CAddress to uint8_t + // For "uint8_t address = i2cAddress;" syntax + // (device assumed to be on the main I2C bus or on a currently selected subbus. + operator uint8_t () const { return _deviceAddress; } + + // Comparison operator + int operator == (I2CAddress &a) const { + if (_deviceAddress != a._deviceAddress) + return false; // Different device address so no match + if (_muxNumber == I2CMux_None || a._muxNumber == I2CMux_None) + return true; // Same device address, one or other on main bus + if (_subBus == SubBus_None || a._subBus == SubBus_None) + return true; // Same device address, one or other on main bus + if (_muxNumber != a._muxNumber) + return false; // Connected to a subbus on a different mux + if (_subBus != a._subBus) + return false; // different subbus + return true; // Same address on same mux and same subbus + } + // Field accessors + I2CMux muxNumber() { return _muxNumber; } + I2CSubBus subBus() { return _subBus; } + uint8_t address() { return _deviceAddress; } +}; + +#else +// Legacy single-byte I2C address type for compact code and smooth changeover. +typedef uint8_t I2CAddress; +#endif // I2C_EXTENDED_ADDRESS + + // Status codes for I2CRB structures. enum : uint8_t { // Codes used by Wire and by native drivers @@ -181,19 +299,19 @@ class I2CRB { uint8_t wait(); bool isBusy(); - void setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen); - void setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); - void setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); + void setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen); + void setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen); + void setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen); void suppressRetries(bool suppress); uint8_t writeLen; uint8_t readLen; uint8_t operation; - uint8_t i2cAddress; + I2CAddress i2cAddress; uint8_t *readBuffer; const uint8_t *writeBuffer; #if !defined(I2C_USE_WIRE) - I2CRB *nextRequest; + I2CRB *nextRequest; // Used by non-blocking devices for I2CRB queue management. #endif }; @@ -210,25 +328,30 @@ class I2CManagerClass { // setTimeout sets the timout value for I2C transactions (milliseconds). void setTimeout(unsigned long); // Check if specified I2C address is responding. - uint8_t checkAddress(uint8_t address); - inline bool exists(uint8_t address) { + uint8_t checkAddress(I2CAddress address); + inline bool exists(I2CAddress address) { return checkAddress(address)==I2C_STATUS_OK; } + // Select/deselect Mux Sub-Bus (if using legacy addresses, just checks address) + // E.g. muxSelectSubBus({I2CMux_0, SubBus_3}); + uint8_t muxSelectSubBus(I2CAddress address) { + return checkAddress(address); + } // Write a complete transmission to I2C from an array in RAM - uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size); - uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb); + uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size); + uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb); // Write a complete transmission to I2C from an array in Flash - uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size); - uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb); + uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size); + uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb); // Write a transmission to I2C from a list of bytes. - uint8_t write(uint8_t address, uint8_t nBytes, ...); + uint8_t write(I2CAddress address, uint8_t nBytes, ...); // Write a command from an array in RAM and read response - uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, + uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, const uint8_t writeBuffer[]=NULL, uint8_t writeSize=0); - uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, + uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb); // Write a command from an arbitrary list of bytes and read response - uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize, + uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, uint8_t writeSize, ...); void queueRequest(I2CRB *req); @@ -280,6 +403,7 @@ class I2CManagerClass { static volatile uint8_t bytesToReceive; static volatile uint8_t operation; static volatile unsigned long startTime; + static volatile uint8_t muxSendStep; volatile uint32_t pendingClockSpeed = 0; diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 267a9211d..24f637674 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -98,7 +98,14 @@ void I2CManagerClass::I2C_sendStart() { bytesToReceive = currentRequest->readLen; rxCount = 0; txCount = 0; +#if defined(I2C_EXTENDED_ADDRESS) + if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { + // Send request to multiplexer + muxSendStep = 1; // When start bit interrupt comes in, send SLA+W to MUX + } +#endif TWCR = (1<i2cAddress.subBus(); + uint8_t subBusMask = (subBus==SubBus_All) ? 0xff : + (subBus==SubBus_None) ? 0x00 : + 1 << subBus; + TWDR = subBusMask; + TWCR = (1<i2cAddress.address() == 0 && bytesToSend == 0) { + // Send stop and post rb. + TWCR = (1<writeBuffer + (txCount++)); else TWDR = currentRequest->writeBuffer[txCount++]; bytesToSend--; - TWCR = (1<i2cAddress << 1) | 1; // SLA+R - else - TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W +#if defined(I2C_EXTENDED_ADDRESS) + if (muxSendStep == 1) { + muxSendStep = 2; + // Send multiplexer address first + uint8_t muxAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); + TWDR = (muxAddress << 1) | 0; // MUXaddress+Write + } else +#endif + { + // Set up address and R/W + uint8_t deviceAddress = currentRequest->i2cAddress; + if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend)) + TWDR = (deviceAddress << 1) | 1; // SLA+R + else + TWDR = (deviceAddress << 1) | 0; // SLA+W + } TWCR = (1<wait(); req->setWriteParams(i2cAddress, writeBuffer, writeLen); @@ -156,7 +156,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t *writeBuffer, u /*************************************************************************** * Initiate a write from PROGMEM (flash) to an I2C device (non-blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) { +uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) { // Make sure previous request has completed. req->wait(); req->setWriteParams(i2cAddress, writeBuffer, writeLen); @@ -169,7 +169,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer * Initiate a read from the I2C device, optionally preceded by a write * (non-blocking operation) ***************************************************************************/ -uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, +uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) { // Make sure previous request has completed. @@ -201,7 +201,7 @@ void I2CManagerClass::checkForTimeout() { unsigned long elapsed = micros() - startTime; if (elapsed > timeout) { #ifdef DIAG_IO - //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), t->i2cAddress, currentRequest); + //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), (int)t->i2cAddress, currentRequest); #endif // Excessive time. Dequeue request queueHead = t->nextRequest; @@ -305,4 +305,8 @@ volatile uint8_t I2CManagerClass::bytesToReceive; volatile unsigned long I2CManagerClass::startTime; uint8_t I2CManagerClass::retryCounter = 0; +#if defined(I2C_EXTENDED_ADDRESS) +volatile uint8_t I2CManagerClass::muxSendStep = 0; +#endif + #endif \ No newline at end of file diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 974956529..7ea75c5b8 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -65,18 +65,40 @@ void I2CManagerClass::setTimeout(unsigned long value) { #endif } +/******************************************************** + * Helper function for I2C Multiplexer operations + ********************************************************/ +#ifdef I2C_EXTENDED_ADDRESS +static uint8_t muxSelect(I2CAddress &address) { + // Select MUX sub bus. + Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+address.muxNumber()); + uint8_t data = address.subBus(); + Wire.write(&data, 1); + return Wire.endTransmission(true); // have to release I2C bus for it to work +} +#endif + /*************************************************************************** * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ -uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { +uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t status = I2C_STATUS_OK; uint8_t retryCount = 0; // If request fails, retry up to the defined limit, unless the NORETRY flag is set // in the request block. do { - Wire.beginTransmission(address); - if (size > 0) Wire.write(buffer, size); - status = Wire.endTransmission(); + status = I2C_STATUS_OK; +#ifdef I2C_EXTENDED_ADDRESS + if (address.muxNumber() != I2CMux_None) { + status = muxSelect(address); + } +#endif + // Only send new transaction if address and size are both nonzero. + if (status == I2C_STATUS_OK && address != 0 && size != 0) { + Wire.beginTransmission(address); + if (size > 0) Wire.write(buffer, size); + status = Wire.endTransmission(); + } } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); rb->status = status; @@ -86,7 +108,7 @@ uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t /*************************************************************************** * Initiate a write from PROGMEM (flash) to an I2C device (blocking operation on Wire) ***************************************************************************/ -uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { +uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { uint8_t ramBuffer[size]; const uint8_t *p1 = buffer; for (uint8_t i=0; i 0) { - Wire.beginTransmission(address); - Wire.write(writeBuffer, writeSize); - status = Wire.endTransmission(false); // Don't free bus yet + status = I2C_STATUS_OK; +#ifdef I2C_EXTENDED_ADDRESS + if (address.muxNumber() != I2CMux_None) { + status = muxSelect(address); } - if (status == I2C_STATUS_OK) { -#ifdef WIRE_HAS_TIMEOUT - Wire.clearWireTimeoutFlag(); #endif - Wire.requestFrom(address, (size_t)readSize); + // Only start new transaction if address and readSize are both nonzero. + if (status == I2C_STATUS_OK && address != 0 && writeSize > 0) { + if (writeSize > 0) { + Wire.beginTransmission(address); + Wire.write(writeBuffer, writeSize); + status = Wire.endTransmission(false); // Don't free bus yet + } + if (status == I2C_STATUS_OK) { #ifdef WIRE_HAS_TIMEOUT - if (!Wire.getWireTimeoutFlag()) { + Wire.clearWireTimeoutFlag(); + Wire.requestFrom(address, (size_t)readSize); + if (!Wire.getWireTimeoutFlag()) { + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; + } else { + status = I2C_STATUS_TIMEOUT; + } +#else + Wire.requestFrom(address, (size_t)readSize); + while (Wire.available() && nBytes < readSize) + readBuffer[nBytes++] = Wire.read(); + if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; #endif - while (Wire.available() && nBytes < readSize) - readBuffer[nBytes++] = Wire.read(); - if (nBytes < readSize) status = I2C_STATUS_TRUNCATED; -#ifdef WIRE_HAS_TIMEOUT - } else { - status = I2C_STATUS_TIMEOUT; } -#endif } } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); @@ -137,6 +169,7 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea return I2C_STATUS_OK; } + /*************************************************************************** * Function to queue a request block and initiate operations. * diff --git a/IODevice.h b/IODevice.h index e451870f7..a546b6d0f 100644 --- a/IODevice.h +++ b/IODevice.h @@ -239,7 +239,7 @@ class IODevice { // Common object fields. VPIN _firstVpin; int _nPins; - uint8_t _I2CAddress; + I2CAddress _I2CAddress; // Flag whether the device supports callbacks. bool _hasCallback = false; @@ -272,7 +272,7 @@ class IODevice { class PCA9685 : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t I2CAddress); + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress); enum ProfileType : uint8_t { Instant = 0, // Moves immediately between positions (if duration not specified) UseDuration = 0, // Use specified duration @@ -285,7 +285,7 @@ class PCA9685 : public IODevice { private: // Constructor - PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress); + PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress); // Device-specific initialisation void _begin() override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 1a66b3deb..2d9f8abeb 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -34,7 +34,7 @@ class GPIOBase : public IODevice { protected: // Constructor - GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin); + GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin); // Device-specific initialisation void _begin() override; // Device-specific pin configuration function. @@ -80,11 +80,11 @@ class GPIOBase : public IODevice { // 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, I2CAddress i2cAddress, int interruptPin) : IODevice(firstVpin, nPins) { _deviceName = deviceName; - _I2CAddress = I2CAddress; + _I2CAddress = i2cAddress; _gpioInterruptPin = interruptPin; _hasCallback = true; // Add device to list of devices. @@ -110,7 +110,7 @@ void GPIOBase::_begin() { _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { - DIAG(F("%S I2C:x%x Device not detected"), _deviceName, _I2CAddress); + DIAG(F("%S I2C:x%x Device not detected"), _deviceName, (int)_I2CAddress); _deviceState = DEVSTATE_FAILED; } } @@ -125,7 +125,7 @@ bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun bool pullup = params[0]; int pin = vpin - _firstVpin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, pullup); + DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, pullup); #endif uint16_t mask = 1 << pin; if (pullup) @@ -155,7 +155,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { _deviceState = DEVSTATE_NORMAL; } else { _deviceState = DEVSTATE_FAILED; - DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, _I2CAddress, status, + DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); } _processCompletion(status); @@ -178,7 +178,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { #ifdef DIAG_IO if (differences) - DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState); + DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); #endif } @@ -199,7 +199,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { template void GPIOBase::_display() { - DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress, + DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -208,7 +208,7 @@ void GPIOBase::_write(VPIN vpin, int value) { int pin = vpin - _firstVpin; T mask = 1 << pin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, value); + DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, value); #endif // Set port mode output if currently not output mode @@ -244,7 +244,7 @@ int GPIOBase::_read(VPIN vpin) { // 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); + DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); #endif } return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) diff --git a/IO_MCP23008.h b/IO_MCP23008.h index bf4d521a1..188d3ea80 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -25,14 +25,14 @@ class MCP23008 : public GPIOBase { public: - static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin); + static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new MCP23008(firstVpin, nPins, i2cAddress, interruptPin); } private: // Constructor - MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) { + MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); diff --git a/IO_MCP23017.h b/IO_MCP23017.h index 65769f600..f8176fa2c 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -30,14 +30,14 @@ class MCP23017 : public GPIOBase { public: - static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(vpin, nPins, I2CAddress)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin); + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, min(nPins,16), i2cAddress, interruptPin); } private: // Constructor - MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin) + MCP23017(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 3d7c34785..13e49689f 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -38,8 +38,8 @@ static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C static void writeRegister(byte address, byte reg, byte value); // Create device driver instance. -void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685(firstVpin, nPins, I2CAddress); +void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress); } // Configure a port on the PCA9685. @@ -73,10 +73,10 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i } // Constructor -PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) { +PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; _nPins = min(nPins, 16); - _I2CAddress = I2CAddress; + _I2CAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. for (int i=0; i<_nPins; i++) @@ -239,13 +239,13 @@ void PCA9685::updatePosition(uint8_t pin) { // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void PCA9685::writeDevice(uint8_t pin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); + DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); #endif // Wait for previous request to complete 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)); + DIAG(F("PCA9685 I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); } else { // Set up new request. outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; @@ -259,7 +259,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) { // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_PCF8574.h b/IO_PCF8574.h index beeeb7ce9..0815511cd 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -43,13 +43,13 @@ class PCF8574 : public GPIOBase { public: - static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin); + static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin); } private: - PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) + PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); } diff --git a/IO_PCF8575.h b/IO_PCF8575.h index 1b271ecc1..c749e5632 100644 --- a/IO_PCF8575.h +++ b/IO_PCF8575.h @@ -44,13 +44,13 @@ class PCF8575 : public GPIOBase { public: - static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), I2CAddress, interruptPin); + static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), i2cAddress, interruptPin); } private: - PCF8575(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, I2CAddress, interruptPin) + PCF8575(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, i2cAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index cb3b6f7f5..bcb937ec9 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -97,7 +97,6 @@ class VL53L0X : public IODevice { private: - uint8_t _i2cAddress; uint16_t _ambient; uint16_t _distance; uint16_t _signal; @@ -145,7 +144,7 @@ class VL53L0X : public IODevice { 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; + _I2CAddress = i2cAddress; _onThreshold = onThreshold; _offThreshold = offThreshold; _xshutPin = xshutPin; @@ -157,7 +156,7 @@ class VL53L0X : public IODevice { // the device will not respond on its default address if it has // already been changed. Therefore, we skip the address configuration if the // desired address is already responding on the I2C bus. - if (_xshutPin == VPIN_NONE && I2CManager.exists(_i2cAddress)) { + if (_xshutPin == VPIN_NONE && I2CManager.exists(_I2CAddress)) { // Device already present on this address, so skip the address initialisation. _nextState = STATE_CONFIGUREDEVICE; } @@ -194,7 +193,7 @@ class VL53L0X : public IODevice { case STATE_CONFIGUREADDRESS: // 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); + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); _addressConfigInProgress = false; _nextState = STATE_SKIP; break; @@ -204,7 +203,7 @@ class VL53L0X : public IODevice { break; case STATE_CONFIGUREDEVICE: // On next entry, check if device address has been set. - if (I2CManager.exists(_i2cAddress)) { + if (I2CManager.exists(_I2CAddress)) { #ifdef DIAG_IO _display(); #endif @@ -213,7 +212,7 @@ class VL53L0X : public IODevice { read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); _nextState = STATE_INITIATESCAN; } else { - DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); + DIAG(F("VL53L0X I2C:x%x device not responding"), (int)_I2CAddress); _deviceState = DEVSTATE_FAILED; _nextState = STATE_FAILED; } @@ -222,7 +221,7 @@ class VL53L0X : public IODevice { // Not scanning, so initiate a scan _outBuffer[0] = VL53L0X_REG_SYSRANGE_START; _outBuffer[1] = 0x01; - I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb); + I2CManager.write(_I2CAddress, _outBuffer, 2, &_rb); _nextState = STATE_CHECKSTATUS; break; case STATE_CHECKSTATUS: @@ -238,7 +237,7 @@ class VL53L0X : public IODevice { case STATE_GETRESULTS: // Ranging completed. Request results _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS; - I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb); + I2CManager.read(_I2CAddress, _inBuffer, 12, _outBuffer, 1, &_rb); _nextState = 3; delayUntil(currentMicros + 5000); // Allow 5ms to get data _nextState = STATE_DECODERESULTS; @@ -278,7 +277,7 @@ class VL53L0X : public IODevice { // Function to report a failed I2C operation. Put the device off-line. void reportError(uint8_t status) { - DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("VL53L0X I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; _value = false; } @@ -308,7 +307,7 @@ class VL53L0X : public IODevice { void _display() override { DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), - _i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, + (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -322,11 +321,11 @@ class VL53L0X : public IODevice { uint8_t outBuffer[2]; outBuffer[0] = reg; outBuffer[1] = data; - return I2CManager.write(_i2cAddress, outBuffer, 2); + return I2CManager.write(_I2CAddress, outBuffer, 2); } uint8_t read_reg(uint8_t reg) { // read byte from register and return value - I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1); + I2CManager.read(_I2CAddress, _inBuffer, 1, ®, 1); return _inBuffer[0]; } }; diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index e036b98b0..b18614f3c 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -41,7 +41,7 @@ // can't assume that its in that state when a sketch starts (and the // LiquidCrystal constructor is called). -LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols, +LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, uint8_t lcd_rows) { _Addr = lcd_Addr; lcdRows = lcd_rows; diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 6881a69b0..20dc1267e 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -64,7 +64,7 @@ class LiquidCrystal_I2C : public LCDDisplay { public: - LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); + LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); void begin(); void clearNative() override; void setRowNative(byte line) override; diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 2fc23c2c1..7373a74a9 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,9 +144,32 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor +SSD1306AsciiWire::SSD1306AsciiWire() {} + +// CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed + + // Probe for I2C device on 0x3c and 0x3d. + for (uint8_t address = 0x3c; address <= 0x3d; address++) { + if (I2CManager.exists(address)) { + begin(address, width, height); + // Set singleton Address so CS is able to call it. + lcdDisplay = this; + return; + } + } + DIAG(F("OLED display not found")); +} + +bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { + if (m_initialised) return true; + + m_i2cAddr = address; m_displayWidth = width; m_displayHeight = height; + // Set size in characters in base class lcdRows = height / 8; lcdCols = width / 6; @@ -154,35 +177,25 @@ SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { m_row = 0; m_colOffset = 0; - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed - for (byte address = 0x3c; address <= 0x3d; address++) { - if (I2CManager.exists(address)) { - m_i2cAddr = address; - if (m_displayWidth==132 && m_displayHeight==64) { - // SH1106 display. This uses 128x64 centered within a 132x64 OLED. - m_colOffset = 2; - I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init)); - } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { - // SSD1306 128x64 or 128x32 - I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); - if (m_displayHeight == 32) - I2CManager.write(address, 5, 0, // Set command mode - SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 - SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap - } else { - DIAG(F("OLED configuration option not recognised")); - return; - } - // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address); - // Set singleton address - lcdDisplay = this; - clear(); - return; - } + if (m_displayWidth==132 && m_displayHeight==64) { + // SH1106 display. This uses 128x64 centered within a 132x64 OLED. + m_colOffset = 2; + I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init)); + } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { + // SSD1306 128x64 or 128x32 + I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); + if (m_displayHeight == 32) + I2CManager.write(m_i2cAddr, 5, 0, // Set command mode + SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 + SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap + } else { + DIAG(F("OLED configuration option not recognised")); + return false; } - DIAG(F("OLED display not found")); + // Device found + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); + clear(); + return true; } /* Clear screen by writing blank pixels. */ diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h index 312a62fa9..f4c9ba75e 100644 --- a/SSD1306Ascii.h +++ b/SSD1306Ascii.h @@ -36,11 +36,12 @@ class SSD1306AsciiWire : public LCDDisplay { public: - // Constructor - SSD1306AsciiWire(int width, int height); + // Constructors + SSD1306AsciiWire(int width, int height); // Auto-detects I2C address + SSD1306AsciiWire(); // Requires call to 'begin()' // Initialize the display controller. - void begin(uint8_t i2cAddr); + bool begin(I2CAddress address, int width, int height); // Clear the display and set the cursor to (0, 0). void clearNative() override; @@ -66,6 +67,8 @@ class SSD1306AsciiWire : public LCDDisplay { uint8_t m_colOffset = 0; // Current font. const uint8_t* const m_font = System5x7; + // Flag to prevent calling begin() twice + uint8_t m_initialised = false; // Only fixed size 5x7 fonts in a 6x8 cell are supported. static const uint8_t fontWidth = 5; @@ -74,7 +77,7 @@ class SSD1306AsciiWire : public LCDDisplay { static const uint8_t m_fontFirstChar = 0x20; static const uint8_t m_fontCharCount = 0x61; - uint8_t m_i2cAddr; + I2CAddress m_i2cAddr; I2CRB requestBlock; uint8_t outputBuffer[fontWidth+letterSpacing+1]; From 754639c7e3af71d448c6817fecb6315d79dc5678 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 6 Feb 2023 19:39:25 +1000 Subject: [PATCH 521/870] Update version --- version.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index c37a1c44e..d95e01dd4 100644 --- a/version.h +++ b/version.h @@ -5,8 +5,9 @@ #define VERSION "4.2.15" -// 4.2.15 Separate Servo from PCA9685 -// Add PWM support to EX-IOExpander +// 4.2.15 Add Servo device driver with PCA9685_basic driver +// Add basic experimental PWM support to EX-IOExpander +// EX-IOExpander 0.0.14 minimum required // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From c870940ddefb5e4f2ff58e09717b95b2d225f5d8 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 7 Feb 2023 07:32:16 +1000 Subject: [PATCH 522/870] Add extra error checking --- IO_EXIOExpander.h | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 59684cd7c..8ec1f4818 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -116,8 +116,13 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] == EXIORDY) { + return true; + } else { + DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); + return false; + } } else { return false; } @@ -128,7 +133,13 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; - I2CManager.write(_i2cAddress, _command2Buffer, 2); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2); + if (_command1Buffer[0] == EXIORDY) { + return true; + } else { + DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin); + return false; + } return true; } @@ -167,7 +178,10 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + } } void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { From d5a394d4e6d568a1d517b2906273818a87c4003f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 14:55:14 +0000 Subject: [PATCH 523/870] Prepare HAL device drivers to support Extended I2C Addresses Update I2C addresses of HAL devices to type I2CAddress (to support extended address functions). Cast I2CAddress variables in DIAG calls to (int). Remove uses of max() function (not available on some platforms. --- IO_AnalogueInputs.h | 21 ++++++++++----------- IO_EXIOExpander.h | 12 ++++++------ IO_EXTurntable.h | 10 +++++----- IO_GPIOBase.h | 20 ++++++++------------ IO_MCP23008.h | 6 +++--- IO_MCP23017.h | 10 +++++----- IO_PCA9685.cpp | 4 ++-- IO_PCF8574.h | 15 ++++++++------- IO_PCF8575.h | 15 +++++++++------ IO_VL53L0X.h | 16 ++++++++++++---- 10 files changed, 68 insertions(+), 61 deletions(-) diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 5f9d0e0b3..77afc5847 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -59,14 +59,14 @@ **********************************************************************************************/ class ADS111x: public IODevice { public: - static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress); } private: - ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) { + ADS111x(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; - _nPins = min(nPins,4); - _i2cAddress = i2cAddress; + _nPins = (nPins > 4) ? 4 : nPins; + _I2CAddress = i2cAddress; _currentPin = 0; for (int8_t i=0; i<_nPins; i++) _value[i] = -1; @@ -79,13 +79,13 @@ class ADS111x: public IODevice { // processing. So stick to fast mode (400kHz maximum). I2CManager.setClock(400000); // Initialise ADS device - if (I2CManager.exists(_i2cAddress)) { + if (I2CManager.exists(_I2CAddress)) { _nextState = STATE_STARTSCAN; #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress); + DIAG(F("ADS111x device not found, I2C:%x"), (int)_I2CAddress); _deviceState = DEVSTATE_FAILED; } } @@ -103,7 +103,7 @@ class ADS111x: public IODevice { _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); + I2CManager.write(_I2CAddress, _outBuffer, 3, &_i2crb); delayUntil(currentMicros + scanInterval); _nextState = STATE_STARTREAD; @@ -112,7 +112,7 @@ class ADS111x: public IODevice { case STATE_STARTREAD: // Reading the pin value _outBuffer[0] = 0x00; // Conversion register address - I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register + I2CManager.read(_I2CAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register _nextState = STATE_GETVALUE; break; @@ -131,7 +131,7 @@ class ADS111x: public IODevice { break; } } else { // error status - DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("ADS111x I2C:x%d Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } } @@ -142,7 +142,7 @@ class ADS111x: public IODevice { } void _display() override { - DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1, + DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } @@ -159,7 +159,6 @@ class ADS111x: public IODevice { 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 diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 1e20fac99..3b13839c8 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -54,13 +54,13 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, int numDigitalPins, int numAnaloguePins) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress, int numDigitalPins, int numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; @@ -79,7 +79,7 @@ class EXIOExpander : public IODevice { // Send config, if EXIORDY returned, we're good, otherwise go offline I2CManager.read(_i2cAddress, _digitalInBuffer, 1, _digitalOutBuffer, 3); if (_digitalInBuffer[0] != EXIORDY) { - DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); + DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), (int)_i2cAddress); _deviceState = DEVSTATE_FAILED; return; } @@ -91,12 +91,12 @@ class EXIOExpander : public IODevice { _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"), - _i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); + (int)_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("EX-IOExpander device not found, I2C:x%x"), _i2cAddress); + DIAG(F("EX-IOExpander device not found, I2C:x%x"), (int)_i2cAddress); _deviceState = DEVSTATE_FAILED; } } @@ -163,7 +163,7 @@ class EXIOExpander : public IODevice { _lastAnalogue = _firstVpin + _nPins - 1; } DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), - _i2cAddress, _majorVer, _minorVer, _patchVer, + (int)_i2cAddress, _majorVer, _minorVer, _patchVer, _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, _numAnaloguePins, _firstAnalogue, _lastAnalogue, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index 2dc9e6b92..5b501b386 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -35,12 +35,12 @@ #include "I2CManager.h" #include "DIAG.h" -void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { +void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { new EXTurntable(firstVpin, nPins, I2CAddress); } // Constructor -EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) { +EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { _firstVpin = firstVpin; _nPins = nPins; _I2CAddress = I2CAddress; @@ -106,15 +106,15 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), - _I2CAddress, stepsMSB, stepsLSB, activity); + (int)_I2CAddress, stepsMSB, stepsLSB, activity); #endif _stepperStatus = 1; // Tell the device driver Turntable-EX is busy - I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity); + I2CManager.write((int)_I2CAddress, 3, stepsMSB, stepsLSB, activity); } // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin, + DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 2d9f8abeb..17e72d67e 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -49,13 +49,13 @@ class GPIOBase : public IODevice { // Data fields // Allocate enough space for all input pins - T _portInputState; - T _portOutputState; - T _portMode; - T _portPullup; - T _portInUse; - // Interval between refreshes of each input port - static const int _portTickTime = 4000; + T _portInputState; // 1=high (inactive), 0=low (activated) + T _portOutputState; // 1 =high, 0=low + T _portMode; // 0=input, 1=output + T _portPullup; // 0=nopullup, 1=pullup + T _portInUse; // 0=not in use, 1=in use + // Target interval between refreshes of each input port + static const int _portTickTime = 4000; // 4ms // Virtual functions for interfacing with I2C GPIO Device virtual void _writeGpioPort() = 0; @@ -69,10 +69,6 @@ class GPIOBase : public IODevice { I2CRB requestBlock; FSH *_deviceName; -#if defined(ARDUINO_ARCH_ESP32) - // workaround: Has somehow no min function for all types - static inline T min(T a, int b) { return a < b ? a : b; }; -#endif }; // Because class GPIOBase is a template, the implementation (below) must be contained within the same @@ -83,6 +79,7 @@ template GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin) : IODevice(firstVpin, nPins) { + if (_nPins > (int)sizeof(T)*8) _nPins = sizeof(T)*8; // Ensure nPins is consistent with the number of bits in T _deviceName = deviceName; _I2CAddress = i2cAddress; _gpioInterruptPin = interruptPin; @@ -117,7 +114,6 @@ void GPIOBase::_begin() { // Configuration parameters for inputs: // params[0]: enable pullup -// params[1]: invert input (optional) template bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { if (configType != CONFIGURE_INPUT) return false; diff --git a/IO_MCP23008.h b/IO_MCP23008.h index 188d3ea80..9598a504f 100644 --- a/IO_MCP23008.h +++ b/IO_MCP23008.h @@ -32,7 +32,7 @@ class MCP23008 : public GPIOBase { private: // Constructor MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) { + : GPIOBase((FSH *)F("MCP23008"), firstVpin, nPins, i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), outputBuffer, sizeof(outputBuffer)); @@ -60,7 +60,7 @@ class MCP23008 : public GPIOBase { if (immediate) { uint8_t buffer; I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO); - _portInputState = buffer; + _portInputState = buffer | _portMode; } else { // Queue new request requestBlock.wait(); // Wait for preceding operation to complete @@ -71,7 +71,7 @@ class MCP23008 : public GPIOBase { // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = inputBuffer[0]; + _portInputState = inputBuffer[0] | _portMode; else _portInputState = 0xff; } diff --git a/IO_MCP23017.h b/IO_MCP23017.h index f8176fa2c..7bdc28873 100644 --- a/IO_MCP23017.h +++ b/IO_MCP23017.h @@ -30,13 +30,13 @@ class MCP23017 : public GPIOBase { public: - static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, min(nPins,16), i2cAddress, interruptPin); + static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, nPins, i2cAddress, interruptPin); } private: // Constructor - MCP23017(VPIN vpin, int nPins, I2CAddress i2cAddress, int interruptPin=-1) + MCP23017(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin) { requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), @@ -65,7 +65,7 @@ class MCP23017 : public GPIOBase { if (immediate) { uint8_t buffer[2]; I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA); - _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode; } else { // Queue new request requestBlock.wait(); // Wait for preceding operation to complete @@ -76,7 +76,7 @@ class MCP23017 : public GPIOBase { // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode; else _portInputState = 0xffff; } diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 13e49689f..f7a6882e9 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -75,7 +75,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i // Constructor PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; - _nPins = min(nPins, 16); + _nPins = (nPins > 16) ? 16 : nPins; _I2CAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. @@ -272,5 +272,5 @@ static void writeRegister(byte address, byte reg, byte value) { // The profile below is in the range 0-100% and should be combined with the desired limits // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. -const byte FLASH PCA9685::_bounceProfile[30] = +const uint8_t FLASH PCA9685::_bounceProfile[30] = {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; diff --git a/IO_PCF8574.h b/IO_PCF8574.h index 0815511cd..d71a32a2f 100644 --- a/IO_PCF8574.h +++ b/IO_PCF8574.h @@ -49,14 +49,16 @@ class PCF8574 : public GPIOBase { private: PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) - : GPIOBase((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), i2cAddress, interruptPin) + : GPIOBase((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin) { requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); } - // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. + // The pin state is driven '1' if the pin is an input, or if it is an output set to 1. + // Unused pins are driven '0'. void _writeGpioPort() override { - I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); + I2CManager.write(_I2CAddress, 1, (_portOutputState | ~_portMode) & _portInUse); } // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. @@ -64,9 +66,8 @@ class PCF8574 : public GPIOBase { // and enable pull-up. void _writePullups() override { } - // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. void _writePortModes() override { - I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode); + _writeGpioPort(); } // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. @@ -76,7 +77,7 @@ class PCF8574 : public GPIOBase { if (immediate) { uint8_t buffer[1]; I2CManager.read(_I2CAddress, buffer, 1); - _portInputState = buffer[0]; + _portInputState = buffer[0] | _portMode; } else { requestBlock.wait(); // Wait for preceding operation to complete // Issue new request to read GPIO register @@ -87,7 +88,7 @@ class PCF8574 : public GPIOBase { // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = inputBuffer[0]; + _portInputState = inputBuffer[0] | _portMode; else _portInputState = 0xff; } diff --git a/IO_PCF8575.h b/IO_PCF8575.h index c749e5632..0674617b1 100644 --- a/IO_PCF8575.h +++ b/IO_PCF8575.h @@ -45,7 +45,7 @@ class PCF8575 : public GPIOBase { public: static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { - if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, min(nPins,(uint8_t)16), i2cAddress, interruptPin); + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, nPins, i2cAddress, interruptPin); } private: @@ -55,9 +55,12 @@ class PCF8575 : public GPIOBase { requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); } - // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. + // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. + // The pin state is driven '1' if the pin is an input, or if it is an output set to 1. + // Unused pins are driven '0'. void _writeGpioPort() override { - I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + uint16_t bits = (_portOutputState | ~_portMode) & _portInUse; + I2CManager.write(_I2CAddress, 2, bits, bits>>8); } // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. @@ -67,7 +70,7 @@ class PCF8575 : public GPIOBase { // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. void _writePortModes() override { - I2CManager.write(_I2CAddress, 2, _portOutputState | ~_portMode, (_portOutputState | ~_portMode)>>8); + _writeGpioPort(); } // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. @@ -77,7 +80,7 @@ class PCF8575 : public GPIOBase { if (immediate) { uint8_t buffer[2]; I2CManager.read(_I2CAddress, buffer, 2); - _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + _portInputState = (((uint16_t)buffer[1]<<8) | buffer[0]) | _portMode; } else { requestBlock.wait(); // Wait for preceding operation to complete // Issue new request to read GPIO register @@ -88,7 +91,7 @@ class PCF8575 : public GPIOBase { // This function is invoked when an I/O operation on the requestBlock completes. void _processCompletion(uint8_t status) override { if (status == I2C_STATUS_OK) - _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode; else _portInputState = 0xffff; } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index bcb937ec9..86d9624a8 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -136,14 +136,14 @@ class VL53L0X : public IODevice { public: - static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin); } protected: - VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { + VL53L0X(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) { _firstVpin = firstVpin; - _nPins = min(nPins, 3); + _nPins = (nPins > 3) ? 3 : nPins; _I2CAddress = i2cAddress; _onThreshold = onThreshold; _offThreshold = offThreshold; @@ -193,7 +193,15 @@ class VL53L0X : public IODevice { case STATE_CONFIGUREADDRESS: // 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); + { + #if defined(I2C_EXTENDED_ADDRESS) + // Add subbus reference for desired address to the device default address. + I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS}; + I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress()); + #else + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); + #endif + } _addressConfigInProgress = false; _nextState = STATE_SKIP; break; From 9435869ee367221a4b316ae34e8397d14b7dd939 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 15:04:03 +0000 Subject: [PATCH 524/870] Prepare HAL device drivers to support Extended I2C Addresses Cast I2CAddress variables in DIAG calls to (int). --- IODevice.cpp | 2 +- IO_AnalogueInputs.h | 2 +- IO_RotaryEncoder.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index ea4301fb3..be26b9265 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -322,7 +322,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) } // Check for overlapping I2C address if (i2cAddress && dev->_I2CAddress==i2cAddress) { - DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress); + DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),(int)i2cAddress); return false; } } diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 77afc5847..a03d90fbf 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -131,7 +131,7 @@ class ADS111x: public IODevice { break; } } else { // error status - DIAG(F("ADS111x I2C:x%d Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("ADS111x I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index b4d538cfc..6de97afb3 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,7 +104,7 @@ class RotaryEncoder : public IODevice { } void _display() override { - DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress, _majorVer, _minorVer, _patchVer, + DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), (int)_I2CAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } From 278a9c52a64d979ec99adc0535378d492f7b338e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 15:07:29 +0000 Subject: [PATCH 525/870] Refactor SSD1306 initialisation to support different initialisation sequences. To support a HAL Display driver, the SSD1306 driver can be created (new) and then the I2C address assigned explicitly in the begin() call. The original approach of looking for the I2C device address has also been retained in a different constructor. --- SSD1306Ascii.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 7373a74a9..f57252f00 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,7 +144,10 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() {} +SSD1306AsciiWire::SSD1306AsciiWire() { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed +} // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { @@ -193,7 +196,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); clear(); return true; } @@ -203,8 +206,9 @@ void SSD1306AsciiWire::clearNative() { const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased - for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { - uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; + for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes) { + uint8_t len = m_displayWidth-c; + if (len > maxBytes) len = maxBytes; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns } } From 261ccf2f3b7b0eff4e684ffe1ccf25116b4aaa9a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 15:09:21 +0000 Subject: [PATCH 526/870] HAL - change variable type for PCA9685 data. --- IODevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IODevice.h b/IODevice.h index a546b6d0f..d9c851ff7 100644 --- a/IODevice.h +++ b/IODevice.h @@ -315,7 +315,7 @@ class PCA9685 : public IODevice { struct ServoData *_servoData [16]; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const byte FLASH _bounceProfile[30]; + static const uint8_t FLASH _bounceProfile[30]; const unsigned int refreshInterval = 50; // refresh every 50ms From cb287f23a4164fd61e744abd560b394698f32e07 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 16:38:30 +0000 Subject: [PATCH 527/870] I2CManager: Add support for I2C Multiplexers to Wire and AVR. AVR Native (non-blocking) driver now supports up to 8 I2C Multiplexers, as does any controller that uses Wire for I2C. Other native drivers will be updated in due course. --- I2CManager.cpp | 60 +++++++++++-- I2CManager.h | 28 +++++-- I2CManager_AVR.h | 177 ++++++++++++++++++++++++++++++--------- I2CManager_Mega4809.h | 11 ++- I2CManager_NonBlocking.h | 16 +++- I2CManager_SAMD.h | 14 ++-- I2CManager_Wire.h | 65 +++++++++----- 7 files changed, 279 insertions(+), 92 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index b00fc8ae8..00aa71b83 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -41,6 +41,28 @@ #endif +// Helper function for listing device types +static const FSH * guessI2CDeviceType(uint8_t address) { + if (address >= 0x20 && address <= 0x26) + return F("GPIO Expander"); + else if (address == 0x27) + return F("GPIO Expander or LCD Display"); + else if (address == 0x29) + return F("Time-of-flight sensor"); + else if (address >= 0x3c && address <= 0x3c) + return F("OLED Display"); + else if (address >= 0x48 && address <= 0x4f) + return F("Analogue Inputs or PWM"); + else if (address >= 0x40 && address <= 0x4f) + return F("PWM"); + else if (address >= 0x50 && address <= 0x5f) + return F("EEPROM"); + else if (address >= 0x70 && address <= 0x77) + return F("I2C Mux"); + else + return F("?"); +} + // If not already initialised, initialise I2C void I2CManagerClass::begin(void) { if (!_beginCompleted) { @@ -60,34 +82,46 @@ void I2CManagerClass::begin(void) { setTimeout(1000); // use 1ms timeout for probes #if defined(I2C_EXTENDED_ADDRESS) - // First switch off all multiplexer subbuses. + // First count the multiplexers and switch off all subbuses + _muxCount = 0; for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { - I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux + if (I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None})==I2C_STATUS_OK) + _muxCount++; } #endif + // Enumerate devices that are visible bool found = false; for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { found = true; - DIAG(F("I2C Device found at x%x"), addr); + DIAG(F("I2C Device found at x%x, %S?"), addr, guessI2CDeviceType(addr)); } } #if defined(I2C_EXTENDED_ADDRESS) // Enumerate all I2C devices that are connected via multiplexer, - // i.e. respond when only one multiplexer has one subBus enabled + // i.e. that respond when only one multiplexer has one subBus enabled // and the device doesn't respond when the mux subBus is disabled. for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; if (exists(muxAddr)) { + // Select Mux Subbus for (uint8_t subBus=0; subBus<=7; subBus++) { + muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); for (uint8_t addr=0x08; addr<0x78; addr++) { - if (exists({(I2CMux)muxNo, (I2CSubBus)subBus, addr}) - && !exists({(I2CMux)muxNo, SubBus_None, addr})) { - found = true; - DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}"), - muxNo, subBus, addr); + if (exists(addr)) { + // De-select subbus + muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); + if (!exists(addr)) { + // Device responds when subbus selected but not when + // subbus disabled - ergo it must be on subbus! + found = true; + DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}, %S?"), + muxNo, subBus, addr, guessI2CDeviceType(addr)); + } + // Re-select subbus + muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); } } } @@ -232,6 +266,14 @@ I2CManagerClass I2CManager = I2CManagerClass(); // try, and failure from timeout does not get retried. unsigned long I2CManagerClass::timeout = 100000UL; +#if defined(I2C_EXTENDED_ADDRESS) +// Count of I2C multiplexers found when initialising. If there is only one +// MUX then the subbus does not de-selecting after use; however, if there +// is two or more, then the subbus must be deselected to avoid multiple +// sub-bus legs on different multiplexers being accessible simultaneously. +uint8_t I2CManagerClass::_muxCount = 0; +#endif + ///////////////////////////////////////////////////////////////////////////// // Helper functions associated with I2C Request Block diff --git a/I2CManager.h b/I2CManager.h index 4ecf15560..2a3d80c1e 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -195,7 +195,7 @@ struct I2CAddress { public: // Constructors // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. - I2CAddress(I2CMux muxNumber, I2CSubBus subBus, uint8_t deviceAddress) { + I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) { _muxNumber = muxNumber; _subBus = subBus; _deviceAddress = deviceAddress; @@ -218,6 +218,11 @@ struct I2CAddress { I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : I2CAddress(muxNumber, subBus, 0x00) {} + // For I2CAddress in form "{i2cAddress, deviceAddress}" + // where deviceAddress is to be on the same subbus as i2cAddress. + I2CAddress(I2CAddress firstAddress, uint8_t newDeviceAddress) : + I2CAddress(firstAddress._muxNumber, firstAddress._subBus, newDeviceAddress) {} + // Conversion operator from I2CAddress to uint8_t // For "uint8_t address = i2cAddress;" syntax // (device assumed to be on the main I2C bus or on a currently selected subbus. @@ -240,7 +245,7 @@ struct I2CAddress { // Field accessors I2CMux muxNumber() { return _muxNumber; } I2CSubBus subBus() { return _subBus; } - uint8_t address() { return _deviceAddress; } + uint8_t deviceAddress() { return _deviceAddress; } }; #else @@ -271,6 +276,7 @@ enum : uint8_t { I2C_STATE_ACTIVE=253, I2C_STATE_FREE=254, I2C_STATE_CLOSING=255, + I2C_STATE_COMPLETED=252, }; typedef enum : uint8_t @@ -369,20 +375,23 @@ class I2CManagerClass { bool _beginCompleted = false; bool _clockSpeedFixed = false; static uint8_t retryCounter; // Count of retries -#if defined(__arm__) - uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32 -#else - uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino. -#endif + // Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD + // and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds + // will not be useful. The speed can be overridden by I2CManager::forceClock(). + uint32_t _clockSpeed = I2C_FREQ; static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. - // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); void _initialise(); void _setClock(unsigned long); +#if defined(I2C_EXTENDED_ADDRESS) + static uint8_t _muxCount; + uint8_t getMuxCount() { return _muxCount; } +#endif + #if !defined(I2C_USE_WIRE) // I2CRB structs are queued on the following two links. // If there are no requests, both are NULL. @@ -395,6 +404,7 @@ class I2CManagerClass { static I2CRB * volatile queueHead; static I2CRB * volatile queueTail; static volatile uint8_t state; + static uint8_t completionStatus; static I2CRB * volatile currentRequest; static volatile uint8_t txCount; @@ -403,7 +413,7 @@ class I2CManagerClass { static volatile uint8_t bytesToReceive; static volatile uint8_t operation; static volatile unsigned long startTime; - static volatile uint8_t muxSendStep; + static volatile uint8_t muxPhase; volatile uint32_t pendingClockSpeed = 0; diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 24f637674..e5e186399 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -101,8 +101,9 @@ void I2CManagerClass::I2C_sendStart() { #if defined(I2C_EXTENDED_ADDRESS) if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { // Send request to multiplexer - muxSendStep = 1; // When start bit interrupt comes in, send SLA+W to MUX - } + muxPhase = MuxPhase_PROLOG; // When start bit interrupt comes in, send SLA+W to MUX + } else + muxPhase = 0; #endif TWCR = (1< MuxPhase_OFF && !(muxPhase==MuxPhase_PASSTHRU && (bytesToSend || bytesToReceive))) { + switch (twsr) { + case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received + if (muxPhase == MuxPhase_PROLOG) { + // Send MUX selecter mask to follow address + I2CSubBus subBus = currentRequest->i2cAddress.subBus(); + TWDR = (subBus==SubBus_All) ? 0xff : + (subBus==SubBus_None) ? 0x00 : + 1 << subBus; + TWCR = (1< 1) { + // Device transaction complete, prepare to deselect MUX by sending start bit + TWCR = (1<i2cAddress.deviceAddress() == 0) { + // Send stop and post rb. + TWDR = 0xff; + TWCR = (1< 0) { + currentRequest->readBuffer[rxCount++] = TWDR; + bytesToReceive--; + } + if (muxPhase == MuxPhase_PASSTHRU && _muxCount > 1) { + // Prepare to transmit epilog to mux - first send the stop bit and start bit + // (we don't need to reset mux if there is only one. + TWCR = (1<i2cAddress.muxNumber(); + TWDR = (muxAddress << 1) | 0; // MUXaddress+Write + TWCR = (1< MuxPhase_EPILOG) { + // Mux Cleardown was NAK'd, send stop and then finish. + TWCR = (1<i2cAddress.subBus(); - uint8_t subBusMask = (subBus==SubBus_All) ? 0xff : - (subBus==SubBus_None) ? 0x00 : - 1 << subBus; - TWDR = subBusMask; - TWCR = (1<i2cAddress.address() == 0 && bytesToSend == 0) { - // Send stop and post rb. - TWCR = (1<writeBuffer + (txCount++)); @@ -173,18 +268,20 @@ void I2CManagerClass::I2C_handleInterrupt() { // Don't need to wait for stop, as the interface won't send the start until // any in-progress stop condition has been sent. TWCR = (1< 0) { currentRequest->readBuffer[rxCount++] = TWDR; bytesToReceive--; } /* fallthrough */ + case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received if (bytesToReceive <= 1) { TWCR = (1< 0) { currentRequest->readBuffer[rxCount++] = TWDR; bytesToReceive--; } TWCR = (1<i2cAddress.muxNumber(); - TWDR = (muxAddress << 1) | 0; // MUXaddress+Write - } else -#endif { // Set up address and R/W uint8_t deviceAddress = currentRequest->i2cAddress; @@ -218,25 +309,29 @@ void I2CManagerClass::I2C_handleInterrupt() { TWDR = (deviceAddress << 1) | 1; // SLA+R else TWDR = (deviceAddress << 1) | 0; // SLA+W + TWCR = (1<writeBuffer[txCount++]; @@ -128,7 +131,7 @@ void I2CManagerClass::I2C_handleInterrupt() { } else { // No more data to send/receive. Initiate a STOP condition. TWI0.MCTRLB = TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; // Done + state = I2C_STATE_COMPLETED; } } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors @@ -142,7 +145,7 @@ void I2CManagerClass::I2C_handleInterrupt() { } else { // Transaction finished, issue NACK and STOP. TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; - state = I2C_STATUS_OK; + state = I2C_STATE_COMPLETED; } } } diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 5892bc249..4214ef9cb 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -76,6 +76,14 @@ for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ #undef I2C_USE_WIRE #endif +enum MuxPhase: uint8_t { + MuxPhase_OFF = 0, + MuxPhase_PROLOG, + MuxPhase_PASSTHRU, + MuxPhase_EPILOG, +} ; + + /*************************************************************************** * Initialise the I2CManagerAsync class. ***************************************************************************/ @@ -108,6 +116,7 @@ void I2CManagerClass::startTransaction() { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { state = I2C_STATE_ACTIVE; + completionStatus = I2C_STATUS_OK; // Check for pending clock speed change if (pendingClockSpeed) { // We're about to start a new I2C transaction, so set clock now. @@ -260,7 +269,7 @@ void I2CManagerClass::handleInterrupt() { // and state isn't active then state contains the completion status of the request. if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { // Operation has completed. - if (state == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES + if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES || currentRequest->operation & OPERATION_NORETRY) { // Status is OK, or has failed and retry count exceeded, or retries disabled. @@ -270,7 +279,7 @@ void I2CManagerClass::handleInterrupt() { queueHead = t->nextRequest; if (!queueHead) queueTail = queueHead; t->nBytes = rxCount; - t->status = state; + t->status = completionStatus; // I2C state machine is now free for next request currentRequest = NULL; @@ -297,6 +306,7 @@ I2CRB * volatile I2CManagerClass::queueHead = NULL; I2CRB * volatile I2CManagerClass::queueTail = NULL; I2CRB * volatile I2CManagerClass::currentRequest = NULL; volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE; +uint8_t I2CManagerClass::completionStatus; volatile uint8_t I2CManagerClass::txCount; volatile uint8_t I2CManagerClass::rxCount; volatile uint8_t I2CManagerClass::operation; @@ -306,7 +316,7 @@ volatile unsigned long I2CManagerClass::startTime; uint8_t I2CManagerClass::retryCounter = 0; #if defined(I2C_EXTENDED_ADDRESS) -volatile uint8_t I2CManagerClass::muxSendStep = 0; +volatile uint8_t I2CManagerClass::muxPhase = 0; #endif #endif \ No newline at end of file diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 08e34f2a1..5eb612ba2 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -155,6 +155,8 @@ void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + txCount = 0; + rxCount = 0; // On a single-master I2C bus, the start bit won't be sent until the bus // state goes to IDLE so we can request it without waiting. On a @@ -207,13 +209,15 @@ void I2CManagerClass::I2C_handleInterrupt() { I2C_sendStart(); // Reinitiate request } else if (s->I2CM.STATUS.bit.BUSERR) { // Bus error - state = I2C_STATUS_BUS_ERROR; + completionStatus = I2C_STATUS_BUS_ERROR; + state = I2C_STATE_COMPLETED; // Completed with error } else if (s->I2CM.INTFLAG.bit.MB) { // Master write completed if (s->I2CM.STATUS.bit.RXNACK) { // Nacked, send stop. I2C_sendStop(); - state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + state = I2C_STATE_COMPLETED; // Completed with error } else if (bytesToSend) { // Acked, so send next byte s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; @@ -222,9 +226,9 @@ void I2CManagerClass::I2C_handleInterrupt() { // Last sent byte acked and no more to send. Send repeated start, address and read bit. s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { - // No more data to send/receive. Initiate a STOP condition. + // No more data to send/receive. Initiate a STOP condition I2C_sendStop(); - state = I2C_STATUS_OK; // Done + state = I2C_STATE_COMPLETED; // Completed OK } } else if (s->I2CM.INTFLAG.bit.SB) { // Master read completed without errors @@ -233,7 +237,7 @@ void I2CManagerClass::I2C_handleInterrupt() { I2C_sendStop(); // send stop currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive = 0; - state = I2C_STATUS_OK; // done + state = I2C_STATE_COMPLETED; // Completed OK } else if (bytesToReceive) { s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 7ea75c5b8..67ef306df 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -69,38 +69,53 @@ void I2CManagerClass::setTimeout(unsigned long value) { * Helper function for I2C Multiplexer operations ********************************************************/ #ifdef I2C_EXTENDED_ADDRESS -static uint8_t muxSelect(I2CAddress &address) { +static uint8_t muxSelect(I2CAddress address) { // Select MUX sub bus. - Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+address.muxNumber()); - uint8_t data = address.subBus(); - Wire.write(&data, 1); - return Wire.endTransmission(true); // have to release I2C bus for it to work + I2CMux muxNo = address.muxNumber(); + I2CSubBus subBus = address.subBus(); + if (muxNo != I2CMux_None) { + Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo); + uint8_t data = (subBus == SubBus_All) ? 0xff : + (subBus == SubBus_None) ? 0x00 : + (1 << subBus); + Wire.write(&data, 1); + return Wire.endTransmission(true); // have to release I2C bus for it to work + } + return I2C_STATUS_OK; } #endif + /*************************************************************************** * Initiate a write to an I2C device (blocking operation on Wire) ***************************************************************************/ uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) { - uint8_t status = I2C_STATUS_OK; + uint8_t status, muxStatus; uint8_t retryCount = 0; // If request fails, retry up to the defined limit, unless the NORETRY flag is set // in the request block. do { - status = I2C_STATUS_OK; + status = muxStatus = I2C_STATUS_OK; #ifdef I2C_EXTENDED_ADDRESS - if (address.muxNumber() != I2CMux_None) { - status = muxSelect(address); - } + if (address.muxNumber() != I2CMux_None) + muxStatus = muxSelect(address); #endif - // Only send new transaction if address and size are both nonzero. - if (status == I2C_STATUS_OK && address != 0 && size != 0) { + // Only send new transaction if address is non-zero. + if (muxStatus == I2C_STATUS_OK && address != 0) { Wire.beginTransmission(address); if (size > 0) Wire.write(buffer, size); status = Wire.endTransmission(); } - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES - || rb->operation & OPERATION_NORETRY)); +#ifdef I2C_EXTENDED_ADDRESS + // Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected + if (_muxCount > 1 && muxStatus == I2C_STATUS_OK + && address.deviceAddress() != 0 && address.muxNumber() != I2CMux_None) { + muxSelect({address.muxNumber(), SubBus_None}); + } + if (muxStatus != I2C_STATUS_OK) status = muxStatus; +#endif + } while (!(status == I2C_STATUS_OK + || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); rb->status = status; return I2C_STATUS_OK; } @@ -123,20 +138,20 @@ uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uin uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize, const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb) { - uint8_t status = I2C_STATUS_OK; + uint8_t status, muxStatus; uint8_t nBytes = 0; uint8_t retryCount = 0; // If request fails, retry up to the defined limit, unless the NORETRY flag is set // in the request block. do { - status = I2C_STATUS_OK; + status = muxStatus = I2C_STATUS_OK; #ifdef I2C_EXTENDED_ADDRESS if (address.muxNumber() != I2CMux_None) { - status = muxSelect(address); + muxStatus = muxSelect(address); } #endif - // Only start new transaction if address and readSize are both nonzero. - if (status == I2C_STATUS_OK && address != 0 && writeSize > 0) { + // Only start new transaction if address is non-zero. + if (muxStatus == I2C_STATUS_OK && address != 0) { if (writeSize > 0) { Wire.beginTransmission(address); Wire.write(writeBuffer, writeSize); @@ -161,8 +176,16 @@ uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t #endif } } - } while (!(status == I2C_STATUS_OK || ++retryCount > MAX_I2C_RETRIES - || rb->operation & OPERATION_NORETRY)); +#ifdef I2C_EXTENDED_ADDRESS + // Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected + if (_muxCount > 1 && muxStatus == I2C_STATUS_OK && address != 0 && address.muxNumber() != I2CMux_None) { + muxSelect({address.muxNumber(), SubBus_None}); + } + if (muxStatus != I2C_STATUS_OK) status = muxStatus; +#endif + + } while (!((status == I2C_STATUS_OK) + || ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY)); rb->nBytes = nBytes; rb->status = status; From 0c88c74706db84fe90d5934125a5ea0959d6ad08 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:01:45 +0000 Subject: [PATCH 528/870] Revert "Refactor SSD1306 initialisation to support different initialisation sequences." This reverts commit 278a9c52a64d979ec99adc0535378d492f7b338e. --- SSD1306Ascii.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index f57252f00..7373a74a9 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,10 +144,7 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() { - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed -} +SSD1306AsciiWire::SSD1306AsciiWire() {} // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { @@ -196,7 +193,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); clear(); return true; } @@ -206,9 +203,8 @@ void SSD1306AsciiWire::clearNative() { const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased - for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes) { - uint8_t len = m_displayWidth-c; - if (len > maxBytes) len = maxBytes; + for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { + uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns } } From 7b79680de28c97d045c4255ce038ef3383972119 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:09:17 +0000 Subject: [PATCH 529/870] First stab at HAL-installable OLED Display Driver. --- IO_OLEDDisplay.h | 178 +++++++++++++++++++++++++++++++++++++++++++++++ SSD1306Ascii.cpp | 11 ++- 2 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 IO_OLEDDisplay.h diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h new file mode 100644 index 000000000..e158cd986 --- /dev/null +++ b/IO_OLEDDisplay.h @@ -0,0 +1,178 @@ +/* + * © 2023, 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 . + */ + +/* + * This driver provides a more immediate interface into the OLED display + * than the one installed through the config.h file. When an LCD(...) call + * is made, the text is output immediately to the specified display line, + * without waiting for the next 2.5 second refresh. However, no scrolling + * takes place, so if the line specified is off the screen then the text + * will instead be shown on the bottom line of the screen. + * + * To install, use the following command in myHal.cpp: + + * OLEDDisplay::create(address, width, height); + * + * where address is the I2C address (0x3c or 0x3d), + * width is the width in pixels of the display, and + * height is the height in pixels of the display. + * + * Valid width and height are 128x32 (SSD1306 controller), + * 128x64 (SSD1306) and 132x64 (SH1106). The driver uses + * a 5x7 character set in a 6x8 pixel cell. + */ + + +#ifndef IO_OLEDDISPLAY_H +#define IO_OLEDDISPLAY_H + +#include "IODevice.h" +#include "DisplayInterface.h" +#include "SSD1306Ascii.h" +#include "version.h" + +class OLEDDisplay : public IODevice, DisplayInterface { +private: + // Here we define the device-specific variables. + uint8_t _height; // in pixels + uint8_t _width; // in pixels + SSD1306AsciiWire *oled; + uint8_t _rowNo = 0; // Row number being written by caller + uint8_t _colNo = 0; // Position in line being written by caller + uint8_t _numRows; + uint8_t _numCols; + char *_buffer = NULL; + uint8_t *_rowGeneration = NULL; + uint8_t *_lastRowGeneration = NULL; + uint8_t _rowNoToScreen = 0; + uint8_t _charPosToScreen = 0; + +public: + // Static function to handle "OLEDDisplay::create(...)" calls. + static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(i2cAddress, width, height); + } + +protected: + // Constructor + OLEDDisplay(I2CAddress i2cAddress, int width, int height) { + _I2CAddress = i2cAddress; + _width = width; + _height = height; + _numCols = _width / 6; // character block 6 x 8 + _numRows = _height / 8; + + // Allocate arrays + _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); + _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + // Create OLED driver + oled = new SSD1306AsciiWire(); + // Initialise device + if (oled->begin(_I2CAddress, _width, _height)) { + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to lcdDisplay methods. + DisplayInterface::lcdDisplay = this; + + DIAG(F("OLEDDisplay installed on address x%x"), (int)_I2CAddress); + + // First clear the entire screen + oled->clearNative(); + + // Set first two lines on screen + LCD(0,F("DCC++ EX v%S"),F(VERSION)); + LCD(1,F("Lic GPLv3")); + } + } + + ///////////////////////////////////////////////// + // DisplayInterface functions + // + // TODO: Limit to one call to setRowNative or writeNative + // per entry so that the function doesn't block waiting + // for I2C to complete. + ///////////////////////////////////////////////// + DisplayInterface* loop2(bool force) override { + + // Loop through the buffer and if a row has changed + // (rowGeneration[row] is changed) then start writing the + // characters from the buffer, one character per entry, + // to the screen until that row has been refreshed. + // TODO: Currently this is done all in one go! Split + // it up so that at most one call is made to either + // setRowNative or writeNative per loop entry - then + // we shan't have to wait for I2C. + for (uint8_t row = 0; row < _numRows; row++) { + if (_rowGeneration[row] != _lastRowGeneration[row]) { + // Row has been modified, write to screen + oled->setRowNative(row); + for (uint8_t _col = 0; _col < _numCols; _col++) { + oled->writeNative(_buffer[(uint16_t)row*_numCols+_col]); + } + _lastRowGeneration[row] = _rowGeneration[row]; + } + } + return NULL; + } + + // Position on nominated line number (0 to number of lines -1) + // Clear the line in the buffer ready for updating + void setRow(byte line) override { + if (line >= _numRows) line = _numRows-1; + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry. + _rowGeneration[_rowNo]++; + } + + // Write blanks to all of the screen (blocks until complete) + void clear () override { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(_rowNo); + } + _rowNo = 0; + } + + // Write one character + size_t write(uint8_t c) override { + // Write character to buffer (if space) + if (_colNo < _numCols) + _buffer[_rowNo*_numCols+_colNo++] = c; + return 1; + } + + // Display information about the device. + void _display() { + DIAG(F("OLEDDisplay Configured addr x%x"), (int)_I2CAddress); + } + +}; + +#endif // IO_OLEDDISPLAY_H \ No newline at end of file diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 7373a74a9..12d8702d9 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,7 +144,11 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() {} +SSD1306AsciiWire::SSD1306AsciiWire() { + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed + +} // CS auto-detect and configure constructor SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { @@ -193,7 +197,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (uint8_t)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); clear(); return true; } @@ -204,7 +208,8 @@ void SSD1306AsciiWire::clearNative() { for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { - uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1; + uint8_t len = m_displayWidth-c+1; + if (len > maxBytes) len = maxBytes; I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns } } From 19070d33ba6e9dbb0b18c8f70f803e46514415bb Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:13:36 +0000 Subject: [PATCH 530/870] Add Arduino M0, and display time on serial monitor. --- platformio.ini | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 82167f225..40dc89ee8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,6 +30,7 @@ include_dir = . [env] build_flags = -Wall -Wextra +monitor_filters = time [env:samd21-dev-usb] platform = atmelsam @@ -51,6 +52,15 @@ monitor_speed = 115200 monitor_echo = yes build_flags = -std=c++17 +[env:Arduino M0] +platform = atmelsam +board = mzeroUSB +framework = arduino +lib_deps = ${env.lib_deps} +monitor_speed = 115200 +monitor_echo = yes +build_flags = -std=c++17 -DI2C_EXTENDED_ADDRESS ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO + [env:mega2560-debug] platform = atmelavr board = megaatmega2560 @@ -61,7 +71,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DDIAG_IO -DDIAG_LOOPTIMES +build_flags = -DI2C_EXTENDED_ADDRESS -DDIAG_IO -DDIAG_LOOPTIMES [env:mega2560-no-HAL] platform = atmelavr From aad0d28d1f7ff1a68822a61c0c6b53b33dcf1dc1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:24:05 +0000 Subject: [PATCH 531/870] Servo driver - split PCA9685 into a filter + PWM driver. Two new drivers: PCA9685pwm drives the PCA9685 I2C device directly, and Servo driver which acts as a 'shim' over the top to control animations. This is aimed at supporting devices like the EXIOExpander by allowing the Servo driver to talk to the EXIOExpander driver instead, to animate servos on another controller. --- IO_PCA9685pwm.h | 149 ++++++++++++++++++++++++ IO_Servo.cpp | 33 ++++++ IO_Servo.h | 298 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 480 insertions(+) create mode 100644 IO_PCA9685pwm.h create mode 100644 IO_Servo.cpp create mode 100644 IO_Servo.h diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h new file mode 100644 index 000000000..5a28f6ee2 --- /dev/null +++ b/IO_PCA9685pwm.h @@ -0,0 +1,149 @@ +/* + * © 2023, 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 . + */ + +/* + * This driver performs the basic interface between the HAL and an + * I2C-connected PCA9685 16-channel PWM module. When requested, it + * commands the device to set the PWM mark-to-period ratio accordingly. + * The call to IODevice::writeAnalogue(vpin, value) specifies the + * desired value in the range 0-4095 (0=0% and 4095=100%). + */ + +#ifndef PCA9685_BASIC_H +#define PCA9685_BASIC_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +/* + * IODevice subclass for PCA9685 16-channel PWM module. + */ + +class PCA9685pwm : public IODevice { +public: + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress); + } + +private: + + // structures for setting up non-blocking writes to servo controller + I2CRB requestBlock; + uint8_t outputBuffer[5]; + + // REGISTER ADDRESSES + const uint8_t PCA9685_MODE1=0x00; // Mode Register + const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first servo register ON*/ + const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ + // MODE1 bits + const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ + const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */ + const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */ + + const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); + const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed + + // Constructor + PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + _firstVpin = firstVpin; + _nPins = (nPins>16) ? 16 : nPins; + _I2CAddress = i2cAddress; + addDevice(this); + + // Initialise structure used for setting pulse rate + requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C + // In reality, other devices including the Arduino will limit + // the clock speed to a lower rate. + + // Initialise I/O module here. + if (I2CManager.exists(_I2CAddress)) { + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); + // In theory, we should wait 500us before sending any other commands to each device, to allow + // the PWM oscillator to get running. However, we don't do any specific wait, as there's + // plenty of other stuff to do before we will send a command. + #if defined(DIAG_IO) + _display(); + #endif + } else + _deviceState = DEVSTATE_FAILED; + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + writeDevice(pin, value); + } + + // Display details of this device. + void _display() override { + DIAG(F("PCA9685pwm I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } + + // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value + // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. + void writeDevice(uint8_t pin, int value) { + #ifdef DIAG_IO + DIAG(F("PCA9685pwm I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); + #endif + // Wait for previous request to complete + uint8_t status = requestBlock.wait(); + if (status != I2C_STATUS_OK) { + _deviceState = DEVSTATE_FAILED; + DIAG(F("PCA9685pwm I2C:x%x failed %S"), (int)_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); + } + } + + // Internal helper function for this device + static void writeRegister(I2CAddress address, uint8_t reg, uint8_t value) { + I2CManager.write(address, 2, reg, value); + } + +}; + +#endif \ No newline at end of file diff --git a/IO_Servo.cpp b/IO_Servo.cpp new file mode 100644 index 000000000..d59c1deff --- /dev/null +++ b/IO_Servo.cpp @@ -0,0 +1,33 @@ +/* + * © 2023, 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 . + */ + +#include "IO_Servo.h" +#include "FSH.h" + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +// +// Note: This has been put into its own .CPP file to ensure that duplicates aren't created +// if the IO_Servo.h library is #include'd in multiple source files. +// +const uint8_t FLASH Servo::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + diff --git a/IO_Servo.h b/IO_Servo.h new file mode 100644 index 000000000..8f95463a4 --- /dev/null +++ b/IO_Servo.h @@ -0,0 +1,298 @@ +/* + * © 2023, 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 . + */ + +/* + * This device is a layered device which is designed to sit on top of another + * device. The underlying device class is expected to accept writeAnalogue calls + * which will normally cause some physical movement of something. The device may be a servo, + * a motor or some other kind of positioner, and the something might be a turnout, + * a semaphore signal or something else. One user has used this capability for + * moving a figure along the platform on their layout! + * + * Example of use: + * In myHal.cpp, + * + * #include "IO_Servo.h" + * ... + * PCA9685::create(100,16,0x40); // First create the hardware interface device + * Servo::create(300,16,100); // Then create the higher level device which + * // references pins 100-115 or a subset of them. + * + * Then any reference to pins 300-315 will cause the servo driver to send output + * PWM commands to the corresponding PCA9685 driver pins 100-115. The PCA9685 driver may + * be substituted with any other driver which provides analogue output + * capability, e.g. EX-IOExpander devices, as long as they are capable of interpreting + * the writeAnalogue() function calls. + */ + +#include "IODevice.h" + +#ifndef IO_SERVO_H +#define IO_SERVO_H + +#include "I2CManager.h" +#include "DIAG.h" + +class Servo : IODevice { + +public: + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin=VPIN_NONE) { + new Servo(firstVpin, nPins, firstSlavePin); + } + +private: + VPIN _firstSlavePin; + IODevice *_slaveDevice = NULL; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + + + // Configure a port on the Servo. + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { + if (_deviceState == DEVSTATE_FAILED) return false; + if (configType != CONFIGURE_SERVO) return false; + if (paramCount != 5) return false; + #ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); + #endif + + int8_t pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + + if (state != -1) { + // Position servo to initial state + writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition); + } + return true; + } + + // Constructor + Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin = VPIN_NONE) { + _firstVpin = firstVpin; + _nPins = (nPins > 16) ? 16 : nPins; + if (firstSlavePin == VPIN_NONE) + _firstSlavePin = firstVpin; + else + _firstSlavePin = firstSlavePin; + + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) + _servoData[i] = NULL; + + // Get reference to slave device. + _slaveDevice = findDevice(_firstSlavePin); + if (!_slaveDevice) { + DIAG(F("Servo: Slave device not found on pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) { + DIAG(F("Servo: Slave device does not cover all pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + + addDevice(this, _slaveDevice); // Link device ahead of slave device to intercept requests + } + + // Device-specific initialisation + void _begin() override { + #if defined(DIAG_IO) + _display(); + #endif + } + + // Device-specific write function, invoked from IODevice::write(). + // For this function, the configured profile is used. + void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; + #ifdef DIAG_IO + DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + #endif + int pin = vpin - _firstVpin; + if (value) value = 1; + + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // Profile is as follows: + // Bit 7: 0=Set output to 0% to power off servo motor when finished + // 1=Keep output at final position (better with LEDs, which will stay lit) + // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) + // 1 (Fast) Move servo in 0.5 seconds + // 2 (Medium) Move servo in 1.0 seconds + // 3 (Slow) Move servo in 2.0 seconds + // 4 (Bounce) Servo 'bounces' at extremes. + // Duration is in deciseconds (tenths of a second) and defaults to 0. + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + // _read returns true if the device is currently in executing an animation, + // changing the output over a period of time. + int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + int pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) + return false; // No structure means no animation! + else + return (s->stepNumber < s->numSteps); + } + + void _loop(unsigned long currentMicros) override { + if (_deviceState == DEVSTATE_FAILED) return; + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } + delayUntil(currentMicros + refreshInterval * 1000UL); + } + + // Private function to reposition servo + // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command to output driver + _slaveDevice->_writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off output driver to avoid servo buzz. + _slaveDevice->_writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } + } + + // Display details of this device. + void _display() override { + DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } +}; + +#endif \ No newline at end of file From 5439dd3158b6571c3d6da8d20c214fff3793f3fd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 17:59:40 +0000 Subject: [PATCH 532/870] Suppress compiler warnings. --- IO_OLEDDisplay.h | 3 ++- IO_PCA9685pwm.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index e158cd986..b3fa283ff 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -100,7 +100,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { // First clear the entire screen oled->clearNative(); - + // Set first two lines on screen LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); @@ -115,6 +115,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { // for I2C to complete. ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { + (void)force; // suppress compiler warning // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index 5a28f6ee2..72884a9c7 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -98,7 +98,8 @@ class PCA9685pwm : public IODevice { // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). // - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + (void)param1; (void)param2; // suppress compiler warning #ifdef DIAG_IO DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); From 31ce2e3feff7e5221914e2146948abb22197a870 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 18:36:48 +0000 Subject: [PATCH 533/870] EXTurntable - change I2C address type to I2CAddress. --- IODevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IODevice.h b/IODevice.h index d9c851ff7..f5c7c6f46 100644 --- a/IODevice.h +++ b/IODevice.h @@ -385,9 +385,9 @@ class ArduinoPins: public IODevice { class EXTurntable : public IODevice { public: - static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress); + static void create(VPIN firstVpin, int nPins, I2CAddress I2CAddress); // Constructor - EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress); + EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress); enum ActivityNumber : uint8_t { Turn = 0, // Rotate turntable, maintain phase Turn_PInvert = 1, // Rotate turntable, invert phase From 57292c2250f8ad60e84ef85af9b00f76febfc1e9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 7 Feb 2023 20:44:03 +0100 Subject: [PATCH 534/870] installer.sh script bug fix and enhancements --- installer.sh | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/installer.sh b/installer.sh index 70a7858a6..98de7716d 100755 --- a/installer.sh +++ b/installer.sh @@ -37,6 +37,10 @@ function need () { need git +if test -d `basename "$DCCEXGITURL"` ; then + : assume we are almost there + cd `basename "$DCCEXGITURL"` || exit 255 +fi if test -d .git ; then : assume we are right here git pull @@ -44,6 +48,21 @@ else git clone "$DCCEXGITURL" cd `basename "$DCCEXGITURL"` || exit 255 fi + +# prepare versions +VERSIONS=/tmp/versions.$$ +git tag --sort=v:refname | grep Prod | tail -1 > $VERSIONS +echo master >> $VERSIONS +git tag --sort=v:refname | grep Devel | tail -1 >> $VERSIONS +echo devel >> $VERSIONS + +# ask user what version to use +echo "What version to use? (give line number) If in doubt, use 1" +cat -n $VERSIONS +echo -n "> " +LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $VERSIONS` +git checkout $LINE + if test -f config.h ; then : all well else @@ -63,7 +82,14 @@ $ACLI core update-index || exit 255 # Board discovery BOARDS=/tmp/boards.$$ -$ACLI board list | grep serial > $BOARDS +$ACLI board list > /dev/null # download missing components +$ACLI board list | grep serial > $BOARDS # real run +if test -s $BOARDS ; then + : all well +else + echo "$ACLI: No boards found" + exit 255 +fi if test x`< $BOARDS wc -l` = 'x1' ; then LINE=`cat $BOARDS` else @@ -96,6 +122,6 @@ echo FQBN is $FQBN # Install phase $ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package -$ACLI board attach -p $PORT --fqbn $FQBN $PWD -$ACLI compile --fqbn $FQBN $PWD -$ACLI upload -v -t -p $PORT $PWD +$ACLI board attach -p $PORT --fqbn $FQBN "$PWD" +$ACLI compile --fqbn $FQBN "$PWD" +$ACLI upload -v -t -p $PORT "$PWD" From e2e9b7bae728280194aa9009ca9adc21a1795018 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 22:04:09 +0000 Subject: [PATCH 535/870] Update version.h --- version.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/version.h b/version.h index 67886459a..b2d1f8be7 100644 --- a/version.h +++ b/version.h @@ -5,6 +5,20 @@ #define VERSION "4.2.14" +// 4.2.15 I2C Multiplexer support through Extended Addresses, +// added for Wire, 4209 and AVR I2C drivers. +// I2C retries when fail. +// I2C timeout handling and recovery completed. +// I2C SAMD Driver Read code completed. +// PCF8575 I2C GPIO driver added. +// EX-RAIL ANOUT function for triggering analogue +// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// Installable HAL OLED Display Driver. +// Layered HAL Drivers PCA9685pwm and Servo added for +// (1) native PWM on PCA9685 module and +// (2) animations of servo movement via PCA9685pwm. +// This is intended to support EXIOExpander and also +// replace the existing PCA9685 driver. // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From 47cb43d1e9378bcb05a9b9a8ecc1d12599238771 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:16:00 +0000 Subject: [PATCH 536/870] Update IO_OLEDDisplay.h Make display updates non-blocking (after initialisation completes). --- IO_OLEDDisplay.h | 77 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index b3fa283ff..9aefbaadd 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -21,9 +21,11 @@ * This driver provides a more immediate interface into the OLED display * than the one installed through the config.h file. When an LCD(...) call * is made, the text is output immediately to the specified display line, - * without waiting for the next 2.5 second refresh. However, no scrolling - * takes place, so if the line specified is off the screen then the text - * will instead be shown on the bottom line of the screen. + * without waiting for the next 2.5 second refresh. However, if the line + * specified is off the screen then the text in the bottom line will be + * overwritten. There is however a special case that if line 255 is specified, + * the existing text will scroll up and the new line added to the bottom + * line of the screen. * * To install, use the following command in myHal.cpp: @@ -78,6 +80,8 @@ class OLEDDisplay : public IODevice, DisplayInterface { _numCols = _width / 6; // character block 6 x 8 _numRows = _height / 8; + _charPosToScreen = _numCols; + // Allocate arrays _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); @@ -107,41 +111,62 @@ class OLEDDisplay : public IODevice, DisplayInterface { } } - ///////////////////////////////////////////////// - // DisplayInterface functions - // - // TODO: Limit to one call to setRowNative or writeNative - // per entry so that the function doesn't block waiting - // for I2C to complete. - ///////////////////////////////////////////////// - DisplayInterface* loop2(bool force) override { - (void)force; // suppress compiler warning + + void _loop(unsigned long) override { // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the // characters from the buffer, one character per entry, // to the screen until that row has been refreshed. - // TODO: Currently this is done all in one go! Split - // it up so that at most one call is made to either - // setRowNative or writeNative per loop entry - then - // we shan't have to wait for I2C. - for (uint8_t row = 0; row < _numRows; row++) { - if (_rowGeneration[row] != _lastRowGeneration[row]) { - // Row has been modified, write to screen - oled->setRowNative(row); - for (uint8_t _col = 0; _col < _numCols; _col++) { - oled->writeNative(_buffer[(uint16_t)row*_numCols+_col]); + + // First check if the OLED driver is still busy from a previous + // call. If so, don't to anything until the next entry. + if (!oled->isBusy()) { + // Check if we've just done the end of a row or just started + if (_charPosToScreen >= _numCols) { + // Move to next line + if (++_rowNoToScreen >= _numRows) + _rowNoToScreen = 0; // Wrap to first row + + if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { + // Row content has changed, so start outputting it + _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; + oled->setRowNative(_rowNoToScreen); + _charPosToScreen = 0; // Prepare to output first character on next entry + } else { + // Row not changed, don't bother writing it. } - _lastRowGeneration[row] = _rowGeneration[row]; - } + } else { + // output character at current position + oled->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + } } - return NULL; + return; } + ///////////////////////////////////////////////// + // DisplayInterface functions + // + ///////////////////////////////////////////////// + DisplayInterface* loop2(bool force) override { + (void)force; // suppress compiler warning + return NULL; + } + // Position on nominated line number (0 to number of lines -1) // Clear the line in the buffer ready for updating void setRow(byte line) override { - if (line >= _numRows) line = _numRows-1; + if (line == 255) { + // LCD(255, "xxx") - scroll the contents of the buffer + // and put the new line at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + _rowNo = line; // Fill line with blanks for (_colNo = 0; _colNo < _numCols; _colNo++) From 73a7d3e0ca02830cdf780f7fb09ed0c84edf7350 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:16:37 +0000 Subject: [PATCH 537/870] Update I2CManager_AVR.h Bug fix in native driver MUX code. --- I2CManager_AVR.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index e5e186399..761313211 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -22,6 +22,7 @@ #include #include "I2CManager.h" +#include "I2CManager_NonBlocking.h" // to satisfy intellisense #include #include @@ -139,9 +140,7 @@ void I2CManagerClass::I2C_handleInterrupt() { #if defined(I2C_EXTENDED_ADDRESS) // First process the MUX state machine. - // This does not need to be entered during passthru phase unless the - // application's send and receive have both completed. - if (muxPhase > MuxPhase_OFF && !(muxPhase==MuxPhase_PASSTHRU && (bytesToSend || bytesToReceive))) { + if (muxPhase > MuxPhase_OFF) { switch (twsr) { case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received if (muxPhase == MuxPhase_PROLOG) { From efb26660603eb02d056b1d6c9c7a7b4124213269 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:18:59 +0000 Subject: [PATCH 538/870] DCCTimer_AVR - incorporate Haba's optimisations to ADC scanning --- DCCTimer.h | 16 ++++----- DCCTimerAVR.cpp | 91 +++++++++++++++++++++++-------------------------- 2 files changed, 51 insertions(+), 56 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 75be8cd97..221485188 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,5 +1,5 @@ /* - * © 2022-2023 Paul M. Antoine + * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021-2022 Harald Barth * © 2021 Fred Decker @@ -102,14 +102,9 @@ class DCCTimer { // that an offset can be initialized. class ADCee { public: - // begin is called for any setup that must be done before - // **init** can be called. On some architectures this involves ADC - // initialisation and clock routing, sampling times etc. - static void begin(); - // init adds the pin to the list of scanned pins (if this + // init does add the pin to the list of scanned pins (if this // platform's implementation scans pins) and returns the first - // read value (which is why it required begin to have been called first!) - // It must be called before the regular scan is started. + // read value. It is called before the regular scan is started. static int init(uint8_t pin); // read does read the pin value from the scanned cache or directly // if this is a platform that does not scan. fromISR is a hint if @@ -118,6 +113,9 @@ class ADCee { static int read(uint8_t pin, bool fromISR=false); // returns possible max value that the ADC can return static int16_t ADCmax(); + // begin is called for any setup that must be done before + // scan can be called. + static void begin(); private: // On platforms that scan, it is called from waveform ISR // only on a regular basis. @@ -126,6 +124,8 @@ class ADCee { static uint16_t usedpins; // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; + // ids to scan (new way) + static byte *idarr; // friend so that we can call scan() and begin() friend class DCCWaveform; }; diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 9b16c4715..40ce0fb69 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -123,13 +123,14 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 15 +#define NUM_ADC_INPUTS 16 #else -#define NUM_ADC_INPUTS 7 +#define NUM_ADC_INPUTS 8 #endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; -bool ADCusesHighPort = false; +byte *ADCee::idarr = NULL; +static bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -138,16 +139,28 @@ bool ADCusesHighPort = false; */ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; - if (id > NUM_ADC_INPUTS) + byte n; + if (id >= NUM_ADC_INPUTS) return -1023; if (id > 7) ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); - if (analogvals == NULL) - analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); - analogvals[id] = value; - usedpins |= (1<setBrake(0); + analogvals[idarr[num]] = (high << 8) | low; waiting = false; - id++; - mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; - } } if (!waiting) { - if (usedpins == 0) // otherwise we would loop forever - return; - // look for a valid track to sample or until we are around - while (true) { - if (mask & usedpins) { - // start new ADC aquire on id + // cycle around in-use analogue pins + num++; + if (idarr[num] == 255) + num = 0; + // start new ADC aquire on id #if defined(ADCSRB) && defined(MUX5) - if (ADCusesHighPort) { // if we ever have started to use high pins) - if (id > 7) // if we use a high ADC pin - bitSet(ADCSRB, MUX5); // set MUX5 bit - else - bitClear(ADCSRB, MUX5); - } -#endif - ADMUX=(1<setBrake(1); - waiting = true; - return; - } - id++; - mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; - } + if (ADCusesHighPort) { // if we ever have started to use high pins) + if (idarr[num] > 7) // if we use a high ADC pin + bitSet(ADCSRB, MUX5); // set MUX5 bit + else + bitClear(ADCSRB, MUX5); } +#endif + ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX + bitSet(ADCSRA, ADSC); // start conversion + waiting = true; } } #pragma GCC pop_options @@ -236,4 +231,4 @@ void ADCee::begin() { //bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX interrupts(); } -#endif +#endif \ No newline at end of file From a0f0b860eb3aa4844bc422d655ce2e5c84199725 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 7 Feb 2023 23:21:23 +0000 Subject: [PATCH 539/870] Update version.h --- version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.h b/version.h index b2d1f8be7..d6ad661de 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.14" +#define VERSION "4.2.15" // 4.2.15 I2C Multiplexer support through Extended Addresses, // added for Wire, 4209 and AVR I2C drivers. // I2C retries when fail. From 3fbcd6f300c3477d93d2c5a20ba2141c63decd08 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 8 Feb 2023 10:04:18 +0800 Subject: [PATCH 540/870] STM32 native I2C driver initial edits --- I2CManager.cpp | 3 + I2CManager_STM32.h | 244 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 I2CManager_STM32.h diff --git a/I2CManager.cpp b/I2CManager.cpp index 00aa71b83..564c7b0b7 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -35,6 +35,9 @@ #elif defined(ARDUINO_ARCH_SAMD) #include "I2CManager_NonBlocking.h" #include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later +#elif defined(ARDUINO_ARCH_STM32) +#include "I2CManager_NonBlocking.h" +#include "I2CManager_STM32.h" // STM32F411RE for now... more later #else #define I2C_USE_WIRE #include "I2CManager_Wire.h" // Other platforms diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h new file mode 100644 index 000000000..0e944e7b2 --- /dev/null +++ b/I2CManager_STM32.h @@ -0,0 +1,244 @@ +/* + * © 2022-23 Paul M Antoine + * © 2023, Neil McKechnie + * All rights reserved. + * + * This file is part of CommandStation-EX + * + * 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 I2CMANAGER_STM32_H +#define I2CMANAGER_STM32_H + +#include +#include "I2CManager.h" + +//#include +//#include +#include + +/*************************************************************************** + * Interrupt handler. + * IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero + * compatible variants such as the Sparkfun SAMD21 Dev Breakout etc. + * Later we may wish to allow use of an alternate I2C bus, or more than one I2C + * bus on the SAMD architecture + ***************************************************************************/ +#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32) +void I2C1_IRQHandler() { + I2CManagerClass::handleInterrupt(); +} +#endif + +// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants +I2C_TypeDef *s = I2C1; + +/*************************************************************************** + * Set I2C clock speed register. This should only be called outside of + * a transmission. The I2CManagerClass::_setClock() function ensures + * that it is only called at the beginning of an I2C transaction. + ***************************************************************************/ +void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { + + // Calculate a rise time appropriate to the requested bus speed + int t_rise; + if (i2cClockSpeed < 200000L) { + i2cClockSpeed = 100000L; + t_rise = 1000; + } else if (i2cClockSpeed < 800000L) { + i2cClockSpeed = 400000L; + t_rise = 300; + } else if (i2cClockSpeed < 1200000L) { + i2cClockSpeed = 1000000L; + t_rise = 120; + } else { + i2cClockSpeed = 100000L; + t_rise = 1000; + } + + + // Disable the I2C master mode and wait for sync + // s->I2CM.CTRLA.bit.ENABLE = 0 ; + // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + + // Calculate baudrate - using a rise time appropriate for the speed + // s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + + // Enable the I2C master mode and wait for sync + // s->I2CM.CTRLA.bit.ENABLE = 1 ; + // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + + // Setting bus idle mode and wait for sync + // s->I2CM.STATUS.bit.BUSSTATE = 1 ; + // while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); +} + +/*************************************************************************** + * Initialise I2C registers. + ***************************************************************************/ +void I2CManagerClass::I2C_init() +{ + //Setting up the clocks + RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK + RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9 + // Standard I2C pins are SCL on PB8 and SDA on PB9 + // Bits (17:16)= 1:0 --> Alternate Function for Pin PB8; + // Bits (19:18)= 1:0 --> Alternate Function for Pin PB9 + GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function + GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability + GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode + GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability + // Alt Function High register routing pins PB8 and PB9 for I2C1: + // Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8 + // Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9 + GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up + + // Software reset the I2C peripheral + s->CR1 |= (1<<15); // reset the I2C + s->CR1 &= ~(1<<15); // Normal operation + + // Program the peripheral input clock in I2C_CR2 Register in order to generate correct timings + s->CR2 |= (16<<0); // PCLK1 FREQUENCY in MHz + + // Configure the Clock Control Register for 100KHz SCL frequency + // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode + // Bit 14: Duty, fast mode duty cycle + // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) + s->CCR = 0x0050; + + // Configure the rise time register - max allowed in 1000ns + s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 + +#if defined(I2C_USE_INTERRUPTS) + // Setting NVIC + NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities + NVIC_EnableIRQ(I2C1_EV_IRQn); + + // CR2 Interrupt Settings + // Bit 15-13: reserved + // Bit 12: LAST - DMA last transfer + // Bit 11: DMAEN - DMA enable + // Bit 10: ITBUFEN - Buffer interrupt enable + // Bit 9: ITEVTEN - Event interrupt enable + // Bit 8: ITERREN - Error interrupt enable + // Bit 7-6: reserved + // Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz) + // Enable all interrupts + s->CR2 |= 0x0700; +#endif + + // Calculate baudrate and set default rate for now + + // Enable the I2C master mode and wait for sync + + // Setting bus idle mode and wait for sync +} + +/*************************************************************************** + * Initiate a start bit for transmission. + ***************************************************************************/ +void I2CManagerClass::I2C_sendStart() { + + // Set counters here in case this is a retry. + bytesToSend = currentRequest->writeLen; + bytesToReceive = currentRequest->readLen; + + // On a single-master I2C bus, the start bit won't be sent until the bus + // state goes to IDLE so we can request it without waiting. On a + // multi-master bus, the bus may be BUSY under control of another master, + // in which case we can avoid some arbitration failures by waiting until + // the bus state is IDLE. We don't do that here. + + // If anything to send, initiate write. Otherwise initiate read. + if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) + { + // Send start and address with read flag (1) or'd in + // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + } + else { + // Send start and address with write flag (0) or'd in + // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + } +} + +/*************************************************************************** + * Initiate a stop bit for transmission (does not interrupt) + ***************************************************************************/ +void I2CManagerClass::I2C_sendStop() { + // s->I2CM.CTRLB.bit.CMD = 3; // Stop condition +} + +/*************************************************************************** + * Close I2C down + ***************************************************************************/ +void I2CManagerClass::I2C_close() { + I2C_sendStop(); + // Disable the I2C master mode and wait for sync + // s->I2CM.CTRLA.bit.ENABLE = 0 ; + // Wait for up to 500us only. + unsigned long startTime = micros(); + // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) { + // if (micros() - startTime >= 500UL) break; + // } +} + +/*************************************************************************** + * Main state machine for I2C, called from interrupt handler or, + * if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function + * (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()). + ***************************************************************************/ +void I2CManagerClass::I2C_handleInterrupt() { + + if (s->I2CM.STATUS.bit.ARBLOST) { + // Arbitration lost, restart + I2C_sendStart(); // Reinitiate request + } else if (s->I2CM.STATUS.bit.BUSERR) { + // Bus error + state = I2C_STATUS_BUS_ERROR; + } else if (s->I2CM.INTFLAG.bit.MB) { + // Master write completed + if (s->I2CM.STATUS.bit.RXNACK) { + // Nacked, send stop. + I2C_sendStop(); + state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + } else if (bytesToSend) { + // Acked, so send next byte + s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + bytesToSend--; + } else if (bytesToReceive) { + // Last sent byte acked and no more to send. Send repeated start, address and read bit. + s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + } else { + // No more data to send/receive. Initiate a STOP condition. + I2C_sendStop(); + state = I2C_STATUS_OK; // Done + } + } else if (s->I2CM.INTFLAG.bit.SB) { + // Master read completed without errors + if (bytesToReceive == 1) { + s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte + I2C_sendStop(); // send stop + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive = 0; + state = I2C_STATUS_OK; // done + } else if (bytesToReceive) { + s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte + currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + bytesToReceive--; + } + } +} + +#endif /* I2CMANAGER_STM32_H */ From d7f92d7b88d5ab2653d80eadd3354cb3f18a474a Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 8 Feb 2023 13:06:11 +0800 Subject: [PATCH 541/870] STM32 native I2C driver updates --- I2CManager_STM32.h | 93 ++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 0e944e7b2..70087501e 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -44,6 +44,7 @@ void I2C1_IRQHandler() { // Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants I2C_TypeDef *s = I2C1; +#define I2C_IRQn I2C1_EV_IRQn /*************************************************************************** * Set I2C clock speed register. This should only be called outside of @@ -109,22 +110,13 @@ void I2CManagerClass::I2C_init() s->CR1 |= (1<<15); // reset the I2C s->CR1 &= ~(1<<15); // Normal operation - // Program the peripheral input clock in I2C_CR2 Register in order to generate correct timings + // Program the peripheral input clock in CR2 Register in order to generate correct timings s->CR2 |= (16<<0); // PCLK1 FREQUENCY in MHz - // Configure the Clock Control Register for 100KHz SCL frequency - // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode - // Bit 14: Duty, fast mode duty cycle - // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) - s->CCR = 0x0050; - - // Configure the rise time register - max allowed in 1000ns - s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 - #if defined(I2C_USE_INTERRUPTS) // Setting NVIC - NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities - NVIC_EnableIRQ(I2C1_EV_IRQn); + NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities + NVIC_EnableIRQ(I2C_IRQn); // CR2 Interrupt Settings // Bit 15-13: reserved @@ -135,14 +127,21 @@ void I2CManagerClass::I2C_init() // Bit 8: ITERREN - Error interrupt enable // Bit 7-6: reserved // Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz) - // Enable all interrupts - s->CR2 |= 0x0700; + s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts #endif // Calculate baudrate and set default rate for now + // Configure the Clock Control Register for 100KHz SCL frequency + // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode + // Bit 14: Duty, fast mode duty cycle + // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) + s->CCR = 0x0050; - // Enable the I2C master mode and wait for sync + // Configure the rise time register - max allowed in 1000ns + s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 + // Enable the I2C master mode + s->CR1 |= (1<<0); // Enable I2C // Setting bus idle mode and wait for sync } @@ -154,6 +153,7 @@ void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. bytesToSend = currentRequest->writeLen; bytesToReceive = currentRequest->readLen; + uint8_t temp; // On a single-master I2C bus, the start bit won't be sent until the bus // state goes to IDLE so we can request it without waiting. On a @@ -164,12 +164,30 @@ void I2CManagerClass::I2C_sendStart() { // If anything to send, initiate write. Otherwise initiate read. if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { - // Send start and address with read flag (1) or'd in - // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + // Send start for read operation + s->CR1 |= (1<<10); // Enable the ACK + s->CR1 |= (1<<8); // Generate START + // Send address with read flag (1) or'd in + s->DR = (currentRequest->i2cAddress << 1) | 1; // send the address + while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + // Special case for 1 byte reads! + if (bytesToReceive == 1) + { + s->CR1 &= ~(1<<10); // clear the ACK bit + temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition + s->CR1 |= (1<<9); // Stop I2C + } + else + temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } else { - // Send start and address with write flag (0) or'd in - // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + // Send start for write operation + s->CR1 |= (1<<10); // Enable the ACK + s->CR1 |= (1<<8); // Generate START + // Send address with write flag (0) or'd in + s->DR = (currentRequest->i2cAddress << 1) | 0; // send the address + while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } } @@ -177,7 +195,7 @@ void I2CManagerClass::I2C_sendStart() { * Initiate a stop bit for transmission (does not interrupt) ***************************************************************************/ void I2CManagerClass::I2C_sendStop() { - // s->I2CM.CTRLB.bit.CMD = 3; // Stop condition + s->CR1 |= (1<<9); // Stop I2C } /*************************************************************************** @@ -186,12 +204,12 @@ void I2CManagerClass::I2C_sendStop() { void I2CManagerClass::I2C_close() { I2C_sendStop(); // Disable the I2C master mode and wait for sync - // s->I2CM.CTRLA.bit.ENABLE = 0 ; - // Wait for up to 500us only. + s->CR1 &= ~(1<<0); // Disable I2C peripheral + // Should never happen, but wait for up to 500us only. unsigned long startTime = micros(); - // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) { - // if (micros() - startTime >= 500UL) break; - // } + while ((s->CR1 && 1) != 0) { + if (micros() - startTime >= 500UL) break; + } } /*************************************************************************** @@ -201,41 +219,44 @@ void I2CManagerClass::I2C_close() { ***************************************************************************/ void I2CManagerClass::I2C_handleInterrupt() { - if (s->I2CM.STATUS.bit.ARBLOST) { + if (s->SR1 && (1<<9)) { // Arbitration lost, restart I2C_sendStart(); // Reinitiate request - } else if (s->I2CM.STATUS.bit.BUSERR) { + } else if (s->SR1 && (1<<8)) { // Bus error state = I2C_STATUS_BUS_ERROR; - } else if (s->I2CM.INTFLAG.bit.MB) { + } else if (s->SR1 && (1<<7)) { // Master write completed - if (s->I2CM.STATUS.bit.RXNACK) { + if (s->SR1 && (1<<10)) { // Nacked, send stop. I2C_sendStop(); state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; } else if (bytesToSend) { // Acked, so send next byte - s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + s->DR = currentRequest->writeBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { + // Check both TxE/BTF == 1 before generating stop + while (!(s->SR1 && (1<<7))); // Check TxE + while (!(s->SR1 && (1<<2))); // Check BTF // No more data to send/receive. Initiate a STOP condition. I2C_sendStop(); state = I2C_STATUS_OK; // Done } - } else if (s->I2CM.INTFLAG.bit.SB) { + } else if (s->SR1 && (1<<6)) { // Master read completed without errors if (bytesToReceive == 1) { - s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte +// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte I2C_sendStop(); // send stop - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive = 0; state = I2C_STATUS_OK; // done } else if (bytesToReceive) { - s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte +// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte + currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive--; } } From 3d480ee9ef378e32b03947ab342179f1b83c04fb Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 9 Feb 2023 05:32:27 +1000 Subject: [PATCH 542/870] Start adding servo to EX-IO --- IO_EXIOExpander.h | 142 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 8ec1f4818..6549873e9 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -49,6 +49,17 @@ */ class EXIOExpander : public IODevice { public: + + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } @@ -59,6 +70,11 @@ class EXIOExpander : public IODevice { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) { + _servoData[i] = NULL; + } addDevice(this); } @@ -123,6 +139,28 @@ class EXIOExpander : public IODevice { DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); return false; } + } else if (configType == CONFIGURE_SERVO) { + if (paramCount != 5) return false; +#ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); +#endif + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + if (state != -1) { + // Position servo to initial state + IODevice::writeAnalogue(pin, state ? s->activePosition : s->inactivePosition, 0, 0); + } + return true; } else { return false; } @@ -184,13 +222,86 @@ class EXIOExpander : public IODevice { } } - void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { int pin = vpin - _firstVpin; + /* Initial _writeAnalogue here _command4Buffer[0] = EXIOWRAN; _command4Buffer[1] = pin; _command4Buffer[2] = value & 0xFF; _command4Buffer[3] = value >> 8; I2CManager.write(_i2cAddress, _command4Buffer, 4); + */ + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command + // _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off PWM to prevent annoying servo buzz + // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } } void _display() override { @@ -218,6 +329,35 @@ class EXIOExpander : public IODevice { byte _receive3Buffer[3]; uint8_t* _analoguePinMap; + // Servo specific + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +const byte FLASH EXIOExpander::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + + // EX-IOExpander protocol flags enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup From 2848ba616b7dcc3fde1c976ab5b8b8a3301c085a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 07:38:00 +1000 Subject: [PATCH 543/870] Some success --- IO_EXIOExpander.h | 86 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 6549873e9..9912fe648 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -184,10 +184,14 @@ class EXIOExpander : public IODevice { // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning + if (_deviceState == DEVSTATE_FAILED) return; _command1Buffer[0] = EXIORDD; I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } } // Obtain the correct analogue input value @@ -205,20 +209,43 @@ class EXIOExpander : public IODevice { // Obtain the correct digital input value int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; - uint8_t pinByte = pin / 8; - bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); - return value; + if (_servoData[pin] == NULL) { + uint8_t pinByte = pin / 8; + bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); + return value; + } else { + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + return false; // No structure means no animation! + } else { + return (s->stepNumber < s->numSteps); + } + } } void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIOWRD; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = value; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + if (_servoData[pin] == NULL) { + _digitalOutBuffer[0] = EXIOWRD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = value; + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + } + } else { + if (value) value = 1; + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } } } @@ -232,12 +259,11 @@ class EXIOExpander : public IODevice { _command4Buffer[3] = value >> 8; I2CManager.write(_i2cAddress, _command4Buffer, 4); */ - #ifdef DIAG_IO +#ifdef DIAG_IO DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); - #endif +#endif if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; if (value > 4095) value = 4095; else if (value < 0) value = 0; @@ -288,22 +314,30 @@ class EXIOExpander : public IODevice { s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); } // Send servo command - // _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + this->writePWM(pin, s->currentPosition); } else if (s->stepNumber < s->numSteps + _catchupSteps) { // We've finished animation, wait a little to allow servo to catch up s->stepNumber++; } else if (s->stepNumber == s->numSteps + _catchupSteps && s->currentPosition != 0) { - #ifdef IO_SWITCH_OFF_SERVO - if ((s->currentProfile & NoPowerOff) == 0) { - // Wait has finished, so switch off PWM to prevent annoying servo buzz - // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); - } - #endif + // #ifdef IO_SWITCH_OFF_SERVO + // if ((s->currentProfile & NoPowerOff) == 0) { + // // Wait has finished, so switch off PWM to prevent annoying servo buzz + // // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + // } + // #endif s->numSteps = 0; // Done now. } } + void writePWM(int pin, uint16_t value) { + _command4Buffer[0] = EXIOWRAN; + _command4Buffer[1] = pin; + _command4Buffer[2] = value & 0xFF; + _command4Buffer[3] = value >> 8; + I2CManager.write(_i2cAddress, _command4Buffer, 4); + } + void _display() override { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, @@ -343,18 +377,18 @@ class EXIOExpander : public IODevice { uint16_t duration; // time (tenths of a second) for animation to complete. }; // 14 bytes per element, i.e. per pin in use - struct ServoData *_servoData [16]; + struct ServoData *_servoData[256]; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const uint8_t FLASH _bounceProfile[30]; + // static const uint8_t FLASH _bounceProfile[30]; const unsigned int refreshInterval = 50; // refresh every 50ms -// Profile for a bouncing signal or turnout -// The profile below is in the range 0-100% and should be combined with the desired limits -// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, -// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. -const byte FLASH EXIOExpander::_bounceProfile[30] = + // Profile for a bouncing signal or turnout + // The profile below is in the range 0-100% and should be combined with the desired limits + // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, + // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. + const byte FLASH _bounceProfile[30] = {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; // EX-IOExpander protocol flags From afc94a75bbc8b152d5305b195733cd022d540173 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 07:39:58 +1000 Subject: [PATCH 544/870] Remove excess drivers --- IO_PCA9685_basic.h | 149 ------------------------ IO_Servo.h | 277 --------------------------------------------- 2 files changed, 426 deletions(-) delete mode 100644 IO_PCA9685_basic.h delete mode 100644 IO_Servo.h diff --git a/IO_PCA9685_basic.h b/IO_PCA9685_basic.h deleted file mode 100644 index 4f809aaaf..000000000 --- a/IO_PCA9685_basic.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * © 2023, 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 . - */ - -/* - * This driver performs the basic interface between the HAL and an - * I2C-connected PCA9685 16-channel PWM module. When requested, it - * commands the device to set the PWM mark-to-period ratio accordingly. - * The call to IODevice::writeAnalogue(vpin, value) specifies the - * desired value in the range 0-4095 (0=0% and 4095=100%). - */ - -#ifndef PCA9685_BASIC_H -#define PCA9685_BASIC_H - -#include "IODevice.h" -#include "I2CManager.h" -#include "DIAG.h" - -/* - * IODevice subclass for PCA9685 16-channel PWM module. - */ - -class PCA9685_basic : public IODevice { -public: - // Create device driver instance. - static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685_basic(firstVpin, nPins, I2CAddress); - } - -private: - - // structures for setting up non-blocking writes to servo controller - I2CRB requestBlock; - uint8_t outputBuffer[5]; - - // REGISTER ADDRESSES - const byte PCA9685_MODE1=0x00; // Mode Register - const byte PCA9685_FIRST_SERVO=0x06; /** low byte first servo register ON*/ - const byte PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ - // MODE1 bits - const byte MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ - const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ - const byte MODE1_RESTART=0x80; /**< Restart enabled */ - - const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ - const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); - const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed - - // Constructor - PCA9685_basic(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - _firstVpin = firstVpin; - _nPins = min(nPins, 16); - _I2CAddress = I2CAddress; - addDevice(this); - - // Initialise structure used for setting pulse rate - requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); - } - - // Device-specific initialisation - void _begin() override { - I2CManager.begin(); - I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C - // In reality, other devices including the Arduino will limit - // the clock speed to a lower rate. - - // Initialise I/O module here. - if (I2CManager.exists(_I2CAddress)) { - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); - writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); - // In theory, we should wait 500us before sending any other commands to each device, to allow - // the PWM oscillator to get running. However, we don't do any specific wait, as there's - // plenty of other stuff to do before we will send a command. - #if defined(DIAG_IO) - _display(); - #endif - } else - _deviceState = DEVSTATE_FAILED; - } - - // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). - // - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { - #ifdef DIAG_IO - DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); - #endif - if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; - if (value > 4095) value = 4095; - else if (value < 0) value = 0; - - writeDevice(pin, value); - } - - // Display details of this device. - void _display() override { - 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("")); - } - - // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value - // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. - void writeDevice(uint8_t pin, int value) { - #ifdef DIAG_IO - DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); - #endif - // Wait for previous request to complete - 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); - } - } - - // Internal helper function for this device - static void writeRegister(byte address, byte reg, byte value) { - I2CManager.write(address, 2, reg, value); - } - -}; - -#endif \ No newline at end of file diff --git a/IO_Servo.h b/IO_Servo.h deleted file mode 100644 index b1935b6f5..000000000 --- a/IO_Servo.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * © 2023, 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_SERVO_H - -#include "IODevice.h" -#include "I2CManager.h" -#include "DIAG.h" - -class Servo : IODevice { - -public: - enum ProfileType : uint8_t { - Instant = 0, // Moves immediately between positions (if duration not specified) - UseDuration = 0, // Use specified duration - Fast = 1, // Takes around 500ms end-to-end - Medium = 2, // 1 second end-to-end - Slow = 3, // 2 seconds end-to-end - Bounce = 4, // For semaphores/turnouts with a bit of bounce!! - NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. - }; - - // Create device driver instance. - static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin) { - if (checkNoOverlap(firstVpin, nPins)) new Servo(firstVpin, nPins, firstSlavePin); - } - -private: - VPIN _firstSlavePin; - IODevice *_slaveDevice = NULL; - - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - }; // 14 bytes per element, i.e. per pin in use - - struct ServoData *_servoData [16]; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const uint8_t FLASH _bounceProfile[30]; - - const unsigned int refreshInterval = 50; // refresh every 50ms - - - // Configure a port on the Servo. - bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { - if (configType != CONFIGURE_SERVO) return false; - if (paramCount != 5) return false; - #ifdef DIAG_IO - DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), - vpin, params[0], params[1], params[2], params[3], params[4]); - #endif - - int8_t pin = vpin - _firstVpin; - VPIN slavePin = vpin - _firstVpin + _firstSlavePin; - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); - s = _servoData[pin]; - if (!s) return false; // Check for failed memory allocation - } - - s->activePosition = params[0]; - s->inactivePosition = params[1]; - s->profile = params[2]; - s->duration = params[3]; - int state = params[4]; - - if (state != -1) { - // Position servo to initial state - IODevice::writeAnalogue(slavePin, state ? s->activePosition : s->inactivePosition, 0, 0); - } - return true; - } - - // Constructor - Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin) { - _firstVpin = firstVpin; - _nPins = (nPins > 16) ? 16 : nPins; - _firstSlavePin = firstSlavePin; - - // To save RAM, space for servo configuration is not allocated unless a pin is used. - // Initialise the pointers to NULL. - for (int i=0; i<_nPins; i++) - _servoData[i] = NULL; - - addDevice(this); - } - - // Device-specific initialisation - void _begin() override { - // Get reference to slave device to make accesses faster. - _slaveDevice = this->findDevice(_firstSlavePin); - // Check firstSlavePin is actually allocated to a device - if (!_slaveDevice) { - DIAG(F("Servo: Slave device not found on pins %d-%d"), - _firstSlavePin, _firstSlavePin+_nPins-1); - _deviceState = DEVSTATE_FAILED; - } - // Check that the last slave pin is allocated to the same device. - if (_slaveDevice != this->findDevice(_firstSlavePin+_nPins-1)) { - DIAG(F("Servo: Slave device does not cover all pins %d-%d"), - _firstSlavePin, _firstSlavePin+_nPins-1); - _deviceState = DEVSTATE_FAILED; - } - #if defined(DIAG_IO) - _display(); - #endif - } - - // Device-specific write function, invoked from IODevice::write(). - // For this function, the configured profile is used. - void _write(VPIN vpin, int value) override { - if (_deviceState == DEVSTATE_FAILED) return; - #ifdef DIAG_IO - DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); - #endif - int pin = vpin - _firstVpin; - // VPIN slavePin = vpin - _firstVpin + _firstSlavePin; - if (value) value = 1; - - struct ServoData *s = _servoData[pin]; - if (s != NULL) { - // Use configured parameters - this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); - } else { - /* simulate digital pin on PWM */ - this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); - } - } - - // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). - // Profile is as follows: - // Bit 7: 0=Set PWM to 0% to power off servo motor when finished - // 1=Keep PWM pulses on (better when using PWM to drive an LED) - // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) - // 1 (Fast) Move servo in 0.5 seconds - // 2 (Medium) Move servo in 1.0 seconds - // 3 (Slow) Move servo in 2.0 seconds - // 4 (Bounce) Servo 'bounces' at extremes. - // - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { - #ifdef DIAG_IO - DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); - #endif - if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; - if (value > 4095) value = 4095; - else if (value < 0) value = 0; - - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - // Servo pin not configured, so configure now using defaults - s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); - if (s == NULL) return; // Check for memory allocation failure - s->activePosition = 4095; - s->inactivePosition = 0; - s->currentPosition = value; - s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) - } - - // Animated profile. Initiate the appropriate action. - s->currentProfile = profile; - uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. - s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds - profileValue==Medium ? 20 : // 1.0 seconds - profileValue==Slow ? 40 : // 2.0 seconds - profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds - duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) - s->stepNumber = 0; - s->toPosition = value; - s->fromPosition = s->currentPosition; - } - - // _read returns true if the device is currently in executing an animation, - // changing the output over a period of time. - int _read(VPIN vpin) override { - if (_deviceState == DEVSTATE_FAILED) return 0; - int pin = vpin - _firstVpin; - struct ServoData *s = _servoData[pin]; - if (s == NULL) - return false; // No structure means no animation! - else - return (s->stepNumber < s->numSteps); - } - - void _loop(unsigned long currentMicros) override { - if (_deviceState == DEVSTATE_FAILED) return; - for (int pin=0; pin<_nPins; pin++) { - updatePosition(pin); - } - delayUntil(currentMicros + refreshInterval * 1000UL); - } - - // Private function to reposition servo - // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. - void updatePosition(uint8_t pin) { - struct ServoData *s = _servoData[pin]; - if (s == NULL) return; // No pin configuration/state data - - if (s->numSteps == 0) return; // No animation in progress - - if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { - // Go straight to end of sequence, output final position. - s->stepNumber = s->numSteps-1; - } - - if (s->stepNumber < s->numSteps) { - // Animation in progress, reposition servo - s->stepNumber++; - if ((s->currentProfile & ~NoPowerOff) == Bounce) { - // Retrieve step positions from array in flash - uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); - s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); - } else { - // All other profiles - calculate step by linear interpolation between from and to positions. - s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); - } - // Send servo command - _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); - } else if (s->stepNumber < s->numSteps + _catchupSteps) { - // We've finished animation, wait a little to allow servo to catch up - s->stepNumber++; - } else if (s->stepNumber == s->numSteps + _catchupSteps - && s->currentPosition != 0) { - #ifdef IO_SWITCH_OFF_SERVO - if ((s->currentProfile & NoPowerOff) == 0) { - // Wait has finished, so switch off PWM to prevent annoying servo buzz - _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); - } - #endif - s->numSteps = 0; // Done now. - } - } - - // Display details of this device. - void _display() override { - DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), - (int)_firstVpin, (int)_firstVpin+_nPins-1, - (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, - (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); - } -}; - -// Profile for a bouncing signal or turnout -// The profile below is in the range 0-100% and should be combined with the desired limits -// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, -// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. -const byte FLASH Servo::_bounceProfile[30] = - {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; - - -#endif \ No newline at end of file From 5cb216dd79b7cbfabbdd632a3ed45e0c365d9f01 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 08:41:50 +1000 Subject: [PATCH 545/870] Servo functional --- IO_EXIOExpander.h | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 9912fe648..f7190ee3e 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -189,8 +189,13 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); - for (int pin=0; pin<_nPins; pin++) { - updatePosition(pin); + if ((currentMicros - _lastRefresh) / 1000UL > refreshInterval) { + _lastRefresh = currentMicros; + for (int pin=0; pin<_nPins; pin++) { + if (_servoData[pin] != NULL) { + updatePosition(pin); + } + } } } @@ -249,16 +254,8 @@ class EXIOExpander : public IODevice { } } - // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { int pin = vpin - _firstVpin; - /* Initial _writeAnalogue here - _command4Buffer[0] = EXIOWRAN; - _command4Buffer[1] = pin; - _command4Buffer[2] = value & 0xFF; - _command4Buffer[3] = value >> 8; - I2CManager.write(_i2cAddress, _command4Buffer, 4); - */ #ifdef DIAG_IO DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); @@ -320,12 +317,6 @@ class EXIOExpander : public IODevice { s->stepNumber++; } else if (s->stepNumber == s->numSteps + _catchupSteps && s->currentPosition != 0) { - // #ifdef IO_SWITCH_OFF_SERVO - // if ((s->currentProfile & NoPowerOff) == 0) { - // // Wait has finished, so switch off PWM to prevent annoying servo buzz - // // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); - // } - // #endif s->numSteps = 0; // Done now. } } @@ -380,9 +371,9 @@ class EXIOExpander : public IODevice { struct ServoData *_servoData[256]; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - // static const uint8_t FLASH _bounceProfile[30]; - + const unsigned int refreshInterval = 50; // refresh every 50ms + unsigned long _lastRefresh = 0; // Profile for a bouncing signal or turnout // The profile below is in the range 0-100% and should be combined with the desired limits From deb49f294356cba8aea36d11af0b16541f98c30a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 09:31:09 +1000 Subject: [PATCH 546/870] Fix dynamic RAM allocation --- IO_EXIOExpander.h | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f7190ee3e..afe645728 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -72,6 +72,7 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. + _servoData = (ServoData**) calloc(_nPins, sizeof(ServoData*)); for (int i=0; i<_nPins; i++) { _servoData[i] = NULL; } @@ -139,28 +140,6 @@ class EXIOExpander : public IODevice { DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); return false; } - } else if (configType == CONFIGURE_SERVO) { - if (paramCount != 5) return false; -#ifdef DIAG_IO - DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), - vpin, params[0], params[1], params[2], params[3], params[4]); -#endif - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); - s = _servoData[pin]; - if (!s) return false; // Check for failed memory allocation - } - s->activePosition = params[0]; - s->inactivePosition = params[1]; - s->profile = params[2]; - s->duration = params[3]; - int state = params[4]; - if (state != -1) { - // Position servo to initial state - IODevice::writeAnalogue(pin, state ? s->activePosition : s->inactivePosition, 0, 0); - } - return true; } else { return false; } @@ -368,7 +347,8 @@ class EXIOExpander : public IODevice { uint16_t duration; // time (tenths of a second) for animation to complete. }; // 14 bytes per element, i.e. per pin in use - struct ServoData *_servoData[256]; + // struct ServoData *_servoData[256]; + ServoData** _servoData; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off From dd0ee8b50a766e800395c350dea862c6a0151dd6 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:13:23 +0000 Subject: [PATCH 547/870] Additional support for Extended I2C Addresses --- I2CManager.cpp | 7 +++- I2CManager.h | 80 +++++++++++++++++++++++++++++++++++++++- I2CManager_NonBlocking.h | 2 +- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 00aa71b83..96081659e 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -95,7 +95,7 @@ void I2CManagerClass::begin(void) { for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { found = true; - DIAG(F("I2C Device found at x%x, %S?"), addr, guessI2CDeviceType(addr)); + DIAG(F("I2C Device found at 0x%x, %S?"), addr, guessI2CDeviceType(addr)); } } @@ -117,7 +117,7 @@ void I2CManagerClass::begin(void) { // Device responds when subbus selected but not when // subbus disabled - ergo it must be on subbus! found = true; - DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,x%x}, %S?"), + DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,0x%x}, %S?"), muxNo, subBus, addr, guessI2CDeviceType(addr)); } // Re-select subbus @@ -266,6 +266,9 @@ I2CManagerClass I2CManager = I2CManagerClass(); // try, and failure from timeout does not get retried. unsigned long I2CManagerClass::timeout = 100000UL; +// Buffer for conversion of I2CAddress to char*. +/* static */ char I2CAddress::addressBuffer[30]; + #if defined(I2C_EXTENDED_ADDRESS) // Count of I2C multiplexers found when initialising. If there is only one // MUX then the subbus does not de-selecting after use; however, if there diff --git a/I2CManager.h b/I2CManager.h index 2a3d80c1e..d50d9ce7f 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -192,6 +192,7 @@ struct I2CAddress { I2CMux _muxNumber; I2CSubBus _subBus; uint8_t _deviceAddress; + static char addressBuffer[]; public: // Constructors // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. @@ -228,6 +229,41 @@ struct I2CAddress { // (device assumed to be on the main I2C bus or on a currently selected subbus. operator uint8_t () const { return _deviceAddress; } + // Conversion from I2CAddress to char* (uses static storage so only + // one conversion can be done at a time). So don't call it twice in a + // single DIAG statement for example. + const char* toString() { + char *ptr = addressBuffer; + if (_muxNumber != I2CMux_None) { + strcpy_P(ptr, (const char*)F("{I2CMux_")); + ptr += 8; + *ptr++ = '0' + _muxNumber; + strcpy_P(ptr, (const char*)F(",Subbus_")); + ptr += 8; + if (_subBus == SubBus_None) { + strcpy_P(ptr, (const char*)F("None")); + ptr += 4; + } else if (_subBus == SubBus_All) { + strcpy_P(ptr, (const char*)F("All")); + ptr += 3; + } else + *ptr++ = '0' + _subBus; + *ptr++ = ','; + } + uint8_t temp = _deviceAddress; + *ptr++ = '0'; + *ptr++ = 'x'; + for (uint8_t index = 0; index<2; index++) { + uint8_t bits = (temp >> 4) & 0x0f; + *ptr++ = bits > 9 ? bits-10+'A' : bits+'0'; + temp <<= 4; + } + if (_muxNumber != I2CMux_None) + *ptr++ = '}'; + *ptr = 0; // terminate string + return addressBuffer; + } + // Comparison operator int operator == (I2CAddress &a) const { if (_deviceAddress != a._deviceAddress) @@ -249,8 +285,50 @@ struct I2CAddress { }; #else +struct I2CAddress { +private: + uint8_t _deviceAddress; + static char addressBuffer[]; +public: + // Constructors + I2CAddress(const uint8_t deviceAddress) { + _deviceAddress = deviceAddress; + } + + // Basic constructor + I2CAddress() : I2CAddress(0) {} + + // Conversion operator from I2CAddress to uint8_t + // For "uint8_t address = i2cAddress;" syntax + operator uint8_t () const { return _deviceAddress; } + + // Conversion from I2CAddress to char* (uses static storage so only + // one conversion can be done at a time). So don't call it twice in a + // single DIAG statement for example. + const char* toString () { + char *ptr = addressBuffer; + // Just display hex value, two digits. + uint8_t temp = _deviceAddress; + *ptr++ = '0'; + *ptr++ = 'x'; + for (uint8_t index = 0; index<2; index++) { + uint8_t bits = (temp >> 4) & 0xf; + *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; + temp <<= 4; + } + *ptr = 0; // terminate string + return addressBuffer; + } + + // Comparison operator + int operator == (I2CAddress &a) const { + if (_deviceAddress != a._deviceAddress) + return false; // Different device address so no match + return true; // Same address on same mux and same subbus + } +}; // Legacy single-byte I2C address type for compact code and smooth changeover. -typedef uint8_t I2CAddress; +//typedef uint8_t I2CAddress; #endif // I2C_EXTENDED_ADDRESS diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 4214ef9cb..4da932ffd 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -210,7 +210,7 @@ void I2CManagerClass::checkForTimeout() { unsigned long elapsed = micros() - startTime; if (elapsed > timeout) { #ifdef DIAG_IO - //DIAG(F("I2CManager Timeout on x%x, I2CRB=x%x"), (int)t->i2cAddress, currentRequest); + //DIAG(F("I2CManager Timeout on %s, I2CRB=%s"), t->i2cAddress.toString(), currentRequest); #endif // Excessive time. Dequeue request queueHead = t->nextRequest; From 9dd9990979f14de3d64831a84b81c896c4f361ad Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:16:06 +0000 Subject: [PATCH 548/870] Improve formatting of I2CAddress data type in diagnostics. --- IODevice.cpp | 13 ++++++++++--- IODevice.h | 6 +++++- IO_AnalogueInputs.h | 6 +++--- IO_EXFastclock.h | 7 +++---- IO_EXIOExpander.h | 31 +++++++++++++++---------------- IO_EXTurntable.h | 6 +++--- IO_GPIOBase.h | 14 +++++++------- IO_OLEDDisplay.h | 24 +++++++++++++++--------- IO_PCA9685.cpp | 6 +++--- IO_PCA9685pwm.h | 10 +++++----- IO_RotaryEncoder.h | 2 +- IO_VL53L0X.h | 30 ++++++++++++++---------------- LiquidCrystal_I2C.cpp | 2 +- SSD1306Ascii.cpp | 2 +- 14 files changed, 86 insertions(+), 73 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index be26b9265..32e6445f8 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -74,6 +74,13 @@ void IODevice::begin() { MCP23017::create(180, 16, 0x21); } +// reset() function to reinitialise all devices +void IODevice::reset() { + for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) { + dev->_begin(); + } +} + // Overarching static loop() method for the IODevice subsystem. Works through the // list of installed devices and calls their individual _loop() method. // Devices may or may not implement this, but if they do it is useful for things like animations @@ -302,9 +309,9 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) { // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device -bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) { +bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) { #ifdef DIAG_IO - DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress); + DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString()); #endif VPIN lastPin=firstPin+nPins-1; for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { @@ -322,7 +329,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) } // Check for overlapping I2C address if (i2cAddress && dev->_I2CAddress==i2cAddress) { - DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),(int)i2cAddress); + DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString()); return false; } } diff --git a/IODevice.h b/IODevice.h index f5c7c6f46..8fd5efda6 100644 --- a/IODevice.h +++ b/IODevice.h @@ -113,6 +113,10 @@ class IODevice { // Also, the _begin method of any existing instances is called from here. static void begin(); + // reset function to invoke all driver's _begin() methods again, to + // reset the state of the devices and reinitialise. + static void reset(); + // configure is used invoke an IODevice instance's _configure method static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]); @@ -165,7 +169,7 @@ class IODevice { void setGPIOInterruptPin(int16_t pinNumber); // Method to check if pins will overlap before creating new device. - static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0); + static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0); // Method used by IODevice filters to locate slave pins that may be overlayed by their own // pin range. diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index a03d90fbf..8ff8683fe 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -85,7 +85,7 @@ class ADS111x: public IODevice { _display(); #endif } else { - DIAG(F("ADS111x device not found, I2C:%x"), (int)_I2CAddress); + DIAG(F("ADS111x device not found, I2C:%s"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } @@ -131,7 +131,7 @@ class ADS111x: public IODevice { break; } } else { // error status - DIAG(F("ADS111x I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("ADS111x I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } } @@ -142,7 +142,7 @@ class ADS111x: public IODevice { } void _display() override { - DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, + DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index 0e1bf76d7..442799b13 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -56,7 +56,7 @@ static void create(uint8_t _I2CAddress) { // XXXX change thistosave2 bytes if (_checkforclock == 0) { FAST_CLOCK_EXISTS = true; - //DIAG(F("I2C Fast Clock found at x%x"), _I2CAddress); + //DIAG(F("I2C Fast Clock found at %s"), _I2CAddress.toString()); new EXFastClock(_I2CAddress); } else { @@ -68,7 +68,6 @@ static void create(uint8_t _I2CAddress) { } private: -uint8_t _I2CAddress; // Initialisation of Fastclock @@ -84,7 +83,7 @@ void _begin() override { } else { _deviceState = DEVSTATE_FAILED; //LCD(6,F("CLOCK NOT FOUND")); - DIAG(F("Fast Clock Not Found at address %d"), _I2CAddress); + DIAG(F("Fast Clock Not Found at address %s"), _I2CAddress.toString()); } } } @@ -120,7 +119,7 @@ void _loop(unsigned long currentMicros) override{ // Display EX-FastClock device driver info. void _display() { - DIAG(F("FastCLock on I2C:x%x - %S"), _I2CAddress, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } }; diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d2269f6f6..06cbc24aa 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -63,7 +63,7 @@ class EXIOExpander : public IODevice { EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress, int numDigitalPins, int numAnaloguePins) { _firstVpin = firstVpin; _nPins = nPins; - _i2cAddress = i2cAddress; + _I2CAddress = i2cAddress; _numDigitalPins = numDigitalPins; _numAnaloguePins = numAnaloguePins; _digitalPinBytes = (numDigitalPins+7)/8; @@ -76,31 +76,31 @@ class EXIOExpander : public IODevice { void _begin() { // Initialise EX-IOExander device I2CManager.begin(); - if (I2CManager.exists(_i2cAddress)) { + if (I2CManager.exists(_I2CAddress)) { _digitalOutBuffer[0] = EXIOINIT; _digitalOutBuffer[1] = _numDigitalPins; _digitalOutBuffer[2] = _numAnaloguePins; // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); + I2CManager.read(_I2CAddress, _commandBuffer, 1, _digitalOutBuffer, 3); if (_commandBuffer[0] != EXIORDY) { - DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), (int)_i2cAddress); + DIAG(F("ERROR configuring EX-IOExpander device, I2C:%s"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; return; } // Attempt to get version, if we don't get it, we don't care, don't go offline // Using digital in buffer in reverse to save RAM _commandBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); + I2CManager.read(_I2CAddress, _versionBuffer, 3, _commandBuffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; - DIAG(F("EX-IOExpander device found, I2C:x%x, Version v%d.%d.%d"), - (int)_i2cAddress, _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); + DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"), + _I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("EX-IOExpander device not found, I2C:x%x"), (int)_i2cAddress); + DIAG(F("EX-IOExpander device not found, I2C:%s"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } @@ -117,7 +117,7 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + I2CManager.write(_I2CAddress, _digitalOutBuffer, 3); return true; } @@ -130,16 +130,16 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _analogueOutBuffer[0] = EXIOENAN; _analogueOutBuffer[1] = pin; - I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + I2CManager.write(_I2CAddress, _analogueOutBuffer, 2); return true; } void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); + I2CManager.read(_I2CAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); _commandBuffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + I2CManager.read(_I2CAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); } int _readAnalogue(VPIN vpin) override { @@ -164,7 +164,7 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + I2CManager.write(_I2CAddress, _digitalOutBuffer, 3); } void _display() override { @@ -176,14 +176,13 @@ class EXIOExpander : public IODevice { _firstAnalogue = _firstVpin + _numDigitalPins; _lastAnalogue = _firstVpin + _nPins - 1; } - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), - (int)_i2cAddress, _majorVer, _minorVer, _patchVer, + DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, _numAnaloguePins, _firstAnalogue, _lastAnalogue, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } - uint8_t _i2cAddress; uint8_t _numDigitalPins; uint8_t _numAnaloguePins; byte _analogueOutBuffer[2]; diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index 5b501b386..02a87e392 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -106,15 +106,15 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), - (int)_I2CAddress, stepsMSB, stepsLSB, activity); + _I2CAddress.toString(), stepsMSB, stepsLSB, activity); #endif _stepperStatus = 1; // Tell the device driver Turntable-EX is busy - I2CManager.write((int)_I2CAddress, 3, stepsMSB, stepsLSB, activity); + I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity); } // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("EX-Turntable I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 17e72d67e..0f7019cb9 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -107,7 +107,7 @@ void GPIOBase::_begin() { _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { - DIAG(F("%S I2C:x%x Device not detected"), _deviceName, (int)_I2CAddress); + DIAG(F("%S I2C:%s Device not detected"), _deviceName, _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } @@ -121,7 +121,7 @@ bool GPIOBase::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun bool pullup = params[0]; int pin = vpin - _firstVpin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, pullup); + DIAG(F("%S I2C:%s Config Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, pullup); #endif uint16_t mask = 1 << pin; if (pullup) @@ -151,7 +151,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { _deviceState = DEVSTATE_NORMAL; } else { _deviceState = DEVSTATE_FAILED; - DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, (int)_I2CAddress, status, + DIAG(F("%S I2C:%s Error:%d %S"), _deviceName, _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); } _processCompletion(status); @@ -174,7 +174,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { #ifdef DIAG_IO if (differences) - DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, (int)_I2CAddress, _portInputState); + DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState); #endif } @@ -195,7 +195,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { template void GPIOBase::_display() { - DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, (int)_I2CAddress, + DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -204,7 +204,7 @@ void GPIOBase::_write(VPIN vpin, int value) { int pin = vpin - _firstVpin; T mask = 1 << pin; #ifdef DIAG_IO - DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, (int)_I2CAddress, pin, value); + DIAG(F("%S I2C:%s Write Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, value); #endif // Set port mode output if currently not output mode @@ -240,7 +240,7 @@ int GPIOBase::_read(VPIN vpin) { // 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, (int)_I2CAddress, _portInputState); + DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState); #endif } return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 9aefbaadd..1ef8ed10c 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -86,32 +86,38 @@ class OLEDDisplay : public IODevice, DisplayInterface { _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + // Fill buffer with spaces + memset(_buffer, ' ', _numCols*_numRows); + + // Create OLED driver + oled = new SSD1306AsciiWire(); + + // Clear the entire screen + oled->clearNative(); addDevice(this); } - + // Device-specific initialisation void _begin() override { - // Create OLED driver - oled = new SSD1306AsciiWire(); // Initialise device if (oled->begin(_I2CAddress, _width, _height)) { // Store pointer to this object into CS display hook, so that we // will intercept any subsequent calls to lcdDisplay methods. DisplayInterface::lcdDisplay = this; - DIAG(F("OLEDDisplay installed on address x%x"), (int)_I2CAddress); - - // First clear the entire screen - oled->clearNative(); + DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString()); // Set first two lines on screen LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; } } - void _loop(unsigned long) override { // Loop through the buffer and if a row has changed @@ -196,7 +202,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { // Display information about the device. void _display() { - DIAG(F("OLEDDisplay Configured addr x%x"), (int)_I2CAddress); + DIAG(F("OLEDDisplay Configured addr %s"), _I2CAddress.toString()); } }; diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index f7a6882e9..68e44a115 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -239,13 +239,13 @@ void PCA9685::updatePosition(uint8_t pin) { // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void PCA9685::writeDevice(uint8_t pin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); + DIAG(F("PCA9685 I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete uint8_t status = requestBlock.wait(); if (status != I2C_STATUS_OK) { _deviceState = DEVSTATE_FAILED; - DIAG(F("PCA9685 I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); + DIAG(F("PCA9685 I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status)); } else { // Set up new request. outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; @@ -259,7 +259,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) { // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index 72884a9c7..ef92118e0 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -101,8 +101,8 @@ class PCA9685pwm : public IODevice { void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning #ifdef DIAG_IO - DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), + vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; @@ -114,7 +114,7 @@ class PCA9685pwm : public IODevice { // Display details of this device. void _display() override { - DIAG(F("PCA9685pwm I2C:x%x Configured on Vpins:%d-%d %S"), (int)_I2CAddress, (int)_firstVpin, + DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } @@ -122,13 +122,13 @@ class PCA9685pwm : public IODevice { // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void writeDevice(uint8_t pin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685pwm I2C:x%x WriteDevice Pin:%d Value:%d"), (int)_I2CAddress, pin, value); + DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete uint8_t status = requestBlock.wait(); if (status != I2C_STATUS_OK) { _deviceState = DEVSTATE_FAILED; - DIAG(F("PCA9685pwm I2C:x%x failed %S"), (int)_I2CAddress, I2CManager.getErrorMessage(status)); + DIAG(F("PCA9685pwm I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status)); } else { // Set up new request. outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin; diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 6de97afb3..9cf4e65e3 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,7 +104,7 @@ class RotaryEncoder : public IODevice { } void _display() override { - DIAG(F("Rotary Encoder I2C:x%x v%d.%d.%d Configured on Vpin:%d-%d %S"), (int)_I2CAddress, _majorVer, _minorVer, _patchVer, + DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 86d9624a8..a1e1dccec 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -159,7 +159,9 @@ class VL53L0X : public IODevice { if (_xshutPin == VPIN_NONE && I2CManager.exists(_I2CAddress)) { // Device already present on this address, so skip the address initialisation. _nextState = STATE_CONFIGUREDEVICE; - } + } else + _nextState = STATE_INIT; + } void _loop(unsigned long currentMicros) override { @@ -171,7 +173,7 @@ class VL53L0X : public IODevice { // If no XSHUT pin is configured, then only one device is supported. if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); _nextState = STATE_RESTARTMODULE; - delayUntil(currentMicros+1000); + delayUntil(currentMicros+10000); break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. @@ -183,9 +185,8 @@ class VL53L0X : public IODevice { // shared flag accessible to all device instances. if (_addressConfigInProgress) return; _addressConfigInProgress = true; - // Set XSHUT pin (if connected). Because of supply voltage differences, - // drive the signal through the digital output's pull-up resistor. - if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, 1); + // Set XSHUT pin (if connected) to bring the module out of sleep mode. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); // Allow the module time to restart delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; @@ -202,15 +203,12 @@ class VL53L0X : public IODevice { I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); #endif } - _addressConfigInProgress = false; - _nextState = STATE_SKIP; - break; - case STATE_SKIP: - // Do nothing on the third entry. - _nextState = STATE_CONFIGUREDEVICE; + delayUntil(currentMicros+10000); break; case STATE_CONFIGUREDEVICE: - // On next entry, check if device address has been set. + // Allow next VL53L0X device to be configured + _addressConfigInProgress = false; + // Now check if device address has been set. if (I2CManager.exists(_I2CAddress)) { #ifdef DIAG_IO _display(); @@ -220,7 +218,7 @@ class VL53L0X : public IODevice { read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); _nextState = STATE_INITIATESCAN; } else { - DIAG(F("VL53L0X I2C:x%x device not responding"), (int)_I2CAddress); + DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; _nextState = STATE_FAILED; } @@ -285,7 +283,7 @@ class VL53L0X : public IODevice { // Function to report a failed I2C operation. Put the device off-line. void reportError(uint8_t status) { - DIAG(F("VL53L0X I2C:x%x Error:%d %S"), (int)_I2CAddress, status, I2CManager.getErrorMessage(status)); + DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; _value = false; } @@ -314,8 +312,8 @@ class VL53L0X : public IODevice { } void _display() override { - DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), - (int)_I2CAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, + DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), + _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index b18614f3c..a5c2e16a2 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -53,7 +53,7 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz. if (I2CManager.exists(lcd_Addr)) { - DIAG(F("%dx%d LCD configured on I2C:x%x"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); + DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; begin(); backlight(); diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 12d8702d9..deae5ac52 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -197,7 +197,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { return false; } // Device found - DIAG(F("%dx%d OLED display configured on I2C:x%x"), m_displayWidth, m_displayHeight, (int)m_i2cAddr); + DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString()); clear(); return true; } From 7de46a0c17d4ccc1aef40af5b55aee14ddf47dc4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:16:39 +0000 Subject: [PATCH 549/870] Add command to attempt to reset failed devices. --- DCCEXParser.cpp | 2 ++ version.h | 1 + 2 files changed, 3 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 478b1f843..c7d23e939 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -948,6 +948,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) case HASH_KEYWORD_HAL: if (p[1] == HASH_KEYWORD_SHOW) IODevice::DumpAll(); + else if (p[1] == HASH_KEYWORD_RESET) + IODevice::reset(); break; #endif diff --git a/version.h b/version.h index d6ad661de..0a263b724 100644 --- a/version.h +++ b/version.h @@ -19,6 +19,7 @@ // (2) animations of servo movement via PCA9685pwm. // This is intended to support EXIOExpander and also // replace the existing PCA9685 driver. +// Add to reinitialise failed drivers. // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From 9e0e110b5d42bad2f6f227fa5436c8da9d1aa1f3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Feb 2023 00:17:31 +0000 Subject: [PATCH 550/870] Update defines.h - inappropriate define NO_INTERRUPTS replaced with I2C_USE_WIRE. --- defines.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/defines.h b/defines.h index 9ad58511f..e9ae631d0 100644 --- a/defines.h +++ b/defines.h @@ -64,8 +64,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY35) #define ARDUINO_TYPE "TEENSY35" @@ -76,8 +76,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY36) #define ARDUINO_TYPE "TEENSY36" @@ -87,8 +87,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY40) #define ARDUINO_TYPE "TEENSY40" @@ -98,8 +98,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_TEENSY41) #define ARDUINO_TYPE "TEENSY41" @@ -109,8 +109,8 @@ #define DISABLE_EEPROM #endif // Teensy support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif #elif defined(ARDUINO_ARCH_ESP8266) #define ARDUINO_TYPE "ESP8266" @@ -135,8 +135,8 @@ #define DISABLE_EEPROM #endif // STM32 support for native I2C is awaiting development -#ifndef I2C_NO_INTERRUPTS - #define I2C_NO_INTERRUPTS +#ifndef I2C_USE_WIRE + #define I2C_USE_WIRE #endif From 93ac1b6d611f3dd8e422e29d84e06e988c92485a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 12:45:34 +1000 Subject: [PATCH 551/870] Revert IODevice.h change --- IODevice.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/IODevice.h b/IODevice.h index e7906c601..72beb9e73 100644 --- a/IODevice.h +++ b/IODevice.h @@ -242,12 +242,11 @@ class IODevice { // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); - private: // Method to check whether the vpin corresponds to this device bool owns(VPIN vpin); + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; From d8cbdb24e154766d16ccbeb00f94c3645e17cf47 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 552/870] Refactored, analogue tested --- IO_EXIOExpander.h | 97 ++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d54feacd8..724ea42d9 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -54,22 +54,18 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress, numDigitalPins, numAnaloguePins); + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress, int numDigitalPins, int numAnaloguePins) { + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _numDigitalPins = numDigitalPins; - _numAnaloguePins = numAnaloguePins; - _digitalPinBytes = (numDigitalPins+7)/8; - _analoguePinBytes = numAnaloguePins * 2; + _digitalPinBytes = (nPins+7)/8; _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); - _analogueInputStates=(byte*) calloc(_analoguePinBytes,1); addDevice(this); } @@ -77,20 +73,26 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { - _digitalOutBuffer[0] = EXIOINIT; - _digitalOutBuffer[1] = _numDigitalPins; - _digitalOutBuffer[2] = _numAnaloguePins; - // Send config, if EXIORDY returned, we're good, otherwise go offline - I2CManager.read(_i2cAddress, _commandBuffer, 1, _digitalOutBuffer, 3); - if (_commandBuffer[0] != EXIORDY) { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; return; } + // We now need to retrieve the analogue pin map + _command1Buffer[0] = EXIOINITA; + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline - // Using digital in buffer in reverse to save RAM - _commandBuffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _commandBuffer, 1); + _command1Buffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -105,13 +107,10 @@ class EXIOExpander : public IODevice { } } + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - if (vpin >= _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is an analogue pin, cannot use as a digital pin"), vpin); - return false; - } bool pullup = params[0]; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIODPUP; @@ -121,37 +120,39 @@ class EXIOExpander : public IODevice { return true; } - // We only use this to detect incorrect use of analogue pins + // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) { - DIAG(F("EX-IOExpander ERROR: Vpin %d is a digital pin, cannot use as an analogue pin"), vpin); - return false; - } int pin = vpin - _firstVpin; - _analogueOutBuffer[0] = EXIOENAN; - _analogueOutBuffer[1] = pin; - I2CManager.write(_i2cAddress, _analogueOutBuffer, 2); + _command2Buffer[0] = EXIOENAN; + _command2Buffer[1] = pin; + I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } + // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning - _commandBuffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _commandBuffer, 1); - _commandBuffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _commandBuffer, 1); + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } + // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { - if (vpin < _firstVpin + _numDigitalPins) return false; - int pin = vpin - _firstVpin - _numDigitalPins; - uint8_t _pinLSBByte = pin * 2; + int pin = vpin - _firstVpin; + uint8_t _pinLSBByte; + for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { + if (_analoguePinMap[aPin] == pin) { + _pinLSBByte = aPin * 2; + } + } uint8_t _pinMSBByte = _pinLSBByte + 1; return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } + // Obtain the correct digital input value int _read(VPIN vpin) override { - if (vpin >= _firstVpin + _numDigitalPins) return false; int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); @@ -159,7 +160,6 @@ class EXIOExpander : public IODevice { } void _write(VPIN vpin, int value) override { - if (vpin >= _firstVpin + _numDigitalPins) return; int pin = vpin - _firstVpin; _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; @@ -168,25 +168,14 @@ class EXIOExpander : public IODevice { } void _display() override { - int _firstAnalogue, _lastAnalogue; - if (_numAnaloguePins == 0) { - _firstAnalogue = 0; - _lastAnalogue = 0; - } else { - _firstAnalogue = _firstVpin + _numDigitalPins; - _lastAnalogue = _firstVpin + _nPins - 1; - } - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d: %d Digital Vpins %d-%d, %d Analogue Vpins %d-%d %S"), + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, - _numDigitalPins, _firstVpin, _firstVpin + _numDigitalPins - 1, - _numAnaloguePins, _firstAnalogue, _lastAnalogue, + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; - uint8_t _numDigitalPins; uint8_t _numAnaloguePins; - byte _analogueOutBuffer[2]; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -196,7 +185,10 @@ class EXIOExpander : public IODevice { byte* _analogueInputStates; uint8_t _digitalPinBytes = 0; uint8_t _analoguePinBytes = 0; - byte _commandBuffer[1]; + byte _command1Buffer[1]; + byte _command2Buffer[2]; + byte _receive2Buffer[2]; + uint8_t* _analoguePinMap; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure @@ -207,6 +199,7 @@ class EXIOExpander : public IODevice { EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input EXIOENAN = 0xE7, // Flag eo enable an analogue pin + EXIOINITA = 0xE8, // Flag we're receiving analogue pin info }; }; From d41b5e09387e1a5514ca372cf77cb9c17dea892f Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 19:26:33 +1000 Subject: [PATCH 553/870] Brief PWM start --- IO_EXIOExpander.h | 76 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 724ea42d9..f284bc17d 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -1,5 +1,5 @@ /* - * © 2021, Peter Cole. All rights reserved. + * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation * @@ -26,19 +26,14 @@ * (Note the device driver is included by default) * * void halSetup() { -* // EXIOExpander::create(vpin, num_vpins, i2c_address, digitalPinCount, analoguePinCount); -* EXIOExpander::create(800, 18, 0x65, 12, 8); +* // EXIOExpander::create(vpin, num_vpins, i2c_address); +* EXIOExpander::create(800, 18, 0x65); * } * -* Note when defining the number of digital and analogue pins, there is no way to sanity check -* this from the device driver, and it is up to the user to define the correct values here. -* -* All pins available on the EX-IOExpander device must be accounted for. -* -* Vpins are allocated to digital pins first, and then analogue pins, so digital pins will -* populate the first part of the specified vpin range, with the analogue pins populating the -* last part of the vpin range. -* Eg. for a default Nano, 800 - 811 are digital (D2 - D13), 812 to 817 are analogue (A0 - A3, A6/A7). +* All pins on an EX-IOExpander device are allocated according to the pin map for the specific +* device in use. There is no way for the device driver to sanity check pins are used for the +* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used +* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output). */ #ifndef IO_EX_IOEXPANDER_H @@ -76,12 +71,15 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive3Buffer[1]; + _numPWMPins = _receive3Buffer[2]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + _servoData = (struct ServoData*) calloc(_numPWMPins, 14); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -109,15 +107,24 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { - if (configType != CONFIGURE_INPUT) return false; if (paramCount != 1) return false; - bool pullup = params[0]; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + if (configType == CONFIGURE_INPUT) { + bool pullup = params[0]; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } else if (configType == CONFIGURE_SERVO) { + DIAG(F("Configure servo at pin %d"), (int)pin); + for (int i = 0; i < paramCount; i++) { + DIAG(F("Param %d is %x"), (int)i, params[i]); + } + return true; + } else { + return false; + } } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -175,7 +182,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins; + uint8_t _numAnaloguePins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -187,8 +194,29 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; + uint8_t _numPWMPins = 0; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + } ServoData; // 14 bytes per element, i.e. per pin in use + + struct ServoData* _servoData; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const byte FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From 53215b496e71b07541fcde0fa165654370e00fcc Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 554/870] Refactored, analogue tested --- IO_EXIOExpander.h | 75 +++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f284bc17d..c96262345 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -71,15 +71,12 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); - if (_receive3Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive3Buffer[1]; - _numPWMPins = _receive3Buffer[2]; + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - _servoData = (struct ServoData*) calloc(_numPWMPins, 14); - } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -105,26 +102,17 @@ class EXIOExpander : public IODevice { } } + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; + bool pullup = params[0]; int pin = vpin - _firstVpin; - if (configType == CONFIGURE_INPUT) { - bool pullup = params[0]; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; - } else if (configType == CONFIGURE_SERVO) { - DIAG(F("Configure servo at pin %d"), (int)pin); - for (int i = 0; i < paramCount; i++) { - DIAG(F("Param %d is %x"), (int)i, params[i]); - } - return true; - } else { - return false; - } + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -133,9 +121,13 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; I2CManager.write(_i2cAddress, _command2Buffer, 2); + _command2Buffer[0] = EXIOENAN; + _command2Buffer[1] = pin; + I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } + // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning @@ -143,10 +135,22 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } + // Obtain the correct analogue input value // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { + int pin = vpin - _firstVpin; + uint8_t _pinLSBByte; + for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { + if (_analoguePinMap[aPin] == pin) { + _pinLSBByte = aPin * 2; + } + } int pin = vpin - _firstVpin; uint8_t _pinLSBByte; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { @@ -158,6 +162,7 @@ class EXIOExpander : public IODevice { return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } + // Obtain the correct digital input value // Obtain the correct digital input value int _read(VPIN vpin) override { int pin = vpin - _firstVpin; @@ -178,11 +183,12 @@ class EXIOExpander : public IODevice { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } uint8_t _i2cAddress; - uint8_t _numAnaloguePins = 0; + uint8_t _numAnaloguePins; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -194,29 +200,8 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive3Buffer[3]; + byte _receive2Buffer[2]; uint8_t* _analoguePinMap; - uint8_t _numPWMPins = 0; - - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - } ServoData; // 14 bytes per element, i.e. per pin in use - - struct ServoData* _servoData; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const byte FLASH _bounceProfile[30]; - - const unsigned int refreshInterval = 50; // refresh every 50ms enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From 0c2f8428dfdf324014654f6e4862c1cd3614c8e0 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 555/870] Refactored, analogue tested --- IO_EXIOExpander.h | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index c96262345..5c10d8843 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -49,12 +49,15 @@ */ class EXIOExpander : public IODevice { public: + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor + EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; @@ -68,6 +71,16 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline @@ -85,9 +98,14 @@ class EXIOExpander : public IODevice { // We now need to retrieve the analogue pin map _command1Buffer[0] = EXIOINITA; I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); + // We now need to retrieve the analogue pin map + _command1Buffer[0] = EXIOINITA; + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline _command1Buffer[0] = EXIOVER; I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); + _command1Buffer[0] = EXIOVER; + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -102,7 +120,6 @@ class EXIOExpander : public IODevice { } } - // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; @@ -115,19 +132,16 @@ class EXIOExpander : public IODevice { return true; } + // Analogue input pin configuration, used to enable on EX-IOExpander device // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { int pin = vpin - _firstVpin; _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; I2CManager.write(_i2cAddress, _command2Buffer, 2); - _command2Buffer[0] = EXIOENAN; - _command2Buffer[1] = pin; - I2CManager.write(_i2cAddress, _command2Buffer, 2); return true; } - // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning @@ -135,22 +149,10 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); - _command1Buffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); } - // Obtain the correct analogue input value // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { - int pin = vpin - _firstVpin; - uint8_t _pinLSBByte; - for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { - if (_analoguePinMap[aPin] == pin) { - _pinLSBByte = aPin * 2; - } - } int pin = vpin - _firstVpin; uint8_t _pinLSBByte; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { @@ -162,7 +164,6 @@ class EXIOExpander : public IODevice { return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; } - // Obtain the correct digital input value // Obtain the correct digital input value int _read(VPIN vpin) override { int pin = vpin - _firstVpin; @@ -180,10 +181,10 @@ class EXIOExpander : public IODevice { } void _display() override { + DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, - (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } @@ -202,6 +203,10 @@ class EXIOExpander : public IODevice { byte _command2Buffer[2]; byte _receive2Buffer[2]; uint8_t* _analoguePinMap; + byte _command1Buffer[1]; + byte _command2Buffer[2]; + byte _receive2Buffer[2]; + uint8_t* _analoguePinMap; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From cf2817d7c41aca8eeea1e0846ebca5e43c68f6f6 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 19:26:33 +1000 Subject: [PATCH 556/870] Brief PWM start --- IO_EXIOExpander.h | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 5c10d8843..5476a41eb 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -90,6 +90,18 @@ class EXIOExpander : public IODevice { _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { + _command2Buffer[0] = EXIOINIT; + _command2Buffer[1] = _nPins; + // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive3Buffer[1]; + _numPWMPins = _receive3Buffer[2]; + _analoguePinBytes = _numAnaloguePins * 2; + _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -123,13 +135,23 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; - bool pullup = params[0]; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + if (configType == CONFIGURE_INPUT) { + bool pullup = params[0]; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } else if (configType == CONFIGURE_SERVO) { + DIAG(F("Configure servo at pin %d"), (int)pin); + for (int i = 0; i < paramCount; i++) { + DIAG(F("Param %d is %x"), (int)i, params[i]); + } + return true; + } else { + return false; + } } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -189,7 +211,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins; + uint8_t _numAnaloguePins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -201,7 +223,7 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; byte _command1Buffer[1]; byte _command2Buffer[2]; From 984ef6feadfbe99e5e4b1869749b98ce0eee653a Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 10:06:01 +1000 Subject: [PATCH 557/870] Refactored, analogue tested --- IO_EXIOExpander.h | 60 ++++++----------------------------------------- 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 5476a41eb..1b0d23f9e 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -49,15 +49,12 @@ */ class EXIOExpander : public IODevice { public: - static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { - if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } private: // Constructor - EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { EXIOExpander(VPIN firstVpin, int nPins, uint8_t i2cAddress) { _firstVpin = firstVpin; _nPins = nPins; @@ -80,28 +77,6 @@ class EXIOExpander : public IODevice { _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { - _command2Buffer[0] = EXIOINIT; - _command2Buffer[1] = _nPins; - // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; - _analoguePinBytes = _numAnaloguePins * 2; - _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); - _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { - _command2Buffer[0] = EXIOINIT; - _command2Buffer[1] = _nPins; - // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); - if (_receive3Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive3Buffer[1]; - _numPWMPins = _receive3Buffer[2]; - _analoguePinBytes = _numAnaloguePins * 2; - _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); - _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -110,14 +85,9 @@ class EXIOExpander : public IODevice { // We now need to retrieve the analogue pin map _command1Buffer[0] = EXIOINITA; I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); - // We now need to retrieve the analogue pin map - _command1Buffer[0] = EXIOINITA; - I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); // Attempt to get version, if we don't get it, we don't care, don't go offline _command1Buffer[0] = EXIOVER; I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); - _command1Buffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -135,26 +105,15 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; + bool pullup = params[0]; int pin = vpin - _firstVpin; - if (configType == CONFIGURE_INPUT) { - bool pullup = params[0]; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; - } else if (configType == CONFIGURE_SERVO) { - DIAG(F("Configure servo at pin %d"), (int)pin); - for (int i = 0; i < paramCount; i++) { - DIAG(F("Param %d is %x"), (int)i, params[i]); - } - return true; - } else { - return false; - } + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; } - // Analogue input pin configuration, used to enable on EX-IOExpander device // Analogue input pin configuration, used to enable on EX-IOExpander device int _configureAnalogIn(VPIN vpin) override { int pin = vpin - _firstVpin; @@ -203,7 +162,6 @@ class EXIOExpander : public IODevice { } void _display() override { - DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, @@ -211,7 +169,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins = 0; + uint8_t _numAnaloguePins; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -223,10 +181,6 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive3Buffer[3]; - uint8_t* _analoguePinMap; - byte _command1Buffer[1]; - byte _command2Buffer[2]; byte _receive2Buffer[2]; uint8_t* _analoguePinMap; From fa38583772b0286768e20de3c6d0d6835aa34112 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 29 Jan 2023 19:26:33 +1000 Subject: [PATCH 558/870] Brief PWM start --- IO_EXIOExpander.h | 56 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 1b0d23f9e..f284bc17d 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -71,12 +71,15 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive3Buffer[1]; + _numPWMPins = _receive3Buffer[2]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + _servoData = (struct ServoData*) calloc(_numPWMPins, 14); + } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -105,13 +108,23 @@ class EXIOExpander : public IODevice { // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; - bool pullup = params[0]; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + if (configType == CONFIGURE_INPUT) { + bool pullup = params[0]; + _digitalOutBuffer[0] = EXIODPUP; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = pullup; + I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + return true; + } else if (configType == CONFIGURE_SERVO) { + DIAG(F("Configure servo at pin %d"), (int)pin); + for (int i = 0; i < paramCount; i++) { + DIAG(F("Param %d is %x"), (int)i, params[i]); + } + return true; + } else { + return false; + } } // Analogue input pin configuration, used to enable on EX-IOExpander device @@ -169,7 +182,7 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; - uint8_t _numAnaloguePins; + uint8_t _numAnaloguePins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -181,8 +194,29 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; + uint8_t _numPWMPins = 0; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + } ServoData; // 14 bytes per element, i.e. per pin in use + + struct ServoData* _servoData; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const byte FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From d375723a137219192d36baea6b135eec76ae1a4b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 31 Jan 2023 19:29:39 +1000 Subject: [PATCH 559/870] Cleaned up PWM start --- IO_EXIOExpander.h | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f284bc17d..34ece1b7f 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -78,7 +78,6 @@ class EXIOExpander : public IODevice { _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - _servoData = (struct ServoData*) calloc(_numPWMPins, 14); } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); @@ -198,26 +197,6 @@ class EXIOExpander : public IODevice { uint8_t* _analoguePinMap; uint8_t _numPWMPins = 0; - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - } ServoData; // 14 bytes per element, i.e. per pin in use - - struct ServoData* _servoData; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const byte FLASH _bounceProfile[30]; - - const unsigned int refreshInterval = 50; // refresh every 50ms - enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup From 6031a0fb7fc841fbc02cf4e8f53aac59c0500da4 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 1 Feb 2023 07:49:31 +1000 Subject: [PATCH 560/870] Fix mess after rebase and conflicts --- IO_EXIOExpander.h | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 34ece1b7f..69fb6af71 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -71,14 +71,12 @@ class EXIOExpander : public IODevice { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); - if (_receive3Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive3Buffer[1]; - _numPWMPins = _receive3Buffer[2]; + I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); + if (_receive2Buffer[0] == EXIOINITA) { + _numAnaloguePins = _receive2Buffer[1]; _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { DIAG(F("ERROR configuring EX-IOExpander device, I2C:x%x"), _i2cAddress); _deviceState = DEVSTATE_FAILED; @@ -115,12 +113,6 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[2] = pullup; I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); return true; - } else if (configType == CONFIGURE_SERVO) { - DIAG(F("Configure servo at pin %d"), (int)pin); - for (int i = 0; i < paramCount; i++) { - DIAG(F("Param %d is %x"), (int)i, params[i]); - } - return true; } else { return false; } @@ -182,6 +174,7 @@ class EXIOExpander : public IODevice { uint8_t _i2cAddress; uint8_t _numAnaloguePins = 0; + uint8_t numDigitalPins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -193,9 +186,8 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive3Buffer[3]; + byte _receive2Buffer[2]; uint8_t* _analoguePinMap; - uint8_t _numPWMPins = 0; enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From 4d31cd64a54f2c2f2386bc40cb6c6e24858c8351 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 31 Jan 2023 19:32:12 +1000 Subject: [PATCH 561/870] Add new drivers --- IODevice.h | 5 +- IO_PCA9685_basic.h | 149 ++++++++++++++++++++++++ IO_Servo.h | 277 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 IO_PCA9685_basic.h create mode 100644 IO_Servo.h diff --git a/IODevice.h b/IODevice.h index 72beb9e73..e7906c601 100644 --- a/IODevice.h +++ b/IODevice.h @@ -242,11 +242,12 @@ class IODevice { // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); + private: // Method to check whether the vpin corresponds to this device bool owns(VPIN vpin); - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; diff --git a/IO_PCA9685_basic.h b/IO_PCA9685_basic.h new file mode 100644 index 000000000..4f809aaaf --- /dev/null +++ b/IO_PCA9685_basic.h @@ -0,0 +1,149 @@ +/* + * © 2023, 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 . + */ + +/* + * This driver performs the basic interface between the HAL and an + * I2C-connected PCA9685 16-channel PWM module. When requested, it + * commands the device to set the PWM mark-to-period ratio accordingly. + * The call to IODevice::writeAnalogue(vpin, value) specifies the + * desired value in the range 0-4095 (0=0% and 4095=100%). + */ + +#ifndef PCA9685_BASIC_H +#define PCA9685_BASIC_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +/* + * IODevice subclass for PCA9685 16-channel PWM module. + */ + +class PCA9685_basic : public IODevice { +public: + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685_basic(firstVpin, nPins, I2CAddress); + } + +private: + + // structures for setting up non-blocking writes to servo controller + I2CRB requestBlock; + uint8_t outputBuffer[5]; + + // REGISTER ADDRESSES + const byte PCA9685_MODE1=0x00; // Mode Register + const byte PCA9685_FIRST_SERVO=0x06; /** low byte first servo register ON*/ + const byte PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ + // MODE1 bits + const byte MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ + const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ + const byte MODE1_RESTART=0x80; /**< Restart enabled */ + + const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); + const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed + + // Constructor + PCA9685_basic(VPIN firstVpin, int nPins, uint8_t I2CAddress) { + _firstVpin = firstVpin; + _nPins = min(nPins, 16); + _I2CAddress = I2CAddress; + addDevice(this); + + // Initialise structure used for setting pulse rate + requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C + // In reality, other devices including the Arduino will limit + // the clock speed to a lower rate. + + // Initialise I/O module here. + if (I2CManager.exists(_I2CAddress)) { + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); + // In theory, we should wait 500us before sending any other commands to each device, to allow + // the PWM oscillator to get running. However, we don't do any specific wait, as there's + // plenty of other stuff to do before we will send a command. + #if defined(DIAG_IO) + _display(); + #endif + } else + _deviceState = DEVSTATE_FAILED; + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + writeDevice(pin, value); + } + + // Display details of this device. + void _display() override { + 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("")); + } + + // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value + // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. + void writeDevice(uint8_t pin, int value) { + #ifdef DIAG_IO + DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); + #endif + // Wait for previous request to complete + 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); + } + } + + // Internal helper function for this device + static void writeRegister(byte address, byte reg, byte value) { + I2CManager.write(address, 2, reg, value); + } + +}; + +#endif \ No newline at end of file diff --git a/IO_Servo.h b/IO_Servo.h new file mode 100644 index 000000000..bd475fbc9 --- /dev/null +++ b/IO_Servo.h @@ -0,0 +1,277 @@ +/* + * © 2023, 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_SERVO_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "DIAG.h" + +class Servo : IODevice { + +public: + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + + // Create device driver instance. + static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin) { + if (checkNoOverlap(firstVpin, nPins)) new Servo(firstVpin, nPins, firstSlavePin); + } + +private: + VPIN _firstSlavePin; + IODevice *_slaveDevice = NULL; + + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + + + // Configure a port on the Servo. + bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { + if (configType != CONFIGURE_SERVO) return false; + if (paramCount != 5) return false; + #ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); + #endif + + int8_t pin = vpin - _firstVpin; + VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + + if (state != -1) { + // Position servo to initial state + IODevice::writeAnalogue(slavePin, state ? s->activePosition : s->inactivePosition, 0, 0); + } + return true; + } + + // Constructor + Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin) { + _firstVpin = firstVpin; + _nPins = (nPins > 16) ? 16 : nPins; + _firstSlavePin = firstSlavePin; + + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) + _servoData[i] = NULL; + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + // Get reference to slave device to make accesses faster. + _slaveDevice = this->findDevice(_firstSlavePin); + // Check firstSlavePin is actually allocated to a device + if (!_slaveDevice) { + DIAG(F("Servo: Slave device not found on pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + // Check that the last slave pin is allocated to the same device. + if (_slaveDevice != this->findDevice(_firstSlavePin+_nPins-1)) { + DIAG(F("Servo: Slave device does not cover all pins %d-%d"), + _firstSlavePin, _firstSlavePin+_nPins-1); + _deviceState = DEVSTATE_FAILED; + } + #if defined(DIAG_IO) + _display(); + #endif + } + + // Device-specific write function, invoked from IODevice::write(). + // For this function, the configured profile is used. + void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; + #ifdef DIAG_IO + DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + #endif + int pin = vpin - _firstVpin; + VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + if (value) value = 1; + + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } + } + + // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). + // Profile is as follows: + // Bit 7: 0=Set PWM to 0% to power off servo motor when finished + // 1=Keep PWM pulses on (better when using PWM to drive an LED) + // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) + // 1 (Fast) Move servo in 0.5 seconds + // 2 (Medium) Move servo in 1.0 seconds + // 3 (Slow) Move servo in 2.0 seconds + // 4 (Bounce) Servo 'bounces' at extremes. + // + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + // _read returns true if the device is currently in executing an animation, + // changing the output over a period of time. + int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + int pin = vpin - _firstVpin; + struct ServoData *s = _servoData[pin]; + if (s == NULL) + return false; // No structure means no animation! + else + return (s->stepNumber < s->numSteps); + } + + void _loop(unsigned long currentMicros) override { + if (_deviceState == DEVSTATE_FAILED) return; + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } + delayUntil(currentMicros + refreshInterval * 1000UL); + } + + // Private function to reposition servo + // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command + _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off PWM to prevent annoying servo buzz + _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } + } + + // Display details of this device. + void _display() override { + DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + (int)_firstVpin, (int)_firstVpin+_nPins-1, + (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, + (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); + } +}; + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +const byte FLASH Servo::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + + +#endif \ No newline at end of file From e53ed7b46d4fbf41595186b56e0e16acdc83607f Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:53:46 +1000 Subject: [PATCH 562/870] Brief start on PWM --- IO_EXIOExpander.h | 25 ++++++++++++++++--------- IO_Servo.h | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 69fb6af71..f653a80e3 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -59,8 +59,6 @@ class EXIOExpander : public IODevice { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - _digitalPinBytes = (nPins+7)/8; - _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); addDevice(this); } @@ -70,10 +68,13 @@ class EXIOExpander : public IODevice { if (I2CManager.exists(_i2cAddress)) { _command2Buffer[0] = EXIOINIT; _command2Buffer[1] = _nPins; - // Send config, if EXIOINITA returned, we're good, setup analogue input buffer, otherwise go offline - I2CManager.read(_i2cAddress, _receive2Buffer, 2, _command2Buffer, 2); - if (_receive2Buffer[0] == EXIOINITA) { - _numAnaloguePins = _receive2Buffer[1]; + // Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + if (_receive3Buffer[0] == EXIOPINS) { + _numDigitalPins = _receive3Buffer[1]; + _numAnaloguePins = _receive3Buffer[2]; + _digitalPinBytes = (_numDigitalPins + 7)/8; + _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); _analoguePinBytes = _numAnaloguePins * 2; _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); @@ -165,6 +166,11 @@ class EXIOExpander : public IODevice { I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); } + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + int pin = vpin - _firstVpin; + DIAG(F("Write %d to pin %d, param 1 %d, param 2 %d"), value, pin, param1, param2); + } + void _display() override { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, @@ -173,8 +179,8 @@ class EXIOExpander : public IODevice { } uint8_t _i2cAddress; + uint8_t _numDigitalPins = 0; uint8_t _numAnaloguePins = 0; - uint8_t numDigitalPins = 0; byte _digitalOutBuffer[3]; uint8_t _versionBuffer[3]; uint8_t _majorVer = 0; @@ -186,7 +192,7 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; - byte _receive2Buffer[2]; + byte _receive3Buffer[3]; uint8_t* _analoguePinMap; enum { @@ -198,7 +204,8 @@ class EXIOExpander : public IODevice { EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input EXIOENAN = 0xE7, // Flag eo enable an analogue pin - EXIOINITA = 0xE8, // Flag we're receiving analogue pin info + EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings + EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers }; }; diff --git a/IO_Servo.h b/IO_Servo.h index bd475fbc9..b1935b6f5 100644 --- a/IO_Servo.h +++ b/IO_Servo.h @@ -139,7 +139,7 @@ class Servo : IODevice { DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; - VPIN slavePin = vpin - _firstVpin + _firstSlavePin; + // VPIN slavePin = vpin - _firstVpin + _firstSlavePin; if (value) value = 1; struct ServoData *s = _servoData[pin]; From 5efb0c50135ce93aff36cbc8cafd4e77dece0e83 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Wed, 1 Feb 2023 19:46:08 +1000 Subject: [PATCH 563/870] Basic PWM working --- IO_EXIOExpander.h | 19 ++++++++++++++----- version.h | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f653a80e3..cb3152850 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -66,10 +66,12 @@ class EXIOExpander : public IODevice { // Initialise EX-IOExander device I2CManager.begin(); if (I2CManager.exists(_i2cAddress)) { - _command2Buffer[0] = EXIOINIT; - _command2Buffer[1] = _nPins; + _command4Buffer[0] = EXIOINIT; + _command4Buffer[1] = _nPins; + _command4Buffer[2] = _firstVpin & 0xFF; + _command4Buffer[3] = _firstVpin >> 8; // Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command2Buffer, 2); + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4); if (_receive3Buffer[0] == EXIOPINS) { _numDigitalPins = _receive3Buffer[1]; _numAnaloguePins = _receive3Buffer[2]; @@ -168,7 +170,11 @@ class EXIOExpander : public IODevice { void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { int pin = vpin - _firstVpin; - DIAG(F("Write %d to pin %d, param 1 %d, param 2 %d"), value, pin, param1, param2); + _command4Buffer[0] = EXIOWRAN; + _command4Buffer[1] = pin; + _command4Buffer[2] = value & 0xFF; + _command4Buffer[3] = value >> 8; + I2CManager.write(_i2cAddress, _command4Buffer, 4); } void _display() override { @@ -192,6 +198,7 @@ class EXIOExpander : public IODevice { uint8_t _analoguePinBytes = 0; byte _command1Buffer[1]; byte _command2Buffer[2]; + byte _command4Buffer[4]; byte _receive3Buffer[3]; uint8_t* _analoguePinMap; @@ -203,9 +210,11 @@ class EXIOExpander : public IODevice { EXIORDAN = 0xE4, // Flag to read an analogue input EXIOWRD = 0xE5, // Flag for digital write EXIORDD = 0xE6, // Flag to read digital input - EXIOENAN = 0xE7, // Flag eo enable an analogue pin + EXIOENAN = 0xE7, // Flag to enable an analogue pin EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers + EXIOWRAN = 0xEA, // Flag we're sending an analogue write (PWM) + EXIOERR = 0xEF, // Flag we've received an error }; }; diff --git a/version.h b/version.h index 67886459a..c37a1c44e 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.14" +#define VERSION "4.2.15" +// 4.2.15 Separate Servo from PCA9685 +// Add PWM support to EX-IOExpander // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From 2a3d48dc009625e1b4384b2c025d6628d160be2d Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 4 Feb 2023 09:19:32 +1000 Subject: [PATCH 564/870] Fix digital read bug --- IO_EXIOExpander.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index cb3152850..d7d73ae7f 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -156,7 +156,7 @@ class EXIOExpander : public IODevice { int _read(VPIN vpin) override { int pin = vpin - _firstVpin; uint8_t pinByte = pin / 8; - bool value = _digitalInputStates[pinByte] >> (pin - pinByte * 8); + bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); return value; } From 938b4cfbd6acaa2ebd558ab5ed1f377d25c93b5c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 6 Feb 2023 19:39:25 +1000 Subject: [PATCH 565/870] Update version --- version.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index c37a1c44e..d95e01dd4 100644 --- a/version.h +++ b/version.h @@ -5,8 +5,9 @@ #define VERSION "4.2.15" -// 4.2.15 Separate Servo from PCA9685 -// Add PWM support to EX-IOExpander +// 4.2.15 Add Servo device driver with PCA9685_basic driver +// Add basic experimental PWM support to EX-IOExpander +// EX-IOExpander 0.0.14 minimum required // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again // 4.2.12 Bugfix for issue #299 TurnoutDescription NULL From ad97260055187bfacd4b4e6d634c012e1d297c81 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 7 Feb 2023 07:32:16 +1000 Subject: [PATCH 566/870] Add extra error checking --- IO_EXIOExpander.h | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d7d73ae7f..43a6155e6 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -114,8 +114,13 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); - return true; + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] == EXIORDY) { + return true; + } else { + DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); + return false; + } } else { return false; } @@ -126,7 +131,13 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; - I2CManager.write(_i2cAddress, _command2Buffer, 2); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2); + if (_command1Buffer[0] == EXIORDY) { + return true; + } else { + DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin); + return false; + } return true; } @@ -165,7 +176,10 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.write(_i2cAddress, _digitalOutBuffer, 3); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + } } void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { From c768bdc36101b536822b01c53ca5a66bfc06ae1f Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 9 Feb 2023 05:32:27 +1000 Subject: [PATCH 567/870] Start adding servo to EX-IO --- IO_EXIOExpander.h | 142 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 43a6155e6..f29dad3ca 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -49,6 +49,17 @@ */ class EXIOExpander : public IODevice { public: + + enum ProfileType : uint8_t { + Instant = 0, // Moves immediately between positions (if duration not specified) + UseDuration = 0, // Use specified duration + Fast = 1, // Takes around 500ms end-to-end + Medium = 2, // 1 second end-to-end + Slow = 3, // 2 seconds end-to-end + Bounce = 4, // For semaphores/turnouts with a bit of bounce!! + NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. + }; + static void create(VPIN vpin, int nPins, uint8_t i2cAddress) { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } @@ -59,6 +70,11 @@ class EXIOExpander : public IODevice { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; + // To save RAM, space for servo configuration is not allocated unless a pin is used. + // Initialise the pointers to NULL. + for (int i=0; i<_nPins; i++) { + _servoData[i] = NULL; + } addDevice(this); } @@ -121,6 +137,28 @@ class EXIOExpander : public IODevice { DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); return false; } + } else if (configType == CONFIGURE_SERVO) { + if (paramCount != 5) return false; +#ifdef DIAG_IO + DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + vpin, params[0], params[1], params[2], params[3], params[4]); +#endif + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); + s = _servoData[pin]; + if (!s) return false; // Check for failed memory allocation + } + s->activePosition = params[0]; + s->inactivePosition = params[1]; + s->profile = params[2]; + s->duration = params[3]; + int state = params[4]; + if (state != -1) { + // Position servo to initial state + IODevice::writeAnalogue(pin, state ? s->activePosition : s->inactivePosition, 0, 0); + } + return true; } else { return false; } @@ -182,13 +220,86 @@ class EXIOExpander : public IODevice { } } - void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { int pin = vpin - _firstVpin; + /* Initial _writeAnalogue here _command4Buffer[0] = EXIOWRAN; _command4Buffer[1] = pin; _command4Buffer[2] = value & 0xFF; _command4Buffer[3] = value >> 8; I2CManager.write(_i2cAddress, _command4Buffer, 4); + */ + #ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); + #endif + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; + if (value > 4095) value = 4095; + else if (value < 0) value = 0; + + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + // Servo pin not configured, so configure now using defaults + s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); + if (s == NULL) return; // Check for memory allocation failure + s->activePosition = 4095; + s->inactivePosition = 0; + s->currentPosition = value; + s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) + } + + // Animated profile. Initiate the appropriate action. + s->currentProfile = profile; + uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. + s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds + profileValue==Medium ? 20 : // 1.0 seconds + profileValue==Slow ? 40 : // 2.0 seconds + profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds + duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) + s->stepNumber = 0; + s->toPosition = value; + s->fromPosition = s->currentPosition; + } + + void updatePosition(uint8_t pin) { + struct ServoData *s = _servoData[pin]; + if (s == NULL) return; // No pin configuration/state data + + if (s->numSteps == 0) return; // No animation in progress + + if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { + // Go straight to end of sequence, output final position. + s->stepNumber = s->numSteps-1; + } + + if (s->stepNumber < s->numSteps) { + // Animation in progress, reposition servo + s->stepNumber++; + if ((s->currentProfile & ~NoPowerOff) == Bounce) { + // Retrieve step positions from array in flash + uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); + s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); + } else { + // All other profiles - calculate step by linear interpolation between from and to positions. + s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); + } + // Send servo command + // _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + } else if (s->stepNumber < s->numSteps + _catchupSteps) { + // We've finished animation, wait a little to allow servo to catch up + s->stepNumber++; + } else if (s->stepNumber == s->numSteps + _catchupSteps + && s->currentPosition != 0) { + #ifdef IO_SWITCH_OFF_SERVO + if ((s->currentProfile & NoPowerOff) == 0) { + // Wait has finished, so switch off PWM to prevent annoying servo buzz + // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + } + #endif + s->numSteps = 0; // Done now. + } } void _display() override { @@ -216,6 +327,35 @@ class EXIOExpander : public IODevice { byte _receive3Buffer[3]; uint8_t* _analoguePinMap; + // Servo specific + struct ServoData { + uint16_t activePosition : 12; // Config parameter + uint16_t inactivePosition : 12; // Config parameter + uint16_t currentPosition : 12; + uint16_t fromPosition : 12; + uint16_t toPosition : 12; + uint8_t profile; // Config parameter + uint16_t stepNumber; // Index of current step (starting from 0) + uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. + uint8_t currentProfile; // profile being used for current animation. + uint16_t duration; // time (tenths of a second) for animation to complete. + }; // 14 bytes per element, i.e. per pin in use + + struct ServoData *_servoData [16]; + + static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off + static const uint8_t FLASH _bounceProfile[30]; + + const unsigned int refreshInterval = 50; // refresh every 50ms + +// Profile for a bouncing signal or turnout +// The profile below is in the range 0-100% and should be combined with the desired limits +// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, +// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. +const byte FLASH EXIOExpander::_bounceProfile[30] = + {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; + + // EX-IOExpander protocol flags enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup From f59fe6e83bcd8684832117cc05c4d885d937ef9f Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 07:38:00 +1000 Subject: [PATCH 568/870] Some success --- IO_EXIOExpander.h | 86 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f29dad3ca..84beb4baa 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -182,10 +182,14 @@ class EXIOExpander : public IODevice { // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning + if (_deviceState == DEVSTATE_FAILED) return; _command1Buffer[0] = EXIORDD; I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); + for (int pin=0; pin<_nPins; pin++) { + updatePosition(pin); + } } // Obtain the correct analogue input value @@ -203,20 +207,43 @@ class EXIOExpander : public IODevice { // Obtain the correct digital input value int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; - uint8_t pinByte = pin / 8; - bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); - return value; + if (_servoData[pin] == NULL) { + uint8_t pinByte = pin / 8; + bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); + return value; + } else { + struct ServoData *s = _servoData[pin]; + if (s == NULL) { + return false; // No structure means no animation! + } else { + return (s->stepNumber < s->numSteps); + } + } } void _write(VPIN vpin, int value) override { + if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIOWRD; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = value; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + if (_servoData[pin] == NULL) { + _digitalOutBuffer[0] = EXIOWRD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = value; + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + } + } else { + if (value) value = 1; + struct ServoData *s = _servoData[pin]; + if (s != NULL) { + // Use configured parameters + this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + } else { + /* simulate digital pin on PWM */ + this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + } } } @@ -230,12 +257,11 @@ class EXIOExpander : public IODevice { _command4Buffer[3] = value >> 8; I2CManager.write(_i2cAddress, _command4Buffer, 4); */ - #ifdef DIAG_IO +#ifdef DIAG_IO DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); - #endif +#endif if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; if (value > 4095) value = 4095; else if (value < 0) value = 0; @@ -286,22 +312,30 @@ class EXIOExpander : public IODevice { s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); } // Send servo command - // _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); + this->writePWM(pin, s->currentPosition); } else if (s->stepNumber < s->numSteps + _catchupSteps) { // We've finished animation, wait a little to allow servo to catch up s->stepNumber++; } else if (s->stepNumber == s->numSteps + _catchupSteps && s->currentPosition != 0) { - #ifdef IO_SWITCH_OFF_SERVO - if ((s->currentProfile & NoPowerOff) == 0) { - // Wait has finished, so switch off PWM to prevent annoying servo buzz - // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); - } - #endif + // #ifdef IO_SWITCH_OFF_SERVO + // if ((s->currentProfile & NoPowerOff) == 0) { + // // Wait has finished, so switch off PWM to prevent annoying servo buzz + // // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); + // } + // #endif s->numSteps = 0; // Done now. } } + void writePWM(int pin, uint16_t value) { + _command4Buffer[0] = EXIOWRAN; + _command4Buffer[1] = pin; + _command4Buffer[2] = value & 0xFF; + _command4Buffer[3] = value >> 8; + I2CManager.write(_i2cAddress, _command4Buffer, 4); + } + void _display() override { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, @@ -341,18 +375,18 @@ class EXIOExpander : public IODevice { uint16_t duration; // time (tenths of a second) for animation to complete. }; // 14 bytes per element, i.e. per pin in use - struct ServoData *_servoData [16]; + struct ServoData *_servoData[256]; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const uint8_t FLASH _bounceProfile[30]; + // static const uint8_t FLASH _bounceProfile[30]; const unsigned int refreshInterval = 50; // refresh every 50ms -// Profile for a bouncing signal or turnout -// The profile below is in the range 0-100% and should be combined with the desired limits -// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, -// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. -const byte FLASH EXIOExpander::_bounceProfile[30] = + // Profile for a bouncing signal or turnout + // The profile below is in the range 0-100% and should be combined with the desired limits + // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, + // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. + const byte FLASH _bounceProfile[30] = {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; // EX-IOExpander protocol flags From 06827a42b73db07fb66cb452b48813efd3f72d07 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 07:39:58 +1000 Subject: [PATCH 569/870] Remove excess drivers --- IO_PCA9685_basic.h | 149 ------------------------ IO_Servo.h | 277 --------------------------------------------- 2 files changed, 426 deletions(-) delete mode 100644 IO_PCA9685_basic.h delete mode 100644 IO_Servo.h diff --git a/IO_PCA9685_basic.h b/IO_PCA9685_basic.h deleted file mode 100644 index 4f809aaaf..000000000 --- a/IO_PCA9685_basic.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * © 2023, 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 . - */ - -/* - * This driver performs the basic interface between the HAL and an - * I2C-connected PCA9685 16-channel PWM module. When requested, it - * commands the device to set the PWM mark-to-period ratio accordingly. - * The call to IODevice::writeAnalogue(vpin, value) specifies the - * desired value in the range 0-4095 (0=0% and 4095=100%). - */ - -#ifndef PCA9685_BASIC_H -#define PCA9685_BASIC_H - -#include "IODevice.h" -#include "I2CManager.h" -#include "DIAG.h" - -/* - * IODevice subclass for PCA9685 16-channel PWM module. - */ - -class PCA9685_basic : public IODevice { -public: - // Create device driver instance. - static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685_basic(firstVpin, nPins, I2CAddress); - } - -private: - - // structures for setting up non-blocking writes to servo controller - I2CRB requestBlock; - uint8_t outputBuffer[5]; - - // REGISTER ADDRESSES - const byte PCA9685_MODE1=0x00; // Mode Register - const byte PCA9685_FIRST_SERVO=0x06; /** low byte first servo register ON*/ - const byte PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ - // MODE1 bits - const byte MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ - const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ - const byte MODE1_RESTART=0x80; /**< Restart enabled */ - - const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ - const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); - const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed - - // Constructor - PCA9685_basic(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - _firstVpin = firstVpin; - _nPins = min(nPins, 16); - _I2CAddress = I2CAddress; - addDevice(this); - - // Initialise structure used for setting pulse rate - requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer)); - } - - // Device-specific initialisation - void _begin() override { - I2CManager.begin(); - I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C - // In reality, other devices including the Arduino will limit - // the clock speed to a lower rate. - - // Initialise I/O module here. - if (I2CManager.exists(_I2CAddress)) { - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); - writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); - // In theory, we should wait 500us before sending any other commands to each device, to allow - // the PWM oscillator to get running. However, we don't do any specific wait, as there's - // plenty of other stuff to do before we will send a command. - #if defined(DIAG_IO) - _display(); - #endif - } else - _deviceState = DEVSTATE_FAILED; - } - - // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). - // - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { - #ifdef DIAG_IO - DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); - #endif - if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; - if (value > 4095) value = 4095; - else if (value < 0) value = 0; - - writeDevice(pin, value); - } - - // Display details of this device. - void _display() override { - 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("")); - } - - // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value - // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. - void writeDevice(uint8_t pin, int value) { - #ifdef DIAG_IO - DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value); - #endif - // Wait for previous request to complete - 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); - } - } - - // Internal helper function for this device - static void writeRegister(byte address, byte reg, byte value) { - I2CManager.write(address, 2, reg, value); - } - -}; - -#endif \ No newline at end of file diff --git a/IO_Servo.h b/IO_Servo.h deleted file mode 100644 index b1935b6f5..000000000 --- a/IO_Servo.h +++ /dev/null @@ -1,277 +0,0 @@ -/* - * © 2023, 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_SERVO_H - -#include "IODevice.h" -#include "I2CManager.h" -#include "DIAG.h" - -class Servo : IODevice { - -public: - enum ProfileType : uint8_t { - Instant = 0, // Moves immediately between positions (if duration not specified) - UseDuration = 0, // Use specified duration - Fast = 1, // Takes around 500ms end-to-end - Medium = 2, // 1 second end-to-end - Slow = 3, // 2 seconds end-to-end - Bounce = 4, // For semaphores/turnouts with a bit of bounce!! - NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move. - }; - - // Create device driver instance. - static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin) { - if (checkNoOverlap(firstVpin, nPins)) new Servo(firstVpin, nPins, firstSlavePin); - } - -private: - VPIN _firstSlavePin; - IODevice *_slaveDevice = NULL; - - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - }; // 14 bytes per element, i.e. per pin in use - - struct ServoData *_servoData [16]; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - static const uint8_t FLASH _bounceProfile[30]; - - const unsigned int refreshInterval = 50; // refresh every 50ms - - - // Configure a port on the Servo. - bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { - if (configType != CONFIGURE_SERVO) return false; - if (paramCount != 5) return false; - #ifdef DIAG_IO - DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), - vpin, params[0], params[1], params[2], params[3], params[4]); - #endif - - int8_t pin = vpin - _firstVpin; - VPIN slavePin = vpin - _firstVpin + _firstSlavePin; - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); - s = _servoData[pin]; - if (!s) return false; // Check for failed memory allocation - } - - s->activePosition = params[0]; - s->inactivePosition = params[1]; - s->profile = params[2]; - s->duration = params[3]; - int state = params[4]; - - if (state != -1) { - // Position servo to initial state - IODevice::writeAnalogue(slavePin, state ? s->activePosition : s->inactivePosition, 0, 0); - } - return true; - } - - // Constructor - Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin) { - _firstVpin = firstVpin; - _nPins = (nPins > 16) ? 16 : nPins; - _firstSlavePin = firstSlavePin; - - // To save RAM, space for servo configuration is not allocated unless a pin is used. - // Initialise the pointers to NULL. - for (int i=0; i<_nPins; i++) - _servoData[i] = NULL; - - addDevice(this); - } - - // Device-specific initialisation - void _begin() override { - // Get reference to slave device to make accesses faster. - _slaveDevice = this->findDevice(_firstSlavePin); - // Check firstSlavePin is actually allocated to a device - if (!_slaveDevice) { - DIAG(F("Servo: Slave device not found on pins %d-%d"), - _firstSlavePin, _firstSlavePin+_nPins-1); - _deviceState = DEVSTATE_FAILED; - } - // Check that the last slave pin is allocated to the same device. - if (_slaveDevice != this->findDevice(_firstSlavePin+_nPins-1)) { - DIAG(F("Servo: Slave device does not cover all pins %d-%d"), - _firstSlavePin, _firstSlavePin+_nPins-1); - _deviceState = DEVSTATE_FAILED; - } - #if defined(DIAG_IO) - _display(); - #endif - } - - // Device-specific write function, invoked from IODevice::write(). - // For this function, the configured profile is used. - void _write(VPIN vpin, int value) override { - if (_deviceState == DEVSTATE_FAILED) return; - #ifdef DIAG_IO - DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); - #endif - int pin = vpin - _firstVpin; - // VPIN slavePin = vpin - _firstVpin + _firstSlavePin; - if (value) value = 1; - - struct ServoData *s = _servoData[pin]; - if (s != NULL) { - // Use configured parameters - this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); - } else { - /* simulate digital pin on PWM */ - this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); - } - } - - // Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue(). - // Profile is as follows: - // Bit 7: 0=Set PWM to 0% to power off servo motor when finished - // 1=Keep PWM pulses on (better when using PWM to drive an LED) - // Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds) - // 1 (Fast) Move servo in 0.5 seconds - // 2 (Medium) Move servo in 1.0 seconds - // 3 (Slow) Move servo in 2.0 seconds - // 4 (Bounce) Servo 'bounces' at extremes. - // - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { - #ifdef DIAG_IO - DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); - #endif - if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; - if (value > 4095) value = 4095; - else if (value < 0) value = 0; - - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - // Servo pin not configured, so configure now using defaults - s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); - if (s == NULL) return; // Check for memory allocation failure - s->activePosition = 4095; - s->inactivePosition = 0; - s->currentPosition = value; - s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) - } - - // Animated profile. Initiate the appropriate action. - s->currentProfile = profile; - uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. - s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds - profileValue==Medium ? 20 : // 1.0 seconds - profileValue==Slow ? 40 : // 2.0 seconds - profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds - duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) - s->stepNumber = 0; - s->toPosition = value; - s->fromPosition = s->currentPosition; - } - - // _read returns true if the device is currently in executing an animation, - // changing the output over a period of time. - int _read(VPIN vpin) override { - if (_deviceState == DEVSTATE_FAILED) return 0; - int pin = vpin - _firstVpin; - struct ServoData *s = _servoData[pin]; - if (s == NULL) - return false; // No structure means no animation! - else - return (s->stepNumber < s->numSteps); - } - - void _loop(unsigned long currentMicros) override { - if (_deviceState == DEVSTATE_FAILED) return; - for (int pin=0; pin<_nPins; pin++) { - updatePosition(pin); - } - delayUntil(currentMicros + refreshInterval * 1000UL); - } - - // Private function to reposition servo - // TODO: Could calculate step number from elapsed time, to allow for erratic loop timing. - void updatePosition(uint8_t pin) { - struct ServoData *s = _servoData[pin]; - if (s == NULL) return; // No pin configuration/state data - - if (s->numSteps == 0) return; // No animation in progress - - if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { - // Go straight to end of sequence, output final position. - s->stepNumber = s->numSteps-1; - } - - if (s->stepNumber < s->numSteps) { - // Animation in progress, reposition servo - s->stepNumber++; - if ((s->currentProfile & ~NoPowerOff) == Bounce) { - // Retrieve step positions from array in flash - uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); - s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); - } else { - // All other profiles - calculate step by linear interpolation between from and to positions. - s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); - } - // Send servo command - _slaveDevice->writeAnalogue(_firstSlavePin+pin, s->currentPosition); - } else if (s->stepNumber < s->numSteps + _catchupSteps) { - // We've finished animation, wait a little to allow servo to catch up - s->stepNumber++; - } else if (s->stepNumber == s->numSteps + _catchupSteps - && s->currentPosition != 0) { - #ifdef IO_SWITCH_OFF_SERVO - if ((s->currentProfile & NoPowerOff) == 0) { - // Wait has finished, so switch off PWM to prevent annoying servo buzz - _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); - } - #endif - s->numSteps = 0; // Done now. - } - } - - // Display details of this device. - void _display() override { - DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), - (int)_firstVpin, (int)_firstVpin+_nPins-1, - (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, - (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); - } -}; - -// Profile for a bouncing signal or turnout -// The profile below is in the range 0-100% and should be combined with the desired limits -// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, -// i.e. the bounce is the same on the down action as on the up action. First entry isn't used. -const byte FLASH Servo::_bounceProfile[30] = - {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; - - -#endif \ No newline at end of file From 6874ddca9bfc1cd704dd508de36f6128b11e10a0 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 08:41:50 +1000 Subject: [PATCH 570/870] Servo functional --- IO_EXIOExpander.h | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 84beb4baa..aedb1ce44 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -187,8 +187,13 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); - for (int pin=0; pin<_nPins; pin++) { - updatePosition(pin); + if ((currentMicros - _lastRefresh) / 1000UL > refreshInterval) { + _lastRefresh = currentMicros; + for (int pin=0; pin<_nPins; pin++) { + if (_servoData[pin] != NULL) { + updatePosition(pin); + } + } } } @@ -247,16 +252,8 @@ class EXIOExpander : public IODevice { } } - // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { int pin = vpin - _firstVpin; - /* Initial _writeAnalogue here - _command4Buffer[0] = EXIOWRAN; - _command4Buffer[1] = pin; - _command4Buffer[2] = value & 0xFF; - _command4Buffer[3] = value >> 8; - I2CManager.write(_i2cAddress, _command4Buffer, 4); - */ #ifdef DIAG_IO DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); @@ -318,12 +315,6 @@ class EXIOExpander : public IODevice { s->stepNumber++; } else if (s->stepNumber == s->numSteps + _catchupSteps && s->currentPosition != 0) { - // #ifdef IO_SWITCH_OFF_SERVO - // if ((s->currentProfile & NoPowerOff) == 0) { - // // Wait has finished, so switch off PWM to prevent annoying servo buzz - // // _slaveDevice->writeAnalogue(_firstSlavePin+pin, 0); - // } - // #endif s->numSteps = 0; // Done now. } } @@ -378,9 +369,9 @@ class EXIOExpander : public IODevice { struct ServoData *_servoData[256]; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - // static const uint8_t FLASH _bounceProfile[30]; - + const unsigned int refreshInterval = 50; // refresh every 50ms + unsigned long _lastRefresh = 0; // Profile for a bouncing signal or turnout // The profile below is in the range 0-100% and should be combined with the desired limits From 6b67760db18fa9d9402f7590d824eb16ef1ba8da Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 09:31:09 +1000 Subject: [PATCH 571/870] Fix dynamic RAM allocation --- IO_EXIOExpander.h | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index aedb1ce44..d6e0dde42 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -72,6 +72,7 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. + _servoData = (ServoData**) calloc(_nPins, sizeof(ServoData*)); for (int i=0; i<_nPins; i++) { _servoData[i] = NULL; } @@ -137,28 +138,6 @@ class EXIOExpander : public IODevice { DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); return false; } - } else if (configType == CONFIGURE_SERVO) { - if (paramCount != 5) return false; -#ifdef DIAG_IO - DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), - vpin, params[0], params[1], params[2], params[3], params[4]); -#endif - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - _servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData)); - s = _servoData[pin]; - if (!s) return false; // Check for failed memory allocation - } - s->activePosition = params[0]; - s->inactivePosition = params[1]; - s->profile = params[2]; - s->duration = params[3]; - int state = params[4]; - if (state != -1) { - // Position servo to initial state - IODevice::writeAnalogue(pin, state ? s->activePosition : s->inactivePosition, 0, 0); - } - return true; } else { return false; } @@ -366,7 +345,8 @@ class EXIOExpander : public IODevice { uint16_t duration; // time (tenths of a second) for animation to complete. }; // 14 bytes per element, i.e. per pin in use - struct ServoData *_servoData[256]; + // struct ServoData *_servoData[256]; + ServoData** _servoData; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off From d36ac7dcfd7d60ba6de4a334d6b1697eea2f8f01 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 12:45:34 +1000 Subject: [PATCH 572/870] Revert IODevice.h change --- IODevice.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/IODevice.h b/IODevice.h index e7906c601..72beb9e73 100644 --- a/IODevice.h +++ b/IODevice.h @@ -242,12 +242,11 @@ class IODevice { // Current state of device DeviceStateEnum _deviceState = DEVSTATE_DORMANT; - // Method to find device handling Vpin - static IODevice *findDevice(VPIN vpin); - private: // Method to check whether the vpin corresponds to this device bool owns(VPIN vpin); + // Method to find device handling Vpin + static IODevice *findDevice(VPIN vpin); IODevice *_nextDevice = 0; unsigned long _nextEntryTime; static IODevice *_firstDevice; From f1c17c360620ae0aa2e0546df7c865220c4fdc1c Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 13:03:00 +1000 Subject: [PATCH 573/870] Add more state checking --- IO_EXIOExpander.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index d6e0dde42..ad48a3a21 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -178,6 +178,7 @@ class EXIOExpander : public IODevice { // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; uint8_t _pinLSBByte; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { @@ -232,6 +233,7 @@ class EXIOExpander : public IODevice { } void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; #ifdef DIAG_IO DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), @@ -345,7 +347,6 @@ class EXIOExpander : public IODevice { uint16_t duration; // time (tenths of a second) for animation to complete. }; // 14 bytes per element, i.e. per pin in use - // struct ServoData *_servoData[256]; ServoData** _servoData; static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off From acadf241e6838bc2517c3af9d94dd83f47e9a05b Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 13:15:04 +1000 Subject: [PATCH 574/870] Update version --- version.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/version.h b/version.h index d95e01dd4..abfa9dfbc 100644 --- a/version.h +++ b/version.h @@ -5,8 +5,7 @@ #define VERSION "4.2.15" -// 4.2.15 Add Servo device driver with PCA9685_basic driver -// Add basic experimental PWM support to EX-IOExpander +// 4.2.15 Add basic experimental PWM support to EX-IOExpander // EX-IOExpander 0.0.14 minimum required // 4.2.14 STM32F4xx fast ADC read implementation // 4.2.13 Broadcast power for again From d8d785877e4f4aec334ab013cd5711445b77ad6e Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 9 Feb 2023 13:38:06 +1000 Subject: [PATCH 575/870] Fix myHal example for EX-IOExpander --- myHal.cpp_example.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index 64adac1ae..d7afe15a3 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -179,19 +179,17 @@ void halSetup() { //======================================================================= // The following directive defines an EX-IOExpander instance. //======================================================================= - // EXIOExpander::create(VPIN, Number of VPINs, I2C Address, Digital pin count, Analogue pin count) + // EXIOExpander::create(VPIN, Number of VPINs, I2C Address) // // The parameters are: // VPIN=an available Vpin - // Number of VPINs=Digital pin count + Analogue pin count (must match device in use as per documentation) + // Number of VPINs=pin count (must match device in use as per documentation) // I2C address=an available I2C address (default 0x65) // // Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default. - // The first example is for an Arduino Nano with the default pin allocations. - // The second example is for an Arduino Uno using all pins as digital only. + // The example is for an Arduino Nano. - //EXIOExpander::create(800, 18, 0x65, 12, 6); - //EXIOExpander::create(820, 16, 0x66, 16, 0); + //EXIOExpander::create(800, 18, 0x65); //======================================================================= From fd07402aec5b0cf7722011333b2a6c4bfd9e6d71 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 9 Feb 2023 15:12:16 +0800 Subject: [PATCH 576/870] STM32 better I2C still work in progress --- I2CManager_SAMD.h | 2 +- I2CManager_STM32.h | 131 ++++++++++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 44 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 5eb612ba2..bdfe702b0 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -55,7 +55,7 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { // Calculate a rise time appropriate to the requested bus speed int t_rise; if (i2cClockSpeed < 200000L) { - i2cClockSpeed = 100000L; + i2cClockSpeed = 100000L; // NB: this overrides a "force clock" of lower than 100KHz! t_rise = 1000; } else if (i2cClockSpeed < 800000L) { i2cClockSpeed = 400000L; diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 70087501e..b462a3c05 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -45,6 +45,39 @@ void I2C1_IRQHandler() { // Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants I2C_TypeDef *s = I2C1; #define I2C_IRQn I2C1_EV_IRQn +#define I2C_BUSFREQ 16 + +// I2C SR1 Status Register #1 bit definitions for convenience +// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert +// #define I2C_SR1_TIMEOUT (1<<14) // Timeout of Tlow error +// #define I2C_SR1_PECERR (1<<12) // PEC error in reception +// #define I2C_SR1_OVR (1<<11) // Overrun/Underrun error +// #define I2C_SR1_AF (1<<10) // Acknowledge failure +// #define I2C_SR1_ARLO (1<<9) // Arbitration lost (master mode) +// #define I2C_SR1_BERR (1<<8) // Bus error (misplaced start or stop condition) +// #define I2C_SR1_TxE (1<<7) // Data register empty on transmit +// #define I2C_SR1_RxNE (1<<6) // Data register not empty on receive +// #define I2C_SR1_STOPF (1<<4) // Stop detection (slave mode) +// #define I2C_SR1_ADD10 (1<<3) // 10 bit header sent +// #define I2C_SR1_BTF (1<<2) // Byte transfer finished - data transfer done +// #define I2C_SR1_ADDR (1<<1) // Address sent (master) or matched (slave) +// #define I2C_SR1_SB (1<<0) // Start bit (master mode) 1=start condition generated + +// I2C CR1 Control Register #1 bit definitions for convenience +// #define I2C_CR1_SWRST (1<<15) // Software reset - places peripheral under reset +// #define I2C_CR1_ALERT (1<<13) // SMBus alert assertion +// #define I2C_CR1_PEC (1<<12) // Packet Error Checking transfer in progress +// #define I2C_CR1_POS (1<<11) // Acknowledge/PEC Postion (for data reception in PEC mode) +// #define I2C_CR1_ACK (1<<10) // Acknowledge enable - ACK returned after byte is received (address or data) +// #define I2C_CR1_STOP (1<<9) // STOP generated +// #define I2C_CR1_START (1<<8) // START generated +// #define I2C_CR1_NOSTRETCH (1<<7) // Clock stretching disable (slave mode) +// #define I2C_CR1_ENGC (1<<6) // General call (broadcast) enable (address 00h is ACKed) +// #define I2C_CR1_ENPEC (1<<5) // PEC Enable +// #define I2C_CR1_ENARP (1<<4) // ARP enable (SMBus) +// #define I2C_CR1_SMBTYPE (1<<3) // SMBus type, 1=host, 0=device +// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C +// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable /*************************************************************************** * Set I2C clock speed register. This should only be called outside of @@ -54,36 +87,47 @@ I2C_TypeDef *s = I2C1; void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) { // Calculate a rise time appropriate to the requested bus speed - int t_rise; + // Use 10x the rise time spec to enable integer divide of 62.5ns clock period + uint16_t t_rise; + uint32_t ccr_freq; if (i2cClockSpeed < 200000L) { - i2cClockSpeed = 100000L; - t_rise = 1000; - } else if (i2cClockSpeed < 800000L) { + // i2cClockSpeed = 100000L; + t_rise = 0x11; // (1000ns /62.5ns) + 1; + } + else if (i2cClockSpeed < 800000L) + { i2cClockSpeed = 400000L; - t_rise = 300; - } else if (i2cClockSpeed < 1200000L) { - i2cClockSpeed = 1000000L; - t_rise = 120; - } else { + t_rise = 0x06; // (300ns / 62.5ns) + 1; + // } else if (i2cClockSpeed < 1200000L) { + // i2cClockSpeed = 1000000L; + // t_rise = 120; + } + else + { i2cClockSpeed = 100000L; - t_rise = 1000; + t_rise = 0x11; // (1000ns /62.5ns) + 1; } - - // Disable the I2C master mode and wait for sync - // s->I2CM.CTRLA.bit.ENABLE = 0 ; - // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Enable the I2C master mode + s->CR1 &= ~(I2C_CR1_PE); // Enable I2C + // Software reset the I2C peripheral + // s->CR1 |= I2C_CR1_SWRST; // reset the I2C + // Release reset + // s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation // Calculate baudrate - using a rise time appropriate for the speed - // s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000)); + ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2; + + // Bit 15: I2C Master mode, 0=standard, 1=Fast Mode + // Bit 14: Duty, fast mode duty cycle + // Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns) + s->CCR = (uint16_t)ccr_freq; - // Enable the I2C master mode and wait for sync - // s->I2CM.CTRLA.bit.ENABLE = 1 ; - // while (s->I2CM.SYNCBUSY.bit.ENABLE != 0); + // Configure the rise time register + s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1 - // Setting bus idle mode and wait for sync - // s->I2CM.STATUS.bit.BUSSTATE = 1 ; - // while (s->I2CM.SYNCBUSY.bit.SYSOP != 0); + // Enable the I2C master mode + s->CR1 |= I2C_CR1_PE; // Enable I2C } /*************************************************************************** @@ -107,11 +151,11 @@ void I2CManagerClass::I2C_init() GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up // Software reset the I2C peripheral - s->CR1 |= (1<<15); // reset the I2C - s->CR1 &= ~(1<<15); // Normal operation + s->CR1 |= I2C_CR1_SWRST; // reset the I2C + s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation // Program the peripheral input clock in CR2 Register in order to generate correct timings - s->CR2 |= (16<<0); // PCLK1 FREQUENCY in MHz + s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz #if defined(I2C_USE_INTERRUPTS) // Setting NVIC @@ -127,7 +171,8 @@ void I2CManagerClass::I2C_init() // Bit 8: ITERREN - Error interrupt enable // Bit 7-6: reserved // Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz) - s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts + // s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts + s->CR2 |= 0x0300; // Enable Event and Error interrupts #endif // Calculate baudrate and set default rate for now @@ -141,7 +186,7 @@ void I2CManagerClass::I2C_init() s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1 // Enable the I2C master mode - s->CR1 |= (1<<0); // Enable I2C + s->CR1 |= I2C_CR1_PE; // Enable I2C // Setting bus idle mode and wait for sync } @@ -165,28 +210,28 @@ void I2CManagerClass::I2C_sendStart() { if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { // Send start for read operation - s->CR1 |= (1<<10); // Enable the ACK - s->CR1 |= (1<<8); // Generate START + s->CR1 |= I2C_CR1_ACK; // Enable the ACK + s->CR1 |= I2C_CR1_START; // Generate START // Send address with read flag (1) or'd in s->DR = (currentRequest->i2cAddress << 1) | 1; // send the address - while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set // Special case for 1 byte reads! if (bytesToReceive == 1) { - s->CR1 &= ~(1<<10); // clear the ACK bit + s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition - s->CR1 |= (1<<9); // Stop I2C + s->CR1 |= I2C_CR1_STOP; // Stop I2C } else temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } else { // Send start for write operation - s->CR1 |= (1<<10); // Enable the ACK - s->CR1 |= (1<<8); // Generate START + s->CR1 |= I2C_CR1_ACK; // Enable the ACK + s->CR1 |= I2C_CR1_START; // Generate START // Send address with write flag (0) or'd in s->DR = (currentRequest->i2cAddress << 1) | 0; // send the address - while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set + while (!(s->SR1 && (1<<1))); // wait for ADDR bit to set temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } } @@ -195,7 +240,7 @@ void I2CManagerClass::I2C_sendStart() { * Initiate a stop bit for transmission (does not interrupt) ***************************************************************************/ void I2CManagerClass::I2C_sendStop() { - s->CR1 |= (1<<9); // Stop I2C + s->CR1 |= I2C_CR1_STOP; // Stop I2C } /*************************************************************************** @@ -204,10 +249,10 @@ void I2CManagerClass::I2C_sendStop() { void I2CManagerClass::I2C_close() { I2C_sendStop(); // Disable the I2C master mode and wait for sync - s->CR1 &= ~(1<<0); // Disable I2C peripheral + s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral // Should never happen, but wait for up to 500us only. unsigned long startTime = micros(); - while ((s->CR1 && 1) != 0) { + while ((s->CR1 && I2C_CR1_PE) != 0) { if (micros() - startTime >= 500UL) break; } } @@ -219,13 +264,13 @@ void I2CManagerClass::I2C_close() { ***************************************************************************/ void I2CManagerClass::I2C_handleInterrupt() { - if (s->SR1 && (1<<9)) { + if (s->SR1 && I2C_SR1_ARLO) { // Arbitration lost, restart I2C_sendStart(); // Reinitiate request - } else if (s->SR1 && (1<<8)) { + } else if (s->SR1 && I2C_SR1_BERR) { // Bus error state = I2C_STATUS_BUS_ERROR; - } else if (s->SR1 && (1<<7)) { + } else if (s->SR1 && I2C_SR1_TXE) { // Master write completed if (s->SR1 && (1<<10)) { // Nacked, send stop. @@ -240,13 +285,13 @@ void I2CManagerClass::I2C_handleInterrupt() { // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; } else { // Check both TxE/BTF == 1 before generating stop - while (!(s->SR1 && (1<<7))); // Check TxE - while (!(s->SR1 && (1<<2))); // Check BTF + while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE + while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF // No more data to send/receive. Initiate a STOP condition. I2C_sendStop(); state = I2C_STATUS_OK; // Done } - } else if (s->SR1 && (1<<6)) { + } else if (s->SR1 && I2C_SR1_RXNE) { // Master read completed without errors if (bytesToReceive == 1) { // s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte From 7e2487ffbb7ffa5bf5b377cf70a14964aa6264fd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:29:09 +0000 Subject: [PATCH 577/870] Avoid compiler error when no HAL installed. --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index c7d23e939..bcc25d7fc 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -944,7 +944,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1])); break; -#if !defined(IO_MINIMAL_HAL) +#if !defined(IO_NO_HAL) case HASH_KEYWORD_HAL: if (p[1] == HASH_KEYWORD_SHOW) IODevice::DumpAll(); From 5f9705d1b702bc5aef353fa8e063f73bb8236d59 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:30:35 +0000 Subject: [PATCH 578/870] Improve IODevice::reset function Ensure that the _loop() function is able to run after a device is reset. --- IODevice.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/IODevice.cpp b/IODevice.cpp index 32e6445f8..e907c23d2 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -76,7 +76,12 @@ void IODevice::begin() { // reset() function to reinitialise all devices void IODevice::reset() { + unsigned long currentMicros = micros(); for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) { + dev->_deviceState = DEVSTATE_DORMANT; + // First ensure that _loop isn't delaying + dev->delayUntil(currentMicros); + // Then invoke _begin to restart driver dev->_begin(); } } From f358880f30bb648bbe6acf3086b21c897fc517ed Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:32:41 +0000 Subject: [PATCH 579/870] IO_VL53L0X: Some bug fixes. Modify state model, and improve recovery after . --- IO_VL53L0X.h | 81 ++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index a1e1dccec..9aa3d9cfd 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -48,7 +48,7 @@ * using a distinct I2C address. The process is described in ST Microelectronics application * note AN4846. * - * WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise, + * WARNING: If the device's XSHUT pin is not connected, then it may be prone to noise, * and the device may reset spontaneously or when handled and the device will stop responding * on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor * (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example) @@ -112,16 +112,15 @@ class VL53L0X : public IODevice { // State machine states. enum : uint8_t { - STATE_INIT = 0, - STATE_RESTARTMODULE = 1, - STATE_CONFIGUREADDRESS = 2, - STATE_SKIP = 3, - STATE_CONFIGUREDEVICE = 4, - STATE_INITIATESCAN = 5, - STATE_CHECKSTATUS = 6, - STATE_GETRESULTS = 7, - STATE_DECODERESULTS = 8, - STATE_FAILED = 9, + STATE_INIT, + STATE_RESTARTMODULE, + STATE_CONFIGUREADDRESS, + STATE_CONFIGUREDEVICE, + STATE_INITIATESCAN, + STATE_CHECKSTATUS, + STATE_GETRESULTS, + STATE_DECODERESULTS, + STATE_FAILED, }; // Register addresses @@ -156,24 +155,25 @@ class VL53L0X : public IODevice { // the device will not respond on its default address if it has // already been changed. Therefore, we skip the address configuration if the // desired address is already responding on the I2C bus. - if (_xshutPin == VPIN_NONE && I2CManager.exists(_I2CAddress)) { - // Device already present on this address, so skip the address initialisation. - _nextState = STATE_CONFIGUREDEVICE; - } else - _nextState = STATE_INIT; - + _nextState = STATE_INIT; + _addressConfigInProgress = false; } void _loop(unsigned long currentMicros) override { uint8_t status; switch (_nextState) { case STATE_INIT: - // On first entry to loop, reset this module by pulling XSHUT low. Each module - // will be addressed in turn, until all are in the reset state. - // If no XSHUT pin is configured, then only one device is supported. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); - _nextState = STATE_RESTARTMODULE; - delayUntil(currentMicros+10000); + if (I2CManager.exists(_I2CAddress)) { + // Device already present on the nominated address, so skip the address initialisation. + _nextState = STATE_CONFIGUREDEVICE; + } else { + // On first entry to loop, reset this module by pulling XSHUT low. Each module + // will be addressed in turn, until all are in the reset state. + // If no XSHUT pin is configured, then only one device is supported. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); + _nextState = STATE_RESTARTMODULE; + delayUntil(currentMicros+10000); + } break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. @@ -183,13 +183,14 @@ class VL53L0X : public IODevice { // instead of driving the output directly. // Ensure XSHUT is set for only one module at a time by using a // shared flag accessible to all device instances. - if (_addressConfigInProgress) return; - _addressConfigInProgress = true; - // Set XSHUT pin (if connected) to bring the module out of sleep mode. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); - // Allow the module time to restart - delayUntil(currentMicros+10000); - _nextState = STATE_CONFIGUREADDRESS; + if (!_addressConfigInProgress) { + _addressConfigInProgress = true; + // Set XSHUT pin (if connected) to bring the module out of sleep mode. + if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, true); + // Allow the module time to restart + delayUntil(currentMicros+10000); + _nextState = STATE_CONFIGUREADDRESS; + } break; case STATE_CONFIGUREADDRESS: // Then write the desired I2C address to the device, while this is the only @@ -198,12 +199,16 @@ class VL53L0X : public IODevice { #if defined(I2C_EXTENDED_ADDRESS) // Add subbus reference for desired address to the device default address. I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS}; - I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress()); + status = I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress()); #else - I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); + status = I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress); #endif + if (status != I2C_STATUS_OK) { + reportError(status); + } } delayUntil(currentMicros+10000); + _nextState = STATE_CONFIGUREDEVICE; break; case STATE_CONFIGUREDEVICE: // Allow next VL53L0X device to be configured @@ -214,9 +219,12 @@ class VL53L0X : public IODevice { _display(); #endif // Set 2.8V mode - write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + status = write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); - _nextState = STATE_INITIATESCAN; + if (status != I2C_STATUS_OK) { + reportError(status); + } else + _nextState = STATE_INITIATESCAN; } else { DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; @@ -235,7 +243,6 @@ class VL53L0X : public IODevice { if (status == I2C_STATUS_PENDING) return; // try next time if (status != I2C_STATUS_OK) { reportError(status); - _nextState = STATE_FAILED; } else _nextState = STATE_GETRESULTS; delayUntil(currentMicros + 95000); // wait for 95 ms before checking. @@ -244,7 +251,6 @@ class VL53L0X : public IODevice { // 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; @@ -269,7 +275,6 @@ class VL53L0X : public IODevice { _nextState = STATE_INITIATESCAN; } else { reportError(status); - _nextState = STATE_FAILED; } break; case STATE_FAILED: @@ -281,7 +286,7 @@ class VL53L0X : public IODevice { } } - // Function to report a failed I2C operation. Put the device off-line. + // Function to report a failed I2C operation. void reportError(uint8_t status) { DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; From 1ffb3a9836a691855b31187f2b1b0c01100b3e71 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:34:13 +0000 Subject: [PATCH 580/870] Update IO_OLEDDisplay.h Round up number of characters per line, so that the last few pixels on the line are erased when writing blanks. --- IO_OLEDDisplay.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 1ef8ed10c..859f82dcb 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -77,8 +77,8 @@ class OLEDDisplay : public IODevice, DisplayInterface { _I2CAddress = i2cAddress; _width = width; _height = height; - _numCols = _width / 6; // character block 6 x 8 - _numRows = _height / 8; + _numCols = (_width+5) / 6; // character block 6 x 8, round up + _numRows = _height / 8; // Round down _charPosToScreen = _numCols; @@ -91,10 +91,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { // Create OLED driver oled = new SSD1306AsciiWire(); - - // Clear the entire screen - oled->clearNative(); - + addDevice(this); } @@ -115,6 +112,10 @@ class OLEDDisplay : public IODevice, DisplayInterface { // Force all rows to be redrawn for (uint8_t row=0; row<_numRows; row++) _rowGeneration[row]++; + + // Start with top line (looks better) + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; } } From 18b148ed1f42fa5a20d949a6b2a8a1621b096eed Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:35:17 +0000 Subject: [PATCH 581/870] IO_EXFastClock - fix compile error due to closing brace outside of #if block. --- IO_EXFastclock.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index 442799b13..923f922a0 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -109,16 +109,14 @@ void _loop(unsigned long currentMicros) override{ // As the minimum clock increment is 2 seconds delay a bit - say 1 sec. // Clock interval is 60/ clockspeed i.e 60/b seconds delayUntil(currentMicros + ((60/b) * 1000000)); - - } #endif } - +} // Display EX-FastClock device driver info. - void _display() { + void _display() override { DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } From 553a94bf67678212f07b534aabeb6c7981b2c619 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:46:50 +0000 Subject: [PATCH 582/870] I2CManager: Refactor common driver code. Put mux handling into I2CManager_NonBlocking.h to keep the native (controller-specific) drivers more simple. Remove almost all but one of the static definitions, in preparation for supporting multiple I2C buses. --- I2CManager.cpp | 38 +++++++------- I2CManager.h | 131 ++++++++++++++++++++++++++++++---------------- I2CManager_Wire.h | 4 +- 3 files changed, 108 insertions(+), 65 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 543eeada0..dbba1a0e0 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -72,6 +72,10 @@ void I2CManagerClass::begin(void) { _beginCompleted = true; _initialise(); + #if defined(I2C_USE_WIRE) + DIAG(F("I2CManager: Using Wire library")); + #endif + // Check for short-circuits on I2C if (!digitalRead(SDA)) DIAG(F("WARNING: Possible short-circuit on I2C SDA line")); @@ -81,7 +85,7 @@ void I2CManagerClass::begin(void) { // Probe and list devices. Use standard mode // (clock speed 100kHz) for best device compatibility. _setClock(100000); - unsigned long originalTimeout = timeout; + unsigned long originalTimeout = _timeout; setTimeout(1000); // use 1ms timeout for probes #if defined(I2C_EXTENDED_ADDRESS) @@ -110,7 +114,7 @@ void I2CManagerClass::begin(void) { uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; if (exists(muxAddr)) { // Select Mux Subbus - for (uint8_t subBus=0; subBus<=7; subBus++) { + for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) { muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); for (uint8_t addr=0x08; addr<0x78; addr++) { if (exists(addr)) { @@ -261,26 +265,9 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) { ***************************************************************************/ I2CManagerClass I2CManager = I2CManagerClass(); -// Default timeout 100ms on I2C request block completion. -// A full 32-byte transmission takes about 8ms at 100kHz, -// so this value allows lots of headroom. -// It can be modified by calling I2CManager.setTimeout() function. -// When retries are enabled, the timeout applies to each -// try, and failure from timeout does not get retried. -unsigned long I2CManagerClass::timeout = 100000UL; - // Buffer for conversion of I2CAddress to char*. /* static */ char I2CAddress::addressBuffer[30]; -#if defined(I2C_EXTENDED_ADDRESS) -// Count of I2C multiplexers found when initialising. If there is only one -// MUX then the subbus does not de-selecting after use; however, if there -// is two or more, then the subbus must be deselected to avoid multiple -// sub-bus legs on different multiplexers being accessible simultaneously. -uint8_t I2CManagerClass::_muxCount = 0; -#endif - - ///////////////////////////////////////////////////////////////////////////// // Helper functions associated with I2C Request Block ///////////////////////////////////////////////////////////////////////////// @@ -346,3 +333,16 @@ void I2CRB::suppressRetries(bool suppress) { else this->operation &= ~OPERATION_NORETRY; } + + +// Helper function for converting a uint8_t to four characters (e.g. 0x23). +void I2CAddress::toHex(const uint8_t value, char *buffer) { + char *ptr = buffer; + // Just display hex value, two digits. + *ptr++ = '0'; + *ptr++ = 'x'; + uint8_t bits = (value >> 4) & 0xf; + *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; + bits = value & 0xf; + *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; +} \ No newline at end of file diff --git a/I2CManager.h b/I2CManager.h index d50d9ce7f..542c92f6b 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -23,6 +23,7 @@ #include #include "FSH.h" +#include "defines.h" /* * Manager for I2C communications. For portability, it allows use @@ -104,7 +105,7 @@ * * Non-interrupting I2C: * - * I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state + * Non-blocking I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state * machine handler, currently invoked from the interrupt service routine, is invoked from the loop() function. * The speed at which I2C operations can be performed then becomes highly dependent on the frequency that * the loop() function is called, and may be adequate under some circumstances. @@ -151,6 +152,11 @@ // I2C multiplexer support. ///////////////////////////////////////////////////////////////////////////////////// +// Currently only one bus supported, and one instance of I2CManager to handle it. +enum I2CBus : uint8_t { + I2CBus_0 = 0, +}; + // Currently I2CAddress supports one I2C bus, with up to eight // multipexers (MUX) attached. Each MUX can have up to eight sub-buses. enum I2CMux : uint8_t { @@ -168,12 +174,17 @@ enum I2CMux : uint8_t { enum I2CSubBus : uint8_t { SubBus_0 = 0, // Enable individual sub-buses... SubBus_1 = 1, +#if !defined(I2CMUX_PCA9542) SubBus_2 = 2, SubBus_3 = 3, +#if !defined(I2CMUX_PCA9544) SubBus_4 = 4, SubBus_5 = 5, SubBus_6 = 6, SubBus_7 = 7, +#endif +#endif + SubBus_No, // Number of subbuses (highest + 1) SubBus_None = 254, // Disable all sub-buses on selected mux SubBus_All = 255, // Enable all sub-buses }; @@ -189,14 +200,16 @@ enum I2CSubBus : uint8_t { struct I2CAddress { private: // Fields + I2CBus _busNumber; I2CMux _muxNumber; I2CSubBus _subBus; uint8_t _deviceAddress; static char addressBuffer[]; public: // Constructors - // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. - I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) { + // For I2CAddress "{I2CBus_0, Mux_0, SubBus_0, 0x23}" syntax. + I2CAddress(const I2CBus busNumber, const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) { + _busNumber = busNumber; _muxNumber = muxNumber; _subBus = subBus; _deviceAddress = deviceAddress; @@ -205,6 +218,10 @@ struct I2CAddress { // Basic constructor I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {} + // For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax. + I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) : + I2CAddress(I2CBus_0, muxNumber, subBus, deviceAddress) {} + // For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70) I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) : I2CAddress(I2CMux_0, subBus, deviceAddress) {} @@ -214,6 +231,12 @@ struct I2CAddress { // (device assumed to be on the main I2C bus). I2CAddress(const uint8_t deviceAddress) : I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {} + + // Conversion from uint8_t to I2CAddress + // For I2CAddress in form "{I2CBus_1, 0x23}" + // (device not connected via multiplexer). + I2CAddress(const I2CBus bus, const uint8_t deviceAddress) : + I2CAddress(bus, I2CMux_None, SubBus_None, deviceAddress) {} // For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector) I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) : @@ -250,14 +273,8 @@ struct I2CAddress { *ptr++ = '0' + _subBus; *ptr++ = ','; } - uint8_t temp = _deviceAddress; - *ptr++ = '0'; - *ptr++ = 'x'; - for (uint8_t index = 0; index<2; index++) { - uint8_t bits = (temp >> 4) & 0x0f; - *ptr++ = bits > 9 ? bits-10+'A' : bits+'0'; - temp <<= 4; - } + toHex(_deviceAddress, ptr); + ptr += 4; if (_muxNumber != I2CMux_None) *ptr++ = '}'; *ptr = 0; // terminate string @@ -282,6 +299,10 @@ struct I2CAddress { I2CMux muxNumber() { return _muxNumber; } I2CSubBus subBus() { return _subBus; } uint8_t deviceAddress() { return _deviceAddress; } + +private: + // Helper function for converting byte to four-character hex string (e.g. 0x23). + void toHex(const uint8_t value, char *buffer); }; #else @@ -308,14 +329,8 @@ struct I2CAddress { const char* toString () { char *ptr = addressBuffer; // Just display hex value, two digits. - uint8_t temp = _deviceAddress; - *ptr++ = '0'; - *ptr++ = 'x'; - for (uint8_t index = 0; index<2; index++) { - uint8_t bits = (temp >> 4) & 0xf; - *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; - temp <<= 4; - } + toHex(_deviceAddress, ptr); + ptr += 4; *ptr = 0; // terminate string return addressBuffer; } @@ -326,9 +341,10 @@ struct I2CAddress { return false; // Different device address so no match return true; // Same address on same mux and same subbus } +private: + // Helper function for converting byte to four-character hex string (e.g. 0x23). + void toHex(const uint8_t value, char *buffer); }; -// Legacy single-byte I2C address type for compact code and smooth changeover. -//typedef uint8_t I2CAddress; #endif // I2C_EXTENDED_ADDRESS @@ -452,12 +468,19 @@ class I2CManagerClass { private: bool _beginCompleted = false; bool _clockSpeedFixed = false; - static uint8_t retryCounter; // Count of retries + uint8_t retryCounter; // Count of retries // Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD // and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds // will not be useful. The speed can be overridden by I2CManager::forceClock(). uint32_t _clockSpeed = I2C_FREQ; - static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled. + // Default timeout 100ms on I2C request block completion. + // A full 32-byte transmission takes about 8ms at 100kHz, + // so this value allows lots of headroom. + // It can be modified by calling I2CManager.setTimeout() function. + // When retries are enabled, the timeout applies to each + // try, and failure from timeout does not get retried. + // A value of 0 means disable timeout monitoring. + unsigned long _timeout = 100000UL; // Finish off request block by waiting for completion and posting status. uint8_t finishRB(I2CRB *rb, uint8_t status); @@ -466,7 +489,11 @@ class I2CManagerClass { void _setClock(unsigned long); #if defined(I2C_EXTENDED_ADDRESS) - static uint8_t _muxCount; +// Count of I2C multiplexers found when initialising. If there is only one +// MUX then the subbus does not de-selecting after use; however, if there +// are two or more, then the subbus must be deselected to avoid multiple +// sub-bus legs on different multiplexers being accessible simultaneously. + uint8_t _muxCount = 0; uint8_t getMuxCount() { return _muxCount; } #endif @@ -479,40 +506,56 @@ class I2CManagerClass { // Within the queue, each request's nextRequest field points to the // next request, or NULL. // Mark volatile as they are updated by IRC and read/written elsewhere. - static I2CRB * volatile queueHead; - static I2CRB * volatile queueTail; - static volatile uint8_t state; - static uint8_t completionStatus; - - static I2CRB * volatile currentRequest; - static volatile uint8_t txCount; - static volatile uint8_t rxCount; - static volatile uint8_t bytesToSend; - static volatile uint8_t bytesToReceive; - static volatile uint8_t operation; - static volatile unsigned long startTime; - static volatile uint8_t muxPhase; + I2CRB * volatile queueHead = NULL; + I2CRB * volatile queueTail = NULL; + + // State is set to I2C_STATE_FREE when the interrupt handler has finished + // the current request and is ready to complete. + uint8_t state = I2C_STATE_FREE; + + // CompletionStatus may be set by the interrupt handler at any time but is + // not written to the I2CRB until the state is I2C_STATE_FREE. + uint8_t completionStatus = I2C_STATUS_OK; + uint8_t overallStatus = I2C_STATUS_OK; + + I2CRB * currentRequest = NULL; + uint8_t txCount = 0; + uint8_t rxCount = 0; + uint8_t bytesToSend = 0; + uint8_t bytesToReceive = 0; + uint8_t operation = 0; + unsigned long startTime = 0; + uint8_t muxPhase = 0; + uint8_t muxAddress = 0; + uint8_t muxData[1]; + uint8_t deviceAddress; + const uint8_t *sendBuffer; + uint8_t *receiveBuffer; volatile uint32_t pendingClockSpeed = 0; void startTransaction(); // Low-level hardware manipulation functions. - static void I2C_init(); - static void I2C_setClock(unsigned long i2cClockSpeed); - static void I2C_handleInterrupt(); - static void I2C_sendStart(); - static void I2C_sendStop(); - static void I2C_close(); + void I2C_init(); + void I2C_setClock(unsigned long i2cClockSpeed); + void I2C_handleInterrupt(); + void I2C_sendStart(); + void I2C_sendStop(); + void I2C_close(); public: // handleInterrupt needs to be public to be called from the ISR function! - static void handleInterrupt(); + void handleInterrupt(); #endif }; +// Pointer to class instance (Note: if there is more than one bus, each will have +// its own instance of I2CManager, selected by the queueRequest function from +// the I2CBus field within the request block's I2CAddress). extern I2CManagerClass I2CManager; + #endif diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 67ef306df..04d99bd9b 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -41,7 +41,7 @@ void I2CManagerClass::_initialise() { Wire.begin(); #if defined(WIRE_HAS_TIMEOUT) - Wire.setWireTimeout(timeout, true); + Wire.setWireTimeout(_timeout, true); #endif } @@ -59,7 +59,7 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { * read is started. ***************************************************************************/ void I2CManagerClass::setTimeout(unsigned long value) { - timeout = value; + _timeout = value; #if defined(WIRE_HAS_TIMEOUT) Wire.setWireTimeout(value, true); #endif From 0b307a67e4dc4e6b37a6691587d0c0431fd57cf0 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 15:47:44 +0000 Subject: [PATCH 583/870] I2CManager: Update native drivers for MUX support from the common code. --- I2CManager_AVR.h | 157 +++++++-------------------------------- I2CManager_Mega4809.h | 10 +-- I2CManager_NonBlocking.h | 119 +++++++++++++++++++++-------- I2CManager_SAMD.h | 10 +-- I2CManager_STM32.h | 31 ++++---- 5 files changed, 140 insertions(+), 187 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index 761313211..c2d73125b 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -95,17 +95,16 @@ void I2CManagerClass::I2C_init() * Initiate a start bit for transmission. ***************************************************************************/ void I2CManagerClass::I2C_sendStart() { - bytesToSend = currentRequest->writeLen; - bytesToReceive = currentRequest->readLen; rxCount = 0; txCount = 0; -#if defined(I2C_EXTENDED_ADDRESS) +#if defined(I2C_EXTENDED_ADDRESSXXXXXXXXXXXX) if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { // Send request to multiplexer muxPhase = MuxPhase_PROLOG; // When start bit interrupt comes in, send SLA+W to MUX } else muxPhase = 0; #endif + while(TWCR & (1< MuxPhase_OFF) { - switch (twsr) { - case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received - if (muxPhase == MuxPhase_PROLOG) { - // Send MUX selecter mask to follow address - I2CSubBus subBus = currentRequest->i2cAddress.subBus(); - TWDR = (subBus==SubBus_All) ? 0xff : - (subBus==SubBus_None) ? 0x00 : - 1 << subBus; - TWCR = (1< 1) { - // Device transaction complete, prepare to deselect MUX by sending start bit - TWCR = (1<i2cAddress.deviceAddress() == 0) { - // Send stop and post rb. - TWDR = 0xff; - TWCR = (1< 0) { - currentRequest->readBuffer[rxCount++] = TWDR; - bytesToReceive--; - } - if (muxPhase == MuxPhase_PASSTHRU && _muxCount > 1) { - // Prepare to transmit epilog to mux - first send the stop bit and start bit - // (we don't need to reset mux if there is only one. - TWCR = (1<i2cAddress.muxNumber(); - TWDR = (muxAddress << 1) | 0; // MUXaddress+Write - TWCR = (1< MuxPhase_EPILOG) { - // Mux Cleardown was NAK'd, send stop and then finish. - TWCR = (1<writeBuffer + (txCount++)); + TWDR = GETFLASH(sendBuffer + (txCount++)); else - TWDR = currentRequest->writeBuffer[txCount++]; + TWDR = sendBuffer[txCount++]; bytesToSend--; TWCR = (1< 0) { - currentRequest->readBuffer[rxCount++] = TWDR; + receiveBuffer[rxCount++] = TWDR; bytesToReceive--; } /* fallthrough */ @@ -292,7 +187,7 @@ void I2CManagerClass::I2C_handleInterrupt() { case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted if (bytesToReceive > 0) { - currentRequest->readBuffer[rxCount++] = TWDR; + receiveBuffer[rxCount++] = TWDR; bytesToReceive--; } TWCR = (1<i2cAddress; - if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend)) - TWDR = (deviceAddress << 1) | 1; // SLA+R - else - TWDR = (deviceAddress << 1) | 0; // SLA+W - TWCR = (1<writeLen; - bytesToReceive = currentRequest->readLen; txCount = 0; rxCount = 0; @@ -123,11 +121,11 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (bytesToSend) { // Acked, so send next byte (don't need to use GETFLASH) - TWI0.MDATA = currentRequest->writeBuffer[txCount++]; + TWI0.MDATA = sendBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. - TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1; + TWI0.MADDR = (deviceAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition. TWI0.MCTRLB = TWI_MCMD_STOP_gc; @@ -136,7 +134,7 @@ void I2CManagerClass::I2C_handleInterrupt() { } else if (currentStatus & TWI_RIF_bm) { // Master read completed without errors if (bytesToReceive) { - currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte + receiveBuffer[rxCount++] = TWI0.MDATA; // Store received byte bytesToReceive--; } if (bytesToReceive) { @@ -155,7 +153,7 @@ void I2CManagerClass::I2C_handleInterrupt() { * Interrupt handler. ***************************************************************************/ ISR(TWI0_TWIM_vect) { - I2CManagerClass::handleInterrupt(); + I2CManager.handleInterrupt(); } #endif diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 4da932ffd..2565c153a 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -79,7 +79,7 @@ for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ enum MuxPhase: uint8_t { MuxPhase_OFF = 0, MuxPhase_PROLOG, - MuxPhase_PASSTHRU, + MuxPhase_PAYLOAD, MuxPhase_EPILOG, } ; @@ -107,7 +107,7 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { } /*************************************************************************** - * Helper function to start operations, if the I2C interface is free and + * Start an I2C transaction, if the I2C interface is free and * there is a queued request to be processed. * If there's an I2C clock speed change pending, then implement it before * starting the operation. @@ -126,9 +126,47 @@ void I2CManagerClass::startTransaction() { startTime = micros(); currentRequest = queueHead; rxCount = txCount = 0; - // Copy key fields to static data for speed. - operation = currentRequest->operation & OPERATION_MASK; + // Start the I2C process going. +#if defined(I2C_EXTENDED_ADDRESS) + I2CMux muxNumber = currentRequest->i2cAddress.muxNumber(); + if (muxNumber != I2CMux_None) { + muxPhase = MuxPhase_PROLOG; + uint8_t subBus = currentRequest->i2cAddress.subBus(); + muxData[0] = (subBus == SubBus_All) ? 0xff : + (subBus == SubBus_None) ? 0x00 : +#if defined(I2CMUX_PCA9547) + 0x08 | subBus; +#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544) + 0x04 | subBus; // NB Only 2 or 4 subbuses respectively +#else + // Default behaviour for most MUXs is to use a mask + // with a bit set for the subBus to be enabled + 1 << subBus; +#endif + deviceAddress = I2C_MUX_BASE_ADDRESS + muxNumber; + sendBuffer = &muxData[0]; + bytesToSend = 1; + bytesToReceive = 0; + operation = OPERATION_SEND; + } else { + // Send/receive payload for device only. + muxPhase = MuxPhase_OFF; + deviceAddress = currentRequest->i2cAddress; + sendBuffer = currentRequest->writeBuffer; + bytesToSend = currentRequest->writeLen; + receiveBuffer = currentRequest->readBuffer; + bytesToReceive = currentRequest->readLen; + operation = currentRequest->operation & OPERATION_MASK; + } +#else + deviceAddress = currentRequest->i2cAddress; + sendBuffer = currentRequest->writeBuffer; + bytesToSend = currentRequest->writeLen; + receiveBuffer = currentRequest->readBuffer; + bytesToReceive = currentRequest->readLen; + operation = currentRequest->operation & OPERATION_MASK; +#endif I2C_sendStart(); } } @@ -194,7 +232,7 @@ uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_ * reset before the read. ***************************************************************************/ void I2CManagerClass::setTimeout(unsigned long value) { - timeout = value; + _timeout = value; }; /*************************************************************************** @@ -205,12 +243,12 @@ void I2CManagerClass::setTimeout(unsigned long value) { void I2CManagerClass::checkForTimeout() { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { I2CRB *t = queueHead; - if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && timeout > 0) { + if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) { // Check for timeout unsigned long elapsed = micros() - startTime; - if (elapsed > timeout) { + if (elapsed > _timeout) { #ifdef DIAG_IO - //DIAG(F("I2CManager Timeout on %s, I2CRB=%s"), t->i2cAddress.toString(), currentRequest); + //DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString()); #endif // Excessive time. Dequeue request queueHead = t->nextRequest; @@ -267,19 +305,58 @@ void I2CManagerClass::handleInterrupt() { // Check if current request has completed. If there's a current request // and state isn't active then state contains the completion status of the request. - if (state != I2C_STATE_ACTIVE && currentRequest != NULL) { + if (state == I2C_STATE_COMPLETED && currentRequest != NULL) { // Operation has completed. if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES || currentRequest->operation & OPERATION_NORETRY) { // Status is OK, or has failed and retry count exceeded, or retries disabled. +#if defined(I2C_EXTENDED_ADDRESS) + if (muxPhase == MuxPhase_PROLOG ) { + overallStatus = completionStatus; + uint8_t rbAddress = currentRequest->i2cAddress.deviceAddress(); + if (completionStatus == I2C_STATUS_OK && rbAddress != 0) { + // Mux request OK, start handling application request. + muxPhase = MuxPhase_PAYLOAD; + deviceAddress = rbAddress; + sendBuffer = currentRequest->writeBuffer; + bytesToSend = currentRequest->writeLen; + bytesToReceive = currentRequest->readLen; + operation = currentRequest->operation & OPERATION_MASK; + state = I2C_STATE_ACTIVE; + I2C_sendStart(); + return; + } + } else if (muxPhase == MuxPhase_PAYLOAD) { + // Application request completed, now send epilogue to mux + overallStatus = completionStatus; + currentRequest->nBytes = rxCount; // Save number of bytes read into rb + muxPhase = MuxPhase_EPILOG; + deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); + muxData[0] = 0x00; + sendBuffer = &muxData[0]; + bytesToSend = 1; + bytesToReceive = 0; + operation = OPERATION_SEND; + state = I2C_STATE_ACTIVE; + I2C_sendStart(); + return; + } else if (muxPhase == MuxPhase_EPILOG) { + // Epilog finished, ignore completionStatus + muxPhase = MuxPhase_OFF; + } else + overallStatus = completionStatus; +#else + overallStatus = completionStatus; + currentRequest->nBytes = rxCount; +#endif + // Remove completed request from head of queue I2CRB * t = queueHead; if (t == currentRequest) { queueHead = t->nextRequest; if (!queueHead) queueTail = queueHead; - t->nBytes = rxCount; - t->status = completionStatus; + t->status = overallStatus; // I2C state machine is now free for next request currentRequest = NULL; @@ -295,28 +372,10 @@ void I2CManagerClass::handleInterrupt() { if (state == I2C_STATE_FREE && queueHead != NULL) { // Allow any pending interrupts before starting the next request. - interrupts(); + //interrupts(); // Start next request I2CManager.startTransaction(); } } -// Fields in I2CManager class specific to Non-blocking implementation. -I2CRB * volatile I2CManagerClass::queueHead = NULL; -I2CRB * volatile I2CManagerClass::queueTail = NULL; -I2CRB * volatile I2CManagerClass::currentRequest = NULL; -volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE; -uint8_t I2CManagerClass::completionStatus; -volatile uint8_t I2CManagerClass::txCount; -volatile uint8_t I2CManagerClass::rxCount; -volatile uint8_t I2CManagerClass::operation; -volatile uint8_t I2CManagerClass::bytesToSend; -volatile uint8_t I2CManagerClass::bytesToReceive; -volatile unsigned long I2CManagerClass::startTime; -uint8_t I2CManagerClass::retryCounter = 0; - -#if defined(I2C_EXTENDED_ADDRESS) -volatile uint8_t I2CManagerClass::muxPhase = 0; -#endif - #endif \ No newline at end of file diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index 5eb612ba2..a50ccba14 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -153,8 +153,6 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. - bytesToSend = currentRequest->writeLen; - bytesToReceive = currentRequest->readLen; txCount = 0; rxCount = 0; @@ -220,11 +218,11 @@ void I2CManagerClass::I2C_handleInterrupt() { state = I2C_STATE_COMPLETED; // Completed with error } else if (bytesToSend) { // Acked, so send next byte - s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++]; + s->I2CM.DATA.bit.DATA = sendBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1; } else { // No more data to send/receive. Initiate a STOP condition I2C_sendStop(); @@ -235,12 +233,12 @@ void I2CManagerClass::I2C_handleInterrupt() { if (bytesToReceive == 1) { s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte I2C_sendStop(); // send stop - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive = 0; state = I2C_STATE_COMPLETED; // Completed OK } else if (bytesToReceive) { s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte - currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte + receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte bytesToReceive--; } } diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 70087501e..6c7423576 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -38,7 +38,7 @@ ***************************************************************************/ #if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32) void I2C1_IRQHandler() { - I2CManagerClass::handleInterrupt(); + I2CManager.handleInterrupt(); } #endif @@ -151,8 +151,7 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { // Set counters here in case this is a retry. - bytesToSend = currentRequest->writeLen; - bytesToReceive = currentRequest->readLen; + rxCount = txCount = 0; uint8_t temp; // On a single-master I2C bus, the start bit won't be sent until the bus @@ -168,7 +167,7 @@ void I2CManagerClass::I2C_sendStart() { s->CR1 |= (1<<10); // Enable the ACK s->CR1 |= (1<<8); // Generate START // Send address with read flag (1) or'd in - s->DR = (currentRequest->i2cAddress << 1) | 1; // send the address + s->DR = (deviceAddress << 1) | 1; // send the address while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set // Special case for 1 byte reads! if (bytesToReceive == 1) @@ -185,7 +184,7 @@ void I2CManagerClass::I2C_sendStart() { s->CR1 |= (1<<10); // Enable the ACK s->CR1 |= (1<<8); // Generate START // Send address with write flag (0) or'd in - s->DR = (currentRequest->i2cAddress << 1) | 0; // send the address + s->DR = (deviceAddress << 1) | 0; // send the address while (!(s->SR1 & (1<<1))); // wait for ADDR bit to set temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } @@ -219,44 +218,48 @@ void I2CManagerClass::I2C_close() { ***************************************************************************/ void I2CManagerClass::I2C_handleInterrupt() { + if (!s) return; + if (s->SR1 && (1<<9)) { // Arbitration lost, restart I2C_sendStart(); // Reinitiate request } else if (s->SR1 && (1<<8)) { // Bus error - state = I2C_STATUS_BUS_ERROR; + completionStatus = I2C_STATUS_BUS_ERROR; + state = I2C_STATE_COMPLETED; } else if (s->SR1 && (1<<7)) { // Master write completed if (s->SR1 && (1<<10)) { // Nacked, send stop. I2C_sendStop(); - state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE; + state = I2C_STATE_COMPLETED; } else if (bytesToSend) { // Acked, so send next byte - s->DR = currentRequest->writeBuffer[txCount++]; + s->DR = sendBuffer[txCount++]; bytesToSend--; } else if (bytesToReceive) { // Last sent byte acked and no more to send. Send repeated start, address and read bit. - // s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + // s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1; } else { // Check both TxE/BTF == 1 before generating stop while (!(s->SR1 && (1<<7))); // Check TxE while (!(s->SR1 && (1<<2))); // Check BTF - // No more data to send/receive. Initiate a STOP condition. + // No more data to send/receive. Initiate a STOP condition and finish I2C_sendStop(); - state = I2C_STATUS_OK; // Done + state = I2C_STATE_COMPLETED; } } else if (s->SR1 && (1<<6)) { // Master read completed without errors if (bytesToReceive == 1) { // s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte I2C_sendStop(); // send stop - currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte + receiveBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive = 0; - state = I2C_STATUS_OK; // done + state = I2C_STATE_COMPLETED; } else if (bytesToReceive) { // s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte - currentRequest->readBuffer[rxCount++] = s->DR; // Store received byte + receiveBuffer[rxCount++] = s->DR; // Store received byte bytesToReceive--; } } From 98697427a364d6b26e83373fc59b935c6ea54dfd Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 18:21:06 +0000 Subject: [PATCH 584/870] Update I2CManager_SAMD.h Fix compile errors following other changes --- I2CManager_SAMD.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h index f763e3cf9..76774a943 100644 --- a/I2CManager_SAMD.h +++ b/I2CManager_SAMD.h @@ -38,7 +38,7 @@ ***************************************************************************/ #if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO) void SERCOM3_Handler() { - I2CManagerClass::handleInterrupt(); + I2CManager.handleInterrupt(); } #endif @@ -166,11 +166,11 @@ void I2CManagerClass::I2C_sendStart() { if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) { // Send start and address with read flag (1) or'd in - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1; + s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1; } else { // Send start and address with write flag (0) or'd in - s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0; + s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1ul) | 0; } } From ad4cedfccff7cb5def7c1c87daff2dcb88c17709 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 18:21:50 +0000 Subject: [PATCH 585/870] Update I2CManager_NonBlocking.h Rationalise ATOMIC_BLOCK macro definition and remove reliance on atomic.h. --- I2CManager_NonBlocking.h | 69 ++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 2565c153a..fb39fba72 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -24,52 +24,40 @@ #include #include "I2CManager.h" -#if defined(I2C_USE_INTERRUPTS) -// atomic.h isn't available on SAMD, and likely others too... -#if defined(__AVR__) -#include -#elif defined(__arm__) -// Helper assembly language functions -static __inline__ uint8_t my_iSeiRetVal(void) -{ - __asm__ __volatile__ ("cpsie i" ::); - return 1; -} -static __inline__ uint8_t my_iCliRetVal(void) -{ - __asm__ __volatile__ ("cpsid i" ::); - return 1; -} - -static __inline__ void my_iRestore(const uint32_t *__s) -{ - uint32_t res = *__s; - __asm__ __volatile__ ("MSR primask, %0" : : "r" (res) ); +// Support for atomic isolation (i.e. a block with interrupts disabled). +// E.g. +// ATOMIC_BLOCK() { +// doSomethingWithInterruptsDisabled(); +// } +// This has the advantage over simple noInterrupts/Interrupts that the +// original interrupt state is restored when the block finishes. +// +#if defined(__AVR__) +static inline uint8_t _deferInterrupts(void) { + noInterrupts(); + return 1; } - -static __inline__ uint32_t my_iGetIReg( void ) -{ - uint32_t reg; - __asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) ); - return reg; +#define ATOMIC_BLOCK(x) \ +for (uint8_t _int_saved=SREG,_ToDo=_deferInterrupts(); \ + _ToDo; _ToDo=0, SREG=_int_saved) +#elif defined(__arm__) +static inline uint8_t _deferInterrupts(void) { + __set_PRIMASK(1); + return 1; } -// Macros for atomic isolation -#define MY_ATOMIC_RESTORESTATE uint32_t _sa_saved \ - __attribute__((__cleanup__(my_iRestore))) = my_iGetIReg() - -#define ATOMIC() \ -for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \ - _done; _done = 0 ) - -#define ATOMIC_BLOCK(x) ATOMIC() -#define ATOMIC_RESTORESTATE -#endif +#define ATOMIC_BLOCK(x) \ +for (uint8_t _int_saved=__get_PRIMASK(),_ToDo=_deferInterrupts(); \ + _ToDo; _ToDo=0, __set_PRIMASK(_int_saved)) #else -#define ATOMIC_BLOCK(x) -#define ATOMIC_RESTORESTATE +// If it's not a recognised target, don't use interrupts in the I2C driver +#ifdef I2C_USE_INTERRUPTS +//#undef I2C_USE_INTERRUPTS +#endif +#define ATOMIC_BLOCK(x) // expand to nothing. #endif + // This module is only compiled if I2C_USE_WIRE is not defined, so undefine it here // to get intellisense to work correctly. #if defined(I2C_USE_WIRE) @@ -178,7 +166,6 @@ void I2CManagerClass::startTransaction() { void I2CManagerClass::queueRequest(I2CRB *req) { req->status = I2C_STATUS_PENDING; req->nextRequest = NULL; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if (!queueTail) queueHead = queueTail = req; // Only item on queue From 1cfe5a1e46cdfdcf5710f69a75053bf9b734d71d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 18:22:35 +0000 Subject: [PATCH 586/870] Update I2CManager_STM32.h Fix some merge errors. --- I2CManager_STM32.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index d7ea665a4..79a372645 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -230,7 +230,7 @@ void I2CManagerClass::I2C_sendStart() { s->CR1 |= I2C_CR1_START; // Generate START // Send address with write flag (0) or'd in s->DR = (deviceAddress << 1) | 0; // send the address - while (!(s->SR1 && (1<SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit } } @@ -270,7 +270,7 @@ void I2CManagerClass::I2C_handleInterrupt() { // Bus error completionStatus = I2C_STATUS_BUS_ERROR; state = I2C_STATE_COMPLETED; - } else if (s->SR1 && (1<SR1 && I2C_SR1_TXE) { // Master write completed if (s->SR1 && (1<<10)) { // Nacked, send stop. From c315895cd96b5cfb185efd8ce71207575583b70a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 10 Feb 2023 19:52:24 +0000 Subject: [PATCH 587/870] Update I2CManager_NonBlocking.h Rework ATOMIC_BLOCK to further simplify and clarify. --- I2CManager_NonBlocking.h | 41 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index fb39fba72..2511a8cde 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -33,28 +33,35 @@ // This has the advantage over simple noInterrupts/Interrupts that the // original interrupt state is restored when the block finishes. // -#if defined(__AVR__) +// (This should really be defined in an include file somewhere more global, so +// it can replace use of noInterrupts/interrupts in other parts of DCC-EX. +// static inline uint8_t _deferInterrupts(void) { noInterrupts(); return 1; } -#define ATOMIC_BLOCK(x) \ -for (uint8_t _int_saved=SREG,_ToDo=_deferInterrupts(); \ - _ToDo; _ToDo=0, SREG=_int_saved) -#elif defined(__arm__) -static inline uint8_t _deferInterrupts(void) { - __set_PRIMASK(1); - return 1; +static inline void _enableInterruptsIf(uint8_t wasEnabled) { + if (wasEnabled) interrupts(); } -#define ATOMIC_BLOCK(x) \ -for (uint8_t _int_saved=__get_PRIMASK(),_ToDo=_deferInterrupts(); \ - _ToDo; _ToDo=0, __set_PRIMASK(_int_saved)) +#if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc. + #define ATOMIC_BLOCK(x) \ + for (bool _int_saved=(SREG & (1< Date: Fri, 10 Feb 2023 19:57:42 +0000 Subject: [PATCH 588/870] Update I2CManager_AVR.h Avoid loop in I2C_sendStart function. --- I2CManager_AVR.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index c2d73125b..e75b32fc7 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -97,15 +97,10 @@ void I2CManagerClass::I2C_init() void I2CManagerClass::I2C_sendStart() { rxCount = 0; txCount = 0; -#if defined(I2C_EXTENDED_ADDRESSXXXXXXXXXXXX) - if (currentRequest->i2cAddress.muxNumber() != I2CMux_None) { - // Send request to multiplexer - muxPhase = MuxPhase_PROLOG; // When start bit interrupt comes in, send SLA+W to MUX - } else - muxPhase = 0; -#endif - while(TWCR & (1< Date: Fri, 10 Feb 2023 22:57:15 +0000 Subject: [PATCH 589/870] Update I2CManager_NonBlocking.h Reduce platform-specific part of ATOMIC_BLOCK definition to one inline function _getInterruptState() --- I2CManager_NonBlocking.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 2511a8cde..991b45ca1 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -40,22 +40,23 @@ static inline uint8_t _deferInterrupts(void) { noInterrupts(); return 1; } -static inline void _enableInterruptsIf(uint8_t wasEnabled) { - if (wasEnabled) interrupts(); +static inline void _conditionalEnableInterrupts(bool *wasEnabled) { + if (*wasEnabled) interrupts(); } +#define ATOMIC_BLOCK(x) \ +for (bool _int_saved __attribute__((__cleanup__(_conditionalEnableInterrupts))) \ + =_getInterruptState(),_ToDo=_deferInterrupts(); _ToDo; _ToDo=0) + #if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc. - #define ATOMIC_BLOCK(x) \ - for (bool _int_saved=(SREG & (1< Date: Sat, 11 Feb 2023 15:47:50 +0000 Subject: [PATCH 590/870] Support for multiple displays (OLED etc). New EXRAIL command LCD2(display,row,"text"). Display 0 is the usual one, other displays can be configured through HAL. --- DIAG.h | 1 + DisplayInterface.h | 12 +++- EXRAIL2.cpp | 7 ++- EXRAIL2.h | 6 +- EXRAIL2MacroReset.h | 4 +- EXRAILMacros.h | 14 ++++- IO_OLEDDisplay.h | 133 ++++++++++++++++++++++++++++++-------------- StringFormatter.cpp | 10 ++++ StringFormatter.h | 1 + 9 files changed, 140 insertions(+), 48 deletions(-) diff --git a/DIAG.h b/DIAG.h index c942e59a2..adb7e07f2 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,4 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd +#define LCD2 StringFormatter::lcd #endif diff --git a/DisplayInterface.h b/DisplayInterface.h index 64fc1de00..d9afdc430 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -28,8 +28,18 @@ class DisplayInterface : public Print { public: virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; virtual void setRow(byte line) { (void)line; }; - virtual void clear() {}; + virtual void clear() { }; virtual size_t write(uint8_t c) { (void)c; return 0; }; + // Additional functions to support multiple displays. + // Display number zero is the default one and the original display + // drivers overloaded the above calls only. Newer display drivers + // (e.g. HAL IO_OledDisplay) should override all functions. + virtual void setRow(uint8_t displayNo, byte line) { + if (!displayNo) setRow(line); + } + virtual void clear(uint8_t displayNo) { + if (!displayNo) clear(); + } static DisplayInterface *lcdDisplay; }; diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index e925d9608..11ad4a96e 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1209,6 +1209,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { case thrunge_parse: case thrunge_broadcast: case thrunge_lcd: + default: // thrunge_lcd+1, ... if (!buffer) buffer=new StringBuffer(); buffer->flush(); stream=buffer; @@ -1244,7 +1245,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { case thrunge_lcd: LCD(id,F("%s"),buffer->getString()); break; - - default: break; + default: // thrunge_lcd+1, ... + if (mode > thrunge_lcd) + LCD2(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display + break; } } diff --git a/EXRAIL2.h b/EXRAIL2.h index 12bca3be9..2c4ad967c 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -73,11 +73,15 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_IFLOCO }; +// Ensure thrunge_lcd is put last as there may be more than one display, +// sequentially numbered from thrunge_lcd. enum thrunger: byte { thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial4, thrunge_serial5, thrunge_serial6, - thrunge_lcd, thrunge_lcn}; + thrunge_lcn, + thrunge_lcd, // Must be last!! + }; diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index e54e48bcc..b81c86ffc 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -80,6 +80,7 @@ #undef KILLALL #undef LATCH #undef LCD +#undef LCD2 #undef LCN #undef MOVETT #undef ONACTIVATE @@ -198,7 +199,8 @@ #define JOIN #define KILLALL #define LATCH(sensor_id) -#define LCD(row,msg) +#define LCD(row,msg) +#define LCD2(display,row,msg) #define LCN(msg) #define MOVETT(id,steps,activity) #define ONACTIVATE(addr,subaddr) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 3d3feced2..599664b54 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -45,7 +45,7 @@ // Descriptive texts for routes and animations are created in a sepaerate function which // can be called to emit a list of routes/automatuions in a form suitable for Withrottle. -// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create +// PRINT(msg), LCD(row,msg) and LCD2(display,row,msg) are implemented in a separate pass to create // a getMessageText(id) function. // CAUTION: The macros below are multiple passed over myAutomation.h @@ -142,6 +142,15 @@ const int StringMacroTracker1=__COUNTER__; tmode=thrunge_lcd; \ lcdid=id;\ break;\ + } +#undef LCD2 +#define LCD2(display,id,msg) \ + case (__COUNTER__ - StringMacroTracker1) : {\ + static const char HIGHFLASH thrunge[]=msg;\ + strfar=(uint32_t)GETFARPTR(thrunge);\ + tmode=(thrunger)(thrunge_lcd+display); \ + lcdid=id;\ + break;\ } void RMFT2::printMessage(uint16_t id) { @@ -298,6 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) +#define LCD2(display,id,msg) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -368,6 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { // Restore normal code LCD & SERIAL macro #undef LCD #define LCD StringFormatter::lcd +#undef LCD2 +#define LCD2 StringFormatter::lcd #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 859f82dcb..7cdb34eae 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -51,6 +51,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { private: + uint8_t _displayNo = 0; // Here we define the device-specific variables. uint8_t _height; // in pixels uint8_t _width; // in pixels @@ -64,16 +65,22 @@ class OLEDDisplay : public IODevice, DisplayInterface { uint8_t *_lastRowGeneration = NULL; uint8_t _rowNoToScreen = 0; uint8_t _charPosToScreen = 0; + DisplayInterface *_nextDisplay = NULL; + uint8_t _selectedDisplayNo = 0; public: // Static function to handle "OLEDDisplay::create(...)" calls. static void create(I2CAddress i2cAddress, int width = 128, int height=64) { - /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(i2cAddress, width, height); + /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); + } + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); } protected: // Constructor - OLEDDisplay(I2CAddress i2cAddress, int width, int height) { + OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + _displayNo = displayNo; _I2CAddress = i2cAddress; _width = width; _height = height; @@ -89,9 +96,26 @@ class OLEDDisplay : public IODevice, DisplayInterface { // Fill buffer with spaces memset(_buffer, ' ', _numCols*_numRows); + // Is this the main display? + if (_displayNo == 0) { + // Set first two lines on screen + setRow(0); + print(F("DCC++ EX v")); + print(F(VERSION)); + setRow(1); + print(F("Lic GPLv3")); + } + // Create OLED driver oled = new SSD1306AsciiWire(); + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to lcdDisplay methods. + // Make a note of the existing display reference, to that we can + // pass on anything we're not interested in. + _nextDisplay = DisplayInterface::lcdDisplay; + DisplayInterface::lcdDisplay = this; + addDevice(this); } @@ -99,15 +123,9 @@ class OLEDDisplay : public IODevice, DisplayInterface { void _begin() override { // Initialise device if (oled->begin(_I2CAddress, _width, _height)) { - // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to lcdDisplay methods. - DisplayInterface::lcdDisplay = this; DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString()); - // Set first two lines on screen - LCD(0,F("DCC++ EX v%S"),F(VERSION)); - LCD(1,F("Lic GPLv3")); // Force all rows to be redrawn for (uint8_t row=0; row<_numRows; row++) @@ -120,7 +138,10 @@ class OLEDDisplay : public IODevice, DisplayInterface { } void _loop(unsigned long) override { + screenUpdate(); + } + void screenUpdate() { // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the // characters from the buffer, one character per entry, @@ -156,54 +177,82 @@ class OLEDDisplay : public IODevice, DisplayInterface { // ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { - (void)force; // suppress compiler warning + //screenUpdate(); + if (_nextDisplay) + return _nextDisplay->loop2(force); // continue to next display return NULL; } // Position on nominated line number (0 to number of lines -1) // Clear the line in the buffer ready for updating - void setRow(byte line) override { - if (line == 255) { - // LCD(255, "xxx") - scroll the contents of the buffer - // and put the new line at the bottom of the screen - for (int row=1; row<_numRows; row++) { - strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); - _rowGeneration[row-1]++; + // The displayNo referenced here is remembered and any following + // calls to write() will be directed to that display. + void setRow(uint8_t displayNo, byte line) override { + _selectedDisplayNo = displayNo; + if (displayNo == _displayNo) { + if (line == 255) { + // LCD(255,"xxx") or LCD2(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry. + _rowGeneration[_rowNo]++; + + } else if (_nextDisplay) + _nextDisplay->setRow(displayNo, line); // Pass to next display + + } + + // Write one character to the screen referenced in the last setRow() call. + size_t write(uint8_t c) override { + if (_selectedDisplayNo == _displayNo) { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; } - line = _numRows-1; - } else if (line >= _numRows) - line = _numRows - 1; // Overwrite bottom line. - - _rowNo = line; - // Fill line with blanks - for (_colNo = 0; _colNo < _numCols; _colNo++) - _buffer[_rowNo*_numCols+_colNo] = ' '; - _colNo = 0; - // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry. - _rowGeneration[_rowNo]++; + return 1; + } else if (_nextDisplay) + return _nextDisplay->write(c); + else + return 0; } // Write blanks to all of the screen (blocks until complete) - void clear () override { - // Clear buffer - for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { - setRow(_rowNo); - } - _rowNo = 0; + void clear (uint8_t displayNo) override { + if (displayNo == _displayNo) { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(displayNo, _rowNo); + } + _rowNo = 0; + } else if (_nextDisplay) + _nextDisplay->clear(displayNo); // Pass to next display } - - // Write one character - size_t write(uint8_t c) override { - // Write character to buffer (if space) - if (_colNo < _numCols) - _buffer[_rowNo*_numCols+_colNo++] = c; - return 1; + + // Overloads of above, for compatibility + void setRow(uint8_t line) override { + setRow(0, line); + } + void clear() override { + clear(0); } // Display information about the device. void _display() { - DIAG(F("OLEDDisplay Configured addr %s"), _I2CAddress.toString()); + DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); } }; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index c1f20c48c..382c48417 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -51,6 +51,16 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(LCDDisplay::lcdDisplay,input,args); } +void StringFormatter::lcd(uint8_t display, byte row, const FSH* input...) { + va_list args; + + if (!LCDDisplay::lcdDisplay) return; + LCDDisplay::lcdDisplay->setRow(display, row); + va_start(args, input); + send2(LCDDisplay::lcdDisplay,input,args); + va_end(args); +} + void StringFormatter::send(Print * stream, const FSH* input...) { va_list args; va_start(args, input); diff --git a/StringFormatter.h b/StringFormatter.h index 47877158a..80543b958 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -46,6 +46,7 @@ class StringFormatter // DIAG support static void diag( const FSH* input...); static void lcd(byte row, const FSH* input...); + static void lcd(uint8_t display, byte row, const FSH* input...); static void printEscapes(char * input); static void printEscape( char c); From 65f7a4917f9ea641fb09869578ce95795cf08874 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 11 Feb 2023 17:04:18 +0000 Subject: [PATCH 591/870] Separate out lcd (write to default display) and lcd2 (any display). --- DIAG.h | 2 +- EXRAILMacros.h | 4 ++-- IO_OLEDDisplay.h | 7 ++++--- StringFormatter.cpp | 3 +-- StringFormatter.h | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DIAG.h b/DIAG.h index adb7e07f2..0595505bb 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,5 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd -#define LCD2 StringFormatter::lcd +#define LCD2 StringFormatter::lcd2 #endif diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 599664b54..868cbc945 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -307,7 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) -#define LCD2(display,id,msg) +#define LCD2(display,id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -379,7 +379,7 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { #undef LCD #define LCD StringFormatter::lcd #undef LCD2 -#define LCD2 StringFormatter::lcd +#define LCD2 StringFormatter::lcd2 #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 7cdb34eae..307b7d737 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -138,7 +138,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { } void _loop(unsigned long) override { - screenUpdate(); + //screenUpdate(); } void screenUpdate() { @@ -177,7 +177,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { // ///////////////////////////////////////////////// DisplayInterface* loop2(bool force) override { - //screenUpdate(); + screenUpdate(); if (_nextDisplay) return _nextDisplay->loop2(force); // continue to next display return NULL; @@ -208,7 +208,8 @@ class OLEDDisplay : public IODevice, DisplayInterface { _buffer[_rowNo*_numCols+_colNo] = ' '; _colNo = 0; // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry. + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. _rowGeneration[_rowNo]++; } else if (_nextDisplay) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 382c48417..234188684 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -51,14 +51,13 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(LCDDisplay::lcdDisplay,input,args); } -void StringFormatter::lcd(uint8_t display, byte row, const FSH* input...) { +void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; if (!LCDDisplay::lcdDisplay) return; LCDDisplay::lcdDisplay->setRow(display, row); va_start(args, input); send2(LCDDisplay::lcdDisplay,input,args); - va_end(args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/StringFormatter.h b/StringFormatter.h index 80543b958..4c95ea6a7 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -46,7 +46,7 @@ class StringFormatter // DIAG support static void diag( const FSH* input...); static void lcd(byte row, const FSH* input...); - static void lcd(uint8_t display, byte row, const FSH* input...); + static void lcd2(uint8_t display, byte row, const FSH* input...); static void printEscapes(char * input); static void printEscape( char c); From 35f3cca9b33e36b6f83ec090d0a2b2e1bad7764d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 11 Feb 2023 23:37:09 +0000 Subject: [PATCH 592/870] Rename LCDDisplay class to Display; renameEXRAIL LCD2 macro to SCREEN --- DCCEX.h | 2 +- DIAG.h | 2 +- LCDDisplay.cpp => Display.cpp | 16 ++++++++-------- LCDDisplay.h => Display.h | 6 +++--- LCD_Implementation.h => Display_Implementation.h | 10 +++++----- EXRAIL2.cpp | 2 +- EXRAIL2MacroReset.h | 4 ++-- EXRAILMacros.h | 12 ++++++------ IO_OLEDDisplay.h | 2 +- LiquidCrystal_I2C.h | 4 ++-- SSD1306Ascii.h | 4 ++-- StringFormatter.cpp | 14 +++++++------- StringFormatter.h | 2 +- 13 files changed, 40 insertions(+), 40 deletions(-) rename LCDDisplay.cpp => Display.cpp (94%) rename LCDDisplay.h => Display.h (95%) rename LCD_Implementation.h => Display_Implementation.h (85%) diff --git a/DCCEX.h b/DCCEX.h index f2eee515d..2dc8eb710 100644 --- a/DCCEX.h +++ b/DCCEX.h @@ -40,7 +40,7 @@ #if ETHERNET_ON == true #include "EthernetInterface.h" #endif -#include "LCD_Implementation.h" +#include "Display_Implementation.h" #include "LCN.h" #include "IODevice.h" #include "Turnouts.h" diff --git a/DIAG.h b/DIAG.h index 0595505bb..5aa54aac0 100644 --- a/DIAG.h +++ b/DIAG.h @@ -24,5 +24,5 @@ #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd -#define LCD2 StringFormatter::lcd2 +#define SCREEN StringFormatter::lcd2 #endif diff --git a/LCDDisplay.cpp b/Display.cpp similarity index 94% rename from LCDDisplay.cpp rename to Display.cpp index c6609cebc..c85d7f41c 100644 --- a/LCDDisplay.cpp +++ b/Display.cpp @@ -45,20 +45,20 @@ */ -#include "LCDDisplay.h" +#include "Display.h" -void LCDDisplay::clear() { +void Display::clear() { clearNative(); for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0'; topRow = -1; // loop2 will fill from row 0 } -void LCDDisplay::setRow(byte line) { +void Display::setRow(byte line) { hotRow = line; hotCol = 0; } -size_t LCDDisplay::write(uint8_t b) { +size_t Display::write(uint8_t b) { if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1; rowBuffer[hotRow][hotCol] = b; hotCol++; @@ -66,12 +66,12 @@ size_t LCDDisplay::write(uint8_t b) { return 1; } -void LCDDisplay::loop() { +void Display::loop() { if (!lcdDisplay) return; lcdDisplay->loop2(false); } -LCDDisplay *LCDDisplay::loop2(bool force) { +Display *Display::loop2(bool force) { if (!lcdDisplay) return NULL; // If output device is busy, don't do anything on this loop @@ -150,7 +150,7 @@ LCDDisplay *LCDDisplay::loop2(bool force) { return NULL; } -void LCDDisplay::moveToNextRow() { +void Display::moveToNextRow() { rowNext = (rowNext + 1) % MAX_LCD_ROWS; #if SCROLLMODE == 1 // Finished if we've looped back to row 0 @@ -161,7 +161,7 @@ void LCDDisplay::moveToNextRow() { #endif } -void LCDDisplay::skipBlankRows() { +void Display::skipBlankRows() { while (!done && rowBuffer[rowNext][0] == 0) moveToNextRow(); } diff --git a/LCDDisplay.h b/Display.h similarity index 95% rename from LCDDisplay.h rename to Display.h index 6e2c69dbe..84cdbde78 100644 --- a/LCDDisplay.h +++ b/Display.h @@ -34,16 +34,16 @@ // This class is created in LCDisplay_Implementation.h -class LCDDisplay : public DisplayInterface { +class Display : public DisplayInterface { public: - LCDDisplay() {}; + Display() {}; static const int MAX_LCD_ROWS = 8; static const int MAX_LCD_COLS = MAX_MSG_SIZE; static const long LCD_SCROLL_TIME = 3000; // 3 seconds // Internally handled functions static void loop(); - LCDDisplay* loop2(bool force) override; + Display* loop2(bool force) override; void setRow(byte line) override; void clear() override; diff --git a/LCD_Implementation.h b/Display_Implementation.h similarity index 85% rename from LCD_Implementation.h rename to Display_Implementation.h index 95b0f8172..4c90dd901 100644 --- a/LCD_Implementation.h +++ b/Display_Implementation.h @@ -27,15 +27,15 @@ #ifndef LCD_Implementation_h #define LCD_Implementation_h -#include "LCDDisplay.h" +#include "Display.h" #include "SSD1306Ascii.h" #include "LiquidCrystal_I2C.h" -// Implement the LCDDisplay shim class as a singleton. -// The DisplayInterface class implements a displayy handler with no code (null device); -// The LCDDisplay class sub-classes DisplayInterface to provide the common display code; -// Then LCDDisplay class is subclassed to the specific device type classes: +// Implement the Display shim class as a singleton. +// The DisplayInterface class implements a display handler with no code (null device); +// The Display class sub-classes DisplayInterface to provide the common display code; +// Then Display class is subclassed to the specific device type classes: // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 11ad4a96e..48f91ccd3 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1247,7 +1247,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { break; default: // thrunge_lcd+1, ... if (mode > thrunge_lcd) - LCD2(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display + SCREEN(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display break; } } diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index b81c86ffc..b4ffc6d1b 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -80,7 +80,7 @@ #undef KILLALL #undef LATCH #undef LCD -#undef LCD2 +#undef SCREEN #undef LCN #undef MOVETT #undef ONACTIVATE @@ -200,7 +200,7 @@ #define KILLALL #define LATCH(sensor_id) #define LCD(row,msg) -#define LCD2(display,row,msg) +#define SCREEN(display,row,msg) #define LCN(msg) #define MOVETT(id,steps,activity) #define ONACTIVATE(addr,subaddr) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 868cbc945..ee20c1f93 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -45,7 +45,7 @@ // Descriptive texts for routes and animations are created in a sepaerate function which // can be called to emit a list of routes/automatuions in a form suitable for Withrottle. -// PRINT(msg), LCD(row,msg) and LCD2(display,row,msg) are implemented in a separate pass to create +// PRINT(msg), LCD(row,msg) and SCREEN(display,row,msg) are implemented in a separate pass to create // a getMessageText(id) function. // CAUTION: The macros below are multiple passed over myAutomation.h @@ -143,8 +143,8 @@ const int StringMacroTracker1=__COUNTER__; lcdid=id;\ break;\ } -#undef LCD2 -#define LCD2(display,id,msg) \ +#undef SCREEN +#define SCREEN(display,id,msg) \ case (__COUNTER__ - StringMacroTracker1) : {\ static const char HIGHFLASH thrunge[]=msg;\ strfar=(uint32_t)GETFARPTR(thrunge);\ @@ -307,7 +307,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define KILLALL OPCODE_KILLALL,0,0, #define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id), #define LCD(id,msg) PRINT(msg) -#define LCD2(display,id,msg) PRINT(msg) +#define SCREEN(display,id,msg) PRINT(msg) #define LCN(msg) PRINT(msg) #define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0), #define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr), @@ -378,8 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = { // Restore normal code LCD & SERIAL macro #undef LCD #define LCD StringFormatter::lcd -#undef LCD2 -#define LCD2 StringFormatter::lcd2 +#undef SCREEN +#define SCREEN StringFormatter::lcd2 #undef SERIAL #define SERIAL 0x0 #endif diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 307b7d737..b381bd81a 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -191,7 +191,7 @@ class OLEDDisplay : public IODevice, DisplayInterface { _selectedDisplayNo = displayNo; if (displayNo == _displayNo) { if (line == 255) { - // LCD(255,"xxx") or LCD2(displayNo,255, "xxx") - + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - // scroll the contents of the buffer and put the new line // at the bottom of the screen for (int row=1; row<_numRows; row++) { diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 20dc1267e..5d5a7415c 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -22,7 +22,7 @@ #define LiquidCrystal_I2C_h #include -#include "LCDDisplay.h" +#include "Display.h" #include "I2CManager.h" // commands @@ -62,7 +62,7 @@ #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit -class LiquidCrystal_I2C : public LCDDisplay { +class LiquidCrystal_I2C : public Display { public: LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); void begin(); diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h index f4c9ba75e..d40eac602 100644 --- a/SSD1306Ascii.h +++ b/SSD1306Ascii.h @@ -23,7 +23,7 @@ #include "Arduino.h" #include "FSH.h" -#include "LCDDisplay.h" +#include "Display.h" #include "I2CManager.h" #include "DIAG.h" @@ -33,7 +33,7 @@ //------------------------------------------------------------------------------ // Constructor -class SSD1306AsciiWire : public LCDDisplay { +class SSD1306AsciiWire : public Display { public: // Constructors diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 234188684..4054b1e4e 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -18,7 +18,7 @@ */ #include "StringFormatter.h" #include -#include "LCDDisplay.h" +#include "Display.h" bool Diag::ACK=false; bool Diag::CMD=false; @@ -45,19 +45,19 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - if (!LCDDisplay::lcdDisplay) return; - LCDDisplay::lcdDisplay->setRow(row); + if (!Display::lcdDisplay) return; + Display::lcdDisplay->setRow(row); va_start(args, input); - send2(LCDDisplay::lcdDisplay,input,args); + send2(Display::lcdDisplay,input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - if (!LCDDisplay::lcdDisplay) return; - LCDDisplay::lcdDisplay->setRow(display, row); + if (!Display::lcdDisplay) return; + Display::lcdDisplay->setRow(display, row); va_start(args, input); - send2(LCDDisplay::lcdDisplay,input,args); + send2(Display::lcdDisplay,input,args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/StringFormatter.h b/StringFormatter.h index 4c95ea6a7..6923c10cf 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -21,7 +21,7 @@ #include #include "FSH.h" #include "RingStream.h" -#include "LCDDisplay.h" +#include "Display.h" class Diag { public: static bool ACK; From 47cda832102068236f9d9d0fd22d16bc245ca99b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 12 Feb 2023 10:36:26 +1000 Subject: [PATCH 593/870] Disabled servo animations --- IO_EXIOExpander.h | 93 +++++++++++++++++++++++++++++++---------------- version.h | 3 +- 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index ad48a3a21..289f28278 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -72,10 +72,10 @@ class EXIOExpander : public IODevice { _i2cAddress = i2cAddress; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. - _servoData = (ServoData**) calloc(_nPins, sizeof(ServoData*)); - for (int i=0; i<_nPins; i++) { - _servoData[i] = NULL; - } + // _servoData = (ServoData**) calloc(_nPins, sizeof(ServoData*)); + // for (int i=0; i<_nPins; i++) { + // _servoData[i] = NULL; + // } addDevice(this); } @@ -166,14 +166,14 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); - if ((currentMicros - _lastRefresh) / 1000UL > refreshInterval) { - _lastRefresh = currentMicros; - for (int pin=0; pin<_nPins; pin++) { - if (_servoData[pin] != NULL) { - updatePosition(pin); - } - } - } + // if ((currentMicros - _lastRefresh) / 1000UL > refreshInterval) { + // _lastRefresh = currentMicros; + // for (int pin=0; pin<_nPins; pin++) { + // if (_servoData[pin] != NULL) { + // updatePosition(pin); + // } + // } + // } } // Obtain the correct analogue input value @@ -194,24 +194,24 @@ class EXIOExpander : public IODevice { int _read(VPIN vpin) override { if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; - if (_servoData[pin] == NULL) { + // if (_servoData[pin] == NULL) { uint8_t pinByte = pin / 8; bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); return value; - } else { - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - return false; // No structure means no animation! - } else { - return (s->stepNumber < s->numSteps); - } - } + // } else { + // struct ServoData *s = _servoData[pin]; + // if (s == NULL) { + // return false; // No structure means no animation! + // } else { + // return (s->stepNumber < s->numSteps); + // } + // } } void _write(VPIN vpin, int value) override { if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; - if (_servoData[pin] == NULL) { + // if (_servoData[pin] == NULL) { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; @@ -219,19 +219,40 @@ class EXIOExpander : public IODevice { if (_command1Buffer[0] != EXIORDY) { DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); } - } else { - if (value) value = 1; - struct ServoData *s = _servoData[pin]; - if (s != NULL) { - // Use configured parameters - this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); - } else { - /* simulate digital pin on PWM */ - this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); - } + // } else { + // if (value) value = 1; + // struct ServoData *s = _servoData[pin]; + // if (s != NULL) { + // // Use configured parameters + // this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); + // } else { + // /* simulate digital pin on PWM */ + // this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); + // } + // } + } + + void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + if (_deviceState == DEVSTATE_FAILED) return; + int pin = vpin - _firstVpin; +#ifdef DIAG_IO + DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); +#endif + _servoBuffer[0] = EXIOWRAN; + _servoBuffer[1] = pin; + _servoBuffer[2] = value & 0xFF; + _servoBuffer[3] = value >> 8; + _servoBuffer[4] = profile; + _servoBuffer[5] = duration & 0xFF; + _servoBuffer[6] = duration >> 8; + I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7); + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin); } } +/* void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; @@ -266,7 +287,9 @@ class EXIOExpander : public IODevice { s->toPosition = value; s->fromPosition = s->currentPosition; } +*/ +/* void updatePosition(uint8_t pin) { struct ServoData *s = _servoData[pin]; if (s == NULL) return; // No pin configuration/state data @@ -299,7 +322,9 @@ class EXIOExpander : public IODevice { s->numSteps = 0; // Done now. } } +*/ +/* void writePWM(int pin, uint16_t value) { _command4Buffer[0] = EXIOWRAN; _command4Buffer[1] = pin; @@ -307,6 +332,7 @@ class EXIOExpander : public IODevice { _command4Buffer[3] = value >> 8; I2CManager.write(_i2cAddress, _command4Buffer, 4); } +*/ void _display() override { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), @@ -331,8 +357,10 @@ class EXIOExpander : public IODevice { byte _command2Buffer[2]; byte _command4Buffer[4]; byte _receive3Buffer[3]; + byte _servoBuffer[7]; uint8_t* _analoguePinMap; +/* // Servo specific struct ServoData { uint16_t activePosition : 12; // Config parameter @@ -360,6 +388,7 @@ class EXIOExpander : public IODevice { // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. const byte FLASH _bounceProfile[30] = {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; +*/ // EX-IOExpander protocol flags enum { diff --git a/version.h b/version.h index abfa9dfbc..3a0fa9eca 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.15" +#define VERSION "4.2.16" +// 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander // EX-IOExpander 0.0.14 minimum required // 4.2.14 STM32F4xx fast ADC read implementation From ec73ac69d9332358aafef8c5442783dda19b982e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 12 Feb 2023 09:01:16 +0000 Subject: [PATCH 594/870] A few more name changes to more generic names --- Display.cpp | 6 +++--- DisplayInterface.cpp | 2 +- DisplayInterface.h | 2 +- Display_Implementation.h | 8 ++++---- IO_OLEDDisplay.h | 6 +++--- LiquidCrystal_I2C.cpp | 2 +- SSD1306Ascii.cpp | 2 +- StringFormatter.cpp | 12 ++++++------ version.h | 3 ++- 9 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Display.cpp b/Display.cpp index c85d7f41c..5af85b826 100644 --- a/Display.cpp +++ b/Display.cpp @@ -67,12 +67,12 @@ size_t Display::write(uint8_t b) { } void Display::loop() { - if (!lcdDisplay) return; - lcdDisplay->loop2(false); + if (!displayHandler) return; + displayHandler->loop2(false); } Display *Display::loop2(bool force) { - if (!lcdDisplay) return NULL; + if (!displayHandler) return NULL; // If output device is busy, don't do anything on this loop // This avoids blocking while waiting for the device to complete. diff --git a/DisplayInterface.cpp b/DisplayInterface.cpp index 8fe45d362..6ef13a443 100644 --- a/DisplayInterface.cpp +++ b/DisplayInterface.cpp @@ -21,4 +21,4 @@ #include "DisplayInterface.h" -DisplayInterface *DisplayInterface::lcdDisplay = 0; +DisplayInterface *DisplayInterface::displayHandler = 0; diff --git a/DisplayInterface.h b/DisplayInterface.h index d9afdc430..82b38d6ca 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -41,7 +41,7 @@ class DisplayInterface : public Print { if (!displayNo) clear(); } - static DisplayInterface *lcdDisplay; + static DisplayInterface *displayHandler; }; #endif diff --git a/Display_Implementation.h b/Display_Implementation.h index 4c90dd901..f04b7d27a 100644 --- a/Display_Implementation.h +++ b/Display_Implementation.h @@ -40,14 +40,14 @@ // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. #if defined(OLED_DRIVER) - #define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) + #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #elif defined(LCD_DRIVER) - #define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) + #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) #else - // Create null display handler just in case someone calls lcdDisplay->something without checking if lcdDisplay is NULL! - #define CONDITIONAL_LCD_START { new DisplayInterface(); } + // Create null display handler just in case someone calls displayHandler->something without checking if displayHandler is NULL! + #define CONDITIONAL_DISPLAY_START { new DisplayInterface(); } #endif #endif // LCD_Implementation_h diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index b381bd81a..12b571f5a 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -110,11 +110,11 @@ class OLEDDisplay : public IODevice, DisplayInterface { oled = new SSD1306AsciiWire(); // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to lcdDisplay methods. + // will intercept any subsequent calls to displayHandler methods. // Make a note of the existing display reference, to that we can // pass on anything we're not interested in. - _nextDisplay = DisplayInterface::lcdDisplay; - DisplayInterface::lcdDisplay = this; + _nextDisplay = DisplayInterface::displayHandler; + DisplayInterface::displayHandler = this; addDevice(this); } diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index a5c2e16a2..c9d811367 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -57,7 +57,7 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; begin(); backlight(); - lcdDisplay = this; + displayHandler = this; } } diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index deae5ac52..5e24c7a66 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -160,7 +160,7 @@ SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { if (I2CManager.exists(address)) { begin(address, width, height); // Set singleton Address so CS is able to call it. - lcdDisplay = this; + displayHandler = this; return; } } diff --git a/StringFormatter.cpp b/StringFormatter.cpp index 4054b1e4e..a659fc47b 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -45,19 +45,19 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - if (!Display::lcdDisplay) return; - Display::lcdDisplay->setRow(row); + if (!Display::displayHandler) return; + Display::displayHandler->setRow(row); va_start(args, input); - send2(Display::lcdDisplay,input,args); + send2(Display::displayHandler,input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - if (!Display::lcdDisplay) return; - Display::lcdDisplay->setRow(display, row); + if (!Display::displayHandler) return; + Display::displayHandler->setRow(display, row); va_start(args, input); - send2(Display::lcdDisplay,input,args); + send2(Display::displayHandler,input,args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/version.h b/version.h index 0a263b724..2e6d81bef 100644 --- a/version.h +++ b/version.h @@ -13,7 +13,8 @@ // PCF8575 I2C GPIO driver added. // EX-RAIL ANOUT function for triggering analogue // HAL drivers (e.g. analogue outputs, DFPlayer, PWM). -// Installable HAL OLED Display Driver. +// Installable HAL OLED Display Driver, with +// support for multiple displays. // Layered HAL Drivers PCA9685pwm and Servo added for // (1) native PWM on PCA9685 module and // (2) animations of servo movement via PCA9685pwm. From 9c95eb69054718e04b34ba38facd6a4e560dc8a0 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sun, 12 Feb 2023 19:06:46 +1000 Subject: [PATCH 595/870] Servo animation moved to EX-IO --- IO_EXIOExpander.h | 167 +++------------------------------------------- 1 file changed, 10 insertions(+), 157 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 289f28278..a782942dd 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -70,12 +70,6 @@ class EXIOExpander : public IODevice { _firstVpin = firstVpin; _nPins = nPins; _i2cAddress = i2cAddress; - // To save RAM, space for servo configuration is not allocated unless a pin is used. - // Initialise the pointers to NULL. - // _servoData = (ServoData**) calloc(_nPins, sizeof(ServoData*)); - // for (int i=0; i<_nPins; i++) { - // _servoData[i] = NULL; - // } addDevice(this); } @@ -166,14 +160,6 @@ class EXIOExpander : public IODevice { I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); _command1Buffer[0] = EXIORDAN; I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); - // if ((currentMicros - _lastRefresh) / 1000UL > refreshInterval) { - // _lastRefresh = currentMicros; - // for (int pin=0; pin<_nPins; pin++) { - // if (_servoData[pin] != NULL) { - // updatePosition(pin); - // } - // } - // } } // Obtain the correct analogue input value @@ -194,42 +180,21 @@ class EXIOExpander : public IODevice { int _read(VPIN vpin) override { if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; - // if (_servoData[pin] == NULL) { - uint8_t pinByte = pin / 8; - bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); - return value; - // } else { - // struct ServoData *s = _servoData[pin]; - // if (s == NULL) { - // return false; // No structure means no animation! - // } else { - // return (s->stepNumber < s->numSteps); - // } - // } + uint8_t pinByte = pin / 8; + bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8); + return value; } void _write(VPIN vpin, int value) override { if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; - // if (_servoData[pin] == NULL) { - _digitalOutBuffer[0] = EXIOWRD; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = value; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); - } - // } else { - // if (value) value = 1; - // struct ServoData *s = _servoData[pin]; - // if (s != NULL) { - // // Use configured parameters - // this->_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration); - // } else { - // /* simulate digital pin on PWM */ - // this->_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0); - // } - // } + _digitalOutBuffer[0] = EXIOWRD; + _digitalOutBuffer[1] = pin; + _digitalOutBuffer[2] = value; + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + } } void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { @@ -252,88 +217,6 @@ class EXIOExpander : public IODevice { } } -/* - void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { - if (_deviceState == DEVSTATE_FAILED) return; - int pin = vpin - _firstVpin; -#ifdef DIAG_IO - DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), - vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); -#endif - if (_deviceState == DEVSTATE_FAILED) return; - if (value > 4095) value = 4095; - else if (value < 0) value = 0; - - struct ServoData *s = _servoData[pin]; - if (s == NULL) { - // Servo pin not configured, so configure now using defaults - s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1); - if (s == NULL) return; // Check for memory allocation failure - s->activePosition = 4095; - s->inactivePosition = 0; - s->currentPosition = value; - s->profile = Instant | NoPowerOff; // Use instant profile (but not this time) - } - - // Animated profile. Initiate the appropriate action. - s->currentProfile = profile; - uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit. - s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds - profileValue==Medium ? 20 : // 1.0 seconds - profileValue==Slow ? 40 : // 2.0 seconds - profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds - duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms) - s->stepNumber = 0; - s->toPosition = value; - s->fromPosition = s->currentPosition; - } -*/ - -/* - void updatePosition(uint8_t pin) { - struct ServoData *s = _servoData[pin]; - if (s == NULL) return; // No pin configuration/state data - - if (s->numSteps == 0) return; // No animation in progress - - if (s->stepNumber == 0 && s->fromPosition == s->toPosition) { - // Go straight to end of sequence, output final position. - s->stepNumber = s->numSteps-1; - } - - if (s->stepNumber < s->numSteps) { - // Animation in progress, reposition servo - s->stepNumber++; - if ((s->currentProfile & ~NoPowerOff) == Bounce) { - // Retrieve step positions from array in flash - uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]); - s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition); - } else { - // All other profiles - calculate step by linear interpolation between from and to positions. - s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition); - } - // Send servo command - this->writePWM(pin, s->currentPosition); - } else if (s->stepNumber < s->numSteps + _catchupSteps) { - // We've finished animation, wait a little to allow servo to catch up - s->stepNumber++; - } else if (s->stepNumber == s->numSteps + _catchupSteps - && s->currentPosition != 0) { - s->numSteps = 0; // Done now. - } - } -*/ - -/* - void writePWM(int pin, uint16_t value) { - _command4Buffer[0] = EXIOWRAN; - _command4Buffer[1] = pin; - _command4Buffer[2] = value & 0xFF; - _command4Buffer[3] = value >> 8; - I2CManager.write(_i2cAddress, _command4Buffer, 4); - } -*/ - void _display() override { DIAG(F("EX-IOExpander I2C:x%x v%d.%d.%d Vpins %d-%d %S"), _i2cAddress, _majorVer, _minorVer, _patchVer, @@ -360,36 +243,6 @@ class EXIOExpander : public IODevice { byte _servoBuffer[7]; uint8_t* _analoguePinMap; -/* - // Servo specific - struct ServoData { - uint16_t activePosition : 12; // Config parameter - uint16_t inactivePosition : 12; // Config parameter - uint16_t currentPosition : 12; - uint16_t fromPosition : 12; - uint16_t toPosition : 12; - uint8_t profile; // Config parameter - uint16_t stepNumber; // Index of current step (starting from 0) - uint16_t numSteps; // Number of steps in animation, or 0 if none in progress. - uint8_t currentProfile; // profile being used for current animation. - uint16_t duration; // time (tenths of a second) for animation to complete. - }; // 14 bytes per element, i.e. per pin in use - - ServoData** _servoData; - - static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off - - const unsigned int refreshInterval = 50; // refresh every 50ms - unsigned long _lastRefresh = 0; - - // Profile for a bouncing signal or turnout - // The profile below is in the range 0-100% and should be combined with the desired limits - // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, - // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. - const byte FLASH _bounceProfile[30] = - {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; -*/ - // EX-IOExpander protocol flags enum { EXIOINIT = 0xE0, // Flag to initialise setup procedure From f1f1be8ad9a5fa03702a58579e1383e0014e1a3f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 12 Feb 2023 09:18:02 +0000 Subject: [PATCH 596/870] Oops- forgot to push this one. Name changes again. --- CommandStation-EX.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 67a7e586b..1f57dc4d2 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -74,7 +74,7 @@ void setup() DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); - CONDITIONAL_LCD_START { + CONDITIONAL_DISPLAY_START { // This block is still executed for DIAGS if LCD not in use LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); @@ -160,7 +160,7 @@ void loop() LCN::loop(); #endif - LCDDisplay::loop(); // ignored if LCD not in use + Display::loop(); // ignored if LCD not in use // Handle/update IO devices. IODevice::loop(); From 2ada89f9180b750a0a15c7df9a9a63c9112d005c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 12 Feb 2023 20:35:57 +0100 Subject: [PATCH 597/870] LCN bugfix --- GITHUB_SHA.h | 2 +- LCN.cpp | 2 +- version.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 919895a75..98ac27af7 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202301290750Z" +#define GITHUB_SHA "devel-202302121935Z" diff --git a/LCN.cpp b/LCN.cpp index efb49ff42..d1e1228c1 100644 --- a/LCN.cpp +++ b/LCN.cpp @@ -43,7 +43,7 @@ void LCN::loop() { while (stream->available()) { int ch = stream->read(); - if (ch >= 0 && ch <= '9') { // accumulate id value + if (ch >= '0' && ch <= '9') { // accumulate id value id = 10 * id + ch - '0'; } else if (ch == 't' || ch == 'T') { // Turnout opcodes diff --git a/version.h b/version.h index 3a0fa9eca..21c7ad2bd 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.16" +#define VERSION "4.2.17" +// 4.2.17 LCN bugfix // 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander // EX-IOExpander 0.0.14 minimum required From 7311f2ce64b66c0703859e786717df57d7cc1eb7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 12 Feb 2023 20:38:03 +0100 Subject: [PATCH 598/870] LCN bugfix --- LCN.cpp | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LCN.cpp b/LCN.cpp index efb49ff42..d1e1228c1 100644 --- a/LCN.cpp +++ b/LCN.cpp @@ -43,7 +43,7 @@ void LCN::loop() { while (stream->available()) { int ch = stream->read(); - if (ch >= 0 && ch <= '9') { // accumulate id value + if (ch >= '0' && ch <= '9') { // accumulate id value id = 10 * id + ch - '0'; } else if (ch == 't' || ch == 'T') { // Turnout opcodes diff --git a/version.h b/version.h index 947b3631f..d340aa03c 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.1.4" +#define VERSION "4.1.5" +// 4.1.5 Bugfix LCN number parsing // 4.1.4 Bugfix for issue #299 TurnoutDescription NULL // 4.1.3 Bugfix: Ethernet init order // 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level From 3292c93192bb8c24cf1ffe786d1624f8c9a3dea4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 14 Feb 2023 21:56:11 +0000 Subject: [PATCH 599/870] Update I2CManager.cpp Report address 0x3d as (probably) OLED Display. If an I2C probe for a device times out, assume that there's a bus problem on the MUX sub-bus, and skip the rest of the devices on that bus. This reduces the impact of the long timeout on some Wire implementations. --- I2CManager.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index dbba1a0e0..05feb283f 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -52,7 +52,7 @@ static const FSH * guessI2CDeviceType(uint8_t address) { return F("GPIO Expander or LCD Display"); else if (address == 0x29) return F("Time-of-flight sensor"); - else if (address >= 0x3c && address <= 0x3c) + else if (address >= 0x3c && address <= 0x3d) return F("OLED Display"); else if (address >= 0x48 && address <= 0x4f) return F("Analogue Inputs or PWM"); @@ -110,6 +110,8 @@ void I2CManagerClass::begin(void) { // Enumerate all I2C devices that are connected via multiplexer, // i.e. that respond when only one multiplexer has one subBus enabled // and the device doesn't respond when the mux subBus is disabled. + // If any probes time out, then assume that the subbus is dead and + // don't do any more on that subbus. for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) { uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo; if (exists(muxAddr)) { @@ -117,7 +119,8 @@ void I2CManagerClass::begin(void) { for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) { muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); for (uint8_t addr=0x08; addr<0x78; addr++) { - if (exists(addr)) { + uint8_t status = checkAddress(addr); + if (status == I2C_STATUS_OK) { // De-select subbus muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); if (!exists(addr)) { @@ -129,11 +132,13 @@ void I2CManagerClass::begin(void) { } // Re-select subbus muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus}); + } else if (status == I2C_STATUS_TIMEOUT) { + // Bus stuck, skip to next one. + break; } } } - // Probe mux address again with SubBus_None to deselect all - // subBuses for that mux. Otherwise its devices will continue to + // Deselect all subBuses for this mux. Otherwise its devices will continue to // respond when other muxes are being probed. I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux } @@ -251,7 +256,7 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) { case I2C_STATUS_NEGATIVE_ACKNOWLEDGE: return F("No response from device (address NAK)"); case I2C_STATUS_TRANSMIT_ERROR: return F("Transmit error (data NAK)"); case I2C_STATUS_OTHER_TWI_ERROR: return F("Other Wire/TWI error"); - case I2C_STATUS_TIMEOUT: return F("Timeout"); + case I2C_STATUS_TIMEOUT: return F("I2C bus timeout"); case I2C_STATUS_ARBITRATION_LOST: return F("Arbitration lost"); case I2C_STATUS_BUS_ERROR: return F("I2C bus error"); case I2C_STATUS_UNEXPECTED_ERROR: return F("Unexpected error"); From 67bd886a98a7c7d006ce7d75d82518b0f232a3ec Mon Sep 17 00:00:00 2001 From: pmantoine Date: Wed, 15 Feb 2023 08:51:21 +0800 Subject: [PATCH 600/870] USB Serial fixes for EX-RAIL & debug --- CommandDistributor.cpp | 2 +- EXRAIL2.cpp | 11 +++++------ Turnouts.cpp | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 652d852b4..4b05c68c0 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -48,7 +48,7 @@ template void CommandDistributor::broadcastReply(clientType t // on a single USB connection config, write direct to Serial and ignore flush/shove template void CommandDistributor::broadcastReply(clientType type, Targs... msg){ (void)type; //shut up compiler warning - StringFormatter::send(&Serial, msg...); + StringFormatter::send(&USB_SERIAL, msg...); } #endif diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 48f91ccd3..3bd8da7ff 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1165,11 +1165,11 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { // Find out where the string is going switch (mode) { case thrunge_print: - StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco); - stream=&Serial; + StringFormatter::send(&USB_SERIAL,F("<* EXRAIL(%d) "),loco); + stream=&USB_SERIAL; break; - case thrunge_serial: stream=&Serial; break; + case thrunge_serial: stream=&USB_SERIAL; break; case thrunge_serial1: #ifdef SERIAL1_COMMANDS stream=&Serial1; @@ -1200,7 +1200,6 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { stream=&Serial6; #endif break; - // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; case thrunge_lcn: #if defined(LCN_SERIAL) stream=&LCN_SERIAL; @@ -1233,11 +1232,11 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { // and decide what to do next switch (mode) { case thrunge_print: - StringFormatter::send(&Serial,F(" *>\n")); + StringFormatter::send(&USB_SERIAL,F(" *>\n")); break; // TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break; case thrunge_parse: - DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL); + DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL); break; case thrunge_broadcast: // TODO CommandDistributor::broadcastText(buffer->getString()); diff --git a/Turnouts.cpp b/Turnouts.cpp index 636922d57..582ea7db2 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -207,7 +207,7 @@ } #ifdef EESTOREDEBUG - printAll(&Serial); + printAll(&USB_SERIAL); #endif return tt; } From 21c82b37b0ade7510ff6a78940b618180507391d Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Feb 2023 22:29:21 +0000 Subject: [PATCH 601/870] Allow frequency of PWM to be set for PCA9685 drivers. It's a parameter on the create() call, e.g. PCA9685::create(vpin, npins, address, frequency); --- IODevice.h | 1 + IO_PCA9685.cpp | 19 +++++++++++-------- IO_PCA9685pwm.h | 40 ++++++++++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/IODevice.h b/IODevice.h index 8fd5efda6..eaebaf743 100644 --- a/IODevice.h +++ b/IODevice.h @@ -326,6 +326,7 @@ class PCA9685 : public IODevice { // structures for setting up non-blocking writes to servo controller I2CRB requestBlock; uint8_t outputBuffer[5]; + uint8_t prescaler; // clock prescaler for setting PWM frequency }; ///////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 68e44a115..25b074573 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -31,22 +31,21 @@ static const byte MODE1_AI=0x20; /**< Auto-Increment enabled */ static const byte MODE1_RESTART=0x80; /**< Restart enabled */ static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ -static const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed // Predeclare helper function static void writeRegister(byte address, byte reg, byte value); // Create device driver instance. -void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { - if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress); +void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) { + if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress, frequency); } // Configure a port on the PCA9685. bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -73,10 +72,14 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i } // Constructor -PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { +PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) { _firstVpin = firstVpin; _nPins = (nPins > 16) ? 16 : nPins; _I2CAddress = i2cAddress; + // Calculate prescaler value for PWM clock + if (frequency > 1526) frequency = 1526; + else if (frequency < 24) frequency = 24; + prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency; // To save RAM, space for servo configuration is not allocated unless a pin is used. // Initialise the pointers to NULL. for (int i=0; i<_nPins; i++) @@ -98,7 +101,7 @@ void PCA9685::_begin() { // Initialise I/O module here. if (I2CManager.exists(_I2CAddress)) { writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); - writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); // In theory, we should wait 500us before sending any other commands to each device, to allow @@ -114,7 +117,7 @@ void PCA9685::_begin() { // Device-specific write function, invoked from IODevice::write(). // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; @@ -141,7 +144,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) { - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index ef92118e0..d950bbf6d 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -23,6 +23,22 @@ * commands the device to set the PWM mark-to-period ratio accordingly. * The call to IODevice::writeAnalogue(vpin, value) specifies the * desired value in the range 0-4095 (0=0% and 4095=100%). + * + * This driver can be used for simple servo control by writing values between + * about 102 and 450 (extremes of movement for 9g micro servos) or 150 to 250 + * for a more restricted range (corresponding to 1.5ms to 2.5ms pulse length). + * A value of zero will switch off the servo. To create the device, use + * the following syntax: + * + * PCA9685_basic::create(vpin, npins, i2caddress); + * + * For LED control, a value of 0 is fully off, and 4095 is fully on. It is + * recommended, to reduce flicker of LEDs, that the frequency be configured + * to a value higher than the default of 50Hz. To do this, create the device + * as follows, for a frequency of 200Hz.: + * + * PCA9685_basic::create(vpin, npins, i2caddress, 200); + * */ #ifndef PCA9685_BASIC_H @@ -39,34 +55,38 @@ class PCA9685pwm : public IODevice { public: // Create device driver instance. - static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { - if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress); + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress, frequency); } private: - // structures for setting up non-blocking writes to servo controller + // structures for setting up non-blocking writes to PWM controller I2CRB requestBlock; uint8_t outputBuffer[5]; + uint16_t prescaler; // REGISTER ADDRESSES const uint8_t PCA9685_MODE1=0x00; // Mode Register - const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first servo register ON*/ + const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first PWM register ON*/ const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ // MODE1 bits const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */ const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */ - const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ + const uint32_t FREQUENCY_OSCILLATOR=25000000; /** Accurate enough for our purposes */ const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed // Constructor - PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) { _firstVpin = firstVpin; _nPins = (nPins>16) ? 16 : nPins; _I2CAddress = i2cAddress; + if (frequency > 1526) frequency = 1526; + else if (frequency < 24) frequency = 24; + prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency; addDevice(this); // Initialise structure used for setting pulse rate @@ -82,8 +102,8 @@ class PCA9685pwm : public IODevice { // Initialise I/O module here. if (I2CManager.exists(_I2CAddress)) { - writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); - writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. + writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); + writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); // In theory, we should wait 500us before sending any other commands to each device, to allow @@ -100,7 +120,7 @@ class PCA9685pwm : public IODevice { // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif @@ -121,7 +141,7 @@ class PCA9685pwm : public IODevice { // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void writeDevice(uint8_t pin, int value) { - #ifdef DIAG_IO + #if DIAG_IO >= 3 DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete From d0445f157c1ba0ced587b10d5b10344d0f4d8286 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Feb 2023 22:34:37 +0000 Subject: [PATCH 602/870] Update IODevice.h Finish changes relating to PWM frequency (doh!) --- IODevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IODevice.h b/IODevice.h index eaebaf743..3ff1dbe2e 100644 --- a/IODevice.h +++ b/IODevice.h @@ -276,7 +276,7 @@ class IODevice { class PCA9685 : public IODevice { public: - static void create(VPIN vpin, int nPins, I2CAddress i2cAddress); + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50); enum ProfileType : uint8_t { Instant = 0, // Moves immediately between positions (if duration not specified) UseDuration = 0, // Use specified duration @@ -289,7 +289,7 @@ class PCA9685 : public IODevice { private: // Constructor - PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress); + PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency); // Device-specific initialisation void _begin() override; bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override; From 8ed3bbd8451b1455130aa60ca6d01436a2fc3c40 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 16:41:13 +0000 Subject: [PATCH 603/870] Support for multiple displays Refactor display handling so DisplayInterface class passes the relevant commands to the relevant display objects. --- CommandStation-EX.ino | 12 +- Display.cpp | 68 ++++--- Display.h | 62 +++--- DisplayInterface.cpp | 5 +- DisplayInterface.h | 80 ++++++-- Display_Implementation.h | 23 ++- IO_OLEDDisplay.h | 214 ++++++++++---------- LiquidCrystal_I2C.cpp | 38 ++-- LiquidCrystal_I2C.h | 15 +- SSD1306Ascii.cpp | 409 ++++++++++++++++++++++++++------------- SSD1306Ascii.h | 32 +-- StringFormatter.cpp | 12 +- defines.h | 4 + 13 files changed, 595 insertions(+), 379 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 1f57dc4d2..3c40bba9f 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -49,6 +49,7 @@ */ #include "DCCEX.h" +#include "Display_Implementation.h" #ifdef CPU_TYPE_ERROR #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h @@ -74,11 +75,11 @@ void setup() DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); - CONDITIONAL_DISPLAY_START { - // This block is still executed for DIAGS if LCD not in use - LCD(0,F("DCC++ EX v%S"),F(VERSION)); + DISPLAY_START ( + // This block is still executed for DIAGS if display not in use + LCD(0,F("DCC-EX v%S"),F(VERSION)); LCD(1,F("Lic GPLv3")); - } + ); // Responsibility 2: Start all the communications before the DCC engine // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi @@ -160,7 +161,8 @@ void loop() LCN::loop(); #endif - Display::loop(); // ignored if LCD not in use + // Display refresh + DisplayInterface::loop(); // Handle/update IO devices. IODevice::loop(); diff --git a/Display.cpp b/Display.cpp index 5af85b826..587a10708 100644 --- a/Display.cpp +++ b/Display.cpp @@ -47,42 +47,65 @@ #include "Display.h" -void Display::clear() { - clearNative(); - for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0'; +// Constructor - allocates device driver. +Display::Display(DisplayDevice *deviceDriver) { + _deviceDriver = deviceDriver; + // Get device dimensions in characters (e.g. 16x2). + numCharacterColumns = _deviceDriver->getNumCols(); + numCharacterRows = _deviceDriver->getNumRows();; + for (uint8_t row=0; rowbegin(); + _deviceDriver->clearNative(); } -void Display::setRow(byte line) { +void Display::_clear() { + _deviceDriver->clearNative(); + for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) + rowBuffer[row][0] = '\0'; + topRow = -1; // loop2 will fill from row 0 +} + +void Display::_setRow(uint8_t line) { hotRow = line; hotCol = 0; + rowBuffer[hotRow][hotCol] = 0; // Clear existing text } -size_t Display::write(uint8_t b) { - if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1; +size_t Display::_write(uint8_t b) { + if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1; rowBuffer[hotRow][hotCol] = b; hotCol++; rowBuffer[hotRow][hotCol] = 0; return 1; } -void Display::loop() { - if (!displayHandler) return; - displayHandler->loop2(false); +// Refresh screen completely (will block until complete). Used +// during start-up. +void Display::_refresh() { + loop2(true); } -Display *Display::loop2(bool force) { - if (!displayHandler) return NULL; - +// On normal loop entries, loop will only make one output request on each +// entry, to avoid blocking while waiting for the I2C. +void Display::_displayLoop() { // If output device is busy, don't do anything on this loop - // This avoids blocking while waiting for the device to complete. - if (isBusy()) return NULL; + // This avoids blocking while waiting for the device to complete. + if (!_deviceDriver->isBusy()) loop2(false); +} +Display *Display::loop2(bool force) { unsigned long currentMillis = millis(); if (!force) { // See if we're in the time between updates - if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME) + if ((currentMillis - lastScrollTime) < DISPLAY_SCROLL_TIME) return NULL; } else { // force full screen update from the beginning. @@ -104,7 +127,7 @@ Display *Display::loop2(bool force) { buffer[i] = rowBuffer[rowNext][i]; } else buffer[0] = '\0'; // Empty line - setRowNative(slot); // Set position for display + _deviceDriver->setRowNative(slot); // Set position for display charIndex = 0; bufferPointer = &buffer[0]; @@ -113,12 +136,12 @@ Display *Display::loop2(bool force) { // Write next character, or a space to erase current position. char ch = *bufferPointer; if (ch) { - writeNative(ch); + _deviceDriver->writeNative(ch); bufferPointer++; } else - writeNative(' '); + _deviceDriver->writeNative(' '); - if (++charIndex >= MAX_LCD_COLS) { + if (++charIndex >= MAX_CHARACTER_COLS) { // Screen slot completed, move to next slot on screen slot++; bufferPointer = 0; @@ -128,7 +151,7 @@ Display *Display::loop2(bool force) { } } - if (slot >= lcdRows) { + if (slot >= numCharacterRows) { // Last slot finished, reset ready for next screen update. #if SCROLLMODE==2 if (!done) { @@ -151,7 +174,8 @@ Display *Display::loop2(bool force) { } void Display::moveToNextRow() { - rowNext = (rowNext + 1) % MAX_LCD_ROWS; + rowNext = rowNext + 1; + if (rowNext >= MAX_CHARACTER_ROWS) rowNext = 0; #if SCROLLMODE == 1 // Finished if we've looped back to row 0 if (rowNext == 0) done = true; @@ -164,4 +188,4 @@ void Display::moveToNextRow() { void Display::skipBlankRows() { while (!done && rowBuffer[rowNext][0] == 0) moveToNextRow(); -} +} \ No newline at end of file diff --git a/Display.h b/Display.h index 84cdbde78..7bbbc7895 100644 --- a/Display.h +++ b/Display.h @@ -16,15 +16,18 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ -#ifndef LCDDisplay_h -#define LCDDisplay_h +#ifndef Display_h +#define Display_h #include #include "defines.h" #include "DisplayInterface.h" // Allow maximum message length to be overridden from config.h #if !defined(MAX_MSG_SIZE) -#define MAX_MSG_SIZE 20 +// On a screen that's 128 pixels wide, character 22 overlaps end of screen +// However, by making the line longer than the screen, we don't have to +// clear the screen, we just overwrite what was there. +#define MAX_MSG_SIZE 22 #endif // Set default scroll mode (overridable in config.h) @@ -32,36 +35,17 @@ #define SCROLLMODE 1 #endif -// This class is created in LCDisplay_Implementation.h +// This class is created in Display_Implementation.h class Display : public DisplayInterface { - public: - Display() {}; - static const int MAX_LCD_ROWS = 8; - static const int MAX_LCD_COLS = MAX_MSG_SIZE; - static const long LCD_SCROLL_TIME = 3000; // 3 seconds +public: + Display(DisplayDevice *deviceDriver); + static const int MAX_CHARACTER_ROWS = 8; + static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; + static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds - // Internally handled functions - static void loop(); - Display* loop2(bool force) override; - void setRow(byte line) override; - void clear() override; - - size_t write(uint8_t b) override; - -protected: - uint8_t lcdRows; - uint8_t lcdCols; - - private: - void moveToNextRow(); - void skipBlankRows(); - - // Relay functions to the live driver in the subclass - virtual void clearNative() = 0; - virtual void setRowNative(byte line) = 0; - virtual size_t writeNative(uint8_t b) = 0; - virtual bool isBusy() = 0; +private: + DisplayDevice *_deviceDriver; unsigned long lastScrollTime = 0; int8_t hotRow = 0; @@ -71,11 +55,25 @@ class Display : public DisplayInterface { int8_t rowFirst = -1; int8_t rowNext = 0; int8_t charIndex = 0; - char buffer[MAX_LCD_COLS + 1]; + char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; bool done = false; + uint16_t numCharacterRows; + uint16_t numCharacterColumns = MAX_CHARACTER_COLS; + + char *rowBuffer[MAX_CHARACTER_ROWS]; + +public: + void begin() override; + void _clear() override; + void _setRow(uint8_t line) override; + size_t _write(uint8_t b) override; + void _refresh() override; + void _displayLoop() override; + Display *loop2(bool force); + void moveToNextRow(); + void skipBlankRows(); - char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1]; }; #endif diff --git a/DisplayInterface.cpp b/DisplayInterface.cpp index 6ef13a443..f2c144e0f 100644 --- a/DisplayInterface.cpp +++ b/DisplayInterface.cpp @@ -21,4 +21,7 @@ #include "DisplayInterface.h" -DisplayInterface *DisplayInterface::displayHandler = 0; +// Install null display driver initially - will be replaced if required. +DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface(); + +uint8_t DisplayInterface::_selectedDisplayNo = 255; diff --git a/DisplayInterface.h b/DisplayInterface.h index 82b38d6ca..f1d598d59 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -25,23 +25,75 @@ // Definition of base class for displays. The base class does nothing. class DisplayInterface : public Print { +protected: + static DisplayInterface *_displayHandler; + static uint8_t _selectedDisplayNo; // Nothing selected. + DisplayInterface *_nextHandler = NULL; + uint8_t _displayNo = 0; + public: - virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; }; - virtual void setRow(byte line) { (void)line; }; - virtual void clear() { }; - virtual size_t write(uint8_t c) { (void)c; return 0; }; - // Additional functions to support multiple displays. - // Display number zero is the default one and the original display - // drivers overloaded the above calls only. Newer display drivers - // (e.g. HAL IO_OledDisplay) should override all functions. - virtual void setRow(uint8_t displayNo, byte line) { - if (!displayNo) setRow(line); + // Add display object to list of displays + void addDisplay(uint8_t displayNo) { + _nextHandler = _displayHandler; + _displayHandler = this; + _displayNo = displayNo; + } + static DisplayInterface *getDisplayHandler() { + return _displayHandler; + } + uint8_t getDisplayNo() { + return _displayNo; } - virtual void clear(uint8_t displayNo) { - if (!displayNo) clear(); - } - static DisplayInterface *displayHandler; + // The next functions are to provide compatibility with calls to the LCD function + // which does not specify a display number. These always apply to display '0'. + static void refresh() { refresh(0); }; + static void setRow(uint8_t line) { setRow(0, line); }; + static void clear() { clear(0); }; + + // Additional functions to support multiple displays. These perform a + // multicast to all displays that match the selected displayNo. + // Display number zero is the default one. + static void setRow(uint8_t displayNo, uint8_t line) { + _selectedDisplayNo = displayNo; + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) { + if (displayNo == p->_displayNo) p->_setRow(line); + } + } + size_t write (uint8_t c) override { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + if (_selectedDisplayNo == p->_displayNo) p->_write(c); + return _displayHandler ? 1 : 0; + } + static void clear(uint8_t displayNo) { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + if (displayNo == p->_displayNo) p->_clear(); + } + static void refresh(uint8_t displayNo) { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + if (displayNo == p->_displayNo) p->_refresh(); + } + static void loop() { + for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) + p->_displayLoop(); + }; + // The following are overridden within the specific device class + virtual void begin() {}; + virtual size_t _write(uint8_t c) { (void)c; return 0; }; + virtual void _setRow(uint8_t line) {} + virtual void _clear() {} + virtual void _refresh() {} + virtual void _displayLoop() {} }; +class DisplayDevice { +public: + virtual bool begin() { return true; } + virtual void clearNative() = 0; + virtual void setRowNative(uint8_t line) = 0; + virtual size_t writeNative(uint8_t c) = 0; + virtual bool isBusy() = 0; + virtual uint16_t getNumRows() = 0; + virtual uint16_t getNumCols() = 0; +}; #endif diff --git a/Display_Implementation.h b/Display_Implementation.h index f04b7d27a..ca19bd79c 100644 --- a/Display_Implementation.h +++ b/Display_Implementation.h @@ -27,7 +27,7 @@ #ifndef LCD_Implementation_h #define LCD_Implementation_h -#include "Display.h" +#include "DisplayInterface.h" #include "SSD1306Ascii.h" #include "LiquidCrystal_I2C.h" @@ -35,19 +35,26 @@ // Implement the Display shim class as a singleton. // The DisplayInterface class implements a display handler with no code (null device); // The Display class sub-classes DisplayInterface to provide the common display code; -// Then Display class is subclassed to the specific device type classes: +// Then Display class talks to the specific device type classes: // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. #if defined(OLED_DRIVER) - #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) + #define DISPLAY_START(xxx) { \ + DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \ + t->begin(); \ + xxx; \ + t->refresh(); \ + } #elif defined(LCD_DRIVER) - #define CONDITIONAL_DISPLAY_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true)) - + #define DISPLAY_START(xxx) { \ + DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \ + t->begin(); \ + xxx; \ + t->refresh();} #else - // Create null display handler just in case someone calls displayHandler->something without checking if displayHandler is NULL! - #define CONDITIONAL_DISPLAY_START { new DisplayInterface(); } -#endif + #define DISPLAY_START(xxx) {} +#endif #endif // LCD_Implementation_h diff --git a/IO_OLEDDisplay.h b/IO_OLEDDisplay.h index 12b571f5a..26c24b337 100644 --- a/IO_OLEDDisplay.h +++ b/IO_OLEDDisplay.h @@ -49,13 +49,15 @@ #include "SSD1306Ascii.h" #include "version.h" -class OLEDDisplay : public IODevice, DisplayInterface { +typedef SSD1306AsciiWire OLED; + +template +class OLEDDisplay : public IODevice, public DisplayInterface { private: - uint8_t _displayNo = 0; // Here we define the device-specific variables. uint8_t _height; // in pixels uint8_t _width; // in pixels - SSD1306AsciiWire *oled; + T *_displayDriver; uint8_t _rowNo = 0; // Row number being written by caller uint8_t _colNo = 0; // Position in line being written by caller uint8_t _numRows; @@ -66,26 +68,25 @@ class OLEDDisplay : public IODevice, DisplayInterface { uint8_t _rowNoToScreen = 0; uint8_t _charPosToScreen = 0; DisplayInterface *_nextDisplay = NULL; - uint8_t _selectedDisplayNo = 0; public: // Static function to handle "OLEDDisplay::create(...)" calls. - static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + static void create(I2CAddress i2cAddress, int width, int height) { /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); } - static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); } protected: // Constructor OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { - _displayNo = displayNo; + _displayDriver = new T(i2cAddress, width, height); _I2CAddress = i2cAddress; _width = width; _height = height; - _numCols = (_width+5) / 6; // character block 6 x 8, round up - _numRows = _height / 8; // Round down + _numCols = _displayDriver->getNumCols(); + _numRows = _displayDriver->getNumRows(); _charPosToScreen = _numCols; @@ -96,51 +97,28 @@ class OLEDDisplay : public IODevice, DisplayInterface { // Fill buffer with spaces memset(_buffer, ' ', _numCols*_numRows); + _displayDriver->clearNative(); + + // Add device to list of HAL devices (not necessary but allows + // status to be displayed using and device to be + // reinitialised using ). + IODevice::addDevice(this); + + // Also add this display to list of display handlers + DisplayInterface::addDisplay(displayNo); + // Is this the main display? - if (_displayNo == 0) { + if (displayNo == 0) { // Set first two lines on screen - setRow(0); - print(F("DCC++ EX v")); + this->setRow(displayNo, 0); + print(F("DCC-EX v")); print(F(VERSION)); - setRow(1); + setRow(displayNo, 1); print(F("Lic GPLv3")); } - - // Create OLED driver - oled = new SSD1306AsciiWire(); - - // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to displayHandler methods. - // Make a note of the existing display reference, to that we can - // pass on anything we're not interested in. - _nextDisplay = DisplayInterface::displayHandler; - DisplayInterface::displayHandler = this; - - addDevice(this); } - // Device-specific initialisation - void _begin() override { - // Initialise device - if (oled->begin(_I2CAddress, _width, _height)) { - - DIAG(F("OLEDDisplay installed on address %s"), _I2CAddress.toString()); - - - // Force all rows to be redrawn - for (uint8_t row=0; row<_numRows; row++) - _rowGeneration[row]++; - - // Start with top line (looks better) - _rowNoToScreen = _numRows; - _charPosToScreen = _numCols; - } - } - - void _loop(unsigned long) override { - //screenUpdate(); - } - + void screenUpdate() { // Loop through the buffer and if a row has changed // (rowGeneration[row] is changed) then start writing the @@ -149,8 +127,8 @@ class OLEDDisplay : public IODevice, DisplayInterface { // First check if the OLED driver is still busy from a previous // call. If so, don't to anything until the next entry. - if (!oled->isBusy()) { - // Check if we've just done the end of a row or just started + if (!_displayDriver->isBusy()) { + // Check if we've just done the end of a row if (_charPosToScreen >= _numCols) { // Move to next line if (++_rowNoToScreen >= _numRows) @@ -159,101 +137,103 @@ class OLEDDisplay : public IODevice, DisplayInterface { if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { // Row content has changed, so start outputting it _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; - oled->setRowNative(_rowNoToScreen); + _displayDriver->setRowNative(_rowNoToScreen); _charPosToScreen = 0; // Prepare to output first character on next entry } else { // Row not changed, don't bother writing it. } } else { // output character at current position - oled->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); } } return; } + + ///////////////////////////////////////////////// + // IODevice Class Member Overrides + ///////////////////////////////////////////////// + + // Device-specific initialisation + void _begin() override { + // Initialise device + if (_displayDriver->begin()) { + + _display(); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; + + // Start with top line (looks better). + // The numbers will wrap round on the first loop2 entry. + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; + } + } + + void _loop(unsigned long) override { + screenUpdate(); + } + + // Display information about the device. + void _display() { + DIAG(F("OLEDDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString()); + } ///////////////////////////////////////////////// // DisplayInterface functions // ///////////////////////////////////////////////// - DisplayInterface* loop2(bool force) override { + +public: + void _displayLoop() override { screenUpdate(); - if (_nextDisplay) - return _nextDisplay->loop2(force); // continue to next display - return NULL; } // Position on nominated line number (0 to number of lines -1) // Clear the line in the buffer ready for updating // The displayNo referenced here is remembered and any following // calls to write() will be directed to that display. - void setRow(uint8_t displayNo, byte line) override { - _selectedDisplayNo = displayNo; - if (displayNo == _displayNo) { - if (line == 255) { - // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - - // scroll the contents of the buffer and put the new line - // at the bottom of the screen - for (int row=1; row<_numRows; row++) { - strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); - _rowGeneration[row-1]++; - } - line = _numRows-1; - } else if (line >= _numRows) - line = _numRows - 1; // Overwrite bottom line. - - _rowNo = line; - // Fill line with blanks - for (_colNo = 0; _colNo < _numCols; _colNo++) - _buffer[_rowNo*_numCols+_colNo] = ' '; - _colNo = 0; - // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry, by which time - // the line should have been written to the buffer. - _rowGeneration[_rowNo]++; - - } else if (_nextDisplay) - _nextDisplay->setRow(displayNo, line); // Pass to next display - - } - - // Write one character to the screen referenced in the last setRow() call. - size_t write(uint8_t c) override { - if (_selectedDisplayNo == _displayNo) { - // Write character to buffer (if there's space) - if (_colNo < _numCols) { - _buffer[_rowNo*_numCols+_colNo++] = c; + void _setRow(byte line) override { + if (line == 255) { + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; } - return 1; - } else if (_nextDisplay) - return _nextDisplay->write(c); - else - return 0; + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. + _rowGeneration[_rowNo]++; } - // Write blanks to all of the screen (blocks until complete) - void clear (uint8_t displayNo) override { - if (displayNo == _displayNo) { - // Clear buffer - for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { - setRow(displayNo, _rowNo); - } - _rowNo = 0; - } else if (_nextDisplay) - _nextDisplay->clear(displayNo); // Pass to next display - } - - // Overloads of above, for compatibility - void setRow(uint8_t line) override { - setRow(0, line); - } - void clear() override { - clear(0); + // Write one character to the screen referenced in the last setRow() call. + virtual size_t _write(uint8_t c) override { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; + } + return 1; } - // Display information about the device. - void _display() { - DIAG(F("OLEDDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); + // Write blanks to all of the screen buffer + void _clear() { + // Clear buffer + memset(_buffer, ' ', _numCols*_numRows); + _colNo = 0; + _rowNo = 0; } }; diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index c9d811367..3bf98ef4f 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -44,24 +44,25 @@ LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols, uint8_t lcd_rows) { _Addr = lcd_Addr; - lcdRows = lcd_rows; - lcdCols = lcd_cols; - + lcdRows = lcd_rows; // Number of character rows (typically 2 or 4). + lcdCols = lcd_cols; // Number of character columns (typically 16 or 20) _backlightval = 0; + } + +bool LiquidCrystal_I2C::begin() { I2CManager.begin(); I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz. - if (I2CManager.exists(lcd_Addr)) { - DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr); + if (I2CManager.exists(_Addr)) { + DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcdCols, (int)lcdRows, _Addr.toString()); _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; - begin(); backlight(); - displayHandler = this; + } else { + DIAG(F("LCD not found on I2C:%s"), _Addr.toString()); + return false; } -} -void LiquidCrystal_I2C::begin() { if (lcdRows > 1) { _displayfunction |= LCD_2LINE; } @@ -99,26 +100,23 @@ void LiquidCrystal_I2C::begin() { _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; display(); - // clear it off - clear(); - // Initialize to default text direction (for roman languages) _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; // set the entry mode command(LCD_ENTRYMODESET | _displaymode); - setRowNative(0); + return true; } /********** high level commands, for the user! */ void LiquidCrystal_I2C::clearNative() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero - delayMicroseconds(2000); // this command takes 1.52ms + delayMicroseconds(1600); // this command takes 1.52ms } void LiquidCrystal_I2C::setRowNative(byte row) { - int row_offsets[] = {0x00, 0x40, 0x14, 0x54}; + uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54}; if (row >= lcdRows) { row = lcdRows - 1; // we count rows starting w/0 } @@ -146,6 +144,10 @@ size_t LiquidCrystal_I2C::writeNative(uint8_t value) { return 1; } +bool LiquidCrystal_I2C::isBusy() { + return rb.isBusy(); +} + /*********** mid level commands, for sending data/cmds */ inline void LiquidCrystal_I2C::command(uint8_t value) { @@ -196,7 +198,7 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { outputBuffer[len++] = highnib; outputBuffer[len++] = lownib|En; outputBuffer[len++] = lownib; - I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously + I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously } // write 4 data bits to the HD44780 LCD controller. @@ -208,12 +210,12 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) { uint8_t len = 0; outputBuffer[len++] = _data|En; outputBuffer[len++] = _data; - I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously + I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously } // write a byte to the PCF8574 I2C interface. We don't need to set // the enable pin for this. void LiquidCrystal_I2C::expanderWrite(uint8_t value) { outputBuffer[0] = value | _backlightval; - I2CManager.write(_Addr, outputBuffer, 1); // Write command synchronously + I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously } \ No newline at end of file diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index 5d5a7415c..daecd61f9 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -62,33 +62,38 @@ #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit -class LiquidCrystal_I2C : public Display { +class LiquidCrystal_I2C : public DisplayDevice { public: LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); - void begin(); + bool begin() override; void clearNative() override; void setRowNative(byte line) override; size_t writeNative(uint8_t c) override; + // I/O is synchronous, so if this is called we're not busy! + bool isBusy() override; void display(); void noBacklight(); void backlight(); void command(uint8_t); + uint16_t getNumCols() { return lcdCols; } + uint16_t getNumRows() { return lcdRows; } + private: void send(uint8_t, uint8_t); void write4bits(uint8_t); void expanderWrite(uint8_t); - uint8_t _Addr; + uint8_t lcdCols=0, lcdRows=0; + I2CAddress _Addr; uint8_t _displayfunction; uint8_t _displaycontrol; uint8_t _displaymode; uint8_t _backlightval; uint8_t outputBuffer[4]; - // I/O is synchronous, so if this is called we're not busy! - bool isBusy() override { return false; } + I2CRB rb; }; #endif diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 5e24c7a66..f5dd281a7 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -144,39 +144,38 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { //------------------------------------------------------------------------------ // Constructor -SSD1306AsciiWire::SSD1306AsciiWire() { - I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed - +SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { + m_i2cAddr = 0; + m_displayWidth = width; + m_displayHeight = height; } // CS auto-detect and configure constructor -SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { +SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) { + m_i2cAddr = address; + m_displayWidth = width; + m_displayHeight = height; +} + +bool SSD1306AsciiWire::begin() { I2CManager.begin(); - I2CManager.setClock(400000L); // Set max supported I2C speed - - // Probe for I2C device on 0x3c and 0x3d. - for (uint8_t address = 0x3c; address <= 0x3d; address++) { - if (I2CManager.exists(address)) { - begin(address, width, height); - // Set singleton Address so CS is able to call it. - displayHandler = this; - return; + I2CManager.setClock(400000L); // Set max supported I2C speede + + if (m_i2cAddr == 0) { + // Probe for I2C device on 0x3c and 0x3d. + for (uint8_t address = 0x3c; address <= 0x3d; address++) { + if (I2CManager.exists(address)) { + m_i2cAddr = address; + break; + } } + if (m_i2cAddr == 0) + DIAG(F("OLED display not found")); } - DIAG(F("OLED display not found")); -} -bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { - if (m_initialised) return true; - - m_i2cAddr = address; - m_displayWidth = width; - m_displayHeight = height; - - // Set size in characters in base class - lcdRows = height / 8; - lcdCols = width / 6; + // Set size in characters + m_charsPerColumn = m_displayHeight / fontHeight; + m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up m_col = 0; m_row = 0; m_colOffset = 0; @@ -186,7 +185,7 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { m_colOffset = 2; I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init)); } else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) { - // SSD1306 128x64 or 128x32 + // SSD1306 or SSD1309 128x64 or 128x32 I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit)); if (m_displayHeight == 32) I2CManager.write(m_i2cAddr, 5, 0, // Set command mode @@ -198,19 +197,18 @@ bool SSD1306AsciiWire::begin(I2CAddress address, int width, int height) { } // Device found DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString()); - clear(); return true; } /* Clear screen by writing blank pixels. */ void SSD1306AsciiWire::clearNative() { - const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire + const int maxBytes = sizeof(blankPixels) - 1; // max number of pixel columns (bytes) per transmission for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) { setRowNative(r); // Position at start of row to be erased - for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) { - uint8_t len = m_displayWidth-c+1; + for (uint8_t c = 0; c < m_displayWidth; c += maxBytes) { + uint8_t len = m_displayWidth-c; // Number of pixel columns remaining if (len > maxBytes) len = maxBytes; - I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns + I2CManager.write_P(m_i2cAddr, blankPixels, len+1); // Write command + 'len' blank columns } } } @@ -263,127 +261,262 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) { outputBuffer[0] = 0x40; // set SSD1306 controller to data mode uint8_t bufferPos = 1; // Copy character pixel columns - for (uint8_t i = 0; i < fontWidth; i++) - outputBuffer[bufferPos++] = GETFLASH(base++); - // Add blank pixels between letters - for (uint8_t i = 0; i < letterSpacing; i++) - outputBuffer[bufferPos++] = 0; + for (uint8_t i = 0; i < fontWidth; i++) { + if (m_col++ < m_displayWidth) + outputBuffer[bufferPos++] = GETFLASH(base++); + } // Write the data to I2C display I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock); - m_col += fontWidth + letterSpacing; return 1; } //------------------------------------------------------------------------------ -// Font characters, 5x7 pixels, 0x61 characters starting at 0x20. +// Font characters, 6x8 pixels, starting at 0x20. // Lower case characters optionally omitted. -const uint8_t FLASH SSD1306AsciiWire::System5x7[] = { +const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { // Fixed width; char width table not used !!!! - // or with lowercase character omitted. // font data - 0x00, 0x00, 0x00, 0x00, 0x00, // (space) - 0x00, 0x00, 0x5F, 0x00, 0x00, // ! - 0x00, 0x07, 0x00, 0x07, 0x00, // " - 0x14, 0x7F, 0x14, 0x7F, 0x14, // # - 0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ - 0x23, 0x13, 0x08, 0x64, 0x62, // % - 0x36, 0x49, 0x55, 0x22, 0x50, // & - 0x00, 0x05, 0x03, 0x00, 0x00, // ' - 0x00, 0x1C, 0x22, 0x41, 0x00, // ( - 0x00, 0x41, 0x22, 0x1C, 0x00, // ) - 0x08, 0x2A, 0x1C, 0x2A, 0x08, // * - 0x08, 0x08, 0x3E, 0x08, 0x08, // + - 0x00, 0x50, 0x30, 0x00, 0x00, // , - 0x08, 0x08, 0x08, 0x08, 0x08, // - - 0x00, 0x60, 0x60, 0x00, 0x00, // . - 0x20, 0x10, 0x08, 0x04, 0x02, // / - 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 - 0x00, 0x42, 0x7F, 0x40, 0x00, // 1 - 0x42, 0x61, 0x51, 0x49, 0x46, // 2 - 0x21, 0x41, 0x45, 0x4B, 0x31, // 3 - 0x18, 0x14, 0x12, 0x7F, 0x10, // 4 - 0x27, 0x45, 0x45, 0x45, 0x39, // 5 - 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 - 0x01, 0x71, 0x09, 0x05, 0x03, // 7 - 0x36, 0x49, 0x49, 0x49, 0x36, // 8 - 0x06, 0x49, 0x49, 0x29, 0x1E, // 9 - 0x00, 0x36, 0x36, 0x00, 0x00, // : - 0x00, 0x56, 0x36, 0x00, 0x00, // ; - 0x00, 0x08, 0x14, 0x22, 0x41, // < - 0x14, 0x14, 0x14, 0x14, 0x14, // = - 0x41, 0x22, 0x14, 0x08, 0x00, // > - 0x02, 0x01, 0x51, 0x09, 0x06, // ? - 0x32, 0x49, 0x79, 0x41, 0x3E, // @ - 0x7E, 0x11, 0x11, 0x11, 0x7E, // A - 0x7F, 0x49, 0x49, 0x49, 0x36, // B - 0x3E, 0x41, 0x41, 0x41, 0x22, // C - 0x7F, 0x41, 0x41, 0x22, 0x1C, // D - 0x7F, 0x49, 0x49, 0x49, 0x41, // E - 0x7F, 0x09, 0x09, 0x01, 0x01, // F - 0x3E, 0x41, 0x41, 0x51, 0x32, // G - 0x7F, 0x08, 0x08, 0x08, 0x7F, // H - 0x00, 0x41, 0x7F, 0x41, 0x00, // I - 0x20, 0x40, 0x41, 0x3F, 0x01, // J - 0x7F, 0x08, 0x14, 0x22, 0x41, // K - 0x7F, 0x40, 0x40, 0x40, 0x40, // L - 0x7F, 0x02, 0x04, 0x02, 0x7F, // M - 0x7F, 0x04, 0x08, 0x10, 0x7F, // N - 0x3E, 0x41, 0x41, 0x41, 0x3E, // O - 0x7F, 0x09, 0x09, 0x09, 0x06, // P - 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q - 0x7F, 0x09, 0x19, 0x29, 0x46, // R - 0x46, 0x49, 0x49, 0x49, 0x31, // S - 0x01, 0x01, 0x7F, 0x01, 0x01, // T - 0x3F, 0x40, 0x40, 0x40, 0x3F, // U - 0x1F, 0x20, 0x40, 0x20, 0x1F, // V - 0x7F, 0x20, 0x18, 0x20, 0x7F, // W - 0x63, 0x14, 0x08, 0x14, 0x63, // X - 0x03, 0x04, 0x78, 0x04, 0x03, // Y - 0x61, 0x51, 0x49, 0x45, 0x43, // Z - 0x00, 0x00, 0x7F, 0x41, 0x41, // [ - 0x02, 0x04, 0x08, 0x10, 0x20, // "\" - 0x41, 0x41, 0x7F, 0x00, 0x00, // ] - 0x04, 0x02, 0x01, 0x02, 0x04, // ^ - 0x40, 0x40, 0x40, 0x40, 0x40, // _ - 0x00, 0x01, 0x02, 0x04, 0x00, // ` + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) (20) + 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, // ! (21) + 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, // " + 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, // # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, // $ + 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, // % + 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, // & + 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, // ' + 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, // ( + 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, // ) + 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, // * + 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, // + + 0x00, 0x50, 0x30, 0x00, 0x00, 0x00, // , + 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // - + 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // . + 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // / (47) + 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, // 0 (48) + 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, // 1 + 0x42, 0x61, 0x51, 0x49, 0x46, 0x00, // 2 + 0x21, 0x41, 0x45, 0x4B, 0x31, 0x00, // 3 + 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, // 4 + 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, // 5 + 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, // 6 + 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, // 7 + 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, // 8 + 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, // 9 (57) + 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, // : + 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, // ; + 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // < + 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // = + 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, // > + 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, // ? + 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, // @ (64) + 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, // A (65) + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, // B + 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, // C + 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, // D + 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, // E + 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, // F + 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, // G + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, // H + 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, // I + 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, // J + 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, // K + 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L + 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, // M + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, // N + 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, // O + 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, // P + 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, // Q + 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, // R + 0x46, 0x49, 0x49, 0x49, 0x31, 0x00, // S + 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, // T + 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, // U + 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, // V + 0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00, // W + 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, // X + 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, // Y + 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, // Z (90) + 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [ + 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, // "\" + 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, // ] + 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, // ^ + 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, // _ + 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, // ' (96) #ifndef NOLOWERCASE - 0x20, 0x54, 0x54, 0x54, 0x78, // a - 0x7F, 0x48, 0x44, 0x44, 0x38, // b - 0x38, 0x44, 0x44, 0x44, 0x20, // c - 0x38, 0x44, 0x44, 0x48, 0x7F, // d - 0x38, 0x54, 0x54, 0x54, 0x18, // e - 0x08, 0x7E, 0x09, 0x01, 0x02, // f - 0x08, 0x14, 0x54, 0x54, 0x3C, // g - 0x7F, 0x08, 0x04, 0x04, 0x78, // h - 0x00, 0x44, 0x7D, 0x40, 0x00, // i - 0x20, 0x40, 0x44, 0x3D, 0x00, // j - 0x00, 0x7F, 0x10, 0x28, 0x44, // k - 0x00, 0x41, 0x7F, 0x40, 0x00, // l - 0x7C, 0x04, 0x18, 0x04, 0x78, // m - 0x7C, 0x08, 0x04, 0x04, 0x78, // n - 0x38, 0x44, 0x44, 0x44, 0x38, // o - 0x7C, 0x14, 0x14, 0x14, 0x08, // p - 0x08, 0x14, 0x14, 0x18, 0x7C, // q - 0x7C, 0x08, 0x04, 0x04, 0x08, // r - 0x48, 0x54, 0x54, 0x54, 0x20, // s - 0x04, 0x3F, 0x44, 0x40, 0x20, // t - 0x3C, 0x40, 0x40, 0x20, 0x7C, // u - 0x1C, 0x20, 0x40, 0x20, 0x1C, // v - 0x3C, 0x40, 0x30, 0x40, 0x3C, // w - 0x44, 0x28, 0x10, 0x28, 0x44, // x - 0x0C, 0x50, 0x50, 0x50, 0x3C, // y - 0x44, 0x64, 0x54, 0x4C, 0x44, // z + 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, // a (97) + 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, // b + 0x38, 0x44, 0x44, 0x44, 0x20, 0x00, // c + 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, // d + 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, // e + 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, // f + 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, // g + 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, // h + 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, // i + 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, // j + 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, // l + 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, // m + 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, // n + 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // o + 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, // p + 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, // q + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, // r + 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, // s + 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, // t + 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, // u + 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, // v + 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, // w + 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x + 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, // y + 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, // z (122) #endif - 0x00, 0x08, 0x36, 0x41, 0x00, // { - 0x00, 0x00, 0x7F, 0x00, 0x00, // | - 0x00, 0x41, 0x36, 0x08, 0x00, // } - 0x08, 0x08, 0x2A, 0x1C, 0x08, // -> - 0x08, 0x1C, 0x2A, 0x08, 0x08, // <- - 0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol + 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, // { (123) + 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, // | + 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, // } + 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, // -> + 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, // <- (127) +#ifndef NO_EXTENDED_CHARACTERS +// Extended characters - based on "DOS Western Europe" characters +// International characters not yet implemented. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x80 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x90 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xa0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + // Extended characters 176-180 + 0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0 + 0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1 + 0x6a, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2 + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3 + 0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4 + + 0x14, 0x14, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + + // Extended characters 185-190 + 0x28, 0x28, 0xef, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9 + 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba + 0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb + 0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbe + // Extended characters 191-199 + 0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, // Top right corner 0xbf + 0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, // Bottom left corner 0xc0 + 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, // Horizontal line with upward spur 0xc1 + 0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, // Horizontal line with downward spur 0xc2 + 0x00, 0x00, 0x00, 0xff, 0x08, 0x08, // Vertical line with right spur 0xc3 + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Horizontal line 0xc4 + 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, // Cross 0xc5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + + // Extended characters 200-206 + 0x00, 0x00, 0x1f, 0x10, 0x17, 0x14, // Double bottom left corner 0xc8 + 0x00, 0x00, 0xfc, 0x04, 0xf4, 0x14, // Double top left corner 0xc9 + 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, // Double horizontal with double upward spur 0xca + 0x14, 0x14, 0xf4, 0x04, 0xf4, 0x14, // Double horizontal with double downward spur 0xcb + 0x00, 0x00, 0xff, 0x00, 0xf7, 0x14, // Double vertical line with double right spur 0xcc + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd + 0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + + // Extended characters 217-223 + 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, // Bottom right corner 0xd9 + 0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, // Top left corner 0xda + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Solid block 0xdb + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // Bottom half block 0xdc + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, // Left half block 0xdd + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xf0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + // Extended character 248 + 0x00, 0x06, 0x09, 0x09, 0x06, 0x00, // degree symbol 0xf8 +#endif + 0x00 }; + +const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6; diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h index d40eac602..57427a1ad 100644 --- a/SSD1306Ascii.h +++ b/SSD1306Ascii.h @@ -27,21 +27,24 @@ #include "I2CManager.h" #include "DIAG.h" +#include "DisplayInterface.h" // Uncomment to remove lower-case letters to save 108 bytes of flash //#define NOLOWERCASE + + //------------------------------------------------------------------------------ // Constructor -class SSD1306AsciiWire : public Display { +class SSD1306AsciiWire : public DisplayDevice { public: // Constructors SSD1306AsciiWire(int width, int height); // Auto-detects I2C address - SSD1306AsciiWire(); // Requires call to 'begin()' + SSD1306AsciiWire(I2CAddress address, int width, int height); // Initialize the display controller. - bool begin(I2CAddress address, int width, int height); + bool begin(); // Clear the display and set the cursor to (0, 0). void clearNative() override; @@ -53,6 +56,8 @@ class SSD1306AsciiWire : public Display { size_t writeNative(uint8_t c) override; bool isBusy() override { return requestBlock.isBusy(); } + uint16_t getNumCols() { return m_charsPerRow; } + uint16_t getNumRows() { return m_charsPerColumn; } private: // Cursor column. @@ -63,28 +68,31 @@ class SSD1306AsciiWire : public Display { uint8_t m_displayWidth; // Display height. uint8_t m_displayHeight; + // Display width in characters + uint8_t m_charsPerRow; + // Display height in characters + uint8_t m_charsPerColumn; // Column offset RAM to SEG. uint8_t m_colOffset = 0; // Current font. - const uint8_t* const m_font = System5x7; + const uint8_t* const m_font = System6x8; // Flag to prevent calling begin() twice uint8_t m_initialised = false; - // Only fixed size 5x7 fonts in a 6x8 cell are supported. - static const uint8_t fontWidth = 5; - static const uint8_t fontHeight = 7; - static const uint8_t letterSpacing = 1; + // Only fixed size 6x8 fonts in a 6x8 cell are supported. + static const uint8_t fontWidth = 6; + static const uint8_t fontHeight = 8; static const uint8_t m_fontFirstChar = 0x20; - static const uint8_t m_fontCharCount = 0x61; + static const uint8_t m_fontCharCount; - I2CAddress m_i2cAddr; + I2CAddress m_i2cAddr = 0; I2CRB requestBlock; - uint8_t outputBuffer[fontWidth+letterSpacing+1]; + uint8_t outputBuffer[fontWidth+1]; static const uint8_t blankPixels[]; - static const uint8_t System5x7[]; + static const uint8_t System6x8[]; static const uint8_t FLASH Adafruit128xXXinit[]; static const uint8_t FLASH SH1106_132x64init[]; }; diff --git a/StringFormatter.cpp b/StringFormatter.cpp index a659fc47b..f7d9c505c 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -18,7 +18,7 @@ */ #include "StringFormatter.h" #include -#include "Display.h" +#include "DisplayInterface.h" bool Diag::ACK=false; bool Diag::CMD=false; @@ -45,19 +45,17 @@ void StringFormatter::lcd(byte row, const FSH* input...) { send2(&USB_SERIAL,input,args); send(&USB_SERIAL,F(" *>\n")); - if (!Display::displayHandler) return; - Display::displayHandler->setRow(row); + DisplayInterface::setRow(row); va_start(args, input); - send2(Display::displayHandler,input,args); + send2(DisplayInterface::getDisplayHandler(),input,args); } void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) { va_list args; - if (!Display::displayHandler) return; - Display::displayHandler->setRow(display, row); + DisplayInterface::setRow(display, row); va_start(args, input); - send2(Display::displayHandler,input,args); + send2(DisplayInterface::getDisplayHandler(),input,args); } void StringFormatter::send(Print * stream, const FSH* input...) { diff --git a/defines.h b/defines.h index e9ae631d0..974882455 100644 --- a/defines.h +++ b/defines.h @@ -46,9 +46,13 @@ #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" #undef HAS_ENOUGH_MEMORY +#define NO_EXTENDED_CHARACTERS +#undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_NANO) #define ARDUINO_TYPE "NANO" #undef HAS_ENOUGH_MEMORY +#define NO_EXTENDED_CHARACTERS +#undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_MEGA) #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_AVR_MEGA2560) From 6b863ea4834a8b6f40e07d3cd0ba8585875545a4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 16:41:33 +0000 Subject: [PATCH 604/870] Update I2CManager_NonBlocking.h Cosmetic changes --- I2CManager_NonBlocking.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 991b45ca1..322ca88c6 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -109,7 +109,7 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) { * starting the operation. ***************************************************************************/ void I2CManagerClass::startTransaction() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ATOMIC_BLOCK() { if ((state == I2C_STATE_FREE) && (queueHead != NULL)) { state = I2C_STATE_ACTIVE; completionStatus = I2C_STATUS_OK; @@ -174,7 +174,7 @@ void I2CManagerClass::startTransaction() { void I2CManagerClass::queueRequest(I2CRB *req) { req->status = I2C_STATUS_PENDING; req->nextRequest = NULL; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ATOMIC_BLOCK() { if (!queueTail) queueHead = queueTail = req; // Only item on queue else @@ -236,7 +236,7 @@ void I2CManagerClass::setTimeout(unsigned long value) { * may be caused by an I2C wire short for example. ***************************************************************************/ void I2CManagerClass::checkForTimeout() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + ATOMIC_BLOCK() { I2CRB *t = queueHead; if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) { // Check for timeout From d6c8595f8a93f6106bc5ffa4be48ea9bb8b059b0 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 16:50:42 +0000 Subject: [PATCH 605/870] Update Display.h Get rid of compiler warning. --- Display.h | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Display.h b/Display.h index 7bbbc7895..d74768014 100644 --- a/Display.h +++ b/Display.h @@ -23,11 +23,8 @@ #include "DisplayInterface.h" // Allow maximum message length to be overridden from config.h -#if !defined(MAX_MSG_SIZE) -// On a screen that's 128 pixels wide, character 22 overlaps end of screen -// However, by making the line longer than the screen, we don't have to -// clear the screen, we just overwrite what was there. -#define MAX_MSG_SIZE 22 +#if !defined(MAX_MSG_SIZE) +#define MAX_MSG_SIZE 20 #endif // Set default scroll mode (overridable in config.h) @@ -48,13 +45,13 @@ class Display : public DisplayInterface { DisplayDevice *_deviceDriver; unsigned long lastScrollTime = 0; - int8_t hotRow = 0; - int8_t hotCol = 0; + uint8_t hotRow = 0; + uint8_t hotCol = 0; int8_t topRow = 0; - int8_t slot = 0; + uint8_t slot = 0; int8_t rowFirst = -1; int8_t rowNext = 0; - int8_t charIndex = 0; + uint8_t charIndex = 0; char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; bool done = false; From 10cd5800612c737e2420801b77eb324a4eb240a2 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 16 Feb 2023 22:27:23 +0000 Subject: [PATCH 606/870] Update I2CManager_NonBlocking.h Missing initialisation of read buffer pointer! --- I2CManager_NonBlocking.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/I2CManager_NonBlocking.h b/I2CManager_NonBlocking.h index 322ca88c6..fb5bae54d 100644 --- a/I2CManager_NonBlocking.h +++ b/I2CManager_NonBlocking.h @@ -316,6 +316,7 @@ void I2CManagerClass::handleInterrupt() { deviceAddress = rbAddress; sendBuffer = currentRequest->writeBuffer; bytesToSend = currentRequest->writeLen; + receiveBuffer = currentRequest->readBuffer; bytesToReceive = currentRequest->readLen; operation = currentRequest->operation & OPERATION_MASK; state = I2C_STATE_ACTIVE; @@ -326,16 +327,21 @@ void I2CManagerClass::handleInterrupt() { // Application request completed, now send epilogue to mux overallStatus = completionStatus; currentRequest->nBytes = rxCount; // Save number of bytes read into rb - muxPhase = MuxPhase_EPILOG; - deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); - muxData[0] = 0x00; - sendBuffer = &muxData[0]; - bytesToSend = 1; - bytesToReceive = 0; - operation = OPERATION_SEND; - state = I2C_STATE_ACTIVE; - I2C_sendStart(); - return; + if (_muxCount == 1) { + // Only one MUX, don't need to deselect subbus + muxPhase = MuxPhase_OFF; + } else { + muxPhase = MuxPhase_EPILOG; + deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber(); + muxData[0] = 0x00; + sendBuffer = &muxData[0]; + bytesToSend = 1; + bytesToReceive = 0; + operation = OPERATION_SEND; + state = I2C_STATE_ACTIVE; + I2C_sendStart(); + return; + } } else if (muxPhase == MuxPhase_EPILOG) { // Epilog finished, ignore completionStatus muxPhase = MuxPhase_OFF; From 9797a0fd2d08d193c8e8d51271723d8fe7dc0a39 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 17 Feb 2023 16:04:21 +0000 Subject: [PATCH 607/870] Add high accuracy timing for STM32, on pins D3 and D6. --- DCCTimerSTM32.cpp | 52 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index df66c986a..48c254f5a 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -1,4 +1,5 @@ /* + * © 2023 Neil McKechnie * © 2022 Paul M. Antoine * © 2021 Mike S * © 2021 Harald Barth @@ -50,13 +51,13 @@ HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #endif INTERRUPT_CALLBACK interruptHandler=0; -// Let's use STM32's timer #11 until disabused of this notion -// Timer #11 is used for "servo" library, but as DCC-EX is not using -// this libary, we should be free and clear. -HardwareTimer timer(TIM11); +// Let's use STM32's timer #2 which supports hardware pulse generation on pins D3 and D6 +// (accurate timing, independent of the latency of interrupt handling). +// Pin D3 is driven by TIM2 channel 2 and D6 is TIM2 channel 3. +HardwareTimer timer(TIM2); // Timer IRQ handler -void Timer11_Handler() { +void Timer_Handler() { interruptHandler(); } @@ -67,9 +68,8 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); timer.pause(); timer.setPrescaleFactor(1); -// timer.setOverflow(CLOCK_CYCLES * 2); timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); - timer.attachInterrupt(Timer11_Handler); + timer.attachInterrupt(Timer_Handler); timer.refresh(); timer.resume(); @@ -77,20 +77,42 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { } bool DCCTimer::isPWMPin(byte pin) { - //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, - // there's no support yet for High Accuracy, so for now return false - // return digitalPinHasPWM(pin); - return false; + // Timer 2 Channel 2 controls pin D3, and Timer2 Channel 3 controls D6. + // Enable the appropriate timer channel. + switch (pin) { + case 3: + timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, D3); + return true; + case 6: + timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, D6); + return true; + default: + return false; + } } void DCCTimer::setPWM(byte pin, bool high) { - // TODO: High Accuracy mode is not supported as yet, and may never need to be - (void) pin; - (void) high; + // Set the timer so that, at the next counter overflow, the requested + // pin state is activated automatically before the interrupt code runs. + switch (pin) { + case 3: + if (high) + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_0; + else + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_1; + break; + case 6: + if (high) + TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_0; + else + TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_1; + break; + } } void DCCTimer::clearPWM() { - return; + timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, NC); } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { From 173676287c2ddaccf93f433a658443f42a763707 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 18 Feb 2023 09:32:38 +0000 Subject: [PATCH 608/870] Allow extended I2C addresses to be specified in non-extended configuration If an extended I2C address is specified (including mux and/or subbus) then these parameters are ignored, but a warning output to the diagnostic console. --- I2CManager.cpp | 8 +++++++- I2CManager.h | 22 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 05feb283f..14e84b748 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -350,4 +350,10 @@ void I2CAddress::toHex(const uint8_t value, char *buffer) { *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; bits = value & 0xf; *ptr++ = bits > 9 ? bits-10+'a' : bits+'0'; -} \ No newline at end of file +} + +#if !defined(I2C_EXTENDED_ADDRESS) + +/* static */ bool I2CAddress::_addressWarningDone = false; + +#endif \ No newline at end of file diff --git a/I2CManager.h b/I2CManager.h index 542c92f6b..016874a26 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -24,6 +24,7 @@ #include #include "FSH.h" #include "defines.h" +#include "DIAG.h" /* * Manager for I2C communications. For portability, it allows use @@ -144,9 +145,6 @@ //#define I2C_EXTENDED_ADDRESS -// Type to hold I2C address -#if defined(I2C_EXTENDED_ADDRESS) - ///////////////////////////////////////////////////////////////////////////////////// // Extended I2C Address type to facilitate extended I2C addresses including // I2C multiplexer support. @@ -189,6 +187,9 @@ enum I2CSubBus : uint8_t { SubBus_All = 255, // Enable all sub-buses }; +// Type to hold I2C address +#if defined(I2C_EXTENDED_ADDRESS) + // First MUX address (they range between 0x70-0x77). #define I2C_MUX_BASE_ADDRESS 0x70 @@ -315,6 +316,14 @@ struct I2CAddress { I2CAddress(const uint8_t deviceAddress) { _deviceAddress = deviceAddress; } + I2CAddress(I2CMux, I2CSubBus, const uint8_t deviceAddress) { + addressWarning(); + _deviceAddress = deviceAddress; + } + I2CAddress(I2CSubBus, const uint8_t deviceAddress) { + addressWarning(); + _deviceAddress = deviceAddress; + } // Basic constructor I2CAddress() : I2CAddress(0) {} @@ -344,6 +353,13 @@ struct I2CAddress { private: // Helper function for converting byte to four-character hex string (e.g. 0x23). void toHex(const uint8_t value, char *buffer); + void addressWarning() { + if (!_addressWarningDone) { + DIAG(F("WARNIING: Extended I2C address used but not supported in this configuration")); + _addressWarningDone = true; + } + } + static bool _addressWarningDone; }; #endif // I2C_EXTENDED_ADDRESS From 33229b48470ee982a1cf880c1907348b05662af3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 19 Feb 2023 19:14:15 +0000 Subject: [PATCH 609/870] Update SSD1306Ascii.cpp Bugfix: Move calculation of m_charsPerColumn and m_charsPerRow into constructors, to avoid incorrect random values being reported. --- SSD1306Ascii.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index f5dd281a7..911074bd9 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -143,18 +143,18 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = { // SSD1306AsciiWire Method Definitions //------------------------------------------------------------------------------ -// Constructor -SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) { - m_i2cAddr = 0; - m_displayWidth = width; - m_displayHeight = height; -} +// Auto-detect address +SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) + : SSD1306AsciiWire(0, width, height) { } -// CS auto-detect and configure constructor +// Constructor with explicit address SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) { m_i2cAddr = address; m_displayWidth = width; m_displayHeight = height; + // Set size in characters + m_charsPerColumn = m_displayHeight / fontHeight; + m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up } bool SSD1306AsciiWire::begin() { @@ -173,9 +173,6 @@ bool SSD1306AsciiWire::begin() { DIAG(F("OLED display not found")); } - // Set size in characters - m_charsPerColumn = m_displayHeight / fontHeight; - m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up m_col = 0; m_row = 0; m_colOffset = 0; From a36dccfad0ecd564c7341db96e821704a9e1097e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 10:55:37 +0000 Subject: [PATCH 610/870] Add UserAddin class to facilitate user-written cyclic functions. UserAddin allows a function to be 'plugged in' to the IODevice (HAL) framework and executed cyclically, using just one line of code in the myHal.cpp. This will facilitate functions for displaying CS state on OLEDs and LCDs, among other things. --- IODevice.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/IODevice.h b/IODevice.h index 3ff1dbe2e..51b5aa06b 100644 --- a/IODevice.h +++ b/IODevice.h @@ -418,6 +418,55 @@ class EXTurntable : public IODevice { ///////////////////////////////////////////////////////////////////////////////////////////////////// + +// IODevice framework for invoking user-written functions. +// To use, define a function that you want to be regularly +// invoked, and then create an instance of UserAddin. +// For example, you can show the status, on screen 3, of the first eight +// locos in the speed table: +// +// void updateLocoScreen() { +// for (int i=0; i<8; i++) { +// if (DCC::speedTable[i].loco > 0) { +// int speed = DCC::speedTable[i].speedCode; +// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco, +// speed & 0x7f, speed & 0x80 ? 'R' : 'F'); +// } +// } +// } +// +// void halSetup() { +// ... +// UserAddin(updateLocoScreen, 1000); // Update every 1000ms +// ... +// } +// +class UserAddin : public IODevice { +private: + void (*_invokeUserFunction)(); + int _delay; // milliseconds +public: + UserAddin(void (*func)(), int delay) { + _invokeUserFunction = func; + _delay = delay; + addDevice(this); + } + // userFunction has no return value, no parameter. delay is in milliseconds. + static void create(void (*userFunction)(), int delay) { + new UserAddin(userFunction, delay); + } +protected: + void _begin() { _display(); } + void _loop(unsigned long currentMicros) override { + _invokeUserFunction(); + // _loop won't be called again until _delay ms have elapsed. + delayUntil(currentMicros + _delay * 1000UL); + } + void _display() override { + DIAG(F("UserAddin run every %dms"), _delay); + } +}; + #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" From 43b1e8db21aed001e586980e209670c2cc7d5471 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:00:27 +0000 Subject: [PATCH 611/870] Update LCD driver to make it more reliable Delay times at startup extended to make start-up configuration more reliable. --- LiquidCrystal_I2C.cpp | 8 ++++---- LiquidCrystal_I2C.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index 3bf98ef4f..b8b8e97cd 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -80,15 +80,15 @@ bool LiquidCrystal_I2C::begin() { // we start in 8bit mode, try to set 4 bit mode write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms + delayMicroseconds(5000); // wait min 4.1ms // second try write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms + delayMicroseconds(5000); // wait min 4.1ms // third go! write4bits(0x03); - delayMicroseconds(150); + delayMicroseconds(5000); // finally, set to 4-bit interface write4bits(0x02); @@ -112,7 +112,7 @@ bool LiquidCrystal_I2C::begin() { /********** high level commands, for the user! */ void LiquidCrystal_I2C::clearNative() { command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero - delayMicroseconds(1600); // this command takes 1.52ms + delayMicroseconds(2000); // this command takes 1.52ms but allow plenty } void LiquidCrystal_I2C::setRowNative(byte row) { diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h index daecd61f9..650ad151a 100644 --- a/LiquidCrystal_I2C.h +++ b/LiquidCrystal_I2C.h @@ -90,7 +90,7 @@ class LiquidCrystal_I2C : public DisplayDevice { uint8_t _displayfunction; uint8_t _displaycontrol; uint8_t _displaymode; - uint8_t _backlightval; + uint8_t _backlightval = 0; uint8_t outputBuffer[4]; I2CRB rb; From 034bb6b675312c05ad8feda5cfacce1b0300f0ee Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:03:14 +0000 Subject: [PATCH 612/870] Update DCCTimerSTM32.cpp Enable hardware pulse generation on STM32 for pins 12 and 13 (standard motor shield pins), using timers TIM2 and TIM3. Any other pins will currently be controlled directly by the interrupt routine (in effect, by digitalWrite calls). --- DCCTimerSTM32.cpp | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 48c254f5a..2504a264e 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -51,10 +51,13 @@ HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #endif INTERRUPT_CALLBACK interruptHandler=0; -// Let's use STM32's timer #2 which supports hardware pulse generation on pins D3 and D6 -// (accurate timing, independent of the latency of interrupt handling). -// Pin D3 is driven by TIM2 channel 2 and D6 is TIM2 channel 3. +// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13. +// Also, timer #3 will do hardware pulses on pin D12. This gives +// accurate timing, independent of the latency of interrupt handling. +// We only need to interrupt on one of these (TIM2), the other will just generate +// pulses. HardwareTimer timer(TIM2); +HardwareTimer timerAux(TIM3); // Timer IRQ handler void Timer_Handler() { @@ -67,24 +70,30 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); timer.pause(); + timerAux.pause(); timer.setPrescaleFactor(1); timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); timer.attachInterrupt(Timer_Handler); timer.refresh(); + timerAux.setPrescaleFactor(1); + timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); + timerAux.refresh(); + timer.resume(); + timerAux.resume(); interrupts(); } bool DCCTimer::isPWMPin(byte pin) { - // Timer 2 Channel 2 controls pin D3, and Timer2 Channel 3 controls D6. + // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12. // Enable the appropriate timer channel. switch (pin) { - case 3: - timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, D3); + case 12: + timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12); return true; - case 6: - timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, D6); + case 13: + timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13); return true; default: return false; @@ -94,25 +103,26 @@ bool DCCTimer::isPWMPin(byte pin) { void DCCTimer::setPWM(byte pin, bool high) { // Set the timer so that, at the next counter overflow, the requested // pin state is activated automatically before the interrupt code runs. + // TIM2 is timer, TIM3 is timerAux. switch (pin) { - case 3: + case 12: if (high) - TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_0; + TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; else - TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC2M_Msk) | TIM_CCMR1_OC2M_1; + TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; break; - case 6: + case 13: if (high) - TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_0; + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; else - TIM2->CCMR2 = (TIM2->CCMR2 & ~TIM_CCMR2_OC3M_Msk) | TIM_CCMR2_OC3M_1; + TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; break; } } void DCCTimer::clearPWM() { - timer.setMode(2, TIMER_OUTPUT_COMPARE_INACTIVE, NC); - timer.setMode(3, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { From 8e90bb69967a15256cf21a95683cff57a0d9ef83 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:07:19 +0000 Subject: [PATCH 613/870] Enable extended addresses and extended OLED characters on all but Nano, Uno and Mega4809.. Define I2C_EXTENDED_ADDRESS on most platforms, and define NO_EXTENDED_CHARACTERS on Nano, Uno and Mega4809. --- defines.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/defines.h b/defines.h index 974882455..88f7fb413 100644 --- a/defines.h +++ b/defines.h @@ -43,6 +43,9 @@ #undef USB_SERIAL // Teensy has this defined by default... #define USB_SERIAL Serial +// Include extended addresses unless specifically excluded +#define I2C_EXTENDED_ADDRESS + #if defined(ARDUINO_AVR_UNO) #define ARDUINO_TYPE "UNO" #undef HAS_ENOUGH_MEMORY @@ -60,6 +63,8 @@ #elif defined(ARDUINO_ARCH_MEGAAVR) #define ARDUINO_TYPE "MEGAAVR" #undef HAS_ENOUGH_MEMORY +#define NO_EXTENDED_CHARACTERS +#undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_TEENSY31) #define ARDUINO_TYPE "TEENSY3132" #undef USB_SERIAL From c2e8557c4ce575fc4e49a2ef89024f41c72b4cba Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 21 Feb 2023 11:12:31 +0000 Subject: [PATCH 614/870] defines.h - cosmetic change to indenting. Lay out long #if statement with indents to make it a bit easier to read. --- defines.h | 178 +++++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/defines.h b/defines.h index 88f7fb413..5582e8be1 100644 --- a/defines.h +++ b/defines.h @@ -47,115 +47,115 @@ #define I2C_EXTENDED_ADDRESS #if defined(ARDUINO_AVR_UNO) -#define ARDUINO_TYPE "UNO" -#undef HAS_ENOUGH_MEMORY -#define NO_EXTENDED_CHARACTERS -#undef I2C_EXTENDED_ADDRESS + #define ARDUINO_TYPE "UNO" + #undef HAS_ENOUGH_MEMORY + #define NO_EXTENDED_CHARACTERS + #undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_NANO) -#define ARDUINO_TYPE "NANO" -#undef HAS_ENOUGH_MEMORY -#define NO_EXTENDED_CHARACTERS -#undef I2C_EXTENDED_ADDRESS + #define ARDUINO_TYPE "NANO" + #undef HAS_ENOUGH_MEMORY + #define NO_EXTENDED_CHARACTERS + #undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_AVR_MEGA) -#define ARDUINO_TYPE "MEGA" + #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_AVR_MEGA2560) -#define ARDUINO_TYPE "MEGA" + #define ARDUINO_TYPE "MEGA" #elif defined(ARDUINO_ARCH_MEGAAVR) -#define ARDUINO_TYPE "MEGAAVR" -#undef HAS_ENOUGH_MEMORY -#define NO_EXTENDED_CHARACTERS -#undef I2C_EXTENDED_ADDRESS + #define ARDUINO_TYPE "MEGAAVR" + #undef HAS_ENOUGH_MEMORY + #define NO_EXTENDED_CHARACTERS + #undef I2C_EXTENDED_ADDRESS #elif defined(ARDUINO_TEENSY31) -#define ARDUINO_TYPE "TEENSY3132" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif + #define ARDUINO_TYPE "TEENSY3132" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif #elif defined(ARDUINO_TEENSY35) -#define ARDUINO_TYPE "TEENSY35" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -// Teensy support for I2C is awaiting development -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif + #define ARDUINO_TYPE "TEENSY35" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + // Teensy support for I2C is awaiting development + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif #elif defined(ARDUINO_TEENSY36) -#define ARDUINO_TYPE "TEENSY36" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif + #define ARDUINO_TYPE "TEENSY36" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif #elif defined(ARDUINO_TEENSY40) -#define ARDUINO_TYPE "TEENSY40" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif -#elif defined(ARDUINO_TEENSY41) -#define ARDUINO_TYPE "TEENSY41" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// Teensy support for native I2C is awaiting development -#ifndef I2C_USE_WIRE + #define ARDUINO_TYPE "TEENSY40" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE #define I2C_USE_WIRE -#endif + #endif +#elif defined(ARDUINO_TEENSY41) + #define ARDUINO_TYPE "TEENSY41" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // Teensy support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif #elif defined(ARDUINO_ARCH_ESP8266) -#define ARDUINO_TYPE "ESP8266" -#warning "ESP8266 platform untested, you are on your own" + #define ARDUINO_TYPE "ESP8266" + #warning "ESP8266 platform untested, you are on your own" #elif defined(ARDUINO_ARCH_ESP32) -#define ARDUINO_TYPE "ESP32" -#ifndef DISABLE_EEPROM -#define DISABLE_EEPROM -#endif -#elif defined(ARDUINO_ARCH_SAMD) -#define ARDUINO_TYPE "SAMD21" -#undef USB_SERIAL -#define USB_SERIAL SerialUSB -// SAMD no EEPROM by default -#ifndef DISABLE_EEPROM + #define ARDUINO_TYPE "ESP32" + #ifndef DISABLE_EEPROM #define DISABLE_EEPROM -#endif + #endif +#elif defined(ARDUINO_ARCH_SAMD) + #define ARDUINO_TYPE "SAMD21" + #undef USB_SERIAL + #define USB_SERIAL SerialUSB + // SAMD no EEPROM by default + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif #elif defined(ARDUINO_ARCH_STM32) -#define ARDUINO_TYPE "STM32" -// STM32 no EEPROM by default -#ifndef DISABLE_EEPROM - #define DISABLE_EEPROM -#endif -// STM32 support for native I2C is awaiting development -#ifndef I2C_USE_WIRE - #define I2C_USE_WIRE -#endif + #define ARDUINO_TYPE "STM32" + // STM32 no EEPROM by default + #ifndef DISABLE_EEPROM + #define DISABLE_EEPROM + #endif + // STM32 support for native I2C is awaiting development + #ifndef I2C_USE_WIRE + #define I2C_USE_WIRE + #endif /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) -#define ARDUINO_TYPE "RP2040" + #define ARDUINO_TYPE "RP2040" */ #else -#define CPU_TYPE_ERROR + #define CPU_TYPE_ERROR #endif // replace board type if provided by compiler From 4eb277f19e2c2b821552319ab7b222a4a630a8ac Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:06:03 +0000 Subject: [PATCH 615/870] Refactor Display handler to (hopefully) improve clarity. --- Display.cpp | 107 +++++++++++++++++++++++++++------------------------- Display.h | 14 +++---- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/Display.cpp b/Display.cpp index 587a10708..2c447781c 100644 --- a/Display.cpp +++ b/Display.cpp @@ -36,7 +36,7 @@ * not held up significantly. The exception to this is when * the loop2() function is called with force=true, where * a screen update is executed to completion. This is normally - * only done during start-up. + * only noMoreRowsToDisplay during start-up. * The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2 * in the config.h. * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), @@ -53,9 +53,9 @@ Display::Display(DisplayDevice *deviceDriver) { // Get device dimensions in characters (e.g. 16x2). numCharacterColumns = _deviceDriver->getNumCols(); numCharacterRows = _deviceDriver->getNumRows();; - for (uint8_t row=0; rowclearNative(); for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) rowBuffer[row][0] = '\0'; - topRow = -1; // loop2 will fill from row 0 + topRow = ROW_INITIAL; // loop2 will fill from row 0 } void Display::_setRow(uint8_t line) { hotRow = line; hotCol = 0; - rowBuffer[hotRow][hotCol] = 0; // Clear existing text + rowBuffer[hotRow][0] = 0; // Clear existing text } size_t Display::_write(uint8_t b) { @@ -109,63 +109,57 @@ Display *Display::loop2(bool force) { return NULL; } else { // force full screen update from the beginning. - rowFirst = -1; - rowNext = 0; + rowFirst = ROW_INITIAL; + rowNext = ROW_INITIAL; bufferPointer = 0; - done = false; + noMoreRowsToDisplay = false; slot = 0; } do { if (bufferPointer == 0) { // Find a line of data to write to the screen. - if (rowFirst < 0) rowFirst = rowNext; - skipBlankRows(); - if (!done) { - // Non-blank line found, so copy it. - for (uint8_t i = 0; i < sizeof(buffer); i++) + if (rowFirst == ROW_INITIAL) rowFirst = rowNext; + if (findNextNonBlankRow()) { + // Non-blank line found, so copy it (including terminator) + for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++) buffer[i] = rowBuffer[rowNext][i]; - } else - buffer[0] = '\0'; // Empty line + } else { + // No non-blank lines left, so draw a blank line + buffer[0] = 0; + } _deviceDriver->setRowNative(slot); // Set position for display charIndex = 0; bufferPointer = &buffer[0]; - } else { - // Write next character, or a space to erase current position. char ch = *bufferPointer; if (ch) { _deviceDriver->writeNative(ch); bufferPointer++; - } else + } else { _deviceDriver->writeNative(' '); + } if (++charIndex >= MAX_CHARACTER_COLS) { // Screen slot completed, move to next slot on screen - slot++; bufferPointer = 0; - if (!done) { - moveToNextRow(); - skipBlankRows(); - } - } - - if (slot >= numCharacterRows) { - // Last slot finished, reset ready for next screen update. + slot++; + if (slot >= numCharacterRows) { + // Last slot on screen written, reset ready for next screen update. #if SCROLLMODE==2 - if (!done) { - // On next refresh, restart one row on from previous start. - rowNext = rowFirst; - moveToNextRow(); - skipBlankRows(); - } + if (!noMoreRowsToDisplay) { + // On next refresh, restart one row on from previous start. + rowNext = rowFirst; + findNextNonBlankRow(); + } #endif - done = false; - slot = 0; - rowFirst = -1; - lastScrollTime = currentMillis; - return NULL; + noMoreRowsToDisplay = false; + slot = 0; + rowFirst = ROW_INITIAL; + lastScrollTime = currentMillis; + return NULL; + } } } } while (force); @@ -173,19 +167,30 @@ Display *Display::loop2(bool force) { return NULL; } -void Display::moveToNextRow() { - rowNext = rowNext + 1; - if (rowNext >= MAX_CHARACTER_ROWS) rowNext = 0; +bool Display::findNextNonBlankRow() { + while (!noMoreRowsToDisplay) { + if (rowNext == ROW_INITIAL) + rowNext = 0; + else + rowNext = rowNext + 1; + if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL; #if SCROLLMODE == 1 - // Finished if we've looped back to row 0 - if (rowNext == 0) done = true; + // Finished if we've looped back to start + if (rowNext == ROW_INITIAL) { + noMoreRowsToDisplay = true; + return false; + } #else - // Finished if we're back to the first one shown - if (rowNext == rowFirst) done = true; + // Finished if we're back to the first one shown + if (rowNext == rowFirst) { + noMoreRowsToDisplay = true; + return false; + } #endif -} - -void Display::skipBlankRows() { - while (!done && rowBuffer[rowNext][0] == 0) - moveToNextRow(); + if (rowBuffer[rowNext][0] != 0) { + // Found non-blank row + return true; + } + } + return false; } \ No newline at end of file diff --git a/Display.h b/Display.h index d74768014..be2479db9 100644 --- a/Display.h +++ b/Display.h @@ -40,6 +40,7 @@ class Display : public DisplayInterface { static const int MAX_CHARACTER_ROWS = 8; static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds + static const uint8_t ROW_INITIAL = 255; private: DisplayDevice *_deviceDriver; @@ -47,18 +48,18 @@ class Display : public DisplayInterface { unsigned long lastScrollTime = 0; uint8_t hotRow = 0; uint8_t hotCol = 0; - int8_t topRow = 0; + uint8_t topRow = 0; uint8_t slot = 0; - int8_t rowFirst = -1; - int8_t rowNext = 0; + uint8_t rowFirst = ROW_INITIAL; + uint8_t rowNext = ROW_INITIAL; uint8_t charIndex = 0; char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; - bool done = false; + bool noMoreRowsToDisplay = false; uint16_t numCharacterRows; uint16_t numCharacterColumns = MAX_CHARACTER_COLS; - char *rowBuffer[MAX_CHARACTER_ROWS]; + char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1]; public: void begin() override; @@ -68,8 +69,7 @@ class Display : public DisplayInterface { void _refresh() override; void _displayLoop() override; Display *loop2(bool force); - void moveToNextRow(); - void skipBlankRows(); + bool findNextNonBlankRow(); }; From 4deb3238028d88325e13857b0c596022e2663713 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:06:39 +0000 Subject: [PATCH 616/870] Update LiquidCrystal_I2C.cpp Ensure that pipelined I/O requests complete before the next one is set up. --- LiquidCrystal_I2C.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp index b8b8e97cd..1354024b7 100644 --- a/LiquidCrystal_I2C.cpp +++ b/LiquidCrystal_I2C.cpp @@ -194,6 +194,7 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode; // Send both nibbles uint8_t len = 0; + rb.wait(); outputBuffer[len++] = highnib|En; outputBuffer[len++] = highnib; outputBuffer[len++] = lownib|En; @@ -208,6 +209,7 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) { // I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the // HD44780 on the trailing edge of the Enable pin. uint8_t len = 0; + rb.wait(); outputBuffer[len++] = _data|En; outputBuffer[len++] = _data; I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously @@ -216,6 +218,7 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) { // write a byte to the PCF8574 I2C interface. We don't need to set // the enable pin for this. void LiquidCrystal_I2C::expanderWrite(uint8_t value) { + rb.wait(); outputBuffer[0] = value | _backlightval; I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously } \ No newline at end of file From c3675367ed62e1092925f7cfff78cb6643420311 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:08:09 +0000 Subject: [PATCH 617/870] Update IO_VL53L0X.h Configure XSHUT control so that the pull-up on the module raises the pin to +2.8V rather than trying to drive it to +5V. --- IO_VL53L0X.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 9aa3d9cfd..7c8051803 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -177,16 +177,18 @@ class VL53L0X : public IODevice { break; case STATE_RESTARTMODULE: // On second entry, set XSHUT pin high to allow this module to restart. - // On some modules, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V, but we can provide additional - // protection by enabling the pull-up resistor on the microcontroller - // instead of driving the output directly. + // I've observed that the device tends to randomly reset if the XSHUT + // pin is set high from a 5V arduino, even through a pullup resistor. + // Assume that there will be a pull-up on the XSHUT pin to +2.8V as + // recommended in the device datasheet. Then we only need to + // turn our output pin high-impedence (by making it an input) and the + // on-board pullup will do its job. // Ensure XSHUT is set for only one module at a time by using a // shared flag accessible to all device instances. if (!_addressConfigInProgress) { _addressConfigInProgress = true; - // Set XSHUT pin (if connected) to bring the module out of sleep mode. - if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, true); + // Configure XSHUT pin (if connected) to bring the module out of sleep mode. + if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, false); // Allow the module time to restart delayUntil(currentMicros+10000); _nextState = STATE_CONFIGUREADDRESS; From 8e8ae90030467b2cf5f0efc606f44a43cb59bd4a Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:09:40 +0000 Subject: [PATCH 618/870] Update I2CManager.cpp Add Real-time clock as a device address category (address 0x68). --- I2CManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/I2CManager.cpp b/I2CManager.cpp index 14e84b748..6d5db41ee 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -60,6 +60,8 @@ static const FSH * guessI2CDeviceType(uint8_t address) { return F("PWM"); else if (address >= 0x50 && address <= 0x5f) return F("EEPROM"); + else if (address == 0x68) + return F("Real-time clock"); else if (address >= 0x70 && address <= 0x77) return F("I2C Mux"); else From a405d36523ca6ce53bf376e1479ffb9b6bbe4b93 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:11:37 +0000 Subject: [PATCH 619/870] Rename class OLEDDisplay to HALDisplay. --- IO_OLEDDisplay.h => IO_HALDisplay.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) rename IO_OLEDDisplay.h => IO_HALDisplay.h (91%) diff --git a/IO_OLEDDisplay.h b/IO_HALDisplay.h similarity index 91% rename from IO_OLEDDisplay.h rename to IO_HALDisplay.h index 26c24b337..471ba9407 100644 --- a/IO_OLEDDisplay.h +++ b/IO_HALDisplay.h @@ -29,7 +29,7 @@ * * To install, use the following command in myHal.cpp: - * OLEDDisplay::create(address, width, height); + * HALDisplay::create(address, width, height); * * where address is the I2C address (0x3c or 0x3d), * width is the width in pixels of the display, and @@ -41,18 +41,20 @@ */ -#ifndef IO_OLEDDISPLAY_H -#define IO_OLEDDISPLAY_H +#ifndef IO_HALDisplay_H +#define IO_HALDisplay_H #include "IODevice.h" #include "DisplayInterface.h" #include "SSD1306Ascii.h" +#include "LiquidCrystal_I2C.h" #include "version.h" typedef SSD1306AsciiWire OLED; +typedef LiquidCrystal_I2C LiquidCrystal; template -class OLEDDisplay : public IODevice, public DisplayInterface { +class HALDisplay : public IODevice, public DisplayInterface { private: // Here we define the device-specific variables. uint8_t _height; // in pixels @@ -70,17 +72,17 @@ class OLEDDisplay : public IODevice, public DisplayInterface { DisplayInterface *_nextDisplay = NULL; public: - // Static function to handle "OLEDDisplay::create(...)" calls. + // Static function to handle "HALDisplay::create(...)" calls. static void create(I2CAddress i2cAddress, int width, int height) { - /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(0, i2cAddress, width, height); + /* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height); } static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { - /* if (checkNoOverlap(i2cAddress)) */ new OLEDDisplay(displayNo, i2cAddress, width, height); + /* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height); } protected: // Constructor - OLEDDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { _displayDriver = new T(i2cAddress, width, height); _I2CAddress = i2cAddress; _width = width; @@ -178,7 +180,7 @@ class OLEDDisplay : public IODevice, public DisplayInterface { // Display information about the device. void _display() { - DIAG(F("OLEDDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString()); + DIAG(F("HALDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString()); } ///////////////////////////////////////////////// @@ -238,4 +240,4 @@ class OLEDDisplay : public IODevice, public DisplayInterface { }; -#endif // IO_OLEDDISPLAY_H \ No newline at end of file +#endif // IO_HALDisplay_H \ No newline at end of file From ef85d5eabac3e1ec2ab63918e57700615f235856 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:28:16 +0000 Subject: [PATCH 620/870] Update version.h 4.2.18 --- I2CManager_STM32.h | 1 + IO_ExternalEEPROM.h | 141 +++++ IO_GPIOBase.h | 9 +- IO_TFTDisplay.h | 256 +++++++++ IO_Wire.h | 136 +++++ .../CommandStation-EX-Arch-v1-0.svg | 6 +- ST7735-TFT.h | 517 ++++++++++++++++++ platformio.ini | 13 +- version.h | 34 +- 9 files changed, 1084 insertions(+), 29 deletions(-) create mode 100644 IO_ExternalEEPROM.h create mode 100644 IO_TFTDisplay.h create mode 100644 IO_Wire.h create mode 100644 ST7735-TFT.h diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 79a372645..a55fd2efd 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -24,6 +24,7 @@ #include #include "I2CManager.h" +#include "I2CManager_NonBlocking.h" // to satisfy intellisense //#include //#include diff --git a/IO_ExternalEEPROM.h b/IO_ExternalEEPROM.h new file mode 100644 index 000000000..d2c90e504 --- /dev/null +++ b/IO_ExternalEEPROM.h @@ -0,0 +1,141 @@ +/* + * © 2023, 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 . + */ + +/* + * This device driver monitors the state of turnout objects and writes updates, + * on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte) + * EEPROM device connected via I2C. + * + * When the device is restarted, it repositions the turnouts in accordance + * with the last saved position. + * + * To create a device instance, + * IO_ExternalEEPROM::create(0, 0, i2cAddress); + * + * + */ + +#ifndef IO_EXTERNALEEPROM_H +#define IO_EXTERNALEEPROM_H + +#include "IODevice.h" +#include "I2CManager.h" +#include "Turnouts.h" + +class ExternalEEPROM : public IODevice { +private: + // Here we define the device-specific variables. + int _sizeInKBytes = 128; + Turnout *_turnout = 0; + int _lastTurnoutHash = 0; + I2CRB _rb; + uint8_t _buffer[32]; // 32 is max for Wire write + +public: + // Static function to handle "IO_ExampleSerial::create(...)" calls. + static void create(I2CAddress i2cAddress, int sizeInKBytes) { + if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes); + } + +protected: + // Constructor. + ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) { + _I2CAddress = i2cAddress; + _sizeInKBytes = sizeInKBytes; + + // Set up I2C structures. + _rb.setWriteParams(_I2CAddress, _buffer, 32); + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { + I2CManager.begin(); + I2CManager.setClock(1000000); // Max supported speed + + if (I2CManager.exists(_I2CAddress)) { + // Initialise or read contents of EEPROM + // and set turnout states accordingly. + // Read 32 bytes from address 0x0000. + I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0); + // Dump data + DIAG(F("EEPROM First 32 bytes:")); + for (int i=0; i<32; i+=8) + DIAG(F("%d: %x %x %x %x %x %x %x %x"), + i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3], + _buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]); + +#if defined(DIAG_IO) + _display(); +#endif + } else { + DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + } + } + + // Loop function to do background scanning of the turnouts + void _loop(unsigned long currentMicros) { + (void)currentMicros; // Suppress compiler warnings + + if (_rb.isBusy()) return; // Can't do anything until previous request has completed. + if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) { + // Device not responding, probably still writing data, so requeue request + I2CManager.queueRequest(&_rb); + return; + } + + if (_lastTurnoutHash != Turnout::turnoutlistHash) { + _lastTurnoutHash = Turnout::turnoutlistHash; + // Turnout list has changed, so pointer held from last run may be invalid + _turnout = 0; // Start at the beginning of the list again. +//#if defined(DIAG_IO) + DIAG(F("Turnout Hash Changed!")); +//#endif + } + + // Locate next turnout, or first one if there is no current one. + if (_turnout) + _turnout = _turnout->next(); + else + _turnout = Turnout::first(); + + // Retrieve turnout state + int turnoutID = _turnout->getId(); + int turnoutState = _turnout->isThrown(); + (void)turnoutID; // Suppress compiler warning + (void)turnoutState; // Suppress compiler warning + + // TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed. + // TODO: If it has, then initiate a write of the updated state to EEPROM + + delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256 + } + + // Display information about the device. + void _display() { + DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(), + _deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + +}; + +#endif // IO_EXTERNALEEPROM_H \ No newline at end of file diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 0f7019cb9..66b9ff6d4 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -86,6 +86,11 @@ GPIOBase::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress _hasCallback = true; // Add device to list of devices. addDevice(this); + + _portMode = 0; // default to input mode + _portPullup = -1; // default to pullup enabled + _portInputState = -1; // default to all inputs high (inactive) + _portInUse = 0; // No ports in use initially. } template @@ -100,10 +105,6 @@ void GPIOBase::_begin() { #if defined(DIAG_IO) _display(); #endif - _portMode = 0; // default to input mode - _portPullup = -1; // default to pullup enabled - _portInputState = -1; - _portInUse = 0; _setupDevice(); _deviceState = DEVSTATE_NORMAL; } else { diff --git a/IO_TFTDisplay.h b/IO_TFTDisplay.h new file mode 100644 index 000000000..1de7b8518 --- /dev/null +++ b/IO_TFTDisplay.h @@ -0,0 +1,256 @@ +/* + * © 2023, 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 . + */ + +/* + * This driver provides a way of driving a ST7735 TFT display through SCREEN(disp,line,"text"). + * If the line specified is off the screen then the text in the bottom line will be + * overwritten. There is however a special case that if line 255 is specified, + * the existing text will scroll up and the new line added to the bottom + * line of the screen. + * + * To install, use the following command in myHal.cpp: + + * TFTDisplay::create(address, width, height); + * + * where address is the I2C address (0x3c or 0x3d), + * width is the width in pixels of the display, and + * height is the height in pixels of the display. + * + */ + + +#ifndef IO_TFTDISPLAY_H +#define IO_TFTDDISPLAY_H + +#include "IODevice.h" +#include "DisplayInterface.h" +#include "version.h" + + +template +class TFTDisplay : public IODevice, public DisplayInterface { +private: + uint8_t _displayNo = 0; + // Here we define the device-specific variables. + uint8_t _height; // in pixels + uint8_t _width; // in pixels + T *_displayDriver; + uint8_t _rowNo = 0; // Row number being written by caller + uint8_t _colNo = 0; // Position in line being written by caller + uint8_t _numRows; + uint8_t _numCols; + char *_buffer = NULL; + uint8_t *_rowGeneration = NULL; + uint8_t *_lastRowGeneration = NULL; + uint8_t _rowNoToScreen = 0; + uint8_t _charPosToScreen = 0; + DisplayInterface *_nextDisplay = NULL; + uint8_t _selectedDisplayNo = 0; + +public: + // Static function to handle "TFTDisplay::create(...)" calls. + static void create(I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(0, i2cAddress, width, height); + } + static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { + /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(displayNo, i2cAddress, width, height); + } + +protected: + // Constructor + TFTDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { + _displayDriver = new T(i2cAddress, width, height); + _displayNo = displayNo; + _I2CAddress = i2cAddress; + _width = width; + _height = height; + _numCols = (_width+5) / 6; // character block 6 x 8, round up + _numRows = _height / 8; // Round down + + _charPosToScreen = _numCols; + + // Allocate arrays + _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); + _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + // Fill buffer with spaces + memset(_buffer, ' ', _numCols*_numRows); + + _displayDriver->clearNative(); + + // Is this the main display? + if (_displayNo == 0) { + // Set first two lines on screen + this->setRow(0, 0); + print(F("DCC-EX v")); + print(F(VERSION)); + setRow(0, 1); + print(F("Lic GPLv3")); + } + + // Store pointer to this object into CS display hook, so that we + // will intercept any subsequent calls to displayHandler methods. + // Make a note of the existing display reference, to that we can + // pass on anything we're not interested in. + _nextDisplay = DisplayInterface::displayHandler; + DisplayInterface::displayHandler = this; + + addDevice(this); + } + + + void screenUpdate() { + // Loop through the buffer and if a row has changed + // (rowGeneration[row] is changed) then start writing the + // characters from the buffer, one character per entry, + // to the screen until that row has been refreshed. + + // First check if the OLED driver is still busy from a previous + // call. If so, don't to anything until the next entry. + if (!_displayDriver->isBusy()) { + // Check if we've just done the end of a row or just started + if (_charPosToScreen >= _numCols) { + // Move to next line + if (++_rowNoToScreen >= _numRows) + _rowNoToScreen = 0; // Wrap to first row + + if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { + // Row content has changed, so start outputting it + _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; + _displayDriver->setRowNative(_rowNoToScreen); + _charPosToScreen = 0; // Prepare to output first character on next entry + } else { + // Row not changed, don't bother writing it. + } + } else { + // output character at current position + _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); + } + } + return; + } + + ///////////////////////////////////////////////// + // IODevice Class Member Overrides + ///////////////////////////////////////////////// + + // Device-specific initialisation + void _begin() override { + // Initialise device + if (_displayDriver->begin()) { + + DIAG(F("TFTDisplay installed on address %s as screen %d"), + _I2CAddress.toString(), _displayNo); + + // Force all rows to be redrawn + for (uint8_t row=0; row<_numRows; row++) + _rowGeneration[row]++; + + // Start with top line (looks better) + _rowNoToScreen = _numRows; + _charPosToScreen = _numCols; + } + } + + void _loop(unsigned long) override { + screenUpdate(); + } + + ///////////////////////////////////////////////// + // DisplayInterface functions + // + ///////////////////////////////////////////////// + +public: + void loop() override { + screenUpdate(); + if (_nextDisplay) + _nextDisplay->loop(); // continue to next display + return; + } + + // Position on nominated line number (0 to number of lines -1) + // Clear the line in the buffer ready for updating + // The displayNo referenced here is remembered and any following + // calls to write() will be directed to that display. + void setRow(uint8_t displayNo, byte line) override { + _selectedDisplayNo = displayNo; + if (displayNo == _displayNo) { + if (line == 255) { + // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - + // scroll the contents of the buffer and put the new line + // at the bottom of the screen + for (int row=1; row<_numRows; row++) { + strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); + _rowGeneration[row-1]++; + } + line = _numRows-1; + } else if (line >= _numRows) + line = _numRows - 1; // Overwrite bottom line. + + _rowNo = line; + // Fill line with blanks + for (_colNo = 0; _colNo < _numCols; _colNo++) + _buffer[_rowNo*_numCols+_colNo] = ' '; + _colNo = 0; + // Mark that the buffer has been touched. It will be + // sent to the screen on the next loop entry, by which time + // the line should have been written to the buffer. + _rowGeneration[_rowNo]++; + + } + if (_nextDisplay) + _nextDisplay->setRow(displayNo, line); // Pass to next display + + } + + // Write one character to the screen referenced in the last setRow() call. + size_t write(uint8_t c) override { + if (_selectedDisplayNo == _displayNo) { + // Write character to buffer (if there's space) + if (_colNo < _numCols) { + _buffer[_rowNo*_numCols+_colNo++] = c; + } + } + if (_nextDisplay) + _nextDisplay->write(c); + return 1; + } + + // Write blanks to all of the screen (blocks until complete) + void clear (uint8_t displayNo) override { + if (displayNo == _displayNo) { + // Clear buffer + for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { + setRow(displayNo, _rowNo); + } + _rowNo = 0; + } + if (_nextDisplay) + _nextDisplay->clear(displayNo); // Pass to next display + } + + // Display information about the device. + void _display() { + DIAG(F("TFTDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); + } + +}; + +#endif // IO_TFTDDISPLAY_H \ No newline at end of file diff --git a/IO_Wire.h b/IO_Wire.h new file mode 100644 index 000000000..624c911fb --- /dev/null +++ b/IO_Wire.h @@ -0,0 +1,136 @@ +/* + * © 2023, 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 purpose of this module is to provide an interface to the DCC + * I2CManager that is compatible with code written for the Arduino + * 'Wire' interface. + * + * To use it, just replace + * #include "Wire.h" or #include + * with + * #include "IO_Wire.h" + * + * Note that the CS only supports I2C master mode, so the calls related to + * slave mode are not implemented here. + * + */ + +#ifndef IO_WIRE +#define IO_WIRE + +#include "IODevice.h" + +#ifndef I2C_USE_WIRE + +class IO_Wire : public IODevice, public Stream { +public: + IO_Wire() { + addDevice(this); + }; + void begin() { + I2CManager.begin(); + } + void setClock(uint32_t speed) { + I2CManager.setClock(speed); + } + void beginTransmission(uint8_t address) { + i2cAddress = address; + outputLength = 0; + } + size_t write(byte value) override { + if (outputLength < sizeof(outputBuffer)) { + outputBuffer[outputLength++] = value; + return 1; + } else + return 0; + } + size_t write(const uint8_t *buffer, size_t size) override { + for (size_t i=0; i +#endif +#endif \ No newline at end of file diff --git a/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg b/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg index b99995d4d..481b97091 100644 --- a/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg +++ b/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.svg @@ -485,10 +485,10 @@ Accessories (Output.cpp) - + Process.14 - Other Utilities (LCDDisplay.cpp) + Other Utilities (Display.cpp) @@ -522,7 +522,7 @@ Other Utilities(LCDDisplay.cpp) + x="14.29" dy="1.2em" class="st5">(Display.cpp) Dynamic connector diff --git a/ST7735-TFT.h b/ST7735-TFT.h new file mode 100644 index 000000000..5893d3ca1 --- /dev/null +++ b/ST7735-TFT.h @@ -0,0 +1,517 @@ +/* Tiny TFT Graphics Library v5 - see http://www.technoblogy.com/show?3WAI + David Johnson-Davies - www.technoblogy.com - 26th October 2022 + + CC BY 4.0 + Licensed under a Creative Commons Attribution 4.0 International license: + http://creativecommons.org/licenses/by/4.0/ +*/ + +#include "FSH.h" +#include "DisplayInterface.h" + + +#if defined(MEGATINYCORE) +// ATtiny402/412 PORTA positions. Change these for the chip you're using +int const dc = 7; +int const mosi = 1; +int const sck = 3; +int const cs = 6; + +// ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port +#define PORT_TOGGLE(x) PORTA.OUTTGL = (x) +#define PORT_LOW(x) PORTA.OUTCLR = (x) +#define PORT_HIGH(x) PORTA.OUTSET = (x) +#define PORT_OUTPUT(x) PORTA.DIRSET = (x) + +#else +// ATtiny45/85 PORTB positions. Change these for the chip you're using +int const dc = 0; +int const mosi = 1; +int const sck = 2; +int const cs = 3; + +// Classic ATtiny port manipulations - assumes all pins in same port +#define PORT_TOGGLE(x) PINB = (x) +#define PORT_LOW(x) PORTB = PORTB & ~((x)); +#define PORT_HIGH(x) PORTB = PORTB | ((x)) +#define PORT_OUTPUT(x) DDRB = (x) + +#endif + +// Display parameters - uncomment the line for the one you want to use + +// Adafruit 1.44" 128x128 display +// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; + +// AliExpress 1.44" 128x128 display +// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; + +// Adafruit 0.96" 160x80 display +// int const xsize = 160, ysize = 80, xoff = 0, yoff = 24, invert = 0, rotate = 6, bgr = 0; + +// AliExpress 0.96" 160x80 display +// int const xsize = 160, ysize = 80, xoff = 1, yoff = 26, invert = 1, rotate = 0, bgr = 1; + +// Adafruit 1.8" 160x128 display +// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; + +// AliExpress 1.8" 160x128 display (red PCB) +int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; + +// AliExpress 1.8" 160x128 display (blue PCB) +// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0; + +// Adafruit 1.14" 240x135 display +// int const xsize = 240, ysize = 135, xoff = 40, yoff = 53, invert = 1, rotate = 6, bgr = 0; + +// AliExpress 1.14" 240x135 display +// int const xsize = 240, ysize = 135, xoff = 40, yoff = 52, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 1.3" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// Adafruit 1.54" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// AliExpress 1.54" 240x240 display +// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; + +// Adafruit 1.9" 320x170 display +// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; + +// AliExpress 1.9" 320x170 display +// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 1.47" 320x172 rounded rectangle display +// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; + +// AliExpress 1.47" 320x172 rounded rectangle display +// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 2.0" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 6, bgr = 0; + +// AliExpress 2.0" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0; + +// Adafruit 2.2" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 4, bgr = 1; + +// AliExpress 2.4" 320x240 display +// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1; + +// Character set for text - stored in program memory +const uint8_t CharMap[96][6] FLASH = { +{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, +{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, +{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, +{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, +{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, +{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, +{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, +{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, +{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, +{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, +{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, +{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, +{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, +{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, +{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, +{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, +{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, +{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, +{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, +{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, +{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, +{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, +{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, +{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, +{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, +{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, +{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, +{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, +{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, +{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, +{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, +{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, +{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, +{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, +{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, +{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, +{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, +{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, +{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, +{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, +{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, +{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, +{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, +{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, +{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, +{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, +{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, +{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, +{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, +{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, +{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, +{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, +{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, +{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, +{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, +{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, +{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, +{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, +{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, +{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, +{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, +{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, +{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, +{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, +{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, +{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, +{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, +{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, +{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, +{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, +{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, +{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, +{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, +{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, +{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, +{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, +{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, +{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, +{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, +{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, +{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, +{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, +{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, +{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, +{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, +{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, +{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, +{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, +{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, +{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, +{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, +{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // degree symbol = '~' +{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 } +}; + +// TFT colour display ********************************************** + +int const CASET = 0x2A; // Define column address +int const RASET = 0x2B; // Define row address +int const RAMWR = 0x2C; // Write to display RAM + +int const White = 0xFFFF; +int const Black = 0; + +// Current plot position and colours +int xpos, ypos; +int fore = White; +int back = Black; +int scale = 1; // Text scale + +// Send a byte to the display + +void Data (uint8_t d) { + for (uint8_t bit = 0x80; bit; bit >>= 1) { + PORT_TOGGLE(1<>8); Data(d1); Data(d2>>8); Data(d2); +} + +void InitDisplay () { + PORT_OUTPUT(1<>3; +} + +// Move current plot position to x,y +void MoveTo (int x, int y) { + xpos = x; ypos = y; +} + +// Plot point at x,y +void PlotPoint (int x, int y) { + PORT_TOGGLE(1<>8); Data(fore & 0xff); + PORT_TOGGLE(1< -dy) { err = err - dy; xpos = xpos + sx; } + if (e2 < dx) { err = err + dx; ypos = ypos + sy; } + } +} + +void FillRect (int w, int h) { + PORT_TOGGLE(1<>8; + uint8_t lo = fore & 0xff; + for (int i=0; i= y) { + MoveTo(x1-x, y1+y); FillRect(x<<1, 1); + MoveTo(x1-y, y1+x); FillRect(y<<1, 1); + MoveTo(x1-y, y1-x); FillRect(y<<1, 1); + MoveTo(x1-x, y1-y); FillRect(x<<1, 1); + if (err > 0) { + x = x - 1; dx = dx + 2; + err = err - (radius<<1) + dx; + } else { + y = y + 1; err = err + dy; + dy = dy + 2; + } + } + xpos = x1; ypos = y1; +} + +void DrawCircle (int radius) { + int x1 = xpos, y1 = ypos, dx = 1, dy = 1; + int x = radius - 1, y = 0; + int err = dx - (radius<<1); + while (x >= y) { + PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y); + PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x); + PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x); + PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y); + if (err > 0) { + x = x - 1; dx = dx + 2; + err = err - (radius<<1) + dx; + } else { + y = y + 1; err = err + dy; + dy = dy + 2; + } + } +} + +// Plot an ASCII character with bottom left corner at x,y +void PlotChar (char c) { + int colour; + PORT_TOGGLE(1<>(7-yy) & 1) colour = fore; else colour = back; + for (int yr=0; yr>8); Data(colour & 0xFF); + } + } + } + } + PORT_TOGGLE(1<0; d = d/10) { + char j = (n/d) % 10; + if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; } + } +} + +void TestChart () { + DrawRect(xsize, ysize); + scale = 8; + fore = Colour(255, 0, 0); + MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F'); + scale = 1; +} + +// Demos ********************************************** + +void BarChart () { + int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; + MoveTo(x0+(w-x1-90)/2+x1, y0+h-8); PlotText(PSTR("Sensor Readings")); + // Horizontal axis + int xinc = (w-x1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+w-1, y0+y1); + for (int i=0; i<=20; i=i+4) { + int mark = x1+i*xinc; + MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); + // Draw histogram + if (i != 20) { + int bar = xinc*4/3; + for (int b=2; b>=0; b--) { + fore = Colour(255, 127*b, 0); // Red, Orange, Yellow + MoveTo(x0+mark+bar*b-b+1, y0+y1+1); FillRect(bar, 5+random(h-y1-20)); + } + fore = White; + } + if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); + PlotInt(i); + } + // Vertical axis + int yinc = (h-y1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); + for (int i=0; i<=20; i=i+5) { + int mark = y1+i*yinc; + MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); + if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); + PlotInt(i); + } +} + +void Waterfall () { + int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; + int factor = 5160/h*10; + MoveTo(x0+(w-x1-60)/2+x1, y0+h-8); PlotText(PSTR("Luminance")); + // Horizontal axis + int xinc = (w-x1-15)/30; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+xinc*20, y0+y1); + for (int i=0; i<=20; i=i+5) { + int mark = x1+i*xinc; + MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); + if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); + PlotInt(i); + } + // Vertical axis + int yinc = (h-y1)/20; + MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); + for (int i=0; i<=20; i=i+5) { + int mark = y1+i*yinc; + MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); + if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); + PlotInt(i); + } + // Diagonal axis + yinc = xinc/2; + // MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+10*xinc, y0+y1+10*xinc); + MoveTo(x0+x1+20*xinc, y0+y1); DrawTo(x0+x1+30*xinc, y0+y1+10*xinc); + for (int i=0; i<=20; i=i+5) { + MoveTo(x0+x1+20*xinc+i*xinc/2, y0+y1+i*xinc/2); + DrawTo(x0+x1+20*xinc+i*xinc/2+3, y0+y1+i*xinc/2); + MoveTo(x0+x1+20*xinc+i*xinc/2+6, y0+y1+i*xinc/2-4); PlotInt(i); + } + // Plot data + for (int y=20; y>=0; y--) { + for (int i=0; i<=20; i++) { + int fn0 = 180-(i-10)*(i-10)-(y-10)*(y-10); + int fn1 = 180-(i+1-10)*(i+1-10)-(y-10)*(y-10); + fore = Colour(255, 255, 0); + MoveTo(x0+x1+y*yinc+i*xinc, y0+y1+y*yinc+fn0*fn0/factor); + DrawTo(x0+x1+y*yinc+(i+1)*xinc, y0+y1+y*yinc+fn1*fn1/factor); + fore = White; + } + } +} + +// Setup ********************************************** + +void setup() { + InitDisplay(); + ClearDisplay(); + DisplayOn(); + MoveTo(0,0); + // TestChart(); +} + +void loop () { + BarChart(); + // Waterfall(); + for (;;); +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 40dc89ee8..1fc729172 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,6 +31,7 @@ include_dir = . [env] build_flags = -Wall -Wextra monitor_filters = time +; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0 [env:samd21-dev-usb] platform = atmelsam @@ -59,7 +60,7 @@ framework = arduino lib_deps = ${env.lib_deps} monitor_speed = 115200 monitor_echo = yes -build_flags = -std=c++17 -DI2C_EXTENDED_ADDRESS ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO +build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO [env:mega2560-debug] platform = atmelavr @@ -71,7 +72,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DI2C_EXTENDED_ADDRESS -DDIAG_IO -DDIAG_LOOPTIMES +build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES [env:mega2560-no-HAL] platform = atmelavr @@ -83,7 +84,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -DIO_NO_HAL +build_flags = -DIO_NO_HAL [env:mega2560-I2C-wire] platform = atmelavr @@ -107,7 +108,7 @@ lib_deps = SPI monitor_speed = 115200 monitor_echo = yes -build_flags = -mcall-prologues +build_flags = ; -DDIAG_LOOPTIMES [env:mega328] platform = atmelavr @@ -143,7 +144,7 @@ lib_deps = monitor_speed = 115200 monitor_echo = yes upload_speed = 19200 -build_flags = -DDIAG_IO +build_flags = [env:uno] platform = atmelavr @@ -187,7 +188,7 @@ platform = ststm32 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable +build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO monitor_speed = 115200 monitor_echo = yes diff --git a/version.h b/version.h index d790206f9..35058dac3 100644 --- a/version.h +++ b/version.h @@ -5,22 +5,24 @@ #define VERSION "4.2.18" -// 4.2.18 I2C Multiplexer support through Extended Addresses, -// added for Wire, 4209 and AVR I2C drivers. -// I2C retries when fail. -// I2C timeout handling and recovery completed. -// I2C SAMD Driver Read code completed. -// PCF8575 I2C GPIO driver added. -// EX-RAIL ANOUT function for triggering analogue -// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). -// Installable HAL OLED Display Driver, with -// support for multiple displays. -// Layered HAL Drivers PCA9685pwm and Servo added for -// (1) native PWM on PCA9685 module and -// (2) animations of servo movement via PCA9685pwm. -// This is intended to support EXIOExpander and also -// replace the existing PCA9685 driver. -// Add to reinitialise failed drivers. +// 4.2.18 - I2C Multiplexer support through Extended Addresses, +// added for Wire, 4209 and AVR I2C drivers. +// - I2C retries when an operation fails. +// - I2C timeout handling and recovery completed. +// - I2C SAMD Driver Read code completed. +// - PCF8575 I2C GPIO driver added. +// - EX-RAIL ANOUT function for triggering analogue +// HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// - Installable HALDisplay Driver, with support +// for multiple displays. +// - Layered HAL Drivers PCA9685pwm and Servo added for +// native PWM on PCA9685 module and +// for animations of servo movement via PCA9685pwm. +// This is intended to support EXIOExpander and also +// replace the existing PCA9685 driver. +// - Add to reinitialise failed drivers. +// - Add UserAddin facility to allow a user-specific C++ +// function to be added in myHal.cpp. // 4.2.17 LCN bugfix // 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander From c52b4a60a56cf84ff014b2433348ef06e8747c77 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 22 Feb 2023 21:34:52 +0000 Subject: [PATCH 621/870] Update IO_HALDisplay.h Update comments. --- IO_HALDisplay.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/IO_HALDisplay.h b/IO_HALDisplay.h index 471ba9407..3919bbfb9 100644 --- a/IO_HALDisplay.h +++ b/IO_HALDisplay.h @@ -28,16 +28,23 @@ * line of the screen. * * To install, use the following command in myHal.cpp: - - * HALDisplay::create(address, width, height); + * + * HALDisplay::create(address, width, height); * - * where address is the I2C address (0x3c or 0x3d), - * width is the width in pixels of the display, and - * height is the height in pixels of the display. + * where address is the I2C address of the OLED display (0x3c or 0x3d), + * width is the width in pixels, and height is the height in pixels. * * Valid width and height are 128x32 (SSD1306 controller), * 128x64 (SSD1306) and 132x64 (SH1106). The driver uses * a 5x7 character set in a 6x8 pixel cell. + * + * OR + * + * HALDisplay::create(address, width, height); + * + * where address is the I2C address of the LCD display (0x27 typically), + * width is the width in characters (16 or 20 typically), + * and height is the height in characters (2 or 4 typically). */ From ce3885b125ee8d883b334a800a0bd6214f1c71d2 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 10:36:42 +0000 Subject: [PATCH 622/870] Update DisplayInterface.h Remove compiler warning --- DisplayInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DisplayInterface.h b/DisplayInterface.h index f1d598d59..c5d8e96c6 100644 --- a/DisplayInterface.h +++ b/DisplayInterface.h @@ -80,7 +80,7 @@ class DisplayInterface : public Print { // The following are overridden within the specific device class virtual void begin() {}; virtual size_t _write(uint8_t c) { (void)c; return 0; }; - virtual void _setRow(uint8_t line) {} + virtual void _setRow(uint8_t line) { (void)line; } virtual void _clear() {} virtual void _refresh() {} virtual void _displayLoop() {} From b7483d99e94c5287df227281d8cee83d63ee6752 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 11:13:05 +0000 Subject: [PATCH 623/870] Update version.h --- version.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index 35058dac3..f846e0d89 100644 --- a/version.h +++ b/version.h @@ -13,6 +13,8 @@ // - PCF8575 I2C GPIO driver added. // - EX-RAIL ANOUT function for triggering analogue // HAL drivers (e.g. analogue outputs, DFPlayer, PWM). +// - EX-RAIL SCREEN function for writing to screens other +// than the primary one. // - Installable HALDisplay Driver, with support // for multiple displays. // - Layered HAL Drivers PCA9685pwm and Servo added for @@ -21,8 +23,13 @@ // This is intended to support EXIOExpander and also // replace the existing PCA9685 driver. // - Add to reinitialise failed drivers. -// - Add UserAddin facility to allow a user-specific C++ -// function to be added in myHal.cpp. +// - Add UserAddin facility to allow a user-written C++ function to be +// declared in myHal.cpp, to be called at a user-specified frequency. +// - Add ability to configure clock speed of PCA9685 drivers +// (to allow flicker-free LED control). +// - Improve stability of VL53L0X driver when XSHUT pin connected. +// - Enable DCC high accuracy mode for STM32 on standard motor shield (pins D12/D13). +// - Incorporate improvements to ADC scanning performance (courtesy of HABA). // 4.2.17 LCN bugfix // 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software // 4.2.15 Add basic experimental PWM support to EX-IOExpander From 5e9ae4a0ace9f8947288ff8f7361f77e3ddf4f0f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:16:29 +0000 Subject: [PATCH 624/870] Remove redundant commented out code. --- I2CManager_AVR.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/I2CManager_AVR.h b/I2CManager_AVR.h index e75b32fc7..567fdc301 100644 --- a/I2CManager_AVR.h +++ b/I2CManager_AVR.h @@ -223,10 +223,7 @@ void I2CManagerClass::I2C_handleInterrupt() { #if defined(I2C_USE_INTERRUPTS) ISR(TWI_vect) { - // pinMode(2,OUTPUT); - // digitalWrite(2,1); I2CManager.handleInterrupt(); - // digitalWrite(2,0); } #endif From 45e3e3d1855fa1da3e4151a64f10f33e61eab692 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:17:15 +0000 Subject: [PATCH 625/870] Update IO_RotaryEncoder.h Update to support extended I2C addresses. --- IO_RotaryEncoder.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 9cf4e65e3..b271b48ea 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -45,14 +45,14 @@ class RotaryEncoder : public IODevice { public: // Constructor - RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){ + RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ _firstVpin = firstVpin; _nPins = nPins; - _I2CAddress = I2CAddress; + _I2CAddress = i2cAddress; addDevice(this); } - static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) { - if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress); + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); } private: @@ -108,7 +108,6 @@ class RotaryEncoder : public IODevice { (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } - uint8_t _I2CAddress; int8_t _position; int8_t _previousPosition = 0; uint8_t _versionBuffer[3]; From eddd6382d9c90a5f6b752db5b969f3f1b131e96e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:23:08 +0000 Subject: [PATCH 626/870] Update IO_EXFastclock.h Update to support extended I2C addresses. --- IO_EXFastclock.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/IO_EXFastclock.h b/IO_EXFastclock.h index 923f922a0..5ed237e39 100644 --- a/IO_EXFastclock.h +++ b/IO_EXFastclock.h @@ -40,24 +40,24 @@ bool FAST_CLOCK_EXISTS = true; class EXFastClock : public IODevice { public: // Constructor - EXFastClock(uint8_t I2CAddress){ - _I2CAddress = I2CAddress; + EXFastClock(I2CAddress i2cAddress){ + _I2CAddress = i2cAddress; addDevice(this); } -static void create(uint8_t _I2CAddress) { +static void create(I2CAddress i2cAddress) { DIAG(F("Checking for Clock")); // Start by assuming we will find the clock // Check if specified I2C address is responding (blocking operation) // Returns I2C_STATUS_OK (0) if OK, or error code. - uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress); + uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress); DIAG(F("Clock check result - %d"), _checkforclock); // XXXX change thistosave2 bytes if (_checkforclock == 0) { FAST_CLOCK_EXISTS = true; - //DIAG(F("I2C Fast Clock found at %s"), _I2CAddress.toString()); - new EXFastClock(_I2CAddress); + //DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString()); + new EXFastClock(i2cAddress); } else { FAST_CLOCK_EXISTS = false; From a23f5839b75d2e0cacd7485640823cffed37cc3c Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:24:42 +0000 Subject: [PATCH 627/870] Update DisplayInterface.cpp Remove dummy display driver object - it's unnecessary now as a null pointer is benign in this context. --- DisplayInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DisplayInterface.cpp b/DisplayInterface.cpp index f2c144e0f..81da7a96b 100644 --- a/DisplayInterface.cpp +++ b/DisplayInterface.cpp @@ -21,7 +21,7 @@ #include "DisplayInterface.h" -// Install null display driver initially - will be replaced if required. -DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface(); +// Start of chain of display handlers. +DisplayInterface *DisplayInterface::_displayHandler = NULL; uint8_t DisplayInterface::_selectedDisplayNo = 255; From ddfc67d2e3c4ebc8c1c94fa569f57e6431bfc131 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:52:13 +0000 Subject: [PATCH 628/870] Remove files that were incorrectly included in the merge. --- IO_TFTDisplay.h | 256 ------------------------ IO_Wire.h | 136 ------------- ST7735-TFT.h | 517 ------------------------------------------------ 3 files changed, 909 deletions(-) delete mode 100644 IO_TFTDisplay.h delete mode 100644 IO_Wire.h delete mode 100644 ST7735-TFT.h diff --git a/IO_TFTDisplay.h b/IO_TFTDisplay.h deleted file mode 100644 index 1de7b8518..000000000 --- a/IO_TFTDisplay.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * © 2023, 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 . - */ - -/* - * This driver provides a way of driving a ST7735 TFT display through SCREEN(disp,line,"text"). - * If the line specified is off the screen then the text in the bottom line will be - * overwritten. There is however a special case that if line 255 is specified, - * the existing text will scroll up and the new line added to the bottom - * line of the screen. - * - * To install, use the following command in myHal.cpp: - - * TFTDisplay::create(address, width, height); - * - * where address is the I2C address (0x3c or 0x3d), - * width is the width in pixels of the display, and - * height is the height in pixels of the display. - * - */ - - -#ifndef IO_TFTDISPLAY_H -#define IO_TFTDDISPLAY_H - -#include "IODevice.h" -#include "DisplayInterface.h" -#include "version.h" - - -template -class TFTDisplay : public IODevice, public DisplayInterface { -private: - uint8_t _displayNo = 0; - // Here we define the device-specific variables. - uint8_t _height; // in pixels - uint8_t _width; // in pixels - T *_displayDriver; - uint8_t _rowNo = 0; // Row number being written by caller - uint8_t _colNo = 0; // Position in line being written by caller - uint8_t _numRows; - uint8_t _numCols; - char *_buffer = NULL; - uint8_t *_rowGeneration = NULL; - uint8_t *_lastRowGeneration = NULL; - uint8_t _rowNoToScreen = 0; - uint8_t _charPosToScreen = 0; - DisplayInterface *_nextDisplay = NULL; - uint8_t _selectedDisplayNo = 0; - -public: - // Static function to handle "TFTDisplay::create(...)" calls. - static void create(I2CAddress i2cAddress, int width = 128, int height=64) { - /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(0, i2cAddress, width, height); - } - static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) { - /* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(displayNo, i2cAddress, width, height); - } - -protected: - // Constructor - TFTDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { - _displayDriver = new T(i2cAddress, width, height); - _displayNo = displayNo; - _I2CAddress = i2cAddress; - _width = width; - _height = height; - _numCols = (_width+5) / 6; // character block 6 x 8, round up - _numRows = _height / 8; // Round down - - _charPosToScreen = _numCols; - - // Allocate arrays - _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); - _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); - _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); - // Fill buffer with spaces - memset(_buffer, ' ', _numCols*_numRows); - - _displayDriver->clearNative(); - - // Is this the main display? - if (_displayNo == 0) { - // Set first two lines on screen - this->setRow(0, 0); - print(F("DCC-EX v")); - print(F(VERSION)); - setRow(0, 1); - print(F("Lic GPLv3")); - } - - // Store pointer to this object into CS display hook, so that we - // will intercept any subsequent calls to displayHandler methods. - // Make a note of the existing display reference, to that we can - // pass on anything we're not interested in. - _nextDisplay = DisplayInterface::displayHandler; - DisplayInterface::displayHandler = this; - - addDevice(this); - } - - - void screenUpdate() { - // Loop through the buffer and if a row has changed - // (rowGeneration[row] is changed) then start writing the - // characters from the buffer, one character per entry, - // to the screen until that row has been refreshed. - - // First check if the OLED driver is still busy from a previous - // call. If so, don't to anything until the next entry. - if (!_displayDriver->isBusy()) { - // Check if we've just done the end of a row or just started - if (_charPosToScreen >= _numCols) { - // Move to next line - if (++_rowNoToScreen >= _numRows) - _rowNoToScreen = 0; // Wrap to first row - - if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { - // Row content has changed, so start outputting it - _lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen]; - _displayDriver->setRowNative(_rowNoToScreen); - _charPosToScreen = 0; // Prepare to output first character on next entry - } else { - // Row not changed, don't bother writing it. - } - } else { - // output character at current position - _displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]); - } - } - return; - } - - ///////////////////////////////////////////////// - // IODevice Class Member Overrides - ///////////////////////////////////////////////// - - // Device-specific initialisation - void _begin() override { - // Initialise device - if (_displayDriver->begin()) { - - DIAG(F("TFTDisplay installed on address %s as screen %d"), - _I2CAddress.toString(), _displayNo); - - // Force all rows to be redrawn - for (uint8_t row=0; row<_numRows; row++) - _rowGeneration[row]++; - - // Start with top line (looks better) - _rowNoToScreen = _numRows; - _charPosToScreen = _numCols; - } - } - - void _loop(unsigned long) override { - screenUpdate(); - } - - ///////////////////////////////////////////////// - // DisplayInterface functions - // - ///////////////////////////////////////////////// - -public: - void loop() override { - screenUpdate(); - if (_nextDisplay) - _nextDisplay->loop(); // continue to next display - return; - } - - // Position on nominated line number (0 to number of lines -1) - // Clear the line in the buffer ready for updating - // The displayNo referenced here is remembered and any following - // calls to write() will be directed to that display. - void setRow(uint8_t displayNo, byte line) override { - _selectedDisplayNo = displayNo; - if (displayNo == _displayNo) { - if (line == 255) { - // LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") - - // scroll the contents of the buffer and put the new line - // at the bottom of the screen - for (int row=1; row<_numRows; row++) { - strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols); - _rowGeneration[row-1]++; - } - line = _numRows-1; - } else if (line >= _numRows) - line = _numRows - 1; // Overwrite bottom line. - - _rowNo = line; - // Fill line with blanks - for (_colNo = 0; _colNo < _numCols; _colNo++) - _buffer[_rowNo*_numCols+_colNo] = ' '; - _colNo = 0; - // Mark that the buffer has been touched. It will be - // sent to the screen on the next loop entry, by which time - // the line should have been written to the buffer. - _rowGeneration[_rowNo]++; - - } - if (_nextDisplay) - _nextDisplay->setRow(displayNo, line); // Pass to next display - - } - - // Write one character to the screen referenced in the last setRow() call. - size_t write(uint8_t c) override { - if (_selectedDisplayNo == _displayNo) { - // Write character to buffer (if there's space) - if (_colNo < _numCols) { - _buffer[_rowNo*_numCols+_colNo++] = c; - } - } - if (_nextDisplay) - _nextDisplay->write(c); - return 1; - } - - // Write blanks to all of the screen (blocks until complete) - void clear (uint8_t displayNo) override { - if (displayNo == _displayNo) { - // Clear buffer - for (_rowNo = 0; _rowNo < _numRows; _rowNo++) { - setRow(displayNo, _rowNo); - } - _rowNo = 0; - } - if (_nextDisplay) - _nextDisplay->clear(displayNo); // Pass to next display - } - - // Display information about the device. - void _display() { - DIAG(F("TFTDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString()); - } - -}; - -#endif // IO_TFTDDISPLAY_H \ No newline at end of file diff --git a/IO_Wire.h b/IO_Wire.h deleted file mode 100644 index 624c911fb..000000000 --- a/IO_Wire.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * © 2023, 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 purpose of this module is to provide an interface to the DCC - * I2CManager that is compatible with code written for the Arduino - * 'Wire' interface. - * - * To use it, just replace - * #include "Wire.h" or #include - * with - * #include "IO_Wire.h" - * - * Note that the CS only supports I2C master mode, so the calls related to - * slave mode are not implemented here. - * - */ - -#ifndef IO_WIRE -#define IO_WIRE - -#include "IODevice.h" - -#ifndef I2C_USE_WIRE - -class IO_Wire : public IODevice, public Stream { -public: - IO_Wire() { - addDevice(this); - }; - void begin() { - I2CManager.begin(); - } - void setClock(uint32_t speed) { - I2CManager.setClock(speed); - } - void beginTransmission(uint8_t address) { - i2cAddress = address; - outputLength = 0; - } - size_t write(byte value) override { - if (outputLength < sizeof(outputBuffer)) { - outputBuffer[outputLength++] = value; - return 1; - } else - return 0; - } - size_t write(const uint8_t *buffer, size_t size) override { - for (size_t i=0; i -#endif -#endif \ No newline at end of file diff --git a/ST7735-TFT.h b/ST7735-TFT.h deleted file mode 100644 index 5893d3ca1..000000000 --- a/ST7735-TFT.h +++ /dev/null @@ -1,517 +0,0 @@ -/* Tiny TFT Graphics Library v5 - see http://www.technoblogy.com/show?3WAI - David Johnson-Davies - www.technoblogy.com - 26th October 2022 - - CC BY 4.0 - Licensed under a Creative Commons Attribution 4.0 International license: - http://creativecommons.org/licenses/by/4.0/ -*/ - -#include "FSH.h" -#include "DisplayInterface.h" - - -#if defined(MEGATINYCORE) -// ATtiny402/412 PORTA positions. Change these for the chip you're using -int const dc = 7; -int const mosi = 1; -int const sck = 3; -int const cs = 6; - -// ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port -#define PORT_TOGGLE(x) PORTA.OUTTGL = (x) -#define PORT_LOW(x) PORTA.OUTCLR = (x) -#define PORT_HIGH(x) PORTA.OUTSET = (x) -#define PORT_OUTPUT(x) PORTA.DIRSET = (x) - -#else -// ATtiny45/85 PORTB positions. Change these for the chip you're using -int const dc = 0; -int const mosi = 1; -int const sck = 2; -int const cs = 3; - -// Classic ATtiny port manipulations - assumes all pins in same port -#define PORT_TOGGLE(x) PINB = (x) -#define PORT_LOW(x) PORTB = PORTB & ~((x)); -#define PORT_HIGH(x) PORTB = PORTB | ((x)) -#define PORT_OUTPUT(x) DDRB = (x) - -#endif - -// Display parameters - uncomment the line for the one you want to use - -// Adafruit 1.44" 128x128 display -// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; - -// AliExpress 1.44" 128x128 display -// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1; - -// Adafruit 0.96" 160x80 display -// int const xsize = 160, ysize = 80, xoff = 0, yoff = 24, invert = 0, rotate = 6, bgr = 0; - -// AliExpress 0.96" 160x80 display -// int const xsize = 160, ysize = 80, xoff = 1, yoff = 26, invert = 1, rotate = 0, bgr = 1; - -// Adafruit 1.8" 160x128 display -// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; - -// AliExpress 1.8" 160x128 display (red PCB) -int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1; - -// AliExpress 1.8" 160x128 display (blue PCB) -// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0; - -// Adafruit 1.14" 240x135 display -// int const xsize = 240, ysize = 135, xoff = 40, yoff = 53, invert = 1, rotate = 6, bgr = 0; - -// AliExpress 1.14" 240x135 display -// int const xsize = 240, ysize = 135, xoff = 40, yoff = 52, invert = 1, rotate = 0, bgr = 0; - -// Adafruit 1.3" 240x240 display -// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; - -// Adafruit 1.54" 240x240 display -// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; - -// AliExpress 1.54" 240x240 display -// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0; - -// Adafruit 1.9" 320x170 display -// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; - -// AliExpress 1.9" 320x170 display -// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0; - -// Adafruit 1.47" 320x172 rounded rectangle display -// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; - -// AliExpress 1.47" 320x172 rounded rectangle display -// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0; - -// Adafruit 2.0" 320x240 display -// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 6, bgr = 0; - -// AliExpress 2.0" 320x240 display -// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0; - -// Adafruit 2.2" 320x240 display -// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 4, bgr = 1; - -// AliExpress 2.4" 320x240 display -// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1; - -// Character set for text - stored in program memory -const uint8_t CharMap[96][6] FLASH = { -{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, -{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, -{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, -{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, -{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, -{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, -{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, -{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, -{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, -{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, -{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, -{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, -{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, -{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, -{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, -{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, -{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, -{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, -{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, -{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, -{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, -{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, -{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, -{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, -{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, -{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, -{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 }, -{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 }, -{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, -{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, -{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, -{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, -{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, -{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, -{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, -{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, -{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, -{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, -{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, -{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, -{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, -{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, -{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, -{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, -{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, -{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, -{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, -{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, -{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, -{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, -{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, -{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, -{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, -{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, -{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, -{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, -{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, -{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, -{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, -{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, -{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, -{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, -{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, -{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, -{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, -{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, -{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, -{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, -{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, -{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, -{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, -{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, -{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, -{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, -{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, -{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, -{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, -{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, -{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, -{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, -{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, -{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, -{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, -{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, -{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, -{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, -{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, -{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, -{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, -{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, -{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, -{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, -{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, -{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, -{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // degree symbol = '~' -{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 } -}; - -// TFT colour display ********************************************** - -int const CASET = 0x2A; // Define column address -int const RASET = 0x2B; // Define row address -int const RAMWR = 0x2C; // Write to display RAM - -int const White = 0xFFFF; -int const Black = 0; - -// Current plot position and colours -int xpos, ypos; -int fore = White; -int back = Black; -int scale = 1; // Text scale - -// Send a byte to the display - -void Data (uint8_t d) { - for (uint8_t bit = 0x80; bit; bit >>= 1) { - PORT_TOGGLE(1<>8); Data(d1); Data(d2>>8); Data(d2); -} - -void InitDisplay () { - PORT_OUTPUT(1<>3; -} - -// Move current plot position to x,y -void MoveTo (int x, int y) { - xpos = x; ypos = y; -} - -// Plot point at x,y -void PlotPoint (int x, int y) { - PORT_TOGGLE(1<>8); Data(fore & 0xff); - PORT_TOGGLE(1< -dy) { err = err - dy; xpos = xpos + sx; } - if (e2 < dx) { err = err + dx; ypos = ypos + sy; } - } -} - -void FillRect (int w, int h) { - PORT_TOGGLE(1<>8; - uint8_t lo = fore & 0xff; - for (int i=0; i= y) { - MoveTo(x1-x, y1+y); FillRect(x<<1, 1); - MoveTo(x1-y, y1+x); FillRect(y<<1, 1); - MoveTo(x1-y, y1-x); FillRect(y<<1, 1); - MoveTo(x1-x, y1-y); FillRect(x<<1, 1); - if (err > 0) { - x = x - 1; dx = dx + 2; - err = err - (radius<<1) + dx; - } else { - y = y + 1; err = err + dy; - dy = dy + 2; - } - } - xpos = x1; ypos = y1; -} - -void DrawCircle (int radius) { - int x1 = xpos, y1 = ypos, dx = 1, dy = 1; - int x = radius - 1, y = 0; - int err = dx - (radius<<1); - while (x >= y) { - PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y); - PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x); - PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x); - PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y); - if (err > 0) { - x = x - 1; dx = dx + 2; - err = err - (radius<<1) + dx; - } else { - y = y + 1; err = err + dy; - dy = dy + 2; - } - } -} - -// Plot an ASCII character with bottom left corner at x,y -void PlotChar (char c) { - int colour; - PORT_TOGGLE(1<>(7-yy) & 1) colour = fore; else colour = back; - for (int yr=0; yr>8); Data(colour & 0xFF); - } - } - } - } - PORT_TOGGLE(1<0; d = d/10) { - char j = (n/d) % 10; - if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; } - } -} - -void TestChart () { - DrawRect(xsize, ysize); - scale = 8; - fore = Colour(255, 0, 0); - MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F'); - scale = 1; -} - -// Demos ********************************************** - -void BarChart () { - int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; - MoveTo(x0+(w-x1-90)/2+x1, y0+h-8); PlotText(PSTR("Sensor Readings")); - // Horizontal axis - int xinc = (w-x1)/20; - MoveTo(x0+x1, y0+y1); DrawTo(x0+w-1, y0+y1); - for (int i=0; i<=20; i=i+4) { - int mark = x1+i*xinc; - MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); - // Draw histogram - if (i != 20) { - int bar = xinc*4/3; - for (int b=2; b>=0; b--) { - fore = Colour(255, 127*b, 0); // Red, Orange, Yellow - MoveTo(x0+mark+bar*b-b+1, y0+y1+1); FillRect(bar, 5+random(h-y1-20)); - } - fore = White; - } - if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); - PlotInt(i); - } - // Vertical axis - int yinc = (h-y1)/20; - MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); - for (int i=0; i<=20; i=i+5) { - int mark = y1+i*yinc; - MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); - if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); - PlotInt(i); - } -} - -void Waterfall () { - int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11; - int factor = 5160/h*10; - MoveTo(x0+(w-x1-60)/2+x1, y0+h-8); PlotText(PSTR("Luminance")); - // Horizontal axis - int xinc = (w-x1-15)/30; - MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+xinc*20, y0+y1); - for (int i=0; i<=20; i=i+5) { - int mark = x1+i*xinc; - MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2); - if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11); - PlotInt(i); - } - // Vertical axis - int yinc = (h-y1)/20; - MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1); - for (int i=0; i<=20; i=i+5) { - int mark = y1+i*yinc; - MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark); - if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4); - PlotInt(i); - } - // Diagonal axis - yinc = xinc/2; - // MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+10*xinc, y0+y1+10*xinc); - MoveTo(x0+x1+20*xinc, y0+y1); DrawTo(x0+x1+30*xinc, y0+y1+10*xinc); - for (int i=0; i<=20; i=i+5) { - MoveTo(x0+x1+20*xinc+i*xinc/2, y0+y1+i*xinc/2); - DrawTo(x0+x1+20*xinc+i*xinc/2+3, y0+y1+i*xinc/2); - MoveTo(x0+x1+20*xinc+i*xinc/2+6, y0+y1+i*xinc/2-4); PlotInt(i); - } - // Plot data - for (int y=20; y>=0; y--) { - for (int i=0; i<=20; i++) { - int fn0 = 180-(i-10)*(i-10)-(y-10)*(y-10); - int fn1 = 180-(i+1-10)*(i+1-10)-(y-10)*(y-10); - fore = Colour(255, 255, 0); - MoveTo(x0+x1+y*yinc+i*xinc, y0+y1+y*yinc+fn0*fn0/factor); - DrawTo(x0+x1+y*yinc+(i+1)*xinc, y0+y1+y*yinc+fn1*fn1/factor); - fore = White; - } - } -} - -// Setup ********************************************** - -void setup() { - InitDisplay(); - ClearDisplay(); - DisplayOn(); - MoveTo(0,0); - // TestChart(); -} - -void loop () { - BarChart(); - // Waterfall(); - for (;;); -} \ No newline at end of file From cdf3927aad542295a1d6106088ab5500ba13c8a4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:52:40 +0000 Subject: [PATCH 629/870] Update I2CManager.h Make getMuxCount() publicly visible. --- I2CManager.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/I2CManager.h b/I2CManager.h index 016874a26..f429c25e6 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -506,10 +506,12 @@ class I2CManagerClass { #if defined(I2C_EXTENDED_ADDRESS) // Count of I2C multiplexers found when initialising. If there is only one -// MUX then the subbus does not de-selecting after use; however, if there +// MUX then the subbus does not need de-selecting after use; however, if there // are two or more, then the subbus must be deselected to avoid multiple // sub-bus legs on different multiplexers being accessible simultaneously. +private: uint8_t _muxCount = 0; +public: uint8_t getMuxCount() { return _muxCount; } #endif @@ -522,6 +524,7 @@ class I2CManagerClass { // Within the queue, each request's nextRequest field points to the // next request, or NULL. // Mark volatile as they are updated by IRC and read/written elsewhere. +private: I2CRB * volatile queueHead = NULL; I2CRB * volatile queueTail = NULL; From 8582e51483458b6f7d763114efb3b683591e6bd1 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 23 Feb 2023 20:57:25 +0000 Subject: [PATCH 630/870] Change example EXRAIL DFplayer calls from SERVO to ANOUT. --- myHal.cpp_example.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index f528b180c..b9e68b7c6 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -156,12 +156,12 @@ void halSetup() { // 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 + // value to the first pin, e.g. ANOUT(10000,23,0,0) will play the 23rd mp3 file. + // ANOUT(10000,23,30,0) 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). + // e.g. ANOUT(10001,30,0,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. @@ -170,7 +170,7 @@ void halSetup() { // 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 + // LCD(4, "") // Clear LCD/OLED line // FOLLOW(1) // Go back to start // DFPlayer::create(10000, 10, Serial1); From 780c6ea1622a6fbb18c60e4a30e4a2b3f918c952 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 24 Feb 2023 09:39:00 +0000 Subject: [PATCH 631/870] Revert "Update DisplayInterface.cpp" This reverts commit a23f5839b75d2e0cacd7485640823cffed37cc3c. --- DisplayInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DisplayInterface.cpp b/DisplayInterface.cpp index 81da7a96b..f2c144e0f 100644 --- a/DisplayInterface.cpp +++ b/DisplayInterface.cpp @@ -21,7 +21,7 @@ #include "DisplayInterface.h" -// Start of chain of display handlers. -DisplayInterface *DisplayInterface::_displayHandler = NULL; +// Install null display driver initially - will be replaced if required. +DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface(); uint8_t DisplayInterface::_selectedDisplayNo = 255; From 02ee8ad08073e14e28d29b9bc248c393531b7c69 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 24 Feb 2023 16:56:21 +0000 Subject: [PATCH 632/870] Removed duplicate DIAG --- CommandDistributor.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4b05c68c0..0c9185dbf 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -179,10 +179,7 @@ void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte { case 1: if (clocktime != lastclocktime){ - if (Diag::CMD) { - DIAG(F("Clock Command Received")); - DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate); - } + // CAH. DIAG removed because LCD does it anyway. LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate); // look for an event for this time RMFT2::clockEvent(clocktime,1); From c2983efebb7fc5dac4ad582b4e8f4e9e43d7f000 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 25 Feb 2023 11:42:12 +0000 Subject: [PATCH 633/870] Update to comments --- I2CManager.h | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/I2CManager.h b/I2CManager.h index f429c25e6..b1003e609 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -84,8 +84,6 @@ */ /* - * Future enhancement possibility: - * * I2C Multiplexer (e.g. TCA9547, TCA9548) * * A multiplexer offers a way of extending the address range of I2C devices. For example, GPIO extenders use address range 0x20-0x27 @@ -98,11 +96,6 @@ * Thirdly, the multiplexer offers the ability to use mixed-speed devices more effectively, by allowing high-speed devices to be * put on a different bus to low-speed devices, enabling the software to switch the I2C speed on-the-fly between I2C transactions. * - * Changes required: Increase the size of the I2CAddress field in the IODevice class from uint8_t to uint16_t. - * The most significant byte would contain a '1' bit flag, the multiplexer number (0-7) and bus number (0-7). Then, when performing - * an I2C operation, the I2CManager would check this byte and, if zero, do what it currently does. If the byte is non-zero, then - * that means the device is connected via a multiplexer so the I2C transaction should be preceded by a select command issued to the - * relevant multiplexer. * * Non-interrupting I2C: * @@ -138,13 +131,9 @@ // may be extended to include multiple buses, and other features. // Uncomment to enable extended address. // -// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc, -// it should be cast to (int) to ensure that the address value is passed rather than -// the struct. //#define I2C_EXTENDED_ADDRESS - ///////////////////////////////////////////////////////////////////////////////////// // Extended I2C Address type to facilitate extended I2C addresses including // I2C multiplexer support. @@ -184,7 +173,7 @@ enum I2CSubBus : uint8_t { #endif SubBus_No, // Number of subbuses (highest + 1) SubBus_None = 254, // Disable all sub-buses on selected mux - SubBus_All = 255, // Enable all sub-buses + SubBus_All = 255, // Enable all sub-buses (not supported by some multiplexers) }; // Type to hold I2C address From 711ad6f030ab9368337cc3dbeb894763982bbec0 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 25 Feb 2023 11:42:43 +0000 Subject: [PATCH 634/870] Update SSD1306Ascii.cpp Add remaining extended graphics characters (still no international characters though. --- SSD1306Ascii.cpp | 72 ++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/SSD1306Ascii.cpp b/SSD1306Ascii.cpp index 911074bd9..ea0c76b37 100644 --- a/SSD1306Ascii.cpp +++ b/SSD1306Ascii.cpp @@ -236,11 +236,6 @@ void SSD1306AsciiWire::setRowNative(uint8_t line) { size_t SSD1306AsciiWire::writeNative(uint8_t ch) { const uint8_t* base = m_font; - if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount)) - return 0; - // Check if character would be partly or wholly off the display - if (m_col + fontWidth > m_displayWidth) - return 0; #if defined(NOLOWERCASE) // Adjust if lowercase is missing if (ch >= 'a') { @@ -250,6 +245,12 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) { ch -= 26; // Allow for missing lowercase letters } #endif + if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount)) + return 0; + // Check if character would be partly or wholly off the display + if (m_col + fontWidth > m_displayWidth) + return 0; + ch -= m_fontFirstChar; base += fontWidth * ch; // Before using buffer, wait for last request to complete @@ -406,8 +407,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x38, 0x44, 0xc6, 0x44, 0x20, 0x00, // cent 0x9b + 0x44, 0x6e, 0x59, 0x49, 0x62, 0x00, // £ 0x9c 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented @@ -425,27 +426,27 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x10, 0x28, 0x54, 0x28, 0x44, 0x00, // << + 0x44, 0x28, 0x54, 0x28, 0x10, 0x00, // >> // Extended characters 176-180 0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0 - 0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1 - 0x6a, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2 + 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x55, // Mid grey 0xb1 + 0x6d, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3 0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4 - 0x14, 0x14, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x14, 0x14, 0x14, 0xff, 0x00, 0x00, // Vertical line with double left spur 0xb5 + 0x08, 0x08, 0xff, 0x00, 0xff, 0x00, // Double vertical line with single left spur + 0x08, 0x08, 0xf8, 0x08, 0xf8, 0x00, // Top right corner, single horiz, double vert + 0x14, 0x14, 0x14, 0xfc, 0x00, 0x00, // Top right corner, double horiz, single vert // Extended characters 185-190 - 0x28, 0x28, 0xef, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9 + 0x14, 0x14, 0xf7, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba 0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb 0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbe + 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x00, // Bottom right corner, single horiz, double vert 0xbd + 0x14, 0x14, 0x14, 0x1f, 0x00, 0x00, // Bottom right corner, double horiz, single vert 0xbe // Extended characters 191-199 0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, // Top right corner 0xbf @@ -455,8 +456,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { 0x00, 0x00, 0x00, 0xff, 0x08, 0x08, // Vertical line with right spur 0xc3 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Horizontal line 0xc4 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, // Cross 0xc5 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x00, 0x00, 0x00, 0xff, 0x14, 0x14, // Vertical line double right spur 0xc6 + 0x00, 0x00, 0xff, 0x00, 0xff, 0x08, // Double vertical line single right spur 0xc7 // Extended characters 200-206 0x00, 0x00, 0x1f, 0x10, 0x17, 0x14, // Double bottom left corner 0xc8 @@ -467,16 +468,16 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd 0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0x14, 0x14, 0x14, 0x17, 0x14, 0x14, // Double horizontal line single upward spur 0xcf + 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, // Horiz single line with double upward spur 0xd0 + 0x14, 0x14, 0x14, 0xf4, 0x14, 0x14, // Horiz double line with single downward spur 0xd1 + 0x08, 0x08, 0xf8, 0x08, 0xf8, 0x08, // Horiz single line with double downward spur 0xd2 + 0x00, 0x00, 0x0f, 0x08, 0x0f, 0x08, // Bottom left corner, double vert single horiz 0xd3 + 0x00, 0x00, 0x00, 0x1f, 0x14, 0x14, // Bottom left corner, single vert double horiz 0xd4 + 0x00, 0x00, 0x00, 0xfc, 0x14, 0x14, // Top left corner, single vert double horiz 0xd5 + 0x00, 0x00, 0xf8, 0x08, 0xf8, 0x08, // Top left corner, double vert single horiz 0xd6 + 0x08, 0x08, 0xff, 0x00, 0xff, 0x08, // Cross, double vert single horiz 0xd7 + 0x14, 0x14, 0x14, 0xf7, 0x14, 0x14, // Cross, single vert double horiz 0xd8 // Extended characters 217-223 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, // Bottom right corner 0xd9 @@ -487,10 +488,10 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented + 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, // Bottom Left block 0xe0 + 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, // Bottom Right block + 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, // Top left block + 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, // Top right block 0xe3 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented @@ -511,9 +512,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented // Extended character 248 - 0x00, 0x06, 0x09, 0x09, 0x06, 0x00, // degree symbol 0xf8 + 0x00, 0x06, 0x09, 0x09, 0x06, 0x00 // degree symbol 0xf8 #endif - 0x00 }; const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6; From 8c8a9136780d8720e1aabf04957bdff6d34e8eb5 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 25 Feb 2023 11:43:05 +0000 Subject: [PATCH 635/870] Update IO_HALDisplay.h Check for memory allocation errors. --- IO_HALDisplay.h | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/IO_HALDisplay.h b/IO_HALDisplay.h index 3919bbfb9..5ef9efca0 100644 --- a/IO_HALDisplay.h +++ b/IO_HALDisplay.h @@ -76,6 +76,7 @@ class HALDisplay : public IODevice, public DisplayInterface { uint8_t *_lastRowGeneration = NULL; uint8_t _rowNoToScreen = 0; uint8_t _charPosToScreen = 0; + bool _startAgain = false; DisplayInterface *_nextDisplay = NULL; public: @@ -91,6 +92,7 @@ class HALDisplay : public IODevice, public DisplayInterface { // Constructor HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { _displayDriver = new T(i2cAddress, width, height); + if (!_displayDriver) return; // Check for memory allocation failure _I2CAddress = i2cAddress; _width = width; _height = height; @@ -101,8 +103,12 @@ class HALDisplay : public IODevice, public DisplayInterface { // Allocate arrays _buffer = (char *)calloc(_numRows*_numCols, sizeof(char)); + if (!_buffer) return; // Check for memory allocation failure _rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + if (!_rowGeneration) return; // Check for memory allocation failure _lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t)); + if (!_lastRowGeneration) return; // Check for memory allocation failure + // Fill buffer with spaces memset(_buffer, ' ', _numCols*_numRows); @@ -116,7 +122,7 @@ class HALDisplay : public IODevice, public DisplayInterface { // Also add this display to list of display handlers DisplayInterface::addDisplay(displayNo); - // Is this the main display? + // Is this the system display (0)? if (displayNo == 0) { // Set first two lines on screen this->setRow(displayNo, 0); @@ -135,13 +141,15 @@ class HALDisplay : public IODevice, public DisplayInterface { // to the screen until that row has been refreshed. // First check if the OLED driver is still busy from a previous - // call. If so, don't to anything until the next entry. + // call. If so, don't do anything until the next entry. if (!_displayDriver->isBusy()) { // Check if we've just done the end of a row if (_charPosToScreen >= _numCols) { // Move to next line - if (++_rowNoToScreen >= _numRows) + if (++_rowNoToScreen >= _numRows || _startAgain) { _rowNoToScreen = 0; // Wrap to first row + _startAgain = false; + } if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) { // Row content has changed, so start outputting it @@ -222,10 +230,14 @@ class HALDisplay : public IODevice, public DisplayInterface { for (_colNo = 0; _colNo < _numCols; _colNo++) _buffer[_rowNo*_numCols+_colNo] = ' '; _colNo = 0; - // Mark that the buffer has been touched. It will be + // Mark that the buffer has been touched. It will start being // sent to the screen on the next loop entry, by which time // the line should have been written to the buffer. _rowGeneration[_rowNo]++; + // Indicate that the output loop is to start updating the screen again from + // row 0. Otherwise, on a full screen rewrite the bottom part may be drawn + // before the top part! + _startAgain = true; } // Write one character to the screen referenced in the last setRow() call. From f56f5d9ebe04240a919f6b5dad7e03c343de2a1e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 25 Feb 2023 11:43:36 +0000 Subject: [PATCH 636/870] Update I2CManager_Wire.h Add alternative multiplexer support for I2C Wire subsystem. --- I2CManager_Wire.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/I2CManager_Wire.h b/I2CManager_Wire.h index 04d99bd9b..1fd4d6778 100644 --- a/I2CManager_Wire.h +++ b/I2CManager_Wire.h @@ -77,7 +77,15 @@ static uint8_t muxSelect(I2CAddress address) { Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo); uint8_t data = (subBus == SubBus_All) ? 0xff : (subBus == SubBus_None) ? 0x00 : - (1 << subBus); +#if defined(I2CMUX_PCA9547) + 0x08 | subBus; +#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544) + 0x04 | subBus; // NB Only 2 or 4 subbuses respectively +#else + // Default behaviour for most MUXs is to use a mask + // with a bit set for the subBus to be enabled + 1 << subBus; +#endif Wire.write(&data, 1); return Wire.endTransmission(true); // have to release I2C bus for it to work } From 4adcdc1b0b9f2868a1ac18908258cea4a802a13b Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Tue, 28 Feb 2023 12:12:08 +0000 Subject: [PATCH 637/870] Update config.example.h Removed redundant define for FastClock no longer required. --- config.example.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/config.example.h b/config.example.h index a2d97c5a1..b253ab319 100644 --- a/config.example.h +++ b/config.example.h @@ -225,7 +225,4 @@ The configuration file for DCC-EX Command Station //#define SERIAL_BT_COMMANDS -// FastClock Enabler -// To build the FastClock code into the CS please uncomment the line below -//#define USEFASTCLOCK ///////////////////////////////////////////////////////////////////////////////////// From b01e4388ce16bc4df7b6e5e2502ef3eb91f6cd4b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 28 Feb 2023 15:17:04 +0100 Subject: [PATCH 638/870] Small typo in tone scale --- MotorDriver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index e87755bbf..763e5a3c5 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -234,7 +234,7 @@ void MotorDriver::startCurrentFromHW() { #if defined(ARDUINO_ARCH_ESP32) uint16_t taurustones[28] = { 165, 175, 196, 220, 247, 262, 294, 330, - 249, 392, 440, 494, + 349, 392, 440, 494, 523, 587, 659, 698, 494, 440, 392, 249, 330, 284, 262, 247, From 8a126906f36b4e3eaa49faec4d6726c6b82b0de3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 28 Feb 2023 15:20:29 +0100 Subject: [PATCH 639/870] Only need do anything if cab existed --- DCC.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index abdee732a..36d305db8 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -576,9 +576,11 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) { void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco setThrottle2(cab,1); // ESTOP this loco if still on track - int reg=lookupSpeedTable(cab); - if (reg>=0) speedTable[reg].loco=0; - setThrottle2(cab,1); // ESTOP if this loco still on track + int reg=lookupSpeedTable(cab, false); + if (reg>=0) { + speedTable[reg].loco=0; + setThrottle2(cab,1); // ESTOP if this loco still on track + } } void DCC::forgetAllLocos() { // removes all speed reminders setThrottle2(0,1); // ESTOP all locos still on track From 1d47dce473d9957cad2be9a4013345bc11882089 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 28 Feb 2023 15:22:57 +0100 Subject: [PATCH 640/870] update tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 98ac27af7..75a9be664 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202302121935Z" +#define GITHUB_SHA "devel-202302281422Z" From 5fb10fa6d0083a7dff94c48f431fd2305cf25a58 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 28 Feb 2023 20:09:37 +0000 Subject: [PATCH 641/870] Delete IO_ExternalEEPROM.h --- IO_ExternalEEPROM.h | 141 -------------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 IO_ExternalEEPROM.h diff --git a/IO_ExternalEEPROM.h b/IO_ExternalEEPROM.h deleted file mode 100644 index d2c90e504..000000000 --- a/IO_ExternalEEPROM.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * © 2023, 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 . - */ - -/* - * This device driver monitors the state of turnout objects and writes updates, - * on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte) - * EEPROM device connected via I2C. - * - * When the device is restarted, it repositions the turnouts in accordance - * with the last saved position. - * - * To create a device instance, - * IO_ExternalEEPROM::create(0, 0, i2cAddress); - * - * - */ - -#ifndef IO_EXTERNALEEPROM_H -#define IO_EXTERNALEEPROM_H - -#include "IODevice.h" -#include "I2CManager.h" -#include "Turnouts.h" - -class ExternalEEPROM : public IODevice { -private: - // Here we define the device-specific variables. - int _sizeInKBytes = 128; - Turnout *_turnout = 0; - int _lastTurnoutHash = 0; - I2CRB _rb; - uint8_t _buffer[32]; // 32 is max for Wire write - -public: - // Static function to handle "IO_ExampleSerial::create(...)" calls. - static void create(I2CAddress i2cAddress, int sizeInKBytes) { - if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes); - } - -protected: - // Constructor. - ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) { - _I2CAddress = i2cAddress; - _sizeInKBytes = sizeInKBytes; - - // Set up I2C structures. - _rb.setWriteParams(_I2CAddress, _buffer, 32); - - addDevice(this); - } - - // Device-specific initialisation - void _begin() override { - I2CManager.begin(); - I2CManager.setClock(1000000); // Max supported speed - - if (I2CManager.exists(_I2CAddress)) { - // Initialise or read contents of EEPROM - // and set turnout states accordingly. - // Read 32 bytes from address 0x0000. - I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0); - // Dump data - DIAG(F("EEPROM First 32 bytes:")); - for (int i=0; i<32; i+=8) - DIAG(F("%d: %x %x %x %x %x %x %x %x"), - i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3], - _buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]); - -#if defined(DIAG_IO) - _display(); -#endif - } else { - DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString()); - _deviceState = DEVSTATE_FAILED; - } - } - - // Loop function to do background scanning of the turnouts - void _loop(unsigned long currentMicros) { - (void)currentMicros; // Suppress compiler warnings - - if (_rb.isBusy()) return; // Can't do anything until previous request has completed. - if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) { - // Device not responding, probably still writing data, so requeue request - I2CManager.queueRequest(&_rb); - return; - } - - if (_lastTurnoutHash != Turnout::turnoutlistHash) { - _lastTurnoutHash = Turnout::turnoutlistHash; - // Turnout list has changed, so pointer held from last run may be invalid - _turnout = 0; // Start at the beginning of the list again. -//#if defined(DIAG_IO) - DIAG(F("Turnout Hash Changed!")); -//#endif - } - - // Locate next turnout, or first one if there is no current one. - if (_turnout) - _turnout = _turnout->next(); - else - _turnout = Turnout::first(); - - // Retrieve turnout state - int turnoutID = _turnout->getId(); - int turnoutState = _turnout->isThrown(); - (void)turnoutID; // Suppress compiler warning - (void)turnoutState; // Suppress compiler warning - - // TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed. - // TODO: If it has, then initiate a write of the updated state to EEPROM - - delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256 - } - - // Display information about the device. - void _display() { - DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(), - _deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F("")); - } - - -}; - -#endif // IO_EXTERNALEEPROM_H \ No newline at end of file From 0663cc6138d023a714a11895e4afcb35014c85ef Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Tue, 28 Feb 2023 23:49:51 +0000 Subject: [PATCH 642/870] Update IO_EXIOExpander.h _I2CAddress or _i2cAddress - the checkOverlap function uses the former, and the driver uses the latter. I incorrectly used the wrong one! --- IO_EXIOExpander.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 58a92aef0..063b5dd29 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -106,12 +106,12 @@ class EXIOExpander : public IODevice { _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"), - _I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); + _i2cAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); #ifdef DIAG_IO _display(); #endif } else { - DIAG(F("EX-IOExpander device not found, I2C:%s"), _I2CAddress.toString()); + DIAG(F("EX-IOExpander device not found, I2C:%s"), _i2cAddress.toString()); _deviceState = DEVSTATE_FAILED; } } From ca4592dc3e4983d875dfe80bb7aae3733fe2a046 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 1 Mar 2023 23:09:27 +0000 Subject: [PATCH 643/870] Reinstate original #ifdef DIAG_IO tests in servo modules --- IO_PCA9685.cpp | 6 +++--- IO_PCA9685pwm.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 25b074573..71f36613d 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -45,7 +45,7 @@ void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; - #if DIAG_IO >= 3 + #ifdef DIAG_IO DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -117,7 +117,7 @@ void PCA9685::_begin() { // Device-specific write function, invoked from IODevice::write(). // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { - #if DIAG_IO >= 3 + #ifdef DIAG_IO DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; @@ -144,7 +144,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 DIAG_IO >= 3 + #ifdef DIAG_IO DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index d950bbf6d..042516be8 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -120,7 +120,7 @@ class PCA9685pwm : public IODevice { // void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning - #if DIAG_IO >= 3 + #ifdef DIAG_IO DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif @@ -141,7 +141,7 @@ class PCA9685pwm : public IODevice { // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. void writeDevice(uint8_t pin, int value) { - #if DIAG_IO >= 3 + #ifdef DIAG_IO DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); #endif // Wait for previous request to complete From f0c1ea958cc9c22cc28b430e4dcdca8d85f128bf Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 2 Mar 2023 10:45:39 +0000 Subject: [PATCH 644/870] 4.2.19 sensorOffset bugfix --- MotorDriver.cpp | 15 ++++++++++----- version.h | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 763e5a3c5..a6a17663d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -90,9 +90,8 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i else brakePin=UNUSED_PIN; currentPin=current_pin; - if (currentPin!=UNUSED_PIN) { - senseOffset = ADCee::init(currentPin); - } + if (currentPin!=UNUSED_PIN) ADCee::init(currentPin); + senseOffset=0; // value can not be obtained until waveform is activated faultPin=fault_pin; if (faultPin != UNUSED_PIN) { @@ -121,8 +120,8 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i if (currentPin==UNUSED_PIN) DIAG(F("** WARNING ** No current or short detection")); else { - DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"), - currentPin-A0, senseOffset,rawCurrentTripValue); + DIAG(F("CurrentPin=A%d, TripValue=%d"), + currentPin-A0, rawCurrentTripValue); // self testing diagnostic for the non-float converters... may be removed when happy // DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"), @@ -144,6 +143,12 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { bool on=mode==POWERMODE::ON; if (on) { + // when switching a track On, we need to check the crrentOffset with the pin OFF + if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) { + senseOffset = ADCee::read(currentPin); + DIAG(F("CurrentPin A%d sensOffset=%d"),currentPin-A0,senseOffset); + } + IODevice::write(powerPin,invertPower ? LOW : HIGH); if (isProgTrack) DCCWaveform::progTrack.clearResets(); diff --git a/version.h b/version.h index f846e0d89..d621edfd4 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.18" +#define VERSION "4.2.19" +// 4.2.19 - Bugfix for analog reading of track current sensor offeset. // 4.2.18 - I2C Multiplexer support through Extended Addresses, // added for Wire, 4209 and AVR I2C drivers. // - I2C retries when an operation fails. From b969563d355de74181ab4b1a0854a807980bcaa4 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 2 Mar 2023 12:56:30 +0000 Subject: [PATCH 645/870] Squashed commit of the following: commit 4d8efcdd056a021023b8c7d7942a05e8530adafe Author: Asbelos Date: Wed Mar 1 16:32:05 2023 +0000 Reinstate obsolete commit 003313998b3708b0b845d88a985d8515834cbe2f Author: Asbelos Date: Wed Mar 1 16:07:11 2023 +0000 Change to commit c72bf51959d77356ba0b1eb6be83b790439ce779 Author: Asbelos Date: Sat Feb 25 17:38:39 2023 +0000 G and I commands --- DCCEXParser.cpp | 18 +++- Release_Notes/CommandRef.md | 171 ++++++++++++++++++++++++++++++++++++ TrackManager.cpp | 30 ++++++- TrackManager.h | 3 + 4 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 Release_Notes/CommandRef.md diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index bcc25d7fc..9e2cf5f7c 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -79,6 +79,8 @@ const int16_t HASH_KEYWORD_TT=2688; const int16_t HASH_KEYWORD_VPIN=-415; const int16_t HASH_KEYWORD_A='A'; const int16_t HASH_KEYWORD_C='C'; +const int16_t HASH_KEYWORD_G='G'; +const int16_t HASH_KEYWORD_I='I'; const int16_t HASH_KEYWORD_R='R'; const int16_t HASH_KEYWORD_T='T'; const int16_t HASH_KEYWORD_X='X'; @@ -501,8 +503,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; case 'c': // SEND METER RESPONSES - // No longer supported because of multiple tracks - break; + // No longer useful because of multiple tracks See and + if (params>0) break; + TrackManager::reportObsoleteCurrent(stream); + return; case 'Q': // SENSORS Sensor::printAll(stream); @@ -583,6 +587,16 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } CommandDistributor::setClockTime(p[1], p[2], 1); return; + + case HASH_KEYWORD_G: // current gauge limits + if (params>1) break; + TrackManager::reportGauges(stream); // + return; + + case HASH_KEYWORD_I: // current values + if (params>1) break; + TrackManager::reportCurrent(stream); // + return; case HASH_KEYWORD_A: // returns automations/routes StringFormatter::send(stream, F(" is actually read as + - Keyword parameters are shown in upper case but may be entered in mixed case. + - value parameters are numeric. + - [something] indicates its optional. + - Not all commands have a response, and not all responses come from the last commands that you have issued. + +Startup status + + +Track power management +<1> +<1 MAIN|PROG|JOIN> +<0> +<0 MAIN|PROG> + +Basic manual control + + + + + +DCC accessory control + + + + +Turnout definition +Note: Turnouts are best defined in myAutomation.h where a turnout description can also be provided ( refer to EXRAIL documentation) or by using these commands in a mySetup.h file. + + + + + + + +Outputs + + + +Sensors + + +Decoder programming + + + + + + + + + + + + + +Advanced DCC control + +

+ +<#> +<-> +<- cabid> + + + + + +EEPROM commands +These commands exist for +backwards JMRI compatibility. +You are strongly discouraged from maintaining your configuration settings in EEPROM. + + + + + + + + + + +Diagnostic commands + + + + + + + + + + + +<+ cmd> +<+> + + +User defined filter commands + + + +Track Management +<=> +<= track DCC|PROG|OFF> +<= track DC|DCX cabid> + + + + +Turntable interface + + +Fast clock interface + + + + +Advanced Throttle access to features + + + + + + + + +******************* +EXRAIL Commands +******************* + + + + + + + + + + + + +Obsolete commands/formats + + + + + + + + +Broadcast responses +Note: broadcasts are sent to all throttles when appropriate (usually because something has changed) + + + + + + + + + +Diagnostic responses +These are not meant to be software readable. They contain diagnostic information for programmers to identify issues. + +<* ... *> + diff --git a/TrackManager.cpp b/TrackManager.cpp index cbb672a5b..33d1d8ae3 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -423,7 +423,35 @@ POWERMODE TrackManager::getProgPower() { return track[t]->getPower(); return POWERMODE::OFF; } - + +void TrackManager::reportObsoleteCurrent(Print* stream) { + // This function is for backward JMRI compatibility only + // It reports the first track only, as main, regardless of track settings. + // + int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue()); + StringFormatter::send(stream, F("\n"), + track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent); +} + +void TrackManager::reportCurrent(Print* stream) { + StringFormatter::send(stream,F("getPower()==POWERMODE::OVERLOAD) ? -1 : + track[t]->raw2mA(track[t]->getCurrentRaw(false))); + } + StringFormatter::send(stream,F(">\n")); +} + +void TrackManager::reportGauges(Print* stream) { + StringFormatter::send(stream,F("raw2mA(track[t]->getRawCurrentTripValue())); + } + StringFormatter::send(stream,F(">\n")); +} + void TrackManager::setJoinRelayPin(byte joinRelayPin) { joinRelay=joinRelayPin; if (joinRelay!=UNUSED_PIN) { diff --git a/TrackManager.h b/TrackManager.h index 90a93455b..4a548cc69 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -77,6 +77,9 @@ class TrackManager { static bool isJoined() { return progTrackSyncMain;} static void setJoinRelayPin(byte joinRelayPin); static void sampleCurrent(); + static void reportGauges(Print* stream); + static void reportCurrent(Print* stream); + static void reportObsoleteCurrent(Print* stream); static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackBoosted; // true when prog track is not current limited From a17a55d90402d3481a780a826963184903c7670b Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 2 Mar 2023 13:03:05 +0000 Subject: [PATCH 646/870] Implement --- version.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/version.h b/version.h index d621edfd4..a97aaec16 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.19" -// 4.2.19 - Bugfix for analog reading of track current sensor offeset. +#define VERSION "4.2.20" +// 4.2.20 - & commands for multi-track gauges +// - Reinstate but remember its a bit useless when TM involved. +// 4.2.19 - Bugfix for analog reading of track current sensor offset. // 4.2.18 - I2C Multiplexer support through Extended Addresses, // added for Wire, 4209 and AVR I2C drivers. // - I2C retries when an operation fails. From 19bbb186e72dae8f534ad8b3d398e96323d3ec28 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 2 Mar 2023 23:01:44 +0000 Subject: [PATCH 647/870] Create IO_TouchKeypad.h Driver for 16-pad capacitative key pad device (TTP229-B based). --- IO_TouchKeypad.h | 134 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 IO_TouchKeypad.h diff --git a/IO_TouchKeypad.h b/IO_TouchKeypad.h new file mode 100644 index 000000000..8984bcec7 --- /dev/null +++ b/IO_TouchKeypad.h @@ -0,0 +1,134 @@ +/* + * © 2023, 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 . + */ + +/* + * Driver for capacitative touch-pad based on the TTP229-B chip with serial + * (not I2C) output. The touchpad has 16 separate pads in a 4x4 matrix, + * numbered 1-16. The communications with the pad are via a clock signal sent + * from the controller to the device, and a data signal sent back by the device. + * The pins clockPin and dataPin must be local pins, not external (GPIO Expander) + * pins. + * + * To use, + * TouchKeypad::create(firstVpin, 16, clockPin, dataPin); + * + * NOTE: Most of these keypads ship with only 8 pads enabled. To enable all + * sixteen pads, locate the area of the board labelled P1 (four pairs of + * holes labelled 1 to 4 from the left); solder a jumper link between the pair + * labelled 3 (connected to pin TP2 on the chip). When this link is connected, + * the pins OUT1 to OUT8 are not used but all sixteen touch pads are operational. + * + * TODO: Allow a list of datapins to be provided so that multiple keypads can + * be read simultaneously by the one device driver and the one shared clock signal. + * As it stands, we can configure multiple driver instances, one for each keypad, + * and it will work fine. The clock will be driven to all devices but only one + * driver will be reading the responses from its corresponding device at a time. + */ + +#ifndef IO_TOUCHKEYPAD_H +#define IO_TOUCHKEYPAD_H + +#include "IODevice.h" + +class TouchKeypad : public IODevice { +private: + // Here we define the device-specific variables. + uint16_t _inputStates = 0; + VPIN _clockPin; + VPIN _dataPin; + +public: + // Static function to handle create calls. + static void create(VPIN firstVpin, int nPins, VPIN clockPin, VPIN dataPin) { + if (checkNoOverlap(firstVpin,nPins)) new TouchKeypad(firstVpin, nPins, clockPin, dataPin); + } + +protected: + // Constructor. + TouchKeypad(VPIN firstVpin, int nPins, VPIN clockPin, VPIN dataPin) { + _firstVpin = firstVpin; + _nPins = (nPins > 16) ? 16 : nPins; // Maximum of 16 pads per device + _clockPin = clockPin; + _dataPin = dataPin; + + addDevice(this); + } + + // Device-specific initialisation + void _begin() override { +#if defined(DIAG_IO) + _display(); +#endif + // Set clock pin as output, initially high, and data pin as input. + // Enable pullup on the input so that the default (not connected) state is + // 'keypad not pressed'. + ArduinoPins::fastWriteDigital(_clockPin, 1); + pinMode(_clockPin, OUTPUT); + pinMode(_dataPin, INPUT_PULLUP); // Force defined state when no connection + } + + // Device-specific read function. + int _read(VPIN vpin) { + if (vpin < _firstVpin || vpin >= _firstVpin + _nPins) return 0; + + // Return a value for the specified vpin. + return _inputStates & (1<<(vpin-_firstVpin)) ? 1 : 0; + } + + // Loop function to do background scanning of the keyboard. + // The TTP229 device requires clock pulses to be sent to it, + // and the data bits can be read on the rising edge of the clock. + // By default the clock and data are inverted (active-low). + // A gap of more than 2ms is advised between successive read + // cycles, we wait for 100ms between reads of the keyboard as this + // provide a good enough response time. + // Maximum clock frequency is 512kHz, so put a 1us delay + // between clock transitions. + // + void _loop(unsigned long currentMicros) { + + // Clock 16 bits from the device + uint16_t data = 0, maskBit = 0x01; + for (uint8_t pad=0; pad<16; pad++) { + ArduinoPins::fastWriteDigital(_clockPin, 0); + delayMicroseconds(1); + ArduinoPins::fastWriteDigital(_clockPin, 1); + data |= (ArduinoPins::fastReadDigital(_dataPin) ? 0 : maskBit); + maskBit <<= 1; + delayMicroseconds(1); + } + _inputStates = data; +#ifdef DIAG_IO + static uint16_t lastData = 0; + if (data != lastData) DIAG(F("KeyPad: %x"), data); + lastData = data; +#endif + delayUntil(currentMicros + 100000); // read every 100ms + } + + // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). + void _display() { + DIAG(F("TouchKeypad Configured on VPins:%d-%d SCL=%d SDO=%d"), (int)_firstVpin, + (int)_firstVpin+_nPins-1, _clockPin, _dataPin); + } + + +}; + +#endif // IO_TOUCHKEYPAD_H \ No newline at end of file From 31ecba08d8b797b3468b04833ac2c3a794602fcc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 3 Mar 2023 20:51:32 +0100 Subject: [PATCH 648/870] faultPin can be inverted (from its inverted sense --- MotorDriver.cpp | 11 +++++++++-- MotorDriver.h | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a6a17663d..7415b3690 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -38,7 +38,7 @@ volatile portreg_t shadowPORTB; volatile portreg_t shadowPORTC; MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { + byte current_pin, float sense_factor, unsigned int trip_milliamps, int8_t fault_pin) { powerPin=power_pin; invertPower=power_pin < 0; if (invertPower) { @@ -95,6 +95,9 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i faultPin=fault_pin; if (faultPin != UNUSED_PIN) { + invertFault=fault_pin < 0; + faultPin=invertFault ? 0-fault_pin : fault_pin; + DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault); getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin); pinMode(faultPin, INPUT); } @@ -196,8 +199,12 @@ int MotorDriver::getCurrentRaw(bool fromISR) { int current; current = ADCee::read(currentPin, fromISR)-senseOffset; if (current<0) current=0-current; - if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) + if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) { + if (invertFault && isLOW(fastFaultPin)) return (current == 0 ? -1 : -current); + if (!invertFault && !isLOW(fastFaultPin)) + return (current == 0 ? -1 : -current); + } return current; } diff --git a/MotorDriver.h b/MotorDriver.h index 51f7654ac..5f361841c 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -112,7 +112,7 @@ class MotorDriver { public: MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); + byte current_pin, float senseFactor, unsigned int tripMilliamps, int8_t fault_pin); void setPower( POWERMODE mode); POWERMODE getPower() { return powerMode;} // as the port registers can be shadowed to get syncronized DCC signals @@ -198,6 +198,7 @@ class MotorDriver { bool dualSignal; // true to use signalPin2 bool invertBrake; // brake pin passed as negative means pin is inverted bool invertPower; // power pin passed as negative means pin is inverted + bool invertFault; // fault pin passed as negative means pin is inverted // Raw to milliamp conversion factors avoiding float data types. // Milliamps=rawADCreading * sensefactorInternal / senseScale From da8faa808bce6abbb51734db289f821126d0edef Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 3 Mar 2023 21:07:58 -0500 Subject: [PATCH 649/870] Update ThrottleAssists.md --- Release_Notes/ThrottleAssists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md index 9d979c96b..c4b2b9920 100644 --- a/Release_Notes/ThrottleAssists.md +++ b/Release_Notes/ThrottleAssists.md @@ -7,7 +7,7 @@ These commands are new and not overlapped with the existing commands which are p Turnouts: -The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons. +The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons. `````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. e.g. response `````` From 0f5b8adb6b56b4b4a6162d30b06ec9705820a470 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 3 Mar 2023 21:26:46 -0500 Subject: [PATCH 650/870] Update ThrottleAssists.md --- Release_Notes/ThrottleAssists.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md index c4b2b9920..5f4a68dfa 100644 --- a/Release_Notes/ThrottleAssists.md +++ b/Release_Notes/ThrottleAssists.md @@ -5,7 +5,8 @@ Chris Harlow April 2022 There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations. These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose. -Turnouts: +Turnouts +============ The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons. @@ -25,6 +26,7 @@ Note: It is still the throttles responsibility to monitor the status broadcasts. Automations/Routes + ==================== A throttle need to know which EXRAIL Automations and Routes it can show the user. @@ -64,6 +66,7 @@ Note: It is still the throttles responsibility to monitor the status broadcasts. COMMANDS TO AVOID + ====================== `````` Use `````` `````` Just drop the slot number From 79eaaa85fabd403ec46f925be8f5ee2fa1dbfa44 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 3 Mar 2023 21:35:13 -0500 Subject: [PATCH 651/870] Update ThrottleAssists.md Fixing formatting --- Release_Notes/ThrottleAssists.md | 64 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md index 5f4a68dfa..e704b425b 100644 --- a/Release_Notes/ThrottleAssists.md +++ b/Release_Notes/ThrottleAssists.md @@ -10,40 +10,44 @@ Turnouts The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons. -`````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. +`````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. e.g. response `````` -````` requests info on turnout 17. -e.g. response `````` or `````` -(T=thrown, C=closed) -or `````` indicating turnout description not given. -or `````` indicating turnout unknown (or possibly hidden.) - -Note: It is still the throttles responsibility to monitor the status broadcasts. - (TBD I'm thinking that the existing broadcast is messy and needs cleaning up) - However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. - Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. +````` requests info on turnout 17. +e.g. response `````` or ````` +(T=thrown, C=closed) +or `````` indicating turnout description not given. +or `````` indicating turnout unknown (or possibly hidden.) + +Note: It is still the throttles responsibility to monitor the status broadcasts. +(TBD I'm thinking that the existing broadcast is messy and needs cleaning up) +However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. +Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. Automations/Routes ==================== - A throttle need to know which EXRAIL Automations and Routes it can show the user. - - `````` Returns a list of Automations/Routes - e.g. `````` - Indicates route/automation ids. - Information on each route needs to be obtained by - `````` - returns e.g. `````` for a route - or `````` for an automation. - or `````` for id not found - - Whats the difference: - A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. - Thus a route can be triggered by sending in for example ``````. + A throttle need to know which EXRAIL Automations and Routes it can show the user. + + `````` Returns a list of Automations/Routes + e.g. `````` + Indicates route/automation ids. + Information on each route needs to be obtained by + `````` + returns e.g. `````` for a route + or `````` for an automation. + or `````` for id not found + + What's the difference? + ----------------------- + +A *Route* is just a call to an **EXRAIL ROUTE**, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. + +Thus, a route can be triggered by sending in for example ``````. + + An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away. - An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away. Thus an Automation expects a start command with a cab id e.g. `````` @@ -68,10 +72,10 @@ Note: It is still the throttles responsibility to monitor the status broadcasts. COMMANDS TO AVOID ====================== - `````` Use `````` - `````` Just drop the slot number - `````` other than `````` - `````` + `````` Use `````` + `````` Just drop the slot number + `````` other than `````` + `````` `````` From d3eceb6d6c61e507304e5f85cb196405b99148f3 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 3 Mar 2023 21:41:22 -0500 Subject: [PATCH 652/870] Update ThrottleAssists.md --- Release_Notes/ThrottleAssists.md | 46 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md index e704b425b..205561881 100644 --- a/Release_Notes/ThrottleAssists.md +++ b/Release_Notes/ThrottleAssists.md @@ -13,14 +13,14 @@ The conventional turnout definition commands and the `````` responses do not `````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. e.g. response `````` -````` requests info on turnout 17. -e.g. response `````` or ````` -(T=thrown, C=closed) -or `````` indicating turnout description not given. +````` requests info on turnout 17.\ +e.g. response `````` or `````\ +(T=thrown, C=closed)\ +or `````` indicating turnout description not given.\ or `````` indicating turnout unknown (or possibly hidden.) -Note: It is still the throttles responsibility to monitor the status broadcasts. -(TBD I'm thinking that the existing broadcast is messy and needs cleaning up) +Note: It is still the throttles responsibility to monitor the status broadcasts.\ +(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)\ However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. @@ -30,13 +30,13 @@ Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "d A throttle need to know which EXRAIL Automations and Routes it can show the user. - `````` Returns a list of Automations/Routes - e.g. `````` - Indicates route/automation ids. - Information on each route needs to be obtained by - `````` - returns e.g. `````` for a route - or `````` for an automation. + `````` Returns a list of Automations/Routes\ + e.g. ``````\ + Indicates route/automation ids.\ + Information on each route needs to be obtained by\ + ``````\ + returns e.g. `````` for a route\ + or `````` for an automation.\ or `````` for id not found What's the difference? @@ -45,14 +45,15 @@ Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "d A *Route* is just a call to an **EXRAIL ROUTE**, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. Thus, a route can be triggered by sending in for example ``````. + +An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away. - An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away. - - Thus an Automation expects a start command with a cab id + Thus an Automation expects a start command with a cab id\ e.g. `````` - Roster Information: + Roster Information + ^^^^^^^^^^^^^^^^^^ The `````` command requests a list of cab ids from the roster. e.g. responding `````` or for none. @@ -63,7 +64,8 @@ Thus, a route can be triggered by sending in for example ``````. Refer to EXRAIL ROSTER command for function map format. - Obtaining throttle status. + Obtaining throttle status + ^^^^^^^^^^^^^^^^^^^^^^^^^^ `````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast. `````` Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled. @@ -72,10 +74,10 @@ Thus, a route can be triggered by sending in for example ``````. COMMANDS TO AVOID ====================== - `````` Use `````` - `````` Just drop the slot number - `````` other than `````` - `````` + `````` Use ``````\ + `````` Just drop the slot number\ + `````` other than ``````\ + ``````\ `````` From 98af5c45eda659ad48b5b29aeba173ec09897db7 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 3 Mar 2023 21:46:07 -0500 Subject: [PATCH 653/870] Update ThrottleAssists.md --- Release_Notes/ThrottleAssists.md | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md index 205561881..56677ae9f 100644 --- a/Release_Notes/ThrottleAssists.md +++ b/Release_Notes/ThrottleAssists.md @@ -13,7 +13,7 @@ The conventional turnout definition commands and the `````` responses do not `````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. e.g. response `````` -````` requests info on turnout 17.\ +````` requests info on turnout 17\ e.g. response `````` or `````\ (T=thrown, C=closed)\ or `````` indicating turnout description not given.\ @@ -24,7 +24,6 @@ Note: It is still the throttles responsibility to monitor the status broadcasts. However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. - Automations/Routes ==================== @@ -51,30 +50,31 @@ An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATIO Thus an Automation expects a start command with a cab id\ e.g. `````` +Roster Information +^^^^^^^^^^^^^^^^^^ - Roster Information - ^^^^^^^^^^^^^^^^^^ - The `````` command requests a list of cab ids from the roster. - e.g. responding `````` - or for none. +The `````` command requests a list of cab ids from the roster. +e.g. responding `````` +or for none. - Each Roster entry had a name and function map obtained by: - `````` reply like ``` +Each Roster entry had a name and function map obtained by: +`````` reply like ``` - Refer to EXRAIL ROSTER command for function map format. +Refer to EXRAIL ROSTER command for function map format. +Obtaining throttle status +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +`````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast. +`````` - Obtaining throttle status - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - `````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast. - `````` - Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled. +Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled. - COMMANDS TO AVOID - ====================== +COMMANDS TO AVOID +====================== - `````` Use ``````\ + `````` - Instead Use ``````\ `````` Just drop the slot number\ `````` other than ``````\ ``````\ From 91d36ae909b21eb7cbd215f4f5b6a85ab1181d83 Mon Sep 17 00:00:00 2001 From: Fred Date: Fri, 3 Mar 2023 21:59:18 -0500 Subject: [PATCH 654/870] Update ThrottleAssists.md --- Release_Notes/ThrottleAssists.md | 35 +++++++++++++------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/Release_Notes/ThrottleAssists.md b/Release_Notes/ThrottleAssists.md index 56677ae9f..19622fd40 100644 --- a/Release_Notes/ThrottleAssists.md +++ b/Release_Notes/ThrottleAssists.md @@ -3,18 +3,17 @@ Throttle Assist updates for versiuon 4.? Chris Harlow April 2022 There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations. -These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose. +These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose. -Turnouts -============ +# Turnouts The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons. `````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. e.g. response `````` -````` requests info on turnout 17\ -e.g. response `````` or `````\ +`````` requests info on turnout 17\ +e.g. response `````` or ``````\ (T=thrown, C=closed)\ or `````` indicating turnout description not given.\ or `````` indicating turnout unknown (or possibly hidden.) @@ -24,8 +23,7 @@ Note: It is still the throttles responsibility to monitor the status broadcasts. However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. - Automations/Routes - ==================== + # Automations/Routes A throttle need to know which EXRAIL Automations and Routes it can show the user. @@ -38,8 +36,7 @@ Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "d or `````` for an automation.\ or `````` for id not found - What's the difference? - ----------------------- + ## What's the difference? A *Route* is just a call to an **EXRAIL ROUTE**, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. @@ -50,31 +47,27 @@ An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATIO Thus an Automation expects a start command with a cab id\ e.g. `````` -Roster Information -^^^^^^^^^^^^^^^^^^ +### Roster Information -The `````` command requests a list of cab ids from the roster. -e.g. responding `````` +The `````` command requests a list of cab ids from the roster\ +e.g. responding ``````\ or for none. -Each Roster entry had a name and function map obtained by: +Each Roster entry had a name and function map obtained by:\ `````` reply like ``` Refer to EXRAIL ROSTER command for function map format. -Obtaining throttle status -^^^^^^^^^^^^^^^^^^^^^^^^^^ +### Obtaining throttle status -`````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast. +`````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast\ `````` Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled. +# COMMANDS TO AVOID -COMMANDS TO AVOID -====================== - - `````` - Instead Use ``````\ + `````` Instead Use ``````\ `````` Just drop the slot number\ `````` other than ``````\ ``````\ From 46070e29992b050c184ff7a06c8392a19adb2a68 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 4 Mar 2023 18:55:13 +1000 Subject: [PATCH 655/870] Non-blocking implemented --- IO_EXIOExpander.h | 19 ++++++++++--------- version.h | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 063b5dd29..0296c9337 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -82,7 +82,7 @@ class EXIOExpander : public IODevice { _command4Buffer[2] = _firstVpin & 0xFF; _command4Buffer[3] = _firstVpin >> 8; // Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4); + I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4, &_i2crb); if (_receive3Buffer[0] == EXIOPINS) { _numDigitalPins = _receive3Buffer[1]; _numAnaloguePins = _receive3Buffer[2]; @@ -98,10 +98,10 @@ class EXIOExpander : public IODevice { } // We now need to retrieve the analogue pin map _command1Buffer[0] = EXIOINITA; - I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1); + I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1, &_i2crb); // Attempt to get version, if we don't get it, we don't care, don't go offline _command1Buffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1); + I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1, &_i2crb); _majorVer = _versionBuffer[0]; _minorVer = _versionBuffer[1]; _patchVer = _versionBuffer[2]; @@ -125,7 +125,7 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIODPUP; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = pullup; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3, &_i2crb); if (_command1Buffer[0] == EXIORDY) { return true; } else { @@ -142,7 +142,7 @@ class EXIOExpander : public IODevice { int pin = vpin - _firstVpin; _command2Buffer[0] = EXIOENAN; _command2Buffer[1] = pin; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2, &_i2crb); if (_command1Buffer[0] == EXIORDY) { return true; } else { @@ -157,9 +157,9 @@ class EXIOExpander : public IODevice { (void)currentMicros; // remove warning if (_deviceState == DEVSTATE_FAILED) return; _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1); + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); _command1Buffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1); + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1, &_i2crb); } // Obtain the correct analogue input value @@ -191,7 +191,7 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3, &_i2crb); if (_command1Buffer[0] != EXIORDY) { DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); } @@ -211,7 +211,7 @@ class EXIOExpander : public IODevice { _servoBuffer[4] = profile; _servoBuffer[5] = duration & 0xFF; _servoBuffer[6] = duration >> 8; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7); + I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7, &_i2crb); if (_command1Buffer[0] != EXIORDY) { DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin); } @@ -242,6 +242,7 @@ class EXIOExpander : public IODevice { byte _receive3Buffer[3]; byte _servoBuffer[7]; uint8_t* _analoguePinMap; + I2CRB _i2crb; // EX-IOExpander protocol flags enum { diff --git a/version.h b/version.h index a97aaec16..eebf8edd8 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.20" +#define VERSION "4.2.21" +// 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver // 4.2.20 - & commands for multi-track gauges // - Reinstate but remember its a bit useless when TM involved. // 4.2.19 - Bugfix for analog reading of track current sensor offset. From bec8aea5a548fdd6a3c80be5335bc39067fa3a6d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 6 Mar 2023 11:57:14 +0000 Subject: [PATCH 656/870] TM Broadcasts TM changes will trigger TM state broadcasts --- .gitignore | 1 + CommandDistributor.cpp | 4 +++ CommandDistributor.h | 1 + TrackManager.cpp | 66 ++++++++++++++++++++++-------------------- TrackManager.h | 2 ++ version.h | 3 +- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index c8e40c222..8f01818fb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ my*.h !my*.example.h .vscode/extensions.json .vscode/extensions.json +compile_commands.json diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 0c9185dbf..ad11b2229 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -233,3 +233,7 @@ void CommandDistributor::broadcastText(const FSH * msg) { broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg); #endif } + +void CommandDistributor::broadcastTrackState(FSH* format,byte trackLetter,int16_t dcAddr) { + broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr); +} diff --git a/CommandDistributor.h b/CommandDistributor.h index bbbc44c45..a5751e9f2 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -51,6 +51,7 @@ public : static int16_t retClockTime(); static void broadcastPower(); static void broadcastText(const FSH * msg); + static void broadcastTrackState(FSH* format,byte trackLetter,int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); diff --git a/TrackManager.cpp b/TrackManager.cpp index 33d1d8ae3..633ed06d4 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -25,6 +25,7 @@ #include "MotorDriver.h" #include "DCCTimer.h" #include "DIAG.h" +#include"CommandDistributor.h" // Virtualised Motor shield multi-track hardware Interface #define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++) @@ -203,6 +204,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[t]->setPower(POWERMODE::OFF); trackMode[t]=TRACK_MODE_OFF; track[t]->makeProgTrack(false); // revoke prog track special handling + streamTrackState(NULL,t); } track[trackToSet]->makeProgTrack(true); // set for prog track special handling } else { @@ -210,7 +212,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } trackMode[trackToSet]=mode; trackDCAddr[trackToSet]=dcAddr; - + streamTrackState(NULL,trackToSet); + // When a track is switched, we must clear any side effects of its previous // state, otherwise trains run away or just dont move. @@ -290,36 +293,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) if (params==0) { // <=> List track assignments FOR_EACH_TRACK(t) - if (track[t]!=NULL) { - StringFormatter::send(stream,F("<= %c "),'A'+t); - switch(trackMode[t]) { - case TRACK_MODE_MAIN: - StringFormatter::send(stream,F("MAIN")); - if (track[t]->trackPWM) - StringFormatter::send(stream,F("+")); - break; - case TRACK_MODE_PROG: - StringFormatter::send(stream,F("PROG")); - if (track[t]->trackPWM) - StringFormatter::send(stream,F("+")); - break; - case TRACK_MODE_OFF: - StringFormatter::send(stream,F("OFF")); - break; - case TRACK_MODE_EXT: - StringFormatter::send(stream,F("EXT")); - break; - case TRACK_MODE_DC: - StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]); - break; - case TRACK_MODE_DCX: - StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]); - break; - default: - break; // unknown, dont care - } - StringFormatter::send(stream,F(">\n")); - } + streamTrackState(stream,t); return true; } @@ -349,6 +323,36 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) return false; } +void TrackManager::streamTrackState(Print* stream, byte t) { + // null stream means send to commandDistributor for broadcast + if (track[t]==NULL) return; + auto format=F(""); + switch(trackMode[t]) { + case TRACK_MODE_MAIN: + format=F("<= %c MAIN>\n"); + break; + case TRACK_MODE_PROG: + format=F("<= %c PROG>\n"); + break; + case TRACK_MODE_OFF: + format=F("<= %c OFF>\n"); + break; + case TRACK_MODE_EXT: + format=F("<= %c EXT>\n"); + break; + case TRACK_MODE_DC: + format=F("<= %c DC %d>\n"); + break; + case TRACK_MODE_DCX: + format=F("<= %c DCX %d>\n"); + break; + default: + break; // unknown, dont care + } + if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]); + else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]); +} + byte TrackManager::nextCycleTrack=MAX_TRACKS; void TrackManager::loop() { diff --git a/TrackManager.h b/TrackManager.h index 4a548cc69..ef4a47c1f 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -80,6 +80,8 @@ class TrackManager { static void reportGauges(Print* stream); static void reportCurrent(Print* stream); static void reportObsoleteCurrent(Print* stream); + static void streamTrackState(Print* stream, byte t); + static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main static bool progTrackBoosted; // true when prog track is not current limited diff --git a/version.h b/version.h index eebf8edd8..789ef2caa 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.21" +#define VERSION "4.2.22" +// 4.2.22 - Implement broadcast of Track Manager changes // 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver // 4.2.20 - & commands for multi-track gauges // - Reinstate but remember its a bit useless when TM involved. From 1d2943600820d758d083efe7ab65ed65b7909194 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 6 Mar 2023 13:47:59 +0000 Subject: [PATCH 657/870] Compiler pedantics --- CommandDistributor.cpp | 2 +- CommandDistributor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index ad11b2229..1714b735d 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -234,6 +234,6 @@ void CommandDistributor::broadcastText(const FSH * msg) { #endif } -void CommandDistributor::broadcastTrackState(FSH* format,byte trackLetter,int16_t dcAddr) { +void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) { broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr); } diff --git a/CommandDistributor.h b/CommandDistributor.h index a5751e9f2..e1dbfeca6 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -51,7 +51,7 @@ public : static int16_t retClockTime(); static void broadcastPower(); static void broadcastText(const FSH * msg); - static void broadcastTrackState(FSH* format,byte trackLetter,int16_t dcAddr); + static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); From f94a5f971ef47338f648439925a432130a3a6a90 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 7 Mar 2023 16:20:00 +0100 Subject: [PATCH 658/870] Bugfix signalpin2 was not set up in shadow port --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 17 +++++++++++++++++ version.h | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 75a9be664..54a79052d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202302281422Z" +#define GITHUB_SHA "devel-202303071416Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a6a17663d..02362151d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -75,6 +75,23 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i dualSignal=true; getFastPin(F("SIG2"),signalPin2,fastSignalPin2); pinMode(signalPin2, OUTPUT); + + fastSignalPin2.shadowinout = NULL; + if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) { + DIAG(F("Found PORTA pin %d"),signalPin2); + fastSignalPin2.shadowinout = fastSignalPin2.inout; + fastSignalPin2.inout = &shadowPORTA; + } + if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) { + DIAG(F("Found PORTB pin %d"),signalPin2); + fastSignalPin2.shadowinout = fastSignalPin2.inout; + fastSignalPin2.inout = &shadowPORTB; + } + if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) { + DIAG(F("Found PORTC pin %d"),signalPin2); + fastSignalPin2.shadowinout = fastSignalPin2.inout; + fastSignalPin2.inout = &shadowPORTC; + } } else dualSignal=false; diff --git a/version.h b/version.h index 789ef2caa..a4520fb62 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.22" +#define VERSION "4.2.23" +// 4.2.23 - Bugfix signalpin2 was not set up in shadow port // 4.2.22 - Implement broadcast of Track Manager changes // 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver // 4.2.20 - & commands for multi-track gauges From 59c6c1e5afefbcaf7c4a5d2d0dd2d7a261481d12 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Mar 2023 15:59:25 +0000 Subject: [PATCH 659/870] Update examples and comments. --- config.example.h | 4 +-- myHal.cpp_example.txt | 78 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/config.example.h b/config.example.h index b253ab319..2a8a11acf 100644 --- a/config.example.h +++ b/config.example.h @@ -125,10 +125,10 @@ The configuration file for DCC-EX Command Station // 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) +//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied) // 128x32 or 128x64 I2C SSD1306-based devices are supported. // Use 132,64 for a SH1106-based I2C device with a 128x64 display. -// #define OLED_DRIVER 128,32 +// #define OLED_DRIVER 128,32,0x3c // Define scroll mode as 0, 1 or 2 // * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index b9e68b7c6..d93ea5c43 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -17,9 +17,11 @@ // Include devices you need. #include "IODevice.h" -#include "IO_HCSR04.h" // Ultrasonic range sensor -#include "IO_VL53L0X.h" // Laser time-of-flight sensor -#include "IO_DFPlayer.h" // MP3 sound player +//#include "IO_HALDisplay.h" // Auxiliary display devices (LCD/OLED) +//#include "IO_HCSR04.h" // Ultrasonic range sensor +//#include "IO_VL53L0X.h" // Laser time-of-flight sensor +//#include "IO_DFPlayer.h" // MP3 sound player +//#include "IO_TouchKeypad.h // Touch keypad with 16 keys //#include "IO_EXTurntable.h" // Turntable-EX turntable controller //#include "IO_EXFastClock.h" // FastClock driver @@ -31,6 +33,61 @@ void halSetup() { + //======================================================================= + // The following directives define auxiliary display devices. + // These can be defined in addition to the system display (display + // number 0) that is defined in config.h. + // A write to a line which is beyond the length of the screen will overwrite + // the bottom line, unless the line number is 255 in which case the + // screen contents will scroll up before the text is written to the + // bottom line. + //======================================================================= + // + // Create a 128x32 OLED display device as display number 1 + // (line 0 is written by EX-RAIL 'SCREEN(1, 0, "text")'). + + //HALDisplay::create(1, 0x3d, 128, 32); + + // Create a 20x4 LCD display device as display number 2 + // (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")'). + + // HALDisplay(2, 0x27, 20, 4); + + + //======================================================================= + // User Add-ins + //======================================================================= + // User add-ins can be created when you want to do something that + // can't be done in EX-RAIL but does not merit a HAL driver. The + // user add-in is a C++ function that is executed periodically by the + // HAL subsystem. + + // Example: The function will be executed once per second and will display, + // on screen #3, the first eight entries (assuming an 8-line display) + // from the loco speed table. + + // Put the following block of code in myHal.cpp OUTSIDE of the + // halSetup() function: + // + // void updateLocoScreen() { + // for (int i=0; i<8; i++) { + // if (DCC::speedTable[i].loco > 0) { + // int speed = DCC::speedTable[i].speedCode; + // char direction = (speed & 0x80) ? 'R' : 'F'; + // speed = speed & 0x7f; + // if (speed > 0) speed = speed - 1; + // SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco, + // speed, direction); + // } + // } + // } + // + // Put the following line INSIDE the halSetup() function: + // + // UserAddin::create(updateLocoScreen, 1000); + // + + //======================================================================= // The following directive defines a PCA9685 PWM Servo driver module. //======================================================================= @@ -176,6 +233,21 @@ void halSetup() { // DFPlayer::create(10000, 10, Serial1); + //======================================================================= + // 16-pad capacitative touch key pad based on TP229 IC. + //======================================================================= + // Parameters below: + // 11000 = first VPIN allocated + // 16 = number of VPINs allocated + // 25 = local GPIO pin number for clock signal + // 24 = local GPIO pin number for data signal + // + // Pressing the key pads numbered 1-16 cause each of the nominated digital VPINs + // (11000-11015 in this case) to be activated. + + // TouchKeypad::create(11000, 16, 25, 24); + + //======================================================================= // The following directive defines an EX-Turntable turntable instance. //======================================================================= From 679e5885c4553c62ceaaf06d1f0c8f26205f97c8 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Mar 2023 15:59:52 +0000 Subject: [PATCH 660/870] Update IO_HALDisplay.h Enable I2C address overlap check on HAL display. --- IO_HALDisplay.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IO_HALDisplay.h b/IO_HALDisplay.h index 5ef9efca0..f2ca3af62 100644 --- a/IO_HALDisplay.h +++ b/IO_HALDisplay.h @@ -82,10 +82,10 @@ class HALDisplay : public IODevice, public DisplayInterface { public: // Static function to handle "HALDisplay::create(...)" calls. static void create(I2CAddress i2cAddress, int width, int height) { - /* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height); + if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(0, i2cAddress, width, height); } static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) { - /* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height); + if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(displayNo, i2cAddress, width, height); } protected: From 471b8ac8e11d225d2e81e3adf2df91459a9a1b67 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Thu, 9 Mar 2023 16:28:07 +0000 Subject: [PATCH 661/870] Update I2CManager.cpp Rearrange I2C short-circuit check to before I2C is initialised. --- I2CManager.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index 6d5db41ee..aad641869 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -72,18 +72,23 @@ static const FSH * guessI2CDeviceType(uint8_t address) { void I2CManagerClass::begin(void) { if (!_beginCompleted) { _beginCompleted = true; + + // Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C + const FSH *message = F("WARNING: Possible short-circuit or inadequate pullup on I2C %S line"); + pinMode(SDA, INPUT); + if (!digitalRead(SDA)) + DIAG(message, F("SDA")); + pinMode(SCL, INPUT); + if (!digitalRead(SCL)) + DIAG(message, F("SCL")); + + // Now initialise I2C _initialise(); #if defined(I2C_USE_WIRE) DIAG(F("I2CManager: Using Wire library")); #endif - // Check for short-circuits on I2C - if (!digitalRead(SDA)) - DIAG(F("WARNING: Possible short-circuit on I2C SDA line")); - if (!digitalRead(SCL)) - DIAG(F("WARNING: Possible short-circuit on I2C SCL line")); - // Probe and list devices. Use standard mode // (clock speed 100kHz) for best device compatibility. _setClock(100000); From 45cf610028f9d8fc27ffe4859f148beeb7dd119d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 10 Mar 2023 17:49:51 +0100 Subject: [PATCH 662/870] Bugfix Ethernet shield: Static IP now possible --- EthernetInterface.cpp | 2 +- GITHUB_SHA.h | 2 +- version.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index 7c5d7685b..5cf531c99 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -136,7 +136,7 @@ bool EthernetInterface::checkLink() { DIAG(F("Ethernet cable connected")); connected=true; #ifdef IP_ADDRESS - setLocalIP(IP_ADDRESS); // for static IP, set it again + Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again #endif IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static) server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 54a79052d..db56eac22 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303071416Z" +#define GITHUB_SHA "devel-202303101548Z" diff --git a/version.h b/version.h index a4520fb62..6e2d0ba8a 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.23" +#define VERSION "4.2.24" +// 4.3.24 - Bugfix Ethernet shield: Static IP now possible // 4.2.23 - Bugfix signalpin2 was not set up in shadow port // 4.2.22 - Implement broadcast of Track Manager changes // 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver From 5e60fb4e2701e5ad1d1099f3e4e6d655c9b46df6 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 11 Mar 2023 22:46:11 +0000 Subject: [PATCH 663/870] SAMD21 odd byte boundary --- EXRAIL2.cpp | 9 +++------ version.h | 5 +++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 3bd8da7ff..8541cd248 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -105,12 +105,9 @@ uint16_t RMFT2::getOperand(byte n) { // getOperand static version, must be provided prog counter from loop etc. uint16_t RMFT2::getOperand(int progCounter,byte n) { int offset=progCounter+1+(n*3); - if (offset&1) { - byte lsb=GETHIGHFLASH(RouteCode,offset); - byte msb=GETHIGHFLASH(RouteCode,offset+1); - return msb<<8|lsb; - } - return GETHIGHFLASHW(RouteCode,offset); + byte lsb=GETHIGHFLASH(RouteCode,offset); + byte msb=GETHIGHFLASH(RouteCode,offset+1); + return msb<<8|lsb; } LookList::LookList(int16_t size) { diff --git a/version.h b/version.h index 6e2d0ba8a..8afe38acb 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.24" -// 4.3.24 - Bugfix Ethernet shield: Static IP now possible +#define VERSION "4.2.25" +// 4.2.25 - Bugfix SAMD21 Exrail odd byte boundary +// 4.2.24 - Bugfix Ethernet shield: Static IP now possible // 4.2.23 - Bugfix signalpin2 was not set up in shadow port // 4.2.22 - Implement broadcast of Track Manager changes // 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver From 0cc07ed1dff8afd1d9727bb911bf9e3b957698ed Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 13 Mar 2023 05:29:22 +1000 Subject: [PATCH 664/870] Starting on driver feedback --- IO_EXIOExpander.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 0296c9337..da01ae760 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -156,10 +156,15 @@ class EXIOExpander : public IODevice { void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning if (_deviceState == DEVSTATE_FAILED) return; - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); - _command1Buffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1, &_i2crb); + if (_i2crb.isBusy()) return; + if (_commandFlag) { + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + } else { + _command1Buffer[0] = EXIORDAN; + I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1, &_i2crb); + } + _commandFlag = !_commandFlag; } // Obtain the correct analogue input value @@ -243,6 +248,7 @@ class EXIOExpander : public IODevice { byte _servoBuffer[7]; uint8_t* _analoguePinMap; I2CRB _i2crb; + bool _commandFlag = 0; // EX-IOExpander protocol flags enum { From 95d01202045dceda8babb746dfa2015a28aabf7a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 13 Mar 2023 08:38:28 +1000 Subject: [PATCH 665/870] Implement status checks --- IO_EXIOExpander.h | 49 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index da01ae760..ddcdf56b3 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -155,16 +155,25 @@ class EXIOExpander : public IODevice { // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { (void)currentMicros; // remove warning - if (_deviceState == DEVSTATE_FAILED) return; - if (_i2crb.isBusy()) return; - if (_commandFlag) { - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return + uint8_t status = _i2crb.status; + if (status == I2C_STATUS_PENDING) return; // If device busy, return + if (status == I2C_STATUS_OK) { // If device ok, read input data + if (_commandFlag) { + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + } else { + _command1Buffer[0] = EXIORDAN; + byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state + I2CManager.read(_i2cAddress, _tempAnalogue, _analoguePinBytes, _command1Buffer, 1, &_i2crb); + memcpy(_analogueInputStates, _tempAnalogue, _analoguePinBytes); // Copy temp buffer to states + } + _commandFlag = !_commandFlag; + // Need to delay here: digital in IO_Base 4000UL, analogue in IO_AnalogueInputs 10000UL (fast) or 1000000UL(slow) } else { - _command1Buffer[0] = EXIORDAN; - I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1, &_i2crb); + DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; } - _commandFlag = !_commandFlag; } // Obtain the correct analogue input value @@ -196,9 +205,14 @@ class EXIOExpander : public IODevice { _digitalOutBuffer[0] = EXIOWRD; _digitalOutBuffer[1] = pin; _digitalOutBuffer[2] = value; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3, &_i2crb); - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + uint8_t status = I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + if (status != I2C_STATUS_OK) { + DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; + } else { + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + } } } @@ -216,9 +230,14 @@ class EXIOExpander : public IODevice { _servoBuffer[4] = profile; _servoBuffer[5] = duration & 0xFF; _servoBuffer[6] = duration >> 8; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7, &_i2crb); - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin); + uint8_t status = I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7); + if (status != I2C_STATUS_OK) { + DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); + _deviceState = DEVSTATE_FAILED; + } else { + if (_command1Buffer[0] != EXIORDY) { + DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin); + } } } @@ -248,7 +267,7 @@ class EXIOExpander : public IODevice { byte _servoBuffer[7]; uint8_t* _analoguePinMap; I2CRB _i2crb; - bool _commandFlag = 0; + bool _commandFlag = 1; // EX-IOExpander protocol flags enum { From d89dd0d1fa4a05161b5ac9955c7dd893e070f95d Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 13 Mar 2023 00:53:42 +0000 Subject: [PATCH 666/870] command ref work in progress --- Release_Notes/CommandRef.md | 76 +++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/Release_Notes/CommandRef.md b/Release_Notes/CommandRef.md index 6420471f1..c06e388e0 100644 --- a/Release_Notes/CommandRef.md +++ b/Release_Notes/CommandRef.md @@ -4,24 +4,37 @@ General points: - Commands below have a single character opcode and parameters. Even is actually read as - Keyword parameters are shown in upper case but may be entered in mixed case. - - value parameters are numeric. + - value parameters are decimal numeric (unless otherwise noted) - [something] indicates its optional. - - Not all commands have a response, and not all responses come from the last commands that you have issued. + - Not all commands have a response, and broadcasts mean that not all responses come from the last commands that you have issued. Startup status - + Return status like + + also returns defined turnout list: + 1=thrown -Track power management -<1> -<1 MAIN|PROG|JOIN> -<0> -<0 MAIN|PROG> +Track power management. After power commands a power state is broadcast to all throttles. + +<1> Power on all +<1 MAIN|PROG|JOIN> Power on MAIN or PROG track +<1 JOIN> Power on MAIN and PROG track but send main track data on both. +<0> Power off all tracks +<0 MAIN|PROG> Power off main or prog track + +Basic manual loco control + Throttle loco. + speed in JMRI-form (-1=ESTOP, 0=STOP, 1..126 = DCC speeds 2..127) + direction 1=forward, 0=reverse + For response see broadcast + + Set loco function 1=ON, 0-OFF + For response see broadcast + + emergency stop all locos + Control turnout id, 0=C=Closed, 1=T=Thrown + response broadcast -Basic manual control - - - - DCC accessory control @@ -35,25 +48,30 @@ Note: Turnouts are best defined in myAutomation.h where a turnout description ca + Valid commands respond with + +Outputs (Used by JMRI, not required by EXRAIL) + Define an output pin that JMRI can set by id + Activate an output pin by id + + +Sensors (Used by JMRI, not required by EXRAIL) + define a sensor to be monitored. + Responses and as sensor changes +Decoder programming - main track + POM write value to cv on loco + POM write bit to cv on loco -Outputs - - +Decoder Programming - prog track + Clear consist and write new cab id (includes long/short settings) + Responds or for error + Write value to cv -Sensors - + Read cv value, much faster if prediction is correct. + Read CV bit -Decoder programming - - - - - - - - - + Read drive-away loco id. (May be a consist id) @@ -152,6 +170,8 @@ Obsolete commands/formats + V command is much faster if prediction is correct. + V command is much faster if prediction is correct. Broadcast responses Note: broadcasts are sent to all throttles when appropriate (usually because something has changed) From df3eb11eb9e0d0f721c22937b03af6c9f0cb4d11 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:19:20 +1000 Subject: [PATCH 667/870] Add read refresh delays --- .vscode/settings.json | 3 ++- IO_EXIOExpander.h | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ffa498ada..6f7d8280f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "string_view": "cpp", "initializer_list": "cpp", "cstdint": "cpp" - } + }, + "cmake.configureOnOpen": false } diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index ddcdf56b3..9efe8f9e6 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -154,19 +154,24 @@ class EXIOExpander : public IODevice { // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { - (void)currentMicros; // remove warning if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return uint8_t status = _i2crb.status; if (status == I2C_STATUS_PENDING) return; // If device busy, return if (status == I2C_STATUS_OK) { // If device ok, read input data if (_commandFlag) { - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay 10ms for digital read refresh + _lastDigitalRead = currentMicros; + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + } } else { - _command1Buffer[0] = EXIORDAN; - byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state - I2CManager.read(_i2cAddress, _tempAnalogue, _analoguePinBytes, _command1Buffer, 1, &_i2crb); - memcpy(_analogueInputStates, _tempAnalogue, _analoguePinBytes); // Copy temp buffer to states + if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay 50ms for analogue read refresh + _lastAnalogueRead = currentMicros; + _command1Buffer[0] = EXIORDAN; + byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state + I2CManager.read(_i2cAddress, _tempAnalogue, _analoguePinBytes, _command1Buffer, 1, &_i2crb); + memcpy(_analogueInputStates, _tempAnalogue, _analoguePinBytes); // Copy temp buffer to states + } } _commandFlag = !_commandFlag; // Need to delay here: digital in IO_Base 4000UL, analogue in IO_AnalogueInputs 10000UL (fast) or 1000000UL(slow) @@ -268,6 +273,10 @@ class EXIOExpander : public IODevice { uint8_t* _analoguePinMap; I2CRB _i2crb; bool _commandFlag = 1; + unsigned long _lastDigitalRead = 0; + unsigned long _lastAnalogueRead = 0; + const unsigned long _digitalRefresh = 10000UL; + const unsigned long _analogueRefresh = 50000UL; // EX-IOExpander protocol flags enum { From c83741d2b4a29e1dfa71dfb7184a617b1e270eec Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:20:27 +1000 Subject: [PATCH 668/870] Add read refresh delays --- IO_EXIOExpander.h | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index ddcdf56b3..9efe8f9e6 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -154,19 +154,24 @@ class EXIOExpander : public IODevice { // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { - (void)currentMicros; // remove warning if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return uint8_t status = _i2crb.status; if (status == I2C_STATUS_PENDING) return; // If device busy, return if (status == I2C_STATUS_OK) { // If device ok, read input data if (_commandFlag) { - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay 10ms for digital read refresh + _lastDigitalRead = currentMicros; + _command1Buffer[0] = EXIORDD; + I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); + } } else { - _command1Buffer[0] = EXIORDAN; - byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state - I2CManager.read(_i2cAddress, _tempAnalogue, _analoguePinBytes, _command1Buffer, 1, &_i2crb); - memcpy(_analogueInputStates, _tempAnalogue, _analoguePinBytes); // Copy temp buffer to states + if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay 50ms for analogue read refresh + _lastAnalogueRead = currentMicros; + _command1Buffer[0] = EXIORDAN; + byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state + I2CManager.read(_i2cAddress, _tempAnalogue, _analoguePinBytes, _command1Buffer, 1, &_i2crb); + memcpy(_analogueInputStates, _tempAnalogue, _analoguePinBytes); // Copy temp buffer to states + } } _commandFlag = !_commandFlag; // Need to delay here: digital in IO_Base 4000UL, analogue in IO_AnalogueInputs 10000UL (fast) or 1000000UL(slow) @@ -268,6 +273,10 @@ class EXIOExpander : public IODevice { uint8_t* _analoguePinMap; I2CRB _i2crb; bool _commandFlag = 1; + unsigned long _lastDigitalRead = 0; + unsigned long _lastAnalogueRead = 0; + const unsigned long _digitalRefresh = 10000UL; + const unsigned long _analogueRefresh = 50000UL; // EX-IOExpander protocol flags enum { From ca2e5e6ce3c956af843357e4dd594c175089a5c4 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:21:55 +1000 Subject: [PATCH 669/870] Undo vscode change --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f7d8280f..ffa498ada 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,5 @@ "string_view": "cpp", "initializer_list": "cpp", "cstdint": "cpp" - }, - "cmake.configureOnOpen": false + } } From 5fc925bfc6d6431f90a072219e2d14e3dcd56979 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:23:42 +1000 Subject: [PATCH 670/870] Delete extensions.json --- .vscode/extensions.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 080e70d08..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} From a51cefbdeb60d1bdabebfa234f1c5378878b0760 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:23:55 +1000 Subject: [PATCH 671/870] Delete settings.json --- .vscode/settings.json | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ffa498ada..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "files.associations": { - "array": "cpp", - "deque": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "string_view": "cpp", - "initializer_list": "cpp", - "cstdint": "cpp" - } -} From 42bddb587ed7500de89eb37110c68cf03695c70a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:25:30 +1000 Subject: [PATCH 672/870] Update .gitignore --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index 8f01818fb..62373592d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,16 +7,9 @@ Release/* .pio/ .vscode/ config.h -.vscode/* -# mySetup.h mySetup.cpp myHal.cpp -# myAutomation.h myFilter.cpp -# myAutomation.h -# myLayout.h my*.h !my*.example.h -.vscode/extensions.json -.vscode/extensions.json compile_commands.json From 25676aab6b50a480807f7339672a4853245129ea Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:32:08 +1000 Subject: [PATCH 673/870] Update comments --- IO_EXIOExpander.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 9efe8f9e6..84433b749 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -159,13 +159,13 @@ class EXIOExpander : public IODevice { if (status == I2C_STATUS_PENDING) return; // If device busy, return if (status == I2C_STATUS_OK) { // If device ok, read input data if (_commandFlag) { - if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay 10ms for digital read refresh + if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh _lastDigitalRead = currentMicros; _command1Buffer[0] = EXIORDD; I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); } } else { - if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay 50ms for analogue read refresh + if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh _lastAnalogueRead = currentMicros; _command1Buffer[0] = EXIORDAN; byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state @@ -174,7 +174,6 @@ class EXIOExpander : public IODevice { } } _commandFlag = !_commandFlag; - // Need to delay here: digital in IO_Base 4000UL, analogue in IO_AnalogueInputs 10000UL (fast) or 1000000UL(slow) } else { DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; @@ -275,8 +274,8 @@ class EXIOExpander : public IODevice { bool _commandFlag = 1; unsigned long _lastDigitalRead = 0; unsigned long _lastAnalogueRead = 0; - const unsigned long _digitalRefresh = 10000UL; - const unsigned long _analogueRefresh = 50000UL; + const unsigned long _digitalRefresh = 10000UL; // Delay refreshing digital inputs for 10ms + const unsigned long _analogueRefresh = 50000UL; // Delay refreshing analogue inputs for 50ms // EX-IOExpander protocol flags enum { From 48cd567bda87482d7e5e5287d9290ca0c96a98eb Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 14 Mar 2023 19:04:08 +1000 Subject: [PATCH 674/870] Update version after testing --- version.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 8afe38acb..21f8e5ee4 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,11 @@ #include "StringFormatter.h" -#define VERSION "4.2.25" +#define VERSION "4.2.26" +// 4.2.26 - EX-IOExpander device driver enhancements +// - Enhance I2C error checking +// - Introduce delays to _loop to allow room for other I2C device comms +// - Improve analogue read reliability // 4.2.25 - Bugfix SAMD21 Exrail odd byte boundary // 4.2.24 - Bugfix Ethernet shield: Static IP now possible // 4.2.23 - Bugfix signalpin2 was not set up in shadow port From 27ba55198679610b37cd2c2542fc5d3ab6f21b85 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 14 Mar 2023 20:50:24 +0100 Subject: [PATCH 675/870] Bugfix LCD showed random characters in SCROLLMODE 2 --- Display.cpp | 15 +++++++++------ GITHUB_SHA.h | 2 +- version.h | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Display.cpp b/Display.cpp index 2c447781c..a1a1ec145 100644 --- a/Display.cpp +++ b/Display.cpp @@ -1,5 +1,6 @@ /* * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. + * © 2023, Harald Barth. * * This file is part of CommandStation-EX * @@ -52,7 +53,7 @@ Display::Display(DisplayDevice *deviceDriver) { _deviceDriver = deviceDriver; // Get device dimensions in characters (e.g. 16x2). numCharacterColumns = _deviceDriver->getNumCols(); - numCharacterRows = _deviceDriver->getNumRows();; + numCharacterRows = _deviceDriver->getNumRows(); for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) rowBuffer[row][0] = '\0'; topRow = ROW_INITIAL; // loop2 will fill from row 0 @@ -173,16 +174,18 @@ bool Display::findNextNonBlankRow() { rowNext = 0; else rowNext = rowNext + 1; - if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL; #if SCROLLMODE == 1 - // Finished if we've looped back to start - if (rowNext == ROW_INITIAL) { + if (rowNext >= MAX_CHARACTER_ROWS) { + // Finished if we've looped back to start + rowNext = ROW_INITIAL; noMoreRowsToDisplay = true; return false; } #else - // Finished if we're back to the first one shown + if (rowNext >= MAX_CHARACTER_ROWS) + rowNext = 0; if (rowNext == rowFirst) { + // Finished if we're back to the first one shown noMoreRowsToDisplay = true; return false; } @@ -193,4 +196,4 @@ bool Display::findNextNonBlankRow() { } } return false; -} \ No newline at end of file +} diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index db56eac22..be808f854 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303101548Z" +#define GITHUB_SHA "devel-202303141949Z" diff --git a/version.h b/version.h index 21f8e5ee4..b8f547f4f 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.26" +#define VERSION "4.2.27" +// 4.2.27 - Bugfix LCD showed random characters in SCROLLMODE 2 // 4.2.26 - EX-IOExpander device driver enhancements // - Enhance I2C error checking // - Introduce delays to _loop to allow room for other I2C device comms From 325d4bce73fe35c2eeef0449100321e751ad5c66 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Mar 2023 09:20:42 +0000 Subject: [PATCH 676/870] Update IO_DFPlayer.h Rework delay between command: instead of sending null characters, which doesn't work on some chip sets, send commands from the _loop() function with a 100ms delay between consecutive commands. --- IO_DFPlayer.h | 91 +++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 4439fbc5d..51dcee328 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -75,20 +75,9 @@ class DFPlayer : public IODevice { bool _playing = false; uint8_t _inputIndex = 0; unsigned long _commandSendTime; // Allows timeout processing - uint8_t _lastVolumeLevel = MAXVOLUME; - - // When two commands are sent in quick succession, the device sometimes - // fails to execute one. A delay is required between successive commands. - // This could be implemented by buffering commands and outputting them - // from the loop() function, but it would somewhat complicate the - // driver. A simpler solution is to output a number of NUL pad characters - // between successive command strings if there isn't sufficient elapsed time - // between them. At 9600 baud, each pad character takes approximately - // 1ms to complete. Experiments indicate that the minimum number of pads - // for reliable operation is 17. This gives 17.7ms between the end of one - // command and the beginning of the next, or 28ms between successive commands - // being completed. I've allowed 20 characters, which is almost 21ms. - const int numPadCharacters = 20; // Number of pad characters between commands + uint8_t _requestedVolumeLevel = MAXVOLUME; + uint8_t _currentVolume = MAXVOLUME; + int _requestedSong = -1; // -1=none, 0=stop, >0=file number public: @@ -148,11 +137,39 @@ class DFPlayer : public IODevice { } else _inputIndex = 0; // Unrecognised character sequence, start again! } + // Check if the initial prompt to device has timed out. Allow 5 seconds if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) { DIAG(F("DFPlayer device not responding on serial port")); _deviceState = DEVSTATE_FAILED; } + + // When two commands are sent in quick succession, the device will often fail to + // execute one. Testing has indicated that a delay of 100ms or more is required + // between successive commands to get reliable operation. + // If 100ms has elapsed since the last thing sent, then check if there's some output to do. + if (currentMicros - _commandSendTime > 100000UL) { + if (_currentVolume > _requestedVolumeLevel) { + // Change volume before changing song if volume is reducing. + _currentVolume = _requestedVolumeLevel; + sendPacket(0x06, _currentVolume); + _commandSendTime = currentMicros; + } else if (_requestedSong > 0) { + // Change song + sendPacket(0x03, _requestedSong); + _requestedSong = -1; + _commandSendTime = currentMicros; + } else if (_requestedSong == 0) { + sendPacket(0x16); // Stop playing + _requestedSong = -1; + _commandSendTime = currentMicros; + } else if (_currentVolume < _requestedVolumeLevel) { + // Change volume after changing song if volume is increasing. + _currentVolume = _requestedVolumeLevel; + sendPacket(0x06, _currentVolume); + _commandSendTime = currentMicros; + } + } delayUntil(currentMicros + 10000); // Only enter every 10ms } @@ -165,14 +182,14 @@ class DFPlayer : public IODevice { #ifdef DIAG_IO DIAG(F("DFPlayer: Play %d"), pin+1); #endif - sendPacket(0x03, pin+1); + _requestedSong = pin+1; _playing = true; } else { // Value 0, stop playing #ifdef DIAG_IO DIAG(F("DFPlayer: Stop")); #endif - sendPacket(0x16); + _requestedSong = 0; // No song _playing = false; } } @@ -181,10 +198,6 @@ class DFPlayer : public IODevice { // 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. - // If starting a new file and setting volume, then avoid a short burst of loud noise by - // the following strategy: - // - If the volume is increasing, start playing the song before setting the volume, - // - If the volume is decreasing, decrease it and then start playing. // void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override { uint8_t pin = vpin - _firstVpin; @@ -198,28 +211,18 @@ class DFPlayer : public IODevice { if (pin == 0) { // Play track - if (value > 0) { - if (volume != 0) { - if (volume <= _lastVolumeLevel) - sendPacket(0x06, volume); // Set volume before starting - sendPacket(0x03, value); // Play track - _playing = true; - if (volume > _lastVolumeLevel) - sendPacket(0x06, volume); // Set volume after starting - _lastVolumeLevel = volume; - } else { - // Volume not changed, just play - sendPacket(0x03, value); - _playing = true; - } + if (value > 0 && volume != 0) { + if (volume != 0) + _requestedVolumeLevel = volume; + _requestedSong = value; + _playing = true; } else { - sendPacket(0x16); // Stop play + _requestedSong = 0; // stop playing _playing = false; } } else if (pin == 1) { // Set volume (0-30) - sendPacket(0x06, value); - _lastVolumeLevel = volume; + _requestedVolumeLevel = volume; } } @@ -246,7 +249,6 @@ class DFPlayer : public IODevice { void sendPacket(uint8_t command, uint16_t arg = 0) { - unsigned long currentMillis = millis(); uint8_t out[] = { 0x7E, 0xFF, 06, @@ -260,19 +262,8 @@ class DFPlayer : public IODevice { setChecksum(out); - // Check how long since the last command was sent. - // Each character takes approx 1ms at 9600 baud - unsigned long minimumGap = numPadCharacters + sizeof(out); - if (currentMillis - _commandSendTime < minimumGap) { - // Output some pad characters to add an - // artificial delay between commands - for (int i=0; iwrite((uint8_t)0); - } - - // Now output the command + // Output the command _serial->write(out, sizeof(out)); - _commandSendTime = currentMillis; } uint16_t calcChecksum(uint8_t* packet) From 4a1210fa64119f391b5c94394e8cdf8d4ee0eec3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Mar 2023 09:31:54 +0000 Subject: [PATCH 677/870] Remove HA mode from STM32 In some pin configurations for DC track mode, the use of analogWrite will conflict with other timer uses including HA mode. Consequently, the HA mode support has been temporarily removed pending a suitable solution for this. Original use of Timer11 has been reinstated. --- DCCTimerSTM32.cpp | 155 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 108 insertions(+), 47 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 2504a264e..83ac99b05 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -40,7 +40,7 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_NUCLEO_F446RE) // Nucleo-64 boards don't have Serial1 defined by default -HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE +HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) @@ -50,17 +50,106 @@ HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE #warning Serial1 not defined #endif +/////////////////////////////////////////////////////////////////////////////////////////////// +// Experimental code for High Accuracy (HA) DCC Signal mode +// Warning - use of TIM2 and TIM3 can affect the use of analogWrite() function on certain pins, +// which is used by the DC motor types. +/////////////////////////////////////////////////////////////////////////////////////////////// + +// INTERRUPT_CALLBACK interruptHandler=0; +// // Let's use STM32's timer #2 which supports hardware pulse generation on pin D13. +// // Also, timer #3 will do hardware pulses on pin D12. This gives +// // accurate timing, independent of the latency of interrupt handling. +// // We only need to interrupt on one of these (TIM2), the other will just generate +// // pulses. +// HardwareTimer timer(TIM2); +// HardwareTimer timerAux(TIM3); +// static bool tim2ModeHA = false; +// static bool tim3ModeHA = false; + +// // Timer IRQ handler +// void Timer_Handler() { +// interruptHandler(); +// } + +// void DCCTimer::begin(INTERRUPT_CALLBACK callback) { +// interruptHandler=callback; +// noInterrupts(); + +// // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); +// timer.pause(); +// timerAux.pause(); +// timer.setPrescaleFactor(1); +// timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); +// timer.attachInterrupt(Timer_Handler); +// timer.refresh(); +// timerAux.setPrescaleFactor(1); +// timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); +// timerAux.refresh(); + +// timer.resume(); +// timerAux.resume(); + +// interrupts(); +// } + +// bool DCCTimer::isPWMPin(byte pin) { +// // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12. +// // Enable the appropriate timer channel. +// switch (pin) { +// case 12: +// return true; +// case 13: +// return true; +// default: +// return false; +// } +// } + +// void DCCTimer::setPWM(byte pin, bool high) { +// // Set the timer so that, at the next counter overflow, the requested +// // pin state is activated automatically before the interrupt code runs. +// // TIM2 is timer, TIM3 is timerAux. +// switch (pin) { +// case 12: +// if (!tim3ModeHA) { +// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12); +// tim3ModeHA = true; +// } +// if (high) +// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; +// else +// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; +// break; +// case 13: +// if (!tim2ModeHA) { +// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13); +// tim2ModeHA = true; +// } +// if (high) +// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; +// else +// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; +// break; +// } +// } + +// void DCCTimer::clearPWM() { +// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); +// tim2ModeHA = false; +// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); +// tim3ModeHA = false; +// } +/////////////////////////////////////////////////////////////////////////////////////////////// + INTERRUPT_CALLBACK interruptHandler=0; -// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13. -// Also, timer #3 will do hardware pulses on pin D12. This gives -// accurate timing, independent of the latency of interrupt handling. -// We only need to interrupt on one of these (TIM2), the other will just generate -// pulses. -HardwareTimer timer(TIM2); -HardwareTimer timerAux(TIM3); +// Let's use STM32's timer #11 until disabused of this notion +// Timer #11 is used for "servo" library, but as DCC-EX is not using +// this libary, we should be free and clear. +HardwareTimer timer(TIM11); // Timer IRQ handler -void Timer_Handler() { +void Timer11_Handler() { interruptHandler(); } @@ -70,59 +159,31 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) { // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES); timer.pause(); - timerAux.pause(); timer.setPrescaleFactor(1); +// timer.setOverflow(CLOCK_CYCLES * 2); timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); - timer.attachInterrupt(Timer_Handler); + timer.attachInterrupt(Timer11_Handler); timer.refresh(); - timerAux.setPrescaleFactor(1); - timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT); - timerAux.refresh(); - timer.resume(); - timerAux.resume(); interrupts(); } bool DCCTimer::isPWMPin(byte pin) { - // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12. - // Enable the appropriate timer channel. - switch (pin) { - case 12: - timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12); - return true; - case 13: - timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13); - return true; - default: - return false; - } + //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, + // there's no support yet for High Accuracy, so for now return false + // return digitalPinHasPWM(pin); + return false; } void DCCTimer::setPWM(byte pin, bool high) { - // Set the timer so that, at the next counter overflow, the requested - // pin state is activated automatically before the interrupt code runs. - // TIM2 is timer, TIM3 is timerAux. - switch (pin) { - case 12: - if (high) - TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; - else - TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; - break; - case 13: - if (high) - TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0; - else - TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1; - break; - } + // TODO: High Accuracy mode is not supported as yet, and may never need to be + (void) pin; + (void) high; } void DCCTimer::clearPWM() { - timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); - timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC); + return; } void DCCTimer::getSimulatedMacAddress(byte mac[6]) { From 75b5806eb707e84c16f3d7f9619b5a7e38ea5d6e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Mar 2023 09:39:20 +0000 Subject: [PATCH 678/870] Shorten I2C error message --- I2CManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2CManager.cpp b/I2CManager.cpp index aad641869..d0d8550f8 100644 --- a/I2CManager.cpp +++ b/I2CManager.cpp @@ -74,7 +74,7 @@ void I2CManagerClass::begin(void) { _beginCompleted = true; // Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C - const FSH *message = F("WARNING: Possible short-circuit or inadequate pullup on I2C %S line"); + const FSH *message = F("WARNING: Check I2C %S line for short/pullup"); pinMode(SDA, INPUT); if (!digitalRead(SDA)) DIAG(message, F("SDA")); From 3d35e78533668c482436d8940746a4f0fa32ef9b Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Mar 2023 09:39:30 +0000 Subject: [PATCH 679/870] Update version.h --- version.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index b8f547f4f..6aafcd589 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.27" +#define VERSION "4.2.28" +// 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode. +// - Update IO_DFPlayer to work with MP3-TF-16P rev3. // 4.2.27 - Bugfix LCD showed random characters in SCROLLMODE 2 // 4.2.26 - EX-IOExpander device driver enhancements // - Enhance I2C error checking From 278347756ac578a182daf96a383cfd5793bd22c5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 15 Mar 2023 16:41:02 +0100 Subject: [PATCH 680/870] Bugfix Scroll LCD without empty lines and consistent --- Display.cpp | 94 ++++++++++++++++++++++++++++++++++------------------ Display.h | 1 + GITHUB_SHA.h | 2 +- version.h | 3 +- 4 files changed, 65 insertions(+), 35 deletions(-) diff --git a/Display.cpp b/Display.cpp index a1a1ec145..03d6039e0 100644 --- a/Display.cpp +++ b/Display.cpp @@ -129,39 +129,76 @@ Display *Display::loop2(bool force) { // No non-blank lines left, so draw a blank line buffer[0] = 0; } - _deviceDriver->setRowNative(slot); // Set position for display - charIndex = 0; - bufferPointer = &buffer[0]; +#if SCROLLMODE==2 + if (buffer[0] == 0 && needScroll){ // surpresses empty line +#else + if (false){ +#endif + charIndex = MAX_CHARACTER_COLS; + slot--; + } else { + _deviceDriver->setRowNative(slot); // Set position for display + charIndex = 0; + bufferPointer = &buffer[0]; + } + rowNext++; } else { // Write next character, or a space to erase current position. char ch = *bufferPointer; if (ch) { - _deviceDriver->writeNative(ch); + _deviceDriver->writeNative(ch); bufferPointer++; } else { _deviceDriver->writeNative(' '); } + } - if (++charIndex >= MAX_CHARACTER_COLS) { - // Screen slot completed, move to next slot on screen - bufferPointer = 0; - slot++; - if (slot >= numCharacterRows) { - // Last slot on screen written, reset ready for next screen update. + if (++charIndex >= MAX_CHARACTER_COLS) { + // Screen slot completed, move to next slot on screen + bufferPointer = 0; + slot++; + if (slot >= numCharacterRows) { + // Last slot on screen written, reset ready for next screen update. +#if SCROLLMODE==2 || SCROLLMODE==1 + if (!noMoreRowsToDisplay) { + needScroll = true; + } + if (needScroll) { #if SCROLLMODE==2 - if (!noMoreRowsToDisplay) { - // On next refresh, restart one row on from previous start. - rowNext = rowFirst; - findNextNonBlankRow(); - } + // SCROLLMODE 2 rotates through rowFirst and we + // (ab)use findNextBlankRow() to figure out + // next valid row which can be start row. + rowNext = rowFirst + 1; + noMoreRowsToDisplay = false; + findNextNonBlankRow(); + if (rowNext == ROW_INITIAL) + rowNext = 0; + rowFirst = ROW_INITIAL; +#else + // SCROLLMODE 1 just alternates when the + // flag indicates that we have come to the end + if (noMoreRowsToDisplay) + rowNext = 0; +#endif + } else { + // SCROLLMODE 1 or 2 but not scroll active + rowNext = 0; + } +#else + // this is for SCROLLMODE 0 but what should it do? + rowNext = 0; #endif - noMoreRowsToDisplay = false; - slot = 0; - rowFirst = ROW_INITIAL; - lastScrollTime = currentMillis; - return NULL; - } + rowFirst = ROW_INITIAL; + + noMoreRowsToDisplay = false; + slot = 0; + lastScrollTime = currentMillis; + return NULL; } +#if SCROLLMODE==2 + if (needScroll) + noMoreRowsToDisplay = false; +#endif } } while (force); @@ -172,28 +209,19 @@ bool Display::findNextNonBlankRow() { while (!noMoreRowsToDisplay) { if (rowNext == ROW_INITIAL) rowNext = 0; - else - rowNext = rowNext + 1; -#if SCROLLMODE == 1 if (rowNext >= MAX_CHARACTER_ROWS) { // Finished if we've looped back to start rowNext = ROW_INITIAL; noMoreRowsToDisplay = true; return false; } -#else - if (rowNext >= MAX_CHARACTER_ROWS) - rowNext = 0; - if (rowNext == rowFirst) { - // Finished if we're back to the first one shown - noMoreRowsToDisplay = true; - return false; - } -#endif if (rowBuffer[rowNext][0] != 0) { + //rowBuffer[rowNext][0] = '0' + rowNext; // usefull for debug + //rowBuffer[rowNext][1] = '0' + rowFirst; // usefull for debug // Found non-blank row return true; } + rowNext = rowNext + 1; } return false; } diff --git a/Display.h b/Display.h index be2479db9..dc59dbbdc 100644 --- a/Display.h +++ b/Display.h @@ -56,6 +56,7 @@ class Display : public DisplayInterface { char buffer[MAX_CHARACTER_COLS + 1]; char* bufferPointer = 0; bool noMoreRowsToDisplay = false; + bool needScroll = false; uint16_t numCharacterRows; uint16_t numCharacterColumns = MAX_CHARACTER_COLS; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index be808f854..4d37765f4 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303141949Z" +#define GITHUB_SHA "devel-202303151539Z" diff --git a/version.h b/version.h index 6aafcd589..66fc35177 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.28" +#define VERSION "4.2.29" +// 4.2.29 - Bugfix Scroll LCD without empty lines and consistent // 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode. // - Update IO_DFPlayer to work with MP3-TF-16P rev3. // 4.2.27 - Bugfix LCD showed random characters in SCROLLMODE 2 From 30730615966646d1a6aae14996bef9b9720de6b4 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Wed, 15 Mar 2023 18:10:27 +0000 Subject: [PATCH 681/870] Update IO_DFPlayer.h Bugfix, volume not working correctly. --- IO_DFPlayer.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 51dcee328..e150ef783 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -160,7 +160,7 @@ class DFPlayer : public IODevice { _requestedSong = -1; _commandSendTime = currentMicros; } else if (_requestedSong == 0) { - sendPacket(0x16); // Stop playing + sendPacket(0x0e); // Pause playing _requestedSong = -1; _commandSendTime = currentMicros; } else if (_currentVolume < _requestedVolumeLevel) { @@ -211,8 +211,8 @@ class DFPlayer : public IODevice { if (pin == 0) { // Play track - if (value > 0 && volume != 0) { - if (volume != 0) + if (value > 0) { + if (volume > 0) _requestedVolumeLevel = volume; _requestedSong = value; _playing = true; @@ -222,7 +222,7 @@ class DFPlayer : public IODevice { } } else if (pin == 1) { // Set volume (0-30) - _requestedVolumeLevel = volume; + _requestedVolumeLevel = value; } } From 5dd2770442c3a56889f980f199989ee1cbee38b3 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 17 Mar 2023 21:15:26 +0000 Subject: [PATCH 682/870] Update IO_HCSR04.h Modify mode of measurement so that the driver doesn't loop for long periods waiting for the incoming pulse to complete. Original loop behaviour can be reinstated by adding LOOP option in create call (see comment header in file). --- IO_HCSR04.h | 200 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 73 deletions(-) diff --git a/IO_HCSR04.h b/IO_HCSR04.h index e7b5622b1..26f0ecd64 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -30,7 +30,7 @@ * * 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 state returned by a read() call changes to 1. If + * 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 @@ -48,6 +48,20 @@ * 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. + * + * Example configuration: + * HCSR04::create(23000, 32, 33, 80, 85); + * + * Where 23000 is the VPIN allocated, + * 32 is the pin connected to the HCSR04 trigger terminal, + * 33 is the pin connected to the HCSR04 echo terminal, + * 80 is the distance in cm below which pin 23000 will be active, + * and 85 is the distance in cm above which pin 23000 will be inactive. + * + * Alternative configuration, which hogs the processor until the measurement is complete + * (old behaviour, more accurate but higher impact on other CS tasks): + * HCSR04::create(23000, 32, 33, 80, 85, HCSR04::LOOP); + * */ #ifndef IO_HCSR04_H @@ -61,38 +75,52 @@ class HCSR04 : public IODevice { // pins must be arduino GPIO pins, not extender pins or HAL pins. int _trigPin = -1; int _echoPin = -1; - // Thresholds for setting active state in cm. + // 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 + // Active=1/inactive=0 _state uint8_t _value = 0; - // Factor for calculating the distance (cm) from echo time (ms). + // Factor for calculating the distance (cm) from echo time (us). // Based on a speed of sound of 345 metres/second. - const uint16_t factor = 58; // ms/cm + const uint16_t factor = 58; // us/cm + // Limit the time spent looping by dropping out when the expected + // worst case threshold value is greater than an arbitrary value. + const uint16_t maxPermittedLoopTime = 10 * factor; // max in us + unsigned long _startTime = 0; + unsigned long _maxTime = 0; + enum {DORMANT, MEASURING}; // _state values + uint8_t _state = DORMANT; + uint8_t _counter = 0; + uint16_t _options = 0; public: + enum Options { + LOOP = 1, // Option HCSR04::LOOP reinstates old behaviour, i.e. complete measurement in one loop entry. + }; // Static create function provides alternative way to create object - static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { + static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options = 0) { if (checkNoOverlap(vpin)) - new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold); + new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold, options); } protected: - // Constructor perfroms static initialisation of the device object - HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) { + // Constructor performs static initialisation of the device object + HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options) { _firstVpin = vpin; _nPins = 1; _trigPin = trigPin; _echoPin = echoPin; _onThreshold = onThreshold; _offThreshold = offThreshold; + _options = options; addDevice(this); } // _begin function called to perform dynamic initialisation of the device void _begin() override { + _state = 0; pinMode(_trigPin, OUTPUT); pinMode(_echoPin, INPUT); ArduinoPins::fastWriteDigital(_trigPin, 0); @@ -112,11 +140,97 @@ class HCSR04 : public IODevice { return _distance; } - // _loop function - read HC-SR04 once every 50 milliseconds. + // _loop function - read HC-SR04 once every 100 milliseconds. void _loop(unsigned long currentMicros) override { - read_HCSR04device(); - // Delay next loop entry until 50ms have elapsed. - delayUntil(currentMicros + 50000UL); + unsigned long waitTime; + switch(_state) { + case DORMANT: // Issue pulse + // If receive pin is still set on from previous call, do nothing till next entry. + if (ArduinoPins::fastReadDigital(_echoPin)) return; + + // Send 10us pulse to trigger transmitter + ArduinoPins::fastWriteDigital(_trigPin, 1); + delayMicroseconds(10); + ArduinoPins::fastWriteDigital(_trigPin, 0); + + // Wait, with timeout, for echo pin to become set. + // Measured time delay is just under 500us, so + // wait for max of 1000us. + _startTime = micros(); + _maxTime = 1000; + + while (!ArduinoPins::fastReadDigital(_echoPin)) { + // Not set yet, see if we've timed out. + waitTime = micros() - _startTime; + if (waitTime > _maxTime) { + // Timeout waiting for pulse start, abort the read and start again + _state = DORMANT; + return; + } + } + + // Echo pulse started, so wait for echo pin to reset, and measure length of pulse + _startTime = micros(); + _maxTime = factor * _offThreshold; + _state = MEASURING; + // If maximum measurement time is high, then skip until next loop entry before + // starting to look for pulse end. + // This gives better accuracy at shorter distance thresholds but without extending + // loop execution time for longer thresholds. If LOOP option is set on, then + // the entire measurement will be done in one loop entry, i.e. the code will fall + // through into the measuring phase. + if (!(_options & LOOP) && _maxTime > maxPermittedLoopTime) break; + /* fallthrough */ + + case MEASURING: // Check if echo pulse has finished + do { + waitTime = micros() - _startTime; + if (!ArduinoPins::fastReadDigital(_echoPin)) { + // Echo pulse completed; check if pulse length is below threshold and if so set value. + if (waitTime <= factor * _onThreshold) { + // Measured time is within the onThreshold, so value is one. + _value = 1; + // If the new distance value is less than the current, use it immediately. + // But if the new distance value is longer, then it may be erroneously long + // (because of extended loop times delays), so apply a delay to distance increases. + uint16_t estimatedDistance = waitTime / factor; + if (estimatedDistance < _distance) + _distance = estimatedDistance; + else + _distance += 1; // Just increase distance slowly. + _counter = 0; + //DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, _distance); + } + _state = DORMANT; + } else { + // Echo pulse hasn't finished, so check if maximum time has elapsed + // If pulse is too long then set return value to zero, + // and finish without waiting for end of pulse. + if (waitTime > _maxTime) { + // Pulse length longer than maxTime, value is provisionally zero. + // But don't change _value unless provisional value is zero for 10 consecutive measurements + if (_value == 1) { + if (++_counter >= 10) { + _value = 0; + _distance = 32767; + _counter = 0; + } + } + _state = DORMANT; // start again + } + } + // If there's lots of time remaining before the expected completion time, + // then exit and wait for next loop entry. Otherwise, loop until we finish. + // If option LOOP is set, then we loop until finished anyway. + uint32_t remainingTime = _maxTime - waitTime; + if (!(_options & LOOP) && remainingTime < maxPermittedLoopTime) return; + } while (_state == MEASURING) ; + break; + } + // Datasheet recommends a wait of at least 60ms between measurement cycles + if (_state == DORMANT) + delayUntil(currentMicros+60000UL); // wait 60ms till next measurement + } void _display() override { @@ -124,66 +238,6 @@ class HCSR04 : public IODevice { _firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold); } -private: - // This polls the HC-SR04 device by sending a pulse and measuring the duration of - // the pulse observed on the receive pin. In order to be kind to the rest of the CS - // software, no interrupts are used and interrupts are not disabled. The pulse duration - // is measured in a loop, using the micros() function. Therefore, interrupts from other - // sources may affect the result. However, interrupts response code in CS typically takes - // much less than the 58us frequency for the DCC interrupt, and 58us corresponds to only 1cm - // in the HC-SR04. - // To reduce chatter on the output, hysteresis is applied on reset: the output is set to 1 when the - // measured distance is less than the onThreshold, and is set to 0 if the measured distance is - // greater than the offThreshold. - // - void read_HCSR04device() { - // uint16 enough to time up to 65ms - uint16_t startTime, waitTime = 0, currentTime, maxTime; - - // If receive pin is still set on from previous call, abort the read. - if (ArduinoPins::fastReadDigital(_echoPin)) - return; - - // Send 10us pulse to trigger transmitter - ArduinoPins::fastWriteDigital(_trigPin, 1); - delayMicroseconds(10); - ArduinoPins::fastWriteDigital(_trigPin, 0); - - // Wait for receive pin to be set - startTime = currentTime = micros(); - maxTime = factor * _offThreshold * 2; - while (!ArduinoPins::fastReadDigital(_echoPin)) { - // lastTime = currentTime; - currentTime = micros(); - waitTime = currentTime - startTime; - if (waitTime > maxTime) { - // Timeout waiting for pulse start, abort the read - return; - } - } - - // Wait for receive pin to reset, and measure length of pulse - startTime = currentTime = micros(); - maxTime = factor * _offThreshold; - while (ArduinoPins::fastReadDigital(_echoPin)) { - currentTime = micros(); - waitTime = currentTime - startTime; - // If pulse is too long then set return value to zero, - // and finish without waiting for end of pulse. - if (waitTime > maxTime) { - // Pulse length longer than maxTime, reset value. - _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); - _distance = waitTime / factor; // in centimetres - if (_distance < _onThreshold) - _value = 1; - } - }; #endif //IO_HCSR04_H From e55dc51bdbd97fc68d284abd4683291c29b11c30 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 18 Mar 2023 15:05:21 +0000 Subject: [PATCH 683/870] EX-IOExpander updates --- IO_EXIOExpander.h | 333 ++++++++++++++++++++++++++++++---------------- version.h | 3 +- 2 files changed, 217 insertions(+), 119 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 84433b749..9c631e0cf 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -34,11 +34,16 @@ * device in use. There is no way for the device driver to sanity check pins are used for the * correct purpose, however the EX-IOExpander device's pin map will prevent pins being used * incorrectly (eg. A6/7 on Nano cannot be used for digital input/output). +* +* The total number of pins cannot exceed 256 because of the communications packet format. +* The number of analogue inputs cannot exceed 16 because of a limit on the maximum +* I2C packet size of 32 bytes (in the Wire library). */ #ifndef IO_EX_IOEXPANDER_H #define IO_EX_IOEXPANDER_H +#include "IODevice.h" #include "I2CManager.h" #include "DIAG.h" #include "FSH.h" @@ -64,134 +69,203 @@ class EXIOExpander : public IODevice { if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress); } -private: +private: // Constructor EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { _firstVpin = firstVpin; + // Number of pins cannot exceed 256 (1 byte) because of I2C message structure. + if (nPins > 256) nPins = 256; _nPins = nPins; - _i2cAddress = i2cAddress; + _I2CAddress = i2cAddress; addDevice(this); } void _begin() { + uint8_t status; // Initialise EX-IOExander device I2CManager.begin(); - if (I2CManager.exists(_i2cAddress)) { - _command4Buffer[0] = EXIOINIT; - _command4Buffer[1] = _nPins; - _command4Buffer[2] = _firstVpin & 0xFF; - _command4Buffer[3] = _firstVpin >> 8; + if (I2CManager.exists(_I2CAddress)) { // Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline - I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4, &_i2crb); - if (_receive3Buffer[0] == EXIOPINS) { - _numDigitalPins = _receive3Buffer[1]; - _numAnaloguePins = _receive3Buffer[2]; - _digitalPinBytes = (_numDigitalPins + 7)/8; - _digitalInputStates=(byte*) calloc(_digitalPinBytes,1); - _analoguePinBytes = _numAnaloguePins * 2; - _analogueInputStates = (byte*) calloc(_analoguePinBytes, 1); - _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); - } else { - DIAG(F("ERROR configuring EX-IOExpander device, I2C:%s"), _i2cAddress.toString()); - _deviceState = DEVSTATE_FAILED; - return; - } + // NB The I2C calls here are done as blocking calls, as they're not time-critical + // during initialisation and the reads require waiting for a response anyway. + // Hence we can allocate I/O buffers from the stack. + uint8_t receiveBuffer[3]; + uint8_t commandBuffer[4] = {EXIOINIT, (uint8_t)_nPins, (uint8_t)(_firstVpin & 0xFF), (uint8_t)(_firstVpin >> 8)}; + status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer)); + if (status == I2C_STATUS_OK) { + if (receiveBuffer[0] == EXIOPINS) { + _numDigitalPins = receiveBuffer[1]; + _numAnaloguePins = receiveBuffer[2]; + + // See if we already have suitable buffers assigned + size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8; + if (_digitalPinBytes < digitalBytesNeeded) { + // Not enough space, free any existing buffer and allocate a new one + if (_digitalPinBytes > 0) free(_digitalInputStates); + _digitalInputStates = (byte*) calloc(_digitalPinBytes, 1); + _digitalPinBytes = digitalBytesNeeded; + } + size_t analogueBytesNeeded = _numAnaloguePins * sizeof(_analogueInputValues[0]); + if (_analoguePinBytes < analogueBytesNeeded) { + // Free any existing buffers and allocate new ones. + if (_analoguePinBytes > 0) { + free(_analogueInputBuffer); + free(_analogueInputValues); + free(_analoguePinMap); + } + _analogueInputValues = (int16_t*) calloc(_analoguePinBytes, 1); + _analogueInputBuffer = (uint8_t*) calloc(_analoguePinBytes, 1); + _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + } + } else { + DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + return; + } + } // We now need to retrieve the analogue pin map - _command1Buffer[0] = EXIOINITA; - I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1, &_i2crb); - // Attempt to get version, if we don't get it, we don't care, don't go offline - _command1Buffer[0] = EXIOVER; - I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1, &_i2crb); - _majorVer = _versionBuffer[0]; - _minorVer = _versionBuffer[1]; - _patchVer = _versionBuffer[2]; - DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"), - _i2cAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]); + if (status == I2C_STATUS_OK) { + commandBuffer[0] = EXIOINITA; + status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1); + } + if (status == I2C_STATUS_OK) { + // Attempt to get version, if we don't get it, we don't care, don't go offline + uint8_t versionBuffer[3]; + commandBuffer[0] = EXIOVER; + if (I2CManager.read(_I2CAddress, versionBuffer, sizeof(versionBuffer), commandBuffer, 1) == I2C_STATUS_OK) { + _majorVer = versionBuffer[0]; + _minorVer = versionBuffer[1]; + _patchVer = versionBuffer[2]; + } + DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"), + _I2CAddress.toString(), _majorVer, _minorVer, _patchVer); + #ifdef DIAG_IO - _display(); + _display(); #endif + } + if (status != I2C_STATUS_OK) + reportError(status); + } else { - DIAG(F("EX-IOExpander device not found, I2C:%s"), _i2cAddress.toString()); + DIAG(F("EX-IOExpander I2C:%s device not found"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } - // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use + // Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if requested. + // Configuration isn't done frequently so we can use blocking I2C calls here, and so buffers can + // be allocated from the stack to reduce RAM allocation. bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override { if (paramCount != 1) return false; int pin = vpin - _firstVpin; if (configType == CONFIGURE_INPUT) { - bool pullup = params[0]; - _digitalOutBuffer[0] = EXIODPUP; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = pullup; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3, &_i2crb); - if (_command1Buffer[0] == EXIORDY) { - return true; - } else { - DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin); - return false; - } - } else { + uint8_t pullup = params[0]; + uint8_t outBuffer[] = {EXIODPUP, (uint8_t)pin, pullup}; + uint8_t responseBuffer[1]; + uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer), + outBuffer, sizeof(outBuffer)); + if (status == I2C_STATUS_OK) { + if (responseBuffer[0] == EXIORDY) { + return true; + } else { + DIAG(F("EXIOVpin %u cannot be used as a digital input pin"), (int)vpin); + } + } else + reportError(status); + } else if (configType == CONFIGURE_ANALOGINPUT) { + // TODO: Consider moving code from _configureAnalogIn() to here and remove _configureAnalogIn + // from IODevice class definition. Not urgent, but each virtual function defined + // means increasing the RAM requirement of every HAL device driver, whether it's relevant + // to the driver or not. return false; } + return false; } - // Analogue input pin configuration, used to enable on EX-IOExpander device + // Analogue input pin configuration, used to enable an EX-IOExpander device. + // Use I2C blocking calls and allocate buffers from stack to save RAM. int _configureAnalogIn(VPIN vpin) override { int pin = vpin - _firstVpin; - _command2Buffer[0] = EXIOENAN; - _command2Buffer[1] = pin; - I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2, &_i2crb); - if (_command1Buffer[0] == EXIORDY) { - return true; - } else { - DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin); - return false; - } - return true; + uint8_t commandBuffer[] = {EXIOENAN, (uint8_t)pin}; + uint8_t responseBuffer[1]; + uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer), + commandBuffer, sizeof(commandBuffer)); + if (status == I2C_STATUS_OK) { + if (responseBuffer[0] == EXIORDY) { + return true; + } else { + DIAG(F("EX-IOExpander: Vpin %u cannot be used as an analogue input pin"), (int)vpin); + } + } else + reportError(status); + + return false; } // Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads) void _loop(unsigned long currentMicros) override { if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return + + // Request block is used for analogue and digital reads from the IOExpander, which are performed + // on a cyclic basis. Writes are performed synchronously as and when requested. + + if (_i2crb.isBusy()) return; // If I2C operation still in progress, return + uint8_t status = _i2crb.status; - if (status == I2C_STATUS_PENDING) return; // If device busy, return - if (status == I2C_STATUS_OK) { // If device ok, read input data - if (_commandFlag) { - if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh - _lastDigitalRead = currentMicros; - _command1Buffer[0] = EXIORDD; - I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1, &_i2crb); - } - } else { - if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh - _lastAnalogueRead = currentMicros; - _command1Buffer[0] = EXIORDAN; - byte _tempAnalogue[_analoguePinBytes]; // Setup temp buffer so reads come from known state - I2CManager.read(_i2cAddress, _tempAnalogue, _analoguePinBytes, _command1Buffer, 1, &_i2crb); - memcpy(_analogueInputStates, _tempAnalogue, _analoguePinBytes); // Copy temp buffer to states - } + if (status == I2C_STATUS_OK) { // If device request ok, read input data + + // First check if we need to process received data + if (_readState == RDS_ANALOGUE) { + // Read of analogue values was in progress, so process received values + // Here we need to copy the values from input buffer to the analogue value array. We need to + // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed + // while the value is being read). + memcpy(_analogueInputValues, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states + _readState = RDS_IDLE; + + } else if (_readState == RDS_DIGITAL) { + // Read of digital states was in progress, so process received values + // The received digital states are placed directly into the digital buffer on receipt, + // so don't need any further processing at this point (unless we want to check for + // changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h). + _readState = RDS_IDLE; + } + } else + reportError(status, false); // report eror but don't go offline. + + // If we're not doing anything now, check to see if a new input transfer is due. + if (_readState == RDS_IDLE) { + if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh + // Issue new read request for digital states. As the request is non-blocking, the buffer has to + // be allocated from heap (object state). + _readCommandBuffer[0] = EXIORDD; + I2CManager.read(_I2CAddress, _digitalInputStates, (_numDigitalPins+7)/8, _readCommandBuffer, 1, &_i2crb); + // non-blocking read + _lastDigitalRead = currentMicros; + _readState = RDS_DIGITAL; + } else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh + // Issue new read for analogue input states + _readCommandBuffer[0] = EXIORDAN; + I2CManager.read(_I2CAddress, _analogueInputBuffer, + _numAnaloguePins * sizeof(_analogueInputBuffer[0]), _readCommandBuffer, 1, &_i2crb); + _lastAnalogueRead = currentMicros; + _readState = RDS_ANALOGUE; } - _commandFlag = !_commandFlag; - } else { - DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); - _deviceState = DEVSTATE_FAILED; } } - // Obtain the correct analogue input value + // Obtain the correct analogue input value, with reference to the analogue + // pin map. + // (QUESTION: Why isn't this mapping done in the remote node before transmission?) int _readAnalogue(VPIN vpin) override { if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; - uint8_t _pinLSBByte; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { - if (_analoguePinMap[aPin] == pin) { - _pinLSBByte = aPin * 2; - } + if (_analoguePinMap[aPin] == pin) + return _analogueInputValues[aPin]; } - uint8_t _pinMSBByte = _pinLSBByte + 1; - return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; + return 0; // Pin not found } // Obtain the correct digital input value @@ -203,75 +277,98 @@ class EXIOExpander : public IODevice { return value; } + // Write digital value. We could have an output buffer of states, that is periodically + // written to the device if there are any changes; this would reduce the I2C overhead + // if lots of output requests are being made. We could also cache the last value + // sent so that we don't write the same value over and over to the output. + // However, for the time being, we just write the current value (blocking I2C) to the + // IOExpander node. As it is a blocking request, we can use buffers allocated from + // the stack to save RAM allocation. void _write(VPIN vpin, int value) override { + uint8_t digitalOutBuffer[3]; + uint8_t responseBuffer[1]; if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; - _digitalOutBuffer[0] = EXIOWRD; - _digitalOutBuffer[1] = pin; - _digitalOutBuffer[2] = value; - uint8_t status = I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3); + digitalOutBuffer[0] = EXIOWRD; + digitalOutBuffer[1] = pin; + digitalOutBuffer[2] = value; + uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, digitalOutBuffer, 3); if (status != I2C_STATUS_OK) { - DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); - _deviceState = DEVSTATE_FAILED; + reportError(status); } else { - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin); + if (responseBuffer[0] != EXIORDY) { + DIAG(F("Vpin %u cannot be used as a digital output pin"), (int)vpin); } } } + // Write analogue (integer) value. Write the parameters (blocking I2C) to the + // IOExpander node. As it is a blocking request, we can use buffers allocated from + // the stack to reduce RAM allocation. void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + uint8_t servoBuffer[7]; + uint8_t responseBuffer[1]; + if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; #ifdef DIAG_IO - DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + DIAG(F("Servo: WriteAnalogue Vpin:%u Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif - _servoBuffer[0] = EXIOWRAN; - _servoBuffer[1] = pin; - _servoBuffer[2] = value & 0xFF; - _servoBuffer[3] = value >> 8; - _servoBuffer[4] = profile; - _servoBuffer[5] = duration & 0xFF; - _servoBuffer[6] = duration >> 8; - uint8_t status = I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7); + servoBuffer[0] = EXIOWRAN; + servoBuffer[1] = pin; + servoBuffer[2] = value & 0xFF; + servoBuffer[3] = value >> 8; + servoBuffer[4] = profile; + servoBuffer[5] = duration & 0xFF; + servoBuffer[6] = duration >> 8; + uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, servoBuffer, 7); if (status != I2C_STATUS_OK) { DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } else { - if (_command1Buffer[0] != EXIORDY) { - DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin); + if (responseBuffer[0] != EXIORDY) { + DIAG(F("Vpin %u cannot be used as a servo/PWM pin"), (int)vpin); } } } + // Display device information and status. void _display() override { - DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %d-%d %S"), - _i2cAddress.toString(), _majorVer, _minorVer, _patchVer, + DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %u-%u %S"), + _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); } - I2CAddress _i2cAddress; + // Helper function for error handling + void reportError(uint8_t status, bool fail=true) { + DIAG(F("EX-IOExpander I2C:%s Error:%d (%S)"), _I2CAddress.toString(), + status, I2CManager.getErrorMessage(status)); + if (fail) + _deviceState = DEVSTATE_FAILED; + } + uint8_t _numDigitalPins = 0; uint8_t _numAnaloguePins = 0; - byte _digitalOutBuffer[3]; - uint8_t _versionBuffer[3]; + uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; - byte* _digitalInputStates; - byte* _analogueInputStates; - uint8_t _digitalPinBytes = 0; - uint8_t _analoguePinBytes = 0; - byte _command1Buffer[1]; - byte _command2Buffer[2]; - byte _command4Buffer[4]; - byte _receive3Buffer[3]; - byte _servoBuffer[7]; + + uint8_t* _digitalInputStates; + int16_t* _analogueInputValues; + uint8_t* _analogueInputBuffer; // buffer for I2C input transfers + uint8_t _readCommandBuffer[1]; + + uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed) + uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed) uint8_t* _analoguePinMap; I2CRB _i2crb; - bool _commandFlag = 1; + + enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states + uint8_t _readState = RDS_IDLE; + unsigned long _lastDigitalRead = 0; unsigned long _lastAnalogueRead = 0; const unsigned long _digitalRefresh = 10000UL; // Delay refreshing digital inputs for 10ms diff --git a/version.h b/version.h index 66fc35177..1801b02d4 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.29" +#define VERSION "4.2.30" +// 4.2.30 - Fixes/enhancements to EX-IOExpander device driver. // 4.2.29 - Bugfix Scroll LCD without empty lines and consistent // 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode. // - Update IO_DFPlayer to work with MP3-TF-16P rev3. From c4b4e11a6773d1cf980b2161b1d279a794a90e5f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sat, 18 Mar 2023 15:30:14 +0000 Subject: [PATCH 684/870] Update IO_EXIOExpander.h Avoid repeated error messages for a single fault. --- IO_EXIOExpander.h | 48 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 9c631e0cf..6d4377890 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -210,29 +210,31 @@ class EXIOExpander : public IODevice { // Request block is used for analogue and digital reads from the IOExpander, which are performed // on a cyclic basis. Writes are performed synchronously as and when requested. - if (_i2crb.isBusy()) return; // If I2C operation still in progress, return - - uint8_t status = _i2crb.status; - if (status == I2C_STATUS_OK) { // If device request ok, read input data - - // First check if we need to process received data - if (_readState == RDS_ANALOGUE) { - // Read of analogue values was in progress, so process received values - // Here we need to copy the values from input buffer to the analogue value array. We need to - // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed - // while the value is being read). - memcpy(_analogueInputValues, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states - _readState = RDS_IDLE; - - } else if (_readState == RDS_DIGITAL) { - // Read of digital states was in progress, so process received values - // The received digital states are placed directly into the digital buffer on receipt, - // so don't need any further processing at this point (unless we want to check for - // changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h). - _readState = RDS_IDLE; - } - } else - reportError(status, false); // report eror but don't go offline. + if (_readState != RDS_IDLE) { + if (_i2crb.isBusy()) return; // If I2C operation still in progress, return + + uint8_t status = _i2crb.status; + if (status == I2C_STATUS_OK) { // If device request ok, read input data + + // First check if we need to process received data + if (_readState == RDS_ANALOGUE) { + // Read of analogue values was in progress, so process received values + // Here we need to copy the values from input buffer to the analogue value array. We need to + // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed + // while the value is being read). + memcpy(_analogueInputValues, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states + + } else if (_readState == RDS_DIGITAL) { + // Read of digital states was in progress, so process received values + // The received digital states are placed directly into the digital buffer on receipt, + // so don't need any further processing at this point (unless we want to check for + // changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h). + } + } else + reportError(status, false); // report eror but don't go offline. + + _readState = RDS_IDLE; + } // If we're not doing anything now, check to see if a new input transfer is due. if (_readState == RDS_IDLE) { From 329dc414523381fc18f93ed1960415ecd2cc3b38 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 18 Mar 2023 18:52:01 +0000 Subject: [PATCH 685/870] Remove implicit AUTOSTART --- EXRAIL2.cpp | 13 +++++++------ myAutomation.example.h | 3 ++- version.h | 4 +++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 8541cd248..bb5989aac 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1,7 +1,7 @@ /* * © 2021 Neil McKechnie * © 2021-2023 Harald Barth - * © 2020-2022 Chris Harlow + * © 2020-2023 Chris Harlow * All rights reserved. * * This file is part of CommandStation-EX @@ -24,8 +24,8 @@ F1. [DONE] DCC accessory packet opcodes (short and long form) F2. [DONE] ONAccessory catchers F3. [DONE] Turnout descriptions for Withrottle - F4. Oled announcements (depends on HAL) - F5. Withrottle roster info + F4. [DONE] Oled announcements (depends on HAL) + F5. [DONE] Withrottle roster info F6. Multi-occupancy semaphore F7. [DONE see AUTOSTART] Self starting sequences F8. Park/unpark @@ -240,8 +240,9 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { case OPCODE_AUTOSTART: // automatically create a task from here at startup. - // but we will do one at 0 anyway by default. - if (progCounter>0) new RMFT2(progCounter); + // Removed if (progCounter>0) check 4.2.31 because + // default start it top of file is now removed. . + new RMFT2(progCounter); break; default: // Ignore @@ -252,7 +253,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS); - new RMFT2(0); // add the startup route + // Removed for 4.2.31 new RMFT2(0); // add the startup route diag=saved_diag; } diff --git a/myAutomation.example.h b/myAutomation.example.h index 1382a5270..e080f2fd5 100644 --- a/myAutomation.example.h +++ b/myAutomation.example.h @@ -23,7 +23,8 @@ * */ -// This is the startup sequence, AKA SEQUENCE(0) +// This is the startup sequence, +AUTOSTART SENDLOCO(3,1) // send loco 3 off along route 1 SENDLOCO(10,2) // send loco 10 off along route 2 DONE // This just ends the startup thread, leaving 2 others running. diff --git a/version.h b/version.h index 1801b02d4..f886a1fd2 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.30" +#define VERSION "4.2.31" +// 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!) +// Just add AUTOSTART to the top of your myAutomation.h to restore this function. // 4.2.30 - Fixes/enhancements to EX-IOExpander device driver. // 4.2.29 - Bugfix Scroll LCD without empty lines and consistent // 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode. From fe183419941a70d75ca6a0a02acb6df6c82d5952 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Sat, 18 Mar 2023 18:53:48 +0000 Subject: [PATCH 686/870] Update myAutomation.example.h better example with power on --- myAutomation.example.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/myAutomation.example.h b/myAutomation.example.h index e080f2fd5..833845202 100644 --- a/myAutomation.example.h +++ b/myAutomation.example.h @@ -24,7 +24,8 @@ */ // This is the startup sequence, -AUTOSTART +AUTOSTART +POWERON // turn on track power SENDLOCO(3,1) // send loco 3 off along route 1 SENDLOCO(10,2) // send loco 10 off along route 2 DONE // This just ends the startup thread, leaving 2 others running. From 2e1a2d38e3b9f8c6f437906d76c35181f0d4417f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 19 Mar 2023 01:20:20 +0000 Subject: [PATCH 687/870] Update IO_EXIOExpander.h Reinstate byte-wise processing of analogue input values. --- IO_EXIOExpander.h | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 6d4377890..1aa75b731 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -105,16 +105,16 @@ class EXIOExpander : public IODevice { _digitalInputStates = (byte*) calloc(_digitalPinBytes, 1); _digitalPinBytes = digitalBytesNeeded; } - size_t analogueBytesNeeded = _numAnaloguePins * sizeof(_analogueInputValues[0]); + size_t analogueBytesNeeded = _numAnaloguePins * 2; if (_analoguePinBytes < analogueBytesNeeded) { // Free any existing buffers and allocate new ones. if (_analoguePinBytes > 0) { free(_analogueInputBuffer); - free(_analogueInputValues); + free(_analogueInputStates); free(_analoguePinMap); } - _analogueInputValues = (int16_t*) calloc(_analoguePinBytes, 1); - _analogueInputBuffer = (uint8_t*) calloc(_analoguePinBytes, 1); + _analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1); + _analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); } } else { @@ -129,6 +129,7 @@ class EXIOExpander : public IODevice { status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1); } if (status == I2C_STATUS_OK) { + DIAG(F("Map: %d %d %d %d %d %d"), _analoguePinMap[0], _analoguePinMap[1], _analoguePinMap[2], _analoguePinMap[3], _analoguePinMap[4], _analoguePinMap[5]); // Attempt to get version, if we don't get it, we don't care, don't go offline uint8_t versionBuffer[3]; commandBuffer[0] = EXIOVER; @@ -222,7 +223,7 @@ class EXIOExpander : public IODevice { // Here we need to copy the values from input buffer to the analogue value array. We need to // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed // while the value is being read). - memcpy(_analogueInputValues, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states + memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states } else if (_readState == RDS_DIGITAL) { // Read of digital states was in progress, so process received values @@ -250,7 +251,7 @@ class EXIOExpander : public IODevice { // Issue new read for analogue input states _readCommandBuffer[0] = EXIORDAN; I2CManager.read(_I2CAddress, _analogueInputBuffer, - _numAnaloguePins * sizeof(_analogueInputBuffer[0]), _readCommandBuffer, 1, &_i2crb); + _numAnaloguePins * 2, _readCommandBuffer, 1, &_i2crb); _lastAnalogueRead = currentMicros; _readState = RDS_ANALOGUE; } @@ -259,15 +260,18 @@ class EXIOExpander : public IODevice { // Obtain the correct analogue input value, with reference to the analogue // pin map. - // (QUESTION: Why isn't this mapping done in the remote node before transmission?) + // Obtain the correct analogue input value int _readAnalogue(VPIN vpin) override { if (_deviceState == DEVSTATE_FAILED) return 0; int pin = vpin - _firstVpin; for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) { - if (_analoguePinMap[aPin] == pin) - return _analogueInputValues[aPin]; + if (_analoguePinMap[aPin] == pin) { + uint8_t _pinLSBByte = aPin * 2; + uint8_t _pinMSBByte = _pinLSBByte + 1; + return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte]; + } } - return 0; // Pin not found + return -1; // pin not found in table } // Obtain the correct digital input value @@ -359,7 +363,7 @@ class EXIOExpander : public IODevice { uint8_t _patchVer = 0; uint8_t* _digitalInputStates; - int16_t* _analogueInputValues; + uint8_t* _analogueInputStates; uint8_t* _analogueInputBuffer; // buffer for I2C input transfers uint8_t _readCommandBuffer[1]; From 43c7baf8f5e6bf6d598aa4fc797b49a011e0609f Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Sun, 19 Mar 2023 22:06:02 +0000 Subject: [PATCH 688/870] Fix display scrolling on LCD and OLED Eliminate spurious blanking of screen in mode 1, duplicated lines of text in mode 2, and non-display of more than the first screen-full of lines in mode 0. --- Display.cpp | 178 +++++++++++++++++++++++++--------------------------- Display.h | 16 ++--- version.h | 3 +- 3 files changed, 95 insertions(+), 102 deletions(-) diff --git a/Display.cpp b/Display.cpp index 03d6039e0..ecb421681 100644 --- a/Display.cpp +++ b/Display.cpp @@ -1,6 +1,5 @@ /* * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. - * © 2023, Harald Barth. * * This file is part of CommandStation-EX * @@ -37,7 +36,7 @@ * not held up significantly. The exception to this is when * the loop2() function is called with force=true, where * a screen update is executed to completion. This is normally - * only noMoreRowsToDisplay during start-up. + * only done during start-up. * The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2 * in the config.h. * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), @@ -52,11 +51,10 @@ Display::Display(DisplayDevice *deviceDriver) { _deviceDriver = deviceDriver; // Get device dimensions in characters (e.g. 16x2). - numCharacterColumns = _deviceDriver->getNumCols(); - numCharacterRows = _deviceDriver->getNumRows(); + numScreenColumns = _deviceDriver->getNumCols(); + numScreenRows = _deviceDriver->getNumRows(); for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) rowBuffer[row][0] = '\0'; - topRow = ROW_INITIAL; // loop2 will fill from row 0 addDisplay(0); // Add this display as display number 0 }; @@ -70,20 +68,19 @@ void Display::_clear() { _deviceDriver->clearNative(); for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++) rowBuffer[row][0] = '\0'; - topRow = ROW_INITIAL; // loop2 will fill from row 0 } void Display::_setRow(uint8_t line) { hotRow = line; hotCol = 0; - rowBuffer[hotRow][0] = 0; // Clear existing text + rowBuffer[hotRow][0] = '\0'; // Clear existing text } size_t Display::_write(uint8_t b) { if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1; rowBuffer[hotRow][hotCol] = b; hotCol++; - rowBuffer[hotRow][hotCol] = 0; + rowBuffer[hotRow][hotCol] = '\0'; return 1; } @@ -110,8 +107,8 @@ Display *Display::loop2(bool force) { return NULL; } else { // force full screen update from the beginning. - rowFirst = ROW_INITIAL; - rowNext = ROW_INITIAL; + rowFirst = 0; + rowCurrent = 0; bufferPointer = 0; noMoreRowsToDisplay = false; slot = 0; @@ -119,109 +116,104 @@ Display *Display::loop2(bool force) { do { if (bufferPointer == 0) { - // Find a line of data to write to the screen. - if (rowFirst == ROW_INITIAL) rowFirst = rowNext; - if (findNextNonBlankRow()) { - // Non-blank line found, so copy it (including terminator) - for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++) - buffer[i] = rowBuffer[rowNext][i]; - } else { - // No non-blank lines left, so draw a blank line - buffer[0] = 0; + // Search for non-blank row + while (!noMoreRowsToDisplay) { + if (!isCurrentRowBlank()) break; + moveToNextRow(); + if (rowCurrent == rowFirst) noMoreRowsToDisplay = true; } -#if SCROLLMODE==2 - if (buffer[0] == 0 && needScroll){ // surpresses empty line -#else - if (false){ -#endif - charIndex = MAX_CHARACTER_COLS; - slot--; + + if (noMoreRowsToDisplay) { + // No non-blank lines left, so draw blank line + buffer[0] = '\0'; } else { - _deviceDriver->setRowNative(slot); // Set position for display - charIndex = 0; - bufferPointer = &buffer[0]; + // Non-blank line found, so copy it (including terminator) + for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++) + buffer[i] = rowBuffer[rowCurrent][i]; } - rowNext++; + _deviceDriver->setRowNative(slot); // Set position for display + charIndex = 0; + bufferPointer = &buffer[0]; } else { // Write next character, or a space to erase current position. char ch = *bufferPointer; if (ch) { - _deviceDriver->writeNative(ch); + _deviceDriver->writeNative(ch); bufferPointer++; } else { _deviceDriver->writeNative(' '); } - } - if (++charIndex >= MAX_CHARACTER_COLS) { - // Screen slot completed, move to next slot on screen - bufferPointer = 0; - slot++; - if (slot >= numCharacterRows) { - // Last slot on screen written, reset ready for next screen update. -#if SCROLLMODE==2 || SCROLLMODE==1 - if (!noMoreRowsToDisplay) { - needScroll = true; - } - if (needScroll) { -#if SCROLLMODE==2 - // SCROLLMODE 2 rotates through rowFirst and we - // (ab)use findNextBlankRow() to figure out - // next valid row which can be start row. - rowNext = rowFirst + 1; - noMoreRowsToDisplay = false; - findNextNonBlankRow(); - if (rowNext == ROW_INITIAL) - rowNext = 0; - rowFirst = ROW_INITIAL; + if (++charIndex >= MAX_CHARACTER_COLS) { + // Screen slot completed, move to next nonblank row + bufferPointer = 0; + for (;;) { + moveToNextRow(); + if (rowCurrent == rowFirst) { + noMoreRowsToDisplay = true; + break; + } + if (!isCurrentRowBlank()) break; + } + // Move to next screen slot, if available + slot++; + if (slot >= numScreenRows) { + // Last slot on screen written, so get ready for next screen update. +#if SCROLLMODE==0 + // Scrollmode 0 scrolls continuously. If the rows fit on the screen, + // then restart at row 0, but otherwise continue with the row + // after the last one displayed. + if (countNonBlankRows() <= numScreenRows) + rowCurrent = 0; + rowFirst = rowCurrent; +#elif SCROLLMODE==1 + // Scrollmode 1 scrolls by page, so if the last page has just completed then + // next time restart with row 0. + if (noMoreRowsToDisplay) + rowFirst = rowCurrent = 0; #else - // SCROLLMODE 1 just alternates when the - // flag indicates that we have come to the end - if (noMoreRowsToDisplay) - rowNext = 0; + // Scrollmode 2 scrolls by row. If the rows don't fit on the screen, + // then start one row further on next time. If they do fit, then + // show them in order and start next page at row 0. + if (countNonBlankRows() <= numScreenRows) { + rowFirst = rowCurrent = 0; + } else { + // Find first non-blank row after the previous first row + rowCurrent = rowFirst; + do { + moveToNextRow(); + } while (isCurrentRowBlank()); + rowFirst = rowCurrent; + } #endif - } else { - // SCROLLMODE 1 or 2 but not scroll active - rowNext = 0; - } -#else - // this is for SCROLLMODE 0 but what should it do? - rowNext = 0; -#endif - rowFirst = ROW_INITIAL; - - noMoreRowsToDisplay = false; - slot = 0; - lastScrollTime = currentMillis; - return NULL; + noMoreRowsToDisplay = false; + slot = 0; + lastScrollTime = currentMillis; + return NULL; + } } -#if SCROLLMODE==2 - if (needScroll) - noMoreRowsToDisplay = false; -#endif } } while (force); return NULL; } -bool Display::findNextNonBlankRow() { - while (!noMoreRowsToDisplay) { - if (rowNext == ROW_INITIAL) - rowNext = 0; - if (rowNext >= MAX_CHARACTER_ROWS) { - // Finished if we've looped back to start - rowNext = ROW_INITIAL; - noMoreRowsToDisplay = true; - return false; - } - if (rowBuffer[rowNext][0] != 0) { - //rowBuffer[rowNext][0] = '0' + rowNext; // usefull for debug - //rowBuffer[rowNext][1] = '0' + rowFirst; // usefull for debug - // Found non-blank row - return true; - } - rowNext = rowNext + 1; +bool Display::isCurrentRowBlank() { + return (rowBuffer[rowCurrent][0] == '\0'); +} + +void Display::moveToNextRow() { + // Skip blank rows + if (++rowCurrent >= MAX_CHARACTER_ROWS) + rowCurrent = 0; +} + +uint8_t Display::countNonBlankRows() { + uint8_t count = 0; + for (uint8_t rowNumber=0; rowNumber Date: Mon, 20 Mar 2023 10:03:02 +0100 Subject: [PATCH 689/870] exchange pin number to track letter --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 5 ++--- MotorDriver.h | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4d37765f4..a35bfaa58 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303151539Z" +#define GITHUB_SHA "devel-202303200902Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 02362151d..a62dc9ca3 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -137,8 +137,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i if (currentPin==UNUSED_PIN) DIAG(F("** WARNING ** No current or short detection")); else { - DIAG(F("CurrentPin=A%d, TripValue=%d"), - currentPin-A0, rawCurrentTripValue); + DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue); // self testing diagnostic for the non-float converters... may be removed when happy // DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"), @@ -163,7 +162,7 @@ void MotorDriver::setPower(POWERMODE mode) { // when switching a track On, we need to check the crrentOffset with the pin OFF if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) { senseOffset = ADCee::read(currentPin); - DIAG(F("CurrentPin A%d sensOffset=%d"),currentPin-A0,senseOffset); + DIAG(F("Track %c sensOffset=%d"),trackLetter,senseOffset); } IODevice::write(powerPin,invertPower ? LOW : HIGH); diff --git a/MotorDriver.h b/MotorDriver.h index 51f7654ac..77b2ec827 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -182,11 +182,13 @@ class MotorDriver { isProgTrack = on; } void checkPowerOverload(bool useProgLimit, byte trackno); + void setTrackLetter(char c); #ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); #endif private: + char trackLetter = '?'; bool isProgTrack = false; // tells us if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); void getFastPin(const FSH* type,int pin, FASTPIN & result) { From 4087cd6e298f2259b265c296d161fcb1bc1c5a41 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 20 Mar 2023 19:05:53 +1000 Subject: [PATCH 690/870] Fixed non-working analogue inputs --- IO_EXIOExpander.h | 3 +-- version.h | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index 1aa75b731..f4705f96b 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -129,7 +129,6 @@ class EXIOExpander : public IODevice { status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1); } if (status == I2C_STATUS_OK) { - DIAG(F("Map: %d %d %d %d %d %d"), _analoguePinMap[0], _analoguePinMap[1], _analoguePinMap[2], _analoguePinMap[3], _analoguePinMap[4], _analoguePinMap[5]); // Attempt to get version, if we don't get it, we don't care, don't go offline uint8_t versionBuffer[3]; commandBuffer[0] = EXIOVER; @@ -223,7 +222,7 @@ class EXIOExpander : public IODevice { // Here we need to copy the values from input buffer to the analogue value array. We need to // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed // while the value is being read). - memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states + memcpy(_analogueInputStates, _analogueInputBuffer, sizeof(_analogueInputBuffer)); // Copy I2C input buffer to states } else if (_readState == RDS_DIGITAL) { // Read of digital states was in progress, so process received values diff --git a/version.h b/version.h index d4144b802..b1704dede 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.32" +#define VERSION "4.2.33" +// 4.2.33 - Fix EX-IOExpander non-working analogue inputs // 4.2.32 - Fix LCD/Display bugfixes from 4.2.29 // 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!) // Just add AUTOSTART to the top of your myAutomation.h to restore this function. From 21c99c86944b9ff100ed89a78c922d529ee6d6d9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 20 Mar 2023 10:11:14 +0100 Subject: [PATCH 691/870] improve Wifi connect messages --- GITHUB_SHA.h | 2 +- WifiInterface.cpp | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a35bfaa58..ee808a775 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303200902Z" +#define GITHUB_SHA "devel-202303200910Z" diff --git a/WifiInterface.cpp b/WifiInterface.cpp index f69cc2ff7..21d474b2e 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -125,17 +125,18 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con wifiState = setup2( SSid, password, hostname, port, channel); if (wifiState == WIFI_NOAT) { - DIAG(F("++ Wifi Setup NO AT ++")); - return wifiState; + LCD(4, F("WiFi no AT chip")); + return wifiState; } if (wifiState == WIFI_CONNECTED) { StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo - checkForOK(200, true); + checkForOK(200, true); + DIAG(F("WiFi CONNECTED")); + // LCD already shows IP + } else { + LCD(4,F("WiFi DISCON.")); } - - - DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED")); return wifiState; } From 7ab5f556d9c9e3b76239b4becb968d276bfe1bdc Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 21 Mar 2023 05:30:33 +1000 Subject: [PATCH 692/870] Using correct size for memcpy --- IO_EXIOExpander.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f4705f96b..675d66c67 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -116,6 +116,7 @@ class EXIOExpander : public IODevice { _analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1); _analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + _analoguePinBytes = analogueBytesNeeded; } } else { DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString()); @@ -222,7 +223,7 @@ class EXIOExpander : public IODevice { // Here we need to copy the values from input buffer to the analogue value array. We need to // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed // while the value is being read). - memcpy(_analogueInputStates, _analogueInputBuffer, sizeof(_analogueInputBuffer)); // Copy I2C input buffer to states + memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states } else if (_readState == RDS_DIGITAL) { // Read of digital states was in progress, so process received values From a75ca00e3c8b24e4c9576dac49d94c1210c6b45f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 20 Mar 2023 21:22:48 +0100 Subject: [PATCH 693/870] exchange pin number to track letter part 2 --- Display.cpp | 2 +- MotorDriver.h | 4 +++- TrackManager.cpp | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Display.cpp b/Display.cpp index ecb421681..d581149c9 100644 --- a/Display.cpp +++ b/Display.cpp @@ -216,4 +216,4 @@ uint8_t Display::countNonBlankRows() { } return count; } - \ No newline at end of file + diff --git a/MotorDriver.h b/MotorDriver.h index 77b2ec827..990fd71a4 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -182,7 +182,9 @@ class MotorDriver { isProgTrack = on; } void checkPowerOverload(bool useProgLimit, byte trackno); - void setTrackLetter(char c); + inline void setTrackLetter(char c) { + trackLetter = c; + }; #ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); diff --git a/TrackManager.cpp b/TrackManager.cpp index 633ed06d4..fcd0c10fd 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -129,6 +129,7 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { track[t]=driver; if (driver) { track[t]->setPower(POWERMODE::OFF); + track[t]->setTrackLetter('A'+t); lastTrack=t; } } From d51281f1f2fa0554d56037c39ffcec54db16b53c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 20 Mar 2023 21:24:42 +0100 Subject: [PATCH 694/870] github tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index ee808a775..46d55b92e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303200910Z" +#define GITHUB_SHA "devel-202303202024Z" From b183439a5b798a956e5d5c37648fb0ce9b33b8c1 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 21 Mar 2023 05:30:33 +1000 Subject: [PATCH 695/870] Using correct size for memcpy --- IO_EXIOExpander.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IO_EXIOExpander.h b/IO_EXIOExpander.h index f4705f96b..675d66c67 100644 --- a/IO_EXIOExpander.h +++ b/IO_EXIOExpander.h @@ -116,6 +116,7 @@ class EXIOExpander : public IODevice { _analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1); _analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1); _analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1); + _analoguePinBytes = analogueBytesNeeded; } } else { DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString()); @@ -222,7 +223,7 @@ class EXIOExpander : public IODevice { // Here we need to copy the values from input buffer to the analogue value array. We need to // do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed // while the value is being read). - memcpy(_analogueInputStates, _analogueInputBuffer, sizeof(_analogueInputBuffer)); // Copy I2C input buffer to states + memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states } else if (_readState == RDS_DIGITAL) { // Read of digital states was in progress, so process received values From a3d03ac68c17fca97ad44f3719dc603e87787d63 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 21 Mar 2023 07:04:08 +1000 Subject: [PATCH 696/870] Fix validated, update version --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index b1704dede..259dbaafc 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.33" +#define VERSION "4.2.34" +// 4.2.34 - Completely fix EX-IOExpander analogue inputs // 4.2.33 - Fix EX-IOExpander non-working analogue inputs // 4.2.32 - Fix LCD/Display bugfixes from 4.2.29 // 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!) From f0ee8aeb85cc16c1f9ffcaef4cecb0315f553626 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 23 Mar 2023 19:52:49 +0000 Subject: [PATCH 697/870] z Commands --- DCCEXParser.cpp | 16 ++++++++++++++-- Release_Notes/CommandRef.md | 12 +++++++++--- version.h | 3 ++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 9e2cf5f7c..4d97b2083 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -344,6 +344,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; break; + case 'z': // direct pin manipulation + if (p[0]==0) break; + if (params==1) { // + if (p[0]>0) IODevice::write(p[0],HIGH); + else IODevice::write(-p[0],LOW); + return; + } + if (params>=2 && params<=4) { // + // unused params default to 0 + IODevice::writeAnalogue(p[0],p[1],p[2],p[3]); + return; + } + break; + case 'Z': // OUTPUT if (parseZ(stream, params, p)) return; @@ -516,9 +530,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) StringFormatter::send(stream, F("\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA)); CommandDistributor::broadcastPower(); // is the only "get power status" command we have Turnout::printAll(stream); //send all Turnout states - Output::printAll(stream); //send all Output states Sensor::printAll(stream); //send all Sensor states - // TODO Send stats of speed reminders table return; #ifndef DISABLE_EEPROM diff --git a/Release_Notes/CommandRef.md b/Release_Notes/CommandRef.md index c06e388e0..9d328d772 100644 --- a/Release_Notes/CommandRef.md +++ b/Release_Notes/CommandRef.md @@ -50,9 +50,12 @@ Note: Turnouts are best defined in myAutomation.h where a turnout description ca Valid commands respond with -Outputs (Used by JMRI, not required by EXRAIL) - Define an output pin that JMRI can set by id - Activate an output pin by id +Direct pin manipulation (replaces Set pin LOW + Set pin HIGH + Set pin analog value + Set pin analig with profile + set pin analog with profile and value Sensors (Used by JMRI, not required by EXRAIL) @@ -172,6 +175,9 @@ Obsolete commands/formats V command is much faster if prediction is correct. V command is much faster if prediction is correct. + (use (use direct pin manipulation command // 4.2.34 - Completely fix EX-IOExpander analogue inputs // 4.2.33 - Fix EX-IOExpander non-working analogue inputs // 4.2.32 - Fix LCD/Display bugfixes from 4.2.29 From 51a480dff3aa556d72db98e4cb3952dd9c3db59f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Fri, 24 Mar 2023 00:24:03 +0000 Subject: [PATCH 698/870] doc typo only --- Release_Notes/CommandRef.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Release_Notes/CommandRef.md b/Release_Notes/CommandRef.md index 9d328d772..6bade5471 100644 --- a/Release_Notes/CommandRef.md +++ b/Release_Notes/CommandRef.md @@ -51,10 +51,10 @@ Note: Turnouts are best defined in myAutomation.h where a turnout description ca Valid commands respond with Direct pin manipulation (replaces Set pin LOW - Set pin HIGH + Set pin HIGH + Set pin LOW Set pin analog value - Set pin analig with profile + Set pin analog with profile set pin analog with profile and value From 1ec378281b5e8d6e7900d2ffe058b23f84cfd8d2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 25 Mar 2023 12:14:58 +0100 Subject: [PATCH 699/870] do not broadcast a turnout state that has not changed --- GITHUB_SHA.h | 2 +- Turnouts.cpp | 35 +++++++++++++---------------------- version.h | 3 ++- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 46d55b92e..9a36e4ae6 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303202024Z" +#define GITHUB_SHA "devel-202303251113Z" diff --git a/Turnouts.cpp b/Turnouts.cpp index 582ea7db2..c02e97c75 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -110,15 +110,16 @@ /* static */ bool Turnout::setClosedStateOnly(uint16_t id, bool closeFlag) { Turnout *tt = get(id); if (!tt) return false; - tt->_turnoutData.closed = closeFlag; - // I know it says setClosedStateOnly, but we need to tell others - // that the state has changed too. - #if defined(EXRAIL_ACTIVE) - RMFT2::turnoutEvent(id, closeFlag); - #endif - - CommandDistributor::broadcastTurnout(id, closeFlag); + // that the state has changed too. But we only broadcast if there + // really has been a change. + if (tt->_turnoutData.closed != closeFlag) { + tt->_turnoutData.closed = closeFlag; + CommandDistributor::broadcastTurnout(id, closeFlag); + } +#if defined(EXRAIL_ACTIVE) + RMFT2::turnoutEvent(id, closeFlag); +#endif return true; } @@ -128,31 +129,21 @@ // type should be placed in the virtual function setClosedInternal(bool) which is // called from here. /* static */ bool Turnout::setClosed(uint16_t id, bool closeFlag) { - #if defined(DIAG_IO) - if (closeFlag) - DIAG(F("Turnout::close(%d)"), id); - else - DIAG(F("Turnout::throw(%d)"), id); - #endif +#if defined(DIAG_IO) + DIAG(F("Turnout(%d,%c)"), id, closeFlag ? 'c':'t'); +#endif Turnout *tt = Turnout::get(id); if (!tt) return false; bool ok = tt->setClosedInternal(closeFlag); if (ok) { - + tt->setClosedStateOnly(id, closeFlag); #ifndef DISABLE_EEPROM // Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress // is always zero for LCN turnouts. if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0) EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags); #endif - - #if defined(EXRAIL_ACTIVE) - RMFT2::turnoutEvent(id, closeFlag); - #endif - - // Send message to JMRI etc. - CommandDistributor::broadcastTurnout(id, closeFlag); } return ok; } diff --git a/version.h b/version.h index 25867b1e3..6a597fefb 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.35" +#define VERSION "4.2.36rc1" +// 4.2.36 - do not broadcast a turnout state that has not changed // 4.2.35 - add direct pin manipulation command // 4.2.34 - Completely fix EX-IOExpander analogue inputs // 4.2.33 - Fix EX-IOExpander non-working analogue inputs From 8a425fe0ef2b2851e57b7a4c01c2de753823cc0c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 25 Mar 2023 19:28:37 +0100 Subject: [PATCH 700/870] do not broadcast a turnout state that has not changed 2 --- GITHUB_SHA.h | 2 +- Turnouts.cpp | 10 +++++----- version.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 9a36e4ae6..8809d5575 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303251113Z" +#define GITHUB_SHA "devel-202303251828Z" diff --git a/Turnouts.cpp b/Turnouts.cpp index c02e97c75..e8f55af39 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -123,7 +123,7 @@ return true; } - +#define DIAG_IO // Static setClosed function is invoked from close(), throw() etc. to perform the // common parts of the turnout operation. Code which is specific to a turnout // type should be placed in the virtual function setClosedInternal(bool) which is @@ -289,7 +289,6 @@ #ifndef IO_NO_HAL IODevice::writeAnalogue(_servoTurnoutData.vpin, close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile); - _turnoutData.closed = close; #else (void)close; // avoid compiler warnings #endif @@ -387,7 +386,6 @@ // and Close writes a 0. // RCN-213 specifies that Throw is 0 and Close is 1. DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant); - _turnoutData.closed = close; return true; } @@ -463,7 +461,6 @@ bool VpinTurnout::setClosedInternal(bool close) { IODevice::write(_vpinTurnoutData.vpin, close); - _turnoutData.closed = close; return true; } @@ -514,7 +511,10 @@ bool LCNTurnout::setClosedInternal(bool close) { // Assume that the LCN command still uses 1 for throw and 0 for close... LCN::send('T', _turnoutData.id, !close); - // The _turnoutData.closed flag should be updated by a message from the LCN master, later. + // The _turnoutData.closed flag should be updated by a message from the LCN master. + // but in this implementation it is updated in setClosedStateOnly() instead. + // If the LCN master updates this, setClosedStateOnly() and all setClosedInternal() + // have to be updated accordingly so that the closed flag is only set once. return true; } diff --git a/version.h b/version.h index 6a597fefb..d8c03cf8a 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.36rc1" +#define VERSION "4.2.36rc2" // 4.2.36 - do not broadcast a turnout state that has not changed // 4.2.35 - add direct pin manipulation command // 4.2.34 - Completely fix EX-IOExpander analogue inputs From bdd4bc9999b37e168850bdeac662ad2173e59ea6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 25 Mar 2023 22:26:57 +0100 Subject: [PATCH 701/870] version --- GITHUB_SHA.h | 2 +- version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8809d5575..4f86707ad 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303251828Z" +#define GITHUB_SHA "devel-202303252126Z" diff --git a/version.h b/version.h index d8c03cf8a..f9484a103 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.36rc2" +#define VERSION "4.2.36" // 4.2.36 - do not broadcast a turnout state that has not changed // 4.2.35 - add direct pin manipulation command // 4.2.34 - Completely fix EX-IOExpander analogue inputs From f348857ddba2ab8897517846a1191680019c8887 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 27 Mar 2023 12:39:11 +0100 Subject: [PATCH 702/870] Add FLAGS device for EX-RAIL state communications. Improve VPIN display in messages. FLAGS HAL device added to IODevice.h, which allows use of SET/RESET// to set and reset a VPIN state, and to allow /IF/IFNOT/AT/WAITFOR/etc. to monitor the VPIN state. Also, correct handling of VPINs above 32767 in DIAG calls within IODevice.cpp and IODevice.h. --- IODevice.cpp | 24 +-- IODevice.h | 69 ++++++++ config.h.txt | 169 ++++++++++++++++++ myHal.cpp.txt | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++ version.h | 4 +- 5 files changed, 719 insertions(+), 12 deletions(-) create mode 100644 config.h.txt create mode 100644 myHal.cpp.txt diff --git a/IODevice.cpp b/IODevice.cpp index e907c23d2..03ccf856f 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -169,7 +169,7 @@ bool IODevice::hasCallback(VPIN vpin) { // Display (to diagnostics) details of the device. void IODevice::_display() { - DIAG(F("Unknown device Vpins:%d-%d %S"), + DIAG(F("Unknown device Vpins:%u-%u %S"), (int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F("")); } @@ -179,7 +179,7 @@ bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i IODevice *dev = findDevice(vpin); if (dev) return dev->_configure(vpin, configType, paramCount, params); #ifdef DIAG_IO - DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin); + DIAG(F("IODevice::configure(): VPIN %u not found!"), (int)vpin); #endif return false; } @@ -191,7 +191,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 %u not found!"), (int)vpin); #endif return false; } @@ -203,7 +203,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 %u not found!"), (int)vpin); #endif return -1023; } @@ -213,7 +213,7 @@ int IODevice::configureAnalogIn(VPIN vpin) { return dev->_configureAnalogIn(vpin); } #ifdef DIAG_IO - DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin); + DIAG(F("IODevice::configureAnalogIn(): VPIN %u not found!"), (int)vpin); #endif return -1023; } @@ -227,7 +227,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 %u not found!"), (int)vpin); #endif } @@ -246,7 +246,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 %u not found!"), (int)vpin); #endif } @@ -314,9 +314,11 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) { // Private helper function to check for vpin overlap. Run during setup only. // returns true if pins DONT overlap with existing device +// TODO: Move the I2C address reservation and checks into the I2CManager code. +// That will enable non-HAL devices to reserve I2C addresses too. bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) { #ifdef DIAG_IO - DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString()); + DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString()); #endif VPIN lastPin=firstPin+nPins-1; for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) { @@ -327,7 +329,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres VPIN lastDevPin=firstDevPin+dev->_nPins-1; bool noOverlap= firstPin>lastDevPin || lastPin= NUM_DIGITAL_PINS) return false; #ifdef DIAG_IO - DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]); + DIAG(F("Arduino _configurePullup pin:%d Val:%d"), pin, p[0]); #endif pinMode(pin, p[0] ? INPUT_PULLUP : INPUT); return true; @@ -528,7 +530,7 @@ int ArduinoPins::_configureAnalogIn(VPIN vpin) { } void ArduinoPins::_display() { - DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1); + DIAG(F("Arduino Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1); } ///////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/IODevice.h b/IODevice.h index 51b5aa06b..769e11123 100644 --- a/IODevice.h +++ b/IODevice.h @@ -467,6 +467,75 @@ class UserAddin : public IODevice { } }; +///////////////////////////////////////////////////////////////////////////////////////////////////// +// +// This HAL device driver is intended for communication in automation +// sequences. A VPIN can be SET or RESET within a sequence, and its +// current state checked elsewhere using IF, IFNOT, AT etc. or monitored +// from JMRI using a Sensor object (DCC-EX command). +// Alternatively, the flag can be set from JMRI and other interfaces +// using the command, to enable or disable actions within a sequence. +// +// Example of configuration in halSetup.h: +// +// FLAGS::create(32000, 128); +// +// or in myAutomation.h: +// +// HAL(FLAGS, 32000, 128); +// +// Both create 128 flags numbered with VPINs 32000-32127. +// +// + +class FLAGS : IODevice { +private: + uint8_t *_states = NULL; + +public: + static void create(VPIN firstVpin, unsigned int nPins) { + if (checkNoOverlap(firstVpin, nPins)) + new FLAGS(firstVpin, nPins); + } + +protected: + // Constructor performs static initialisation of the device object + FLAGS (VPIN firstVpin, int nPins) { + _firstVpin = firstVpin; + _nPins = nPins; + _states = (uint8_t *)calloc(1, (_nPins+7)/8); + if (!_states) { + DIAG(F("FLAGS: ERROR Memory Allocation Failure")); + return; + } + + addDevice(this); + } + + int _read(VPIN vpin) override { + int pin = vpin - _firstVpin; + if (pin >= _nPins || pin < 0) return 0; + uint8_t mask = 1 << (pin & 7); + return (_states[pin>>3] & mask) ? 1 : 0; + } + + void _write(VPIN vpin, int value) override { + int pin = vpin - _firstVpin; + if (pin >= _nPins || pin < 0) return; + uint8_t mask = 1 << (pin & 7); + if (value) + _states[pin>>3] |= mask; + else + _states[pin>>3] &= ~mask; + } + + void _display() override { + DIAG(F("FLAGS configured on VPINs %u-%u"), + _firstVpin, _firstVpin+_nPins-1); + } + +}; + #include "IO_MCP23008.h" #include "IO_MCP23017.h" #include "IO_PCF8574.h" diff --git a/config.h.txt b/config.h.txt new file mode 100644 index 000000000..98cc3fc53 --- /dev/null +++ b/config.h.txt @@ -0,0 +1,169 @@ +/********************************************************************** + +Config.h +COPYRIGHT (c) 2013-2016 Gregg E. Berman +COPYRIGHT (c) 2020 Fred Decker + +The configuration file for DCC++ EX Command Station + +**********************************************************************/ +///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Before connecting these boards and selecting one in this software +// check the quick install guides!!! Some of these boards require a voltage +// generating resitor on the current sense pin of the device. Failure to select +// the correct resistor could damage the sense pin on your Arduino or destroy +// the device. +// +// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: +// +// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel +// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) +// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) +// FIREBOX_MK1 : The Firebox MK1 +// FIREBOX_MK1S : The Firebox MK1S +// | +// +-----------------------v +// +// #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), +// new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), +// new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN) + +#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD + +///////////////////////////////////////////////////////////////////////////////////// +// +// The IP port to talk to a WIFI or Ethernet shield. +// +#define IP_PORT 2560 + +///////////////////////////////////////////////////////////////////////////////////// +// +// NOTE: Only supported on Arduino Mega +// Set to false if you not even want it on the Arduino Mega +// +//#define ENABLE_WIFI true + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE WiFi Parameters (only in effect if WIFI is on) +// +// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with +// the <+> commands and this sketch will not change anything over +// AT commands and the other WIFI_* defines below do not have any effect. +//#define DONT_TOUCH_WIFI_CONF +// +// WIFI_SSID is the network name IF you want to use your existing home network. +// Do NOT change this if you want to use the WiFi in Access Point (AP) mode. +// +// If you do NOT set the WIFI_SSID, the WiFi chip will first try +// to connect to the previously configured network and if that fails +// fall back to Access Point mode. The SSID of the AP will be +// automatically set to DCCEX_*. +// +// Your SSID may not conain ``"'' (double quote, ASCII 0x22). +#define WIFI_SSID "Your network name" +// +// WIFI_PASSWORD is the network password for your home network or if +// you want to change the password from default AP mode password +// to the AP password you want. +// Your password may not conain ``"'' (double quote, ASCII 0x22). +#define WIFI_PASSWORD "deadcafe" +// +// WIFI_HOSTNAME: You probably don't need to change this +#define WIFI_HOSTNAME "dccex" +// +///////////////////////////////////////////////////////////////////////////////////// +// +// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need +// to set this if you have an extremely slow Wifi router. +// +#define WIFI_CONNECT_TIMEOUT 14000 + +///////////////////////////////////////////////////////////////////////////////////// +// +// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This +// is not for Wifi. You will then need the Arduino Ethernet library as well +// +//#define ENABLE_ETHERNET true + + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP +// +//#define IP_ADDRESS { 192, 168, 1, 31 } + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE +// +// Uncomment to use with Ethernet Shields +// +// Ethernet Shields do not have have a MAC address in hardware. There may be one on +// a sticker on the Shield that you should use. Otherwise choose one of the ones below +// Be certain that no other device on your network has this same MAC address! +// +// 52:b8:8a:8e:ce:21 +// e3:e9:73:e1:db:0d +// 54:2b:13:52:ac:0c + +// NOTE: This is not used with ESP8266 WiFi modules. + +//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above + +// +// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } + +///////////////////////////////////////////////////////////////////////////////////// +// +// 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', +// OR an I2C Oled screen based on SSD1306 (128x64 or 128x32) controller, +// OR an I2C Oled screen based on SH1106 (132x64) controller. +// To enable, uncomment one of the lines below + +// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows +//#define LCD_DRIVER {SubBus_4,0x27},20,4 + +//OR define OLED_DRIVER width,height in pixels (address auto detected) +#if defined(ARDUINO_ARCH_STM32) +#define OLED_DRIVER 0x3c, 128, 64 +#else +#define OLED_DRIVER {SubBus_0,0x3c}, 128, 32 +#endif + +#define SCROLLMODE 1 + +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE EEPROM +// +// If you do not need the EEPROM at all, you can disable all the code that saves +// data in the EEPROM. You might want to do that if you are in a Arduino UNO +// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM +// to do that. Of course, then none of the EEPROM related commands work. +// +#define DISABLE_EEPROM + + +///////////////////////////////////////////////////////////////////////////////////// +// +// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213 +// +// According to norm RCN-213 a DCC packet with a 1 is closed/straight +// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous +// versions of DCC++EX, a turnout throw command was implemented in the packet as +// '1' and a close command as '0'. The #define below makes the states +// match with the norm. But we don't want to cause havoc on existent layouts, +// so we define this only for new installations. If you don't want this, +// don't add it to your config.h. +//#define DCC_TURNOUTS_RCN_213 + +// The following #define likewise inverts the behaviour of the command +// for triggering DCC Accessory Decoders, so that generates a +// DCC packet with D=1 (close turnout) and generates D=0 +// (throw turnout). +//#define DCC_ACCESSORY_RCN_213 + +///////////////////////////////////////////////////////////////////////////////////// diff --git a/myHal.cpp.txt b/myHal.cpp.txt new file mode 100644 index 000000000..b5b612e47 --- /dev/null +++ b/myHal.cpp.txt @@ -0,0 +1,465 @@ +#include "defines.h" +#include "IODevice.h" + +#ifndef IO_NO_HAL + +#include "IO_VL53L0X.h" +#include "IO_HCSR04.h" +#include "Sensors.h" +#include "Turnouts.h" +#include "IO_DFPlayer.h" +//#include "IO_Wire.h" +#include "IO_AnalogueInputs.h" +#if __has_include("IO_Servo.h") +#include "IO_Servo.h" +#include "IO_PCA9685pwm.h" +#endif + +#include "IO_HALDisplay.h" +#include "LiquidCrystal_I2C.h" + +#if __has_include("IO_CMRI.h") +#include "IO_CMRI.h" +#endif + +//#include "IO_ExampleSerial.h" + +//#include "IO_EXFastclock.h" +//#include "IO_EXTurntable.h" + +#if __has_include("IO_ExternalEEPROM.h") +#include "IO_ExternalEEPROM.h" +#endif + +#if __has_include("IO_Network.h") +#include "IO_Network.h" +#include "Net_RF24.h" +#include "Net_ENC28J60.h" +#include "Net_Ethernet.h" +#define NETWORK_PRESENT +#endif + +#include "IO_TouchKeypad.h" + +#define WIRE_TEST 0 +#define TESTHARNESS 1 +#define I2C_STRESS_TEST 0 +#define I2C_SETCLOCK 0 + +#include "DCC.h" + + +#if 0 // Long Strings +#define s10 "0123456789" +#define s100 s10 s10 s10 s10 s10 s10 s10 s10 s10 s10 +#define s1k s100 s100 s100 s100 s100 s100 s100 s100 s100 s100 +#define s10k s1k s1k s1k s1k s1k s1k s1k s1k s1k s1k +#define s32k s10k s10k s10k s1k s1k +volatile const char PROGMEM ss1[] = s32k; +#endif + + +#if TESTHARNESS + +// Function to be invoked by test harness +void myTest() { + // DIAG(F("VL53L0X #1 Test: dist=%d signal=%d ambient=%d value=%d"), + // IODevice::readAnalogue(5000), + // IODevice::readAnalogue(5001), + // IODevice::readAnalogue(5002), + // IODevice::read(5000)); + // DIAG(F("VL53L0X #2 Test: dist=%d signal=%d ambient=%d value=%d"), + // IODevice::readAnalogue(5003), + // IODevice::readAnalogue(5004), + // IODevice::readAnalogue(5005), + // IODevice::read(5003)); + // DIAG(F("HCSR04 Test: dist=%d value=%d"), + // IODevice::readAnalogue(2000), + // IODevice::read(2000)); + // DIAG(F("ADS111x Test: %d %d %d %d %d"), + // IODevice::readAnalogue(4500), + // IODevice::readAnalogue(4501), + // IODevice::readAnalogue(4502), + // IODevice::readAnalogue(4503), + // IODevice::readAnalogue(A5) + // ); + // DIAG(F("RF24 Test: 4000:%d 4002:%d"), + // IODevice::read(4000), + // IODevice::read(4002) + // ); + DIAG(F("EXPANDER: 2212:%d 2213:%d 2214:%d"), + IODevice::readAnalogue(2212), + IODevice::readAnalogue(2213), + IODevice::readAnalogue(2214)); +} +#endif + +#if I2C_STRESS_TEST +static bool initialised = false; +static uint8_t lastStatus = 0; +static const int nRBs = 3; // request blocks concurrently +static const int I2cTestPeriod = 1; // milliseconds +static I2CAddress testDevice = {SubBus_6, 0x27}; +static I2CRB rb[nRBs]; +static uint8_t readBuffer[nRBs*32]; // nRB x 32-byte input buffer +static uint8_t writeBuffer[nRBs]; // nRB x 1-byte output buffer +static unsigned long count = 0; +static unsigned long errors = 0; +static unsigned long lastOutput = millis(); + +void I2CTest() { + if (!initialised) { + // I2C Loading for stress test. + // Write value then read back 32 times + for (int i=0; i 60000) { // 1 minute + DIAG(F("I2CTest: Count=%l Errors=%l"), count, errors); + count = errors = 0; + lastOutput = millis(); + } +} +#endif + +void updateLocoScreen() { + for (int i=0; i<8; i++) { + if (DCC::speedTable[i].loco > 0) { + int speed = DCC::speedTable[i].speedCode; + char direction = (speed & 0x80) ? 'R' : 'F'; + speed = speed & 0x7f; + if (speed > 0) speed = speed - 1; + SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco, + speed, direction); + } + } +} + +void updateTime() { + uint8_t buffer[20]; + I2CAddress rtc = {SubBus_1, 0x68}; // Real-time clock I2C address + buffer[0] = 0; + + // Set time - only needs to be done once if battery is ok. + static bool timeSet = false; + if (!timeSet) { + // I2CManager.read(rtc, buffer+1, sizeof(buffer)-1); + // uint8_t year = 23; // 2023 + // uint8_t day = 2; // tuesday + // uint8_t date = 21; // 21st + // uint8_t month = 2; // feb + // uint8_t hours = 23; // xx: + // uint8_t minutes = 25; // :xx + // buffer[1] = 0; // seconds + // buffer[2] = ((minutes / 10) << 4) | (minutes % 10); + // buffer[3] = ((hours / 10) << 4) | (hours % 10); + // buffer[4] = day; + // buffer[5] = ((date/10) << 4) + date%10; // 24th + // buffer[6] = ((month/10) << 4) + month%10; // feb + // buffer[7] = ((year/10) << 4) + year%10; // xx23 + // for (uint8_t i=8; i> 4; + uint8_t seconds1 = buffer[1] & 0xf; + uint8_t minutes10 = buffer[2] >> 4; + uint8_t minutes1 = buffer[2] & 0xf; + uint8_t hours10 = buffer[3] >> 4; + uint8_t hours1 = buffer[3] & 0xf; + SCREEN(10, 0, F("Departures %d%d:%d%d:%d%d"), + hours10, hours1, minutes10, minutes1, seconds10, seconds1); + } +} + +void showCharacterSet() { + if (millis() < 3000) return; + const uint8_t lineLen = 20; + char buffer[lineLen+1]; + static uint8_t nextChar = 0x20; + for (uint8_t row=0; row<8; row+=1) { + for (uint8_t col=0; col::create(10, {SubBus_5, 0x3c}, 132, 64); // SH1106 + // UserAddin::create(updateLocoScreen, 1000); + // UserAddin::create(showCharacterSet, 5000); + // UserAddin::create(updateTime, 1000); + + HALDisplay::create(10, {SubBus_4, 0x3c}, 128, 32); + HALDisplay::create(10, {SubBus_7, 0x3c}, 128, 32); + + //HALDisplay::create(10, {SubBus_4, 0x27}, 20, 4); + + // Draw double boxes with X O O X inside. + // SCREEN(3, 2, F("\xc9\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xbb")); + // SCREEN(3, 3, F("\xba X \xba O \xba O \xba O \xba X \xba")); + // SCREEN(3, 4, F("\xcc\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xb9")); + // SCREEN(3, 5, F("\xba X \xba O \xba O \xba O \xba X \xba")); + // SCREEN(3, 6, F("\xc8\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xbc")); + + // Draw single boxes with X O O X inside. + // SCREEN(3, 0, F("Summary Data:")); + // SCREEN(3, 1, F("\xda\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xbf")); + // SCREEN(3, 2, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3")); + // SCREEN(3, 3, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4")); + // SCREEN(3, 4, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3")); + // SCREEN(3, 5, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4")); + // SCREEN(3, 6, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3")); + // SCREEN(3, 7, F("\xc0\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xd9")); + + // Blocks of different greyness + // SCREEN(3, 0, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb")); + // SCREEN(3, 1, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb")); + // SCREEN(3, 2, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb")); + + // DCCEX logo + // SCREEN(3, 1, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0")); + // SCREEN(3, 2, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0")); + // SCREEN(3, 3, F("\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0\xb0\x20\x20\x20\xb0\x20")); + // SCREEN(3, 4, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0")); + // SCREEN(3, 5, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0")); + // SCREEN(3, 7, F("\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1")); + +#if 0 + // List versions of devices that respond to the version request + for (uint8_t address = 8; address<0x78; address++) { + uint8_t buffer[3]; + uint8_t status = I2CManager.read(0x7c, buffer, sizeof(buffer), 1, address); + if (status == I2C_STATUS_OK) { + uint16_t manufacturer = ((uint16_t)buffer[0] << 4 ) | (buffer[1] >> 4); + uint16_t deviceID = ((uint16_t)(buffer[1] & 0x0f) << 5) | (buffer[2] >> 3); + uint16_t dieRevision = buffer[2] & 0x1f; + DIAG(F("Addr %s version: %x %x %x"), address.toString(), manufacturer, deviceID, dieRevision); + } + } +#endif + +#if I2C_STRESS_TEST + UserAddin::create(I2CTest, I2cTestPeriod); +#endif + +#if WIRE_TEST + // Test of Wire-I2CManager interface + Wire.begin(); + Wire.setClock(400000); + Wire.beginTransmission(0x23); + Wire.print("Hello"); + uint8_t status = Wire.endTransmission(); + if (status==0) DIAG(F("Wire: device Found on 0x23")); + + Wire.beginTransmission(0x23); + Wire.write(0xde); + Wire.endTransmission(false); // don't send stop + Wire.requestFrom(0x23, 1); + if (Wire.available()) { + DIAG(F("Wire: value=x%x"), Wire.read()); + } + uint8_t st = I2CManager.write(0x33, 0, 0); + DIAG(F("I2CManager 0x33 st=%d \"%S\""), st, + I2CManager.getErrorMessage(st)); +#endif + +#if I2C_SETCLOCK + // Test I2C clock changes + // Set up two I2C request blocks + I2CRB rb1, rb2; + uint8_t readBuff[32]; + rb1.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff)); + rb2.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff)); + // First set clock to 400kHz and then issue requests + I2CManager.forceClock(400000); + I2CManager.queueRequest(&rb1); + I2CManager.queueRequest(&rb2); + // Wait a little to allow the first transaction to start + delayMicroseconds(2); + // ... then request a clock speed change + I2CManager.forceClock(100000); + DIAG(F("I2CClock: rb1 status=%d"), rb1.wait()); + DIAG(F("I2CClock: rb2 status=%d"), rb2.wait()); + // Reset clock speed + I2CManager.forceClock(400000); +#endif + + EXIOExpander::create(2200, 18, {SubBus_0, 0x65}); + //UserAddin::create(myTest, 1000); + // ServoTurnout::create(2200, 2200, 400, 200, 0); + // ServoTurnout::create(2200, 2200, 400, 200, 0); + + TouchKeypad::create(2300, 16, 25, 24); + + // GPIO + PCF8574::create(800, 8, {SubBus_1, 0x23}); + //PCF8574::create(808, 8, {SubBus_2, 0x27}); + PCF8574::create(65000, 8, 0x27); + + MCP23017::create(164,16,{SubBus_3, 0x20}); + //MCP23017::create(180,16,{SubBus_0, 0x27}); + Sensor::create(170, 170, 1); // Hall effect, enable pullup. + Sensor::create(171, 171, 1); + + // PWM (LEDs and Servos) + // For servos, use default 50Hz pulses. + PCA9685::create(100, 16, {SubBus_1, 0x41}); + // For LEDs, use 1kHz pulses. + PCA9685::create(116, 16, {SubBus_1, 0x40}, 1000); + + // 4-pin Analogue Input Module + //ADS111x::create(4500, 4, 0x48); + + // Laser Time-Of-Flight Sensors + VL53L0X::create(5000, 3, {SubBus_0, 0x60}, 300, 310, 46); + //VL53L0X::create(5003, 3, {SubBus_6, 0x61}, 300, 310, 47); + Sensor::create(5000, 5000, 0); + Sensor::create(5003, 5003, 0); + // Monitor reset digital on first TOF + //Sensor::create(46,46,0); + + // // External 24C256 EEPROM (256kBytes) on I2C address 0x50. + // ExternalEEPROM::create({SubBus_0, 0x50}, 256); + + // Play up to 10 sounds on pins 10000-10009. Player is connected to Serial1 or Serial2. + #if defined(HAVE_HWSERIAL1) && !defined(ARDUINO_ARCH_STM32) + DFPlayer::create(10000, 14, Serial1); + #elif defined(ARDUINO_ARCH_STM32) + DFPlayer::create(10000, 10, Serial3); // Pins PC11 (RX) and PC10 (TX) + #endif + + // Ultrasound echo device + HCSR04::create(2000, 32, 33, 80, 85 /*, HCSR04::LOOP */); + Sensor::create(2000, 2000, 0); + +#if __has_include("IO_CMRI.h") + CMRIbus::create(0, Serial2, 115200, 50, 40); // 50ms cycle, pin 40 for DE/!RE pins + CMRInode::create(25000, 72, 0, 0, 'M'); // SMINI address 0 + for (int pin=0; pin<24; pin++) { + Sensor::create(25000+pin, 25000+pin, 0); + } +#endif + + //CMRInode::create(25072, 72, 0, 13, 'M'); // SMINI address 13 + //CMRInode::create(25144, 288, 0, 14, 'C', 144, 144); // CPNODE address 14 + +#ifdef NETWORK_PRESENT + // Define remote pins to be used. The range of remote pins is like a common data area shared + // between all nodes. + // For outputs, a write to a remote VPIN causes a message to be sent to another node, which then performs + // the write operation on the device VPIN that is local to that node. + // For inputs, the state of remote input VPIN is read on the node where it is connected, and then + // sent to other nodes in the system where the state is saved and processed. Updates are sent on change, and + // also periodically if no changes. + // + // Each definition is a triple of remote node, remote pin, indexed by relative pin. Up to 224 rpins can + // be configured (per node). This is to fit into a 32-byte packet. + REMOTEPINS rpins[] = { + {30,164,RPIN_IN} , //4000 Node 30, first MCP23017 pin, input + {30,165,RPIN_IN}, //4001 Node 30, second MCP23017 pin, input + {30,166,RPIN_OUT}, //4002 Node 30, third MCP23017 pin, output + {30,166,RPIN_OUT}, //4003 Node 30, fourth MCP23017 pin, output + {30,100,RPIN_INOUT}, //4004 Node 30, first PCA9685 servo pin + {30,101,RPIN_INOUT}, //4005 Node 30, second PCA9685 servo pin + {30,102,RPIN_INOUT}, //4006 Node 30, third PCA9685 servo pin + {30,103,RPIN_INOUT}, //4007 Node 30, fourth PCA9685 servo pin + {30,24,RPIN_IN}, //4008 Node 30, Arduino pin D24 + {30,25,RPIN_IN}, //4009 Node 30, Arduino pin D25 + {30,26,RPIN_IN}, //4010 Node 30, Arduino pin D26 + {30,27,RPIN_IN}, //4011 Node 30, Arduino pin D27 + {30,1000,RPIN_OUT}, //4012 Node 30, DFPlayer playing flag (when read) / Song selector (when written) + {30,5000,RPIN_IN}, //4013 Node 30, VL53L0X detect pin + {30,VPIN_NONE,0}, //4014 Node 30, spare + {30,VPIN_NONE,0}, //4015 Node 30, spare + + {31,164,RPIN_IN} , //4016 Node 31, first MCP23017 pin, input + {31,165,RPIN_IN}, //4017 Node 31, second MCP23017 pin, input + {31,166,RPIN_OUT}, //4018 Node 31, third MCP23017 pin, output + {31,166,RPIN_OUT}, //4019 Node 31, fourth MCP23017 pin, output + {31,100,RPIN_INOUT}, //4020 Node 31, first PCA9685 servo pin + {31,101,RPIN_INOUT}, //4021 Node 31, second PCA9685 servo pin + {31,102,RPIN_INOUT}, //4022 Node 31, third PCA9685 servo pin + {31,103,RPIN_INOUT}, //4023 Node 31, fourth PCA9685 servo pin + {31,24,RPIN_IN}, //4024 Node 31, Arduino pin D24 + {31,25,RPIN_IN}, //4025 Node 31, Arduino pin D25 + {31,26,RPIN_IN}, //4026 Node 31, Arduino pin D26 + {31,27,RPIN_IN}, //4027 Node 31, Arduino pin D27 + {31,3,RPIN_IN}, //4028 Node 31, Arduino pin D3 + {31,VPIN_NONE,0}, //4029 Node 31, spare + {31,VPIN_NONE,0}, //4030 Node 31, spare + {31,VPIN_NONE,0} //4031 Node 31, spare + }; + // FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin + // Net_RF24 *rf24Driver = new Net_RF24(48, 49); + // Network::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, rf24Driver); + #if NODE==30 + //Net_ENC28J60 *encDriver = new Net_ENC28J60(49); + //Network::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver); + #elif NODE==31 + Net_ENC28J60 *encDriver = new Net_ENC28J60(53); + Network::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver); + #else + Net_Ethernet *etherDriver = new Net_Ethernet(); + Network::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, etherDriver); + #endif + for (int i=0; i<=32; i++) + Sensor::create(4000+i, 4000+i, 0); +#endif + +#ifdef ARDUINO_ARCH_STM32 +//PCF8574::create(1900, 8, 0x27); +Sensor::create(1900,100,1); +Sensor::create(1901,101,1); +#endif + +} +#endif // IO_NO_HAL diff --git a/version.h b/version.h index f9484a103..47d82dbc0 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.36" +#define VERSION "4.2.37" +// 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL; +// - Fix diag display of high VPINs within IODevice class. // 4.2.36 - do not broadcast a turnout state that has not changed // 4.2.35 - add direct pin manipulation command // 4.2.34 - Completely fix EX-IOExpander analogue inputs From 60ea7f081a48e830500a1c35e5a9ed7986419ad2 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 27 Mar 2023 13:03:19 +0100 Subject: [PATCH 703/870] Correct display of high VPIN numbers in diagnostic output. No functional change. VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE). Values above 32767 were erroneously being displayed as negative. This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used. --- EXRAIL2.cpp | 4 ++-- IODevice.cpp | 2 +- IO_AnalogueInputs.h | 2 +- IO_DCCAccessory.cpp | 2 +- IO_DFPlayer.h | 4 ++-- IO_EXTurntable.h | 4 ++-- IO_ExampleSerial.h | 6 +++--- IO_GPIOBase.h | 2 +- IO_HCSR04.h | 2 +- IO_PCA9685.cpp | 8 ++++---- IO_PCA9685pwm.h | 4 ++-- IO_RotaryEncoder.h | 2 +- IO_Servo.h | 8 ++++---- IO_TouchKeypad.h | 2 +- IO_VL53L0X.h | 2 +- IO_duinoNodes.h | 2 +- 16 files changed, 28 insertions(+), 28 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index bb5989aac..52d10e8f2 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -198,7 +198,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { case OPCODE_IFNOT: { int16_t pin = (int16_t)operand; if (pin<0) pin = -pin; - DIAG(F("EXRAIL input vpin %d"),pin); + DIAG(F("EXRAIL input VPIN %u"),pin); IODevice::configureInput((VPIN)pin,true); break; } @@ -208,7 +208,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { case OPCODE_IFGTE: case OPCODE_IFLT: case OPCODE_DRIVE: { - DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand); + DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand); IODevice::configureAnalogIn((VPIN)operand); break; } diff --git a/IODevice.cpp b/IODevice.cpp index 03ccf856f..af6efc89b 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -329,7 +329,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres VPIN lastDevPin=firstDevPin+dev->_nPins-1; bool noOverlap= firstPin>lastDevPin || lastPin> 8; uint8_t stepsLSB = value & 0xFF; #ifdef DIAG_IO - DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), + DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), _I2CAddress.toString(), stepsMSB, stepsLSB, activity); @@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, + DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_ExampleSerial.h b/IO_ExampleSerial.h index da421c58c..5c70eb4f3 100644 --- a/IO_ExampleSerial.h +++ b/IO_ExampleSerial.h @@ -84,7 +84,7 @@ class IO_ExampleSerial : public IODevice { void _write(VPIN vpin, int value) { int pin = vpin -_firstVpin; #ifdef DIAG_IO - DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value); + DIAG(F("IO_ExampleSerial::_write VPIN:%u Value:%d"), (int)vpin, value); #endif // Send a command string over the serial line _serial->print('#'); @@ -153,10 +153,10 @@ class IO_ExampleSerial : public IODevice { // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). // Here we display the current values held for the pins. void _display() { - DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin, + DIAG(F("IO_ExampleSerial Configured on Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1); for (int i=0; i<_nPins; i++) - DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]); + DIAG(F(" VPin %2u: %d"), _firstVpin+i, _pinValues[i]); } diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 66b9ff6d4..94265c323 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -196,7 +196,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { template void GPIOBase::_display() { - DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(), + DIAG(F("%S I2C:%s Configured on Vpins:%u-%u %S"), _deviceName, _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 26f0ecd64..be1d30339 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -234,7 +234,7 @@ class HCSR04 : public IODevice { } void _display() override { - DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"), + DIAG(F("HCSR04 Configured on VPIN:%u TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"), _firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold); } diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 71f36613d..3c8b37ab0 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -46,7 +46,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; #ifdef DIAG_IO - DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + DIAG(F("PCA9685 Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -118,7 +118,7 @@ void PCA9685::_begin() { // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); + DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; if (value) value = 1; @@ -145,7 +145,7 @@ void PCA9685::_write(VPIN vpin, int value) { // void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { #ifdef DIAG_IO - DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + DIAG(F("PCA9685 WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; @@ -262,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) { // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, + DIAG(F("PCA9685 I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index 042516be8..a12cb84d2 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -121,7 +121,7 @@ class PCA9685pwm : public IODevice { void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning #ifdef DIAG_IO - DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), + DIAG(F("PCA9685pwm WriteAnalogue VPIN:%u Value:%d %S"), vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; @@ -134,7 +134,7 @@ class PCA9685pwm : public IODevice { // Display details of this device. void _display() override { - DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, + DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index b271b48ea..09d0c4d4f 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,7 +104,7 @@ class RotaryEncoder : public IODevice { } void _display() override { - DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, + DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_Servo.h b/IO_Servo.h index 8f95463a4..216b88610 100644 --- a/IO_Servo.h +++ b/IO_Servo.h @@ -98,7 +98,7 @@ class Servo : IODevice { if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; #ifdef DIAG_IO - DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + DIAG(F("Servo: Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -165,7 +165,7 @@ class Servo : IODevice { void _write(VPIN vpin, int value) override { if (_deviceState == DEVSTATE_FAILED) return; #ifdef DIAG_IO - DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; if (value) value = 1; @@ -193,7 +193,7 @@ class Servo : IODevice { // void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { #ifdef DIAG_IO - DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + DIAG(F("Servo: WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; @@ -288,7 +288,7 @@ class Servo : IODevice { // Display details of this device. void _display() override { - DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + DIAG(F("Servo Configured on Vpins:%u-%u, slave pins:%d-%d %S"), (int)_firstVpin, (int)_firstVpin+_nPins-1, (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); diff --git a/IO_TouchKeypad.h b/IO_TouchKeypad.h index 8984bcec7..48e3b2547 100644 --- a/IO_TouchKeypad.h +++ b/IO_TouchKeypad.h @@ -124,7 +124,7 @@ class TouchKeypad : public IODevice { // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). void _display() { - DIAG(F("TouchKeypad Configured on VPins:%d-%d SCL=%d SDO=%d"), (int)_firstVpin, + DIAG(F("TouchKeypad Configured on Vpins:%u-%u SCL=%d SDO=%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1, _clockPin, _dataPin); } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 7c8051803..08fe44e18 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -319,7 +319,7 @@ class VL53L0X : public IODevice { } void _display() override { - DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), + DIAG(F("VL53L0X I2C:%s Configured on Vpins:%u-%u On:%dmm Off:%dmm %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h index 73de1a1d1..60ef2eade 100644 --- a/IO_duinoNodes.h +++ b/IO_duinoNodes.h @@ -121,7 +121,7 @@ void _loopOutput() { } void _display() override { - DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"), + DIAG(F("IO_duinoNodes %SPUT Configured on Vpins:%u-%u shift=%d"), _pinMap?F("IN"):F("OUT"), (int)_firstVpin, (int)_firstVpin+_nPins-1, _nShiftBytes*8); From 86c30206724fadf70854345d7e90d459988b8e02 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Mon, 27 Mar 2023 13:08:14 +0100 Subject: [PATCH 704/870] Correct display of high VPIN numbers in diagnostic output. No functional change. VPINs are unsigned integers in the range 0-65535 (although the highest values are special, 65535=VPIN_NONE). Values above 32767 were erroneously being displayed as negative. This has been fixed, which is a pre-requisite for allowing VPINs above 32767 to be used. --- DCCEXParser.cpp | 2 +- EXRAIL2.cpp | 4 ++-- IODevice.cpp | 2 +- IO_AnalogueInputs.h | 4 ++-- IO_DCCAccessory.cpp | 2 +- IO_DFPlayer.h | 4 ++-- IO_EXTurntable.h | 4 ++-- IO_ExampleSerial.h | 6 +++--- IO_GPIOBase.h | 2 +- IO_HCSR04.h | 2 +- IO_PCA9685.cpp | 8 ++++---- IO_PCA9685pwm.h | 4 ++-- IO_RotaryEncoder.h | 2 +- IO_Servo.h | 12 ++++++------ IO_TouchKeypad.h | 2 +- IO_VL53L0X.h | 2 +- IO_duinoNodes.h | 2 +- 17 files changed, 32 insertions(+), 32 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 4d97b2083..102d27158 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -967,7 +967,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) break; case HASH_KEYWORD_ANIN: // Display analogue input value - DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1])); + DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1])); break; #if !defined(IO_NO_HAL) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index bb5989aac..52d10e8f2 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -198,7 +198,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { case OPCODE_IFNOT: { int16_t pin = (int16_t)operand; if (pin<0) pin = -pin; - DIAG(F("EXRAIL input vpin %d"),pin); + DIAG(F("EXRAIL input VPIN %u"),pin); IODevice::configureInput((VPIN)pin,true); break; } @@ -208,7 +208,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) { case OPCODE_IFGTE: case OPCODE_IFLT: case OPCODE_DRIVE: { - DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand); + DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand); IODevice::configureAnalogIn((VPIN)operand); break; } diff --git a/IODevice.cpp b/IODevice.cpp index 03ccf856f..af6efc89b 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -329,7 +329,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres VPIN lastDevPin=firstDevPin+dev->_nPins-1; bool noOverlap= firstPin>lastDevPin || lastPin> 8; uint8_t stepsLSB = value & 0xFF; #ifdef DIAG_IO - DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"), + DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"), vpin, value, activity, duration); DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"), _I2CAddress.toString(), stepsMSB, stepsLSB, activity); @@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_ // Display Turnetable-EX device driver info. void EXTurntable::_display() { - DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, + DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_ExampleSerial.h b/IO_ExampleSerial.h index da421c58c..5c70eb4f3 100644 --- a/IO_ExampleSerial.h +++ b/IO_ExampleSerial.h @@ -84,7 +84,7 @@ class IO_ExampleSerial : public IODevice { void _write(VPIN vpin, int value) { int pin = vpin -_firstVpin; #ifdef DIAG_IO - DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value); + DIAG(F("IO_ExampleSerial::_write VPIN:%u Value:%d"), (int)vpin, value); #endif // Send a command string over the serial line _serial->print('#'); @@ -153,10 +153,10 @@ class IO_ExampleSerial : public IODevice { // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). // Here we display the current values held for the pins. void _display() { - DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin, + DIAG(F("IO_ExampleSerial Configured on Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1); for (int i=0; i<_nPins; i++) - DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]); + DIAG(F(" VPin %2u: %d"), _firstVpin+i, _pinValues[i]); } diff --git a/IO_GPIOBase.h b/IO_GPIOBase.h index 66b9ff6d4..94265c323 100644 --- a/IO_GPIOBase.h +++ b/IO_GPIOBase.h @@ -196,7 +196,7 @@ void GPIOBase::_loop(unsigned long currentMicros) { template void GPIOBase::_display() { - DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(), + DIAG(F("%S I2C:%s Configured on Vpins:%u-%u %S"), _deviceName, _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_HCSR04.h b/IO_HCSR04.h index 26f0ecd64..be1d30339 100644 --- a/IO_HCSR04.h +++ b/IO_HCSR04.h @@ -234,7 +234,7 @@ class HCSR04 : public IODevice { } void _display() override { - DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"), + DIAG(F("HCSR04 Configured on VPIN:%u TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"), _firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold); } diff --git a/IO_PCA9685.cpp b/IO_PCA9685.cpp index 71f36613d..3c8b37ab0 100644 --- a/IO_PCA9685.cpp +++ b/IO_PCA9685.cpp @@ -46,7 +46,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; #ifdef DIAG_IO - DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + DIAG(F("PCA9685 Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -118,7 +118,7 @@ void PCA9685::_begin() { // For this function, the configured profile is used. void PCA9685::_write(VPIN vpin, int value) { #ifdef DIAG_IO - DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); + DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; if (value) value = 1; @@ -145,7 +145,7 @@ void PCA9685::_write(VPIN vpin, int value) { // void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { #ifdef DIAG_IO - DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + DIAG(F("PCA9685 WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; @@ -262,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) { // Display details of this device. void PCA9685::_display() { - DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, + DIAG(F("PCA9685 I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_PCA9685pwm.h b/IO_PCA9685pwm.h index 042516be8..a12cb84d2 100644 --- a/IO_PCA9685pwm.h +++ b/IO_PCA9685pwm.h @@ -121,7 +121,7 @@ class PCA9685pwm : public IODevice { void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { (void)param1; (void)param2; // suppress compiler warning #ifdef DIAG_IO - DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), + DIAG(F("PCA9685pwm WriteAnalogue VPIN:%u Value:%d %S"), vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; @@ -134,7 +134,7 @@ class PCA9685pwm : public IODevice { // Display details of this device. void _display() override { - DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin, + DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin, (int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index b271b48ea..09d0c4d4f 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,7 +104,7 @@ class RotaryEncoder : public IODevice { } void _display() override { - DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, + DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, (int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_Servo.h b/IO_Servo.h index 8f95463a4..9e3211e36 100644 --- a/IO_Servo.h +++ b/IO_Servo.h @@ -98,7 +98,7 @@ class Servo : IODevice { if (configType != CONFIGURE_SERVO) return false; if (paramCount != 5) return false; #ifdef DIAG_IO - DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), + DIAG(F("Servo: Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), vpin, params[0], params[1], params[2], params[3], params[4]); #endif @@ -140,12 +140,12 @@ class Servo : IODevice { // Get reference to slave device. _slaveDevice = findDevice(_firstSlavePin); if (!_slaveDevice) { - DIAG(F("Servo: Slave device not found on pins %d-%d"), + DIAG(F("Servo: Slave device not found on Vpins %u-%u"), _firstSlavePin, _firstSlavePin+_nPins-1); _deviceState = DEVSTATE_FAILED; } if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) { - DIAG(F("Servo: Slave device does not cover all pins %d-%d"), + DIAG(F("Servo: Slave device does not cover all Vpins %u-%u"), _firstSlavePin, _firstSlavePin+_nPins-1); _deviceState = DEVSTATE_FAILED; } @@ -165,7 +165,7 @@ class Servo : IODevice { void _write(VPIN vpin, int value) override { if (_deviceState == DEVSTATE_FAILED) return; #ifdef DIAG_IO - DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value); + DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value); #endif int pin = vpin - _firstVpin; if (value) value = 1; @@ -193,7 +193,7 @@ class Servo : IODevice { // void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { #ifdef DIAG_IO - DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), + DIAG(F("Servo: WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"), vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); #endif if (_deviceState == DEVSTATE_FAILED) return; @@ -288,7 +288,7 @@ class Servo : IODevice { // Display details of this device. void _display() override { - DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"), + DIAG(F("Servo Configured on Vpins:%u-%u, slave pins:%d-%d %S"), (int)_firstVpin, (int)_firstVpin+_nPins-1, (int)_firstSlavePin, (int)_firstSlavePin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); diff --git a/IO_TouchKeypad.h b/IO_TouchKeypad.h index 8984bcec7..48e3b2547 100644 --- a/IO_TouchKeypad.h +++ b/IO_TouchKeypad.h @@ -124,7 +124,7 @@ class TouchKeypad : public IODevice { // Display information about the device, and perhaps its current condition (e.g. active, disabled etc). void _display() { - DIAG(F("TouchKeypad Configured on VPins:%d-%d SCL=%d SDO=%d"), (int)_firstVpin, + DIAG(F("TouchKeypad Configured on Vpins:%u-%u SCL=%d SDO=%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1, _clockPin, _dataPin); } diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 7c8051803..08fe44e18 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -319,7 +319,7 @@ class VL53L0X : public IODevice { } void _display() override { - DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"), + DIAG(F("VL53L0X I2C:%s Configured on Vpins:%u-%u On:%dmm Off:%dmm %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); } diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h index 73de1a1d1..60ef2eade 100644 --- a/IO_duinoNodes.h +++ b/IO_duinoNodes.h @@ -121,7 +121,7 @@ void _loopOutput() { } void _display() override { - DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"), + DIAG(F("IO_duinoNodes %SPUT Configured on Vpins:%u-%u shift=%d"), _pinMap?F("IN"):F("OUT"), (int)_firstVpin, (int)_firstVpin+_nPins-1, _nShiftBytes*8); From f8b054cf6ab0dfa05451f4ee0e07c53d89a51942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Mon, 27 Mar 2023 16:44:47 +0200 Subject: [PATCH 705/870] [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing (#325) * [ESP32] Use GPIO 35/A2 and 34/A3 for current sensing while used in combination with the standard Motor Shield * Update version.h changelog --- MotorDrivers.h | 6 +++--- version.h | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index c0660528e..cc13f5494 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -73,12 +73,12 @@ #elif defined(ARDUINO_ARCH_ESP32) // STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the // 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the -// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that +// analog inputs. Here we use analog inputs A2 and A3 as A0 and A1 are wired in a way so that // they are not useable at the same time as WiFi (what a bummer). The numbers below are the // actual GPIO numbers. In comments the numbers the pins have on an Uno. #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN) + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN) #else // STANDARD shield on any Arduino Uno or Mega compatible with the original specification. diff --git a/version.h b/version.h index 47d82dbc0..5cb5cea5c 100644 --- a/version.h +++ b/version.h @@ -8,12 +8,13 @@ // 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL; // - Fix diag display of high VPINs within IODevice class. // 4.2.36 - do not broadcast a turnout state that has not changed +// - Use A2/A3 for current sensing on ESP32 + Motor Shield // 4.2.35 - add direct pin manipulation command // 4.2.34 - Completely fix EX-IOExpander analogue inputs // 4.2.33 - Fix EX-IOExpander non-working analogue inputs // 4.2.32 - Fix LCD/Display bugfixes from 4.2.29 // 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!) -// Just add AUTOSTART to the top of your myAutomation.h to restore this function. +// Just add AUTOSTART to the top of your myAutomation.h to restore this function. // 4.2.30 - Fixes/enhancements to EX-IOExpander device driver. // 4.2.29 - Bugfix Scroll LCD without empty lines and consistent // 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode. From 89664eff9d947ab84be7900ae75d2db9ffac82e6 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 30 Mar 2023 06:54:18 +1000 Subject: [PATCH 706/870] Cleaned up warning --- IO_RotaryEncoder.h | 3 ++- version.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 09d0c4d4f..00a82493f 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -98,7 +98,8 @@ class RotaryEncoder : public IODevice { void _write(VPIN vpin, int value) override { if (vpin == _firstVpin + 1) { - byte _feedbackBuffer[2] = {RE_OP, value}; + if (value != 0) value = 0x01; + byte _feedbackBuffer[2] = {RE_OP, (byte)value}; I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } diff --git a/version.h b/version.h index 5cb5cea5c..c29f773ff 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.37" +#define VERSION "4.2.38" +// 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included // 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL; // - Fix diag display of high VPINs within IODevice class. // 4.2.36 - do not broadcast a turnout state that has not changed From 6b535654f8027c4dffcd3e8de32fd66fdda27a0e Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 31 Mar 2023 16:40:40 +0100 Subject: [PATCH 707/870] DFplayer driver now polls device to detect failures and errors. Add cyclic (1-second) poll of DFplayer device to detect if it goes unresponsive. --- IO_DFPlayer.h | 155 ++++++++++++++++++++++++++++++++++++-------------- version.h | 3 +- 2 files changed, 114 insertions(+), 44 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index f3ebaaac5..c12ae0b38 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -1,5 +1,5 @@ /* - * © 2022, Neil McKechnie. All rights reserved. + * © 2023, Neil McKechnie. All rights reserved. * * This file is part of DCC++EX API * @@ -33,10 +33,13 @@ * and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1). * * Example: - * In mySetup function within mySetup.cpp: + * In halSetup function within myHal.cpp: * DFPlayer::create(3500, 5, Serial1); + * or in myAutomation.h: + * HAL(DFPlayer, 3500, 5, Serial1) * - * Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card; + * Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the + * SD card; e.g. a value of 1 will play the first file, 2 for the second file etc. * Writing an analogue value 0 to the first pin (3500) will stop the file playing; * Writing an analogue value 0-30 to the second pin (3501) will set the volume; * Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g. @@ -61,6 +64,10 @@ * card (as listed by the DIR command in Windows). This may not match the order of the files * as displayed by Windows File Manager, which sorts the file names. It is suggested that * files be copied into an empty SDcard in the desired order, one at a time. + * + * The driver now polls the device for its current status every second. Should the device + * fail to respond it will be marked off-line and its busy indicator cleared, to avoid + * lock-ups in automation scripts that are executing for a WAITFOR(). */ #ifndef IO_DFPlayer_h @@ -74,7 +81,10 @@ class DFPlayer : public IODevice { HardwareSerial *_serial; bool _playing = false; uint8_t _inputIndex = 0; - unsigned long _commandSendTime; // Allows timeout processing + unsigned long _commandSendTime; // Time (us) that last transmit took place. + unsigned long _timeoutTime; + uint8_t _recvCMD; // Last received command code byte + bool _awaitingResponse = false; uint8_t _requestedVolumeLevel = MAXVOLUME; uint8_t _currentVolume = MAXVOLUME; int _requestedSong = -1; // -1=none, 0=stop, >0=file number @@ -102,80 +112,135 @@ class DFPlayer : public IODevice { // Send a query to the device to see if it responds sendPacket(0x42); - _commandSendTime = micros(); + _timeoutTime = micros() + 5000000UL; // 5 second timeout + _awaitingResponse = true; } void _loop(unsigned long currentMicros) override { - // Check for incoming data on _serial, and update busy flag accordingly. + + // Read responses from device + processIncoming(); + + // Check if a command sent to device has timed out. Allow 0.5 second for response + if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { + DIAG(F("DFPlayer device not responding on serial port")); + _deviceState = DEVSTATE_FAILED; + _awaitingResponse = false; + _playing = false; + } + + // Send any commands that need to go. + processOutgoing(currentMicros); + + delayUntil(currentMicros + 10000); // Only enter every 10ms + } + + // Check for incoming data on _serial, and update busy flag and other state accordingly + void processIncoming() { // Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF" + bool ok = false; while (_serial->available()) { int c = _serial->read(); - if (c == 0x7E && _inputIndex == 0) - _inputIndex = 1; - else if ((c==0xFF && _inputIndex==1) - || (c==0x3D && _inputIndex==3) - || (_inputIndex >=4 && _inputIndex <= 8)) - _inputIndex++; - else if (c==0x06 && _inputIndex==2) { - // 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 - if (_playing) { - #ifdef DIAG_IO - DIAG(F("DFPlayer: Finished")); - #endif - _playing = false; - } - _inputIndex = 0; - } else - _inputIndex = 0; // Unrecognised character sequence, start again! + switch (_inputIndex) { + case 0: + if (c == 0x7E) ok = true; + break; + case 1: + if (c == 0xFF) ok = true; + break; + case 2: + if (c== 0x06) ok = true; + break; + case 3: + _recvCMD = c; // CMD byte + ok = true; + break; + case 6: + switch (_recvCMD) { + case 0x42: + // Response to status query + _playing = (c != 0); + // Mark the device online and cancel timeout + if (_deviceState==DEVSTATE_INITIALISING) { + _deviceState = DEVSTATE_NORMAL; + #ifdef DIAG_IO + _display(); + #endif + } + _awaitingResponse = false; + break; + case 0x3d: + // End of play + if (_playing) { + #ifdef DIAG_IO + DIAG(F("DFPlayer: Finished")); + #endif + _playing = false; + } + break; + case 0x40: + // Error code + DIAG(F("DFPlayer: Error %d returned from device"), c); + _playing = false; + break; + } + ok = true; + break; + case 4: case 5: case 7: case 8: + ok = true; // Skip over these bytes in message. + break; + case 9: + if (c==0xef) { + // Message finished + } + break; + default: + break; + } + if (ok) + _inputIndex++; // character as expected, so increment index + else + _inputIndex = 0; // otherwise reset. } + } - // Check if the initial prompt to device has timed out. Allow 5 seconds - if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) { - DIAG(F("DFPlayer device not responding on serial port")); - _deviceState = DEVSTATE_FAILED; - } + // Send any commands that need to be sent + void processOutgoing(unsigned long currentMicros) { // When two commands are sent in quick succession, the device will often fail to // execute one. Testing has indicated that a delay of 100ms or more is required // between successive commands to get reliable operation. // If 100ms has elapsed since the last thing sent, then check if there's some output to do. - if (currentMicros - _commandSendTime > 100000UL) { + if (((int32_t)currentMicros - _commandSendTime) > 100000) { if (_currentVolume > _requestedVolumeLevel) { // Change volume before changing song if volume is reducing. _currentVolume = _requestedVolumeLevel; sendPacket(0x06, _currentVolume); - _commandSendTime = currentMicros; } else if (_requestedSong > 0) { // Change song sendPacket(0x03, _requestedSong); _requestedSong = -1; - _commandSendTime = currentMicros; } else if (_requestedSong == 0) { sendPacket(0x0e); // Pause playing _requestedSong = -1; - _commandSendTime = currentMicros; } else if (_currentVolume < _requestedVolumeLevel) { // Change volume after changing song if volume is increasing. _currentVolume = _requestedVolumeLevel; sendPacket(0x06, _currentVolume); - _commandSendTime = currentMicros; + } else if ((int32_t)currentMicros - _commandSendTime > 1000000) { + // Poll device every second that other commands aren't being sent, + // to check if it's still connected and responding. + sendPacket(0x42); + _timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds + _awaitingResponse = true; } } - delayUntil(currentMicros + 10000); // Only enter every 10ms } // 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 { + if (_deviceState == DEVSTATE_FAILED) return; int pin = vpin - _firstVpin; if (value) { // Value 1, start playing @@ -200,6 +265,7 @@ class DFPlayer : public IODevice { // WriteAnalogue on second pin sets the output volume. // void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override { + if (_deviceState == DEVSTATE_FAILED) return; uint8_t pin = vpin - _firstVpin; #ifdef DIAG_IO @@ -228,6 +294,7 @@ class DFPlayer : public IODevice { // A read on any pin indicates whether the player is still playing. int _read(VPIN) override { + if (_deviceState == DEVSTATE_FAILED) return false; return _playing; } @@ -264,6 +331,8 @@ class DFPlayer : public IODevice { // Output the command _serial->write(out, sizeof(out)); + + _commandSendTime = micros(); } uint16_t calcChecksum(uint8_t* packet) diff --git a/version.h b/version.h index c29f773ff..da95f9318 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.38" +#define VERSION "4.2.39" +// 4.2.39 - DFplayer driver now polls device to detect failures and errors. // 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included // 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL; // - Fix diag display of high VPINs within IODevice class. From 6fbaca79300677b355e15215f78e35cb5f724857 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 31 Mar 2023 16:50:18 +0100 Subject: [PATCH 708/870] Update IO_DFPlayer.h Ensure device goes off-line when not responding. --- IO_DFPlayer.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index c12ae0b38..4c2b2f197 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -231,8 +231,10 @@ class DFPlayer : public IODevice { // Poll device every second that other commands aren't being sent, // to check if it's still connected and responding. sendPacket(0x42); - _timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds - _awaitingResponse = true; + if (!_awaitingResponse) { + _timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds + _awaitingResponse = true; + } } } } From 1b4faa92cd708a403d6f26890a1c94fc1bad0304 Mon Sep 17 00:00:00 2001 From: Neil McKechnie Date: Fri, 31 Mar 2023 17:58:30 +0100 Subject: [PATCH 709/870] Update IO_DFPlayer.h Reinstate STOP command in place of PAUSE, as PAUSE was being reported differently to STOP in the status response. --- IO_DFPlayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IO_DFPlayer.h b/IO_DFPlayer.h index 4c2b2f197..edd416de3 100644 --- a/IO_DFPlayer.h +++ b/IO_DFPlayer.h @@ -221,7 +221,7 @@ class DFPlayer : public IODevice { sendPacket(0x03, _requestedSong); _requestedSong = -1; } else if (_requestedSong == 0) { - sendPacket(0x0e); // Pause playing + sendPacket(0x16); // Stop playing _requestedSong = -1; } else if (_currentVolume < _requestedVolumeLevel) { // Change volume after changing song if volume is increasing. From 95c1b1da312082c571d6d23799f6603e413a3c66 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:48:11 +1000 Subject: [PATCH 710/870] Figuring out commands --- installer.ps1 | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 installer.ps1 diff --git a/installer.ps1 b/installer.ps1 new file mode 100644 index 000000000..e740d80ab --- /dev/null +++ b/installer.ps1 @@ -0,0 +1,53 @@ +# +# © 2023 Peter Cole +# +# This file is part of EX-CommandStation +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# It is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with CommandStation. If not, see . +# + +# 32/64 bit Win installer +# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip +# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip + +param( + [Parameter()] + [String]$BUILD_DIR +) + +if (!$PSBoundParameters.ContainsKey('BUILD_DIR')) { +# Use the current date/time stamp to create a unique directory if one is not specified. + $BUILD_DATE = Get-Date -Format 'yyyyMMdd-HHmmss' + $BUILD_DIR = $env:TEMP + "\" + $BUILD_DATE +} + +if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { + $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" + $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" +} else { + $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" + $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" +} + +Write-Output "Downloading installer to $OUTFILE" + +$ProgressPreference = "SilentlyContinue" +Invoke-WebRequest -Uri $URL -OutFile $OUTFILE + +$CLI_INSTALL = $env:TEMP + "\" + "arduino-cli_installer" + +Expand-Archive -Path $OUTFILE -DestinationPath $CLI_INSTALL -Force +$ProgressPreference = "Continue" + +Write-Output "Installing using directory $BUILD_DIR" \ No newline at end of file From 305e0902f47bafb24d012ae6c2dd738c3cef4b45 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 4 Apr 2023 19:30:59 +1000 Subject: [PATCH 711/870] Got tag list --- installer.ps1 | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index e740d80ab..defccd601 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -21,33 +21,39 @@ # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip +$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" + param( [Parameter()] - [String]$BUILD_DIR + [String]$buildDirectory ) -if (!$PSBoundParameters.ContainsKey('BUILD_DIR')) { +if (!$PSBoundParameters.ContainsKey('buildDirectory')) { # Use the current date/time stamp to create a unique directory if one is not specified. - $BUILD_DATE = Get-Date -Format 'yyyyMMdd-HHmmss' - $BUILD_DIR = $env:TEMP + "\" + $BUILD_DATE + $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' + $buildDirectory = $env:TEMP + "\" + $buildDate } if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { - $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" - $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" + $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" + $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" } else { - $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" - $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" + $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" + $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" } -Write-Output "Downloading installer to $OUTFILE" +Write-Output "Downloading installer to $arduinoCLIZip" $ProgressPreference = "SilentlyContinue" -Invoke-WebRequest -Uri $URL -OutFile $OUTFILE +Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip -$CLI_INSTALL = $env:TEMP + "\" + "arduino-cli_installer" +$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" -Expand-Archive -Path $OUTFILE -DestinationPath $CLI_INSTALL -Force +Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force $ProgressPreference = "Continue" -Write-Output "Installing using directory $BUILD_DIR" \ No newline at end of file +Write-Output "Installing using directory $buildDirectory" + +foreach ($tag in Invoke-RestMethod -Uri $gitHubAPITags | Format-List -Property ref) { + $tag.getType() +} From b18df1405cc0317cf419977a7f57a3626ed84258 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Wed, 5 Apr 2023 05:30:00 +1000 Subject: [PATCH 712/870] Got tag version and URL --- installer.ps1 | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index defccd601..3b9553547 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -20,10 +20,11 @@ # 32/64 bit Win installer # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip +# +# For script errors set ExecutionPolicy: +# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass -$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" - -param( +Param( [Parameter()] [String]$buildDirectory ) @@ -34,6 +35,8 @@ if (!$PSBoundParameters.ContainsKey('buildDirectory')) { $buildDirectory = $env:TEMP + "\" + $buildDate } +$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" + if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" @@ -54,6 +57,12 @@ $ProgressPreference = "Continue" Write-Output "Installing using directory $buildDirectory" -foreach ($tag in Invoke-RestMethod -Uri $gitHubAPITags | Format-List -Property ref) { - $tag.getType() +$tagList = Invoke-RestMethod -Uri $gitHubAPITags + +# Example zip: https://github.com/DCC-EX/CommandStation-EX/archive/refs/tags/v4.2.36-Devel.zip + +foreach ($tag in $tagList) { + $version = $tag.ref.split("/")[2] + $versionURL = "https://github.com/DCC-EX/CommandStation-EX/archive/" + $tag.ref + Write-Output "$version : $versionURL" } From de6e91a7782576c460d4d2011ffea9ad5cf49994 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:54:48 +1000 Subject: [PATCH 713/870] Working on logic --- installer.ps1 | 115 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 25 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 3b9553547..d23218775 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -1,4 +1,4 @@ -# +<# # © 2023 Peter Cole # # This file is part of EX-CommandStation @@ -15,28 +15,31 @@ # # You should have received a copy of the GNU General Public License # along with CommandStation. If not, see . -# +#> -# 32/64 bit Win installer -# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip -# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip -# -# For script errors set ExecutionPolicy: -# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass +<############################################ +For script errors set ExecutionPolicy: +Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass +############################################> +<############################################ +Optional command line parameters: + $buildDirectory - specify an existing directory rather than generating a new unique one + $version - specify an exact version to download +############################################> Param( [Parameter()] - [String]$buildDirectory + [String]$buildDirectory, + [Parameter()] + [String]$version ) -if (!$PSBoundParameters.ContainsKey('buildDirectory')) { -# Use the current date/time stamp to create a unique directory if one is not specified. - $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' - $buildDirectory = $env:TEMP + "\" + $buildDate -} - +<############################################ +Define global parameters here such as known URLs etc. +############################################> +$installerVersion = "v0.0.1" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" - +$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" @@ -44,25 +47,87 @@ if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchi $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" } +$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" +$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe" + +<############################################ +Set default action for progress indicators, warnings, and errors +############################################> +$global:ProgressPreference = "SilentlyContinue" +$global:WarningPreference = "SilentlyContinue" +$global:ErrorActionPreference = "SilentlyContinue" + +<############################################ +If $buildDirectory not provided, generate a new time/date stamp based directory to use +############################################> +if (!$PSBoundParameters.ContainsKey('buildDirectory')) { + $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' + $buildDirectory = $env:TEMP + "\" + $buildDate +} +$commandStationDirectory = $buildDirectory + "\CommandStation-EX" -Write-Output "Downloading installer to $arduinoCLIZip" +<############################################ +Write out intro message and prompt to continue +############################################> +@" +Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion) -$ProgressPreference = "SilentlyContinue" -Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip +Current installer options: +- EX-CommandStation will be built in $commandStationDirectory +- Arduino CLI will be in $arduinoCLIDirectory +"@ -$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" -Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force -$ProgressPreference = "Continue" +<############################################ +Create build directory if it doesn't exist, or fail +############################################> +if (!(Test-Path -PathType Container -Path $buildDirectory)) { + try { + New-Item -ItemType Directory -Path $buildDirectory | Out-Null + } + catch { + Write-Output "Could not create build directory $buildDirectory" + Exit + } +} +<############################################ +See if we have the Arduino CLI already, otherwise download and extract it +############################################> +if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { + if (!(Test-Path -PathType Container -Path $arduinoCLIDirectory)) { + try { + New-Item -ItemType Directory -Path $arduinoCLIDirectory | Out-Null + } + catch { + Write-Output "Arduino CLI does not exist and cannot create directory $arduinoCLIDirectory" + Exit + } + } + Write-Output "Downloading and extracting Arduino CLI" + try { + Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip + } + catch { + Write-Output "Failed to download Arduino CLI" + Exit + } + try { + Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force + } + catch { + Write-Output "Failed to extract Arduino CLI" + } +} + +<# Write-Output "Installing using directory $buildDirectory" $tagList = Invoke-RestMethod -Uri $gitHubAPITags -# Example zip: https://github.com/DCC-EX/CommandStation-EX/archive/refs/tags/v4.2.36-Devel.zip - foreach ($tag in $tagList) { $version = $tag.ref.split("/")[2] - $versionURL = "https://github.com/DCC-EX/CommandStation-EX/archive/" + $tag.ref + $versionURL = $gitHubURLPrefix + $tag.ref Write-Output "$version : $versionURL" } +#> \ No newline at end of file From 18a992bf0867a64d335fce149bfffd053d4afb0e Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 6 Apr 2023 05:31:11 +1000 Subject: [PATCH 714/870] Start getting tag list --- installer.ps1 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/installer.ps1 b/installer.ps1 index d23218775..296fb6d8d 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -120,6 +120,31 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { } } +<# +Get the list of tags +#> +try { + $tagList = Invoke-RestMethod -Uri $gitHubAPITags +} +catch { + Write-Output "Failed to obtain list of available EX-CommandStation versions" + Exit +} + +<# +Get latest two Prod and Devel releases, add to hash table for selection by user +#> +foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { + if ($tag.ref -Like "*Prod") { + Write-Output $tag.ref + } +} +foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { + if ($tag.ref -Like "*Devel") { + Write-Output $tag.ref + } +} + <# Write-Output "Installing using directory $buildDirectory" From 9571088e1b0aa918cce3998845b57dc2f5d7090f Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 6 Apr 2023 07:03:59 +1000 Subject: [PATCH 715/870] Added disable logic --- IODevice.cpp | 28 ++++++++++++++++++++++------ version.h | 3 ++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/IODevice.cpp b/IODevice.cpp index af6efc89b..2ed21b6cd 100644 --- a/IODevice.cpp +++ b/IODevice.cpp @@ -63,15 +63,31 @@ void IODevice::begin() { if (exrailHalSetup) exrailHalSetup(); - // Predefine two PCA9685 modules 0x40-0x41 + // Predefine two PCA9685 modules 0x40-0x41 if no conflicts // Allocates 32 pins 100-131 - PCA9685::create(100, 16, 0x40); - PCA9685::create(116, 16, 0x41); + if (checkNoOverlap(100, 16, 0x40)) { + PCA9685::create(100, 16, 0x40); + } else { + DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device")); + } + if (checkNoOverlap(116, 16, 0x41)) { + PCA9685::create(116, 16, 0x41); + } else { + DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device")); + } - // Predefine two MCP23017 module 0x20/0x21 + // Predefine two MCP23017 module 0x20/0x21 if no conflicts // Allocates 32 pins 164-195 - MCP23017::create(164, 16, 0x20); - MCP23017::create(180, 16, 0x21); + if (checkNoOverlap(164, 16, 0x20)) { + MCP23017::create(164, 16, 0x20); + } else { + DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device")); + } + if (checkNoOverlap(180, 16, 0x21)) { + MCP23017::create(180, 16, 0x21); + } else { + DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device")); + } } // reset() function to reinitialise all devices diff --git a/version.h b/version.h index da95f9318..399f68d6a 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.39" +#define VERSION "4.2.40" +// 4.2.40 - Automatically detect conflicting default I2C devices and disable // 4.2.39 - DFplayer driver now polls device to detect failures and errors. // 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included // 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL; From 273f55b1435fad720c982d9679da266859106a11 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 5 Apr 2023 23:19:43 +0100 Subject: [PATCH 716/870] 4.2.41 Hal setup and DNOU8 fix --- CommandStation-EX.ino | 8 ++++---- IO_duinoNodes.h | 1 + version.h | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 3c40bba9f..736356625 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -75,6 +75,9 @@ void setup() DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); +// Initialise HAL layer before reading EEprom or setting up MotorDrivers + IODevice::begin(); + DISPLAY_START ( // This block is still executed for DIAGS if display not in use LCD(0,F("DCC-EX v%S"),F(VERSION)); @@ -96,10 +99,7 @@ void setup() #if ETHERNET_ON EthernetInterface::setup(); #endif // ETHERNET_ON - - // Initialise HAL layer before reading EEprom or setting up MotorDrivers - IODevice::begin(); - + // As the setup of a motor shield may require a read of the current sense input from the ADC, // let's make sure to initialise the ADCee class! ADCee::begin(); diff --git a/IO_duinoNodes.h b/IO_duinoNodes.h index 60ef2eade..c764db212 100644 --- a/IO_duinoNodes.h +++ b/IO_duinoNodes.h @@ -55,6 +55,7 @@ class IO_duinoNodes : public IODevice { pinMode(_clockPin,OUTPUT); pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT); _display(); + if (!_pinMap) _loopOutput(); } // loop called by HAL supervisor diff --git a/version.h b/version.h index 399f68d6a..a8c8bde7b 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.40" +#define VERSION "4.2.41" +// 4.2.41 - Move HAl startup to ASAP in setup() +// - Fix DNOU8 output pin setup to all LOW // 4.2.40 - Automatically detect conflicting default I2C devices and disable // 4.2.39 - DFplayer driver now polls device to detect failures and errors. // 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included From de06c0ae3eb9422f8e4d85e7d0d5de432507658e Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:06:22 +1000 Subject: [PATCH 717/870] Working on CLI commands --- installer.ps1 | 129 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 14 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 296fb6d8d..b803e50a3 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -31,7 +31,7 @@ Param( [Parameter()] [String]$buildDirectory, [Parameter()] - [String]$version + [String]$configDirectory ) <############################################ @@ -120,31 +120,132 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { } } -<# +<############################################ Get the list of tags -#> +############################################> try { - $tagList = Invoke-RestMethod -Uri $gitHubAPITags + $gitHubTags = Invoke-RestMethod -Uri $gitHubAPITags } catch { Write-Output "Failed to obtain list of available EX-CommandStation versions" Exit } -<# -Get latest two Prod and Devel releases, add to hash table for selection by user -#> -foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { - if ($tag.ref -Like "*Prod") { - Write-Output $tag.ref - } +<############################################ +Get our GitHub tag list in a hash so we can sort by version numbers and extract just the ones we want +############################################> +$versionMatch = ".*?v(\d+)\.(\d+).(\d+)-(.*)" +$tagList = @{} +foreach ($tag in $gitHubTags) { + $tagHash = @{} + $tagHash["Ref"] = $tag.ref + $version = $tag.ref.split("/")[2] + $null = $version -match $versionMatch + $tagHash["Major"] = [int]$Matches[1] + $tagHash["Minor"] = [int]$Matches[2] + $tagHash["Patch"] = [int]$Matches[3] + $tagHash["Type"] = $Matches[4] + $tagList.Add($version, $tagHash) } -foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { - if ($tag.ref -Like "*Devel") { - Write-Output $tag.ref + +<############################################ +Get latest two Prod and Devel for user to select +############################################> +$userList = @{} +$prodCount = 1 +$devCount = 1 +$select = 1 +foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$_]["Minor"]},{$tagList[$_]["Patch"]} -Descending) { + if (($tagList[$tag]["Type"] -eq "Prod") -and $prodCount -le 2) { + $userList[$select] = $tag + $select++ + $prodCount++ + } elseif (($tagList[$tag]["Type"] -eq "Devel") -and $devCount -le 2) { + $userList[$select] = $tag + $select++ + $devCount++ } } +<############################################ +Display options for user to select and get the selection +############################################> +foreach ($selection in $userList.Keys | Sort-Object $selection) { + Write-Output "$selection - $($userList[$selection])" +} +Write-Output "5 - Exit" +$userSelection = 0 +do { + [int]$userSelection = Read-Host "Select the version to install from the list above (1 - 5)" +} until ( + (($userSelection -ge 1) -and ($userSelection -le 5)) +) +if ($userSelection -eq 5) { + Write-Output "Exiting installer" + Exit +} else { + $downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip" +} + +<############################################ +Download the chosen version to the build directory +############################################> +$downladFile = $buildDirectory + "\CommandStation-EX.zip" +Write-Output "Downloading and extracting $($userList[$userSelection])" +try { + Invoke-WebRequest -Uri $downloadURL -OutFile $downladFile +} +catch { + Write-Output "Error downloading EX-CommandStation zip file" + Exit +} + +<############################################ +Extract and rename to CommandStation-EX to allow building +############################################> +try { + Expand-Archive -Path $downladFile -DestinationPath $buildDirectory -Force +} +catch { + Write-Output "Failed to extract EX-CommandStation zip file" + Exit +} + +$folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "") +Write-Output $folderName +try { + Rename-Item -Path $folderName -NewName $commandStationDirectory +} +catch { + Write-Output "Could not rename folder" + Exit +} + +<############################################ +If config directory provided, copy files here +############################################> + + +<############################################ +Once files all together, identify available board(s) +############################################> +# Need to do an initial board list to download everything first +& $arduinoCLI board list | Out-Null +# Run again to generate the list of discovered boards +& $arduinoCLI board list --format json + +<############################################ +Get user to select board +############################################> + + + + +<############################################ +Upload the sketch to the selected board +############################################> +#$arduinoCLI upload -b fqbn -p port $commandStationDirectory + <# Write-Output "Installing using directory $buildDirectory" From 9f212c27bf3ec38576fd7a4525db6bde85ee7e24 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Fri, 7 Apr 2023 08:06:32 +1000 Subject: [PATCH 718/870] Initial test working! --- installer.ps1 | 142 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 16 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index b803e50a3..1c47fc350 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -34,6 +34,24 @@ Param( [String]$configDirectory ) +<############################################ +List of supported devices with FQBN in case clones used that aren't detected +############################################> +$supportedDevices = @( + @{ + name = "Arduino Mega or Mega 2560" + fqbn = "arduino:avr:mega" + }, + @{ + name = "Arduino Nano" + fqbn = "arduino:avr:nano" + }, + @{ + name = "Arduino Uno" + fqbn = "arduino:avr:uno" + } +) + <############################################ Define global parameters here such as known URLs etc. ############################################> @@ -75,6 +93,9 @@ Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVers Current installer options: - EX-CommandStation will be built in $commandStationDirectory - Arduino CLI will be in $arduinoCLIDirectory + +Available EX-CommandStation versions: +------------------------------------- "@ @@ -176,7 +197,7 @@ foreach ($selection in $userList.Keys | Sort-Object $selection) { Write-Output "5 - Exit" $userSelection = 0 do { - [int]$userSelection = Read-Host "Select the version to install from the list above (1 - 5)" + [int]$userSelection = Read-Host "`r`nSelect the version to install from the list above (1 - 5)" } until ( (($userSelection -ge 1) -and ($userSelection -le 5)) ) @@ -200,6 +221,14 @@ catch { Exit } +<############################################ +If folder exists, bail out and tell user +############################################> +if (Test-Path -PathType Container -Path "$buildDirectory\CommandStation-EX") { + Write-Output "EX-CommandStation directory already exists, please ensure you have copied any user files then delete manually: $buildDirectory\CommandStation-EX" + Exit +} + <############################################ Extract and rename to CommandStation-EX to allow building ############################################> @@ -224,36 +253,117 @@ catch { <############################################ If config directory provided, copy files here ############################################> - +# To be done +# If exists copy config.h, myAutomation.h, myHal.cpp, mySetup.h <############################################ Once files all together, identify available board(s) ############################################> # Need to do an initial board list to download everything first -& $arduinoCLI board list | Out-Null -# Run again to generate the list of discovered boards -& $arduinoCLI board list --format json +try { + & $arduinoCLI board list | Out-Null +} +catch { + Write-Output "Failed to update Arduino CLI board list" + Exit +} +# Run again to generate the list of discovered boards into a custom object +try { + $boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to obtain list of boards" + Exit +} <############################################ Get user to select board ############################################> +if ($boardList.count -eq 0) { + Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it" + Exit +} else { +@" +Devices attached to COM ports: +------------------------------ +"@ + $boardSelect = 1 + foreach ($board in $boardList) { + if ($board.matching_boards.name) { + $boardName = $board.matching_boards.name + } else { + $boardName = "Unknown device" + } + $port = $board.port.address + Write-Output "$boardSelect - $boardName on port $port" + $boardSelect++ + } + Write-Output "$boardSelect - Exit" + $userSelection = 0 + do { + [int]$userSelection = Read-Host "`r`nSelect the device to use from the list above" + } until ( + (($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1))) + ) + if ($userSelection -eq ($boardList.count + 1)) { + Write-Output "Exiting installer" + Exit + } else { + $selectedBoard = $userSelection - 1 + } +} +<############################################ +If the board is unknown, need to choose which one +############################################> +if ($null -eq $boardList[$selectedBoard].matching_boards.name) { + Write-Output "The device selected is unknown, these boards are supported:`r`n" + $deviceSelect = 1 + foreach ($device in $supportedDevices) { + Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)" + $deviceSelect++ + } + Write-Output "$deviceSelect - Exit" + $userSelection = 0 + do { + [int]$userSelection = Read-Host "Select the board type from the list above" + } until ( + (($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1))) + ) + if ($userSelection -eq ($supportedDevices.count + 1)) { + Write-Output "Exiting installer" + Exit + } else { + $deviceName = $supportedDevices[$userSelection - 1].name + $deviceFQBN = $supportedDevices[$userSelection - 1].fqbn + } +} else { + $deviceName = $boardList[$selectedBoard].matching_boards.name + $deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn + $devicePort = $boardList[$selectedBoard].port.address +} <############################################ Upload the sketch to the selected board ############################################> #$arduinoCLI upload -b fqbn -p port $commandStationDirectory - -<# -Write-Output "Installing using directory $buildDirectory" - -$tagList = Invoke-RestMethod -Uri $gitHubAPITags - -foreach ($tag in $tagList) { - $version = $tag.ref.split("/")[2] - $versionURL = $gitHubURLPrefix + $tag.ref - Write-Output "$version : $versionURL" +Write-Output "Compiling for $deviceName" +try { + $output = & $arduinoCLI compile -b $deviceFQBN $commandStationDirectory --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to compile" + Exit +} +Write-Output "$output" +Write-Output "Now uploading to $deviceName on port $devicePort" +try { + $output = & $arduinoCLI upload -t -b $deviceFQBN -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to upload" + Exit } -#> \ No newline at end of file +Write-Output "$output" From c3d2e5b2222302a927b46cdd8c1cc7993bb546f4 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 7 Apr 2023 13:59:49 +1000 Subject: [PATCH 719/870] Fix to PIO build target names for Teensy --- platformio.ini | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1fc729172..a8022dc6b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,11 +20,11 @@ default_envs = ESP32 Nucleo-F411RE Nucleo-F446RE - Teensy3.2 - Teensy3.5 - Teensy3.6 - Teensy4.0 - Teensy4.1 + Teensy3_2 + Teensy3_5 + Teensy3_6 + Teensy4_0 + Teensy4_1 src_dir = . include_dir = . @@ -192,7 +192,7 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO monitor_speed = 115200 monitor_echo = yes -[env:Teensy3.2] +[env:Teensy3_2] platform = teensy board = teensy31 framework = arduino @@ -200,7 +200,7 @@ build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet -[env:Teensy3.5] +[env:Teensy3_5] platform = teensy board = teensy35 framework = arduino @@ -208,7 +208,7 @@ build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet -[env:Teensy3.6] +[env:Teensy3_6] platform = teensy board = teensy36 framework = arduino @@ -216,7 +216,7 @@ build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet -[env:Teensy4.0] +[env:Teensy4_0] platform = teensy board = teensy40 framework = arduino @@ -224,7 +224,7 @@ build_flags = -std=c++17 -Os -g2 lib_deps = ${env.lib_deps} lib_ignore = NativeEthernet -[env:Teensy4.1] +[env:Teensy4_1] platform = teensy board = teensy41 framework = arduino From ef3d36ae25027bc0d5e93a2573fc22405dce4b05 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:48:11 +1000 Subject: [PATCH 720/870] Figuring out commands --- installer.ps1 | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 installer.ps1 diff --git a/installer.ps1 b/installer.ps1 new file mode 100644 index 000000000..e740d80ab --- /dev/null +++ b/installer.ps1 @@ -0,0 +1,53 @@ +# +# © 2023 Peter Cole +# +# This file is part of EX-CommandStation +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# It is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with CommandStation. If not, see . +# + +# 32/64 bit Win installer +# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip +# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip + +param( + [Parameter()] + [String]$BUILD_DIR +) + +if (!$PSBoundParameters.ContainsKey('BUILD_DIR')) { +# Use the current date/time stamp to create a unique directory if one is not specified. + $BUILD_DATE = Get-Date -Format 'yyyyMMdd-HHmmss' + $BUILD_DIR = $env:TEMP + "\" + $BUILD_DATE +} + +if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { + $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" + $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" +} else { + $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" + $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" +} + +Write-Output "Downloading installer to $OUTFILE" + +$ProgressPreference = "SilentlyContinue" +Invoke-WebRequest -Uri $URL -OutFile $OUTFILE + +$CLI_INSTALL = $env:TEMP + "\" + "arduino-cli_installer" + +Expand-Archive -Path $OUTFILE -DestinationPath $CLI_INSTALL -Force +$ProgressPreference = "Continue" + +Write-Output "Installing using directory $BUILD_DIR" \ No newline at end of file From 3a3071f35bd4ddfdaea854b560cd34f326b03029 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 4 Apr 2023 19:30:59 +1000 Subject: [PATCH 721/870] Got tag list --- installer.ps1 | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index e740d80ab..defccd601 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -21,33 +21,39 @@ # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip +$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" + param( [Parameter()] - [String]$BUILD_DIR + [String]$buildDirectory ) -if (!$PSBoundParameters.ContainsKey('BUILD_DIR')) { +if (!$PSBoundParameters.ContainsKey('buildDirectory')) { # Use the current date/time stamp to create a unique directory if one is not specified. - $BUILD_DATE = Get-Date -Format 'yyyyMMdd-HHmmss' - $BUILD_DIR = $env:TEMP + "\" + $BUILD_DATE + $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' + $buildDirectory = $env:TEMP + "\" + $buildDate } if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { - $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" - $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" + $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" + $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" } else { - $URL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" - $OUTFILE = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" + $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" + $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" } -Write-Output "Downloading installer to $OUTFILE" +Write-Output "Downloading installer to $arduinoCLIZip" $ProgressPreference = "SilentlyContinue" -Invoke-WebRequest -Uri $URL -OutFile $OUTFILE +Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip -$CLI_INSTALL = $env:TEMP + "\" + "arduino-cli_installer" +$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" -Expand-Archive -Path $OUTFILE -DestinationPath $CLI_INSTALL -Force +Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force $ProgressPreference = "Continue" -Write-Output "Installing using directory $BUILD_DIR" \ No newline at end of file +Write-Output "Installing using directory $buildDirectory" + +foreach ($tag in Invoke-RestMethod -Uri $gitHubAPITags | Format-List -Property ref) { + $tag.getType() +} From 61db37c7ea2717b4c2ddfd952bdab87cf4e52a31 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Wed, 5 Apr 2023 05:30:00 +1000 Subject: [PATCH 722/870] Got tag version and URL --- installer.ps1 | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index defccd601..3b9553547 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -20,10 +20,11 @@ # 32/64 bit Win installer # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip # https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip +# +# For script errors set ExecutionPolicy: +# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass -$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" - -param( +Param( [Parameter()] [String]$buildDirectory ) @@ -34,6 +35,8 @@ if (!$PSBoundParameters.ContainsKey('buildDirectory')) { $buildDirectory = $env:TEMP + "\" + $buildDate } +$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" + if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" @@ -54,6 +57,12 @@ $ProgressPreference = "Continue" Write-Output "Installing using directory $buildDirectory" -foreach ($tag in Invoke-RestMethod -Uri $gitHubAPITags | Format-List -Property ref) { - $tag.getType() +$tagList = Invoke-RestMethod -Uri $gitHubAPITags + +# Example zip: https://github.com/DCC-EX/CommandStation-EX/archive/refs/tags/v4.2.36-Devel.zip + +foreach ($tag in $tagList) { + $version = $tag.ref.split("/")[2] + $versionURL = "https://github.com/DCC-EX/CommandStation-EX/archive/" + $tag.ref + Write-Output "$version : $versionURL" } From d2c7e7fb8d9a536a00536e5c7aaa0f3130b8b9f8 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:54:48 +1000 Subject: [PATCH 723/870] Working on logic --- installer.ps1 | 115 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 25 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 3b9553547..d23218775 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -1,4 +1,4 @@ -# +<# # © 2023 Peter Cole # # This file is part of EX-CommandStation @@ -15,28 +15,31 @@ # # You should have received a copy of the GNU General Public License # along with CommandStation. If not, see . -# +#> -# 32/64 bit Win installer -# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip -# https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip -# -# For script errors set ExecutionPolicy: -# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass +<############################################ +For script errors set ExecutionPolicy: +Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass +############################################> +<############################################ +Optional command line parameters: + $buildDirectory - specify an existing directory rather than generating a new unique one + $version - specify an exact version to download +############################################> Param( [Parameter()] - [String]$buildDirectory + [String]$buildDirectory, + [Parameter()] + [String]$version ) -if (!$PSBoundParameters.ContainsKey('buildDirectory')) { -# Use the current date/time stamp to create a unique directory if one is not specified. - $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' - $buildDirectory = $env:TEMP + "\" + $buildDate -} - +<############################################ +Define global parameters here such as known URLs etc. +############################################> +$installerVersion = "v0.0.1" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" - +$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" @@ -44,25 +47,87 @@ if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchi $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" } +$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" +$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe" + +<############################################ +Set default action for progress indicators, warnings, and errors +############################################> +$global:ProgressPreference = "SilentlyContinue" +$global:WarningPreference = "SilentlyContinue" +$global:ErrorActionPreference = "SilentlyContinue" + +<############################################ +If $buildDirectory not provided, generate a new time/date stamp based directory to use +############################################> +if (!$PSBoundParameters.ContainsKey('buildDirectory')) { + $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' + $buildDirectory = $env:TEMP + "\" + $buildDate +} +$commandStationDirectory = $buildDirectory + "\CommandStation-EX" -Write-Output "Downloading installer to $arduinoCLIZip" +<############################################ +Write out intro message and prompt to continue +############################################> +@" +Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion) -$ProgressPreference = "SilentlyContinue" -Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip +Current installer options: +- EX-CommandStation will be built in $commandStationDirectory +- Arduino CLI will be in $arduinoCLIDirectory +"@ -$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" -Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force -$ProgressPreference = "Continue" +<############################################ +Create build directory if it doesn't exist, or fail +############################################> +if (!(Test-Path -PathType Container -Path $buildDirectory)) { + try { + New-Item -ItemType Directory -Path $buildDirectory | Out-Null + } + catch { + Write-Output "Could not create build directory $buildDirectory" + Exit + } +} +<############################################ +See if we have the Arduino CLI already, otherwise download and extract it +############################################> +if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { + if (!(Test-Path -PathType Container -Path $arduinoCLIDirectory)) { + try { + New-Item -ItemType Directory -Path $arduinoCLIDirectory | Out-Null + } + catch { + Write-Output "Arduino CLI does not exist and cannot create directory $arduinoCLIDirectory" + Exit + } + } + Write-Output "Downloading and extracting Arduino CLI" + try { + Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip + } + catch { + Write-Output "Failed to download Arduino CLI" + Exit + } + try { + Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force + } + catch { + Write-Output "Failed to extract Arduino CLI" + } +} + +<# Write-Output "Installing using directory $buildDirectory" $tagList = Invoke-RestMethod -Uri $gitHubAPITags -# Example zip: https://github.com/DCC-EX/CommandStation-EX/archive/refs/tags/v4.2.36-Devel.zip - foreach ($tag in $tagList) { $version = $tag.ref.split("/")[2] - $versionURL = "https://github.com/DCC-EX/CommandStation-EX/archive/" + $tag.ref + $versionURL = $gitHubURLPrefix + $tag.ref Write-Output "$version : $versionURL" } +#> \ No newline at end of file From 72ceb6391335898ed1d7dbd81a55772be592622f Mon Sep 17 00:00:00 2001 From: peteGSX Date: Thu, 6 Apr 2023 05:31:11 +1000 Subject: [PATCH 724/870] Start getting tag list --- installer.ps1 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/installer.ps1 b/installer.ps1 index d23218775..296fb6d8d 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -120,6 +120,31 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { } } +<# +Get the list of tags +#> +try { + $tagList = Invoke-RestMethod -Uri $gitHubAPITags +} +catch { + Write-Output "Failed to obtain list of available EX-CommandStation versions" + Exit +} + +<# +Get latest two Prod and Devel releases, add to hash table for selection by user +#> +foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { + if ($tag.ref -Like "*Prod") { + Write-Output $tag.ref + } +} +foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { + if ($tag.ref -Like "*Devel") { + Write-Output $tag.ref + } +} + <# Write-Output "Installing using directory $buildDirectory" From 91bc9df44eb6d9630af0013e5e4f55ba9943d517 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 6 Apr 2023 15:06:22 +1000 Subject: [PATCH 725/870] Working on CLI commands --- installer.ps1 | 129 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 14 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 296fb6d8d..b803e50a3 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -31,7 +31,7 @@ Param( [Parameter()] [String]$buildDirectory, [Parameter()] - [String]$version + [String]$configDirectory ) <############################################ @@ -120,31 +120,132 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { } } -<# +<############################################ Get the list of tags -#> +############################################> try { - $tagList = Invoke-RestMethod -Uri $gitHubAPITags + $gitHubTags = Invoke-RestMethod -Uri $gitHubAPITags } catch { Write-Output "Failed to obtain list of available EX-CommandStation versions" Exit } -<# -Get latest two Prod and Devel releases, add to hash table for selection by user -#> -foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { - if ($tag.ref -Like "*Prod") { - Write-Output $tag.ref - } +<############################################ +Get our GitHub tag list in a hash so we can sort by version numbers and extract just the ones we want +############################################> +$versionMatch = ".*?v(\d+)\.(\d+).(\d+)-(.*)" +$tagList = @{} +foreach ($tag in $gitHubTags) { + $tagHash = @{} + $tagHash["Ref"] = $tag.ref + $version = $tag.ref.split("/")[2] + $null = $version -match $versionMatch + $tagHash["Major"] = [int]$Matches[1] + $tagHash["Minor"] = [int]$Matches[2] + $tagHash["Patch"] = [int]$Matches[3] + $tagHash["Type"] = $Matches[4] + $tagList.Add($version, $tagHash) } -foreach ($tag in $tagList | Sort-Object -Property ref -Descending) { - if ($tag.ref -Like "*Devel") { - Write-Output $tag.ref + +<############################################ +Get latest two Prod and Devel for user to select +############################################> +$userList = @{} +$prodCount = 1 +$devCount = 1 +$select = 1 +foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$_]["Minor"]},{$tagList[$_]["Patch"]} -Descending) { + if (($tagList[$tag]["Type"] -eq "Prod") -and $prodCount -le 2) { + $userList[$select] = $tag + $select++ + $prodCount++ + } elseif (($tagList[$tag]["Type"] -eq "Devel") -and $devCount -le 2) { + $userList[$select] = $tag + $select++ + $devCount++ } } +<############################################ +Display options for user to select and get the selection +############################################> +foreach ($selection in $userList.Keys | Sort-Object $selection) { + Write-Output "$selection - $($userList[$selection])" +} +Write-Output "5 - Exit" +$userSelection = 0 +do { + [int]$userSelection = Read-Host "Select the version to install from the list above (1 - 5)" +} until ( + (($userSelection -ge 1) -and ($userSelection -le 5)) +) +if ($userSelection -eq 5) { + Write-Output "Exiting installer" + Exit +} else { + $downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip" +} + +<############################################ +Download the chosen version to the build directory +############################################> +$downladFile = $buildDirectory + "\CommandStation-EX.zip" +Write-Output "Downloading and extracting $($userList[$userSelection])" +try { + Invoke-WebRequest -Uri $downloadURL -OutFile $downladFile +} +catch { + Write-Output "Error downloading EX-CommandStation zip file" + Exit +} + +<############################################ +Extract and rename to CommandStation-EX to allow building +############################################> +try { + Expand-Archive -Path $downladFile -DestinationPath $buildDirectory -Force +} +catch { + Write-Output "Failed to extract EX-CommandStation zip file" + Exit +} + +$folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "") +Write-Output $folderName +try { + Rename-Item -Path $folderName -NewName $commandStationDirectory +} +catch { + Write-Output "Could not rename folder" + Exit +} + +<############################################ +If config directory provided, copy files here +############################################> + + +<############################################ +Once files all together, identify available board(s) +############################################> +# Need to do an initial board list to download everything first +& $arduinoCLI board list | Out-Null +# Run again to generate the list of discovered boards +& $arduinoCLI board list --format json + +<############################################ +Get user to select board +############################################> + + + + +<############################################ +Upload the sketch to the selected board +############################################> +#$arduinoCLI upload -b fqbn -p port $commandStationDirectory + <# Write-Output "Installing using directory $buildDirectory" From 751b46b6bb4ceef91d31d018ad4b6c378e7fe8d0 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Fri, 7 Apr 2023 08:06:32 +1000 Subject: [PATCH 726/870] Initial test working! --- installer.ps1 | 142 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 16 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index b803e50a3..1c47fc350 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -34,6 +34,24 @@ Param( [String]$configDirectory ) +<############################################ +List of supported devices with FQBN in case clones used that aren't detected +############################################> +$supportedDevices = @( + @{ + name = "Arduino Mega or Mega 2560" + fqbn = "arduino:avr:mega" + }, + @{ + name = "Arduino Nano" + fqbn = "arduino:avr:nano" + }, + @{ + name = "Arduino Uno" + fqbn = "arduino:avr:uno" + } +) + <############################################ Define global parameters here such as known URLs etc. ############################################> @@ -75,6 +93,9 @@ Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVers Current installer options: - EX-CommandStation will be built in $commandStationDirectory - Arduino CLI will be in $arduinoCLIDirectory + +Available EX-CommandStation versions: +------------------------------------- "@ @@ -176,7 +197,7 @@ foreach ($selection in $userList.Keys | Sort-Object $selection) { Write-Output "5 - Exit" $userSelection = 0 do { - [int]$userSelection = Read-Host "Select the version to install from the list above (1 - 5)" + [int]$userSelection = Read-Host "`r`nSelect the version to install from the list above (1 - 5)" } until ( (($userSelection -ge 1) -and ($userSelection -le 5)) ) @@ -200,6 +221,14 @@ catch { Exit } +<############################################ +If folder exists, bail out and tell user +############################################> +if (Test-Path -PathType Container -Path "$buildDirectory\CommandStation-EX") { + Write-Output "EX-CommandStation directory already exists, please ensure you have copied any user files then delete manually: $buildDirectory\CommandStation-EX" + Exit +} + <############################################ Extract and rename to CommandStation-EX to allow building ############################################> @@ -224,36 +253,117 @@ catch { <############################################ If config directory provided, copy files here ############################################> - +# To be done +# If exists copy config.h, myAutomation.h, myHal.cpp, mySetup.h <############################################ Once files all together, identify available board(s) ############################################> # Need to do an initial board list to download everything first -& $arduinoCLI board list | Out-Null -# Run again to generate the list of discovered boards -& $arduinoCLI board list --format json +try { + & $arduinoCLI board list | Out-Null +} +catch { + Write-Output "Failed to update Arduino CLI board list" + Exit +} +# Run again to generate the list of discovered boards into a custom object +try { + $boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to obtain list of boards" + Exit +} <############################################ Get user to select board ############################################> +if ($boardList.count -eq 0) { + Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it" + Exit +} else { +@" +Devices attached to COM ports: +------------------------------ +"@ + $boardSelect = 1 + foreach ($board in $boardList) { + if ($board.matching_boards.name) { + $boardName = $board.matching_boards.name + } else { + $boardName = "Unknown device" + } + $port = $board.port.address + Write-Output "$boardSelect - $boardName on port $port" + $boardSelect++ + } + Write-Output "$boardSelect - Exit" + $userSelection = 0 + do { + [int]$userSelection = Read-Host "`r`nSelect the device to use from the list above" + } until ( + (($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1))) + ) + if ($userSelection -eq ($boardList.count + 1)) { + Write-Output "Exiting installer" + Exit + } else { + $selectedBoard = $userSelection - 1 + } +} +<############################################ +If the board is unknown, need to choose which one +############################################> +if ($null -eq $boardList[$selectedBoard].matching_boards.name) { + Write-Output "The device selected is unknown, these boards are supported:`r`n" + $deviceSelect = 1 + foreach ($device in $supportedDevices) { + Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)" + $deviceSelect++ + } + Write-Output "$deviceSelect - Exit" + $userSelection = 0 + do { + [int]$userSelection = Read-Host "Select the board type from the list above" + } until ( + (($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1))) + ) + if ($userSelection -eq ($supportedDevices.count + 1)) { + Write-Output "Exiting installer" + Exit + } else { + $deviceName = $supportedDevices[$userSelection - 1].name + $deviceFQBN = $supportedDevices[$userSelection - 1].fqbn + } +} else { + $deviceName = $boardList[$selectedBoard].matching_boards.name + $deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn + $devicePort = $boardList[$selectedBoard].port.address +} <############################################ Upload the sketch to the selected board ############################################> #$arduinoCLI upload -b fqbn -p port $commandStationDirectory - -<# -Write-Output "Installing using directory $buildDirectory" - -$tagList = Invoke-RestMethod -Uri $gitHubAPITags - -foreach ($tag in $tagList) { - $version = $tag.ref.split("/")[2] - $versionURL = $gitHubURLPrefix + $tag.ref - Write-Output "$version : $versionURL" +Write-Output "Compiling for $deviceName" +try { + $output = & $arduinoCLI compile -b $deviceFQBN $commandStationDirectory --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to compile" + Exit +} +Write-Output "$output" +Write-Output "Now uploading to $deviceName on port $devicePort" +try { + $output = & $arduinoCLI upload -t -b $deviceFQBN -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to upload" + Exit } -#> \ No newline at end of file +Write-Output "$output" From 2e518fcac229a18efe6f4a0c6f9610198d62b03b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Fri, 7 Apr 2023 19:30:15 +1000 Subject: [PATCH 727/870] Enable using existing configs --- installer.ps1 | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 1c47fc350..0b4e94bc4 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -55,7 +55,7 @@ $supportedDevices = @( <############################################ Define global parameters here such as known URLs etc. ############################################> -$installerVersion = "v0.0.1" +$installerVersion = "v0.0.2" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" $gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { @@ -253,8 +253,18 @@ catch { <############################################ If config directory provided, copy files here ############################################> -# To be done -# If exists copy config.h, myAutomation.h, myHal.cpp, mySetup.h +$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h") +if ($PSBoundParameters.ContainsKey('configDirectory')) { + if (Test-Path -PathType Container -Path $configDirectory) { + foreach ($file in $configFiles) { + if (Test-Path -PathType Leaf -Path "$configDirectory\$file") { + Copy-Item -Path "$configDirectory\$file" -Destination "$commandStationDirectory\$file" + } + } + } else { + Write-Output "Provided configuration directory $configDirectory does not exist, skipping" + } +} <############################################ Once files all together, identify available board(s) @@ -338,6 +348,7 @@ if ($null -eq $boardList[$selectedBoard].matching_boards.name) { } else { $deviceName = $supportedDevices[$userSelection - 1].name $deviceFQBN = $supportedDevices[$userSelection - 1].fqbn + $devicePort = $boardList[$selectedBoard].port.address } } else { $deviceName = $boardList[$selectedBoard].matching_boards.name @@ -349,21 +360,13 @@ if ($null -eq $boardList[$selectedBoard].matching_boards.name) { Upload the sketch to the selected board ############################################> #$arduinoCLI upload -b fqbn -p port $commandStationDirectory -Write-Output "Compiling for $deviceName" +Write-Output "Compiling and uploading to $deviceName on $devicePort" +Write-Output "& $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory" try { - $output = & $arduinoCLI compile -b $deviceFQBN $commandStationDirectory --format jsonmini | ConvertFrom-Json + $output = & $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json } catch { Write-Output "Failed to compile" Exit } Write-Output "$output" -Write-Output "Now uploading to $deviceName on port $devicePort" -try { - $output = & $arduinoCLI upload -t -b $deviceFQBN -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json -} -catch { - Write-Output "Failed to upload" - Exit -} -Write-Output "$output" From 5d0de6b807ff21e27a8676729a0927c4df21a9f7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 7 Apr 2023 20:44:40 +0200 Subject: [PATCH 728/870] platformio wants this --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a8022dc6b..d85c76cb0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -53,7 +53,7 @@ monitor_speed = 115200 monitor_echo = yes build_flags = -std=c++17 -[env:Arduino M0] +[env:Arduino-M0] platform = atmelsam board = mzeroUSB framework = arduino From ff6034dff2ebcaca75376547f4d59c4302b89195 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 7 Apr 2023 20:45:21 +0200 Subject: [PATCH 729/870] curl only needed when downloading --- installer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.sh b/installer.sh index 98de7716d..17a9c042c 100755 --- a/installer.sh +++ b/installer.sh @@ -69,10 +69,10 @@ else # need to do this config better cp -p config.example.h config.h fi -need curl if test -x "$ACLI" ; then : all well else + need curl curl "$ACLIINSTALL" > acliinstall.sh chmod +x acliinstall.sh ./acliinstall.sh From 0d823703809120711c42b535968f6051993d06c4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 7 Apr 2023 20:46:20 +0200 Subject: [PATCH 730/870] devel date --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4f86707ad..fbd3f68cf 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202303252126Z" +#define GITHUB_SHA "devel-202304071845Z" From a100d709ce5ef60e5c87f4ba599e5b222f2fc23b Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 8 Apr 2023 06:22:09 +1000 Subject: [PATCH 731/870] Install core library, add batch wrapper --- install_via_powershell.cmd | 15 +++++++++++++++ installer.ps1 | 29 +++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 install_via_powershell.cmd diff --git a/install_via_powershell.cmd b/install_via_powershell.cmd new file mode 100644 index 000000000..f367b2124 --- /dev/null +++ b/install_via_powershell.cmd @@ -0,0 +1,15 @@ +@ECHO OFF + +FOR /f "tokens=*" %%a IN ('powershell Get-ExecutionPolicy -Scope CurrentUser') DO SET PS_POLICY=%%a + +IF NOT %PS_POLICY=="Bypass" ( + powershell Set-ExecutionPolicy -Scope CurrentUser Bypass +) + +powershell %~dp0%installer.ps1 + +IF NOT %PS_POLICY=="Bypass" ( + powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY% +) + +PAUSE \ No newline at end of file diff --git a/installer.ps1 b/installer.ps1 index 0b4e94bc4..65b0a61ef 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -55,7 +55,7 @@ $supportedDevices = @( <############################################ Define global parameters here such as known URLs etc. ############################################> -$installerVersion = "v0.0.2" +$installerVersion = "v0.0.3" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" $gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { @@ -241,7 +241,6 @@ catch { } $folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "") -Write-Output $folderName try { Rename-Item -Path $folderName -NewName $commandStationDirectory } @@ -356,12 +355,24 @@ if ($null -eq $boardList[$selectedBoard].matching_boards.name) { $devicePort = $boardList[$selectedBoard].port.address } +<############################################ +Install core libraries for the platform +############################################> +$platformArray = $deviceFQBN.split(":") +$platform = $platformArray[0] + ":" + $platformArray[1] +try { + & $arduinoCLI core install $platform +} +catch { + Write-Output "Error install core libraries" + Exit +} + <############################################ Upload the sketch to the selected board ############################################> #$arduinoCLI upload -b fqbn -p port $commandStationDirectory Write-Output "Compiling and uploading to $deviceName on $devicePort" -Write-Output "& $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory" try { $output = & $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json } @@ -369,4 +380,14 @@ catch { Write-Output "Failed to compile" Exit } -Write-Output "$output" +if ($output.success -eq "True") { + Write-Output "`r`nCongratulations! DCC-EX EX-CommandStation $($userList[$userSelection]) has been installed on your $deviceName`r`n" +} else { + Write-Output "`r`nThere was an error installing $($userList[$userSelection]) on your $($deviceName), please take note of the errors provided:`r`n" + if ($null -ne $output.compiler_err) { + Write-Output "Compiler error: $($output.compiler_err)`r`n" + } + if ($null -ne $output.builder_result) { + Write-Output "Builder result: $($output.builder_result)`r`n" + } +} From 4d236446b039047ba706532f7d34fb3fe973d375 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 8 Apr 2023 06:57:09 +1000 Subject: [PATCH 732/870] Add core index update --- installer.ps1 | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 65b0a61ef..a92edaedc 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -34,6 +34,22 @@ Param( [String]$configDirectory ) +<############################################ +Define global parameters here such as known URLs etc. +############################################> +$installerVersion = "v0.0.4" +$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" +$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" +if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { + $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" + $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" +} else { + $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" + $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" +} +$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" +$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe" + <############################################ List of supported devices with FQBN in case clones used that aren't detected ############################################> @@ -52,22 +68,6 @@ $supportedDevices = @( } ) -<############################################ -Define global parameters here such as known URLs etc. -############################################> -$installerVersion = "v0.0.3" -$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" -$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" -if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { - $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" - $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" -} else { - $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" - $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" -} -$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" -$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe" - <############################################ Set default action for progress indicators, warnings, and errors ############################################> @@ -266,9 +266,17 @@ if ($PSBoundParameters.ContainsKey('configDirectory')) { } <############################################ -Once files all together, identify available board(s) +Make sure Arduino CLI core index updated and list of boards populated ############################################> # Need to do an initial board list to download everything first +try { + & $arduinoCLI core update-index | Out-Null +} +catch { + Write-Output "Failed to update Arduino CLI core index" + Exit +} +# Need to do an initial board list to download everything first try { & $arduinoCLI board list | Out-Null } @@ -276,7 +284,10 @@ catch { Write-Output "Failed to update Arduino CLI board list" Exit } -# Run again to generate the list of discovered boards into a custom object + +<############################################ +Once files all together, identify available board(s) +############################################> try { $boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json } From fe035f4096d2723e0f0336fd45a4a5ccbf0eb928 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 8 Apr 2023 08:38:02 +1000 Subject: [PATCH 733/870] Move from temp to user home dir --- installer.ps1 | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index a92edaedc..ac2ec9eb2 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -37,17 +37,18 @@ Param( <############################################ Define global parameters here such as known URLs etc. ############################################> -$installerVersion = "v0.0.4" +$installerVersion = "v0.0.5" +$userDirectory = $env:USERPROFILE + "\" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" $gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") { $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip" - $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_64bit.zip" + $arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_64bit.zip" } else { $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" - $arduinoCLIZip = $env:TEMP + "\" + "arduino-cli_latest_Windows_32bit.zip" + $arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_32bit.zip" } -$arduinoCLIDirectory = $env:TEMP + "\" + "arduino-cli_installer" +$arduinoCLIDirectory = $userDirectory + "arduino-cli_installer" $arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe" <############################################ @@ -80,7 +81,7 @@ If $buildDirectory not provided, generate a new time/date stamp based directory ############################################> if (!$PSBoundParameters.ContainsKey('buildDirectory')) { $buildDate = Get-Date -Format 'yyyyMMdd-HHmmss' - $buildDirectory = $env:TEMP + "\" + $buildDate + $buildDirectory = $userDirectory + "EX-CommandStation-Installer\" + $buildDate } $commandStationDirectory = $buildDirectory + "\CommandStation-EX" @@ -94,8 +95,6 @@ Current installer options: - EX-CommandStation will be built in $commandStationDirectory - Arduino CLI will be in $arduinoCLIDirectory -Available EX-CommandStation versions: -------------------------------------- "@ @@ -191,6 +190,11 @@ foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$ <############################################ Display options for user to select and get the selection ############################################> +@" + +Available EX-CommandStation versions: +------------------------------------- +"@ foreach ($selection in $userList.Keys | Sort-Object $selection) { Write-Output "$selection - $($userList[$selection])" } From e7d9626a729e52fb05c1cbc2f86b75cdde63f48e Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 8 Apr 2023 08:39:42 +1000 Subject: [PATCH 734/870] Fix CLI directory name --- installer.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index ac2ec9eb2..cb3bf73f9 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -37,7 +37,7 @@ Param( <############################################ Define global parameters here such as known URLs etc. ############################################> -$installerVersion = "v0.0.5" +$installerVersion = "v0.0.6" $userDirectory = $env:USERPROFILE + "\" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" $gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" @@ -48,7 +48,7 @@ if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchi $arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip" $arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_32bit.zip" } -$arduinoCLIDirectory = $userDirectory + "arduino-cli_installer" +$arduinoCLIDirectory = $userDirectory + "arduino-cli" $arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe" <############################################ From 758991763882513111443766a22ce18ebc0b5aca Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 8 Apr 2023 14:15:38 +0800 Subject: [PATCH 735/870] STM32 ADCee fix --- DCCTimerSTM32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 83ac99b05..de2c2357c 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -237,7 +237,7 @@ int16_t ADCee::ADCmax() { } int ADCee::init(uint8_t pin) { - uint id = pin - A0; + uint8_t id = pin - A0; int value = 0; PinName stmpin = digitalPin[analogInputPin[id]]; uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32 From 3868bb19ac17da648eacdc0a0d6809b3375aa023 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 8 Apr 2023 14:51:10 +0800 Subject: [PATCH 736/870] More uint type fixes --- DCCTimerSAMD.cpp | 4 ++-- DCCTimerSTM32.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 463cc647e..f878ae551 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -162,7 +162,7 @@ uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; int ADCee::init(uint8_t pin) { - uint id = pin - A0; + uint8_t id = pin - A0; int value = 0; if (id > NUM_ADC_INPUTS) @@ -210,7 +210,7 @@ int ADCee::read(uint8_t pin, bool fromISR) { #pragma GCC push_options #pragma GCC optimize ("-O3") void ADCee::scan() { - static uint id = 0; // id and mask are the same thing but it is faster to + static uint8_t id = 0; // id and mask are the same thing but it is faster to static uint16_t mask = 1; // increment and shift instead to calculate mask from id static bool waiting = false; diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index de2c2357c..8038d71f5 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -306,7 +306,7 @@ int ADCee::read(uint8_t pin, bool fromISR) { #pragma GCC push_options #pragma GCC optimize ("-O3") void ADCee::scan() { - static uint id = 0; // id and mask are the same thing but it is faster to + static uint8_t id = 0; // id and mask are the same thing but it is faster to static uint16_t mask = 1; // increment and shift instead to calculate mask from id static bool waiting = false; From c11d8f6359390ae531ee7bd198b2ac2e0306f708 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Sat, 8 Apr 2023 17:22:39 +0100 Subject: [PATCH 737/870] Add TURNOUTL Macro to EXRAIL Add TURNOUTL Macro to EXRAIL and retrospective update to version.h to record addition of EX-FastClock mods. --- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 1 + version.h | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index b4ffc6d1b..181fb5d33 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -134,6 +134,7 @@ #undef STOP #undef THROW #undef TURNOUT +#undef TURNOUTL #undef UNJOIN #undef UNLATCH #undef VIRTUAL_SIGNAL @@ -254,6 +255,7 @@ #define STOP #define THROW(id) #define TURNOUT(id,addr,subaddr,description...) +#define TURNOUTL(id,addr,description...) #define UNJOIN #define UNLATCH(sensor_id) #define VIRTUAL_SIGNAL(id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index ee20c1f93..3aa46318e 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -361,6 +361,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define STOP OPCODE_SPEED,V(0), #define THROW(id) OPCODE_THROW,V(id), #define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr), +#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description) #define UNJOIN OPCODE_UNJOIN,0,0, #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), #define VIRTUAL_SIGNAL(id) diff --git a/version.h b/version.h index a8c8bde7b..768e8b24f 100644 --- a/version.h +++ b/version.h @@ -5,6 +5,7 @@ #define VERSION "4.2.41" +// 4.2.42 - Added EXRAIL TURNOUTL Macro definition // 4.2.41 - Move HAl startup to ASAP in setup() // - Fix DNOU8 output pin setup to all LOW // 4.2.40 - Automatically detect conflicting default I2C devices and disable @@ -72,6 +73,10 @@ // 4.2.11 Exrail IFLOCO feature added // 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted // IO_EXIOExpander.h input speed optimisation +// ONCLOCK and ONCLOCKTIME command added to EXRAIL for EX-FastCLock +// Serial command added for EX-FastClock +// Broadcast added for EX-FastClock +// IO_EXFastClock.h added for I2C FastClock connection // 4.2.9 duinoNodes support // 4.2.8 HIGHMEM (EXRAIL support beyond 64kb) // Withrottle connect/disconnect improvements From 9b7d1ae85889fd7781d0108d67ac40561801449c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 8 Apr 2023 23:33:33 +0200 Subject: [PATCH 738/870] STM32: Use predefined function for pinnames --- DCCTimerSTM32.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 83ac99b05..d1f202716 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -237,9 +237,8 @@ int16_t ADCee::ADCmax() { } int ADCee::init(uint8_t pin) { - uint id = pin - A0; int value = 0; - PinName stmpin = digitalPin[analogInputPin[id]]; + PinName stmpin = analogInputToPinName(pin); uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32 uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!) GPIO_TypeDef * gpioBase; @@ -280,6 +279,8 @@ int ADCee::init(uint8_t pin) { analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t)); } + + uint8_t id = pin - PNUM_ANALOG_BASE; analogvals[id] = value; // Store sampled value analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready @@ -291,7 +292,7 @@ int ADCee::init(uint8_t pin) { * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ int ADCee::read(uint8_t pin, bool fromISR) { - uint8_t id = pin - A0; + uint8_t id = pin - PNUM_ANALOG_BASE; // Was this pin initialised yet? if ((usedpins & (1<CR2 |= (1 << 0); // Switch on ADC1 interrupts(); } -#endif \ No newline at end of file +#endif From bfa33a9df767aab290ee1c27bdf67efaacc4bcfe Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 10 Apr 2023 01:47:00 +0200 Subject: [PATCH 739/870] Fix STM32 set right port mode bits for analog port --- DCCTimerSTM32.cpp | 17 ++++++++++++++--- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 5 ++++- version.h | 3 ++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 5dd72db9d..5660be1c3 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -240,7 +240,10 @@ int ADCee::init(uint8_t pin) { int value = 0; PinName stmpin = analogInputToPinName(pin); - uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32 + if (stmpin == NC) // do not continue if this is not an analog pin at all + return -1024; // some silly value as error + + uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32) uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!) GPIO_TypeDef * gpioBase; @@ -258,12 +261,20 @@ int ADCee::init(uint8_t pin) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC gpioBase = GPIOC; break; + default: + return -1023; // some silly value as error } - // Set pin mux mode to analog input - gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode + // Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin + gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11) // Set the sampling rate for that analog input + // This is F411x specific! Different on for example F334 + // STM32F11xC/E Reference manual + // 11.12.4 ADC sample time register 1 (ADC_SMPR1) (channels 10 to 18) + // 11.12.5 ADC sample time register 2 (ADC_SMPR2) (channels 0 to 9) + if (adcchan > 18) + return -1022; // silly value as error if (adcchan < 10) ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles else diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index fbd3f68cf..fbecd70e6 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304071845Z" +#define GITHUB_SHA "devel-202304092344Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a62dc9ca3..28ecba678 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -210,7 +210,10 @@ int MotorDriver::getCurrentRaw(bool fromISR) { (void)fromISR; if (currentPin==UNUSED_PIN) return 0; int current; - current = ADCee::read(currentPin, fromISR)-senseOffset; + current = ADCee::read(currentPin, fromISR); + // here one can diag raw value + // if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current); + current = current-senseOffset; // adjust with offset if (current<0) current=0-current; if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON) return (current == 0 ? -1 : -current); diff --git a/version.h b/version.h index 768e8b24f..442c35171 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.41" +#define VERSION "4.2.43" +// 4.2.43 - Fix STM32 set right port mode bits for analog // 4.2.42 - Added EXRAIL TURNOUTL Macro definition // 4.2.41 - Move HAl startup to ASAP in setup() // - Fix DNOU8 output pin setup to all LOW From 1d29be9de6d604c625d17843a0cc5d83f4996f43 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Mon, 10 Apr 2023 19:52:31 +1000 Subject: [PATCH 740/870] Working on 0.0.7 --- installer.ps1 | 317 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 213 insertions(+), 104 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index cb3bf73f9..56eeec006 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -25,7 +25,7 @@ Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass <############################################ Optional command line parameters: $buildDirectory - specify an existing directory rather than generating a new unique one - $version - specify an exact version to download + $configDirectory - specify a directory containing existing files as per $configFiles ############################################> Param( [Parameter()] @@ -37,7 +37,9 @@ Param( <############################################ Define global parameters here such as known URLs etc. ############################################> -$installerVersion = "v0.0.6" +$installerVersion = "v0.0.7" +$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h") +$wifiBoards = @("arduino:avr:mega", "esp32:esp32:esp32") $userDirectory = $env:USERPROFILE + "\" $gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags" $gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/" @@ -66,6 +68,47 @@ $supportedDevices = @( @{ name = "Arduino Uno" fqbn = "arduino:avr:uno" + }, + @{ + name = "ESP32 Dev Module" + fqbn = "esp32:esp32:esp32" + } +) + +<############################################ +List of supported displays and scroll modes +############################################> +$displayList = @( + @{ + option = "LCD 16 columns x 2 rows" + configLine = "#define LCD_DRIVER 0x27,16,2" + }, + @{ + option = "LCD 16 columns x 4 rows" + configLine = "#define LCD_DRIVER 0x27,16,4" + }, + @{ + option = "OLED 128 x 32" + configLine = "#define OLED_DRIVER 128,32,0x3c" + }, + @{ + option = "OLED 128 x 64" + configLine = "#define OLED_DRIVER 128,64,0x3c" + } +) + +$scrollList = @( + @{ + mode = "Continuous - fill screen if possible" + configLine = "#define SCROLLMODE 0" + }, + @{ + mode = "Page (default) - alternate between pages" + configLine = "#define SCROLLMODE 1" + }, + @{ + mode = "Row - move up one row at a time" + configLine = "#define SCROLLMODE 2" } ) @@ -92,23 +135,27 @@ Write out intro message and prompt to continue Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion) Current installer options: + - EX-CommandStation will be built in $commandStationDirectory -- Arduino CLI will be in $arduinoCLIDirectory +- Arduino CLI will downloaded and extracted to $arduinoCLIDirectory -"@ +Before continuing, please ensure: + +- Your computer is connected to the internet +- The device you wish to install EX-CommandStation on is connected to a USB port +This installer will obtain the Arduino CLI (if not already present), and then download and install your chosen version of EX-CommandStation + +"@ <############################################ -Create build directory if it doesn't exist, or fail +Prompt user to confirm all is ready to proceed ############################################> -if (!(Test-Path -PathType Container -Path $buildDirectory)) { - try { - New-Item -ItemType Directory -Path $buildDirectory | Out-Null - } - catch { - Write-Output "Could not create build directory $buildDirectory" - Exit - } +$confirmation = Read-Host "Enter 'Y' or 'y' then press to confirm you are ready to proceed, any other key to exit" +if ($confirmation -ne "Y" -and $confirmation -ne "y") { + Exit +} else { + Write-Output "Proceeding to obtain the Arduino CLI..." } <############################################ @@ -140,6 +187,107 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { } } +<############################################ +Make sure Arduino CLI core index updated and list of boards populated +############################################> +# Need to do an initial board list to download everything first +try { + & $arduinoCLI core update-index | Out-Null +} +catch { + Write-Output "Failed to update Arduino CLI core index" + Exit +} +# Need to do an initial board list to download everything first +try { + & $arduinoCLI board list | Out-Null +} +catch { + Write-Output "Failed to update Arduino CLI board list" + Exit +} + +<############################################ +Identify available board(s) +############################################> +try { + $boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json +} +catch { + Write-Output "Failed to obtain list of boards" + Exit +} + +<############################################ +Get user to select board +############################################> +if ($boardList.count -eq 0) { + Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it" + Exit +} else { +@" + +Devices attached to COM ports: +------------------------------ +"@ + + $boardSelect = 1 + foreach ($board in $boardList) { + if ($board.matching_boards.name) { + $boardName = $board.matching_boards.name + } else { + $boardName = "Unknown device" + } + $port = $board.port.address + Write-Output "$boardSelect - $boardName on port $port" + $boardSelect++ + } + Write-Output "$boardSelect - Exit" + $userSelection = 0 + do { + [int]$userSelection = Read-Host "`r`nSelect the device to use from the list above" + } until ( + (($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1))) + ) + if ($userSelection -eq ($boardList.count + 1)) { + Write-Output "Exiting installer" + Exit + } else { + $selectedBoard = $userSelection - 1 + } +} + +<############################################ +If the board is unknown, need to choose which one +############################################> +if ($null -eq $boardList[$selectedBoard].matching_boards.name) { + Write-Output "The device selected is unknown, these boards are supported:`r`n" + $deviceSelect = 1 + foreach ($device in $supportedDevices) { + Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)" + $deviceSelect++ + } + Write-Output "$deviceSelect - Exit" + $userSelection = 0 + do { + [int]$userSelection = Read-Host "Select the board type from the list above" + } until ( + (($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1))) + ) + if ($userSelection -eq ($supportedDevices.count + 1)) { + Write-Output "Exiting installer" + Exit + } else { + $deviceName = $supportedDevices[$userSelection - 1].name + $deviceFQBN = $supportedDevices[$userSelection - 1].fqbn + $devicePort = $boardList[$selectedBoard].port.address + } +} else { + $deviceName = $boardList[$selectedBoard].matching_boards.name + $deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn + $devicePort = $boardList[$selectedBoard].port.address +} + <############################################ Get the list of tags ############################################> @@ -212,6 +360,19 @@ if ($userSelection -eq 5) { $downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip" } +<############################################ +Create build directory if it doesn't exist, or fail +############################################> +if (!(Test-Path -PathType Container -Path $buildDirectory)) { + try { + New-Item -ItemType Directory -Path $buildDirectory | Out-Null + } + catch { + Write-Output "Could not create build directory $buildDirectory" + Exit + } +} + <############################################ Download the chosen version to the build directory ############################################> @@ -256,7 +417,6 @@ catch { <############################################ If config directory provided, copy files here ############################################> -$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h") if ($PSBoundParameters.ContainsKey('configDirectory')) { if (Test-Path -PathType Container -Path $configDirectory) { foreach ($file in $configFiles) { @@ -265,110 +425,59 @@ if ($PSBoundParameters.ContainsKey('configDirectory')) { } } } else { - Write-Output "Provided configuration directory $configDirectory does not exist, skipping" + Write-Output "User provided configuration directory $configDirectory does not exist, skipping" } -} - -<############################################ -Make sure Arduino CLI core index updated and list of boards populated -############################################> -# Need to do an initial board list to download everything first -try { - & $arduinoCLI core update-index | Out-Null -} -catch { - Write-Output "Failed to update Arduino CLI core index" - Exit -} -# Need to do an initial board list to download everything first -try { - & $arduinoCLI board list | Out-Null -} -catch { - Write-Output "Failed to update Arduino CLI board list" - Exit -} - -<############################################ -Once files all together, identify available board(s) -############################################> -try { - $boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json -} -catch { - Write-Output "Failed to obtain list of boards" - Exit -} +} else { <############################################ -Get user to select board +If no config directory provided, prompt for display option, and WiFi if using Mega ############################################> -if ($boardList.count -eq 0) { - Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it" - Exit -} else { -@" - -Devices attached to COM ports: ------------------------------- -"@ - - $boardSelect = 1 - foreach ($board in $boardList) { - if ($board.matching_boards.name) { - $boardName = $board.matching_boards.name - } else { - $boardName = "Unknown device" - } - $port = $board.port.address - Write-Output "$boardSelect - $boardName on port $port" - $boardSelect++ + Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n" + $displaySelect = 1 + foreach ($display in $displayList) { + Write-Output "$displaySelect - $($displayList[$displaySelect - 1].option)" + $displaySelect++ } - Write-Output "$boardSelect - Exit" - $userSelection = 0 + Write-Output "$($displayList.Count + 1) - I have no display" + Write-Output "$($displayList.Count + 2) - Exit" do { - [int]$userSelection = Read-Host "`r`nSelect the device to use from the list above" + [int]$displayChoice = Read-Host "`r`nSelect a display option" } until ( - (($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1))) + ($displayChoice -ge 1 -and $displayChoice -le ($displayList.Count + 2)) ) - if ($userSelection -eq ($boardList.count + 1)) { - Write-Output "Exiting installer" + if ($displayChoice -eq ($displayList.Count + 2)) { Exit - } else { - $selectedBoard = $userSelection - 1 + } elseif ($displayChoice -le ($displayList.Count + 1)) { + $displayLine = $displayList[$displayChoice - 1].configLine + $scrollSelect = 1 + $defaultScroll = 1 + Write-Host "`r`n Select a scroll option, or press to accept the default [$($defaultScroll)]" + foreach ($scroll in $scrollList) { + Write-Output "$scrollSelect - $($scrollList[$scrollSelect - 1].mode)" + $scrollSelect++ + } + Write-Output "$($scrollList.Count + 1) - Exit" + do { + [int]$displayScroll = Read-Host "`r`n Select a scroll option, or press to accept the default [$($defaultScroll)]" + } until ( + (($displayScroll -ge 1 -and $displayScroll -le ($scrollList.Count + 1)) -or $displayScroll -eq "") + ) + if ($displayScroll -eq ($scrollList.Count + 1)) { + Exit + } elseif ($displayScroll -eq "") { + $displayScroll = $defaultScroll + } + $scrollLine = $scrollList[$displayScroll - 1] + } + if ($wifiBoards.Contains($deviceFQBN)) { + # WiFi prompt } } <############################################ -If the board is unknown, need to choose which one +If display or WiFi options set, create config.h ############################################> -if ($null -eq $boardList[$selectedBoard].matching_boards.name) { - Write-Output "The device selected is unknown, these boards are supported:`r`n" - $deviceSelect = 1 - foreach ($device in $supportedDevices) { - Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)" - $deviceSelect++ - } - Write-Output "$deviceSelect - Exit" - $userSelection = 0 - do { - [int]$userSelection = Read-Host "Select the board type from the list above" - } until ( - (($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1))) - ) - if ($userSelection -eq ($supportedDevices.count + 1)) { - Write-Output "Exiting installer" - Exit - } else { - $deviceName = $supportedDevices[$userSelection - 1].name - $deviceFQBN = $supportedDevices[$userSelection - 1].fqbn - $devicePort = $boardList[$selectedBoard].port.address - } -} else { - $deviceName = $boardList[$selectedBoard].matching_boards.name - $deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn - $devicePort = $boardList[$selectedBoard].port.address -} + <############################################ Install core libraries for the platform From 1aae0aed0af18207204987bce46bf21cca09605e Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 11 Apr 2023 05:31:44 +1000 Subject: [PATCH 741/870] Working on config output --- installer.ps1 | 64 +++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index 56eeec006..e1cd44ac1 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -76,7 +76,7 @@ $supportedDevices = @( ) <############################################ -List of supported displays and scroll modes +List of supported displays ############################################> $displayList = @( @{ @@ -97,19 +97,13 @@ $displayList = @( } ) -$scrollList = @( - @{ - mode = "Continuous - fill screen if possible" - configLine = "#define SCROLLMODE 0" - }, - @{ - mode = "Page (default) - alternate between pages" - configLine = "#define SCROLLMODE 1" - }, - @{ - mode = "Row - move up one row at a time" - configLine = "#define SCROLLMODE 2" - } +<############################################ +Basics of config.h +############################################> +$configLines = @( + "/*", + "This config.h file was generated by the DCC-EX PowerShell installer $version", + "*/`r`n" ) <############################################ @@ -447,37 +441,31 @@ If no config directory provided, prompt for display option, and WiFi if using Me ) if ($displayChoice -eq ($displayList.Count + 2)) { Exit - } elseif ($displayChoice -le ($displayList.Count + 1)) { - $displayLine = $displayList[$displayChoice - 1].configLine - $scrollSelect = 1 - $defaultScroll = 1 - Write-Host "`r`n Select a scroll option, or press to accept the default [$($defaultScroll)]" - foreach ($scroll in $scrollList) { - Write-Output "$scrollSelect - $($scrollList[$scrollSelect - 1].mode)" - $scrollSelect++ - } - Write-Output "$($scrollList.Count + 1) - Exit" - do { - [int]$displayScroll = Read-Host "`r`n Select a scroll option, or press to accept the default [$($defaultScroll)]" - } until ( - (($displayScroll -ge 1 -and $displayScroll -le ($scrollList.Count + 1)) -or $displayScroll -eq "") - ) - if ($displayScroll -eq ($scrollList.Count + 1)) { - Exit - } elseif ($displayScroll -eq "") { - $displayScroll = $defaultScroll - } - $scrollLine = $scrollList[$displayScroll - 1] + } elseif ($displayChoice -le 1 -and ($displayList.Count + 1)) { + $configLines.Add("// Display configuration") + $configLines.Add($displayList[$displayChoice - 1].configLine) + $configLines.Add("#define SCROLLMODE 1 // Alternate between pages") } if ($wifiBoards.Contains($deviceFQBN)) { # WiFi prompt } -} <############################################ -If display or WiFi options set, create config.h +Write out config.h to a file here only if config directory not provided ############################################> - + $configH = $commandStationDirectory + "\config.h" + foreach ($line in $configLines) { + Write-Output $line + } + Pause + try { + $configLines | Out-File -FilePath $configH + } + catch { + Write-Output "Error writing config file to $configH" + Exit + } +} <############################################ Install core libraries for the platform From 6199cecd42d4f8e5531a9b94ba52b6652b8788fe Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 11 Apr 2023 08:55:04 +1000 Subject: [PATCH 742/870] 0.0.7 Ready for testing --- installer.ps1 | 64 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/installer.ps1 b/installer.ps1 index e1cd44ac1..9436e0ab5 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -89,11 +89,11 @@ $displayList = @( }, @{ option = "OLED 128 x 32" - configLine = "#define OLED_DRIVER 128,32,0x3c" + configLine = "#define OLED_DRIVER 128,32" }, @{ option = "OLED 128 x 64" - configLine = "#define OLED_DRIVER 128,64,0x3c" + configLine = "#define OLED_DRIVER 128,64" } ) @@ -102,8 +102,12 @@ Basics of config.h ############################################> $configLines = @( "/*", - "This config.h file was generated by the DCC-EX PowerShell installer $version", - "*/`r`n" + "This config.h file was generated by the DCC-EX PowerShell installer $installerVersion", + "*/", + "", + "// Define standard motor shield", + "#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD", + "" ) <############################################ @@ -148,8 +152,6 @@ Prompt user to confirm all is ready to proceed $confirmation = Read-Host "Enter 'Y' or 'y' then press to confirm you are ready to proceed, any other key to exit" if ($confirmation -ne "Y" -and $confirmation -ne "y") { Exit -} else { - Write-Output "Proceeding to obtain the Arduino CLI..." } <############################################ @@ -165,7 +167,7 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { Exit } } - Write-Output "Downloading and extracting Arduino CLI" + Write-Output "`r`nDownloading and extracting Arduino CLI" try { Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip } @@ -179,6 +181,8 @@ if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) { catch { Write-Output "Failed to extract Arduino CLI" } +} else { + Write-Output "`r`nArduino CLI already downloaded, ensuring it is up to date and you have a board connected" } <############################################ @@ -424,7 +428,7 @@ if ($PSBoundParameters.ContainsKey('configDirectory')) { } else { <############################################ -If no config directory provided, prompt for display option, and WiFi if using Mega +If no config directory provided, prompt for display option ############################################> Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n" $displaySelect = 1 @@ -441,25 +445,49 @@ If no config directory provided, prompt for display option, and WiFi if using Me ) if ($displayChoice -eq ($displayList.Count + 2)) { Exit - } elseif ($displayChoice -le 1 -and ($displayList.Count + 1)) { - $configLines.Add("// Display configuration") - $configLines.Add($displayList[$displayChoice - 1].configLine) - $configLines.Add("#define SCROLLMODE 1 // Alternate between pages") + } elseif ($displayChoice -le ($displayList.Count)) { + $configLines+= "// Display configuration" + $configLines+= "$($displayList[$displayChoice - 1].configLine)" + $configLines+= "#define SCROLLMODE 1 // Alternate between pages" } +<############################################ +If device supports WiFi, prompt to configure +############################################> if ($wifiBoards.Contains($deviceFQBN)) { - # WiFi prompt + Write-Output "`r`nYour chosen board supports WiFi`r`n" + Write-Output "1 - I don't want WiFi, skip this step +2 - Configure my device as an access point I will connect to directly +3 - Configure my device to connect to my home WiFi network +4 - Exit" + do { + [int]$wifiChoice = Read-Host "`r`nSelect a WiFi option" + } until ( + ($wifiChoice -ge 1 -and $wifiChoice -le 4) + ) + if ($wifiChoice -eq 4) { + Exit + } elseif ($wifiChoice -ne 1) { + $configLines+= "" + $configLines+= "// WiFi configuration" + $configLines+= "#define ENABLE_WIFI true" + $configLines+= "#define IP_PORT 2560" + $configLines+= "#define WIFI_HOSTNAME ""dccex""" + $configLines+= "#define WIFI_CHANNEL 1" + if ($wifiChoice -eq 3) { + $wifiSSID = Read-Host "Please enter the SSID of your home network here" + $wifiPassword = Read-Host "Please enter your home network WiFi password here" + $configLines+= "#define WIFI_SSID ""$($wifiSSID)""" + $configLines+= "#define WIFI_PASSWORD ""$($wifiPassword)""" + } + } } <############################################ Write out config.h to a file here only if config directory not provided ############################################> $configH = $commandStationDirectory + "\config.h" - foreach ($line in $configLines) { - Write-Output $line - } - Pause try { - $configLines | Out-File -FilePath $configH + $configLines | Out-File -FilePath $configH -Encoding ascii } catch { Write-Output "Error writing config file to $configH" From bb7cdc5422603465bb4486f24e2a51fd8caf99ed Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 11 Apr 2023 09:42:54 +1000 Subject: [PATCH 743/870] WiFi AP mode compiles --- installer.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/installer.ps1 b/installer.ps1 index 9436e0ab5..49310819d 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -473,6 +473,10 @@ If device supports WiFi, prompt to configure $configLines+= "#define IP_PORT 2560" $configLines+= "#define WIFI_HOSTNAME ""dccex""" $configLines+= "#define WIFI_CHANNEL 1" + if ($wifiChoice -eq 2) { + $configLines+= "#define WIFI_SSID ""Your network name""" + $configLines+= "#define WIFI_PASSWORD ""Your network passwd""" + } if ($wifiChoice -eq 3) { $wifiSSID = Read-Host "Please enter the SSID of your home network here" $wifiPassword = Read-Host "Please enter your home network WiFi password here" From 05b0fc3d2e9524afbfdafaaf4ef3ea024a87eb05 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:26:15 +1000 Subject: [PATCH 744/870] Add exe version --- install_via_powershell.cmd | 2 -- installer.exe | Bin 0 -> 75264 bytes installer.ps1 | 15 +++++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 installer.exe diff --git a/install_via_powershell.cmd b/install_via_powershell.cmd index f367b2124..c3a7e0364 100644 --- a/install_via_powershell.cmd +++ b/install_via_powershell.cmd @@ -11,5 +11,3 @@ powershell %~dp0%installer.ps1 IF NOT %PS_POLICY=="Bypass" ( powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY% ) - -PAUSE \ No newline at end of file diff --git a/installer.exe b/installer.exe new file mode 100644 index 0000000000000000000000000000000000000000..f783eb2c325a77da38f9b6a62d96ec446f83d8b2 GIT binary patch literal 75264 zcmeIb34B~vc{hCK&Z^a(87)>~$BGj>@*2rYu!FN`vn|__Y;9gh;*2$8X{^zVGBdIz zy9T?od=v;JY$XBG09~LENJ>kA(gKCDlt7_C3-1q7czMIu@U;*q(2qis_y0f7J-T-m z$#N26zl#%{d(V2$dCqg5?L6m9@6p#OuTsi~zh|CN>ce>Q-|aeFI5>~&+La%zRex6T z#cMz8+5g3BhsKk+=BaFEEE}6_9*w2bnSAr{M00jJ-JDD}cl8f8PiEqYO*J)@*V|1W z=vHdK$E(8c*%~)r`;M|aRh}xPmV1;6N$QtgjQ3`|-;O7H$9GHmjW^wYKGk{TBc1=e z>h|AOYN`I;dD>AaqGr(UAm7pMcL;>ye|LLSb#dBa;>~ki*QG;HEnNl^rNuj6|?f^{7&4Sa7zkXyzC7HwkERS58YHQ$#r=c<+!eBi6s z@OixDT6}fm()Gc{mX%0Y)6$G^<4;fy8M++0Rk;o$i?#;Tn=x4YA71&h7GOYtO<#v> zJbl-^{8$|*ad!Z{9KT&nI4Uw!=jKVyET5U!bJJ4@pF~@LS@t!2!<*mz7`BE zum&v^cBG`sxfWE8`WvEw^|^*6rR+Ht+3 zgW?P>@oEfeZ8f9!>g3JMh!e?>&*^DhYJlL?$v1k|8KjH^ZKNOoT}Z(wTdpQSM-PAHoR4^05e)<)o`@CpM*=M<8(6b0+FGGLhiO2*foH?! zF&hl_@bO@O?L=9#?6IAV1S_)xaZVe_jl`-VLU5K?x^H;sPFcEBEjOdwx`qZRS{<=m z6>B11O{pX=>ccW~TE(dSI(LC)Nq72yBUtBibjR0_0rjo)HR?w(jj@vrr0v26(u<}i zB$_UILVD_=C(N}!bhz<|i?Xns{Yjl+XiP)YzkZrD10(?SqJ=bqsH*2Y=+kMWAg;N0 zrxM(*LPfX`yufA*eX#qr`ORTJ2!=oGxBIjCyiMq^?tv|&>6QY0C-VwN)YJ7Kj~-0> zPIluK_D{9k%JDc+yn}-6;se}iw|-hJd-&A-1S_M66JGgq5~@IDfYdHf6%U#(eU<1Q7IhGKu)Wys;drWhVY^*Y^eD%h_0t)7zx$uP0k2oq0AvX zb}Yjh3uO-DwI++#sg-v}BaukU5hP?W$x|&y@z%l`QD4~CcrPZ`6OD$W4HfSUM>8+6 z)0^wigSwaAfoNUkP9_5V>N0onEdXYj#XL_%L&3~3q{vWKSXs>Wlq7|NuL1UXTJA=6 z1|SFdInKJFa7e~sJ$`FARM9fZ#EP&li*HOd*P+98!2Wx&3Ov!ea9u+fy{gN^QEJWO z{%B>m@{DlR^}mfe30EB{lm`>@M5_XJC#rHNQkgk{VEbKcJ6c^AtqFY@^DZE48AH~N z{{T9Kz6^29o%jhPx`$V7NlcF>m>EY!8F!VHokmCO^f`EJs3|J+`=*c$ikfgu_79QP zl0;J*YSBE~)Y!28rf`jb2@pPs)M$0MdgHa>nvejj`tkZTR`$K7LETKW=5gh2apPOo z4_SJeih|4p8>|i2MyuCvc8{>Z%Kkf=w3AZo3}>z`y2Q#%vd)t5l4!MoZM6ei%*@j8 z(lbEVG>YBa`CrdG^LTYQydj8`3{t)oz?yEyB=XEGt^n8`EGC()>1o-5a6FxglG;Xr-c0ZAjdmeQG1x=J|E z(6+{ICLGZ1I3+WuQKn^v1^w%5CB0E6M*YFCFEfjrK;{g?hBn~VGiH_pwZ1LuEbFW$ zPMX+c&Z1P-?@?1>uoPKqzk#H%uX;U80*t=QIV9Mx^Atz-D=~OIcmMIUp1W$yUA4^J zeC;&hv!ND^CH$raXf)XJQoQRiM1v50GcQAQ!xFxo$6K`O+Zc|5j7?g&p`zA@lz&6Y ziT>NK#CizV0HQSl(Qp-(gKFQe z<#vt7OM%}z@n`TD+3FPHJMjDfo>&WdJpi@U7ZCpxp07p3l#jN0)t@7+YF@ne%ZLND zfu>^{>#XcKbl63gE7#xXN~^V*Q^czEW#7apA-YO&za~)r$~vIt{Z1pK4^;t~)<710 zo~kshGzC`Pp%+cVjkR`r0nLOe1Hs(OQLj2gcyJ6KSi~4MTzS2}M%EX^9%4TR!?A$O zaDKwM-$GiYsO6NtsY$J@*%EB|ZM>}Sw^Od!a$O+v3Zyi&eJvnwjYl&74-&GU@_v?zah)OzIAB2P;$mD+ew z<)zUAA_>wf{rX!0oe~tA2IM$?f880NzZQ|sLVWaPzlhIzHO^Bi46OShHKxFZhL%^c zQ{S7*&AghGQ5S3%VpAjQI=NE!Dh&?7gT?gmHE6P+kBb~zNj;2>Y3l-dbV6HKu!p~J z`p*@uv$dRf7V@$$8?_1tuVu^I0bBmFTJA-a%xe*x17smQ>iF+64j3W+I=)`7U+=@K zaVUm$GpHg zpdvrWCqKj|zq0PC>_&8$QT>5U4Ocg;T%LOas$%5w-wEq1`_P!V1Fg!tmjYj4T~-8W z@5ZWZFIvgH5!KG1%WK&3X6r1UdUDs@BoVKctmm>g<4%^2nOcOXlci%Oi(q!Lbj)NC z37sq*Gg-t;Crign7AM!q(lL|88F#XD%w%0tkfmcLYh^)}jsd_oFm)BcqhFBFViNR= z(MINbp0Uni>V0Ri7JQjE1JY}(vp3?sG4p$zEyy@tg?XoyH(Xblo4^EB1+8-%Q6y-c z#p?E*WBs7-9BWx;vG#p|jWxli11S{Ey#cE@zzuRr1;89-J$8faONKncv5AL$&1^ z5!X3owRb?E3v(RN`nqUC?gTm*ZKPZofc@&F520RlxG@LJ4xD3u0^vsez1$9@)Px%w za_>NqbA(jOJMmWcbO1tTAoni3XZ`>|Mav%|q@L4|`6IkRy!vB2>Y|Zc5(TODgd5Kh zX5nbdyODE_(8^6RFN}ZDbFIt^qYwuhj%-{Kjy@eY$&b8;El0wUmOo*`Q4MFb|6V7S zc^}?04>O@26&s%pjIrYTkr1s9*JnO}m)s+a`ALie8Yt?K?YI-c+-avZVWb7ZAae}3T2r8at8OBLQR>!K%ouC!xfG9ZUKr!uMS_` zkbM)f-xPqogE5ag+f<_uL?K4 zHhJ3;2wg3IiR|qAP@&~xc+311f@qcKo*S2j!c{GQjg$s-=Nfj$p1!Kg$C0`QYGU@s zp9P`&1Z&lVgVEaUk9kn6#tPRykohE2p$ldH1}{6#Gh<2SQ+UsO8bQda@5yvDAyEc01b{TzeOBhWY?I$@Jtq|qq?U4?>TY2EC#pR+~h zjn|dtYQ-eG-W1ue(ey<+?)pIEYmQ@9GJnS&0uI~1M2wJ9uAXLJWSW})Yn`UZhIR9P zt)cL1>qqA;S=xuWKCa&g=q@ugtMwe2{;$U()=<}}U*M?1wZIjqzZOts9kh&cQzLb< z2Qh@qW4e-^GR72{K6^I(_A0CkH^?F%=Tan@jW&cE>N1$E^=ksOlVE=a1Z*7^?#g%u zaIM_%FLviLe~*}X|1#b)UqNv7mQ^;vE6Dl>=6{s|3Gw3%cIJj^Q!0bxZ-EqjjoH;< zA2bi2XdaJW6|Kuob6#GFJQ!Bkj9!?JtKf#E*>^GPHB9?D0`xkDUat!;L!amLmDl3^ z13sm0@hb&^PXDtI-i^OsA^i@dSL;*>4&xC)+DZI@Q>)v7UMI6(;g{Ztk7WLlpPNCD z`zHiv3|mwkgL^wXVgk_Zo!Yvob<>vCEo~tFv;?FOE=LEhd#O^d#PbtPcpuDXlj*S> zOY9-(#)7(Tcu?*Ch3tV|w|ls!3wd`So(~|eBSo{6{)aJm4&C;y6&00;{^#l~9319O zfo79ODG*$3w;08f>EFO#1^zfz)YUo~%b{6yE&f2dn(l$9d1MzP@6qpMcrKpE7f~-%6D{%*JAo>bohHZd`gG^Kf+42EpSyJpgIB{ z!u!d9}p@KK}()L-lH%eu^pU`;Tf?hUg1kAvR|R;n99?8#eo_~|OXZ?A5u zu2lPUctnS<)!~2CJW*4rYHJzZpu-Lw-l@YgI{aYm8)_@nS9SQyTDH@%gl+!62m|W+ zrR?+ZrA&D{!hrgiPN@nr<-K8+|D+Bd)8P+wx;JuFpi=cjSf)>hZ_?r4M%a_D>+rwp z@JStpqbzxi4qq2N7p+u(sNX+=uvUF4%GPeKYpttPch~hJd`%t8zf*^wLrOq>yN>Yr zB~k)ve?8ytsOS6p_4_~8v)0daN=*ZEcQmktVT1worwy-e2&lh7SgHP7151V)ncmT; z+rfLKdc6+cufuT)PqkN+fg9W#y*Wp>ltB5p}_i{nRsr*jw$`66>gns`uHkRn{@Y zK4ZtWSqW9AzG%m~tR!MzmzWx~(!i-7+IexS3$dTsu^FoeF-0P*KBL}YWkJf{xk|@E zAGBVk8r6I3*r%<2#6D`r9@nwQB&LGimm_cJI%#Ez_f=Rce{j9TuJ^uKEmt!{X?)>& z?}O?Z^|duR7P`rM6!>l3Ah90rF~pYJu{*r|=+<64HsKvYEN;inc;5#NY{HJc%KHdn z+!0mk70Vv*rj_o9o`(lL-*m-J`~F#7uYN<9e6xDF?+L85Kd@tO(y_P?C$2VE zt6vqwQe1!-(JKQhJ*(BX)=TUifi_Qz`r0OmeLrxkXM=jnjV89wvsn#-*Aff4Osk#hp}E*yo}KC&I(9*wMVq&&C1l$e(LJcwp&ISj+k#`BPSs?`{sej5 zYP%i#OT>1m!#YODzudE1{ek3#+Cr}eWfsyy&QQ5 z)iZYNLF64&HCqX#SA;%-ya9EU9s3&c2J~Ju^L~W9LAA}!tE%cjtkaI&P}QY|)B!uz ziM$~;h<~4{Iac*DH7pUt(p8`JII(k82{kg8_o}KSVt46QzF)z5htvr>#(IZT#*VSx zVRhP$vEE_z%57}uKK1dczxNzbzx85?eW&W{o}=pbbxcdYvopxY}*E`Gxv>eaF!) z$N|+>GdMGZR7LE>Om!-i2ICeGs8nx%aY!AQtd#PR_!{a$Ma#&6P_N=W1b&)+R@Hac;CC^=LnA?Y*!OY z5~{~Dz2raf{@eQfb$IXbyhE3HgrU?@f30&r&0O^l`u!Wa{svljNBCP- z1-_nE7Xa<=TK8%AH|dnMI;>EXt11*_yZb#q3;)nstGViOoqIhThAi&}wK7uY{T;PF z@;>z*&)tzG?|VGi$SQSADA)Gs`bM$>Am?)gsL^Df=<`_z|p3;!+pl=ptm z|A_t^;lJ7`zr_2yBFcBa=ch>7s=r&IXM{bx%TuA}sK?_Ae#6?TeiaS)wyIEFweLRN zE5hn7&;9t~cHg7=yFH$Jg1hk^u1ly#)$%&dU2pJP)}v~5-9BHBr@Joh3tN<1Sn^v| zlQmX%(${2B>cBg~&#T$GDPNmzZB|{VoA!ML@8^BjT3lai5vEnl`hNY()IsY9^`G@5 zk@9h0kL7LnTi*_=sUZPgAFBI~53`IJS*s{p4e6A2>$--Q0g_u9e&}mMtVru ze~;B&cZ2^9^zh@p{c57|#s0YNc^r`J!~1Mw8eeChhji}!p5JU7^55@yRpU{F?`n)8 z{A6Pi;b$B3{?A+g()fh>isf5&#{Z3@dYZ!h~8!Y5It$Es-hl>b_9bJG)eZ)xIqJDYysU+bN0dJ5s`rl0#g)^9ewPu*v| zmbsuIzw*yoZ$yem_hg5cqYHu4G+-UIs;3Zch3%&TdiQ@K+^L>Hc)Riem-eU{g!^^L z1JKPkLEG&NY*Jfv_!2dM_v`fgi*?wk!+km&R6T)Dc|H~Rnfjg%ue11mclCRHKLa28 z@17<2qYPp(0H@;9r?y&C)~xlQ^{3XCtgl-CVm;)I`fl?b@E!Ku={xKDd!H4sKngzntDuWjL+h!5cB9lo-akk9h!-|_^_O1Q(AD30KaKF~I{gXn=kWd$og4D8OoNYQ zR_pgx-(z@xEJ&+)b;Y|8E~|JL;SD<6Uh$`R@2p^%Jr#d}_qT>VhVW)Tzu2$CxDLhhNp<4|He+SnKzy*q?V+v4_q(&jNTc!hnWI5HZLg2rY~`i1$i_7RFtH z_i7z3LCjLMIt&967WCRB2&2$_Eoh5Tlx))B)v6vTSLtv$v`UX!fzVRxkZ(b|xf*n;-~gchY`ybmF?AOXJ!?;|=qtnD&~ba(_k@~ERayaPS5V3%QdCwk;j zcOkT3n`y`U-3aMd*@^e#2y2&oHyp5*SpRPQ(yI1;)B9cTzj*)2_kG`H|89Sm2h!XU z54^?wY7?HfdxGk>JbYHD2k}(U*&g%&Bf+sw`z+-XoR_u-W`mgR5dNw#yS13XCHPy4khA$#&p-QhlRJAe@#$3JR+UWW^08DZ zk=>M-NvPgfGA%g+vD3rQjdZEL*kodGDmI!>oqO5`j`j5I>Q|lp{R7ZYdfWH!KQ_?a zIixx>>0Bn2IM$Oskx{#nsl?FiR6-37b?u3z<0*I3Y@-9YiFC*Gi4%#e+MUP`=HoWp>;f7-YAlgI z))5<>7|Uj+)A$5nrgCM{29swJQdqaBqq>|E?aXAd@nkxdPvmsT&gpCpGmyz8H8@$i zD2X4Jq`CH7(Os$O+;~qKgSAJ&;=3~0L~&n>)1-K3B9#K%M)R@s7_bE(u+uX@gLGc{ zxGyo=ekzts#g3y0+wYCd92lR?B}ZeaL&vIL4fwCq$J@ zMbQTb%XUE;EdWzd68q))Gy&;~I=Ms1JVxPUOMKI49&=he$I>-DxS~mM&p;8d3!qgV zQiV_|8i=dc@+v(XPA5lk{I7F7mR0S!(PUEI^4O(1K22;o3^ihZ;zV8zWv0|Xa%?=W zIx=}IRL<%gW$4LiWEn`rlUYoABCdLJvRDd|y0h6#)|GOKtMk|~wX0`;_pzZP2fB|9 z_wDQJKh)<+>FOEW=ZcbOxuOSpa9Yu+KG5CUKh&)kaZjIX4Rcj@4-E7Vm_?n{6jAr5 z7=?9W@thrsWz*QVD=up%7nklz9G@;Js}qY$jwG_jGr2@@yLNJMX^_;hY$BH{E^g9` z%iClWq$Pz};VRj*JCROglcTCDsS{$^*}FG`9@^BgOa;jS??|LT-c3Vyw}CArQt_@t zZZw z8)?D*WEwn5*vepHbQ)9|)POGu*>xqAIS|X`PJ>u#a={wyxq>2^whM*V6Q�i5f4Q zXyKdYKxq!go+3~`enr?xXh{Qo=N5l68bXv}y%VKOk>t5KZ68a7@R(h9MpU)1b7I ziA|bDWu^ub*;63UIY{E!+;}Y2o@+Pk9^IJK@_1a!`{3YCR{XR}ZZQ!pcdFDB$?pJ` zE0%gs+NqP8()7>HNll4p<_LzVsm@dqm5rbnKMl5#ryPrx`ZZ-k~ zGeo@1*aBcKGo6Kk5qFwUqi9~8V11#J3Y1PH(A=C%K29k{jmCtDO@h5bAz>?qvE|gL zW@$Mjo``|=+H7nZnujJF`B)ZcpkJYVWlksJ4v!L+cA`j{J6#l=@@UkNrIl{fkY}cqu*S3Xjjzj=99;hDX?;< z^*J?Hb)`~0lT(?jYb+CqY&wzJvUyWn>mWtC+o3Yb+aaU2YSJ%Qekd8wkGtY~5?b`- zYL_gMf#RZeAt%dK3yT;UnUE%j>5FVt~6cEUgSpmVLe+C=c{Q-M0BR; zO6~DD1x6)ZrCz32w$3Nk=JKKqIOE;#PPPYNe;Pty@WKvjyk>8IaJX2aXhuAf$TgqXG90{bJ}Zhmn7~6Ce)P})Ql`_8mAI6w)SiyrrNXF z*eo@Z{fYFLJ%LhNLxVGOSbtv<2{fh&{EG?}7IDq3g8)*bIPU7GqZY~=7a+_S2OW{pHP26YpXImYkA^>A$# zqTdeyc9RJJ&k%%NXh;(;jT-jSlhTr42c{89lp08N0Wb zX2@{hI1>q50nvqY=lKRE2N6^QCj)B^j7}YZ1{JPU3{;R2O!i#Q4eD{Eu8QD2r!o`R zWJ~9_Y*vsBU`=AfrxIDJCEylf3X)>Ipq^+HD@0|cVChQSGYt_Nz#Icc>`A1idTmyK z+-`SXmv8}XS~5J=(TsBL$pkRs;aYr87 zNr~y!WppQn~Hw0HHcK zPW#o=>D;*C44u?(Qf0c(TGYaErx$IiOeaR6-Ev3{zF@tMW>U6gac&4(l>XSESTb*~ zxxqwUID+m9xIYHo32}p>gwm?gU>@X5&mkl#N)bdPjX3g_BRfrD<;Rs!4d8?bmF~H< zQ%c25-682rR>k7+a+0WxT4jy8n?PA|G(5vpXTR+N5zl02=i&--6S#oM2GYi|d7_hM z4tn@SN&UcTyH=UGa`#rI5bNZCW{4#FbKj9U0j)~JCWX0~clV$%QylgjM&g44PuLQG z#_OWDR~ zn78w)D>F?)?@)I30Q8Oozl|5+L<-UaEgHI}(AAC%_8($!t%%1BP2oFW3JoQWRYW2# zb^)vm=nP~NG#J=Cz_oJt+E3FE*GEA`iX58T$V!=Oa4x;1wC-cO+AX{1Q&Ii#<$TUdo&>z2Lfaa3Au#?GT@*JFR3X`aSm-CvFxz6gMS!mfJ< zE_pbhN0U>hHC*D76W$3=!hY;Z4#FpM0DGV+7((q3_Umy9rM?TP9mw6SOUu_)JxdOt z)_^;;X+*csfpoUTKHKG+4q>ha@Ev+B7PPeT0Ll(v(}11oM8Z$_bBQ}ZuP@=-TD_wRJ8<;mMb^b>^0CO{tB!dq&>kr1 zWz!;&O8UTs(2OAywQI0dO2jGGr<;K!J6+$9DQEk)BFE$BoAM@b#(+RdqgNh)lIV6E z5ExUQPpD6~AHWLj#k_Q)tG!4(jY`ma@O~1rhAmJ0bznF9C^k<9Fc^ZXLv^8v%>Yme zZwCRIlbG9fVEjHjS$-Iu-;LZei0?+-DU{!h)G?$V*54ULd>6{b@#PM63T4>tE_ix& zV#Q1QZ2*i50jx*S{~c&Y;FQ;J z8o>CEVge>yaAtc4(LSu|dVGX$kB;v~uTr`{0`Ez{L*U4Mv!4Ti_)*=@J)n9iJ+1+a zYeawlFnkrp@m<0do@{7;FAyq*C+Bxa_xGfR$2PPp?GO%}x6`ij9526fNcZzdQU5s) z($8*`ao{^#7QSQX^CaNXhJH;J&0`W@9n;?-9Emq0XpiGOim{F2yL}peHtYEid^GqX z<%w72#>IAz18+!o41Vv^^Thcw^DS^^xeon(`eRH3lZgj}Bj=6naDH3SCo@mPi&^w< zGkQsUOrn40JLWltR%D(H-XBFf#7pAeUW|)y9ntLyoHhcR=)nk}c@WX{1JbMmolzxXn7bgApQ@a{0@|6emCO7 z2zTk)-Dsx^tqh}o953n7LBvPU%0Y~~4ZYc=<&<_^kL%5>Q?3*C%+SB*Yh6xixRNd$ z2Ru10S+5g#67J*nyq`h8%F)B4x<1zj@sxN(dN2bR5KiOx54&g>tD$p37gF~85f6?4 zHl&BLKDOEL{dLeGH~hHnIFF>Kq)QH-ao+m$dOL&}VL8&1A&f)TC)W?_QJ&;H4I+L7 zPnO$(8QQPc4e6fng9924ts3t-uwMF6Z@Y%m7Nl)KdGZS4CB3+~4kz))_0DzdrlaI* zr?7(DbYQ*BABo2&vHr*>$x~m*r|?Iizd~30@O|N9r1zvFLTC5t@ul@TA->3Z?#6Sg zhF2dTPB;s_OQD_v2Pi|J0vu-wVM@1)ZFLA6q~|9QC%#e+*bF!x)KAiZBY-j2`+k&S zzXZ>?t~k#yCxAYZo{i1pVm4JUepB3q2oaY(63DRphT1 z5+7u|96uCDz?1TXnHM8Ba2_@TzK;AwdcpbPIud#z@*Cx(Ha*|Mw?_e6!2?-;dr@yM zN^o70?hI)B5qW{~6z6*{FoN^h3OpD^ocMSI-%sIN&~y=Jzeta%lNtWWr=brbpAk=p zPXjg{6R(7?6v`FkS4O@&s_~rdh} zjscCw#B=JChfua#^T~4k+pouW2K|(E!tzHoJa(fN^ri+b=8~Bi;%q<_4u{{mWMGP1Hd)TufU1gF5yUd zk917r3*x1br=Gv>%DTHa-pF`GpSTj}2xTqetwSHUu4aIrM*z27S}xr)&%6k~F>=4i zHJi(}KKVvF`bT++&*NA* zDa?k<1Nk<~asHBkhTto`;f20&1ydfAbvmHuw@cIigQ&-G%le+sctART9A&0ZK8~^X zKwqakJc4)1r_Q)YpSGYsX8^~OD8EpgCwQmk;7j?0a?7aZQRUaWnJ3Z{(%mEI2lWH$ z1=No^G(J*C+zUu_YyPkgw1x0FjByz~bO3clZ|c$Gqh3Wlgql40QrrcP9>8fFkfC1W zmQM*E>VG1iQV*m&--h~0JwN12t(p&#ZkN6x?A^YH{+kDZd%t z61`LO5w3gcA7Y<5s=wplE$IOD`a$%61bq{Isuy3U{+qJ#c`INe^FQBsO*=8W9gbW2 zJqSGN)>OS0cuW~_4C6eg*9+}0hcG)_H@i^26(e@zxl>;7?Ih-f{FeIbAoKtk#}vj( z`&2vdc^|$NM+wT!ZD@<_!%-6LPNI~^A7a6!WUm_|@0fKgavt?0a@iS-+|2jI z=a+nS3*aC*eZB!gx-q%qZn?&54zFwjEk>w=e3ZYQqLq`+66g*@H2db@+0~80C<7O6{o-tx)CEh zHsg);xDUd85=U<4{$3waj+F2*@|)eLJEPY*=Z*RW<=E2ovyfaabb$Rnf%>QMHSWVO zeh}+B=Zcemb!fgW_9RDMn6T&F&Hp(L!hw1LZ5B?u(hhAM-1nlM=*X3&a>Pw~JhUd(Q>lBY^HOMoc=-@lxO6o_Yu1 zOu2)2M)}I2UxW+g8q$R{=7sto=h^7B3+Z9SdKzUJXFg8=Q)sW*XFp{gwxi7D(C^u= z2`xu)eUlCn9^7A;Z@qIJiT#Ln&78(J%KI{o7vApMgKwLCO74YGp6%20o%D4ZX5QFW zjhwnzy2SB`UPF5BtUJPsw@I=DIO~70^x*k{2QltSg9r7%=Mx?~FfRk3CR|Uwz`!10sDlr*{kgvPQ|=-?Gv6zZ2b_2E1MZj8j#NyK7Q@G=mpl6^ZvHu>@paeSdZ3I# zyobLf)`#fZZu%$utk9kpN3Vp>b3BH>C9$?B4;9*Bo$pc4qfLtY<~JrJH;-%18JnQg>+#S)Xd0Jmz&PcKY#SQDKG!g{&EU@ zkND@7KWM)#^sg#6Zjs+e7s;pTPa<+R(`mn=oI|?<v{TU!h~J^zO7ODO?oB+FeJJ|#(5}G!K-q`dt8tckp3JN85Bdc;asX*S2H)kg zIK3D?gxh}ftpoU8ydKD>xnCys9nsT-kJE3U73m@;(=J1M8taR{MCo_SuQ%H1hC#*J zFrE{5qrb{te3O19-GFu*-j5oCvE%%Xfz>hsB zFY{{lLut*9Nh;_Df^L> zfjACX&(y;men3AAS^v}zsDI9YB8pvH;)J8@V;lZT{!zGZ*N-+!;rT*-pYoabhY>Fh z0xqNFsyL_Lb|6ML@U!{ya8=>H&e zb8-98)SpSeh~Jbyh&S}_O4)M3LA^f8(|@iju@jIlkOmt0g?VSB4QM~19ZdKY?$&eRk@3Sg z11?JbxDEWf6LYY*`5VQ06~EI$yHBYU^$u*{`Rc6Xnn}@QrlD;m3wv(T{-gr_dkDduCqf^Wf0w z%TG5ZAh!$O6 zll%CKTQ^+)V_5I=$?4_%Q{3_-?=>|1SmcU@`$>M}whKA_fr7u{|8^2jKaxU! zclWvzKXlRu;h$z+9Dl~r^CXnJ%h5BT6VlJ}c6Qm{B|aDStK5Dq@r3Jx@*U5$5WneP zE9U`>JZ$)AEAW8&;X?b$!wdGC>tqUZ&hH)tA2<8_#(qM6Y~(;bX$z+wCw}_eR}nrf z{S%5S+*+=m{) zIOIGE?X8qqNtZ+p>;R;Rhei+P_vsJC_6gr|>x_1yOIuf5$HX_WpE~2`zQTOtqkk#) z11N70ubq81p>G@?E+4f0HRz`~-?|mOhx9`9aOzFmuOptxI&jmWh4GmCQ2dVDZ=U0! zAD`$Q(Jh< z$h}TKd9FtI0`+5-qaU64hxK3;P9TkZ)ttW}9$tDo?0-3bGC6M|{pC8OpOLd3FYkE? z+Ox#(-_S?eLCIH0-*(|$=F_pC9>ly9`hyYA_$w*h7{AB?Gez@n&MlgG-K*sahYw$z ze$u~$^q%%zv6m3v=*M;W*Kwh~jq6a+PqAxJf2BMu{*l}damQJ18`hoJv$@|h0z4Hw zc9&zlh2*6{|d-=-`h`$FIDr0${bC5~UGyL0$7A}>eOJ-}(j&1)$T@A=f9bc& zb_(q#PPsxowaxzC0OT#BuaYkm&jWRib!1(owzYzJ)!gxHN+&_pm3*ltu-}KYS%?^CbJ|E#)2xr;? zc}|)5GK}>&ggEVAlt1KrAoruFFH8dd(}0oavxJYF|KMID`LmoqT3GLK$}LPcjlEj> zOFAd=OX>Xc{O2~%Wy%M6;6K+P_rH$dPweT3kz(kN;5Fp}`spnUe|k3a{E5&h0~hC9 z#>MN4Kdpo3&VDP$MfpMKg`9&a=P$K5ygrI{NZ)85GxE5|dB)$D_{n)AJ>@!DSU)Jn zFW%cA=kg4HDaE6O=#$i!^FhQD@&TFuLVx^1esD25Pj7MR^D;lgOQGkY@4Dqi?utlU z=sWF+#AmlZouLEs+y6*Mck6Y|^Eb|UaKh8QzFvrVEyw?P&Yf~*p&y+Ym)Z9qzcBrs zLcfK+QSTEuCjpx2oS$0^{*f+F4&gbouU>3ksCNn95_poo3Ewktb@qo!?OXKE;`azI zqmR4cDCfVp{>5*G{NM1$E5o0c{v#qk3EqibNcn){=9Bnp{G7S(OuR|~-lA{QACh)& zu0O6X`hioPCw{u|u@uh7wPn+?`JxBfM6ZZqT-V5iE^G5rZSudP#%0a{v>UVDZmvupWG5+_h zm|yCN^YLe~Z&FU-KAzYIDd+HPB>lFz-o(#_bZ1J-Yho{O!@V6ei}ZdWe#iE>kIeN* zJ(h9zdj4N`{tM|ifp5mI z`ttZwoAdnCC(7X`^>}BV2DIO!p_}HM5cR=@^?mxa%};-M?*z|N@H_MeqTNvBhAF^@ z^|m3O@FiX6I_LUz{Ak3Fj(0q8?}X*q-k@H8w4aIH*wBq%FCFAMb?$>8KXT(W`Kz4k zal?b>=iU0$m27{bjbHYc%iF!>ULWc?+)onu%)uX{_rCDwf=@W()~K}FY-U`hf!{)zX11ng#S4ESt7?%E|+uoko%6tZB}6WxJ%Hb*9CtB!fa9TG*&aw=vhOeR;`j&1eK_1- zcJE7y9ce!JQhqe{ryXe7Jub@2{Wd;vUz+sh`FaXpO9JOa-jO)kMf%S>_9sJEDJSi+?R4B$bzeV*$ok8h4$)bz7( zzNo)&U7LH)qt7kH*;HjQv68hxZZ4{t^A- z3fC>=UbD|9>sR)>1)q$5Tc|gX9`F<@=_%z6-XKXjZO&^K_D}4;i`}1f+lva@As-}N zoZrvG=uP5xNIM75BXXS|*XzdFmn)Qu=yyZ>m;F1p{4eWG_^^zF^KQ19QJNO_lXy6oTc$uqQ$zCbON^1JA(goCjQ#vvuR;co8H zarl3!9JM(8LC&2CT@|||^%*1g7V{NHUN`o4%GcDV#m>1E;}g4?_^(Jig>s?D;j$m& z*sV+NRp9x3v(K;Vhr<;GuxncMEq8&~ACFy5i@Qif6xIOlZaG<_T zx-IvXI(mp%|E2PO={%7Bb^u2x4{`s3cAN71)%1tD__%~_OlbZ@JRpB^`xy#e(RRUo z4p~p+m7vKI2YV%%`LiyRvM}@DjzlC^HET@tFyY*VPocVmi#l3I&zplJgEYA^t z=BK}un|VS)?19{qr`#j=j)}h!^$Yj@1ntqg`<>ixy@&h;byv)o6O^8w^{)Wb+O9XuK@yg?^IaH|X}?;5?PP*V2s_A_o=Q5k;R^oc>x$C)|3PIp2Tj^lWkXM?J9@qZj^a z?%Q$8$HpE~On-@M!{7n^SO>-quy}sJ+f^1X_lrCzbl17>#?ALQ50o_C-x{#zznmOZcwcH9c92W4Yn8{3QvQBW z(|Mj7aPYTK9(%TOs&fw(?}eqG5%H4y%+yci{5Abe1b@jtF7G`P@_QjG49Rum{(U9!!;t$R%>5&!aHQR38}OF;uJ9AN zH{6j6Isb*{=)~SY{oC=YV81928he50m!@A^Y&p~TFN5tm;v{KKYw!ymxvlat>qM2VsY%JY5`bMZVPMIu`k!a){6exnIlpZ^`+Z zcG%^%pckZfJU23*p3Ht8LT}vq@j`Tn-!GI`-TmPGI~AXU_X*c;EOT zqo8QVl^pzr4 z(T_~v?WWJP>vG@Qx%Y;00Wp+*;p9hRKjJ#&xXu1D=?2^3ype_xAJ|T@9aZ3cx#_y2 zhdS~!W&e|Y zG_>!EU%c#Z3B8kjA^JBGzsut-&s)$xk@kM^6CASJo$o&T7Zu>K*(Vaci1*i#N(%j8 zzos&>1O=H8m{yMAYUV$cKm~Q9_JwNo^+i4GD(DzZ_Z_(H^(ogRPR~Lek}chisc*k zK8pA|8M)E;|2X5A&)#T$-?3HGF~Jifk1VcVyx&0ltsHxY<44JJb<*#J_JKqX>cuQj z&M)VO#NTfdK5q1q#kVVVE!w}h-_(PawqX8*9=p%87VlGu{lnM;OZ6_v7r#{Ued^8B zw@3#>UcGdGB+3W$)1iDpy>+-?UT9BQ*gt6u^$YoR;r_RShaxA?4#e+tm*HcVwvT7z zh&C-p$URb&N5pRMT>Ogq0FP36YBBrf&wgLayI2+zpeS@+=b+_i?0u!h9)+Z+Dpvuo6x>-la}|K z`_HZ%yFKqGqn$_W6U1ZgUpT)%$~f_z^07IuNqdK!9~b%1_dZybCX&meC+1<0@oO7YPL%t~dop@M$F9!YmDA!)>cQeb~ zzcJsw1;5jQ{Z#7pqo9BMJ$3G>(Vvs_(24W+zm5Gx>;&b1H}%=~TlhThj~Bn=HuQ_* zrQJ^EoBP6r{=L)#9s5%;ePH>;?Q=MK4(+(KUsJymeCz}MihLvfLLCM3K={pnA1n0| z_xhOse7lib#XnN+M>F>dIrcFlCzz+e>y{~8d3q}8=A6KayD}If#kKy*O z;r@c`KXG0}-V?aX)Ty zYs&4iF6N_O&-MNh;?qL%gL58&HVDB(${}KJ5I;-an?SzAwM}`Mevd|ek>~TV56gW| zxlf%iDc^75AKVYRRK3#qy+!(EnsFvwbhp^w=t{Cf7{6%RRe8U<*i!^fa=u*j1anWl z-2X2Abew1*+-$hxA6Wecbw&DCl-6A*dytO zK)p%yJMQ~XA1}V=%bfdhXzph=YLB5;!^J8T}U3K z-dK7cit)=Mf1&+>dZp|)irj1bsNMIX$iBOryCi)mysu8~_Yixu`~D8OPt4eP1z$P8 z)a#5Nzx#ef;`#q$~d8ZGUPo3RQUKhVeGVhf0%eQGVb#dn5PpH*%;Cirz_kkJtfa z--R@gah|{Wb?{xsZd>gCR1UvhXueB1gyW~YDe#>DeiIMf=TH|9PxE`PmrBoRFDCtx z`o;FJ;{7X!pBCHy3_ge-0q^0ZK1{z?%G12}i+U;d|M~my^!p&b@V-=I_aa|2_J%@# zH{y?by}R#aB)u%vw+rP7`W=&g$o=yyFM4ylDBawnyD&Xn_&#)_-z}uyE|y=K{YUwo zCHi@hK2dKKJ<5$QV$Uc4q5Lze=hXOtl(WwXou__!dHo)!2k`zS!i(n|IF1x>WMO-h z+2@pd7Px+BuO`2jcKCaRwEs?OIz_o&_9JMY7J76g$%FJC7{)5$Y*3Gr{c--@ik$Bf zJ}34a(tr9{m)f6resjMb--Nbn^EdNIU*!H`{vPCf_Pz4IuR^{j=UadM@+#?E;r!1h zug zzm}8F&G}pN!0{OSZ3T@PYyo0$0g^v`(PI)-6Y-N`sTTGkxRJlD7Wz*R)ZI!Pf?!Zx)eLcIM(S1 zdR_j0A@_I|;)g;X-2E%$i^cgVmmk}Fd~tE`lYQTdkB9sDose@16gi`d;u$KlxDc|NSr&rJ3i_`BS+ zx6kiaLVV*mNdMe+JK=xD{@%v!=HLglbK1pr;5%G*v|q_NgF&=QIi}q2i8%KT@YnQ& zKe+Fo8wC7I$IJCo=!aIgPK7@&?AO6@%?D4)&HUXH${}J8q(11Jt0o*v{S&!P7ludi zeKVza!{51;c6h&&vu`Q)v5EdIex=4AM8@Ix^Sk9v*~eI1zi27sxTt45w|=F`o0jQ&|p4l73=48Jn< zWW7?4r5!kH`;Sw;y*T{E?`(1FPwubac@N$%A@}q-@;U7`w3o_#ZO^6OJyzsBS?hn$hiu8f| z%j9$NJBWl|IeGKSuvc&$@&0Ltj+8sk&ih>`UrhlvlURKs2bgmUZQ%D}&uN8RMZ0tH zJks9A-xm@&<#L{%CH*L*Kb$xE1raaY_wUF#@N)Kv-Z_5mzVDHC-~r&%1m>UjZ4tkW z{vh`WkPa?}kGlO64*{RJKgj(t!d3W}2Z(wY={w~w;vfBIr3puIBEB$kR-=k1&C*G2dQy(Zix5;@SzHokv?+J9))2~b3DjeTJa+a)j`n@dX z9vfMwat?~?h4V$KmC^WZes7Zd)b9KJ7O)xUno6SL%-C8=9k|mz6t+a==+3IF`sbwJ=bjx6pa0$ z%p>%`p`%QfeY!$>>wI#H;o}Y+E zeV9?o1!9+#bw2>S;_s!9U(8?)9R^L{Et@>!M|vRsMs285PM;GvEFON`x8i&X{gL_M z?~w6V6xz^>1LzH7ycg#r-Z$gv`12dQG|%VuUCI$M&Xhh?M}Ny9^o{q|QX=CGI~*Tn zO|c)7Ud!(`P=|8c6&!qcuJgg$fXE*Q(Gz+*528Q2u&*|RK8yewg3smgaDI7W;q@u{ z?tJU>`N6ZoaS^&4C+$|Ut_tM~k@rczXjk8>{r|{6%zm!ybC$~Czpi;W2%Myyi1^Wp zc^Sm|AY9mw6#k>#?y$xQS$D07lOFIsVexX&j0_T}N}*dOOxhvmj4av0Cw7`)=SRXP8&@Hw4> z=nv_Z+zUav!13~)0_tJxgZaHx8L#kZ*&i<__qgR?(*HvIAl`8Q)oo|J*fs>6Jj(C1&WF#x4*IdsdgPfr@w;&3135=+)}Mjz!u>8? zhvxUn#LjQ>g%4cb--|8WKQs60aQznJ#eVQ!-fLp?h9j8i4x1m#{m$ZNCwj8Eza|I# zW;x0iqW@9PGI|94Y)kbI(rwX)oPDu!eg@{=Q=WXa(@w^two8}&b1DY@KcMc2v@7?jj{b^^Mj-LbZMf_FG zJ=(N0lm3Zc3F$ujlLCG?^08ST!pE5Ze4J0ApQYRXo4>DNemB(Ie^4w>3x3kBBKnT; zQ!FR94;IY>;a%+SQ@H-GEdOuvUHS!34|LlNob^mOA_=}se?IvgL)vvIUpV`%4!<`0 zWTY3yztG%Mb2;s2)c?#rCvjWkMDu%|v@3}{!#Vdz{_WToDVv!463gAA)UV|;gU>ww z?A#;cz`@*SQa+y=4gHJgf53gl9QeHW^T~OZ9_^pZ^&xTj{Ri4>DObq-NXNmKIZrZQ z+c2xn{YTIB_tvO48oZG8Ec>S7KTW<}eq1vD2Jh)_D*hF6e@meru{b;9E*nnl5BUrA z*Jq2zj$T&0Zq0pwSHk^7yw8yMOFqi|KH5i|`yY9q9nXVu9m_prhF|k3ze`biuYkD+ z-F^SR(BsQ_P9q7emHIR3Ht#v$xlNu6BNp+rIpqr4JGj31yJB)Kfc6{8y!>4axhI0_ zgnqE3$Ikt)q|e3<@Lc>%7l)_DAO5uFf5y&2{^Ho(Xx|e*-O_VovM*uw!QJ%8eeW>k zH`MOoa-XLl=EG3uQvDVId5_=F4qzD)lsZR+BHT%k9b~J?0%*8)5Wb9?gPvWeYR-%CE?(m|Ci#R5 z;Ys=``*r2?)h=MGgV%1mjM0yIj|$hF+#@UZu{iq?<><1}tH|F7Uyg(P^2FSHJhy$^ zV!mbUa(v1?F{7Xb)N2g9#c#e9`CO+wS4laKe97SlgcpC`PT-bAzvqLCxwoU#j!8L> z_|5aBqPbo7U!3FQ1?gponybGJ&tsQ zeo}j|zU5q@@uw@LuZ!#F#o$#auaehZ{P*b`e@xnCO}|CIqkpH^ak6?mvcF^YkInB- zQ@(QazjEuJa5s2=2ymx9#B#i6lw7+Hn9unj{CIzc_}S53B)@Y@I(ry2Hv>32cGqsq zKKEA!F?*B~9sV<+Zc*ySyVOS2qF$ozQa7t+b*Ead)*{x7*q!ROxtu%II;3B(JQYLG zsyJR%bv?eteXef2??O+xzK~vXJtWI^U^e$yRDA}e^Y}&%VHO-PjedA)B$hy}Nu@Tn zBefZIXE445=s+IjGCD1T6xwmJNRR6ts*QV)n@9N=O3dOpfjmC*s3WZ~f0O=C-8hU= zJcmr*1;Qb2mnr@dd)BP@@Bh2$_2FwCd3(=a{q&K4|I+lDTU4m+ed+Ne#rO}N@uma7Va9em=RkP0%-WXnuzqP?; zi|=b|_*`v@qi`*osr9wsgAA@^u-e5v0)FLb3O6GRFK_ZHWm$m= zZy>zfMW~ldPyp~b{~jx3rWr3XHR#mR@Y+yFKdVC_&W@Ku37>!X z+F(@yUWDsRtx$+~60Y;3^XDJc;h$sHycMV&@`lfU#0mv1d=PK-!OCSoBKZ%PM1%@g z*QyG1kN@W^^x;m@kA9e@Qic=;87J)p2+#HhtGc#Ai_&627^aHc{$w`%}iT?KYxH> z6jOixyW#UsM9x3SIH2@zfd29o%XNk2%Y({Vz8o1f&Hm-JQVyA%wdI`tsET?jfTJjZ z|NVHYtwmT{yL@}F-tKOsTXJ>y-Q}U$<&g{M#a}J2jr2y2;N9-MS9f4J26Mr0)rMr0 zJ@(joF#ok7F2;}-L2XEX1%vZgQ3X&%(Ut3iwIRE5ZKS8RHpDQ5Bm$?$eh{H+BS(Tk zFTzMK!0q8^yuqLqIf5C+Bt(v2ArfW$N<}VI=&**UnD7Inw=8FoTY|(4{PP7O7n%qP zjA{kvEOOymtcC4NUu~zaMZ!)dtmUXKY(#aU^YUdC%@&GBE^J%Q&tKRUxt#+*{)HVF z8Rn9}U~ww|u%a`!$QRm!=*WehNE4&s3%xpTH6|RX2c-xAhVsLeL=tI{O&>vl6;-HY z-lac;BR)qM*J2TRK`1Qr-Kq^=xU;g^yP|eQZ76&phElaFy!vBFfMSK8^=eo6F&7@x z18!YdEstr9b{EbCh!IO`bk;1_BugxplFP4dwfq>q?%y@i&1=xay@ZFqlC%pmV}0MUfFrU~qnHexFy0PA zga0uQQd1T3V9D^`H6GNP>C9(VtLEEF+E{#orIw;_vr^}m;kg}8 zJY4@cEQP)8^6BGc(pmXDgMxo-MXA@}@1fg0YCpM<{qLy`kNSaR>y-W0Ry;=_YaByJ zpK1Ej^g~uUhG!onk$zq7uY5mx8tcz)#d+Ljzxwbj&h64^Baj`k+EzyCp$AgJ3CJ1z zcfDdQ%0rYca*!@!kUK!Et7a3qRkz(*Q@Jyi z%Oxg{r)HbcKstBJs_AU{=G^FbVltN7IGG&HW^$Pm`HiEQ$(v)j$xWx)Ry9w?(#aEv zTzt!^=`Hshbu4oKAK zG<&>1aVn8&PBFY?RV>$&K9!kBWLGs$C)-Cy6FGqKL@bp{*n`rI+*qbJrt3Er^nK@z nb3j0Woi~~pLWHyZfB#(+0<9iZjY403@kLAi-va*|Lg0S_LL^!) literal 0 HcmV?d00001 diff --git a/installer.ps1 b/installer.ps1 index 49310819d..b690824cf 100644 --- a/installer.ps1 +++ b/installer.ps1 @@ -37,7 +37,7 @@ Param( <############################################ Define global parameters here such as known URLs etc. ############################################> -$installerVersion = "v0.0.7" +$installerVersion = "v0.0.8" $configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h") $wifiBoards = @("arduino:avr:mega", "esp32:esp32:esp32") $userDirectory = $env:USERPROFILE + "\" @@ -431,12 +431,12 @@ if ($PSBoundParameters.ContainsKey('configDirectory')) { If no config directory provided, prompt for display option ############################################> Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n" - $displaySelect = 1 + Write-Output "1 - I have no display, skip this step" + $displaySelect = 2 foreach ($display in $displayList) { - Write-Output "$displaySelect - $($displayList[$displaySelect - 1].option)" + Write-Output "$displaySelect - $($displayList[$displaySelect - 2].option)" $displaySelect++ } - Write-Output "$($displayList.Count + 1) - I have no display" Write-Output "$($displayList.Count + 2) - Exit" do { [int]$displayChoice = Read-Host "`r`nSelect a display option" @@ -445,9 +445,9 @@ If no config directory provided, prompt for display option ) if ($displayChoice -eq ($displayList.Count + 2)) { Exit - } elseif ($displayChoice -le ($displayList.Count)) { + } elseif ($displayChoice -ge 2) { $configLines+= "// Display configuration" - $configLines+= "$($displayList[$displayChoice - 1].configLine)" + $configLines+= "$($displayList[$displayChoice - 2].configLine)" $configLines+= "#define SCROLLMODE 1 // Alternate between pages" } <############################################ @@ -535,3 +535,6 @@ if ($output.success -eq "True") { Write-Output "Builder result: $($output.builder_result)`r`n" } } + +Write-Output "`r`nPress any key to exit the installer" +[void][System.Console]::ReadKey($true) From 3e9537281664e50f4bacae7fb91e0c39555ce0bd Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 11 Apr 2023 11:48:35 +1000 Subject: [PATCH 745/870] Rename installer exe --- ...ler.exe => EX-CommandStation-installer.exe | Bin 75264 -> 75264 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename installer.exe => EX-CommandStation-installer.exe (80%) diff --git a/installer.exe b/EX-CommandStation-installer.exe similarity index 80% rename from installer.exe rename to EX-CommandStation-installer.exe index f783eb2c325a77da38f9b6a62d96ec446f83d8b2..88f12a243dc84a5eda81c224c52548563bbd7f4c 100644 GIT binary patch delta 5399 zcma)=33OD|8OOhS-!eYArgf8zSd6&-o%u)MjYKl=A4rsjAIA9f2%NRGb6p403}k;=c6brq}Bl*bKqQ-PUn znp9-&fIZV31ACsSqRUy|f#0NEte;0-=8|&Bbm?Tov9ZF46v%Z(@O13{@``Z*% z6%MFqZa`&@f$gRl?AaT5Enw1NsGGhEsLbr3%A63~L6(~;0d9Jo^&?g-bTWv+LcWlj z28N3KiS$_Lu23Q^V6A3d33bzZp$v5aY+rzFQYfs3&kL(b7s58lV$aU-%0%@>s+U%& zI(z6^i@KA+=oKF752uSHrIH&|#AMi1%;W$G6qcbbf#N_KI!!?=8PtyHL8fGo8sbr8 zrr<9|PlCdfMtK;;L(h{%Xmbtqqh&~b4MCy|x}6KR@DLePf~FqY=U16oG=ZsmilXbN zoM{kK4&BYPnW+O!qQc4&;UkT1)ptRQS;y-i839o`;n+s&;ZQTct%oTBXRs z@O$N8;6^)mlBpp^6xL=VR@5feCS99PAzINkMqRZf)Pkm~7aK!#)s|8#+H(z)^A@cb zZ@J8f(d}9ZsD({YZ7}B5pUK+hd`K$-j<` zcNNXk|4D6WCTA|8r}RdA;8xi5GE;+18<|eWdRpAH9bJl{P4{HRuIU*wRkWl1*Tra^ znJY4B&-F3-#=J>n)6?ynXn^QQL$Tbc2O94v5?$%`7*W7cM_p+=(=58tF$%GRcFekk z@sf=tj&7uY9*^`nw z#(K_1T^|bC^s42UDEd;^#!U#`OxF3?tE;hcG}b*vBC7IO?M(@4WTb=oQdENI&ITa2o}=! zHq|3mNC}J$Q zsET&SeJDne^+=Bxo{sxO6w?b#u}}JFs^r2ZYRiRl$e*%6A5D{O%1(JoA4_wu$}FKR zF`}B3m3j$1eO0WKmf5kjs4JyK)>XlARA4tffg#3WUV?n_GEGwJc)|iY9jeo#P=l(W zDk^?5TGtfCD!(yaptb=Xo%X{v=p;wavsyAiHTpSqa@5P(3@Rxds!D4e&_X z%Kob-R05B>Y`lLzmGpCEpK!YaTpVi=(?<3mbEs}Rus*AOLu`OwP>W~r3rYWzbA*RK z&O@I4k$ppo>UMSXtsi(2r~@6MI&Hr=1+}7d0jDSlQHQn8#hT0-W=&_!V$Fq?Qyw%* zz1b5H?}uUn-(UOQAE@xeG7XP+v{OR#8%GPlXT9)7cQ64 z=N$e@h3OpImpHXRs&>PrDhq4ht781y6%o^<8hE}uicfJfPZ^D(cC0z9H$daLv)|TU zo{)^EUY;~r2v4Srh!LK1!oqxUDik%I;c%_Qe?Jki7FsA8Jfmfy*y9-|OHnWZ(NoBb z2$76@@+OZ%Bemx#B(?E!k=kNKL|XDfIYV?wUXA>o$i$a%GS&l8#P&G0?@Cq|b3e4V zMUTSOn-+2484kR{`lsa2&|+)ye(A#SQJRGten?jFBM7r6i`7MHtK}lK->Vg2?{S$$ zYLRWh(YDmZE46Iu>-B5%Xt1}AR*6TvVQrOI;%yDJmU=C?Uh!sWZ;C%7Tr0MF+avIS zw=493w-+?k*AJ`M^}b@XPzz|S=;6CV%ObTu$l~qA5z*f_5t+k$leHNb_#v$Yjq^PW z&jjCmR8{y?)g!*euow9r7g_X;IY6L~<$DH=)F$MV*y6M3g80N2rOrI}8+q>eJohR( z!!5q^oz!ZD(|-=?@vEla`7XlV?j^legy8AS1H1U4s40#WE`CfA5paAaJ5!q9)H_qQ zKVGlGw5&Q@!E7YeV#sd#8XhU#_8ue}Enl<^J({vQ#%& zW$u?@`MCerdRVG$NLZ@v$4+|Qf3Kd)-Dc2A|8Ml&uq*X+sXk>{&?uEkcfhi!P$mYp zi*mR&>k*j|*sABrUj@pr_GkMK>S0n3C5zN{tB5_lWkJBgb&m)f*K<*|UCfXZ1E=&^ z++l`%I&fZ}Cr<>v)2n1c&@`%KS`Zr<*)Eu1ERT&M`UQPPM2-qJGpvZL2&NmWWVt`X z7>zk@)(6tUU=L$F&wV^@=B==wM~fo%6mhgxyc8@lYQ?L;V(9i@DYP+I4m}pEFm}rL zP$TV@nV|=aBO*6cY3!DLLUW9LvM}@n1|A!F(l~^v)YBnLE(p~cr(|u&q9bxcXqjFUR~s(vL`XgFV?pfT;9rlZ2ycQWgr9`wgx@jJwft}+?15o* z#S_CvjV!Gyd=mO>_?#i+OW``Il515I-zArf88YFIa0#AEo>o`=P=`;gwb}+Oy?@a* zLf_XmL-%X%Lce7Hd2I)5U02a&teLvXxq2JF?&P0)dc8nKjhS|2_wU|qrbChG^RjjWeg{iZ5g6{jY&K2A;1w%4U}93-rc zNru;yN%G(FqKwz}X@|70v`_WJdPk$c$QK&*OcMB&%b?qE?j;TxFQ*z1MF zxi`(zCFzyu{)0nHQcLZX1M41x-sEZq!%eNwOReNr4*YyneX&zd@697~`c9rSsWdu% zXhms7`Q&KMy6&C7O#CT-df$&f_6+;iut@{E>x zC_AkxJ)rICA!=T3DGF*5TZw5c7vn;jj&6Mg9lcUQi))s*5-*4|HN8_s8?mdVELBX^ z?x3`qzo&|pZEnVfF^BrnWc->jiApI-+WzcfWK? zMU!69q_0>ay&I~Qb`LRil3*?gaJzt3ubt$^>hO+XRb5)zsSw#9y#KslT*i%Fv#JY?c z7t6(QQnk_X9_w4$O>RmRLE)zE%$}f0y~S=aX(Z^Tr#NzuBbrq4yO=f1R_4D!H}x~y znI`3!n=zhcj=^}Isp{`xZbIComzcY_ri0~3a+q|tLrpm5IN)$ohEr{2HS_fZH6D-{ zO?1;3<`vB4%|vH^qFa=2GUp%r|^$D|?y$WVSH9epT6#xx&B5 z@1{q2{DMDdiDY`sui6a?WCh$bC6Eu^6;Kl%X1vGCVm zk4RP^nXZoHMv|$Fc^$J7bkk#zp!xzl-ixtGCn9S3U{r0oXH;G9D?xnYXmzr>Bh^i- zsE!_rSk!|IR-f?D00dnmsS~+jIc$b)quCrVfr&+!mqZCL4V|VS=Y@4+D`WG*)Dm-1 z8N`2atAIr*jRs;B4=p2$(55?$q_rr15J{p8x{@b8%}ZoZ37UH7*?_9dq#10XptAF+ zj4hL`C(UMC&6Y*iQej1&@RLSE^j+X+=4HD2JzS-K0G8+>}DQe zO0zv$yziI`?sv=se{d`Wy-vjooEDhvTmg=8sq9k;P2lDPKfa8r2LnrVlZFHz04D_3 zf`1BbJQw*aY17$=WfyG=Zq{+use-E0N<@3UJ>oV8W~vl&RcUvKi*TLK+8QwwSFs>X z`F7{?A{TeT1lyWK3|3-Wv&bhORoZq!6vI}-aQ(MCQ{+VQQ@x$+EK6Y9ZR;neQ-I#F zEmxMo_Qs8hWI1lEqjs)T=EAa=p=o$#etBlfqkdHSc+ zm2TzA`{;JP89%s3Y`d4O$+johj>mgi+_W2=iK}hbsNW&J(-O0j=tc+5kJ~!4m*_!z zE{xk@^I~xU-G9+p8zXvC5gvEyf_`rni~e+F+(>uSQ-3ODtEBTBS0H!L&RP9g&)Ayh zn2cnfUh$@Rjt2ap7;IY+Y!FSfZ5nJa-DlezM>!3lwdZWI7(ySiRnkKA97-v@<2~nK zUJiwAd%$wc6vHWM>yt>1paHhM2pdV4v#FDRyBI}}#B74*rK-m z=q$s$K5S~++r%YQ5I1MOE0@O5Rkr=n6|+ipj)kbbgl4RVE) z)K{IS#m*OyyNue|wgt^+|M{ZO-7m`ea(trm}=yi5txg zKB||{{cUnnX|0`Gj(JmQh1DiGjYio`-wxibPs6_Onvf{d@T5*8JV0lFI?V+QssvS5 z{ARZ9Y!*-XClUqbZb3w+w=g#76V5g>tyVt#9#3)>k9sWT1SO?_8np*?O5?byk@mP7 z*IdByU-#_C@uAEST$yL#pwYOtA&yJ~b(;07_q|wY6PuER#GFVQQHmW z@vPQF+<(7S^n24@<91CvIo=}9-5lTVP~A4;`K;a>6x%NOn`7($Df+LPeZ2euUh=bF zjr~EjaNBI{FCs~lMTe+PJ0OmMRym!*p`3)M!?VuC^fIH&bY>>A7g$CE!E(yxNK8C0 znng@Bi6bH#eOfWTB;_PH5zMC9Dbpz?Zc6zc<3ICw1;#P4j%ylHPFnbb%hTv3PQR|w z^gfS2V4--zGf5VT2G2A(6%%G4`wl8&;$u}$M-eY1 z_0CgB>cz`N>J=*{&Uo&T3xwCZ5%ul8sHgM1cpiu>9$(DkT(9~vMPRl?QxVFhYdCQ; zCl)hnydR>)I`0AL!t&)*i4%TER`Df>awL=KBK4}}BK5vkE57v}m6@a-vZ-9HnRz5Lf{3$XAaEtM|z--bwze<@}a_|>c_ z{#6)X<6kB+>99FQppWIRLnHMPa!fqnx9F7E0sbIgRmpAL+H?1R{NTVHaN%wZ+N8#g{22bjOFXFP-t5dS7w|67?!<%c{pWn1iBP zEIB~m#0$s79LFe(y@BbpiBbdV`sFze$W7EOFh-Aw5rIl?P;0&J4`bqf)(w zU<~f01%d1JUfgW~RR(U--@~{ARCzdPSyU*G1b2usgkIKTQVYGI50uf+ zbUgc=frC1(G%iXesn@Myj%3R&Aqyw7Pw1%L3$u2J1#)=km|n>p7RVbz-|2VCw?jYb zRk9^y8ddU42rn{Hgp-VQ@l{0Uu-}Nue&Ke86_W+wbYp`Y5y&tmVUI8CV`+AHuu;l; zFU85c6ypWqaxAZQUCh~9aa*|9s1-}Y6T$W2so=J78Mq^Sqp?$d8E&Tcq#n7&Xc0+~ z3gbQ5F|yd$FMCGr!oq_hD~&_gN&_9TWLc!vI3{nx{|UFqs>oX7lzbpEorJbA@)$}t zqx6(?1~wWl?d^!ViFSnX28Z}xk^MNb6>LFGOuC}48R=SDv>D?bQT2_7M~@hp+O^SE za9;GJA>?h*da9DkRTh6s&KL`1(kg@m?`5D?SG_1}pS4zd7LVQrZ43CK_A>aE_Byzi zluuPyHw3+*K-)Jc5Vd+ z8EVCRW@!!|7BW{dA7^f5?q(igN>fd%Nl+VlI6-aE9ybPi=Lsu0<&f8uDe@b6S|)1y zwL{vM+CKfT-rE>uc7(4vEjkk-%d1q z^X^UE|L|Oou(;9mt;N3up8dR}#%WdQAxA?zTC<~r7*(Am(`u4Cikmx(#a7RLT5|*) z`FSdhty$MmJS6ICvO9?`VoA;PPGYV$gpzCi(MfcuNlg>ES}yGBG|}z3`ZO_4s~Ofs XjCN%SJV1zc4SvqsReUX~vK;>bA^r0( From 4bad33487569bb6c79d7b4d20d154221ed47ee8b Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 11 Apr 2023 12:01:25 +1000 Subject: [PATCH 746/870] Update version --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 442c35171..612d9d732 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.43" +#define VERSION "4.2.44" +// 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe // 4.2.43 - Fix STM32 set right port mode bits for analog // 4.2.42 - Added EXRAIL TURNOUTL Macro definition // 4.2.41 - Move HAl startup to ASAP in setup() From 16e44eb11a3d8bdfe3923d9aefed12750bff744d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 11 Apr 2023 12:13:47 +0200 Subject: [PATCH 747/870] Check for max 16 analog channels --- DCCTimerSTM32.cpp | 10 ++++++---- GITHUB_SHA.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 5660be1c3..fcbf286d5 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -286,13 +286,15 @@ int ADCee::init(uint8_t pin) { while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete value = ADC1->DR; // Read value from register - if (analogvals == NULL) - { + uint8_t id = pin - PNUM_ANALOG_BASE; + if (id > 15) { // today we have not enough bits in the mask to support more + return -1021; + } + + if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init. analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t)); } - - uint8_t id = pin - PNUM_ANALOG_BASE; analogvals[id] = value; // Store sampled value analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index fbecd70e6..0ac9c5fac 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304092344Z" +#define GITHUB_SHA "devel-202304111013Z" From 294b9693c5360a485daf26baad1011004eca4379 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 12 Apr 2023 12:07:08 +0100 Subject: [PATCH 748/870] Additions to FastClock Added ONCLOCKMINS to fastclock to allow hourly repeated events. --- EXRAIL2.cpp | 5 ++++- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 52d10e8f2..31a0de628 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1129,7 +1129,10 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) { // Hunt for an ONTIME for this time if (Diag::CMD) DIAG(F("Looking for clock event at : %d"), clocktime); - if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime); + if (change) { + handleEvent(F("CLOCK"),onClockLookup,clocktime); + handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60); + } } void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) { diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 181fb5d33..6ea195e25 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -91,6 +91,7 @@ #undef ONCLOSE #undef ONTIME #undef ONCLOCKTIME +#undef ONCLOCKMINS #undef ONGREEN #undef ONRED #undef ONTHROW @@ -209,6 +210,7 @@ #define ONAMBER(signal_id) #define ONTIME(value) #define ONCLOCKTIME(hours,mins) +#define ONCLOCKMINS(mins) #define ONDEACTIVATE(addr,subaddr) #define ONDEACTIVATEL(linear) #define ONCLOSE(turnout_id) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 3aa46318e..446b38885 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -316,6 +316,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id), #define ONTIME(value) OPCODE_ONTIME,V(value), #define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)), +#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins) #define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr), #define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3), #define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id), From ff53b9003491d277241b5c9bb8a5baeb8167aaaf Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 12 Apr 2023 12:11:43 +0100 Subject: [PATCH 749/870] Update version.h Added ONCLOCKMINS for FastClock --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 612d9d732..15c6d0b09 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.44" +#define VERSION "4.2.45" +// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events // 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe // 4.2.43 - Fix STM32 set right port mode bits for analog // 4.2.42 - Added EXRAIL TURNOUTL Macro definition From 75f274e3b75f4975d8968c7bb621e780b1902fa7 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 13 Apr 2023 15:27:22 +0800 Subject: [PATCH 750/870] STM32 unsupported board selection error reporting --- DCCTimerSTM32.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index fcbf286d5..4f9cbddec 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -1,8 +1,8 @@ /* * © 2023 Neil McKechnie - * © 2022 Paul M. Antoine + * © 2022-23 Paul M. Antoine * © 2021 Mike S - * © 2021 Harald Barth + * © 2021, 2023 Harald Barth * © 2021 Fred Decker * © 2021 Chris Harlow * © 2021 David Cutting @@ -43,11 +43,18 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. +// NB: USART3 and USART6 are available but as yet undefined #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE +// Serial2 is defined to use USART2 by default, but is in fact used as the diag console +// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. +// NB: +// On all of the above, USART3, and USART6 are available but as yet undefined +// On F446ZE and F429ZI, UART4, UART5 are also available but as yet undefined +// On F429ZI, UART7 and UART8 are also available but as yet undefined #else -#warning Serial1 not defined +#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined #endif /////////////////////////////////////////////////////////////////////////////////////////////// @@ -227,6 +234,9 @@ void DCCTimer::reset() { #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS // TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs! +#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing! +#endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; uint32_t * analogchans = NULL; From d2cc60812d743abcdc90542f37ca0b55e1533393 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sun, 16 Apr 2023 15:40:27 +0800 Subject: [PATCH 751/870] Merge ESP32-fixes into DCCTimerESP --- DCCTimer.h | 7 ++++-- DCCTimerESP.cpp | 39 +++++++++++++++++++++++++++++++ ESP32-fixes.cpp | 61 ------------------------------------------------- ESP32-fixes.h | 26 --------------------- MotorDriver.cpp | 12 ++++------ 5 files changed, 48 insertions(+), 97 deletions(-) delete mode 100644 ESP32-fixes.cpp delete mode 100644 ESP32-fixes.h diff --git a/DCCTimer.h b/DCCTimer.h index 221485188..7a9d94072 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,7 +1,7 @@ /* - * © 2022 Paul M. Antoine + * © 2022-2023 Paul M. Antoine * © 2021 Mike S - * © 2021-2022 Harald Barth + * © 2021-2023 Harald Barth * © 2021 Fred Decker * All rights reserved. * @@ -62,6 +62,9 @@ class DCCTimer { static bool isPWMPin(byte pin); static void setPWM(byte pin, bool high); static void clearPWM(); + static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency); + static void DCCEXanalogWrite(uint8_t pin, int value); + // Update low ram level. Allow for extra bytes to be specified // by estimation or inspection, that may be used by other // called subroutines. Must be called with interrupts disabled. diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index caaae9d2a..cf5978e61 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -150,6 +150,45 @@ int DCCTimer::freeMemory() { void DCCTimer::reset() { ESP.restart(); } + +#include "esp32-hal.h" +#include "soc/soc_caps.h" + + +#ifdef SOC_LEDC_SUPPORT_HS_MODE +#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1) +#else +#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM) +#endif + +static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 }; +static int cnt_channel = LEDC_CHANNELS; + +void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) { + if (pin < SOC_GPIO_PIN_COUNT) { + if (pin_to_channel[pin] != 0) { + ledcSetup(pin_to_channel[pin], frequency, 8); + } + } +} + +void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) { + if (pin < SOC_GPIO_PIN_COUNT) { + if (pin_to_channel[pin] == 0) { + if (!cnt_channel) { + log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS); + return; + } + pin_to_channel[pin] = --cnt_channel; + ledcAttachPin(pin, cnt_channel); + ledcSetup(cnt_channel, 1000, 8); + } else { + ledcAttachPin(pin, pin_to_channel[pin]); + } + ledcWrite(pin_to_channel[pin], value); + } +} + int ADCee::init(uint8_t pin) { pinMode(pin, ANALOG); adc1_config_width(ADC_WIDTH_BIT_12); diff --git a/ESP32-fixes.cpp b/ESP32-fixes.cpp deleted file mode 100644 index e3c350cb3..000000000 --- a/ESP32-fixes.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * © 2022 Harald Barth - * All rights reserved. - * - * This file is part of CommandStation-EX - * - * 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 . - */ -#ifdef ARDUINO_ARCH_ESP32 -#include -#include "ESP32-fixes.h" - -#include "esp32-hal.h" -#include "soc/soc_caps.h" - - -#ifdef SOC_LEDC_SUPPORT_HS_MODE -#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1) -#else -#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM) -#endif - -static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 }; -static int cnt_channel = LEDC_CHANNELS; - -void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) { - if (pin < SOC_GPIO_PIN_COUNT) { - if (pin_to_channel[pin] != 0) { - ledcSetup(pin_to_channel[pin], frequency, 8); - } - } -} - -void DCCEXanalogWrite(uint8_t pin, int value) { - if (pin < SOC_GPIO_PIN_COUNT) { - if (pin_to_channel[pin] == 0) { - if (!cnt_channel) { - log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS); - return; - } - pin_to_channel[pin] = --cnt_channel; - ledcAttachPin(pin, cnt_channel); - ledcSetup(cnt_channel, 1000, 8); - } else { - ledcAttachPin(pin, pin_to_channel[pin]); - } - ledcWrite(pin_to_channel[pin], value); - } -} -#endif diff --git a/ESP32-fixes.h b/ESP32-fixes.h deleted file mode 100644 index 0fd4a7f0a..000000000 --- a/ESP32-fixes.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * © 2022 Harald Barth - * All rights reserved. - * - * This file is part of CommandStation-EX - * - * 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 . - */ -#ifdef ARDUINO_ARCH_ESP32 -#pragma once -#include -void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency); -void DCCEXanalogWrite(uint8_t pin, int value); -#endif - diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 28ecba678..9f39123de 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -1,8 +1,8 @@ /* - * © 2022 Paul M Antoine + * © 2022-2023 Paul M Antoine * © 2021 Mike S * © 2021 Fred Decker - * © 2020-2022 Harald Barth + * © 2020-2023 Harald Barth * © 2020-2021 Chris Harlow * All rights reserved. * @@ -27,10 +27,6 @@ #include "DCCTimer.h" #include "DIAG.h" -#if defined(ARDUINO_ARCH_ESP32) -#include "ESP32-fixes.h" -#endif - bool MotorDriver::commonFaultPin=false; volatile portreg_t shadowPORTA; @@ -286,7 +282,7 @@ void MotorDriver::setDCSignal(byte speedcode) { f = taurustones[ (tSpeed-2)/2 ] ; } } - DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup + DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup } #endif if (tSpeed <= 1) brake = 255; @@ -295,7 +291,7 @@ void MotorDriver::setDCSignal(byte speedcode) { if (invertBrake) brake=255-brake; #if defined(ARDUINO_ARCH_ESP32) - DCCEXanalogWrite(brakePin,brake); + DCCTimer::DCCEXanalogWrite(brakePin,brake); #else analogWrite(brakePin,brake); #endif From f465020e93d271aa8027003fa9febe253b703298 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 17 Apr 2023 23:40:48 +0200 Subject: [PATCH 752/870] Support boards with inverted fault pin --- GITHUB_SHA.h | 2 +- MotorDrivers.h | 14 +++++++++++--- version.h | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 0ac9c5fac..f3e8efdbb 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304111013Z" +#define GITHUB_SHA "devel-202304172140Z" diff --git a/MotorDrivers.h b/MotorDrivers.h index cc13f5494..a355d26f3 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -76,9 +76,13 @@ // analog inputs. Here we use analog inputs A2 and A3 as A0 and A1 are wired in a way so that // they are not useable at the same time as WiFi (what a bummer). The numbers below are the // actual GPIO numbers. In comments the numbers the pins have on an Uno. -#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN) +#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \ + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN) + +#define EX8874_SHIELD F("EX8874"),\ + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.17, 5000, -36 /*-A4*/), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.17, 5000, -39 /*-A5*/) #else // STANDARD shield on any Arduino Uno or Mega compatible with the original specification. @@ -116,6 +120,10 @@ new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \ new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12) +#define EX8874_SHIELD F("EX8874"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, -A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, -A5) + // Firebox Mk1 #define FIREBOX_MK1 F("FIREBOX_MK1"), \ new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ diff --git a/version.h b/version.h index 15c6d0b09..385c5f2b1 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.45" +#define VERSION "4.2.46" +// 4.2.46 - Support boards with inverted fault pin // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events // 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe // 4.2.43 - Fix STM32 set right port mode bits for analog From 70fae16ab35077cf6f75da9b72cf67bc0a6fa1e5 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Wed, 19 Apr 2023 11:18:47 +0100 Subject: [PATCH 753/870] Correct response to --- EXRAIL2.cpp | 7 ++++--- version.h | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 31a0de628..374d54655 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -265,16 +265,17 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) { char RMFT2::getRouteType(int16_t id) { for (int16_t i=0;;i+=2) { int16_t rid= GETHIGHFLASHW(routeIdList,i); - if (rid==id) return 'R'; if (rid==0) break; + if (rid==id) return 'R'; } for (int16_t i=0;;i+=2) { int16_t rid= GETHIGHFLASHW(automationIdList,i); - if (rid==id) return 'A'; if (rid==0) break; + if (rid==id) return 'A'; } return 'X'; -} +} + // This filter intercepts <> commands to do the following: // - Implement RMFT specific commands/diagnostics // - Reject/modify JMRI commands that would interfere with RMFT processing diff --git a/version.h b/version.h index 385c5f2b1..f168477e1 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.46" +#define VERSION "4.2.47" +// 4.2.47 - Correct response to // 4.2.46 - Support boards with inverted fault pin // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events // 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe From d857c4f2e41ef4a219a17c66b0f1cc83075708a7 Mon Sep 17 00:00:00 2001 From: Colin Murdoch Date: Wed, 19 Apr 2023 19:45:40 +0100 Subject: [PATCH 754/870] Added to Copyright notice Added my name to copyright notice --- CommandDistributor.cpp | 1 + CommandDistributor.h | 2 ++ DCCEXParser.cpp | 1 + EXRAIL2.cpp | 1 + EXRAIL2.h | 1 + EXRAIL2MacroReset.h | 1 + EXRAILMacros.h | 1 + 7 files changed, 8 insertions(+) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 1714b735d..4b4159d09 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -2,6 +2,7 @@ * © 2022 Harald Barth * © 2020-2021 Chris Harlow * © 2020 Gregor Baues + * © 2022 Colin Murdoch * All rights reserved. * * This file is part of CommandStation-EX diff --git a/CommandDistributor.h b/CommandDistributor.h index e1dbfeca6..cad6271a0 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -2,6 +2,8 @@ * © 2022 Harald Barth * © 2020-2021 Chris Harlow * © 2020 Gregor Baues + * © 2022 Colin Murdoch + * * All rights reserved. * * This file is part of CommandStation-EX diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 102d27158..07c679b8b 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -7,6 +7,7 @@ * © 2020-2021 M Steve Todd * © 2020-2021 Fred Decker * © 2020-2021 Chris Harlow + * © 2022 Colin Murdoch * All rights reserved. * * This file is part of CommandStation-EX diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 31a0de628..dbcab7132 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -2,6 +2,7 @@ * © 2021 Neil McKechnie * © 2021-2023 Harald Barth * © 2020-2023 Chris Harlow + * © 2022 Colin Murdoch * All rights reserved. * * This file is part of CommandStation-EX diff --git a/EXRAIL2.h b/EXRAIL2.h index 2c4ad967c..e3d90076e 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -1,6 +1,7 @@ /* * © 2021 Neil McKechnie * © 2020-2022 Chris Harlow + * © 2022 Colin Murdoch * © 2023 Harald Barth * All rights reserved. * diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 6ea195e25..5c047dade 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -1,5 +1,6 @@ /* * © 2020-2022 Chris Harlow. All rights reserved. + * © 2022 Colin Murdoch * © 2023 Harald Barth * * This file is part of CommandStation-EX diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 446b38885..633ce5ac1 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -1,6 +1,7 @@ /* * © 2021 Neil McKechnie * © 2020-2022 Chris Harlow + * © 2022 Colin Murdoch * © 2023 Harald Barth * All rights reserved. * From b6f8889e8c958deda4bb4201d375730fb9632489 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 20 Apr 2023 07:08:11 +1000 Subject: [PATCH 755/870] Disable most programming functions --- DCCEXParser.cpp | 6 ++ EXRAIL2.cpp | 6 +- EXRAILMacros.h | 2 +- config.example.h | 13 +++- config.h.txt | 169 ----------------------------------------------- 5 files changed, 23 insertions(+), 173 deletions(-) delete mode 100644 config.h.txt diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 07c679b8b..728b157e4 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -369,6 +369,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; break; +#ifndef DISABLE_PROG case 'w': // WRITE CV on MAIN DCC::writeCVByteMain(p[0], p[1], p[2]); return; @@ -376,6 +377,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) case 'b': // WRITE CV BIT ON MAIN DCC::writeCVBitMain(p[0], p[1], p[2], p[3]); return; +#endif case 'M': // WRITE TRANSPARENT DCC PACKET MAIN case 'P': // WRITE TRANSPARENT DCC PACKET PROG

@@ -393,6 +395,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) } return; +#ifndef DISABLE_PROG case 'W': // WRITE CV ON PROG if (!stashCallback(stream, p, ringStream)) break; @@ -427,7 +430,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::writeCVBit(p[0], p[1], p[2], callback_B); return; +#endif case 'R': // READ CV ON PROG +#ifndef DISABLE_PROG if (params == 1) { // -- uses verify callback if (!stashCallback(stream, p, ringStream)) @@ -442,6 +447,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::readCV(p[0], callback_R); return; } +#endif if (params == 0) { // New read loco id if (!stashCallback(stream, p, ringStream)) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 5b6986b95..d273b9ccd 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -704,11 +704,13 @@ void RMFT2::loop2() { DCC::setThrottle(0,1,true); // pause all locos on the track pausingTask=this; break; - + +#ifndef DISABLE_PROG case OPCODE_POM: if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1)); break; - +#endif + case OPCODE_POWEROFF: TrackManager::setPower(POWERMODE::OFF); TrackManager::setJoin(false); diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 633ce5ac1..c6846c0fa 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -325,7 +325,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id), #define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define PAUSE OPCODE_PAUSE,0,0, -#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), +#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), #define POWEROFF OPCODE_POWEROFF,0,0, #define POWERON OPCODE_POWERON,0,0, diff --git a/config.example.h b/config.example.h index 2a8a11acf..728c1dd14 100644 --- a/config.example.h +++ b/config.example.h @@ -141,7 +141,7 @@ The configuration file for DCC-EX Command Station // // If you do not need the EEPROM at all, you can disable all the code that saves // data in the EEPROM. You might want to do that if you are in a Arduino UNO -// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM +// and want to use the EXRAIL automation. Otherwise you do not have enough RAM // to do that. Of course, then none of the EEPROM related commands work. // // EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled, @@ -149,6 +149,17 @@ The configuration file for DCC-EX Command Station // // #define DISABLE_EEPROM +///////////////////////////////////////////////////////////////////////////////////// +// DISABLE PROG +// +// If you do not need programming capability, you can disable all programming related +// commands. You might want to do that if you are using an Arduino UNO and still want +// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both. +// +// Note this disables all programming functionality, including EXRAIL. +// +// #define DISABLE_PROG + ///////////////////////////////////////////////////////////////////////////////////// // REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address // is 127 and the first long address is 128. There are manufacturers which have diff --git a/config.h.txt b/config.h.txt deleted file mode 100644 index 98cc3fc53..000000000 --- a/config.h.txt +++ /dev/null @@ -1,169 +0,0 @@ -/********************************************************************** - -Config.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman -COPYRIGHT (c) 2020 Fred Decker - -The configuration file for DCC++ EX Command Station - -**********************************************************************/ -///////////////////////////////////////////////////////////////////////////////////// -// NOTE: Before connecting these boards and selecting one in this software -// check the quick install guides!!! Some of these boards require a voltage -// generating resitor on the current sense pin of the device. Failure to select -// the correct resistor could damage the sense pin on your Arduino or destroy -// the device. -// -// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: -// -// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel -// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) -// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) -// FIREBOX_MK1 : The Firebox MK1 -// FIREBOX_MK1S : The Firebox MK1S -// | -// +-----------------------v -// -// #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), -// new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), -// new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN) - -#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD - -///////////////////////////////////////////////////////////////////////////////////// -// -// The IP port to talk to a WIFI or Ethernet shield. -// -#define IP_PORT 2560 - -///////////////////////////////////////////////////////////////////////////////////// -// -// NOTE: Only supported on Arduino Mega -// Set to false if you not even want it on the Arduino Mega -// -//#define ENABLE_WIFI true - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE WiFi Parameters (only in effect if WIFI is on) -// -// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with -// the <+> commands and this sketch will not change anything over -// AT commands and the other WIFI_* defines below do not have any effect. -//#define DONT_TOUCH_WIFI_CONF -// -// WIFI_SSID is the network name IF you want to use your existing home network. -// Do NOT change this if you want to use the WiFi in Access Point (AP) mode. -// -// If you do NOT set the WIFI_SSID, the WiFi chip will first try -// to connect to the previously configured network and if that fails -// fall back to Access Point mode. The SSID of the AP will be -// automatically set to DCCEX_*. -// -// Your SSID may not conain ``"'' (double quote, ASCII 0x22). -#define WIFI_SSID "Your network name" -// -// WIFI_PASSWORD is the network password for your home network or if -// you want to change the password from default AP mode password -// to the AP password you want. -// Your password may not conain ``"'' (double quote, ASCII 0x22). -#define WIFI_PASSWORD "deadcafe" -// -// WIFI_HOSTNAME: You probably don't need to change this -#define WIFI_HOSTNAME "dccex" -// -///////////////////////////////////////////////////////////////////////////////////// -// -// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need -// to set this if you have an extremely slow Wifi router. -// -#define WIFI_CONNECT_TIMEOUT 14000 - -///////////////////////////////////////////////////////////////////////////////////// -// -// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This -// is not for Wifi. You will then need the Arduino Ethernet library as well -// -//#define ENABLE_ETHERNET true - - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP -// -//#define IP_ADDRESS { 192, 168, 1, 31 } - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE -// -// Uncomment to use with Ethernet Shields -// -// Ethernet Shields do not have have a MAC address in hardware. There may be one on -// a sticker on the Shield that you should use. Otherwise choose one of the ones below -// Be certain that no other device on your network has this same MAC address! -// -// 52:b8:8a:8e:ce:21 -// e3:e9:73:e1:db:0d -// 54:2b:13:52:ac:0c - -// NOTE: This is not used with ESP8266 WiFi modules. - -//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above - -// -// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } - -///////////////////////////////////////////////////////////////////////////////////// -// -// 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', -// OR an I2C Oled screen based on SSD1306 (128x64 or 128x32) controller, -// OR an I2C Oled screen based on SH1106 (132x64) controller. -// To enable, uncomment one of the lines below - -// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows -//#define LCD_DRIVER {SubBus_4,0x27},20,4 - -//OR define OLED_DRIVER width,height in pixels (address auto detected) -#if defined(ARDUINO_ARCH_STM32) -#define OLED_DRIVER 0x3c, 128, 64 -#else -#define OLED_DRIVER {SubBus_0,0x3c}, 128, 32 -#endif - -#define SCROLLMODE 1 - -///////////////////////////////////////////////////////////////////////////////////// -// DISABLE EEPROM -// -// If you do not need the EEPROM at all, you can disable all the code that saves -// data in the EEPROM. You might want to do that if you are in a Arduino UNO -// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM -// to do that. Of course, then none of the EEPROM related commands work. -// -#define DISABLE_EEPROM - - -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213 -// -// According to norm RCN-213 a DCC packet with a 1 is closed/straight -// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous -// versions of DCC++EX, a turnout throw command was implemented in the packet as -// '1' and a close command as '0'. The #define below makes the states -// match with the norm. But we don't want to cause havoc on existent layouts, -// so we define this only for new installations. If you don't want this, -// don't add it to your config.h. -//#define DCC_TURNOUTS_RCN_213 - -// The following #define likewise inverts the behaviour of the command -// for triggering DCC Accessory Decoders, so that generates a -// DCC packet with D=1 (close turnout) and generates D=0 -// (throw turnout). -//#define DCC_ACCESSORY_RCN_213 - -///////////////////////////////////////////////////////////////////////////////////// From 7addb13785199f50416a858d99046185978a85d0 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 20 Apr 2023 07:21:32 +1000 Subject: [PATCH 756/870] Disable completely --- DCCEXParser.cpp | 4 +--- version.h | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 728b157e4..0335438eb 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -430,9 +430,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::writeCVBit(p[0], p[1], p[2], callback_B); return; -#endif case 'R': // READ CV ON PROG -#ifndef DISABLE_PROG if (params == 1) { // -- uses verify callback if (!stashCallback(stream, p, ringStream)) @@ -447,7 +445,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) DCC::readCV(p[0], callback_R); return; } -#endif if (params == 0) { // New read loco id if (!stashCallback(stream, p, ringStream)) @@ -456,6 +453,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; } break; +#endif case '1': // POWERON <1 [MAIN|PROG|JOIN]> { diff --git a/version.h b/version.h index f168477e1..c44797326 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.47" +#define VERSION "4.2.48" +// 4.2.48 - Add DISABLE_PROG to disable programming to save RAM/Flash +// - Disables all DCC-EX prog commands and EXRAIL POM, leaves IFLOCO() // 4.2.47 - Correct response to // 4.2.46 - Support boards with inverted fault pin // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events From e780c40b34f41a68d597edce53f3043cabb13b89 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:16:32 +1000 Subject: [PATCH 757/870] Disable POM OPCODE --- EXRAIL2.h | 5 ++++- EXRAIL2MacroReset.h | 4 ++++ EXRAILMacros.h | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/EXRAIL2.h b/EXRAIL2.h index e3d90076e..785267ab6 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -45,7 +45,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE, OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR, OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN, - OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM, + OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2, +#ifndef DISABLE_PROG + OPCODE_POM, +#endif OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET, OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 5c047dade..17a328e0b 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -101,7 +101,9 @@ #undef PAUSE #undef PIN_TURNOUT #undef PRINT +#ifndef DISABLE_PROG #undef POM +#endif #undef POWEROFF #undef POWERON #undef READ_LOCO @@ -223,7 +225,9 @@ #define PIN_TURNOUT(id,pin,description...) #define PRINT(msg) #define PARSE(msg) +#ifndef DISABLE_PROG #define POM(cv,value) +#endif #define POWEROFF #define POWERON #define READ_LOCO diff --git a/EXRAILMacros.h b/EXRAILMacros.h index c6846c0fa..a67f86c26 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -326,7 +326,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id), #define PAUSE OPCODE_PAUSE,0,0, #define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin), +#ifndef DISABLE_PROG #define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value), +#endif #define POWEROFF OPCODE_POWEROFF,0,0, #define POWERON OPCODE_POWERON,0,0, #define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2), From 2d1e695ac73e796e908b2b43b268242cf14dca56 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:26:17 +1000 Subject: [PATCH 758/870] Disable --- DCCEXParser.cpp | 2 ++ version.h | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 0335438eb..b07fe6696 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -897,6 +897,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory()); break; +#ifndef DISABLE_PROG case HASH_KEYWORD_ACK: // if (params >= 3) { if (p[1] == HASH_KEYWORD_LIMIT) { @@ -917,6 +918,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) Diag::ACK = onOff; } return true; +#endif case HASH_KEYWORD_CMD: // Diag::CMD = onOff; diff --git a/version.h b/version.h index c44797326..d043e0db9 100644 --- a/version.h +++ b/version.h @@ -6,7 +6,6 @@ #define VERSION "4.2.48" // 4.2.48 - Add DISABLE_PROG to disable programming to save RAM/Flash -// - Disables all DCC-EX prog commands and EXRAIL POM, leaves IFLOCO() // 4.2.47 - Correct response to // 4.2.46 - Support boards with inverted fault pin // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events From 72d131035e777cb6b73d68c6f0028b6fdf3bd440 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 23 Apr 2023 20:24:29 +0200 Subject: [PATCH 759/870] disable more PROG stuff (all hash keywords PROG etc) --- DCCACK.cpp | 4 ++-- DCCEXParser.cpp | 14 +++++++++++--- GITHUB_SHA.h | 2 +- TrackManager.cpp | 21 ++++++++++++++++++--- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/DCCACK.cpp b/DCCACK.cpp index 8c5e8c71e..1b339eede 100644 --- a/DCCACK.cpp +++ b/DCCACK.cpp @@ -152,7 +152,7 @@ byte DCCACK::getAck() { return(0); // pending set off but not detected means no ACK. } - +#ifndef DISABLE_PROG void DCCACK::loop() { while (ackManagerProg) { byte opcode=GETFLASH(ackManagerProg); @@ -414,7 +414,7 @@ void DCCACK::callback(int value) { (ackManagerCallback)( value); } } - +#endif void DCCACK::checkAck(byte sentResetsSincePacket) { if (!ackPending) return; diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index b07fe6696..0a496d5b5 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -54,7 +54,6 @@ // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command -const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_JOIN = -30750; const int16_t HASH_KEYWORD_CABS = -11981; @@ -64,7 +63,10 @@ const int16_t HASH_KEYWORD_ACK = 3113; const int16_t HASH_KEYWORD_ON = 2657; const int16_t HASH_KEYWORD_DCC = 6436; const int16_t HASH_KEYWORD_SLOW = -17209; +#ifndef DISABLE_PROG +const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_PROGBOOST = -6353; +#endif #ifndef DISABLE_EEPROM const int16_t HASH_KEYWORD_EEPROM = -7168; #endif @@ -380,7 +382,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif case 'M': // WRITE TRANSPARENT DCC PACKET MAIN +#ifndef DISABLE_PROG case 'P': // WRITE TRANSPARENT DCC PACKET PROG

+#endif // NOTE: this command was parsed in HEX instead of decimal params--; // drop REG if (params<1) break; @@ -474,9 +478,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> main=true; } +#ifndef DISABLE_PROG else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> prog=true; } +#endif else break; // will reply } if (main) TrackManager::setMainPower(POWERMODE::ON); @@ -500,9 +506,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN> main=true; } +#ifndef DISABLE_PROG else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG> prog=true; } +#endif else break; // will reply } @@ -941,11 +949,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) Diag::LCN = onOff; return true; #endif - +#ifndef DISABLE_PROG case HASH_KEYWORD_PROGBOOST: TrackManager::progTrackBoosted=true; return true; - +#endif case HASH_KEYWORD_RESET: DCCTimer::reset(); break; // and if we didnt restart diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f3e8efdbb..b530ccd72 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304172140Z" +#define GITHUB_SHA "devel-202304231822Z" diff --git a/TrackManager.cpp b/TrackManager.cpp index fcd0c10fd..9c0f0b04d 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -33,8 +33,9 @@ FOR_EACH_TRACK(t) \ if (trackMode[t]==findmode) \ track[t]->function; - +#ifndef DISABLE_PROG const int16_t HASH_KEYWORD_PROG = -29718; +#endif const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_OFF = 22479; const int16_t HASH_KEYWORD_DC = 2183; @@ -116,7 +117,11 @@ void TrackManager::Setup(const FSH * shieldname, // Default the first 2 tracks (which may be null) and perform HA waveform check. setTrackMode(0,TRACK_MODE_MAIN); +#ifndef DISABLE_PROG setTrackMode(1,TRACK_MODE_PROG); +#else + setTrackMode(1,TRACK_MODE_MAIN); +#endif // TODO Fault pin config for odd motor boards (example pololu) // MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) @@ -198,7 +203,11 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input } #endif +#ifndef DISABLE_PROG if (mode==TRACK_MODE_PROG) { +#else + if (false) { +#endif // only allow 1 track to be prog FOR_EACH_TRACK(t) if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) { @@ -306,8 +315,10 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN> return setTrackMode(p[0],TRACK_MODE_MAIN); +#ifndef DISABLE_PROG if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG> return setTrackMode(p[0],TRACK_MODE_PROG); +#endif if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF> return setTrackMode(p[0],TRACK_MODE_OFF); @@ -332,9 +343,11 @@ void TrackManager::streamTrackState(Print* stream, byte t) { case TRACK_MODE_MAIN: format=F("<= %c MAIN>\n"); break; +#ifndef DISABLE_PROG case TRACK_MODE_PROG: format=F("<= %c PROG>\n"); break; +#endif case TRACK_MODE_OFF: format=F("<= %c OFF>\n"); break; @@ -357,8 +370,10 @@ void TrackManager::streamTrackState(Print* stream, byte t) { byte TrackManager::nextCycleTrack=MAX_TRACKS; void TrackManager::loop() { - DCCWaveform::loop(); - DCCACK::loop(); + DCCWaveform::loop(); +#ifndef DISABLE_PROG + DCCACK::loop(); +#endif bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted; nextCycleTrack++; if (nextCycleTrack>lastTrack) nextCycleTrack=0; From 4eaad2d05bf15e670f67413ec9c5fbc765d687a9 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 23 Apr 2023 22:45:47 +0200 Subject: [PATCH 760/870] disable more PROG stuff (JOIN/UNJOIN from EXRAIL) --- DCCEXParser.cpp | 14 +++++++------- EXRAIL2.cpp | 20 ++++++++++---------- EXRAIL2.h | 4 ++-- GITHUB_SHA.h | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 0a496d5b5..fb9c24d55 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -3,7 +3,7 @@ * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Herb Morton - * © 2020-2022 Harald Barth + * © 2020-2023 Harald Barth * © 2020-2021 M Steve Todd * © 2020-2021 Fred Decker * © 2020-2021 Chris Harlow @@ -55,7 +55,6 @@ // These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter. // To discover new keyword numbers , use the <$ YOURKEYWORD> command const int16_t HASH_KEYWORD_MAIN = 11339; -const int16_t HASH_KEYWORD_JOIN = -30750; const int16_t HASH_KEYWORD_CABS = -11981; const int16_t HASH_KEYWORD_RAM = 25982; const int16_t HASH_KEYWORD_CMD = 9962; @@ -64,6 +63,7 @@ const int16_t HASH_KEYWORD_ON = 2657; const int16_t HASH_KEYWORD_DCC = 6436; const int16_t HASH_KEYWORD_SLOW = -17209; #ifndef DISABLE_PROG +const int16_t HASH_KEYWORD_JOIN = -30750; const int16_t HASH_KEYWORD_PROG = -29718; const int16_t HASH_KEYWORD_PROGBOOST = -6353; #endif @@ -470,15 +470,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) prog=true; } if (params==1) { - if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> + if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> main=true; - prog=true; - join=true; } - else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN> +#ifndef DISABLE_PROG + else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN> main=true; + prog=true; + join=true; } -#ifndef DISABLE_PROG else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG> prog=true; } diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index d273b9ccd..62b34180b 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -705,11 +705,9 @@ void RMFT2::loop2() { pausingTask=this; break; -#ifndef DISABLE_PROG case OPCODE_POM: if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1)); break; -#endif case OPCODE_POWEROFF: TrackManager::setPower(POWERMODE::OFF); @@ -885,23 +883,18 @@ void RMFT2::loop2() { while(loopTask) loopTask->kill(F("KILLALL")); return; +#ifndef DISABLE_PROG case OPCODE_JOIN: TrackManager::setPower(POWERMODE::ON); TrackManager::setJoin(true); CommandDistributor::broadcastPower(); break; - - case OPCODE_POWERON: - TrackManager::setMainPower(POWERMODE::ON); - TrackManager::setJoin(false); - CommandDistributor::broadcastPower(); - break; - + case OPCODE_UNJOIN: TrackManager::setJoin(false); CommandDistributor::broadcastPower(); break; - + case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet DCC::getLocoId(readLocoCallback); @@ -922,6 +915,13 @@ void RMFT2::loop2() { forward=true; invert=false; break; +#endif + + case OPCODE_POWERON: + TrackManager::setMainPower(POWERMODE::ON); + TrackManager::setJoin(false); + CommandDistributor::broadcastPower(); + break; case OPCODE_START: { diff --git a/EXRAIL2.h b/EXRAIL2.h index 785267ab6..99b080887 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -45,10 +45,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE, OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR, OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN, - OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2, #ifndef DISABLE_PROG - OPCODE_POM, + OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2, #endif + OPCODE_POM, OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET, OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON, OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT, diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index b530ccd72..f889b981e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304231822Z" +#define GITHUB_SHA "devel-202304232045Z" From cc3aba1febc310cb5d67afbef27ecd21c84e0611 Mon Sep 17 00:00:00 2001 From: stevet Date: Tue, 25 Apr 2023 16:02:42 -0400 Subject: [PATCH 761/870] Update WiThrottle.cpp Fix: turnout state should be 2/4, not T2/T4 --- WiThrottle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 60bdf7dd9..f081270d3 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -127,7 +127,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { #endif char tchar=Turnout::isClosed(id)?'2':'4'; if (tdesc==NULL) // turnout with no description - StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar); + StringFormatter::send(stream,F("]\\[%d}|{T%d}|{%c"), id,id,tchar); else StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar); } From 28d984313384a76104b6d4e13822507e0d8b1bd6 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Mon, 1 May 2023 14:25:45 +0100 Subject: [PATCH 762/870] Broadcast changes in EXRAIlr --- CommandDistributor.cpp | 7 ++----- CommandDistributor.h | 5 +++-- EXRAIL2.cpp | 5 ++++- EXRAIL2.h | 3 ++- EXRAIL2MacroReset.h | 2 ++ EXRAILMacros.h | 3 +++ 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4b4159d09..9684291f0 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -228,11 +228,8 @@ void CommandDistributor::broadcastPower() { LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason); } -void CommandDistributor::broadcastText(const FSH * msg) { - broadcastReply(COMMAND_TYPE, F("\n"),msg); -#ifdef CD_HANDLE_RING - broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg); -#endif +void CommandDistributor::broadcastRaw(clientType type, char * msg) { + broadcastReply(type, F("%s"),msg); } void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) { diff --git a/CommandDistributor.h b/CommandDistributor.h index cad6271a0..45f814776 100644 --- a/CommandDistributor.h +++ b/CommandDistributor.h @@ -35,8 +35,9 @@ #endif class CommandDistributor { -private: +public: enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; +private: static void broadcastToClients(clientType type); static StringBuffer * broadcastBufferWriter; #ifdef CD_HANDLE_RING @@ -52,7 +53,7 @@ public : static void setClockTime(int16_t time, int8_t rate, byte opt); static int16_t retClockTime(); static void broadcastPower(); - static void broadcastText(const FSH * msg); + static void broadcastRaw(clientType type,char * msg); static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr); template static void broadcastReply(clientType type, Targs... msg); static void forget(byte clientId); diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 5b6986b95..3f220552b 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -1242,7 +1242,10 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) { DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL); break; case thrunge_broadcast: - // TODO CommandDistributor::broadcastText(buffer->getString()); + CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString()); + break; + case thrunge_withrottle: + CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString()); break; case thrunge_lcd: LCD(id,F("%s"),buffer->getString()); diff --git a/EXRAIL2.h b/EXRAIL2.h index e3d90076e..78ed94975 100644 --- a/EXRAIL2.h +++ b/EXRAIL2.h @@ -77,7 +77,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE, // Ensure thrunge_lcd is put last as there may be more than one display, // sequentially numbered from thrunge_lcd. enum thrunger: byte { - thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse, + thrunge_print, thrunge_broadcast, thrunge_withrottle, + thrunge_serial,thrunge_parse, thrunge_serial1, thrunge_serial2, thrunge_serial3, thrunge_serial4, thrunge_serial5, thrunge_serial6, thrunge_lcn, diff --git a/EXRAIL2MacroReset.h b/EXRAIL2MacroReset.h index 5c047dade..71b5251ef 100644 --- a/EXRAIL2MacroReset.h +++ b/EXRAIL2MacroReset.h @@ -142,6 +142,7 @@ #undef VIRTUAL_SIGNAL #undef VIRTUAL_TURNOUT #undef WAITFOR +#undef WITHROTTLE #undef XFOFF #undef XFON @@ -264,6 +265,7 @@ #define VIRTUAL_SIGNAL(id) #define VIRTUAL_TURNOUT(id,description...) #define WAITFOR(pin) +#define WITHROTTLE(msg) #define XFOFF(cab,func) #define XFON(cab,func) #endif diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 633ce5ac1..e449fbf0b 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -153,6 +153,8 @@ const int StringMacroTracker1=__COUNTER__; lcdid=id;\ break;\ } +#undef WITHROTTLE +#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle) void RMFT2::printMessage(uint16_t id) { thrunger tmode; @@ -368,6 +370,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = { #define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id), #define VIRTUAL_SIGNAL(id) #define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0), +#define WITHROTTLE(msg) PRINT(msg) #define WAITFOR(pin) OPCODE_WAITFOR,V(pin), #define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func), #define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func), From efdbfcb03053ff88db13198520a01ecbaba80115 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 1 May 2023 20:18:32 +0200 Subject: [PATCH 763/870] Add serial output for sabertooth controller --- CommandDistributor.cpp | 30 ++++++++++++++++++++++++++++++ SerialManager.cpp | 3 +++ 2 files changed, 33 insertions(+) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 4b4159d09..f7880340e 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -205,6 +205,36 @@ int16_t CommandDistributor::retClockTime() { void CommandDistributor::broadcastLoco(byte slot) { DCC::LOCO * sp=&DCC::speedTable[slot]; broadcastReply(COMMAND_TYPE, F("\n"), sp->loco,slot,sp->speedCode,sp->functions); +#ifdef SABERTOOTH + if (Serial2 && sp->loco == SABERTOOTH) { + static uint8_t rampingmode = 0; + bool direction = (sp->speedCode & 0x80) !=0; // true for forward + int32_t speed = sp->speedCode & 0x7f; + if (speed == 1) { // emergency stop + if (rampingmode != 1) { + rampingmode = 1; + Serial2.print("R1: 0\r\n"); + Serial2.print("R2: 0\r\n"); + } + Serial2.print("MD: 0\r\n"); + } else { + if (speed != 0) { + // speed is here 2 to 127 + speed = (speed - 1) * 1625 / 100; + speed = speed * (direction ? 1 : -1); + // speed is here -2047 to 2047 + } + if (rampingmode != 2) { + rampingmode = 2; + Serial2.print("R1: 2047\r\n"); + Serial2.print("R2: 2047\r\n"); + } + Serial2.print("MD: "); + Serial2.print(speed); + Serial2.print("\r\n"); + } + } +#endif #ifdef CD_HANDLE_RING WiThrottle::markForBroadcast(sp->loco); #endif diff --git a/SerialManager.cpp b/SerialManager.cpp index 0ac567d88..bacfceb92 100644 --- a/SerialManager.cpp +++ b/SerialManager.cpp @@ -87,6 +87,9 @@ void SerialManager::init() { delay(1000); } #endif +#ifdef SABERTOOTH + Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32 +#endif } void SerialManager::broadcast(char * stringBuffer) { From a3c9800aba8e81b0689ac362e6a750f1ac2d0cb8 Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 2 May 2023 12:29:03 +0100 Subject: [PATCH 764/870] Update version.h --- version.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index f168477e1..27ec74f75 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.47" +#define VERSION "4.2.48" +// 4.2.48 - BROADCAST/WITHROTTLE Exrail macros // 4.2.47 - Correct response to // 4.2.46 - Support boards with inverted fault pin // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events From 6781e44fdd4281ffe1ca6d419f8a1ea4d7d22a8f Mon Sep 17 00:00:00 2001 From: Asbelos Date: Tue, 2 May 2023 22:02:52 +0100 Subject: [PATCH 765/870] Fix EXRAIL speed issue --- EXRAIL2.cpp | 1 + version.h | 1 + 2 files changed, 2 insertions(+) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index 3f220552b..95d608bb5 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -610,6 +610,7 @@ void RMFT2::loop2() { break; case OPCODE_SPEED: + forward=DCC::getThrottleDirection(loco)^invert; driveLoco(operand); break; diff --git a/version.h b/version.h index 27ec74f75..ba96ba824 100644 --- a/version.h +++ b/version.h @@ -5,6 +5,7 @@ #define VERSION "4.2.48" +// 4.2.49 - Exrail SPEED take notice of external direction change // 4.2.48 - BROADCAST/WITHROTTLE Exrail macros // 4.2.47 - Correct response to // 4.2.46 - Support boards with inverted fault pin From 9d953c70b8a4df5b205c75e1f48588aef8c23f87 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 2 May 2023 23:51:17 +0200 Subject: [PATCH 766/870] use M1 and M2 instead of MD for motor control --- CommandDistributor.cpp | 5 ++++- GITHUB_SHA.h | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index f7880340e..9b61f7862 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -229,7 +229,10 @@ void CommandDistributor::broadcastLoco(byte slot) { Serial2.print("R1: 2047\r\n"); Serial2.print("R2: 2047\r\n"); } - Serial2.print("MD: "); + Serial2.print("M1: "); + Serial2.print(speed); + Serial2.print("\r\n"); + Serial2.print("M2: "); Serial2.print(speed); Serial2.print("\r\n"); } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f3e8efdbb..95642098f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304172140Z" +#define GITHUB_SHA "devel-202305022149Z" From 6d802f3a736cec3efc2cc75d39a5f05ba5ae7521 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 5 May 2023 16:14:44 +0200 Subject: [PATCH 767/870] estop all locos in list, even last one --- DCC.cpp | 2 +- GITHUB_SHA.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DCC.cpp b/DCC.cpp index 36d305db8..c9535b2ef 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -693,7 +693,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) { if (loco==0) { // broadcast stop/estop but dont change direction - for (int reg = 0; reg < highestUsedReg; reg++) { + for (int reg = 0; reg <= highestUsedReg; reg++) { if (speedTable[reg].loco==0) continue; byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); if (speedTable[reg].speedCode != newspeed) { diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f3e8efdbb..5f04f3466 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202304172140Z" +#define GITHUB_SHA "devel-202305051413Z" From df7b8907580f291732904ea1bb02a555953a42fe Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 7 May 2023 23:58:47 +0200 Subject: [PATCH 768/870] No EEPROM so you do not need this --- Turnouts.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Turnouts.cpp b/Turnouts.cpp index e8f55af39..83603fcf4 100644 --- a/Turnouts.cpp +++ b/Turnouts.cpp @@ -250,6 +250,7 @@ } } tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed); + DIAG(F("Turnout 0x%x size %d size %d"), tt, sizeof(Turnout),sizeof(struct TurnoutData)); IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant); return tt; #else From 3759fc2a1a600f1618b225bf984b6f65eff48b4c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 8 May 2023 00:19:59 +0200 Subject: [PATCH 769/870] add checks for broken cab ID --- DCCEXParser.cpp | 2 ++ WiThrottle.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 07c679b8b..bc7992772 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -286,6 +286,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) if (direction < 0 || direction > 1) break; // invalid direction code + if (cab > 10239 || cab < 0) + break; // beyond DCC range DCC::setThrottle(cab, tspeed, direction); if (params == 4) // send obsolete format T response diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 69c5d87b1..aa92dc6bb 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -235,6 +235,10 @@ int WiThrottle::getLocoId(byte * cmd) { void WiThrottle::multithrottle(RingStream * stream, byte * cmd){ char throttleChar=cmd[1]; int locoid=getLocoId(cmd+3); // -1 for * + if (locoid > 10239 || locoid < -1) { + StringFormatter::send(stream, F("No valid DCC loco %d\n"), locoid); + return; + } byte * aval=cmd; while(*aval !=';' && *aval !='\0') aval++; if (*aval) aval+=2; // skip ;> From 5164bd143c8ce96038628566db9ca6d0c59d4944 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 8 May 2023 00:22:31 +0200 Subject: [PATCH 770/870] versions --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 5f04f3466..787fefcd6 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305051413Z" +#define GITHUB_SHA "devel-202305072222Z" diff --git a/version.h b/version.h index ba96ba824..b2ae42451 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.48" +#define VERSION "4.2.50" +// 4.2.50 - Fixes: estop all, turnout eeprom, cab ID check // 4.2.49 - Exrail SPEED take notice of external direction change // 4.2.48 - BROADCAST/WITHROTTLE Exrail macros // 4.2.47 - Correct response to From 991bda63e0243f0afe4594c700e429dbaad5a44f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 8 May 2023 00:35:00 +0200 Subject: [PATCH 771/870] update to production shield factors --- MotorDrivers.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index a355d26f3..115406500 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -80,9 +80,11 @@ new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \ new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN) +// EX 8874 based shield connected to a 3.3V system (like ESP32) and 12bit (4096) ADC +// numbers are GPIO numbers. comments are UNO form factor shield pin numbers #define EX8874_SHIELD F("EX8874"),\ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.17, 5000, -36 /*-A4*/), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.17, 5000, -39 /*-A5*/) + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, -36 /*-A4*/), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, -39 /*-A5*/) #else // STANDARD shield on any Arduino Uno or Mega compatible with the original specification. @@ -120,9 +122,10 @@ new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \ new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12) +// EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC #define EX8874_SHIELD F("EX8874"), \ - new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, -A4), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, -A5) + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, -A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, -A5) // Firebox Mk1 #define FIREBOX_MK1 F("FIREBOX_MK1"), \ From f4e3ca7c8163eb4a161a628d69fac50172d2f0c3 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 8 May 2023 08:32:47 +0800 Subject: [PATCH 772/870] EX8874 entry for SAMD/STM32 --- MotorDrivers.h | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index 115406500..8d1c34d50 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -1,7 +1,7 @@ /* - * © 2022 Paul M. Antoine + * © 2022-2023 Paul M. Antoine * © 2021 Fred Decker - * © 2020-2022 Harald Barth + * © 2020-2023 Harald Barth * (c) 2020 Chris Harlow. All rights reserved. * (c) 2021 Fred Decker. All rights reserved. * (c) 2020 Harald Barth. All rights reserved. @@ -60,7 +60,8 @@ // Arduino STANDARD Motor Shield, used on different architectures: #if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) -// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3 +// Standard Motor Shield definition for 3v3 processors (other than the ESP32) +// Setup for SAMD21 Sparkfun DEV board MUST use Arduino Motor Shield R3 (MUST be R3 // for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using // 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need // to tweak both these @@ -70,6 +71,12 @@ #define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD #define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD +// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC +#define EX8874_SHIELD F("EX8874"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, -A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, -A5) + + #elif defined(ARDUINO_ARCH_ESP32) // STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the // 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the @@ -94,6 +101,12 @@ #define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \ new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \ new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN) + +// EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC +#define EX8874_SHIELD F("EX8874"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, -A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, -A5) + #endif // Pololu Motor Shield @@ -122,11 +135,6 @@ new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \ new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12) -// EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC -#define EX8874_SHIELD F("EX8874"), \ - new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, -A4), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, -A5) - // Firebox Mk1 #define FIREBOX_MK1 F("FIREBOX_MK1"), \ new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \ From 9aad2e3206baa44844a5628697c99ce80986c789 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 9 May 2023 14:17:30 +0200 Subject: [PATCH 773/870] save another 2 bytes in turnouts if eeprom is disabled --- Turnouts.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Turnouts.h b/Turnouts.h index 7c40bdee8..56b7f8292 100644 --- a/Turnouts.h +++ b/Turnouts.h @@ -69,10 +69,12 @@ class Turnout { uint16_t id; } _turnoutData; // 3 bytes +#ifndef DISABLE_EEPROM // Address in eeprom of first byte of the _turnoutData struct (containing the closed flag). // Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and // for all LCN turnouts. uint16_t _eepromAddress = 0; +#endif // Pointer to next turnout on linked list. Turnout *_nextTurnout = 0; From 94e9c2021b6f74f77f4c0dd622a36f89b033c9df Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 15 May 2023 11:31:44 +0800 Subject: [PATCH 774/870] Fix config.example.h OLED_DRIVER #define --- config.example.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.h b/config.example.h index 728c1dd14..11852c516 100644 --- a/config.example.h +++ b/config.example.h @@ -128,7 +128,7 @@ The configuration file for DCC-EX Command Station //OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied) // 128x32 or 128x64 I2C SSD1306-based devices are supported. // Use 132,64 for a SH1106-based I2C device with a 128x64 display. -// #define OLED_DRIVER 128,32,0x3c +// #define OLED_DRIVER 0x3c,128,32 // Define scroll mode as 0, 1 or 2 // * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), From c472f48d9303633433948bbd07451e3aa48e62b0 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 May 2023 14:57:00 +0200 Subject: [PATCH 775/870] Experimental support for sabertooth motor controller on ESP32 --- GITHUB_SHA.h | 2 +- config.example.h | 10 ++++++++++ version.h | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 95642098f..355e2e93e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305022149Z" +#define GITHUB_SHA "devel-202305201256Z" diff --git a/config.example.h b/config.example.h index 2a8a11acf..058bc7a66 100644 --- a/config.example.h +++ b/config.example.h @@ -224,5 +224,15 @@ The configuration file for DCC-EX Command Station // //#define SERIAL_BT_COMMANDS +// SABERTOOTH +// +// This is a very special option and only useful if you happen to have a +// sabertooth motor controller from dimension engineering configured to +// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX) +// and GPIO16 (RX, currently unused). +// The number defined is the DCC address for which speed controls are sent +// to the sabertooth controller _as_well_. Default: Undefined. +// +//#define SABERTOOTH 1 ///////////////////////////////////////////////////////////////////////////////////// diff --git a/version.h b/version.h index f168477e1..030cff666 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.47" +#define VERSION "4.2.52" +// 4.2.52 - Experimental support for sabertooth motor controller on ESP32 // 4.2.47 - Correct response to // 4.2.46 - Support boards with inverted fault pin // 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events From 1a3d295564a97aa2bcadf20a2d9bc962f7cbf2e7 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 20 May 2023 21:50:20 +0800 Subject: [PATCH 776/870] Nucleo-F446RE and other serial port updates. --- DCCTimerSTM32.cpp | 9 ++++++--- defines.h | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 4f9cbddec..944f46636 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -37,16 +37,19 @@ HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) -HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE +HardwareSerial Serial3(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_NUCLEO_F446RE) // Nucleo-64 boards don't have Serial1 defined by default -HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE +// HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. +HardwareSerial Serial1(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE +HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE // NB: USART3 and USART6 are available but as yet undefined #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default -HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE +HardwareSerial Serial1(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F412ZG/F446ZE +HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F412ZG/F446ZE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // NB: diff --git a/defines.h b/defines.h index 5582e8be1..3f5c3bad8 100644 --- a/defines.h +++ b/defines.h @@ -147,7 +147,8 @@ #ifndef I2C_USE_WIRE #define I2C_USE_WIRE #endif - + #undef NUM_SERIAL + #define NUM_SERIAL 3 /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) From 99521f8a3fd5ac603f926e54243f9ecdf9fa16fc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 May 2023 17:35:09 +0200 Subject: [PATCH 777/870] Support DCC-EX shield --- MotorDrivers.h | 6 ++++++ config.example.h | 1 + version.h | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MotorDrivers.h b/MotorDrivers.h index cfb0dd848..3b82858da 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -50,6 +50,12 @@ new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) +// DCC-EX TI DRV8874 based motor shield +// This motor shield has reverse sense fault pins thus the -A4 and -A5 pin values. +#define EX8874_SHIELD F("EX8874"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, A5) + // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \ diff --git a/config.example.h b/config.example.h index 3dac20717..3c9610dd5 100644 --- a/config.example.h +++ b/config.example.h @@ -41,6 +41,7 @@ The configuration file for DCC-EX Command Station // FIREBOX_MK1 : The Firebox MK1 // FIREBOX_MK1S : The Firebox MK1S // IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN +// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield // | // +-----------------------v // diff --git a/version.h b/version.h index d340aa03c..23b0a7c44 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.1.5" +#define VERSION "4.1.6" +// 4.1.6 Support DCC-EX shield // 4.1.5 Bugfix LCN number parsing // 4.1.4 Bugfix for issue #299 TurnoutDescription NULL // 4.1.3 Bugfix: Ethernet init order From 132b0773efc449ae171f405b0e137629c46c4462 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 May 2023 23:15:15 +0200 Subject: [PATCH 778/870] Fault pin handling made more straight forward --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 8 +++----- MotorDrivers.h | 12 ++++++------ version.h | 3 ++- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 355e2e93e..267e56272 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305201256Z" +#define GITHUB_SHA "devel-202305202114Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index b8068f3b1..603549edd 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -214,14 +214,12 @@ int MotorDriver::getCurrentRaw(bool fromISR) { // if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current); current = current-senseOffset; // adjust with offset if (current<0) current=0-current; - if ((faultPin != UNUSED_PIN) && powerMode==POWERMODE::ON) { - if (invertFault && isLOW(fastFaultPin)) - return (current == 0 ? -1 : -current); - if (!invertFault && !isLOW(fastFaultPin)) + // current >= 0 here, we use negative current as fault pin flag + if ((faultPin != UNUSED_PIN) && powerPin) { + if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin)) return (current == 0 ? -1 : -current); } return current; - } #ifdef ANALOG_READ_INTERRUPT diff --git a/MotorDrivers.h b/MotorDrivers.h index 8d1c34d50..a40c4f10d 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -73,8 +73,8 @@ // EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC #define EX8874_SHIELD F("EX8874"), \ - new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, -A4), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, -A5) + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5) #elif defined(ARDUINO_ARCH_ESP32) @@ -90,8 +90,8 @@ // EX 8874 based shield connected to a 3.3V system (like ESP32) and 12bit (4096) ADC // numbers are GPIO numbers. comments are UNO form factor shield pin numbers #define EX8874_SHIELD F("EX8874"),\ - new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, -36 /*-A4*/), \ - new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, -39 /*-A5*/) + new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, 36 /*A4*/), \ + new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, 39 /*A5*/) #else // STANDARD shield on any Arduino Uno or Mega compatible with the original specification. @@ -104,8 +104,8 @@ // EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC #define EX8874_SHIELD F("EX8874"), \ - new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, -A4), \ - new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, -A5) + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, A5) #endif diff --git a/version.h b/version.h index fc4ee3c92..9ddacd26c 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.52" +#define VERSION "4.2.53" +// 4.2.53 - Fix: Fault pin handling made more straight forward // 4.2.52 - Experimental support for sabertooth motor controller on ESP32 // 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash // 4.2.50 - Fixes: estop all, turnout eeprom, cab ID check From 8786285624e21917c9b42f39ff0b119a34bd47e3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 20 May 2023 23:57:17 +0200 Subject: [PATCH 779/870] Assume that we have enough HW serials --- WifiInterface.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 21d474b2e..d5504b608 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -58,6 +58,20 @@ Stream * WifiInterface::wifiStream; #define NUM_SERIAL 1 #endif +// To be able to define these in platform specific +// DCCTimer.cpp files, we here make the +// assumption that these exist to link against. +// if enough serial interfaces exist. +#if NUM_SERIAL > 0 +extern HardwareSerial Serial1; +#endif +#if NUM_SERIAL > 1 +extern HardwareSerial Serial2; +#endif +#if NUM_SERIAL > 2 +extern HardwareSerial Serial3; +#endif + bool WifiInterface::setup(long serial_link_speed, const FSH *wifiESSID, const FSH *wifiPassword, From b80d7bd5170737a1207d97be70ff88161468d1ca Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 21 May 2023 11:54:46 +0200 Subject: [PATCH 780/870] Pin handling supports pins up to 254 --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 33 ++++++++++++++++++++++++--------- MotorDriver.h | 7 ++++--- MotorDrivers.h | 2 +- version.h | 3 ++- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 267e56272..19661bfc3 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305202114Z" +#define GITHUB_SHA "devel-202305210948Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 603549edd..c27f777b3 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -33,9 +33,10 @@ volatile portreg_t shadowPORTA; volatile portreg_t shadowPORTB; volatile portreg_t shadowPORTC; -MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float sense_factor, unsigned int trip_milliamps, int8_t fault_pin) { - powerPin=power_pin; +MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin, + byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) { + bool pinWarning = false; + invertPower=power_pin < 0; if (invertPower) { powerPin = 0-power_pin; @@ -91,28 +92,38 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } else dualSignal=false; - brakePin=brake_pin; if (brake_pin!=UNUSED_PIN){ invertBrake=brake_pin < 0; - brakePin=invertBrake ? 0-brake_pin : brake_pin; + if (invertBrake) + brake_pin = 0-brake_pin; + if (brake_pin > MAX_PIN) + pinWarning = true; + brakePin=(byte)brake_pin; getFastPin(F("BRAKE"),brakePin,fastBrakePin); // if brake is used for railcom cutout we need to do PORTX register trick here as well pinMode(brakePin, OUTPUT); setBrake(true); // start with brake on in case we hace DC stuff going on + } else { + brakePin=UNUSED_PIN; } - else brakePin=UNUSED_PIN; currentPin=current_pin; - if (currentPin!=UNUSED_PIN) ADCee::init(currentPin); + if (currentPin!=UNUSED_PIN) + ADCee::init(currentPin); senseOffset=0; // value can not be obtained until waveform is activated - faultPin=fault_pin; if (faultPin != UNUSED_PIN) { invertFault=fault_pin < 0; - faultPin=invertFault ? 0-fault_pin : fault_pin; + if (invertFault) + fault_pin = 0-fault_pin; + if (fault_pin > MAX_PIN) + pinWarning = true; + faultPin=(byte)fault_pin; DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault); getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin); pinMode(faultPin, INPUT); + } else { + faultPin=UNUSED_PIN; } // This conversion performed at compile time so the remainder of the code never needs @@ -143,6 +154,10 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i // senseFactorInternal, raw2mA(1000),mA2raw(1000)); } + // give general warning if pin values out of range were encountered + if (pinWarning) + DIAG(F("** WARNING ** Pin values > 255")); + // prepare values for current detection sampleDelay = 0; lastSampleTaken = millis(); diff --git a/MotorDriver.h b/MotorDriver.h index 743e57984..be77d2da0 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -74,8 +74,9 @@ // Virtualised Motor shield 1-track hardware Interface #ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h -#define UNUSED_PIN 127 // inside int8_t +#define UNUSED_PIN 255 // inside uint8_t #endif +#define MAX_PIN 254 class pinpair { public: @@ -111,8 +112,8 @@ enum class POWERMODE : byte { OFF, ON, OVERLOAD }; class MotorDriver { public: - MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, - byte current_pin, float senseFactor, unsigned int tripMilliamps, int8_t fault_pin); + MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin, + byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin); void setPower( POWERMODE mode); POWERMODE getPower() { return powerMode;} // as the port registers can be shadowed to get syncronized DCC signals diff --git a/MotorDrivers.h b/MotorDrivers.h index a40c4f10d..907c11b69 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -36,7 +36,7 @@ // custom defines in config.h. #ifndef UNUSED_PIN // sync define with the one in MotorDriver.h -#define UNUSED_PIN 127 // inside int8_t +#define UNUSED_PIN 255 // inside uint8_t #endif // The MotorDriver definition is: diff --git a/version.h b/version.h index 9ddacd26c..bdfbacb3a 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.53" +#define VERSION "4.2.54pre1" +// 4.2.54 - Fix: Pin handling supports pins up to 254 // 4.2.53 - Fix: Fault pin handling made more straight forward // 4.2.52 - Experimental support for sabertooth motor controller on ESP32 // 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash From 16f94ecbdc8f3f21a64c1d2e1ec83ea675d7b2d5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 21 May 2023 20:20:32 +0200 Subject: [PATCH 781/870] Restict where what SerialX is used --- GITHUB_SHA.h | 2 +- WifiInterface.cpp | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 19661bfc3..8db811c8e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305210948Z" +#define GITHUB_SHA "devel-202305211819Z" diff --git a/WifiInterface.cpp b/WifiInterface.cpp index d5504b608..732b39368 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -58,20 +58,14 @@ Stream * WifiInterface::wifiStream; #define NUM_SERIAL 1 #endif -// To be able to define these in platform specific -// DCCTimer.cpp files, we here make the -// assumption that these exist to link against. -// if enough serial interfaces exist. -#if NUM_SERIAL > 0 -extern HardwareSerial Serial1; -#endif -#if NUM_SERIAL > 1 -extern HardwareSerial Serial2; -#endif +// For STM32 we need to define Serial3 in the platform specific +// DCCTimerSTM32.cpp file, we here make the assumption that it +// exists to link against. +#ifdef ARDUINO_ARCH_STM32 #if NUM_SERIAL > 2 extern HardwareSerial Serial3; #endif - +#endif bool WifiInterface::setup(long serial_link_speed, const FSH *wifiESSID, const FSH *wifiPassword, @@ -97,6 +91,8 @@ bool WifiInterface::setup(long serial_link_speed, #endif // Other serials are tried, depending on hardware. +// Currently only the Arduino Mega 2560 has usable Serial2 +#if defined(ARDUINO_AVR_MEGA2560) #if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS) if (wifiUp == WIFI_NOAT) { @@ -104,7 +100,10 @@ bool WifiInterface::setup(long serial_link_speed, wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel); } #endif - +#endif + +// We guess here that in all architctures that have a Serial3 +// we can use it for our purpose. #if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS) if (wifiUp == WIFI_NOAT) { From 9478c3263de02c8dca9856796d3809e3d63fb123 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 May 2023 16:39:24 +0200 Subject: [PATCH 782/870] Try to find default roster entry --- GITHUB_SHA.h | 2 +- WiThrottle.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8db811c8e..bc74cd016 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305211819Z" +#define GITHUB_SHA "devel-202305221431Z" diff --git a/WiThrottle.cpp b/WiThrottle.cpp index aa92dc6bb..c77c1ef66 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -573,7 +573,11 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) { #ifdef EXRAIL_ACTIVE const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); if (!functionNames) { - // no roster, use non-exrail presets as above + // no roster entry for locoid, try to find default entry + functionNames=(char *) RMFT2::getRosterFunctions(0); + } + if (!functionNames) { + // no default roster entry either, use non-exrail presets as above } else if (GETFLASH(functionNames)=='\0') { // "" = Roster but no functions given From fdbcbdf4181bf689a0b9f875b048a3e274e38bc3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 22 May 2023 22:51:35 +0200 Subject: [PATCH 783/870] Do not send default roster entry on withrottle but on JR --- DCCEXParser.cpp | 2 +- EXRAILMacros.h | 8 ++++---- GITHUB_SHA.h | 2 +- WiThrottle.cpp | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index e4f270f36..cd929e0bd 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -47,7 +47,7 @@ #define SENDFLASHLIST(stream,flashList) \ for (int16_t i=0;;i+=sizeof(flashList[0])) { \ int16_t value=GETHIGHFLASHW(flashList,i); \ - if (value==0) break; \ + if (value<0) break; \ StringFormatter::send(stream,F(" %d"),value); \ } diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 5e8161286..aea997dee 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -81,14 +81,14 @@ void exrailHalSetup() { #define ROUTE(id, description) id, const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" - 0}; + -1}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" - 0}; + -1}; // Pass 3 Create route descriptions: #undef ROUTE @@ -190,7 +190,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) { // Pass 6: Roster IDs (count) #include "EXRAIL2MacroReset.h" #undef ROSTER -#define ROSTER(cabid,name,funcmap...) +1 +#define ROSTER(cabid,name,funcmap...) +(cabid == 0 ? 0 : 1) const byte RMFT2::rosterNameCount=0 #include "myAutomation.h" ; @@ -201,7 +201,7 @@ const byte RMFT2::rosterNameCount=0 #define ROSTER(cabid,name,funcmap...) cabid, const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" - 0}; + -1}; // Pass 7: Roster names getter #include "EXRAIL2MacroReset.h" diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index bc74cd016..01f7c207c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305221431Z" +#define GITHUB_SHA "devel-202305222043Z" diff --git a/WiThrottle.cpp b/WiThrottle.cpp index c77c1ef66..11a28fb00 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -533,8 +533,9 @@ void WiThrottle::sendRoster(Print* stream) { StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); for (int16_t r=0;r 0) + StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"), + RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L'); } StringFormatter::send(stream,F("\n")); #else From 951a6637f0e2f5d8e32e4729a459f8bf7fd5749d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 23 May 2023 10:57:45 +0200 Subject: [PATCH 784/870] INT16_MAX is a better end of array marker than -1 --- DCCEXParser.cpp | 2 +- EXRAILMacros.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index cd929e0bd..12e7715e5 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -47,7 +47,7 @@ #define SENDFLASHLIST(stream,flashList) \ for (int16_t i=0;;i+=sizeof(flashList[0])) { \ int16_t value=GETHIGHFLASHW(flashList,i); \ - if (value<0) break; \ + if (value==INT16_MAX) break; \ StringFormatter::send(stream,F(" %d"),value); \ } diff --git a/EXRAILMacros.h b/EXRAILMacros.h index aea997dee..6a4ad4fd5 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -81,14 +81,14 @@ void exrailHalSetup() { #define ROUTE(id, description) id, const int16_t HIGHFLASH RMFT2::routeIdList[]= { #include "myAutomation.h" - -1}; + INT16_MAX}; // Pass 2a create throttle automation list #include "EXRAIL2MacroReset.h" #undef AUTOMATION #define AUTOMATION(id, description) id, const int16_t HIGHFLASH RMFT2::automationIdList[]= { #include "myAutomation.h" - -1}; + INT16_MAX}; // Pass 3 Create route descriptions: #undef ROUTE @@ -201,7 +201,7 @@ const byte RMFT2::rosterNameCount=0 #define ROSTER(cabid,name,funcmap...) cabid, const int16_t HIGHFLASH RMFT2::rosterIdList[]={ #include "myAutomation.h" - -1}; + INT16_MAX}; // Pass 7: Roster names getter #include "EXRAIL2MacroReset.h" From c22d789513dafef2989ae8f6396b43ba4cf100fa Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 23 May 2023 18:59:03 +0200 Subject: [PATCH 785/870] INT16_MAX was missing at more places --- EXRAIL2.cpp | 4 ++-- EXRAILMacros.h | 2 +- GITHUB_SHA.h | 2 +- WiThrottle.cpp | 4 +++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/EXRAIL2.cpp b/EXRAIL2.cpp index cda9c9d67..0e17ea910 100644 --- a/EXRAIL2.cpp +++ b/EXRAIL2.cpp @@ -266,12 +266,12 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) { char RMFT2::getRouteType(int16_t id) { for (int16_t i=0;;i+=2) { int16_t rid= GETHIGHFLASHW(routeIdList,i); - if (rid==0) break; + if (rid==INT16_MAX) break; if (rid==id) return 'R'; } for (int16_t i=0;;i+=2) { int16_t rid= GETHIGHFLASHW(automationIdList,i); - if (rid==0) break; + if (rid==INT16_MAX) break; if (rid==id) return 'A'; } return 'X'; diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 6a4ad4fd5..f48ca6f1c 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -190,7 +190,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) { // Pass 6: Roster IDs (count) #include "EXRAIL2MacroReset.h" #undef ROSTER -#define ROSTER(cabid,name,funcmap...) +(cabid == 0 ? 0 : 1) +#define ROSTER(cabid,name,funcmap...) +(cabid <= 0 ? 0 : 1) const byte RMFT2::rosterNameCount=0 #include "myAutomation.h" ; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 01f7c207c..dec43d110 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305222043Z" +#define GITHUB_SHA "devel-202305231659Z" diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 11a28fb00..974d850b5 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -531,11 +531,13 @@ void WiThrottle::sendRoster(Print* stream) { rosterSent=true; #ifdef EXRAIL_ACTIVE StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); - for (int16_t r=0;r 0) StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"), RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L'); + else if (cabid == INT16_MAX) + break; } StringFormatter::send(stream,F("\n")); #else From be4235e792f365068c69a08e83efc8b598cd0368 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 24 May 2023 13:29:20 +0200 Subject: [PATCH 786/870] Arduino Mega2560: Use timer5 as timer4 for PWM DC --- MotorDriver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c27f777b3..893496956 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -289,6 +289,7 @@ void MotorDriver::setDCSignal(byte speedcode) { #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs + TCCR5B = (TCCR5B & B11111000) | B00000100; // same for timer 5 which is like timer 4 #endif // spedcoode is a dcc speed & direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 From 20d66fad4e88147ad3d1aa4b9fdac28fd4a06b4c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 24 May 2023 13:31:18 +0200 Subject: [PATCH 787/870] Routes, automations and roster lists: Exclude ID 0 to be presented as available --- DCCEXParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 12e7715e5..d39fb5a58 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -48,7 +48,7 @@ for (int16_t i=0;;i+=sizeof(flashList[0])) { \ int16_t value=GETHIGHFLASHW(flashList,i); \ if (value==INT16_MAX) break; \ - StringFormatter::send(stream,F(" %d"),value); \ + if (value != 0) StringFormatter::send(stream,F(" %d"),value); \ } From 342d9263d1cfabf45c346081e62579c038d7a3b8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 24 May 2023 13:32:34 +0200 Subject: [PATCH 788/870] time stamp --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index dec43d110..a564b9754 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305231659Z" +#define GITHUB_SHA "devel-202305241131Z" From 72bfc6abc7c6c1c2a855ee35a81d82f3156027d3 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 24 May 2023 22:57:43 +0200 Subject: [PATCH 789/870] INT16_MAX missing again --- WiThrottle.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 974d850b5..9b8705d2f 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -551,14 +551,14 @@ void WiThrottle::sendRoutes(Print* stream) { // first pass automations for (int ix=0;;ix+=2) { int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix); - if (id==0) break; + if (id==INT16_MAX) break; const FSH * desc=RMFT2::getRouteDescription(id); StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc); } // second pass routes. for (int ix=0;;ix+=2) { int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix); - if (id==0) break; + if (id==INT16_MAX) break; const FSH * desc=RMFT2::getRouteDescription(id); StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc); } From 50313ebbd2fd8d92ced50289b310ddafa0057e21 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 24 May 2023 22:58:21 +0200 Subject: [PATCH 790/870] cast to big enough type --- CommandDistributor.cpp | 2 +- GITHUB_SHA.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CommandDistributor.cpp b/CommandDistributor.cpp index 7ff17bc0c..ab6b52f83 100644 --- a/CommandDistributor.cpp +++ b/CommandDistributor.cpp @@ -168,7 +168,7 @@ void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) { // be safe for both types. broadcastReply(COMMAND_TYPE, F("\n"),time, rate); #ifdef CD_HANDLE_RING - broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate); + broadcastReply(WITHROTTLE_TYPE, F("PFT%l<;>%d\n"), (int32_t)time*60, rate); #endif } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a564b9754..68a291872 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305241131Z" +#define GITHUB_SHA "devel-202305242057Z" From bf136d49e0717fdb5fb8487e3b793d9ed33d0620 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 25 May 2023 08:34:20 +0800 Subject: [PATCH 791/870] Fix Serial ports for Nucleo-144 boards --- DCCTimerSTM32.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 944f46636..a45c4d4ab 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -48,14 +48,9 @@ HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE // NB: USART3 and USART6 are available but as yet undefined #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default -HardwareSerial Serial1(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F412ZG/F446ZE -HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F412ZG/F446ZE -// Serial2 is defined to use USART2 by default, but is in fact used as the diag console +HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 +// Serial3 is defined to use USART3 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. -// NB: -// On all of the above, USART3, and USART6 are available but as yet undefined -// On F446ZE and F429ZI, UART4, UART5 are also available but as yet undefined -// On F429ZI, UART7 and UART8 are also available but as yet undefined #else #error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined #endif From 5a9adea2b6289ecdde28fc435ee78cea1b6d0f04 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 25 May 2023 08:05:07 +0200 Subject: [PATCH 792/870] Bug: Withrottle roster list end was not detected --- WiThrottle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WiThrottle.cpp b/WiThrottle.cpp index 9b8705d2f..eca5377a2 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -536,7 +536,7 @@ void WiThrottle::sendRoster(Print* stream) { if (cabid > 0) StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"), RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L'); - else if (cabid == INT16_MAX) + if (cabid == INT16_MAX) break; } StringFormatter::send(stream,F("\n")); From 58e62aaa810f019740635fad9865105ec9cd4c94 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 25 May 2023 09:27:41 +0200 Subject: [PATCH 793/870] Bug: Withrottle roster list returning empty string vs NULL --- EXRAILMacros.h | 2 +- WiThrottle.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index f48ca6f1c..66b0111f8 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -223,7 +223,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) { #include "myAutomation.h" default: break; } - return F(""); + return NULL; } // Pass 8 Signal definitions diff --git a/WiThrottle.cpp b/WiThrottle.cpp index eca5377a2..d69d6eee0 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -533,11 +533,11 @@ void WiThrottle::sendRoster(Print* stream) { StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount); for (int16_t r=0;;r++) { int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2); + if (cabid == INT16_MAX) + break; if (cabid > 0) StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"), RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L'); - if (cabid == INT16_MAX) - break; } StringFormatter::send(stream,F("\n")); #else @@ -574,12 +574,12 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) { myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle #ifdef EXRAIL_ACTIVE - const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid); - if (!functionNames) { + const FSH * functionNames= RMFT2::getRosterFunctions(locoid); + if (functionNames == NULL) { // no roster entry for locoid, try to find default entry - functionNames=(char *) RMFT2::getRosterFunctions(0); + functionNames= RMFT2::getRosterFunctions(0); } - if (!functionNames) { + if (functionNames == NULL) { // no default roster entry either, use non-exrail presets as above } else if (GETFLASH(functionNames)=='\0') { From 5607ff7167f084df3c792604acb7cbe05fe2022b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 25 May 2023 09:29:42 +0200 Subject: [PATCH 794/870] version --- GITHUB_SHA.h | 2 +- version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 68a291872..db01055e6 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305242057Z" +#define GITHUB_SHA "devel-202305250729Z" diff --git a/version.h b/version.h index bdfbacb3a..cfea8a877 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.54pre1" +#define VERSION "4.2.54pre2" // 4.2.54 - Fix: Pin handling supports pins up to 254 // 4.2.53 - Fix: Fault pin handling made more straight forward // 4.2.52 - Experimental support for sabertooth motor controller on ESP32 From db0e0cbf8b7e9f94cbc7625b1cc5ce533d648213 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 25 May 2023 10:29:01 +0200 Subject: [PATCH 795/870] Send default function list in jR as well --- DCCEXParser.cpp | 8 ++++++-- GITHUB_SHA.h | 2 +- version.h | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index d39fb5a58..048ae2a1f 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -652,8 +652,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) if (params==1) { SENDFLASHLIST(stream,RMFT2::rosterIdList) } - else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""), - id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id)); + else { + const FSH * functionNames= RMFT2::getRosterFunctions(id); + StringFormatter::send(stream,F(" %d \"%S\" \"%S\""), + id, RMFT2::getRosterName(id), + functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames); + } #endif StringFormatter::send(stream, F(">\n")); return; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index db01055e6..9d9a4c8a3 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305250729Z" +#define GITHUB_SHA "devel-202305250828Z" diff --git a/version.h b/version.h index cfea8a877..e8c829449 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.54pre2" +#define VERSION "4.2.54pre3" // 4.2.54 - Fix: Pin handling supports pins up to 254 // 4.2.53 - Fix: Fault pin handling made more straight forward // 4.2.52 - Experimental support for sabertooth motor controller on ESP32 From 82929245ed32b1c5fb189114f097a399daed1a6d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 25 May 2023 14:02:28 +0200 Subject: [PATCH 796/870] char * / flashstring conflict --- GITHUB_SHA.h | 2 +- WiThrottle.cpp | 2 +- version.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 9d9a4c8a3..08d468133 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305250828Z" +#define GITHUB_SHA "devel-202305251144Z" diff --git a/WiThrottle.cpp b/WiThrottle.cpp index d69d6eee0..4eb0a2542 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -595,7 +595,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) { fkeys=0; bool firstchar=true; for (int fx=0;;fx++) { - char c=GETFLASH(functionNames+fx); + char c=GETFLASH((char *)functionNames+fx); if (c=='\0') { fkeys++; break; diff --git a/version.h b/version.h index e8c829449..bcc3f62a4 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.54pre3" +#define VERSION "4.2.54pre4" // 4.2.54 - Fix: Pin handling supports pins up to 254 // 4.2.53 - Fix: Fault pin handling made more straight forward // 4.2.52 - Experimental support for sabertooth motor controller on ESP32 From e81d1cc93ab1f7baa85d1c79f20a9eb90b2a62a6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 29 May 2023 09:48:22 +0200 Subject: [PATCH 797/870] Better warnings for pin number errors --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 16 ++++++---------- version.h | 6 ++++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 08d468133..6716cc997 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305251144Z" +#define GITHUB_SHA "devel-202305290743Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 893496956..678a28a54 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -35,7 +35,7 @@ volatile portreg_t shadowPORTC; MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) { - bool pinWarning = false; + const FSH * warnString = F("** WARNING **"); invertPower=power_pin < 0; if (invertPower) { @@ -97,7 +97,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i if (invertBrake) brake_pin = 0-brake_pin; if (brake_pin > MAX_PIN) - pinWarning = true; + DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN); brakePin=(byte)brake_pin; getFastPin(F("BRAKE"),brakePin,fastBrakePin); // if brake is used for railcom cutout we need to do PORTX register trick here as well @@ -112,12 +112,12 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i ADCee::init(currentPin); senseOffset=0; // value can not be obtained until waveform is activated - if (faultPin != UNUSED_PIN) { + if (fault_pin != UNUSED_PIN) { invertFault=fault_pin < 0; if (invertFault) fault_pin = 0-fault_pin; if (fault_pin > MAX_PIN) - pinWarning = true; + DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN); faultPin=(byte)fault_pin; DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault); getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin); @@ -145,19 +145,15 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } if (currentPin==UNUSED_PIN) - DIAG(F("** WARNING ** No current or short detection")); + DIAG(F("%S No current or short detection"), warnString); else { - DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue); + DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue); // self testing diagnostic for the non-float converters... may be removed when happy // DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"), // senseFactorInternal, raw2mA(1000),mA2raw(1000)); } - // give general warning if pin values out of range were encountered - if (pinWarning) - DIAG(F("** WARNING ** Pin values > 255")); - // prepare values for current detection sampleDelay = 0; lastSampleTaken = millis(); diff --git a/version.h b/version.h index bcc3f62a4..ce3d3de78 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.54pre4" -// 4.2.54 - Fix: Pin handling supports pins up to 254 +#define VERSION "4.2.54pre5" +// 4.2.54 - Fix: Better warnings for pin number errors +// - Fix: Default roster list possible in Withrottle and +// - Fix: Pin handling supports pins up to 254 // 4.2.53 - Fix: Fault pin handling made more straight forward // 4.2.52 - Experimental support for sabertooth motor controller on ESP32 // 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash From 8a69403ddaca955c388997ff332b8a821890c13f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 3 Jun 2023 22:01:14 +0200 Subject: [PATCH 798/870] devel release version 4.2.54 --- GITHUB_SHA.h | 2 +- config.example.h | 16 +++++++++++++--- version.h | 5 +++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6716cc997..7ef89043f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202305290743Z" +#define GITHUB_SHA "devel-202306031954Z" diff --git a/config.example.h b/config.example.h index ec4c210eb..16402b6a9 100644 --- a/config.example.h +++ b/config.example.h @@ -1,7 +1,7 @@ /* * © 2022 Paul M. Antoine * © 2021 Neil McKechnie - * © 2020-2021 Harald Barth + * © 2020-2023 Harald Barth * © 2020-2021 Fred Decker * © 2020-2021 Chris Harlow * @@ -27,6 +27,16 @@ The configuration file for DCC-EX Command Station **********************************************************************/ +///////////////////////////////////////////////////////////////////////////////////// +// If you want to add your own motor driver definition(s), add them here +// For example MY_SHIELD with display name "MINE": +// (remove comment start and end marker if you want to edit and use that) +/* +#define MY_SHIELD F("MINE"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \ + new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5) +*/ + ///////////////////////////////////////////////////////////////////////////////////// // NOTE: Before connecting these boards and selecting one in this software // check the quick install guides!!! Some of these boards require a voltage @@ -34,15 +44,15 @@ The configuration file for DCC-EX Command Station // the correct resistor could damage the sense pin on your Arduino or destroy // the device. // -// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE: +// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h // // STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel // POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) -// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver // FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) // FIREBOX_MK1 : The Firebox MK1 // FIREBOX_MK1S : The Firebox MK1S // IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN +// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield // | // +-----------------------v // diff --git a/version.h b/version.h index ce3d3de78..c3d47d9cd 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.54pre5" -// 4.2.54 - Fix: Better warnings for pin number errors +#define VERSION "4.2.54" +// 4.2.54 - EX8874 shield in config.example.h +// - Fix: Better warnings for pin number errors // - Fix: Default roster list possible in Withrottle and // - Fix: Pin handling supports pins up to 254 // 4.2.53 - Fix: Fault pin handling made more straight forward From f5d4dcb97c73cc8693bf557233a278c5925873e4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 14 Jun 2023 00:58:02 +0200 Subject: [PATCH 799/870] new overload detection --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 110 ++++++++++++++++++++++++++++-------------------- MotorDriver.h | 19 ++++++--- 3 files changed, 80 insertions(+), 51 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7ef89043f..4ac112c3a 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306031954Z" +#define GITHUB_SHA "devel-202306132257Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 678a28a54..3bfaaeab8 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -154,11 +154,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i // senseFactorInternal, raw2mA(1000),mA2raw(1000)); } - // prepare values for current detection - sampleDelay = 0; - lastSampleTaken = millis(); progTripValue = mA2raw(TRIP_CURRENT_PROG); - } bool MotorDriver::isPWMCapable() { @@ -167,6 +163,7 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { + if (powerMode == mode) return; bool on=mode==POWERMODE::ON; if (on) { // when switching a track On, we need to check the crrentOffset with the pin OFF @@ -367,63 +364,86 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res } void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { - if (millis() - lastSampleTaken < sampleDelay) return; - lastSampleTaken = millis(); + //if (millis() - lastSampleTaken < sampleDelay) return; + //lastSampleTaken = millis(); int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); + + // check if there was a change from last time + if (powerMode != oldPowerMode) { + lastPowerChange = micros(); + oldPowerMode = powerMode; + } - // Trackname for diag messages later switch (powerMode) { case POWERMODE::OFF: - sampleDelay = POWER_SAMPLE_OFF_WAIT; + if (microsSinceLastPowerChange() > 5000000UL) { + power_sample_overload_wait = 100UL; + } break; case POWERMODE::ON: // Check current lastCurrent=getCurrentRaw(); if (lastCurrent < 0) { - // We have a fault pin condition to take care of - lastCurrent = -lastCurrent; - setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again - if (commonFaultPin) { - if (lastCurrent < tripValue) { - setPower(POWERMODE::ON); // maybe other track - } - // Write this after the fact as we want to turn on as fast as possible - // because we don't know which output actually triggered the fault pin - DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); + // We have a fault pin condition to take care of + lastCurrent = -lastCurrent; + if (commonFaultPin) { + if (lastCurrent < tripValue) { + // probably other track, do a fast toggle. + setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again + setPower(POWERMODE::ON); // maybe other track + // Write this after the fact as we want to turn on as fast as possible + // because we don't know which output actually triggered the fault pin + DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); + } + } else { + unsigned long us; + if (lastCurrent < tripValue) { + if (power_sample_overload_wait <= 1000UL && // almost virgin + (us = microsSinceLastPowerChange()) < 50000UL) { // Ignore 50ms fault pin if no current + DIAG(F("TRACK %c FAULT PIN ACTIVE - 50ms ignore %lus"), trackno + 'A', us); + break; + } + lastCurrent = tripValue; // exaggerate } else { - DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A'); - if (lastCurrent < tripValue) { - lastCurrent = tripValue; // exaggerate - } + if (power_sample_overload_wait <= 100UL && // virgin + microsSinceLastPowerChange() < 5000UL) { // Ignore 5ms fault pin if we see current + DIAG(F("TRACK %c FAULT PIN ACTIVE - 5ms ignore"), trackno + 'A'); + break; + } } + DIAG(F("TRACK %c FAULT PIN ACTIVE - for real"), trackno + 'A'); + } } + // // // if (lastCurrent < tripValue) { - sampleDelay = POWER_SAMPLE_ON_WAIT; - if(power_good_counter<100) - power_good_counter++; - else - if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT; + if (microsSinceLastPowerChange() > 5000000UL) { + power_sample_overload_wait = 100UL; + } } else { - setPower(POWERMODE::OVERLOAD); - unsigned int mA=raw2mA(lastCurrent); - unsigned int maxmA=raw2mA(tripValue); - power_good_counter=0; - sampleDelay = power_sample_overload_wait; - DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay); - if (power_sample_overload_wait >= 10000) - power_sample_overload_wait = 10000; - else - power_sample_overload_wait *= 2; + // too much current + if (power_sample_overload_wait > 100UL || // not virgin + microsSinceLastPowerChange() > 10000UL) { // Longer than 10ms + setPower(POWERMODE::OVERLOAD); + unsigned int mA=raw2mA(lastCurrent); + unsigned int maxmA=raw2mA(tripValue); + DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %lus"), + trackno + 'A', mA, maxmA, power_sample_overload_wait); + } } break; - case POWERMODE::OVERLOAD: - // Try setting it back on after the OVERLOAD_WAIT + case POWERMODE::OVERLOAD: + // Try setting it back on after the OVERLOAD_WAIT + if (microsSinceLastPowerChange() > power_sample_overload_wait) { + // adjust next wait time + power_sample_overload_wait *= 2; + if (power_sample_overload_wait > 10000000UL) + power_sample_overload_wait = 10000000UL; + // power on test setPower(POWERMODE::ON); - sampleDelay = POWER_SAMPLE_ON_WAIT; - // Debug code.... - DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay); - break; - default: - sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. + DIAG(F("TRACK %c POWER RESTORE (check %lus)"), trackno + 'A', power_sample_overload_wait); + } + break; + default: + break; } } diff --git a/MotorDriver.h b/MotorDriver.h index be77d2da0..a6e0aa734 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -186,6 +186,15 @@ class MotorDriver { inline void setTrackLetter(char c) { trackLetter = c; }; + // this returns how much time has passed since the last power change. If it + // was really long ago (approx > 52min) advance counter approx 35 min so that + // we are at 18 minutes again. Times for 32 bit unsigned long. + inline unsigned long microsSinceLastPowerChange() { + unsigned long diff = micros() - lastPowerChange; + if (diff > (1UL << (6 *sizeof(unsigned long)))) + lastPowerChange += 1UL << (4 * sizeof(unsigned long)); + return diff; + }; #ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); @@ -217,8 +226,8 @@ class MotorDriver { int rawCurrentTripValue; // current sampling POWERMODE powerMode; - unsigned long lastSampleTaken; - unsigned int sampleDelay; + POWERMODE oldPowerMode; + unsigned long lastPowerChange; // timestamp in microseconds int progTripValue; int lastCurrent; #ifdef ANALOG_READ_INTERRUPT @@ -229,9 +238,9 @@ class MotorDriver { int tripmA; // Wait times for power management. Unit: milliseconds - static const int POWER_SAMPLE_ON_WAIT = 100; - static const int POWER_SAMPLE_OFF_WAIT = 1000; - static const int POWER_SAMPLE_OVERLOAD_WAIT = 20; + static const int POWER_SAMPLE_ON_WAIT = 1; + static const int POWER_SAMPLE_OFF_WAIT = 100; + static const int POWER_SAMPLE_OVERLOAD_WAIT = 500UL; // Trip current for programming track, 250mA. Change only if you really // need to be non-NMRA-compliant because of decoders that are not either. From f99deb4276e5059382e3f6d96accaeb7cde2ace6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 14 Jun 2023 22:57:28 +0200 Subject: [PATCH 800/870] overload detection different timestamps and verbose diag --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 46 +++++++++++++++++++++++++++++++++++----------- MotorDriver.h | 2 +- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4ac112c3a..22bb009ad 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306132257Z" +#define GITHUB_SHA "devel-202306142056Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 3bfaaeab8..c1f6ca487 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -364,18 +364,15 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res } void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { - //if (millis() - lastSampleTaken < sampleDelay) return; - //lastSampleTaken = millis(); int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); - // check if there was a change from last time - if (powerMode != oldPowerMode) { - lastPowerChange = micros(); - oldPowerMode = powerMode; - } - switch (powerMode) { case POWERMODE::OFF: + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD POFF OFF")); + } if (microsSinceLastPowerChange() > 5000000UL) { power_sample_overload_wait = 100UL; } @@ -385,6 +382,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { lastCurrent=getCurrentRaw(); if (lastCurrent < 0) { // We have a fault pin condition to take care of + if (!overloadNow) { + overloadNow=true; + lastPowerChange = micros(); + DIAG(F("OVERLOAD FPIN ON")); + } lastCurrent = -lastCurrent; if (commonFaultPin) { if (lastCurrent < tripValue) { @@ -416,22 +418,44 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { } // // // if (lastCurrent < tripValue) { + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD PON OFF")); + } if (microsSinceLastPowerChange() > 5000000UL) { power_sample_overload_wait = 100UL; } } else { // too much current + if (!overloadNow) { + overloadNow=true; + lastPowerChange = micros(); + DIAG(F("OVERLOAD PON ON")); + } + unsigned long us = microsSinceLastPowerChange(); if (power_sample_overload_wait > 100UL || // not virgin - microsSinceLastPowerChange() > 10000UL) { // Longer than 10ms + us > 10000UL) { // Longer than 10ms setPower(POWERMODE::OVERLOAD); + // the setPower just turned off, so overload is now gone + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD PON OFF")); + } unsigned int mA=raw2mA(lastCurrent); unsigned int maxmA=raw2mA(tripValue); - DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %lus"), - trackno + 'A', mA, maxmA, power_sample_overload_wait); + DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown after %lus for %lus"), + trackno + 'A', mA, maxmA, us, power_sample_overload_wait); } } break; case POWERMODE::OVERLOAD: + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD POVER OFF")); + } // Try setting it back on after the OVERLOAD_WAIT if (microsSinceLastPowerChange() > power_sample_overload_wait) { // adjust next wait time diff --git a/MotorDriver.h b/MotorDriver.h index a6e0aa734..89a2e082c 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -226,7 +226,7 @@ class MotorDriver { int rawCurrentTripValue; // current sampling POWERMODE powerMode; - POWERMODE oldPowerMode; + bool overloadNow = false; unsigned long lastPowerChange; // timestamp in microseconds int progTripValue; int lastCurrent; From 95fe7aafe09aede12c091d1a5c036d255f37de11 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 08:59:37 +0200 Subject: [PATCH 801/870] overload detection code cleanup --- MotorDriver.cpp | 65 ++++++++++++++++++++++++++++--------------------- MotorDriver.h | 17 ++++++++++--- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c1f6ca487..b1a89ebfb 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -369,12 +369,13 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { switch (powerMode) { case POWERMODE::OFF: if (overloadNow) { + // reset overload condition as we have just turned off power + // DIAG(F("OVERLOAD POFF OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD POFF OFF")); } - if (microsSinceLastPowerChange() > 5000000UL) { - power_sample_overload_wait = 100UL; + if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; } break; case POWERMODE::ON: @@ -383,9 +384,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { if (lastCurrent < 0) { // We have a fault pin condition to take care of if (!overloadNow) { + // turn on overload condition as fault pin has gone active + // DIAG(F("OVERLOAD FPIN ON")); overloadNow=true; lastPowerChange = micros(); - DIAG(F("OVERLOAD FPIN ON")); } lastCurrent = -lastCurrent; if (commonFaultPin) { @@ -398,73 +400,80 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); } } else { - unsigned long us; if (lastCurrent < tripValue) { - if (power_sample_overload_wait <= 1000UL && // almost virgin - (us = microsSinceLastPowerChange()) < 50000UL) { // Ignore 50ms fault pin if no current - DIAG(F("TRACK %c FAULT PIN ACTIVE - 50ms ignore %lus"), trackno + 'A', us); + if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin + microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) { + // Ignore 50ms fault pin if no current + DIAG(F("TRACK %c FAULT PIN 50ms ignore"), trackno + 'A'); break; } - lastCurrent = tripValue; // exaggerate + lastCurrent = tripValue; // exaggerate so condition below (*) is true } else { - if (power_sample_overload_wait <= 100UL && // virgin - microsSinceLastPowerChange() < 5000UL) { // Ignore 5ms fault pin if we see current - DIAG(F("TRACK %c FAULT PIN ACTIVE - 5ms ignore"), trackno + 'A'); + if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin + microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) { + // Ignore 5ms fault pin if we see current + DIAG(F("TRACK %c FAULT PIN 5ms ignore"), trackno + 'A'); break; } } - DIAG(F("TRACK %c FAULT PIN ACTIVE - for real"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN ACTIVE"), trackno + 'A'); } } // // // - if (lastCurrent < tripValue) { + // above we looked at fault pin, below we look at current + // // // + if (lastCurrent < tripValue) { // see above (*) if (overloadNow) { + // current is below trip value, turn off overload condition + // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD PON OFF")); } - if (microsSinceLastPowerChange() > 5000000UL) { - power_sample_overload_wait = 100UL; + if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; } } else { // too much current if (!overloadNow) { + // current is over trip value, turn on overload condition + // DIAG(F("OVERLOAD PON ON")); overloadNow=true; lastPowerChange = micros(); - DIAG(F("OVERLOAD PON ON")); } - unsigned long us = microsSinceLastPowerChange(); - if (power_sample_overload_wait > 100UL || // not virgin - us > 10000UL) { // Longer than 10ms + unsigned long uSecs = microsSinceLastPowerChange(); + if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin + uSecs > POWER_SAMPLE_OFF_DELAY) { + // Overload has existed longer than delay (typ. 10ms) setPower(POWERMODE::OVERLOAD); - // the setPower just turned off, so overload is now gone if (overloadNow) { + // the setPower just turned off, so overload is now gone + // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD PON OFF")); } unsigned int mA=raw2mA(lastCurrent); unsigned int maxmA=raw2mA(tripValue); DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown after %lus for %lus"), - trackno + 'A', mA, maxmA, us, power_sample_overload_wait); + trackno + 'A', mA, maxmA, uSecs, power_sample_overload_wait); } } break; case POWERMODE::OVERLOAD: if (overloadNow) { + // state overload mode means power is off, turn off overload condition flag as well + // DIAG(F("OVERLOAD POVER OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD POVER OFF")); } // Try setting it back on after the OVERLOAD_WAIT if (microsSinceLastPowerChange() > power_sample_overload_wait) { // adjust next wait time power_sample_overload_wait *= 2; - if (power_sample_overload_wait > 10000000UL) - power_sample_overload_wait = 10000000UL; + if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) + power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; // power on test setPower(POWERMODE::ON); - DIAG(F("TRACK %c POWER RESTORE (check %lus)"), trackno + 'A', power_sample_overload_wait); + DIAG(F("TRACK %c POWER RESTORE (was off %lus)"), trackno + 'A', power_sample_overload_wait); } break; default: diff --git a/MotorDriver.h b/MotorDriver.h index 89a2e082c..fe577bdf8 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -237,10 +237,19 @@ class MotorDriver { int maxmA; int tripmA; - // Wait times for power management. Unit: milliseconds - static const int POWER_SAMPLE_ON_WAIT = 1; - static const int POWER_SAMPLE_OFF_WAIT = 100; - static const int POWER_SAMPLE_OVERLOAD_WAIT = 500UL; + // Times for overload management. Unit: microseconds. + // Base for wait time until power is turned on again + static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 100UL; + // Time after we consider all faults old and forgotten + static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL; + // How long to ignore fault pin if current is under limit + static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 50000UL; + // How long to ignore fault pin if current is higher than limit + static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL; + // How long to wait between overcurrent and turning off + static const unsigned long POWER_SAMPLE_OFF_DELAY = 10000UL; + // Upper limit for retry period + static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL; // Trip current for programming track, 250mA. Change only if you really // need to be non-NMRA-compliant because of decoders that are not either. From 277825c5305fcd2c40c24839705537230559d302 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 09:01:32 +0200 Subject: [PATCH 802/870] versiontag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 22bb009ad..37d7d5f8d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306142056Z" +#define GITHUB_SHA "devel-202306180700Z" From cade89ba1692f12b610368c4f67897a2d4fe7fa8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 09:48:15 +0200 Subject: [PATCH 803/870] check ADCee::init() return value --- MotorDriver.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index b1a89ebfb..5ed86845b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -108,8 +108,13 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } currentPin=current_pin; - if (currentPin!=UNUSED_PIN) - ADCee::init(currentPin); + if (currentPin!=UNUSED_PIN) { + int ret = ADCee::init(currentPin); + if (ret < -1010) { // XXX give value a name later + DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin); + currentPin = UNUSED_PIN; + } + } senseOffset=0; // value can not be obtained until waveform is activated if (fault_pin != UNUSED_PIN) { From f83be0522034d3ad9c9cdefd473095eb431b6a89 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 19:26:38 +0200 Subject: [PATCH 804/870] STM32: Use mask as loop variable --- DCCTimerSTM32.cpp | 28 +++++++++++++++++++--------- GITHUB_SHA.h | 2 +- TrackManager.h | 12 +++++++++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index a45c4d4ab..7ed2b9562 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -30,6 +30,10 @@ #ifdef ARDUINO_ARCH_STM32 #include "DCCTimer.h" +#ifdef DEBUG_ADC +#include "TrackManager.h" +#endif +#include "DIAG.h" #if defined(ARDUINO_NUCLEO_F411RE) // Nucleo-64 boards don't have Serial1 defined by default @@ -307,6 +311,8 @@ int ADCee::init(uint8_t pin) { analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready + DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id); + return value; } @@ -340,11 +346,13 @@ void ADCee::scan() { // found value analogvals[id] = ADC1->DR; // advance at least one track - // for scope debug TrackManager::track[1]->setBrake(0); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(0); +#endif waiting = false; id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (mask == 0) { // the 1 has been shifted out id = 0; mask = 1; } @@ -355,18 +363,20 @@ void ADCee::scan() { // look for a valid track to sample or until we are around while (true) { if (mask & usedpins) { - // start new ADC aquire on id + // start new ADC aquire on id ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART - // for scope debug TrackManager::track[1]->setBrake(1); - waiting = true; - return; +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(1); +#endif + waiting = true; + return; } id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; + if (mask == 0) { // the 1 has been shifted out + id = 0; + mask = 1; } } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 37d7d5f8d..8bd38128a 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306180700Z" +#define GITHUB_SHA "devel-202306181725Z" diff --git a/TrackManager.h b/TrackManager.h index ef4a47c1f..19e756d59 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -84,8 +84,15 @@ class TrackManager { static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main - static bool progTrackBoosted; // true when prog track is not current limited - + static bool progTrackBoosted; // true when prog track is not current limited + +#ifdef DEBUG_ADC + public: +#else + private: +#endif + static MotorDriver* track[MAX_TRACKS]; + private: static void addTrack(byte t, MotorDriver* driver); static byte lastTrack; @@ -93,7 +100,6 @@ class TrackManager { static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); - static MotorDriver* track[MAX_TRACKS]; static TRACK_MODE trackMode[MAX_TRACKS]; static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX #ifdef ARDUINO_ARCH_ESP32 From befb41ce981e8e8fcece4c00f242a143f3e2cf80 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 09:48:15 +0200 Subject: [PATCH 805/870] check ADCee::init() return value --- MotorDriver.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 678a28a54..e41e150ab 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -108,8 +108,13 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } currentPin=current_pin; - if (currentPin!=UNUSED_PIN) - ADCee::init(currentPin); + if (currentPin!=UNUSED_PIN) { + int ret = ADCee::init(currentPin); + if (ret < -1010) { // XXX give value a name later + DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin); + currentPin = UNUSED_PIN; + } + } senseOffset=0; // value can not be obtained until waveform is activated if (fault_pin != UNUSED_PIN) { From 02669368758977411c83ffb3896556dd37b547e4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 19:26:38 +0200 Subject: [PATCH 806/870] STM32: Use mask as loop variable --- DCCTimerSTM32.cpp | 28 +++++++++++++++++++--------- GITHUB_SHA.h | 2 +- TrackManager.h | 12 +++++++++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index a45c4d4ab..7ed2b9562 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -30,6 +30,10 @@ #ifdef ARDUINO_ARCH_STM32 #include "DCCTimer.h" +#ifdef DEBUG_ADC +#include "TrackManager.h" +#endif +#include "DIAG.h" #if defined(ARDUINO_NUCLEO_F411RE) // Nucleo-64 boards don't have Serial1 defined by default @@ -307,6 +311,8 @@ int ADCee::init(uint8_t pin) { analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready + DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id); + return value; } @@ -340,11 +346,13 @@ void ADCee::scan() { // found value analogvals[id] = ADC1->DR; // advance at least one track - // for scope debug TrackManager::track[1]->setBrake(0); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(0); +#endif waiting = false; id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (mask == 0) { // the 1 has been shifted out id = 0; mask = 1; } @@ -355,18 +363,20 @@ void ADCee::scan() { // look for a valid track to sample or until we are around while (true) { if (mask & usedpins) { - // start new ADC aquire on id + // start new ADC aquire on id ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART - // for scope debug TrackManager::track[1]->setBrake(1); - waiting = true; - return; +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(1); +#endif + waiting = true; + return; } id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; + if (mask == 0) { // the 1 has been shifted out + id = 0; + mask = 1; } } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7ef89043f..8bd38128a 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306031954Z" +#define GITHUB_SHA "devel-202306181725Z" diff --git a/TrackManager.h b/TrackManager.h index ef4a47c1f..19e756d59 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -84,8 +84,15 @@ class TrackManager { static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main - static bool progTrackBoosted; // true when prog track is not current limited - + static bool progTrackBoosted; // true when prog track is not current limited + +#ifdef DEBUG_ADC + public: +#else + private: +#endif + static MotorDriver* track[MAX_TRACKS]; + private: static void addTrack(byte t, MotorDriver* driver); static byte lastTrack; @@ -93,7 +100,6 @@ class TrackManager { static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); - static MotorDriver* track[MAX_TRACKS]; static TRACK_MODE trackMode[MAX_TRACKS]; static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX #ifdef ARDUINO_ARCH_ESP32 From 7783837545d1f6f937726083c79605cb23242603 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 21:08:52 +0200 Subject: [PATCH 807/870] Back out this as it is bigger and slower This reverts commit efb26660603eb02d056b1d6c9c7a7b4124213269. --- DCCTimer.h | 14 ++++---- DCCTimerAVR.cpp | 91 ++++++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 7a9d94072..29b88198f 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -105,9 +105,14 @@ class DCCTimer { // that an offset can be initialized. class ADCee { public: - // init does add the pin to the list of scanned pins (if this + // begin is called for any setup that must be done before + // **init** can be called. On some architectures this involves ADC + // initialisation and clock routing, sampling times etc. + static void begin(); + // init adds the pin to the list of scanned pins (if this // platform's implementation scans pins) and returns the first - // read value. It is called before the regular scan is started. + // read value (which is why it required begin to have been called first!) + // It must be called before the regular scan is started. static int init(uint8_t pin); // read does read the pin value from the scanned cache or directly // if this is a platform that does not scan. fromISR is a hint if @@ -116,9 +121,6 @@ class ADCee { static int read(uint8_t pin, bool fromISR=false); // returns possible max value that the ADC can return static int16_t ADCmax(); - // begin is called for any setup that must be done before - // scan can be called. - static void begin(); private: // On platforms that scan, it is called from waveform ISR // only on a regular basis. @@ -127,8 +129,6 @@ class ADCee { static uint16_t usedpins; // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; - // ids to scan (new way) - static byte *idarr; // friend so that we can call scan() and begin() friend class DCCWaveform; }; diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 40ce0fb69..9b16c4715 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -123,14 +123,13 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 16 +#define NUM_ADC_INPUTS 15 #else -#define NUM_ADC_INPUTS 8 +#define NUM_ADC_INPUTS 7 #endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; -byte *ADCee::idarr = NULL; -static bool ADCusesHighPort = false; +bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -139,28 +138,16 @@ static bool ADCusesHighPort = false; */ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; - byte n; - if (id >= NUM_ADC_INPUTS) + if (id > NUM_ADC_INPUTS) return -1023; if (id > 7) ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); - if (analogvals == NULL) { - analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int)); - for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker - analogvals[n] = -32768; // 16 bit int min value - idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value - for (n=0 ; n <= NUM_ADC_INPUTS; n++) - idarr[n] = 255; // set 255 as end of array marker - } - analogvals[id] = value; // store before enable by idarr[n] - for (n=0 ; n <= NUM_ADC_INPUTS; n++) { - if (idarr[n] == 255) { - idarr[n] = id; - break; - } - } + if (analogvals == NULL) + analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogvals[id] = value; + usedpins |= (1<setBrake(0); waiting = false; + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } } if (!waiting) { - // cycle around in-use analogue pins - num++; - if (idarr[num] == 255) - num = 0; - // start new ADC aquire on id + if (usedpins == 0) // otherwise we would loop forever + return; + // look for a valid track to sample or until we are around + while (true) { + if (mask & usedpins) { + // start new ADC aquire on id #if defined(ADCSRB) && defined(MUX5) - if (ADCusesHighPort) { // if we ever have started to use high pins) - if (idarr[num] > 7) // if we use a high ADC pin - bitSet(ADCSRB, MUX5); // set MUX5 bit - else - bitClear(ADCSRB, MUX5); - } + if (ADCusesHighPort) { // if we ever have started to use high pins) + if (id > 7) // if we use a high ADC pin + bitSet(ADCSRB, MUX5); // set MUX5 bit + else + bitClear(ADCSRB, MUX5); + } #endif - ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX - bitSet(ADCSRA, ADSC); // start conversion - waiting = true; + ADMUX=(1<setBrake(1); + waiting = true; + return; + } + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } } } #pragma GCC pop_options @@ -231,4 +236,4 @@ void ADCee::begin() { //bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX interrupts(); } -#endif \ No newline at end of file +#endif From 56fcb4e5f790086010c357a7da912ca066fb0532 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 00:06:04 +0200 Subject: [PATCH 808/870] Optimize DCCTimerARV.cpp --- DCCTimer.h | 1 + DCCTimerAVR.cpp | 39 +++++++++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 29b88198f..7402f166c 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -127,6 +127,7 @@ class ADCee { static void scan(); // bit array of used pins (max 16) static uint16_t usedpins; + static uint8_t highestPin; // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; // friend so that we can call scan() and begin() diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 9b16c4715..80cd24530 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -1,6 +1,6 @@ /* * © 2021 Mike S - * © 2021-2022 Harald Barth + * © 2021-2023 Harald Barth * © 2021 Fred Decker * © 2021 Chris Harlow * © 2021 David Cutting @@ -29,6 +29,9 @@ #include #include #include "DCCTimer.h" +#ifdef DEBUG_ADC +#include "TrackManager.h" +#endif INTERRUPT_CALLBACK interruptHandler=0; // Arduino nano, uno, mega etc @@ -123,13 +126,14 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 15 +#define NUM_ADC_INPUTS 16 #else -#define NUM_ADC_INPUTS 7 +#define NUM_ADC_INPUTS 8 #endif uint16_t ADCee::usedpins = 0; +uint8_t ADCee::highestPin = 0; int * ADCee::analogvals = NULL; -bool ADCusesHighPort = false; +static bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -138,16 +142,17 @@ bool ADCusesHighPort = false; */ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; - if (id > NUM_ADC_INPUTS) + if (id >= NUM_ADC_INPUTS) return -1023; if (id > 7) ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); if (analogvals == NULL) - analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int)); analogvals[id] = value; usedpins |= (1< highestPin) highestPin = id; return value; } int16_t ADCee::ADCmax() { @@ -157,13 +162,15 @@ int16_t ADCee::ADCmax() { * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ int ADCee::read(uint8_t pin, bool fromISR) { - (void)fromISR; // AVR does ignore this arg uint8_t id = pin - A0; if ((usedpins & (1<setBrake(0); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(0); +#endif waiting = false; id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (id > highestPin) { // the 1 has been shifted out id = 0; mask = 1; } @@ -212,13 +221,15 @@ void ADCee::scan() { #endif ADMUX=(1<setBrake(1); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(1); +#endif waiting = true; return; } id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (id > highestPin) { id = 0; mask = 1; } From d5dad767a42eed0730de8f288b7f0d5261151395 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 00:09:27 +0200 Subject: [PATCH 809/870] version 4.2.55 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8bd38128a..25e57bf0e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306181725Z" +#define GITHUB_SHA "devel-202306182208Z" diff --git a/version.h b/version.h index c3d47d9cd..6355f6b8e 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.54" +#define VERSION "4.2.55" +// 4.2.55 - Optimize analog read for AVR // 4.2.54 - EX8874 shield in config.example.h // - Fix: Better warnings for pin number errors // - Fix: Default roster list possible in Withrottle and From 0cf81d589ec2d1ac5aafb6eb1272c6e2447e5dfb Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:25:20 +1000 Subject: [PATCH 810/870] Add _writeAnalogue() --- IO_RotaryEncoder.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 00a82493f..2c5a5ec98 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -1,4 +1,5 @@ /* + * © 2023, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation @@ -28,9 +29,23 @@ * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * IFRE(vpin, position) - test to see if specified rotary encoder position has been received * -* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * +* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other +* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished +* using the EXRAIL SERVO(vpin, position, profile) command, where: +* - vpin = the third defined Vpin (any other is ignored) +* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255 +* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance +* +* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples: +* +* #include "IO_RotaryEncoder.h" +* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software +* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software +* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software +* * Refer to the documentation for further information including the valid activities and examples. */ @@ -103,6 +118,15 @@ class RotaryEncoder : public IODevice { I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } + + void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { + if (vpin == _firstVpin + 2) { + if (position >= 0 && position <= 255) { + byte _positionBuffer[2] = {RE_MOVE, position}; + I2CManager.write(_I2CAddress, _positionBuffer, 2); + } + } + } void _display() override { DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, @@ -120,6 +144,7 @@ class RotaryEncoder : public IODevice { enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation + RE_MOVE = 0xA2, // Flag for sending a position update }; }; From 955ff4f96d591084c086ec48e5f3c5a185122abf Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:25:20 +1000 Subject: [PATCH 811/870] Add _writeAnalogue() --- IO_RotaryEncoder.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 00a82493f..2c5a5ec98 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -1,4 +1,5 @@ /* + * © 2023, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation @@ -28,9 +29,23 @@ * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * IFRE(vpin, position) - test to see if specified rotary encoder position has been received * -* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * +* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other +* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished +* using the EXRAIL SERVO(vpin, position, profile) command, where: +* - vpin = the third defined Vpin (any other is ignored) +* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255 +* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance +* +* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples: +* +* #include "IO_RotaryEncoder.h" +* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software +* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software +* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software +* * Refer to the documentation for further information including the valid activities and examples. */ @@ -103,6 +118,15 @@ class RotaryEncoder : public IODevice { I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } + + void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { + if (vpin == _firstVpin + 2) { + if (position >= 0 && position <= 255) { + byte _positionBuffer[2] = {RE_MOVE, position}; + I2CManager.write(_I2CAddress, _positionBuffer, 2); + } + } + } void _display() override { DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, @@ -120,6 +144,7 @@ class RotaryEncoder : public IODevice { enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation + RE_MOVE = 0xA2, // Flag for sending a position update }; }; From 6dd175f63bddeb361942df0c2725f7974f1c6e7a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 00:33:53 +0200 Subject: [PATCH 812/870] fix power change timer micros overflow --- GITHUB_SHA.h | 2 +- MotorDriver.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8bd38128a..41f732d3c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306181725Z" +#define GITHUB_SHA "devel-202306182233Z" diff --git a/MotorDriver.h b/MotorDriver.h index fe577bdf8..1eb4e951e 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -190,9 +190,10 @@ class MotorDriver { // was really long ago (approx > 52min) advance counter approx 35 min so that // we are at 18 minutes again. Times for 32 bit unsigned long. inline unsigned long microsSinceLastPowerChange() { - unsigned long diff = micros() - lastPowerChange; - if (diff > (1UL << (6 *sizeof(unsigned long)))) - lastPowerChange += 1UL << (4 * sizeof(unsigned long)); + unsigned long now = micros(); + unsigned long diff = now - lastPowerChange; + if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds + lastPowerChange = now - 30000000UL; // 30 seconds ago return diff; }; #ifdef ANALOG_READ_INTERRUPT From 1888073dc230aa18794490c44a94bd1c05620d59 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 08:43:50 +0200 Subject: [PATCH 813/870] set lastPowerChange we doing the power on retry after overload --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 41f732d3c..8b6faac02 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306182233Z" +#define GITHUB_SHA "devel-202306190642Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 5ed86845b..0eed19afa 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -478,6 +478,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; // power on test setPower(POWERMODE::ON); + // here we change power but not the overloadNow as that was + // already changed to false when we entered POWERMODE::OVERLOAD + // so we need to set the lastPowerChange anyway. + lastPowerChange = micros(); DIAG(F("TRACK %c POWER RESTORE (was off %lus)"), trackno + 'A', power_sample_overload_wait); } break; From c3eb6b8d8a9774d8e308f8f80d5e51d134d0de18 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 19 Jun 2023 17:47:42 +0800 Subject: [PATCH 814/870] STM32 ADCee highestPin --- DCCTimerAVR.cpp | 6 +++--- DCCTimerSTM32.cpp | 16 ++++++++++------ platformio.ini | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 80cd24530..3e6c43600 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -199,7 +199,7 @@ void ADCee::scan() { waiting = false; id++; mask = mask << 1; - if (id > highestPin) { // the 1 has been shifted out + if (id > highestPin) { id = 0; mask = 1; } @@ -230,8 +230,8 @@ void ADCee::scan() { id++; mask = mask << 1; if (id > highestPin) { - id = 0; - mask = 1; + id = 0; + mask = 1; } } } diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 7ed2b9562..17ad84eab 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -233,13 +233,16 @@ void DCCTimer::reset() { while(true) {}; } -#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS - // TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs! #if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) #warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing! #endif +// For now, define the max of 16 ports - some variants have more, but this not **yet** supported +#define NUM_ADC_INPUTS 16 +// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS + uint16_t ADCee::usedpins = 0; +uint8_t ADCee::highestPin = 0; int * ADCee::analogvals = NULL; uint32_t * analogchans = NULL; bool adc1configured = false; @@ -310,6 +313,7 @@ int ADCee::init(uint8_t pin) { analogvals[id] = value; // Store sampled value analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready + if (id > highestPin) highestPin = id; // Store our highest pin in use DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id); @@ -352,7 +356,7 @@ void ADCee::scan() { waiting = false; id++; mask = mask << 1; - if (mask == 0) { // the 1 has been shifted out + if (id > highestPin) { // the 1 has been shifted out id = 0; mask = 1; } @@ -374,9 +378,9 @@ void ADCee::scan() { } id++; mask = mask << 1; - if (mask == 0) { // the 1 has been shifted out - id = 0; - mask = 1; + if (id > highestPin) { + id = 0; + mask = 1; } } } diff --git a/platformio.ini b/platformio.ini index d85c76cb0..3091d7fbe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -188,7 +188,7 @@ platform = ststm32 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO +build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO monitor_speed = 115200 monitor_echo = yes From 0be9af22707ba84a770d5a57ed865956a1d1bdec Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 20 Jun 2023 09:37:19 +1000 Subject: [PATCH 815/870] Update some logic --- IO_RotaryEncoder.h | 100 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 2c5a5ec98..3391f6dd5 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -59,50 +59,98 @@ class RotaryEncoder : public IODevice { public: + + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); + } + +private: // Constructor RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ _firstVpin = firstVpin; _nPins = nPins; + if (_nPins > 3) { + _nPins = 3; + DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins); + } _I2CAddress = i2cAddress; addDevice(this); } - static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { - if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); - } -private: // Initiate the device void _begin() { + uint8_t _status; + // Attempt to initilalise device I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { - byte _getVersion[1] = {RE_VER}; - I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); - _majorVer = _versionBuffer[0]; - _minorVer = _versionBuffer[1]; - _patchVer = _versionBuffer[2]; - _buffer[0] = RE_OP; - I2CManager.write(_I2CAddress, _buffer, 1); + // Send RE_OP, must receive RE_OP to be online + _sendBuffer[0] = RE_OP; + _status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1); + if (_status == I2C_STATUS_OK) { + if (_rcvBuffer[0] == RE_OP) { + _sendBuffer[0] = RE_VER; + if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) { + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + } + } else { + DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]); + _deviceState = DEVSTATE_FAILED; + return; + } + } else { + DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + return; + } + // byte _getVersion[1] = {RE_VER}; + // I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); + // _majorVer = _versionBuffer[0]; + // _minorVer = _versionBuffer[1]; + // _patchVer = _versionBuffer[2]; + // _buffer[0] = RE_OP; + // I2CManager.write(_I2CAddress, _buffer, 1); #ifdef DIAG_IO _display(); #endif } else { - _deviceState = DEVSTATE_FAILED; + DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; } } void _loop(unsigned long currentMicros) override { - I2CManager.read(_I2CAddress, _buffer, 1); - _position = _buffer[0]; - // This here needs to have a change check, ie. position is a different value. - #if defined(EXRAIL_ACTIVE) + if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed + if (_i2crb.isBusy()) return; // Return if I2C operation still in progress + + if (currentMicros - _lastPositionRead > _positionRefresh) { + _lastPositionRead = currentMicros; + _sendBuffer[0] = RE_READ; + I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder + _position = _rcvBuffer[0]; + // If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use +#if defined(EXRAIL_ACTIVE) if (_position != _previousPosition) { _previousPosition = _position; - RMFT2::changeEvent(_firstVpin,1); + RMFT2::changeEvent(_firstVpin, 1); } else { - RMFT2::changeEvent(_firstVpin,0); + RMFT2::changeEvent(_firstVpin, 0); } - #endif - delayUntil(currentMicros + 100000); +#endif + } + // I2CManager.read(_I2CAddress, _buffer, 1); + // _position = _buffer[0]; + // // This here needs to have a change check, ie. position is a different value. + // #if defined(EXRAIL_ACTIVE) + // if (_position != _previousPosition) { + // _previousPosition = _position; + // RMFT2::changeEvent(_firstVpin,1); + // } else { + // RMFT2::changeEvent(_firstVpin,0); + // } + // #endif + // delayUntil(currentMicros + 100000); } // Device specific read function @@ -122,7 +170,8 @@ class RotaryEncoder : public IODevice { void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { if (vpin == _firstVpin + 2) { if (position >= 0 && position <= 255) { - byte _positionBuffer[2] = {RE_MOVE, position}; + byte newPosition = position & 0xFF; + byte _positionBuffer[2] = {RE_MOVE, newPosition}; I2CManager.write(_I2CAddress, _positionBuffer, 2); } } @@ -136,15 +185,20 @@ class RotaryEncoder : public IODevice { int8_t _position; int8_t _previousPosition = 0; uint8_t _versionBuffer[3]; - uint8_t _buffer[1]; + uint8_t _sendBuffer[1]; + uint8_t _rcvBuffer[1]; uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; + I2CRB _i2crb; + unsigned long _lastPositionRead = 0; + const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation - RE_MOVE = 0xA2, // Flag for sending a position update + RE_MOVE = 0xA2, // Flag for sending a position update from the device driver to the encoder + RE_READ = 0xA3, // Flag to read the current position of the encoder }; }; From 988011475c84e7f02a7adb2a17a637cc8075a4b0 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 20 Jun 2023 08:34:27 +0800 Subject: [PATCH 816/870] STM32 Serial port handling for WiFi --- DCCTimerSTM32.cpp | 16 +++++++++------- WifiInterface.cpp | 41 +++++++++++++++++++++++++++-------------- defines.h | 2 -- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 17ad84eab..cc6054718 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -36,23 +36,25 @@ #include "DIAG.h" #if defined(ARDUINO_NUCLEO_F411RE) -// Nucleo-64 boards don't have Serial1 defined by default +// Nucleo-64 boards don't have additional serial ports defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) -HardwareSerial Serial3(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE +HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_NUCLEO_F446RE) -// Nucleo-64 boards don't have Serial1 defined by default +// Nucleo-64 boards don't have additional serial ports defined by default +// On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins // HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. -HardwareSerial Serial1(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE -HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE -// NB: USART3 and USART6 are available but as yet undefined +// On the F446RE, Serial3 and Serial5 are easy to use: +HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE +HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE +// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default -HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 +HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 // Serial3 is defined to use USART3 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #else diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 732b39368..3a88aea2c 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -52,20 +52,32 @@ Stream * WifiInterface::wifiStream; #if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) #define NUM_SERIAL 3 +#define SERIAL1 Serial1 +#define SERIAL3 Serial3 +#endif + +#if defined(ARDUINO_ARCH_STM32) +// Handle serial ports availability on STM32 for variants! +// #undef NUM_SERIAL +#if defined(ARDUINO_NUCLEO_F411RE) +#define NUM_SERIAL 3 +#define SERIAL1 Serial1 +#define SERIAL3 Serial6 +#elif defined(ARDUINO_NUCLEO_F446RE) +#define NUM_SERIAL 3 +#define SERIAL1 Serial3 +#define SERIAL3 Serial5 +#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#define NUM_SERIAL 2 +#define SERIAL1 Serial6 +#endif #endif #ifndef NUM_SERIAL #define NUM_SERIAL 1 +#define SERIAL1 Serial1 #endif -// For STM32 we need to define Serial3 in the platform specific -// DCCTimerSTM32.cpp file, we here make the assumption that it -// exists to link against. -#ifdef ARDUINO_ARCH_STM32 -#if NUM_SERIAL > 2 -extern HardwareSerial Serial3; -#endif -#endif bool WifiInterface::setup(long serial_link_speed, const FSH *wifiESSID, const FSH *wifiPassword, @@ -84,14 +96,15 @@ bool WifiInterface::setup(long serial_link_speed, (void) port; (void) channel; #endif - + +// See if the WiFi is attached to the first serial port #if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS) - Serial1.begin(serial_link_speed); - wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel); + SERIAL1.begin(serial_link_speed); + wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel); #endif // Other serials are tried, depending on hardware. -// Currently only the Arduino Mega 2560 has usable Serial2 +// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!) #if defined(ARDUINO_AVR_MEGA2560) #if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS) if (wifiUp == WIFI_NOAT) @@ -107,8 +120,8 @@ bool WifiInterface::setup(long serial_link_speed, #if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS) if (wifiUp == WIFI_NOAT) { - Serial3.begin(serial_link_speed); - wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel); + SERIAL3.begin(serial_link_speed); + wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel); } #endif diff --git a/defines.h b/defines.h index 3f5c3bad8..48f7c6bc7 100644 --- a/defines.h +++ b/defines.h @@ -147,8 +147,6 @@ #ifndef I2C_USE_WIRE #define I2C_USE_WIRE #endif - #undef NUM_SERIAL - #define NUM_SERIAL 3 /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) From c9323251204030db247b02744c393f4a566a4d9a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:25:20 +1000 Subject: [PATCH 817/870] Add _writeAnalogue() --- IO_RotaryEncoder.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 00a82493f..2c5a5ec98 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -1,4 +1,5 @@ /* + * © 2023, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation @@ -28,9 +29,23 @@ * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * IFRE(vpin, position) - test to see if specified rotary encoder position has been received * -* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * +* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other +* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished +* using the EXRAIL SERVO(vpin, position, profile) command, where: +* - vpin = the third defined Vpin (any other is ignored) +* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255 +* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance +* +* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples: +* +* #include "IO_RotaryEncoder.h" +* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software +* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software +* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software +* * Refer to the documentation for further information including the valid activities and examples. */ @@ -103,6 +118,15 @@ class RotaryEncoder : public IODevice { I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } + + void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { + if (vpin == _firstVpin + 2) { + if (position >= 0 && position <= 255) { + byte _positionBuffer[2] = {RE_MOVE, position}; + I2CManager.write(_I2CAddress, _positionBuffer, 2); + } + } + } void _display() override { DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, @@ -120,6 +144,7 @@ class RotaryEncoder : public IODevice { enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation + RE_MOVE = 0xA2, // Flag for sending a position update }; }; From 247cea6dc12f517ac57a7802e5d3887088132c66 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 20 Jun 2023 09:37:19 +1000 Subject: [PATCH 818/870] Update some logic --- IO_RotaryEncoder.h | 100 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 2c5a5ec98..3391f6dd5 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -59,50 +59,98 @@ class RotaryEncoder : public IODevice { public: + + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); + } + +private: // Constructor RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ _firstVpin = firstVpin; _nPins = nPins; + if (_nPins > 3) { + _nPins = 3; + DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins); + } _I2CAddress = i2cAddress; addDevice(this); } - static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { - if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); - } -private: // Initiate the device void _begin() { + uint8_t _status; + // Attempt to initilalise device I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { - byte _getVersion[1] = {RE_VER}; - I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); - _majorVer = _versionBuffer[0]; - _minorVer = _versionBuffer[1]; - _patchVer = _versionBuffer[2]; - _buffer[0] = RE_OP; - I2CManager.write(_I2CAddress, _buffer, 1); + // Send RE_OP, must receive RE_OP to be online + _sendBuffer[0] = RE_OP; + _status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1); + if (_status == I2C_STATUS_OK) { + if (_rcvBuffer[0] == RE_OP) { + _sendBuffer[0] = RE_VER; + if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) { + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + } + } else { + DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]); + _deviceState = DEVSTATE_FAILED; + return; + } + } else { + DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + return; + } + // byte _getVersion[1] = {RE_VER}; + // I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); + // _majorVer = _versionBuffer[0]; + // _minorVer = _versionBuffer[1]; + // _patchVer = _versionBuffer[2]; + // _buffer[0] = RE_OP; + // I2CManager.write(_I2CAddress, _buffer, 1); #ifdef DIAG_IO _display(); #endif } else { - _deviceState = DEVSTATE_FAILED; + DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; } } void _loop(unsigned long currentMicros) override { - I2CManager.read(_I2CAddress, _buffer, 1); - _position = _buffer[0]; - // This here needs to have a change check, ie. position is a different value. - #if defined(EXRAIL_ACTIVE) + if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed + if (_i2crb.isBusy()) return; // Return if I2C operation still in progress + + if (currentMicros - _lastPositionRead > _positionRefresh) { + _lastPositionRead = currentMicros; + _sendBuffer[0] = RE_READ; + I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder + _position = _rcvBuffer[0]; + // If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use +#if defined(EXRAIL_ACTIVE) if (_position != _previousPosition) { _previousPosition = _position; - RMFT2::changeEvent(_firstVpin,1); + RMFT2::changeEvent(_firstVpin, 1); } else { - RMFT2::changeEvent(_firstVpin,0); + RMFT2::changeEvent(_firstVpin, 0); } - #endif - delayUntil(currentMicros + 100000); +#endif + } + // I2CManager.read(_I2CAddress, _buffer, 1); + // _position = _buffer[0]; + // // This here needs to have a change check, ie. position is a different value. + // #if defined(EXRAIL_ACTIVE) + // if (_position != _previousPosition) { + // _previousPosition = _position; + // RMFT2::changeEvent(_firstVpin,1); + // } else { + // RMFT2::changeEvent(_firstVpin,0); + // } + // #endif + // delayUntil(currentMicros + 100000); } // Device specific read function @@ -122,7 +170,8 @@ class RotaryEncoder : public IODevice { void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { if (vpin == _firstVpin + 2) { if (position >= 0 && position <= 255) { - byte _positionBuffer[2] = {RE_MOVE, position}; + byte newPosition = position & 0xFF; + byte _positionBuffer[2] = {RE_MOVE, newPosition}; I2CManager.write(_I2CAddress, _positionBuffer, 2); } } @@ -136,15 +185,20 @@ class RotaryEncoder : public IODevice { int8_t _position; int8_t _previousPosition = 0; uint8_t _versionBuffer[3]; - uint8_t _buffer[1]; + uint8_t _sendBuffer[1]; + uint8_t _rcvBuffer[1]; uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; + I2CRB _i2crb; + unsigned long _lastPositionRead = 0; + const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation - RE_MOVE = 0xA2, // Flag for sending a position update + RE_MOVE = 0xA2, // Flag for sending a position update from the device driver to the encoder + RE_READ = 0xA3, // Flag to read the current position of the encoder }; }; From 2af01e3c426fbce18212bc2870cee46c5381bbfa Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:48:13 +1000 Subject: [PATCH 819/870] Add ready flag --- IO_RotaryEncoder.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 3391f6dd5..e3ac5c500 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -83,11 +83,11 @@ class RotaryEncoder : public IODevice { // Attempt to initilalise device I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { - // Send RE_OP, must receive RE_OP to be online - _sendBuffer[0] = RE_OP; + // Send RE_RDY, must receive RE_RDY to be online + _sendBuffer[0] = RE_RDY; _status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1); if (_status == I2C_STATUS_OK) { - if (_rcvBuffer[0] == RE_OP) { + if (_rcvBuffer[0] == RE_RDY) { _sendBuffer[0] = RE_VER; if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) { _majorVer = _versionBuffer[0]; @@ -195,10 +195,11 @@ class RotaryEncoder : public IODevice { const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms enum { - RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device - RE_OP = 0xA1, // Flag for normal operation - RE_MOVE = 0xA2, // Flag for sending a position update from the device driver to the encoder - RE_READ = 0xA3, // Flag to read the current position of the encoder + RE_RDY = 0xA0, // Flag to check if encoder is ready for operation + RE_VER = 0xA1, // Flag to retrieve rotary encoder software version + RE_READ = 0xA2, // Flag to read the current position of the encoder + RE_OP = 0xA3, // Flag for operation start/end, sent to when sending feedback on move start/end + RE_MOVE = 0xA4, // Flag for sending a position update from the device driver to the encoder }; }; From 2b8b99530760951bbfa3ff86968aaaa0c0bce65c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Jun 2023 19:24:49 +1000 Subject: [PATCH 820/870] Updated logic, sending move --- IO_RotaryEncoder.h | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index e3ac5c500..42d8a2d32 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,13 +104,6 @@ class RotaryEncoder : public IODevice { _deviceState = DEVSTATE_FAILED; return; } - // byte _getVersion[1] = {RE_VER}; - // I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); - // _majorVer = _versionBuffer[0]; - // _minorVer = _versionBuffer[1]; - // _patchVer = _versionBuffer[2]; - // _buffer[0] = RE_OP; - // I2CManager.write(_I2CAddress, _buffer, 1); #ifdef DIAG_IO _display(); #endif @@ -139,18 +132,6 @@ class RotaryEncoder : public IODevice { } #endif } - // I2CManager.read(_I2CAddress, _buffer, 1); - // _position = _buffer[0]; - // // This here needs to have a change check, ie. position is a different value. - // #if defined(EXRAIL_ACTIVE) - // if (_position != _previousPosition) { - // _previousPosition = _position; - // RMFT2::changeEvent(_firstVpin,1); - // } else { - // RMFT2::changeEvent(_firstVpin,0); - // } - // #endif - // delayUntil(currentMicros + 100000); } // Device specific read function From 995c6f8ede39284d1a78ebf2b6e965a486c0672a Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Jun 2023 19:32:43 +1000 Subject: [PATCH 821/870] Update version --- version.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 6355f6b8e..ab5609de7 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.55" +#define VERSION "4.2.56" +// 4.2.56 - Update IO_RotaryEncoder.h: +// - Improved I2C communication, non-blocking reads +// - Enable sending positions to the encoder from EXRAIL via SERVO() // 4.2.55 - Optimize analog read for AVR // 4.2.54 - EX8874 shield in config.example.h // - Fix: Better warnings for pin number errors From 08f41415dc37fcd85c39857ce69776f17d1e5439 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 21 Jun 2023 10:43:41 +0200 Subject: [PATCH 822/870] format option to write microseconds --- StringFormatter.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index f7d9c505c..c475ef006 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -117,6 +117,24 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { case 'o': stream->print(va_arg(args, int), OCT); break; case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break; case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break; + case 'M': + { // this prints a unsigned long microseconds time in readable format + unsigned long time = va_arg(args, long); + if (time >= 2000) { + time = time / 1000; + if (time >= 2000) { + printPadded(stream, time/1000, formatWidth, formatLeft); + stream->print(F("sec")); + } else { + printPadded(stream,time, formatWidth, formatLeft); + stream->print(F("msec")); + } + } else { + printPadded(stream,time, formatWidth, formatLeft); + stream->print(F("usec")); + } + } + break; //case 'f': stream->print(va_arg(args, double), 2); break; //format width prefix case '-': From dd80260781b51dedf6e45faa537eb6bdb72be5f4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 21 Jun 2023 10:44:57 +0200 Subject: [PATCH 823/870] add common fault pin handling to new overload code --- DCCEXParser.cpp | 4 ++-- MotorDriver.cpp | 49 +++++++++++++++++++----------------------------- MotorDriver.h | 20 ++++++++++++++++++-- TrackManager.cpp | 14 +++++++++++--- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 048ae2a1f..1e3df9026 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -467,7 +467,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) bool prog=false; bool join=false; if (params > 1) break; - if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually + if (params==0) { // All main=true; prog=true; } @@ -500,7 +500,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) bool main=false; bool prog=false; if (params > 1) break; - if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually + if (params==0) { // All main=true; prog=true; } diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 0eed19afa..bf5c47b15 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -27,7 +27,7 @@ #include "DCCTimer.h" #include "DIAG.h" -bool MotorDriver::commonFaultPin=false; +unsigned long MotorDriver::globalOverloadStart = 0; volatile portreg_t shadowPORTA; volatile portreg_t shadowPORTB; @@ -377,7 +377,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // reset overload condition as we have just turned off power // DIAG(F("OVERLOAD POFF OFF")); overloadNow=false; - lastPowerChange = micros(); + setLastPowerChange(); } if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; @@ -392,24 +392,15 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // turn on overload condition as fault pin has gone active // DIAG(F("OVERLOAD FPIN ON")); overloadNow=true; - lastPowerChange = micros(); + setLastPowerChangeOverload(); } lastCurrent = -lastCurrent; - if (commonFaultPin) { - if (lastCurrent < tripValue) { - // probably other track, do a fast toggle. - setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again - setPower(POWERMODE::ON); // maybe other track - // Write this after the fact as we want to turn on as fast as possible - // because we don't know which output actually triggered the fault pin - DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); - } - } else { + { if (lastCurrent < tripValue) { if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) { // Ignore 50ms fault pin if no current - DIAG(F("TRACK %c FAULT PIN 50ms ignore"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A'); break; } lastCurrent = tripValue; // exaggerate so condition below (*) is true @@ -417,11 +408,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) { // Ignore 5ms fault pin if we see current - DIAG(F("TRACK %c FAULT PIN 5ms ignore"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A'); break; } } - DIAG(F("TRACK %c FAULT PIN ACTIVE"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN"), trackno + 'A'); } } // // // @@ -432,7 +423,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // current is below trip value, turn off overload condition // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; - lastPowerChange = micros(); + setLastPowerChange(); } if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; @@ -443,7 +434,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // current is over trip value, turn on overload condition // DIAG(F("OVERLOAD PON ON")); overloadNow=true; - lastPowerChange = micros(); + setLastPowerChange(); } unsigned long uSecs = microsSinceLastPowerChange(); if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin @@ -454,24 +445,20 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // the setPower just turned off, so overload is now gone // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; - lastPowerChange = micros(); + setLastPowerChangeOverload(); } unsigned int mA=raw2mA(lastCurrent); unsigned int maxmA=raw2mA(tripValue); - DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown after %lus for %lus"), + DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"), trackno + 'A', mA, maxmA, uSecs, power_sample_overload_wait); } } break; case POWERMODE::OVERLOAD: - if (overloadNow) { - // state overload mode means power is off, turn off overload condition flag as well - // DIAG(F("OVERLOAD POVER OFF")); - overloadNow=false; - lastPowerChange = micros(); - } + { // Try setting it back on after the OVERLOAD_WAIT - if (microsSinceLastPowerChange() > power_sample_overload_wait) { + unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange()); + if (mslpc > power_sample_overload_wait) { // adjust next wait time power_sample_overload_wait *= 2; if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) @@ -481,10 +468,12 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // here we change power but not the overloadNow as that was // already changed to false when we entered POWERMODE::OVERLOAD // so we need to set the lastPowerChange anyway. - lastPowerChange = micros(); - DIAG(F("TRACK %c POWER RESTORE (was off %lus)"), trackno + 'A', power_sample_overload_wait); + overloadNow=false; + setLastPowerChange(); + DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc); } - break; + } + break; default: break; } diff --git a/MotorDriver.h b/MotorDriver.h index 1eb4e951e..b268aea2b 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -175,7 +175,10 @@ class MotorDriver { bool isPWMCapable(); bool canMeasureCurrent(); bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform - static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs + bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs + inline byte setCommonFaultPin() { + return commonFaultPin = true; + } inline byte getFaultPin() { return faultPin; } @@ -196,6 +199,17 @@ class MotorDriver { lastPowerChange = now - 30000000UL; // 30 seconds ago return diff; }; + inline void setLastPowerChange() { + lastPowerChange = micros(); + }; + // as setLastPowerChange but sets the global timestamp as well which + // is only used to sync power restore in case of common Fault pin. + inline void setLastPowerChangeOverload() { + if (commonFaultPin) + globalOverloadStart = lastPowerChange = micros(); + else + setLastPowerChange(); + }; #ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); @@ -228,7 +242,9 @@ class MotorDriver { // current sampling POWERMODE powerMode; bool overloadNow = false; - unsigned long lastPowerChange; // timestamp in microseconds + unsigned long lastPowerChange; // timestamp in microseconds + // used to sync restore time when common Fault pin detected + static unsigned long globalOverloadStart; // timestamp in microseconds int progTripValue; int lastCurrent; #ifdef ANALOG_READ_INTERRUPT diff --git a/TrackManager.cpp b/TrackManager.cpp index 9c0f0b04d..dd0fb4a77 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -123,9 +123,17 @@ void TrackManager::Setup(const FSH * shieldname, setTrackMode(1,TRACK_MODE_MAIN); #endif - // TODO Fault pin config for odd motor boards (example pololu) - // MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) - // && (mainDriver->getFaultPin() != UNUSED_PIN)); + // Fault pin config for odd motor boards (example pololu) + FOR_EACH_TRACK(t) { + for (byte s=t+1;s<=lastTrack;s++) { + if (track[t]->getFaultPin() != UNUSED_PIN && + track[t]->getFaultPin() == track[s]->getFaultPin()) { + track[t]->setCommonFaultPin(); + track[s]->setCommonFaultPin(); + DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A'); + } + } + } DCC::begin(shieldname); } From 0d679ad993662d9c5b6885bdb203a7759404686b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 21 Jun 2023 10:49:49 +0200 Subject: [PATCH 824/870] version --- GITHUB_SHA.h | 2 +- version.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8b6faac02..57a21870f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306190642Z" +#define GITHUB_SHA "devel-202306210849Z" diff --git a/version.h b/version.h index ab5609de7..fc470c878 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.56" +#define VERSION "4.2.57" +// 4.2.57 - New overload handling (faster and handles commonFaultPin again) +// - Optimize analog read STM32 // 4.2.56 - Update IO_RotaryEncoder.h: // - Improved I2C communication, non-blocking reads // - Enable sending positions to the encoder from EXRAIL via SERVO() From db555e882059199b94fc23be16e379ce23065fe6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 22 Jun 2023 22:57:59 +0200 Subject: [PATCH 825/870] Start motordriver as soon as possible but without waveform --- CommandStation-EX.ino | 8 +++----- DCC.cpp | 3 +-- DCC.h | 5 ++++- GITHUB_SHA.h | 2 +- TrackManager.cpp | 2 +- version.h | 3 ++- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 736356625..cbe55a35d 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -77,6 +77,8 @@ void setup() // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); +// Set up MotorDrivers early to initialize all pins + TrackManager::Setup(MOTOR_SHIELD_TYPE); DISPLAY_START ( // This block is still executed for DIAGS if display not in use @@ -104,11 +106,7 @@ void setup() // let's make sure to initialise the ADCee class! ADCee::begin(); // Responsibility 3: Start the DCC engine. - // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) - // Standard supported devices have pre-configured macros but custome hardware installations require - // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - TrackManager::Setup(MOTOR_SHIELD_TYPE); + DCC::begin(); // Start RMFT aka EX-RAIL (ignored if no automnation) RMFT::begin(); diff --git a/DCC.cpp b/DCC.cpp index c9535b2ef..30fcf5fe6 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10; FSH* DCC::shieldName=NULL; byte DCC::globalSpeedsteps=128; -void DCC::begin(const FSH * motorShieldName) { - shieldName=(FSH *)motorShieldName; +void DCC::begin() { StringFormatter::send(&USB_SERIAL,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); #ifndef DISABLE_EEPROM // Load stuff from EEprom diff --git a/DCC.h b/DCC.h index 15d5a4f7e..74b4e7743 100644 --- a/DCC.h +++ b/DCC.h @@ -51,7 +51,10 @@ const byte MAX_LOCOS = 30; class DCC { public: - static void begin(const FSH * motorShieldName); + static inline void setShieldName(const FSH * motorShieldName) { + shieldName=(FSH *)motorShieldName; + }; + static void begin(); static void loop(); // Public DCC API functions diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 57a21870f..549ccdaca 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306210849Z" +#define GITHUB_SHA "devel-202306222055Z" diff --git a/TrackManager.cpp b/TrackManager.cpp index dd0fb4a77..8f66677e9 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -134,7 +134,7 @@ void TrackManager::Setup(const FSH * shieldname, } } } - DCC::begin(shieldname); + DCC::setShieldName(shieldname); } void TrackManager::addTrack(byte t, MotorDriver* driver) { diff --git a/version.h b/version.h index fc470c878..75228d024 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.57" +#define VERSION "4.2.58" +// 4.2.58 - Start motordriver as soon as possible but without waveform // 4.2.57 - New overload handling (faster and handles commonFaultPin again) // - Optimize analog read STM32 // 4.2.56 - Update IO_RotaryEncoder.h: From 99b6ca025a9f841586131c35ea10c91853d860af Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 22 Jun 2023 23:30:28 +0200 Subject: [PATCH 826/870] move ADCee begin as well --- CommandStation-EX.ino | 9 +++++---- GITHUB_SHA.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index cbe55a35d..24a0aef22 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -77,7 +77,11 @@ void setup() // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); -// Set up MotorDrivers early to initialize all pins + + // As the setup of a motor shield may require a read of the current sense input from the ADC, + // let's make sure to initialise the ADCee class! + ADCee::begin(); + // Set up MotorDrivers early to initialize all pins TrackManager::Setup(MOTOR_SHIELD_TYPE); DISPLAY_START ( @@ -102,9 +106,6 @@ void setup() EthernetInterface::setup(); #endif // ETHERNET_ON - // As the setup of a motor shield may require a read of the current sense input from the ADC, - // let's make sure to initialise the ADCee class! - ADCee::begin(); // Responsibility 3: Start the DCC engine. DCC::begin(); diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 549ccdaca..c49561034 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306222055Z" +#define GITHUB_SHA "devel-202306222129Z" From 196a27a27abf1ddab5c685a7ed8489dd68306890 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 22 Jun 2023 19:47:20 -0400 Subject: [PATCH 827/870] Update WifiESP32.cpp (#338) * Update WifiESP32.cpp Fix SSID for AP from DCC_ to DCCEX_ * Update version.h to 4.2.59 --- WifiESP32.cpp | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 7754e114f..ef02d8dcf 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -176,7 +176,7 @@ bool WifiESP::setup(const char *SSid, } if (!haveSSID) { // prepare all strings - String strSSID("DCC_"); + String strSSID("DCCEX_"); String strPass("PASS_"); String strMac = WiFi.macAddress(); strMac.remove(0,9); diff --git a/version.h b/version.h index 75228d024..cfad079f3 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.58" +#define VERSION "4.2.59" +// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ // 4.2.58 - Start motordriver as soon as possible but without waveform // 4.2.57 - New overload handling (faster and handles commonFaultPin again) // - Optimize analog read STM32 From ec6e730559f3af3268dc22b6d44a31e62a03edd9 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 23 Jun 2023 18:08:05 +0800 Subject: [PATCH 828/870] ESP32 mDNS registration for throttle autodiscovery --- WifiESP32.cpp | 10 ++++++++++ platformio.ini | 2 ++ version.h | 3 ++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index ef02d8dcf..9a74138a9 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -20,6 +20,7 @@ #if defined(ARDUINO_ARCH_ESP32) #include #include "defines.h" +#include "ESPmDNS.h" #include #include "esp_wifi.h" #include "WifiESP32.h" @@ -209,6 +210,15 @@ bool WifiESP::setup(const char *SSid, // no idea to go on return false; } + + // Now Wifi is up, register the mDNS service + if(!MDNS.begin(hostname)) { + DIAG(F("Wifi setup failed to start mDNS")); + } + if(!MDNS.addService("withrottle", "tcp", 2560)) { + DIAG(F("Wifi setup failed to add withrottle service to mDNS")); + } + server = new WiFiServer(port); // start listening on tcp port server->begin(); // server started here diff --git a/platformio.ini b/platformio.ini index 3091d7fbe..1a8777020 100644 --- a/platformio.ini +++ b/platformio.ini @@ -173,6 +173,8 @@ board = esp32dev framework = arduino lib_deps = ${env.lib_deps} build_flags = -std=c++17 +monitor_speed = 115200 +monitor_echo = yes [env:Nucleo-F411RE] platform = ststm32 diff --git a/version.h b/version.h index cfad079f3..9fd3001cc 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.59" +#define VERSION "4.2.60" +// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery // 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ // 4.2.58 - Start motordriver as soon as possible but without waveform // 4.2.57 - New overload handling (faster and handles commonFaultPin again) From f3cb263aaac48fee2052af9b3fa0c47f2d594187 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 23 Jun 2023 13:54:25 +0200 Subject: [PATCH 829/870] convert mac addr hex chars to lower case to be compatible with AT software --- WifiESP32.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 9a74138a9..bc22d16d0 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -106,6 +106,12 @@ void wifiLoop(void *){ } #endif +char asciitolower(char in) { + if (in <= 'Z' && in >= 'A') + return in - ('Z' - 'z'); + return in; +} + bool WifiESP::setup(const char *SSid, const char *password, const char *hostname, @@ -183,6 +189,8 @@ bool WifiESP::setup(const char *SSid, strMac.remove(0,9); strMac.replace(":",""); strMac.replace(":",""); + // convert mac addr hex chars to lower case to be compatible with AT software + std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower); strSSID.concat(strMac); strPass.concat(strMac); From dfba6c6fc13ef8e3b0eb7c3521b3ff0aa8274a5f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 23 Jun 2023 13:55:34 +0200 Subject: [PATCH 830/870] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c49561034..6a1e6257f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306222129Z" +#define GITHUB_SHA "devel-202306231154Z" From 35fd912c6094c3fbe6ec76fd40e2288c4d6c2405 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 26 Jun 2023 20:00:03 +0200 Subject: [PATCH 831/870] MAX_CURRENT restriction (caps motor shield value) --- MotorDriver.cpp | 6 +++++- config.example.h | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index bf5c47b15..12a263c11 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -135,7 +135,11 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i // float calculations or libraray code. senseFactorInternal=sense_factor * senseScale; tripMilliamps=trip_milliamps; - rawCurrentTripValue=mA2raw(trip_milliamps); +#ifdef MAX_CURRENT + if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps) + tripMilliamps = MAX_CURRENT; +#endif + rawCurrentTripValue=mA2raw(tripMilliamps); if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) { // This would mean that the values obtained from the ADC never diff --git a/config.example.h b/config.example.h index 16402b6a9..a945ec972 100644 --- a/config.example.h +++ b/config.example.h @@ -57,6 +57,21 @@ The configuration file for DCC-EX Command Station // +-----------------------v // #define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD +// +///////////////////////////////////////////////////////////////////////////////////// +// +// If you want to restrict the maximum current LOWER than what your +// motor shield can provide, you can do that here. For example if you +// have a motor shield that can provide 5A and your power supply can +// only provide 2.5A then you should restict the maximum current to +// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before +// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example +// it would be 2250, adjust the number according to your PS. If your +// PS has a higher rating than your motor shield you do not need this. +// You can use this as well if you are cautious and your trains do not +// need full current. +// #define MAX_CURRENT 2250 +// ///////////////////////////////////////////////////////////////////////////////////// // // The IP port to talk to a WIFI or Ethernet shield. From c2fa76c76aba19039d9bf77a1a0754d06c4cd1f2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 26 Jun 2023 20:01:56 +0200 Subject: [PATCH 832/870] version 2.4.61 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6a1e6257f..6ae3e6c03 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306231154Z" +#define GITHUB_SHA "devel-202306261801Z" diff --git a/version.h b/version.h index 9fd3001cc..2325ceb36 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.60" +#define VERSION "4.2.61" +// 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery // 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ // 4.2.58 - Start motordriver as soon as possible but without waveform From 1bdb05a471e7f140b22fd51ae72834fe2e9d0ebc Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 29 Jun 2023 11:00:14 +0800 Subject: [PATCH 833/870] ESP32 now sets hostname to dccex in STA mode --- WifiESP32.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index bc22d16d0..6e09aa177 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -1,4 +1,5 @@ /* + © 2023, Paul M. Antoine © 2021, Harald Barth. This file is part of CommandStation-EX @@ -145,6 +146,7 @@ bool WifiESP::setup(const char *SSid, havePassword = false; if (haveSSID && havePassword) { + WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE! WiFi.mode(WIFI_STA); #ifdef SERIAL_BT_COMMANDS WiFi.setSleep(true); From 2eb0f4899456e9e292442e4f37251aa18293ac5c Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 29 Jun 2023 14:24:51 -0400 Subject: [PATCH 834/870] Update .gitignore Updated .gitignore from the devel branch and used "my@.cpp" instead of listing the individual files so we can ignore anything that starts with "my" --- .gitignore | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 2bf0b5e1c..32e70d64c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,12 +7,7 @@ Release/* .pio/ .vscode/ config.h -.vscode/* -mySetup.h -mySetup.cpp -myHal.cpp -myAutomation.h -myFilter.cpp -myAutomation.h -myFilter.cpp -myLayout.h +my*.cpp +my*.h +!my*.example.h +compile_commands.json From 9c5e48c3d5f9b4c6a99bb713468a8dc6bec54e88 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 30 Jun 2023 02:05:10 +0200 Subject: [PATCH 835/870] test more tolerant alg --- MotorDriver.cpp | 17 ++++++++++++----- MotorDriver.h | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 12a263c11..1d7b2bc4e 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -401,22 +401,26 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { lastCurrent = -lastCurrent; { if (lastCurrent < tripValue) { - if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin + if (/*power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) &&*/ // almost virgin microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) { // Ignore 50ms fault pin if no current DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A'); break; } - lastCurrent = tripValue; // exaggerate so condition below (*) is true + setPower(POWERMODE::OVERLOAD); + overloadNow=false; + DIAG(F("TRACK %c FAULT PIN"), trackno + 'A'); + break; + //lastCurrent = tripValue; // exaggerate so condition below (*) is true } else { - if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin + if (/*power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && */ // virgin microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) { // Ignore 5ms fault pin if we see current DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A'); break; } } - DIAG(F("TRACK %c FAULT PIN"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN AND OVERCURRENT"), trackno + 'A'); } } // // // @@ -441,8 +445,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { setLastPowerChange(); } unsigned long uSecs = microsSinceLastPowerChange(); - if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin + if (/*power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || */ // not virgin uSecs > POWER_SAMPLE_OFF_DELAY) { + /* + if (micros() - overloadStart > POWER_SAMPLE_OFF_DELAY) { + */ // Overload has existed longer than delay (typ. 10ms) setPower(POWERMODE::OVERLOAD); if (overloadNow) { diff --git a/MotorDriver.h b/MotorDriver.h index b268aea2b..b6f46700a 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -256,7 +256,7 @@ class MotorDriver { // Times for overload management. Unit: microseconds. // Base for wait time until power is turned on again - static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 100UL; + static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 10000UL; // Time after we consider all faults old and forgotten static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL; // How long to ignore fault pin if current is under limit @@ -264,7 +264,7 @@ class MotorDriver { // How long to ignore fault pin if current is higher than limit static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL; // How long to wait between overcurrent and turning off - static const unsigned long POWER_SAMPLE_OFF_DELAY = 10000UL; + static const unsigned long POWER_SAMPLE_OFF_DELAY = 100000UL; // Upper limit for retry period static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL; From 7c41ec7c25efc8d8576ffa8943b72f2135bd7986 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 30 Jun 2023 02:06:12 +0200 Subject: [PATCH 836/870] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6ae3e6c03..0f0532809 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306261801Z" +#define GITHUB_SHA "devel-overcurrent-202306300005Z" From 5d17f247de761a972af83b642668bd728e465644 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 1 Jul 2023 05:18:45 +1000 Subject: [PATCH 837/870] RotaryEnoder, EX-Turntable fixes --- IO_EXTurntable.h | 2 +- IO_RotaryEncoder.h | 8 ++++++-- version.h | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index 40c02eec7..29ce6796a 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -50,12 +50,12 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) { // Initialisation of EXTurntable void EXTurntable::_begin() { I2CManager.begin(); - I2CManager.setClock(1000000); if (I2CManager.exists(_I2CAddress)) { #ifdef DIAG_IO _display(); #endif } else { + DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString()); _deviceState = DEVSTATE_FAILED; } } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 42d8a2d32..9d40b345e 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -134,12 +134,13 @@ class RotaryEncoder : public IODevice { } } - // Device specific read function + // Return the position sent by the rotary encoder software int _readAnalogue(VPIN vpin) override { if (_deviceState == DEVSTATE_FAILED) return 0; return _position; } + // Send the feedback value to the rotary encoder software void _write(VPIN vpin, int value) override { if (vpin == _firstVpin + 1) { if (value != 0) value = 0x01; @@ -148,9 +149,12 @@ class RotaryEncoder : public IODevice { } } + // Send a position update to the rotary encoder software + // To be valid, must be 0 to 255, and different to the current position + // If the current position is the same, it was initiated by the rotary encoder void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { if (vpin == _firstVpin + 2) { - if (position >= 0 && position <= 255) { + if (position >= 0 && position <= 255 && position != _position) { byte newPosition = position & 0xFF; byte _positionBuffer[2] = {RE_MOVE, newPosition}; I2CManager.write(_I2CAddress, _positionBuffer, 2); diff --git a/version.h b/version.h index 2325ceb36..ee7eb491e 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.61" +#define VERSION "4.2.62" +// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position +// - Update IO_EXTurntable.h to remove forced I2C clock speed +// - Show device offline if EX-Turntable not connected // 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery // 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ From 70d4c016ef79e09af5189735154073061ff85693 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Jul 2023 01:33:41 +0200 Subject: [PATCH 838/870] completely new overcurrent detection --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 185 +++++++++++++++++++++++------------------------- MotorDriver.h | 48 +++++++------ version.h | 3 +- 4 files changed, 117 insertions(+), 121 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 0f0532809..07d912319 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202306300005Z" +#define GITHUB_SHA "devel-overcurrent-202307012332Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 1d7b2bc4e..e05059bfc 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -173,7 +173,11 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { if (powerMode == mode) return; - bool on=mode==POWERMODE::ON; + //DIAG(F("POWERMODE=%d"), (int)mode); + lastPowerChange[(int)mode] = micros(); + if (mode == POWERMODE::OVERLOAD) + globalOverloadStart = lastPowerChange[(int)mode]; + bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT); if (on) { // when switching a track On, we need to check the crrentOffset with the pin OFF if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) { @@ -213,8 +217,8 @@ bool MotorDriver::canMeasureCurrent() { return currentPin!=UNUSED_PIN; } /* - * Return the current reading as pin reading 0 to 1023. If the fault - * pin is activated return a negative current to show active fault pin. + * Return the current reading as pin reading 0 to max resolution (1024 or 4096). + * If the fault pin is activated return a negative current to show active fault pin. * As there is no -0, cheat a little and return -1 in that case. * * senseOffset handles the case where a shield returns values above or below @@ -373,118 +377,103 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res } void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { - int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); switch (powerMode) { - case POWERMODE::OFF: - if (overloadNow) { - // reset overload condition as we have just turned off power - // DIAG(F("OVERLOAD POFF OFF")); - overloadNow=false; - setLastPowerChange(); - } - if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { - power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + + case POWERMODE::OFF: { + lastPowerMode = POWERMODE::OFF; + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + break; + } + + case POWERMODE::ON: { + lastPowerMode = POWERMODE::ON; + bool cF = checkFault(); + bool cC = checkCurrent(useProgLimit); + if(cF || cC ) { + if (cC) { + unsigned int mA=raw2mA(lastCurrent); + DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A', + cF ? "FAULT" : "", + mA); + } else { + DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A'); } + setPower(POWERMODE::ALERT); break; - case POWERMODE::ON: - // Check current - lastCurrent=getCurrentRaw(); - if (lastCurrent < 0) { - // We have a fault pin condition to take care of - if (!overloadNow) { - // turn on overload condition as fault pin has gone active - // DIAG(F("OVERLOAD FPIN ON")); - overloadNow=true; - setLastPowerChangeOverload(); - } - lastCurrent = -lastCurrent; - { - if (lastCurrent < tripValue) { - if (/*power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) &&*/ // almost virgin - microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) { - // Ignore 50ms fault pin if no current - DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A'); - break; - } - setPower(POWERMODE::OVERLOAD); - overloadNow=false; - DIAG(F("TRACK %c FAULT PIN"), trackno + 'A'); - break; - //lastCurrent = tripValue; // exaggerate so condition below (*) is true - } else { - if (/*power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && */ // virgin - microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) { - // Ignore 5ms fault pin if we see current - DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A'); - break; - } - } - DIAG(F("TRACK %c FAULT PIN AND OVERCURRENT"), trackno + 'A'); - } + } + // all well + if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) { + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; + } + break; + } + + case POWERMODE::ALERT: { + // set local flags that handle how much is output to diag (do not output duplicates) + bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD); + bool newPowerMode = (powerMode != lastPowerMode); + unsigned long now = micros(); + if (newPowerMode) + lastBadSample = now; + lastPowerMode = POWERMODE::ALERT; + // check how long we have been in this state + unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT); + if(checkFault()) { + lastBadSample = now; + unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW; + if ( mslpc < timeout) { + if (newPowerMode) + DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout); + break; } - // // // - // above we looked at fault pin, below we look at current - // // // - if (lastCurrent < tripValue) { // see above (*) - if (overloadNow) { - // current is below trip value, turn off overload condition - // DIAG(F("OVERLOAD PON OFF")); - overloadNow=false; - setLastPowerChange(); - } - if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { - power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; - } - } else { - // too much current - if (!overloadNow) { - // current is over trip value, turn on overload condition - // DIAG(F("OVERLOAD PON ON")); - overloadNow=true; - setLastPowerChange(); - } - unsigned long uSecs = microsSinceLastPowerChange(); - if (/*power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || */ // not virgin - uSecs > POWER_SAMPLE_OFF_DELAY) { - /* - if (micros() - overloadStart > POWER_SAMPLE_OFF_DELAY) { - */ - // Overload has existed longer than delay (typ. 10ms) - setPower(POWERMODE::OVERLOAD); - if (overloadNow) { - // the setPower just turned off, so overload is now gone - // DIAG(F("OVERLOAD PON OFF")); - overloadNow=false; - setLastPowerChangeOverload(); - } + DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait); + setPower(POWERMODE::OVERLOAD); + break; + } + if (checkCurrent(useProgLimit)) { + lastBadSample = now; + if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) { + if (newPowerMode) { unsigned int mA=raw2mA(lastCurrent); - unsigned int maxmA=raw2mA(tripValue); - DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"), - trackno + 'A', mA, maxmA, uSecs, power_sample_overload_wait); + DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA); } + break; } + unsigned int mA=raw2mA(lastCurrent); + unsigned int maxmA=raw2mA(tripValue); + DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"), + trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait); + setPower(POWERMODE::OVERLOAD); break; - case POWERMODE::OVERLOAD: - { - // Try setting it back on after the OVERLOAD_WAIT - unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange()); + } + // all well + unsigned long goodtime = micros() - lastBadSample; + if (goodtime > POWER_SAMPLE_ALERT_GOOD) { + if (true || notFromOverload) { // we did a RESTORE message XXX + unsigned int mA=raw2mA(lastCurrent); + DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA); + } + setPower(POWERMODE::ON); + } + break; + } + + case POWERMODE::OVERLOAD: { + lastPowerMode = POWERMODE::OVERLOAD; + unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD)); if (mslpc > power_sample_overload_wait) { // adjust next wait time - power_sample_overload_wait *= 2; + power_sample_overload_wait *= 4; if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; // power on test - setPower(POWERMODE::ON); - // here we change power but not the overloadNow as that was - // already changed to false when we entered POWERMODE::OVERLOAD - // so we need to set the lastPowerChange anyway. - overloadNow=false; - setLastPowerChange(); DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc); + setPower(POWERMODE::ALERT); } + break; } - break; + default: break; } diff --git a/MotorDriver.h b/MotorDriver.h index b6f46700a..0ebf494d0 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -107,7 +107,7 @@ extern volatile portreg_t shadowPORTA; extern volatile portreg_t shadowPORTB; extern volatile portreg_t shadowPORTC; -enum class POWERMODE : byte { OFF, ON, OVERLOAD }; +enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT }; class MotorDriver { public: @@ -192,24 +192,13 @@ class MotorDriver { // this returns how much time has passed since the last power change. If it // was really long ago (approx > 52min) advance counter approx 35 min so that // we are at 18 minutes again. Times for 32 bit unsigned long. - inline unsigned long microsSinceLastPowerChange() { + inline unsigned long microsSinceLastPowerChange(POWERMODE mode) { unsigned long now = micros(); - unsigned long diff = now - lastPowerChange; + unsigned long diff = now - lastPowerChange[(int)mode]; if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds - lastPowerChange = now - 30000000UL; // 30 seconds ago + lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago return diff; }; - inline void setLastPowerChange() { - lastPowerChange = micros(); - }; - // as setLastPowerChange but sets the global timestamp as well which - // is only used to sync power restore in case of common Fault pin. - inline void setLastPowerChangeOverload() { - if (commonFaultPin) - globalOverloadStart = lastPowerChange = micros(); - else - setLastPowerChange(); - }; #ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); @@ -218,9 +207,22 @@ class MotorDriver { char trackLetter = '?'; bool isProgTrack = false; // tells us if this is a prog track void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); - void getFastPin(const FSH* type,int pin, FASTPIN & result) { + inline void getFastPin(const FSH* type,int pin, FASTPIN & result) { getFastPin(type, pin, 0, result); - } + }; + // side effect sets lastCurrent and tripValue + inline bool checkCurrent(bool useProgLimit) { + tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); + lastCurrent = getCurrentRaw(); + if (lastCurrent < 0) + lastCurrent = -lastCurrent; + return lastCurrent >= tripValue; + }; + // side effect sets lastCurrent + inline bool checkFault() { + lastCurrent = getCurrentRaw(); + return lastCurrent < 0; + }; VPIN powerPin; byte signalPin, signalPin2, currentPin, faultPin, brakePin; FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; @@ -241,12 +243,14 @@ class MotorDriver { int rawCurrentTripValue; // current sampling POWERMODE powerMode; - bool overloadNow = false; - unsigned long lastPowerChange; // timestamp in microseconds + POWERMODE lastPowerMode; + unsigned long lastPowerChange[4]; // timestamp in microseconds + unsigned long lastBadSample; // timestamp in microseconds // used to sync restore time when common Fault pin detected static unsigned long globalOverloadStart; // timestamp in microseconds int progTripValue; - int lastCurrent; + int lastCurrent; //temp value + int tripValue; //temp value #ifdef ANALOG_READ_INTERRUPT volatile unsigned long sampleCurrentTimestamp; volatile uint16_t sampleCurrent; @@ -259,12 +263,14 @@ class MotorDriver { static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 10000UL; // Time after we consider all faults old and forgotten static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL; + // Time after which we consider a ALERT over + static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL; // How long to ignore fault pin if current is under limit static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 50000UL; // How long to ignore fault pin if current is higher than limit static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL; // How long to wait between overcurrent and turning off - static const unsigned long POWER_SAMPLE_OFF_DELAY = 100000UL; + static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL; // Upper limit for retry period static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL; diff --git a/version.h b/version.h index 2325ceb36..3741dc4c3 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.61" +#define VERSION "4.2.62pre1" +// 4.2.62 - completely new overcurrent detection // 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery // 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ From ab1356d07061fd056da9dbe718206126297a2cbf Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Jul 2023 13:55:56 +0200 Subject: [PATCH 839/870] Change first join/unjoin and set power after that --- DCCEXParser.cpp | 4 ++-- GITHUB_SHA.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 1e3df9026..aa635ae02 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -487,9 +487,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif else break; // will reply } + TrackManager::setJoin(join); if (main) TrackManager::setMainPower(POWERMODE::ON); if (prog) TrackManager::setProgPower(POWERMODE::ON); - TrackManager::setJoin(join); CommandDistributor::broadcastPower(); return; @@ -516,12 +516,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else break; // will reply } + TrackManager::setJoin(false); if (main) TrackManager::setMainPower(POWERMODE::OFF); if (prog) { TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off TrackManager::setProgPower(POWERMODE::OFF); } - TrackManager::setJoin(false); CommandDistributor::broadcastPower(); return; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 07d912319..c676511b5 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307012332Z" +#define GITHUB_SHA "devel-overcurrent-202307021155Z" From 10c59028e1525f1c12949d2c4a5434183cb241c8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Jul 2023 20:33:29 +0200 Subject: [PATCH 840/870] Add documentation --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c676511b5..ddcb181ba 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307021155Z" +#define GITHUB_SHA "devel-overcurrent-202307021833Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index e05059bfc..64bb8ca95 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -173,7 +173,7 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { if (powerMode == mode) return; - //DIAG(F("POWERMODE=%d"), (int)mode); + //DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode); lastPowerChange[(int)mode] = micros(); if (mode == POWERMODE::OVERLOAD) globalOverloadStart = lastPowerChange[(int)mode]; @@ -376,6 +376,67 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH); } +/////////////////////////////////////////////////////////////////////////////////////////// +// checkPowerOverload(useProgLimit, trackno) +// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode +// byte trackno: trackmanager knows it's number (could be skipped?) +// +// Short ciruit handling strategy: +// +// There are the following power states: ON ALERT OVERLOAD OFF +// OFF state is only changed to/from manually. Power is on +// during ON and ALERT. Power is off during OVERLOAD and OFF. +// The overload mechanism changes between the other states like +// +// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON +// or +// ON -1-> ALERT -4-> ON +// +// Times are in class MotorDriver (MotorDriver.h). +// +// 1. ON to ALERT: +// Transition on fault pin condition or current overload +// +// 2. ALERT to OVERLOAD: +// Transition happens if different timeouts have elapsed. +// If only the fault pin is active, timeout is +// POWER_SAMPLE_IGNORE_FAULT_LOW (50ms) +// If only overcurrent is detected, timeout is +// POWER_SAMPLE_IGNORE_CURRENT (100ms) +// If fault pin and overcurrent are active, timeout is +// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms) +// Transition to OVERLOAD turns off power to the affected +// output (unless fault pins are shared) +// If the transition conditions are not fullfilled, +// transition according to 4 is tested. +// +// 3. OVERLOAD to ALERT +// Transiton happens when timeout has elapsed, timeout +// is named power_sample_overload_wait. It is started +// at POWER_SAMPLE_OVERLOAD_WAIT (10ms) at first entry +// to OVERLOAD and then increased by a factor of 2 +// at further entries to the OVERLOAD condition. This +// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached. +// power_sample_overload_wait is reset by a poweroff or +// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON. +// After timeout power is turned on again and state +// goes back to ALERT. +// +// 4. ALERT to ON +// Transition happens by watching the current and fault pin +// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If +// values have been good during that time, transition is +// made back to ON. Note that even if state is back to ON, +// the power_sample_overload_wait time is first reset +// later (see above). +// +// The time keeping is handled by timestamps lastPowerChange[] +// which are set by each power change and by lastBadSample which +// keeps track if conditions during ALERT have been good enough +// to go back to ON. The time differences are calculated by +// microsSinceLastPowerChange(). +// + void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { switch (powerMode) { From 96a46f36c2a4008b11377b82352d7f39b02911ec Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 3 Jul 2023 00:21:52 +0200 Subject: [PATCH 841/870] Adjust overcurrent timeouts --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 6 +++--- MotorDriver.h | 4 ++-- version.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index ddcb181ba..f2440cb24 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307021833Z" +#define GITHUB_SHA "devel-overcurrent-202307022222Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 64bb8ca95..dc820444d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -400,7 +400,7 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // 2. ALERT to OVERLOAD: // Transition happens if different timeouts have elapsed. // If only the fault pin is active, timeout is -// POWER_SAMPLE_IGNORE_FAULT_LOW (50ms) +// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms) // If only overcurrent is detected, timeout is // POWER_SAMPLE_IGNORE_CURRENT (100ms) // If fault pin and overcurrent are active, timeout is @@ -413,7 +413,7 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // 3. OVERLOAD to ALERT // Transiton happens when timeout has elapsed, timeout // is named power_sample_overload_wait. It is started -// at POWER_SAMPLE_OVERLOAD_WAIT (10ms) at first entry +// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry // to OVERLOAD and then increased by a factor of 2 // at further entries to the OVERLOAD condition. This // happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached. @@ -525,7 +525,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD)); if (mslpc > power_sample_overload_wait) { // adjust next wait time - power_sample_overload_wait *= 4; + power_sample_overload_wait *= 2; if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; // power on test diff --git a/MotorDriver.h b/MotorDriver.h index 0ebf494d0..454015df6 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -260,13 +260,13 @@ class MotorDriver { // Times for overload management. Unit: microseconds. // Base for wait time until power is turned on again - static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 10000UL; + static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL; // Time after we consider all faults old and forgotten static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL; // Time after which we consider a ALERT over static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL; // How long to ignore fault pin if current is under limit - static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 50000UL; + static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL; // How long to ignore fault pin if current is higher than limit static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL; // How long to wait between overcurrent and turning off diff --git a/version.h b/version.h index 3741dc4c3..e445bb154 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.62pre1" +#define VERSION "4.2.62pre2" // 4.2.62 - completely new overcurrent detection // 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery From ae2bbbf668f4233600d6415caa0a8db6782a8dfa Mon Sep 17 00:00:00 2001 From: Nathan Kellenicki Date: Wed, 28 Jun 2023 22:29:53 -0700 Subject: [PATCH 842/870] Added WIFI_FORCE_AP to force AP mode when specifying SSID/pass --- CommandStation-EX.ino | 4 ++-- WifiESP32.cpp | 29 ++++++++++++++++------------- WifiESP32.h | 3 ++- WifiInterface.cpp | 36 +++++++++++++++++++++--------------- WifiInterface.h | 7 ++++--- config.example.h | 5 +++++ 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 24a0aef22..a6885ff13 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -95,11 +95,11 @@ void setup() // Start Ethernet if it exists #ifndef ARDUINO_ARCH_ESP32 #if WIFI_ON - WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL); + WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); #endif // WIFI_ON #else // ESP32 needs wifi on always - WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL); + WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); #endif // ARDUINO_ARCH_ESP32 #if ETHERNET_ON diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 6e09aa177..3fe91ae1e 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -117,7 +117,8 @@ bool WifiESP::setup(const char *SSid, const char *password, const char *hostname, int port, - const byte channel) { + const byte channel, + const bool forceAP) { bool havePassword = true; bool haveSSID = true; bool wifiUp = false; @@ -145,7 +146,7 @@ bool WifiESP::setup(const char *SSid, if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0) havePassword = false; - if (haveSSID && havePassword) { + if (haveSSID && havePassword && !forceAP) { WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE! WiFi.mode(WIFI_STA); #ifdef SERIAL_BT_COMMANDS @@ -183,18 +184,20 @@ bool WifiESP::setup(const char *SSid, } } } - if (!haveSSID) { + if (!haveSSID || forceAP) { // prepare all strings - String strSSID("DCCEX_"); - String strPass("PASS_"); - String strMac = WiFi.macAddress(); - strMac.remove(0,9); - strMac.replace(":",""); - strMac.replace(":",""); - // convert mac addr hex chars to lower case to be compatible with AT software - std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower); - strSSID.concat(strMac); - strPass.concat(strMac); + String strSSID(forceAP ? SSid : "DCCEX_"); + String strPass(forceAP ? password : "PASS_"); + if (!forceAP) { + String strMac = WiFi.macAddress(); + strMac.remove(0,9); + strMac.replace(":",""); + strMac.replace(":",""); + // convert mac addr hex chars to lower case to be compatible with AT software + std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower); + strSSID.concat(strMac); + strPass.concat(strMac); + } WiFi.mode(WIFI_AP); #ifdef SERIAL_BT_COMMANDS diff --git a/WifiESP32.h b/WifiESP32.h index 100e3931b..1cf0d917a 100644 --- a/WifiESP32.h +++ b/WifiESP32.h @@ -31,7 +31,8 @@ class WifiESP const char *wifiPassword, const char *hostname, const int port, - const byte channel); + const byte channel, + const bool forceAP); static void loop(); private: }; diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 3a88aea2c..c377319f1 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -83,7 +83,8 @@ bool WifiInterface::setup(long serial_link_speed, const FSH *wifiPassword, const FSH *hostname, const int port, - const byte channel) { + const byte channel, + const bool forceAP) { wifiSerialState wifiUp = WIFI_NOAT; @@ -100,7 +101,7 @@ bool WifiInterface::setup(long serial_link_speed, // See if the WiFi is attached to the first serial port #if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS) SERIAL1.begin(serial_link_speed); - wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel); + wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP); #endif // Other serials are tried, depending on hardware. @@ -110,7 +111,7 @@ bool WifiInterface::setup(long serial_link_speed, if (wifiUp == WIFI_NOAT) { Serial2.begin(serial_link_speed); - wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel); + wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP); } #endif #endif @@ -121,7 +122,7 @@ bool WifiInterface::setup(long serial_link_speed, if (wifiUp == WIFI_NOAT) { SERIAL3.begin(serial_link_speed); - wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel); + wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP); } #endif @@ -139,7 +140,7 @@ bool WifiInterface::setup(long serial_link_speed, } wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password, - const FSH* hostname, int port, byte channel) { + const FSH* hostname, int port, byte channel, bool forceAP) { wifiSerialState wifiState; static uint8_t ntry = 0; ntry++; @@ -148,7 +149,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con DIAG(F("++ Wifi Setup Try %d ++"), ntry); - wifiState = setup2( SSid, password, hostname, port, channel); + wifiState = setup2( SSid, password, hostname, port, channel, forceAP); if (wifiState == WIFI_NOAT) { LCD(4, F("WiFi no AT chip")); @@ -172,7 +173,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con #pragma GCC diagnostic ignored "-Wunused-parameter" #endif wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, - const FSH* hostname, int port, byte channel) { + const FSH* hostname, int port, byte channel, bool forceAP) { bool ipOK = false; bool oldCmd = false; @@ -225,7 +226,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, if (!checkForOK(1000, F("0.0.0.0"), true,false)) ipOK = true; } - } else { + } else if (!forceAP) { // SSID was configured, so we assume station (client) mode. if (oldCmd) { // AT command early version supports CWJAP/CWSAP @@ -285,14 +286,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, i=0; do { - if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) { - // unconfigured - StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"), - oldCmd ? "" : "_CUR", macTail, macTail, channel); + if (!forceAP) { + if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) { + // unconfigured + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"), + oldCmd ? "" : "_CUR", macTail, macTail, channel); + } else { + // password configured by user + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", + macTail, password, channel); + } } else { - // password configured by user - StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", - macTail, password, channel); + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%s\",\"%s\",%d,4\r\n"), + oldCmd ? "" : "_CUR", SSid, password, channel); } } while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok if (i >= 2) diff --git a/WifiInterface.h b/WifiInterface.h index 7c0a43330..33bc2113b 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -36,17 +36,18 @@ class WifiInterface const FSH *wifiPassword, const FSH *hostname, const int port, - const byte channel); + const byte channel, + const bool forceAP); static void loop(); static void ATCommand(HardwareSerial * stream,const byte *command); private: static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password, - const FSH *hostname, int port, byte channel); + const FSH *hostname, int port, byte channel, bool forceAP); static Stream *wifiStream; static DCCEXParser parser; static wifiSerialState setup2(const FSH *SSSid, const FSH *password, - const FSH *hostname, int port, byte channel); + const FSH *hostname, int port, byte channel, bool forceAP); static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true); static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true); static bool connected; diff --git a/config.example.h b/config.example.h index a945ec972..9fe613573 100644 --- a/config.example.h +++ b/config.example.h @@ -123,6 +123,11 @@ The configuration file for DCC-EX Command Station // this line exists or not. If you need to use an alternate channel (we recommend // using only 1,6, or 11) you may change it here. #define WIFI_CHANNEL 1 +// +// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this +// true. Otherwise it is assumed that you'd like to connect to an existing network +// with that SSID. +#define WIFI_FORCE_AP false ///////////////////////////////////////////////////////////////////////////////////// // From b3251e89d7371c486c5d7dc1281c520e85d4dca9 Mon Sep 17 00:00:00 2001 From: Nathan Kellenicki Date: Sun, 2 Jul 2023 19:50:38 -0700 Subject: [PATCH 843/870] Fixed Arduino --- WifiInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c377319f1..bd9cf2208 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -297,7 +297,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, macTail, password, channel); } } else { - StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%s\",\"%s\",%d,4\r\n"), + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", SSid, password, channel); } } while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok From e6a40e622c7ab89ee94335595ad4de245a9e6ecc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 3 Jul 2023 23:43:21 +0200 Subject: [PATCH 844/870] download graphic installer if DISPLAY --- installer.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/installer.sh b/installer.sh index 17a9c042c..4821284a3 100755 --- a/installer.sh +++ b/installer.sh @@ -1,7 +1,7 @@ #!/bin/bash # -# © 2022 Harald Barth +# © 2022,2023 Harald Barth # # This file is part of CommandStation-EX # @@ -29,14 +29,28 @@ ACLI="./bin/arduino-cli" function need () { type -p $1 > /dev/null && return + dpkg -l $1 2>&1 | egrep ^ii >/dev/null && return sudo apt-get install $1 type -p $1 > /dev/null && return echo "Could not install $1, abort" exit 255 } - need git + +if [ x$DISPLAY != x ] ; then + # we have DISPLAY, do the graphic thing + need python3-tk + need python3.8-venv + mkdir -p ~/ex-installer/venv + python3 -m venv ~/ex-installer/venv + cd ~/ex-installer/venv || exit 255 + source ./bin/activate + git clone https://github.com/DCC-EX/EX-Installer + cd EX-Installer || exit 255 + pip3 install -r requirements.txt + exec python3 -m ex_installer +fi if test -d `basename "$DCCEXGITURL"` ; then : assume we are almost there cd `basename "$DCCEXGITURL"` || exit 255 From f19db3aa5cc956833cb59198841542c796425d8f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Jul 2023 16:25:15 +0200 Subject: [PATCH 845/870] DISABLE_PROG does count as less RAM as well --- GITHUB_SHA.h | 2 +- defines.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f2440cb24..623806628 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307022222Z" +#define GITHUB_SHA "devel-overcurrent-202307041424Z" diff --git a/defines.h b/defines.h index 48f7c6bc7..f40ed2fec 100644 --- a/defines.h +++ b/defines.h @@ -205,7 +205,7 @@ #define WIFI_SERIAL_LINK_SPEED 115200 #if __has_include ( "myAutomation.h") - #if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) + #if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG) #define EXRAIL_ACTIVE #else #define EXRAIL_WARNING From c2fcdddd1f079c98d5746b9c561e7d40e2c292ce Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 6 Jul 2023 15:19:44 +0200 Subject: [PATCH 846/870] ESP32 protect from race in RMT code --- DCCRMT.cpp | 4 ++++ DCCWaveform.cpp | 3 +++ GITHUB_SHA.h | 2 +- version.h | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 631cc16b9..cbd9af69e 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -194,8 +194,10 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit setEOT(data + bitcounter++); // EOT marker dataLen = bitcounter; + noInterrupts(); // keep dataReady and dataRepeat consistnet to each other dataReady = true; dataRepeat = repeatCount+1; // repeatCount of 0 means send once + interrupts(); return 0; } @@ -212,6 +214,8 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() { if (dataReady) { // if we have new data, fill while preamble is running rmt_fill_tx_items(channel, data, dataLen, preambleLen-1); dataReady = false; + if (dataRepeat == 0) // all data should go out at least once + DIAG(F("Channel %d DCC signal lost data"), channel); } if (dataRepeat > 0) // if a repeat count was specified, work on that dataRepeat--; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index e06564801..4a9999736 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -247,6 +247,9 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea pendingPacket[byteCount] = checksum; pendingLength = byteCount + 1; pendingRepeats = repeats; +// DIAG repeated commands (accesories) +// if (pendingRepeats > 0) +// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG"); // The resets will be zero not only now but as well repeats packets into the future clearResets(repeats+1); { diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 623806628..2b88c4b8e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307041424Z" +#define GITHUB_SHA "devel-overcurrent-202307061318Z" diff --git a/version.h b/version.h index e445bb154..93df89112 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.62pre2" +#define VERSION "4.2.62pre3" // 4.2.62 - completely new overcurrent detection +// - ESP32 protect from race in RMT code // 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery // 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ From 4192c1f5a331a0127a20cdd0201b2e720cd906d8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 6 Jul 2023 16:58:36 +0200 Subject: [PATCH 847/870] Do not invoke graphical install on Raspbian --- GITHUB_SHA.h | 2 +- installer.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 2b88c4b8e..5c0ffb6ad 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307061318Z" +#define GITHUB_SHA "devel-overcurrent-202307061457Z" diff --git a/installer.sh b/installer.sh index 4821284a3..857710e72 100755 --- a/installer.sh +++ b/installer.sh @@ -38,6 +38,11 @@ function need () { need git +if cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then + # we are on a raspi where we do not support graphical + unset DISPLAY +fi + if [ x$DISPLAY != x ] ; then # we have DISPLAY, do the graphic thing need python3-tk From b44bebc1c6bb3320077982b3e7bd3c7d22ecf581 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 8 Jul 2023 08:58:00 +0200 Subject: [PATCH 848/870] copyright, version and compile warnings fix --- CommandStation-EX.ino | 1 + GITHUB_SHA.h | 2 +- WifiESP32.cpp | 5 +++-- WifiESP32.h | 3 ++- WifiInterface.cpp | 2 ++ WifiInterface.h | 1 + config.example.h | 1 + version.h | 3 ++- 8 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index a6885ff13..77e8f40ea 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -30,6 +30,7 @@ * © 2021 Neil McKechnie * © 2020-2021 Chris Harlow, Harald Barth, David Cutting, * Fred Decker, Gregor Baues, Anthony W - Dayton + * © 2023 Nathan Kellenicki * All rights reserved. * * This file is part of CommandStation-EX diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 5c0ffb6ad..5ec114e61 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307061457Z" +#define GITHUB_SHA "devel-202307080654Z" diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 3fe91ae1e..28a15feda 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -1,6 +1,7 @@ /* - © 2023, Paul M. Antoine - © 2021, Harald Barth. + © 2023 Paul M. Antoine + © 2021 Harald Barth + © 2023 Nathan Kellenicki This file is part of CommandStation-EX diff --git a/WifiESP32.h b/WifiESP32.h index 1cf0d917a..ec2f560a3 100644 --- a/WifiESP32.h +++ b/WifiESP32.h @@ -1,5 +1,6 @@ /* - * © 2021, Harald Barth. + * © 2021 Harald Barth + * © 2023 Nathan Kellenicki * * This file is part of CommandStation-EX * diff --git a/WifiInterface.cpp b/WifiInterface.cpp index bd9cf2208..20dc2356f 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -2,6 +2,7 @@ * © 2021 Fred Decker * © 2020-2022 Harald Barth * © 2020-2022 Chris Harlow + * © 2023 Nathan Kellenicki * All rights reserved. * * This file is part of CommandStation-EX @@ -96,6 +97,7 @@ bool WifiInterface::setup(long serial_link_speed, (void) hostname; (void) port; (void) channel; + (void) forceAP; #endif // See if the WiFi is attached to the first serial port diff --git a/WifiInterface.h b/WifiInterface.h index 33bc2113b..652156dfb 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -1,6 +1,7 @@ /* * © 2020-2021 Chris Harlow * © 2020, Harald Barth. + * © 2023 Nathan Kellenicki * All rights reserved. * * This file is part of CommandStation-EX diff --git a/config.example.h b/config.example.h index 9fe613573..0f136f949 100644 --- a/config.example.h +++ b/config.example.h @@ -4,6 +4,7 @@ * © 2020-2023 Harald Barth * © 2020-2021 Fred Decker * © 2020-2021 Chris Harlow + * © 2023 Nathan Kellenicki * * This file is part of CommandStation-EX * diff --git a/version.h b/version.h index 0962b7657..7f6bdc756 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.63" +#define VERSION "4.2.64" +// 4.2.64 - new config WIFI_FORCE_AP option // 4.2.63 - completely new overcurrent detection // - ESP32 protect from race in RMT code // 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position From aa1f25fc7292bce7240a190f844a20258973fcaf Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 9 Jul 2023 12:04:40 +0200 Subject: [PATCH 849/870] Set WIFI_FORCE_AP default as false --- GITHUB_SHA.h | 2 +- defines.h | 9 +++++++++ version.h | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 5ec114e61..e226e4a37 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307080654Z" +#define GITHUB_SHA "devel-202307091003Z" diff --git a/defines.h b/defines.h index f40ed2fec..f3822cafe 100644 --- a/defines.h +++ b/defines.h @@ -182,6 +182,15 @@ #define WIFI_ON false #endif +#ifndef WIFI_FORCE_AP + #define WIFI_FORCE_AP false +#else + #if WIFI_FORCE_AP==true || WIFI_FORCE_AP==false + #else + #error WIFI_FORCE_AP needs to be true or false + #endif +#endif + #if ENABLE_ETHERNET #if defined(HAS_ENOUGH_MEMORY) #define ETHERNET_ON true diff --git a/version.h b/version.h index 7f6bdc756..f1f1ca504 100644 --- a/version.h +++ b/version.h @@ -3,8 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.64" -// 4.2.64 - new config WIFI_FORCE_AP option +#define VERSION "4.2.65" +// 4.2.65 - new config WIFI_FORCE_AP option // 4.2.63 - completely new overcurrent detection // - ESP32 protect from race in RMT code // 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position From 0edf34bfe23b90fbd84757b7ea1be46c5e2f4ba4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 14 Jul 2023 23:10:50 +0200 Subject: [PATCH 850/870] inrush test ESP32 only --- MotorDriver.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index dc820444d..a9cc971ca 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -481,6 +481,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // check how long we have been in this state unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT); if(checkFault()) { +#define INRUSH +#ifdef INRUSH + DCCTimer::DCCEXanalogWrite(brakePin,208); + DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500); +#endif lastBadSample = now; unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW; if ( mslpc < timeout) { @@ -489,6 +494,9 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { break; } DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait); +#ifdef INRUSH + DCCTimer::DCCEXanalogWrite(brakePin,0); +#endif setPower(POWERMODE::OVERLOAD); break; } @@ -505,6 +513,9 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned int maxmA=raw2mA(tripValue); DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"), trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait); +#ifdef INRUSH + DCCTimer::DCCEXanalogWrite(brakePin,0); +#endif setPower(POWERMODE::OVERLOAD); break; } @@ -515,6 +526,9 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned int mA=raw2mA(lastCurrent); DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA); } +#ifdef INRUSH + DCCTimer::DCCEXanalogWrite(brakePin,0); +#endif setPower(POWERMODE::ON); } break; From 6476a7aac20ca6f81dca428562a9c02b4b9fed31 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 14 Jul 2023 23:11:22 +0200 Subject: [PATCH 851/870] version --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index e226e4a37..c12d38b9e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307091003Z" +#define GITHUB_SHA "devel-202307142111Z" From 6036ff9b155160bf259171e4a42d2c9f5acf7c1f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 17 Jul 2023 02:22:35 +0200 Subject: [PATCH 852/870] ESP32: ledcSetup before ledcAttach --- DCCTimerESP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DCCTimerESP.cpp b/DCCTimerESP.cpp index cf5978e61..7ed3f28ee 100644 --- a/DCCTimerESP.cpp +++ b/DCCTimerESP.cpp @@ -180,8 +180,8 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) { return; } pin_to_channel[pin] = --cnt_channel; - ledcAttachPin(pin, cnt_channel); ledcSetup(cnt_channel, 1000, 8); + ledcAttachPin(pin, cnt_channel); } else { ledcAttachPin(pin, pin_to_channel[pin]); } From 9b75026eefb51d3f047b4bd77eb25fc67cad0e84 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 17 Jul 2023 02:26:29 +0200 Subject: [PATCH 853/870] change from trackMode[t] to track[t]->{get,set}Mode --- MotorDriver.h | 11 +++++++++++ TrackManager.cpp | 41 ++++++++++++++++++++--------------------- TrackManager.h | 5 ----- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/MotorDriver.h b/MotorDriver.h index 454015df6..8458de1bf 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -27,6 +27,10 @@ #include "IODevice.h" #include "DCCTimer.h" +// use powers of two so we can do logical and/or on the track modes in if clauses. +enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, + TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32}; + #define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH #define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW #define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) @@ -203,6 +207,12 @@ class MotorDriver { bool sampleCurrentFromHW(); void startCurrentFromHW(); #endif + inline void setMode(TRACK_MODE m) { + trackMode = m; + }; + inline TRACK_MODE getMode() { + return trackMode; + }; private: char trackLetter = '?'; bool isProgTrack = false; // tells us if this is a prog track @@ -279,6 +289,7 @@ class MotorDriver { static const int TRIP_CURRENT_PROG=250; unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; unsigned int power_good_counter = 0; + TRACK_MODE trackMode = TRACK_MODE_OFF; // we assume off at startup }; #endif diff --git a/TrackManager.cpp b/TrackManager.cpp index 8f66677e9..a23140c2f 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -31,7 +31,7 @@ #define APPLY_BY_MODE(findmode,function) \ FOR_EACH_TRACK(t) \ - if (trackMode[t]==findmode) \ + if (track[t]->getMode()==findmode) \ track[t]->function; #ifndef DISABLE_PROG const int16_t HASH_KEYWORD_PROG = -29718; @@ -44,7 +44,6 @@ const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii. MotorDriver * TrackManager::track[MAX_TRACKS]; -TRACK_MODE TrackManager::trackMode[MAX_TRACKS]; int16_t TrackManager::trackDCAddr[MAX_TRACKS]; POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF; @@ -74,7 +73,7 @@ void TrackManager::sampleCurrent() { waiting = false; tr++; if (tr > lastTrack) tr = 0; - if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) { + if (lastTrack < 2 || track[tr]->getMode() & TRACK_MODE_PROG) { return; // We could continue but for prog track we // rather do it in next interrupt beacuse // that gives us well defined sampling point. @@ -85,7 +84,7 @@ void TrackManager::sampleCurrent() { if (!waiting) { // look for a valid track to sample or until we are around while (true) { - if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) { + if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) { track[tr]->startCurrentFromHW(); // for scope debug track[1]->setBrake(1); waiting = true; @@ -138,10 +137,10 @@ void TrackManager::Setup(const FSH * shieldname, } void TrackManager::addTrack(byte t, MotorDriver* driver) { - trackMode[t]=TRACK_MODE_OFF; track[t]=driver; if (driver) { track[t]->setPower(POWERMODE::OFF); + track[t]->setMode(TRACK_MODE_OFF); track[t]->setTrackLetter('A'+t); lastTrack=t; } @@ -183,8 +182,8 @@ void TrackManager::setPROGSignal( bool on) { void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { FOR_EACH_TRACK(t) { if (trackDCAddr[t]!=cab) continue; - if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); - else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); + if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); + else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); } } @@ -218,9 +217,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr #endif // only allow 1 track to be prog FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) { + if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) { track[t]->setPower(POWERMODE::OFF); - trackMode[t]=TRACK_MODE_OFF; + track[t]->setMode(TRACK_MODE_OFF); track[t]->makeProgTrack(false); // revoke prog track special handling streamTrackState(NULL,t); } @@ -228,7 +227,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr } else { track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type } - trackMode[trackToSet]=mode; + track[trackToSet]->setMode(mode); trackDCAddr[trackToSet]=dcAddr; streamTrackState(NULL,trackToSet); @@ -255,7 +254,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr // DC tracks must not have the DCC PWM switched on // so we globally turn it off if one of the PWM // capable tracks is now DC or DCX. - if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) { + if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) { if (track[t]->isPWMCapable()) { canDo=false; // this track is capable but can not run PWM break; // in this mode, so abort and prevent globally below @@ -263,7 +262,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr track[t]->trackPWM=false; // this track sure can not run with PWM //DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A'); } - } else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) { + } else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) { track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here //DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM); canDo &= track[t]->trackPWM; @@ -301,7 +300,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr void TrackManager::applyDCSpeed(byte t) { uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]); - if (trackMode[t]==TRACK_MODE_DCX) + if (track[t]->getMode()==TRACK_MODE_DCX) speedByte = speedByte ^ 128; // reverse direction bit track[t]->setDCSignal(speedByte); } @@ -347,7 +346,7 @@ void TrackManager::streamTrackState(Print* stream, byte t) { // null stream means send to commandDistributor for broadcast if (track[t]==NULL) return; auto format=F(""); - switch(trackMode[t]) { + switch(track[t]->getMode()) { case TRACK_MODE_MAIN: format=F("<= %c MAIN>\n"); break; @@ -387,13 +386,13 @@ void TrackManager::loop() { if (nextCycleTrack>lastTrack) nextCycleTrack=0; if (track[nextCycleTrack]==NULL) return; MotorDriver * motorDriver=track[nextCycleTrack]; - bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG; + bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG; motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack); } MotorDriver * TrackManager::getProgDriver() { FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_PROG) return track[t]; + if (track[t]->getMode()==TRACK_MODE_PROG) return track[t]; return NULL; } @@ -401,7 +400,7 @@ MotorDriver * TrackManager::getProgDriver() { std::vectorTrackManager::getMainDrivers() { std::vector v; FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]); + if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]); return v; } #endif @@ -411,7 +410,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) { FOR_EACH_TRACK(t) { MotorDriver * driver=track[t]; if (!driver) continue; - switch (trackMode[t]) { + switch (track[t]->getMode()) { case TRACK_MODE_MAIN: if (setProg) break; // toggle brake before turning power on - resets overcurrent error @@ -447,8 +446,8 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) { POWERMODE TrackManager::getProgPower() { FOR_EACH_TRACK(t) - if (trackMode[t]==TRACK_MODE_PROG) - return track[t]->getPower(); + if (track[t]->getMode()==TRACK_MODE_PROG) + return track[t]->getPower(); return POWERMODE::OFF; } @@ -492,7 +491,7 @@ void TrackManager::setJoin(bool joined) { #ifdef ARDUINO_ARCH_ESP32 if (joined) { FOR_EACH_TRACK(t) { - if (trackMode[t]==TRACK_MODE_PROG) { + if (track[t]->getMode()==TRACK_MODE_PROG) { tempProgTrack = t; setTrackMode(t, TRACK_MODE_MAIN); break; diff --git a/TrackManager.h b/TrackManager.h index 19e756d59..965cfa346 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -27,10 +27,6 @@ #include "MotorDriver.h" // Virtualised Motor shield multi-track hardware Interface -// use powers of two so we can do logical and/or on the track modes in if clauses. -enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, - TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32}; - // These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0; const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1; @@ -100,7 +96,6 @@ class TrackManager { static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); - static TRACK_MODE trackMode[MAX_TRACKS]; static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX #ifdef ARDUINO_ARCH_ESP32 static byte tempProgTrack; // holds the prog track number during join From ec0499e9da17b0eb2c23053cde959bef5e71f731 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 17 Jul 2023 02:30:11 +0200 Subject: [PATCH 854/870] throttleInrush() (tested on ESP32) --- MotorDriver.cpp | 46 +++++++++++++++++++++++++++++++--------------- MotorDriver.h | 1 + 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index a9cc971ca..ee154f027 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -347,7 +347,33 @@ void MotorDriver::setDCSignal(byte speedcode) { interrupts(); } } - +void MotorDriver::throttleInrush(bool on) { + if (brakePin == UNUSED_PIN) + return; + byte duty = on ? 208 : 0; + if (invertBrake) + duty = 255-duty; +#if defined(ARDUINO_ARCH_ESP32) + if(on) { + DCCTimer::DCCEXanalogWrite(brakePin,duty); + DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500); + } else { + ledcDetachPin(brakePin); + } +#else + if(on){ +#if defined(ARDUINO_AVR_UNO) + TCCR2B = (TCCR2B & B11111000) | B00000001; // div 1 is max +#endif +#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) + TCCR2B = (TCCR2B & B11111000) | B00000001; // div 1 is max + TCCR4B = (TCCR4B & B11111000) | B00000001; // div 1 is max + TCCR5B = (TCCR5B & B11111000) | B00000001; // div 1 is max +#endif + } + analogWrite(brakePin,duty); +#endif +} unsigned int MotorDriver::raw2mA( int raw) { //DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale); return (int32_t)raw * senseFactorInternal / senseScale; @@ -481,11 +507,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // check how long we have been in this state unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT); if(checkFault()) { -#define INRUSH -#ifdef INRUSH - DCCTimer::DCCEXanalogWrite(brakePin,208); - DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500); -#endif + throttleInrush(true); lastBadSample = now; unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW; if ( mslpc < timeout) { @@ -494,9 +516,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { break; } DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait); -#ifdef INRUSH - DCCTimer::DCCEXanalogWrite(brakePin,0); -#endif + throttleInrush(false); setPower(POWERMODE::OVERLOAD); break; } @@ -513,9 +533,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned int maxmA=raw2mA(tripValue); DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"), trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait); -#ifdef INRUSH - DCCTimer::DCCEXanalogWrite(brakePin,0); -#endif + throttleInrush(false); setPower(POWERMODE::OVERLOAD); break; } @@ -526,9 +544,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned int mA=raw2mA(lastCurrent); DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA); } -#ifdef INRUSH - DCCTimer::DCCEXanalogWrite(brakePin,0); -#endif + throttleInrush(false); setPower(POWERMODE::ON); } break; diff --git a/MotorDriver.h b/MotorDriver.h index 8458de1bf..b8de0b0f7 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -149,6 +149,7 @@ class MotorDriver { }; inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); }; void setDCSignal(byte speedByte); + void throttleInrush(bool on); inline void detachDCSignal() { #if defined(__arm__) pinMode(brakePin, OUTPUT); From 94648ead2852a124034c1419f8d8be1830e1ab16 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 17 Jul 2023 02:31:00 +0200 Subject: [PATCH 855/870] versiontag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c12d38b9e..bdcfc16d8 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307142111Z" +#define GITHUB_SHA "devel-202307170030Z" From c2eb5f23b4e0f888097cc1d73f1acb53540460e6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 17 Jul 2023 09:42:39 +0200 Subject: [PATCH 856/870] restrict to relevant TRACK_MODE(s) --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index bdcfc16d8..1bec170e2 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307170030Z" +#define GITHUB_SHA "devel-202307170741Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index ee154f027..160d8ad6e 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -350,6 +350,8 @@ void MotorDriver::setDCSignal(byte speedcode) { void MotorDriver::throttleInrush(bool on) { if (brakePin == UNUSED_PIN) return; + if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT))) + return; byte duty = on ? 208 : 0; if (invertBrake) duty = 255-duty; From 2950ef010a3d19331a7a967b882e54f421c18bf8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 18 Jul 2023 01:25:38 +0200 Subject: [PATCH 857/870] diag --- GITHUB_SHA.h | 2 +- TrackManager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 1bec170e2..7cdc46f4e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307170741Z" +#define GITHUB_SHA "devel-202307172316Z" diff --git a/TrackManager.cpp b/TrackManager.cpp index a23140c2f..1725a8d50 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -190,7 +190,7 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; - //DIAG(F("Track=%c"),trackToSet+'A'); + DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); // DC tracks require a motorDriver that can set brake! if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) && !track[trackToSet]->brakeCanPWM()) { From 495bbf66bf972f69a1fd4ab816828a47ad228e5a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 25 Jul 2023 11:23:36 +0200 Subject: [PATCH 858/870] better variable name --- MotorDriver.cpp | 8 ++++---- TrackManager.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 160d8ad6e..444d502d9 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -501,9 +501,9 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { case POWERMODE::ALERT: { // set local flags that handle how much is output to diag (do not output duplicates) bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD); - bool newPowerMode = (powerMode != lastPowerMode); + bool powerModeChange = (powerMode != lastPowerMode); unsigned long now = micros(); - if (newPowerMode) + if (powerModeChange) lastBadSample = now; lastPowerMode = POWERMODE::ALERT; // check how long we have been in this state @@ -513,7 +513,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { lastBadSample = now; unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW; if ( mslpc < timeout) { - if (newPowerMode) + if (powerModeChange) DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout); break; } @@ -525,7 +525,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { if (checkCurrent(useProgLimit)) { lastBadSample = now; if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) { - if (newPowerMode) { + if (powerModeChange) { unsigned int mA=raw2mA(lastCurrent); DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA); } diff --git a/TrackManager.cpp b/TrackManager.cpp index 1725a8d50..858838345 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -190,7 +190,7 @@ void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) { if (trackToSet>lastTrack || track[trackToSet]==NULL) return false; - DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); + //DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); // DC tracks require a motorDriver that can set brake! if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) && !track[trackToSet]->brakeCanPWM()) { From 4c7e11ddc130b27e5490056fc1c7abab95bb2be2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 25 Jul 2023 11:30:08 +0200 Subject: [PATCH 859/870] version --- GITHUB_SHA.h | 2 +- version.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7cdc46f4e..76dcf440c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307172316Z" +#define GITHUB_SHA "devel-202307250927Z" diff --git a/version.h b/version.h index f1f1ca504..bfb3a0bf2 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.65" +#define VERSION "4.2.66" +// 4.2.66 - Throttle inrush current by applying PWM to brake pin when +// fault pin goes active // 4.2.65 - new config WIFI_FORCE_AP option // 4.2.63 - completely new overcurrent detection // - ESP32 protect from race in RMT code From 399030d8ae7a88e3334ab6ba49d4e4e926999bbb Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 25 Jul 2023 12:51:23 +0200 Subject: [PATCH 860/870] make variable frequency a compile option --- MotorDriver.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 444d502d9..5b51778d5 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -278,6 +278,7 @@ void MotorDriver::startCurrentFromHW() { #endif //ANALOG_READ_INTERRUPT #if defined(ARDUINO_ARCH_ESP32) +#ifdef VARIABLE_TONES uint16_t taurustones[28] = { 165, 175, 196, 220, 247, 262, 294, 330, 349, 392, 440, 494, @@ -286,6 +287,7 @@ uint16_t taurustones[28] = { 165, 175, 196, 220, 330, 284, 262, 247, 220, 196, 175, 165 }; #endif +#endif void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) return; @@ -304,11 +306,13 @@ void MotorDriver::setDCSignal(byte speedcode) { #if defined(ARDUINO_ARCH_ESP32) { int f = 131; +#ifdef VARIABLE_TONES if (tSpeed > 2) { if (tSpeed <= 58) { f = taurustones[ (tSpeed-2)/2 ] ; } } +#endif DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup } #endif From f754fe2fbfd726d2cdbf1462bd4caf376b59708c Mon Sep 17 00:00:00 2001 From: kempe63 <78020007+kempe63@users.noreply.github.com> Date: Sat, 29 Jul 2023 20:34:39 +0100 Subject: [PATCH 861/870] GPIO PCA9555 / TCA9555 support My 1st commit, be gentle --- IO_PCA9555.h | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 IO_PCA9555.h diff --git a/IO_PCA9555.h b/IO_PCA9555.h new file mode 100644 index 000000000..137e287b7 --- /dev/null +++ b/IO_PCA9555.h @@ -0,0 +1,112 @@ +/* + * © 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_pca9555_h +#define io_pca9555_h + +#include "IO_GPIOBase.h" +#include "FSH.h" + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments). + */ + +class PCA9555 : public GPIOBase { +public: + static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) { + new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin); + } + + // Constructor + PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) + : GPIOBase((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin) + { + requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), + outputBuffer, sizeof(outputBuffer)); + outputBuffer[0] = REG_INPUT_P0; + } + +private: + void _writeGpioPort() override { + I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8); + } + void _writePullups() override { + // Do nothing, pull-ups are always in place for input ports + // This function is here for HAL GPIOBase API compatibilitiy + + } + void _writePortModes() override { + // Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others. + // PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge + uint16_t temp = ~(_portMode & _portInUse); + I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8); + } + void _readGpioPort(bool immediate) override { + if (immediate) { + uint8_t buffer[2]; + I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0); + _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; + /* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h + * after a Read operation to the PCA9555 device or before reading from + * another device" + * Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data + * Issue not seen during testing, uncomment if needed + */ + //I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0); + } else { + // Queue new request + requestBlock.wait(); // Wait for preceding operation to complete + // Issue new request to read GPIO register + I2CManager.queueRequest(&requestBlock); + } + } + // This function is invoked when an I/O operation on the requestBlock completes. + void _processCompletion(uint8_t status) override { + if (status == I2C_STATUS_OK) + _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; + else + _portInputState = 0xffff; + } + + void _setupDevice() override { + // HAL API calls + _writePortModes(); + _writePullups(); + _writeGpioPort(); + } + + uint8_t inputBuffer[2]; + uint8_t outputBuffer[1]; + + + enum { + REG_INPUT_P0 = 0x00, + REG_INPUT_P1 = 0x01, + REG_OUTPUT_P0 = 0x02, + REG_OUTPUT_P1 = 0x03, + REG_POL_INV_P0 = 0x04, + REG_POL_INV_P1 = 0x05, + REG_CONF_P0 = 0x06, + REG_CONF_P1 = 0x07, + }; + +}; + +#endif From 415e756020707e1c0f1c145e0608d1493743d8dc Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 31 Jul 2023 16:51:25 +0800 Subject: [PATCH 862/870] More Nucleo variant defines --- DCCTimerSTM32.cpp | 4 ++-- WifiInterface.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index cc6054718..cffae408c 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -35,7 +35,7 @@ #endif #include "DIAG.h" -#if defined(ARDUINO_NUCLEO_F411RE) +#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) // Nucleo-64 boards don't have additional serial ports defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console @@ -52,7 +52,7 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE // On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins -#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 // Serial3 is defined to use USART3 by default, but is in fact used as the diag console diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 20dc2356f..7511af697 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -60,7 +60,7 @@ Stream * WifiInterface::wifiStream; #if defined(ARDUINO_ARCH_STM32) // Handle serial ports availability on STM32 for variants! // #undef NUM_SERIAL -#if defined(ARDUINO_NUCLEO_F411RE) +#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) #define NUM_SERIAL 3 #define SERIAL1 Serial1 #define SERIAL3 Serial6 @@ -68,9 +68,11 @@ Stream * WifiInterface::wifiStream; #define NUM_SERIAL 3 #define SERIAL1 Serial3 #define SERIAL3 Serial5 -#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) #define NUM_SERIAL 2 #define SERIAL1 Serial6 +#else +#warning This variant of Nucleo not yet explicitly supported #endif #endif From e3ac3a8ddf5f434838b39545b4c2314e227093d4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 2 Aug 2023 01:02:46 +0200 Subject: [PATCH 863/870] Protect Uno user from choosing DC(X) --- TrackManager.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index 858838345..b786181c3 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -192,11 +192,16 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr //DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode); // DC tracks require a motorDriver that can set brake! - if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) - && !track[trackToSet]->brakeCanPWM()) { - DIAG(F("Brake pin can't PWM: No DC")); - return false; - } + if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) { +#if defined(ARDUINO_AVR_UNO) + DIAG(F("Uno has no PWM timers available for DC")); + return false; +#endif + if (!track[trackToSet]->brakeCanPWM()) { + DIAG(F("Brake pin can't PWM: No DC")); + return false; + } + } #ifdef ARDUINO_ARCH_ESP32 // remove pin from MUX matrix and turn it off From 36d139268dacdc1128ca7a6c6ce956ca0eb3b11c Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 2 Aug 2023 01:05:31 +0200 Subject: [PATCH 864/870] AVR: Pin specific timer register seting for speed and mode when inrush throttling and for DC PWM --- MotorDriver.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 5b51778d5..d5dca1387 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -291,14 +291,39 @@ uint16_t taurustones[28] = { 165, 175, 196, 220, void MotorDriver::setDCSignal(byte speedcode) { if (brakePin == UNUSED_PIN) return; + switch(brakePin) { #if defined(ARDUINO_AVR_UNO) - TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz + // Not worth doin something here as: + // If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source. + // If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc. + // We are most likely not on pin 3 or 11 as no known motor shield has that as brake. #endif #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz - TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs - TCCR5B = (TCCR5B & B11111000) | B00000100; // same for timer 5 which is like timer 4 + case 9: + case 10: + // Timer2 (is differnet) + TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM + TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz + //DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B); + break; + case 6: + case 7: + case 8: + // Timer4 + TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit + TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz + break; + case 46: + case 45: + case 44: + // Timer5 + TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit + TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz + break; #endif + default: + break; + } // spedcoode is a dcc speed & direction byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127 byte tDir=speedcode & 0x80; @@ -368,14 +393,39 @@ void MotorDriver::throttleInrush(bool on) { } #else if(on){ + switch(brakePin) { #if defined(ARDUINO_AVR_UNO) - TCCR2B = (TCCR2B & B11111000) | B00000001; // div 1 is max + // Not worth doin something here as: + // If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source. + // If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc. + // We are most likely not on pin 3 or 11 as no known motor shield has that as brake. #endif #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - TCCR2B = (TCCR2B & B11111000) | B00000001; // div 1 is max - TCCR4B = (TCCR4B & B11111000) | B00000001; // div 1 is max - TCCR5B = (TCCR5B & B11111000) | B00000001; // div 1 is max + case 9: + case 10: + // Timer2 (is different) + TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM + TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max) + DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B); + break; + case 6: + case 7: + case 8: + // Timer4 + TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit + TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max) + break; + case 46: + case 45: + case 44: + // Timer5 + TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit + TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max) + break; #endif + default: + break; + } } analogWrite(brakePin,duty); #endif From df2e6512174398b48314903fde2b6c891603c59d Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 2 Aug 2023 01:12:32 +0200 Subject: [PATCH 865/870] version, compile warning --- DCCEXParser.cpp | 3 +++ GITHUB_SHA.h | 2 +- version.h | 6 +++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index aa635ae02..aaf733c5d 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -219,6 +219,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) { void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) { +#ifdef DISABLE_PROG + (void)ringStream; +#endif #ifndef DISABLE_EEPROM (void)EEPROM; // tell compiler not to warn this is unused #endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 76dcf440c..815023534 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202307250927Z" +#define GITHUB_SHA "devel-202308012308Z" diff --git a/version.h b/version.h index bfb3a0bf2..93236065a 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,11 @@ #include "StringFormatter.h" -#define VERSION "4.2.66" +#define VERSION "4.2.67" +// 4.2.67 - AVR: Pin specific timer register seting +// - Protect Uno user from choosing DC(X) +// - More Nucleo variant defines +// - GPIO PCA9555 / TCA9555 support // 4.2.66 - Throttle inrush current by applying PWM to brake pin when // fault pin goes active // 4.2.65 - new config WIFI_FORCE_AP option From a74d85e895dab6e9816b9d1bd33203667a527da6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 2 Aug 2023 10:00:43 +0200 Subject: [PATCH 866/870] Rename track mode OFF to NONE --- GITHUB_SHA.h | 2 +- MotorDriver.h | 4 ++-- TrackManager.cpp | 15 ++++++++------- version.h | 3 ++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 815023534..77a75c579 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202308012308Z" +#define GITHUB_SHA "devel-202308020800Z" diff --git a/MotorDriver.h b/MotorDriver.h index b8de0b0f7..21bceb6c7 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -28,7 +28,7 @@ #include "DCCTimer.h" // use powers of two so we can do logical and/or on the track modes in if clauses. -enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, +enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32}; #define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH @@ -290,7 +290,7 @@ class MotorDriver { static const int TRIP_CURRENT_PROG=250; unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; unsigned int power_good_counter = 0; - TRACK_MODE trackMode = TRACK_MODE_OFF; // we assume off at startup + TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup }; #endif diff --git a/TrackManager.cpp b/TrackManager.cpp index b786181c3..db5b6b966 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -38,6 +38,7 @@ const int16_t HASH_KEYWORD_PROG = -29718; #endif const int16_t HASH_KEYWORD_MAIN = 11339; const int16_t HASH_KEYWORD_OFF = 22479; +const int16_t HASH_KEYWORD_NONE = -26550; const int16_t HASH_KEYWORD_DC = 2183; const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal @@ -140,7 +141,7 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) { track[t]=driver; if (driver) { track[t]->setPower(POWERMODE::OFF); - track[t]->setMode(TRACK_MODE_OFF); + track[t]->setMode(TRACK_MODE_NONE); track[t]->setTrackLetter('A'+t); lastTrack=t; } @@ -224,7 +225,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr FOR_EACH_TRACK(t) if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) { track[t]->setPower(POWERMODE::OFF); - track[t]->setMode(TRACK_MODE_OFF); + track[t]->setMode(TRACK_MODE_NONE); track[t]->makeProgTrack(false); // revoke prog track special handling streamTrackState(NULL,t); } @@ -332,8 +333,8 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[]) return setTrackMode(p[0],TRACK_MODE_PROG); #endif - if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF> - return setTrackMode(p[0],TRACK_MODE_OFF); + if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE> + return setTrackMode(p[0],TRACK_MODE_NONE); if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT> return setTrackMode(p[0],TRACK_MODE_EXT); @@ -360,8 +361,8 @@ void TrackManager::streamTrackState(Print* stream, byte t) { format=F("<= %c PROG>\n"); break; #endif - case TRACK_MODE_OFF: - format=F("<= %c OFF>\n"); + case TRACK_MODE_NONE: + format=F("<= %c NONE>\n"); break; case TRACK_MODE_EXT: format=F("<= %c EXT>\n"); @@ -443,7 +444,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) { driver->setBrake(false); driver->setPower(mode); break; - case TRACK_MODE_OFF: + case TRACK_MODE_NONE: break; } } diff --git a/version.h b/version.h index 93236065a..465f6846f 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.67" +#define VERSION "4.2.68" +// 4.2.68 - Rename track mode OFF to NONE // 4.2.67 - AVR: Pin specific timer register seting // - Protect Uno user from choosing DC(X) // - More Nucleo variant defines From f2be3aeac325a9ffbacf700c8b444d2693d2bdf5 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 4 Aug 2023 14:45:05 +0200 Subject: [PATCH 867/870] Make work in DC mode --- GITHUB_SHA.h | 2 +- TrackManager.cpp | 2 +- version.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 77a75c579..a889ba7c7 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202308020800Z" +#define GITHUB_SHA "devel-202308041244Z" diff --git a/TrackManager.cpp b/TrackManager.cpp index db5b6b966..0f6923529 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -182,7 +182,7 @@ void TrackManager::setPROGSignal( bool on) { // with interrupts turned off around the critical section void TrackManager::setDCSignal(int16_t cab, byte speedbyte) { FOR_EACH_TRACK(t) { - if (trackDCAddr[t]!=cab) continue; + if (trackDCAddr[t]!=cab && cab != 0) continue; if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte); else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128); } diff --git a/version.h b/version.h index 465f6846f..eb2d0682f 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.68" +#define VERSION "4.2.69" +// 4.2.69 - Bugfix: Make work in DC mode // 4.2.68 - Rename track mode OFF to NONE // 4.2.67 - AVR: Pin specific timer register seting // - Protect Uno user from choosing DC(X) From 3bddf4dfd1cf54c78d299682e790cde2e4f5bad7 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 7 Aug 2023 19:45:45 +0200 Subject: [PATCH 868/870] Make 4.2.69 the 5.0.0 release --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a889ba7c7..9d4f5f665 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202308041244Z" +#define GITHUB_SHA "master-202308071744Z" diff --git a/version.h b/version.h index eb2d0682f..3eafa76bf 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.69" +#define VERSION "5.0.0" +// 5.0.0 - Make 4.2.69 the 5.0.0 release // 4.2.69 - Bugfix: Make work in DC mode // 4.2.68 - Rename track mode OFF to NONE // 4.2.67 - AVR: Pin specific timer register seting From fd58a749ef2ac71b85539b2e9b18fecaa48c5441 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 7 Aug 2023 19:45:45 +0200 Subject: [PATCH 869/870] Committing a SHA --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index a889ba7c7..ae005b527 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202308041244Z" +#define GITHUB_SHA "3bddf4d" diff --git a/version.h b/version.h index eb2d0682f..3eafa76bf 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.69" +#define VERSION "5.0.0" +// 5.0.0 - Make 4.2.69 the 5.0.0 release // 4.2.69 - Bugfix: Make work in DC mode // 4.2.68 - Rename track mode OFF to NONE // 4.2.67 - AVR: Pin specific timer register seting From 04b5255bda6dd8f66ec3bfbb95e3292aca55be88 Mon Sep 17 00:00:00 2001 From: Kcsmith0708 Date: Wed, 9 Aug 2023 19:56:36 -0400 Subject: [PATCH 870/870] Update myHal.cpp_example.txt Corrected HALDisplay::create(2, 0x27, 20, 4); --- myHal.cpp_example.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/myHal.cpp_example.txt b/myHal.cpp_example.txt index d93ea5c43..5e9fec44a 100644 --- a/myHal.cpp_example.txt +++ b/myHal.cpp_example.txt @@ -51,7 +51,7 @@ void halSetup() { // Create a 20x4 LCD display device as display number 2 // (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")'). - // HALDisplay(2, 0x27, 20, 4); + // HALDisplay::create(2, 0x27, 20, 4); //=======================================================================